From 2c3c1048746a4622d8c89a29670120dc8fab93c4 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:49:45 +0200 Subject: Adding upstream version 6.1.76. Signed-off-by: Daniel Baumann --- drivers/gpu/drm/i915/display/dvo_ch7017.c | 415 + drivers/gpu/drm/i915/display/dvo_ch7xxx.c | 367 + drivers/gpu/drm/i915/display/dvo_ivch.c | 503 ++ drivers/gpu/drm/i915/display/dvo_ns2501.c | 710 ++ drivers/gpu/drm/i915/display/dvo_sil164.c | 280 + drivers/gpu/drm/i915/display/dvo_tfp410.c | 319 + drivers/gpu/drm/i915/display/g4x_dp.c | 1404 +++ drivers/gpu/drm/i915/display/g4x_dp.h | 30 + drivers/gpu/drm/i915/display/g4x_hdmi.c | 606 ++ drivers/gpu/drm/i915/display/g4x_hdmi.h | 19 + drivers/gpu/drm/i915/display/hsw_ips.c | 271 + drivers/gpu/drm/i915/display/hsw_ips.h | 26 + drivers/gpu/drm/i915/display/i9xx_plane.c | 1056 +++ drivers/gpu/drm/i915/display/i9xx_plane.h | 28 + drivers/gpu/drm/i915/display/icl_dsi.c | 2127 +++++ drivers/gpu/drm/i915/display/icl_dsi.h | 15 + drivers/gpu/drm/i915/display/icl_dsi_regs.h | 342 + drivers/gpu/drm/i915/display/intel_acpi.c | 360 + drivers/gpu/drm/i915/display/intel_acpi.h | 31 + drivers/gpu/drm/i915/display/intel_atomic.c | 351 + drivers/gpu/drm/i915/display/intel_atomic.h | 55 + drivers/gpu/drm/i915/display/intel_atomic_plane.c | 1120 +++ drivers/gpu/drm/i915/display/intel_atomic_plane.h | 69 + drivers/gpu/drm/i915/display/intel_audio.c | 1410 +++ drivers/gpu/drm/i915/display/intel_audio.h | 26 + drivers/gpu/drm/i915/display/intel_audio_regs.h | 160 + drivers/gpu/drm/i915/display/intel_backlight.c | 1794 ++++ drivers/gpu/drm/i915/display/intel_backlight.h | 52 + .../gpu/drm/i915/display/intel_backlight_regs.h | 124 + drivers/gpu/drm/i915/display/intel_bios.c | 3790 ++++++++ drivers/gpu/drm/i915/display/intel_bios.h | 280 + drivers/gpu/drm/i915/display/intel_bw.c | 1203 +++ drivers/gpu/drm/i915/display/intel_bw.h | 72 + drivers/gpu/drm/i915/display/intel_cdclk.c | 3283 +++++++ drivers/gpu/drm/i915/display/intel_cdclk.h | 86 + drivers/gpu/drm/i915/display/intel_color.c | 2295 +++++ drivers/gpu/drm/i915/display/intel_color.h | 26 + drivers/gpu/drm/i915/display/intel_combo_phy.c | 439 + drivers/gpu/drm/i915/display/intel_combo_phy.h | 20 + .../gpu/drm/i915/display/intel_combo_phy_regs.h | 162 + drivers/gpu/drm/i915/display/intel_connector.c | 295 + drivers/gpu/drm/i915/display/intel_connector.h | 36 + drivers/gpu/drm/i915/display/intel_crt.c | 1124 +++ drivers/gpu/drm/i915/display/intel_crt.h | 20 + drivers/gpu/drm/i915/display/intel_crtc.c | 705 ++ drivers/gpu/drm/i915/display/intel_crtc.h | 39 + .../gpu/drm/i915/display/intel_crtc_state_dump.c | 315 + .../gpu/drm/i915/display/intel_crtc_state_dump.h | 16 + drivers/gpu/drm/i915/display/intel_cursor.c | 828 ++ drivers/gpu/drm/i915/display/intel_cursor.h | 17 + drivers/gpu/drm/i915/display/intel_ddi.c | 4525 ++++++++++ drivers/gpu/drm/i915/display/intel_ddi.h | 73 + drivers/gpu/drm/i915/display/intel_ddi_buf_trans.c | 1662 ++++ drivers/gpu/drm/i915/display/intel_ddi_buf_trans.h | 73 + drivers/gpu/drm/i915/display/intel_de.h | 84 + drivers/gpu/drm/i915/display/intel_display.c | 9140 ++++++++++++++++++++ drivers/gpu/drm/i915/display/intel_display.h | 721 ++ drivers/gpu/drm/i915/display/intel_display_core.h | 426 + .../gpu/drm/i915/display/intel_display_debugfs.c | 2254 +++++ .../gpu/drm/i915/display/intel_display_debugfs.h | 23 + drivers/gpu/drm/i915/display/intel_display_power.c | 2489 ++++++ drivers/gpu/drm/i915/display/intel_display_power.h | 279 + .../gpu/drm/i915/display/intel_display_power_map.c | 1614 ++++ .../gpu/drm/i915/display/intel_display_power_map.h | 14 + .../drm/i915/display/intel_display_power_well.c | 1956 +++++ .../drm/i915/display/intel_display_power_well.h | 177 + drivers/gpu/drm/i915/display/intel_display_trace.c | 9 + drivers/gpu/drm/i915/display/intel_display_trace.h | 589 ++ drivers/gpu/drm/i915/display/intel_display_types.h | 2074 +++++ drivers/gpu/drm/i915/display/intel_dkl_phy.c | 109 + drivers/gpu/drm/i915/display/intel_dkl_phy.h | 24 + drivers/gpu/drm/i915/display/intel_dmc.c | 1132 +++ drivers/gpu/drm/i915/display/intel_dmc.h | 61 + drivers/gpu/drm/i915/display/intel_dmc_regs.h | 89 + drivers/gpu/drm/i915/display/intel_dp.c | 5475 ++++++++++++ drivers/gpu/drm/i915/display/intel_dp.h | 124 + drivers/gpu/drm/i915/display/intel_dp_aux.c | 771 ++ drivers/gpu/drm/i915/display/intel_dp_aux.h | 14 + .../gpu/drm/i915/display/intel_dp_aux_backlight.c | 519 ++ .../gpu/drm/i915/display/intel_dp_aux_backlight.h | 13 + drivers/gpu/drm/i915/display/intel_dp_hdcp.c | 823 ++ drivers/gpu/drm/i915/display/intel_dp_hdcp.h | 15 + .../gpu/drm/i915/display/intel_dp_link_training.c | 1456 ++++ .../gpu/drm/i915/display/intel_dp_link_training.h | 42 + drivers/gpu/drm/i915/display/intel_dp_mst.c | 1076 +++ drivers/gpu/drm/i915/display/intel_dp_mst.h | 26 + drivers/gpu/drm/i915/display/intel_dpio_phy.c | 1106 +++ drivers/gpu/drm/i915/display/intel_dpio_phy.h | 59 + drivers/gpu/drm/i915/display/intel_dpll.c | 2061 +++++ drivers/gpu/drm/i915/display/intel_dpll.h | 47 + drivers/gpu/drm/i915/display/intel_dpll_mgr.c | 4564 ++++++++++ drivers/gpu/drm/i915/display/intel_dpll_mgr.h | 376 + drivers/gpu/drm/i915/display/intel_dpt.c | 316 + drivers/gpu/drm/i915/display/intel_dpt.h | 23 + drivers/gpu/drm/i915/display/intel_drrs.c | 299 + drivers/gpu/drm/i915/display/intel_drrs.h | 28 + drivers/gpu/drm/i915/display/intel_dsb.c | 363 + drivers/gpu/drm/i915/display/intel_dsb.h | 23 + drivers/gpu/drm/i915/display/intel_dsi.c | 114 + drivers/gpu/drm/i915/display/intel_dsi.h | 177 + .../gpu/drm/i915/display/intel_dsi_dcs_backlight.c | 200 + .../gpu/drm/i915/display/intel_dsi_dcs_backlight.h | 13 + drivers/gpu/drm/i915/display/intel_dsi_vbt.c | 1037 +++ drivers/gpu/drm/i915/display/intel_dsi_vbt.h | 21 + drivers/gpu/drm/i915/display/intel_dvo.c | 550 ++ drivers/gpu/drm/i915/display/intel_dvo.h | 13 + drivers/gpu/drm/i915/display/intel_dvo_dev.h | 140 + drivers/gpu/drm/i915/display/intel_fb.c | 2081 +++++ drivers/gpu/drm/i915/display/intel_fb.h | 96 + drivers/gpu/drm/i915/display/intel_fb_pin.c | 303 + drivers/gpu/drm/i915/display/intel_fb_pin.h | 28 + drivers/gpu/drm/i915/display/intel_fbc.c | 1846 ++++ drivers/gpu/drm/i915/display/intel_fbc.h | 49 + drivers/gpu/drm/i915/display/intel_fbdev.c | 733 ++ drivers/gpu/drm/i915/display/intel_fbdev.h | 60 + drivers/gpu/drm/i915/display/intel_fdi.c | 1076 +++ drivers/gpu/drm/i915/display/intel_fdi.h | 40 + drivers/gpu/drm/i915/display/intel_fifo_underrun.c | 511 ++ drivers/gpu/drm/i915/display/intel_fifo_underrun.h | 27 + drivers/gpu/drm/i915/display/intel_frontbuffer.c | 328 + drivers/gpu/drm/i915/display/intel_frontbuffer.h | 170 + drivers/gpu/drm/i915/display/intel_global_state.c | 257 + drivers/gpu/drm/i915/display/intel_global_state.h | 90 + drivers/gpu/drm/i915/display/intel_gmbus.c | 1003 +++ drivers/gpu/drm/i915/display/intel_gmbus.h | 49 + drivers/gpu/drm/i915/display/intel_gmbus_regs.h | 81 + drivers/gpu/drm/i915/display/intel_hdcp.c | 2586 ++++++ drivers/gpu/drm/i915/display/intel_hdcp.h | 46 + drivers/gpu/drm/i915/display/intel_hdcp_regs.h | 270 + drivers/gpu/drm/i915/display/intel_hdmi.c | 3237 +++++++ drivers/gpu/drm/i915/display/intel_hdmi.h | 58 + drivers/gpu/drm/i915/display/intel_hotplug.c | 769 ++ drivers/gpu/drm/i915/display/intel_hotplug.h | 32 + drivers/gpu/drm/i915/display/intel_lpe_audio.c | 368 + drivers/gpu/drm/i915/display/intel_lpe_audio.h | 22 + drivers/gpu/drm/i915/display/intel_lspcon.c | 716 ++ drivers/gpu/drm/i915/display/intel_lspcon.h | 47 + drivers/gpu/drm/i915/display/intel_lvds.c | 1019 +++ drivers/gpu/drm/i915/display/intel_lvds.h | 22 + drivers/gpu/drm/i915/display/intel_modeset_setup.c | 735 ++ drivers/gpu/drm/i915/display/intel_modeset_setup.h | 15 + .../gpu/drm/i915/display/intel_modeset_verify.c | 246 + .../gpu/drm/i915/display/intel_modeset_verify.h | 21 + drivers/gpu/drm/i915/display/intel_opregion.c | 1250 +++ drivers/gpu/drm/i915/display/intel_opregion.h | 139 + drivers/gpu/drm/i915/display/intel_overlay.c | 1533 ++++ drivers/gpu/drm/i915/display/intel_overlay.h | 29 + drivers/gpu/drm/i915/display/intel_panel.c | 686 ++ drivers/gpu/drm/i915/display/intel_panel.h | 54 + drivers/gpu/drm/i915/display/intel_pch_display.c | 650 ++ drivers/gpu/drm/i915/display/intel_pch_display.h | 45 + drivers/gpu/drm/i915/display/intel_pch_refclk.c | 679 ++ drivers/gpu/drm/i915/display/intel_pch_refclk.h | 22 + drivers/gpu/drm/i915/display/intel_pipe_crc.c | 672 ++ drivers/gpu/drm/i915/display/intel_pipe_crc.h | 38 + drivers/gpu/drm/i915/display/intel_plane_initial.c | 323 + drivers/gpu/drm/i915/display/intel_plane_initial.h | 13 + drivers/gpu/drm/i915/display/intel_pps.c | 1554 ++++ drivers/gpu/drm/i915/display/intel_pps.h | 56 + drivers/gpu/drm/i915/display/intel_psr.c | 2656 ++++++ drivers/gpu/drm/i915/display/intel_psr.h | 61 + drivers/gpu/drm/i915/display/intel_qp_tables.c | 309 + drivers/gpu/drm/i915/display/intel_qp_tables.h | 14 + drivers/gpu/drm/i915/display/intel_quirks.c | 230 + drivers/gpu/drm/i915/display/intel_quirks.h | 25 + drivers/gpu/drm/i915/display/intel_sdvo.c | 3446 ++++++++ drivers/gpu/drm/i915/display/intel_sdvo.h | 22 + drivers/gpu/drm/i915/display/intel_sdvo_regs.h | 741 ++ drivers/gpu/drm/i915/display/intel_snps_phy.c | 2036 +++++ drivers/gpu/drm/i915/display/intel_snps_phy.h | 38 + drivers/gpu/drm/i915/display/intel_snps_phy_regs.h | 75 + drivers/gpu/drm/i915/display/intel_sprite.c | 1848 ++++ drivers/gpu/drm/i915/display/intel_sprite.h | 50 + drivers/gpu/drm/i915/display/intel_tc.c | 1007 +++ drivers/gpu/drm/i915/display/intel_tc.h | 41 + drivers/gpu/drm/i915/display/intel_tc_phy_regs.h | 280 + drivers/gpu/drm/i915/display/intel_tv.c | 2020 +++++ drivers/gpu/drm/i915/display/intel_tv.h | 13 + drivers/gpu/drm/i915/display/intel_vbt_defs.h | 1067 +++ drivers/gpu/drm/i915/display/intel_vdsc.c | 1218 +++ drivers/gpu/drm/i915/display/intel_vdsc.h | 30 + drivers/gpu/drm/i915/display/intel_vga.c | 167 + drivers/gpu/drm/i915/display/intel_vga.h | 18 + drivers/gpu/drm/i915/display/intel_vrr.c | 262 + drivers/gpu/drm/i915/display/intel_vrr.h | 33 + drivers/gpu/drm/i915/display/skl_scaler.c | 847 ++ drivers/gpu/drm/i915/display/skl_scaler.h | 35 + drivers/gpu/drm/i915/display/skl_universal_plane.c | 2525 ++++++ drivers/gpu/drm/i915/display/skl_universal_plane.h | 35 + drivers/gpu/drm/i915/display/skl_watermark.c | 3575 ++++++++ drivers/gpu/drm/i915/display/skl_watermark.h | 80 + drivers/gpu/drm/i915/display/vlv_dsi.c | 2015 +++++ drivers/gpu/drm/i915/display/vlv_dsi.h | 19 + drivers/gpu/drm/i915/display/vlv_dsi_pll.c | 624 ++ drivers/gpu/drm/i915/display/vlv_dsi_pll.h | 38 + drivers/gpu/drm/i915/display/vlv_dsi_pll_regs.h | 109 + drivers/gpu/drm/i915/display/vlv_dsi_regs.h | 482 ++ 197 files changed, 135473 insertions(+) create mode 100644 drivers/gpu/drm/i915/display/dvo_ch7017.c create mode 100644 drivers/gpu/drm/i915/display/dvo_ch7xxx.c create mode 100644 drivers/gpu/drm/i915/display/dvo_ivch.c create mode 100644 drivers/gpu/drm/i915/display/dvo_ns2501.c create mode 100644 drivers/gpu/drm/i915/display/dvo_sil164.c create mode 100644 drivers/gpu/drm/i915/display/dvo_tfp410.c create mode 100644 drivers/gpu/drm/i915/display/g4x_dp.c create mode 100644 drivers/gpu/drm/i915/display/g4x_dp.h create mode 100644 drivers/gpu/drm/i915/display/g4x_hdmi.c create mode 100644 drivers/gpu/drm/i915/display/g4x_hdmi.h create mode 100644 drivers/gpu/drm/i915/display/hsw_ips.c create mode 100644 drivers/gpu/drm/i915/display/hsw_ips.h create mode 100644 drivers/gpu/drm/i915/display/i9xx_plane.c create mode 100644 drivers/gpu/drm/i915/display/i9xx_plane.h create mode 100644 drivers/gpu/drm/i915/display/icl_dsi.c create mode 100644 drivers/gpu/drm/i915/display/icl_dsi.h create mode 100644 drivers/gpu/drm/i915/display/icl_dsi_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_acpi.c create mode 100644 drivers/gpu/drm/i915/display/intel_acpi.h create mode 100644 drivers/gpu/drm/i915/display/intel_atomic.c create mode 100644 drivers/gpu/drm/i915/display/intel_atomic.h create mode 100644 drivers/gpu/drm/i915/display/intel_atomic_plane.c create mode 100644 drivers/gpu/drm/i915/display/intel_atomic_plane.h create mode 100644 drivers/gpu/drm/i915/display/intel_audio.c create mode 100644 drivers/gpu/drm/i915/display/intel_audio.h create mode 100644 drivers/gpu/drm/i915/display/intel_audio_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_backlight.c create mode 100644 drivers/gpu/drm/i915/display/intel_backlight.h create mode 100644 drivers/gpu/drm/i915/display/intel_backlight_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_bios.c create mode 100644 drivers/gpu/drm/i915/display/intel_bios.h create mode 100644 drivers/gpu/drm/i915/display/intel_bw.c create mode 100644 drivers/gpu/drm/i915/display/intel_bw.h create mode 100644 drivers/gpu/drm/i915/display/intel_cdclk.c create mode 100644 drivers/gpu/drm/i915/display/intel_cdclk.h create mode 100644 drivers/gpu/drm/i915/display/intel_color.c create mode 100644 drivers/gpu/drm/i915/display/intel_color.h create mode 100644 drivers/gpu/drm/i915/display/intel_combo_phy.c create mode 100644 drivers/gpu/drm/i915/display/intel_combo_phy.h create mode 100644 drivers/gpu/drm/i915/display/intel_combo_phy_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_connector.c create mode 100644 drivers/gpu/drm/i915/display/intel_connector.h create mode 100644 drivers/gpu/drm/i915/display/intel_crt.c create mode 100644 drivers/gpu/drm/i915/display/intel_crt.h create mode 100644 drivers/gpu/drm/i915/display/intel_crtc.c create mode 100644 drivers/gpu/drm/i915/display/intel_crtc.h create mode 100644 drivers/gpu/drm/i915/display/intel_crtc_state_dump.c create mode 100644 drivers/gpu/drm/i915/display/intel_crtc_state_dump.h create mode 100644 drivers/gpu/drm/i915/display/intel_cursor.c create mode 100644 drivers/gpu/drm/i915/display/intel_cursor.h create mode 100644 drivers/gpu/drm/i915/display/intel_ddi.c create mode 100644 drivers/gpu/drm/i915/display/intel_ddi.h create mode 100644 drivers/gpu/drm/i915/display/intel_ddi_buf_trans.c create mode 100644 drivers/gpu/drm/i915/display/intel_ddi_buf_trans.h create mode 100644 drivers/gpu/drm/i915/display/intel_de.h create mode 100644 drivers/gpu/drm/i915/display/intel_display.c create mode 100644 drivers/gpu/drm/i915/display/intel_display.h create mode 100644 drivers/gpu/drm/i915/display/intel_display_core.h create mode 100644 drivers/gpu/drm/i915/display/intel_display_debugfs.c create mode 100644 drivers/gpu/drm/i915/display/intel_display_debugfs.h create mode 100644 drivers/gpu/drm/i915/display/intel_display_power.c create mode 100644 drivers/gpu/drm/i915/display/intel_display_power.h create mode 100644 drivers/gpu/drm/i915/display/intel_display_power_map.c create mode 100644 drivers/gpu/drm/i915/display/intel_display_power_map.h create mode 100644 drivers/gpu/drm/i915/display/intel_display_power_well.c create mode 100644 drivers/gpu/drm/i915/display/intel_display_power_well.h create mode 100644 drivers/gpu/drm/i915/display/intel_display_trace.c create mode 100644 drivers/gpu/drm/i915/display/intel_display_trace.h create mode 100644 drivers/gpu/drm/i915/display/intel_display_types.h create mode 100644 drivers/gpu/drm/i915/display/intel_dkl_phy.c create mode 100644 drivers/gpu/drm/i915/display/intel_dkl_phy.h create mode 100644 drivers/gpu/drm/i915/display/intel_dmc.c create mode 100644 drivers/gpu/drm/i915/display/intel_dmc.h create mode 100644 drivers/gpu/drm/i915/display/intel_dmc_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_dp.c create mode 100644 drivers/gpu/drm/i915/display/intel_dp.h create mode 100644 drivers/gpu/drm/i915/display/intel_dp_aux.c create mode 100644 drivers/gpu/drm/i915/display/intel_dp_aux.h create mode 100644 drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c create mode 100644 drivers/gpu/drm/i915/display/intel_dp_aux_backlight.h create mode 100644 drivers/gpu/drm/i915/display/intel_dp_hdcp.c create mode 100644 drivers/gpu/drm/i915/display/intel_dp_hdcp.h create mode 100644 drivers/gpu/drm/i915/display/intel_dp_link_training.c create mode 100644 drivers/gpu/drm/i915/display/intel_dp_link_training.h create mode 100644 drivers/gpu/drm/i915/display/intel_dp_mst.c create mode 100644 drivers/gpu/drm/i915/display/intel_dp_mst.h create mode 100644 drivers/gpu/drm/i915/display/intel_dpio_phy.c create mode 100644 drivers/gpu/drm/i915/display/intel_dpio_phy.h create mode 100644 drivers/gpu/drm/i915/display/intel_dpll.c create mode 100644 drivers/gpu/drm/i915/display/intel_dpll.h create mode 100644 drivers/gpu/drm/i915/display/intel_dpll_mgr.c create mode 100644 drivers/gpu/drm/i915/display/intel_dpll_mgr.h create mode 100644 drivers/gpu/drm/i915/display/intel_dpt.c create mode 100644 drivers/gpu/drm/i915/display/intel_dpt.h create mode 100644 drivers/gpu/drm/i915/display/intel_drrs.c create mode 100644 drivers/gpu/drm/i915/display/intel_drrs.h create mode 100644 drivers/gpu/drm/i915/display/intel_dsb.c create mode 100644 drivers/gpu/drm/i915/display/intel_dsb.h create mode 100644 drivers/gpu/drm/i915/display/intel_dsi.c create mode 100644 drivers/gpu/drm/i915/display/intel_dsi.h create mode 100644 drivers/gpu/drm/i915/display/intel_dsi_dcs_backlight.c create mode 100644 drivers/gpu/drm/i915/display/intel_dsi_dcs_backlight.h create mode 100644 drivers/gpu/drm/i915/display/intel_dsi_vbt.c create mode 100644 drivers/gpu/drm/i915/display/intel_dsi_vbt.h create mode 100644 drivers/gpu/drm/i915/display/intel_dvo.c create mode 100644 drivers/gpu/drm/i915/display/intel_dvo.h create mode 100644 drivers/gpu/drm/i915/display/intel_dvo_dev.h create mode 100644 drivers/gpu/drm/i915/display/intel_fb.c create mode 100644 drivers/gpu/drm/i915/display/intel_fb.h create mode 100644 drivers/gpu/drm/i915/display/intel_fb_pin.c create mode 100644 drivers/gpu/drm/i915/display/intel_fb_pin.h create mode 100644 drivers/gpu/drm/i915/display/intel_fbc.c create mode 100644 drivers/gpu/drm/i915/display/intel_fbc.h create mode 100644 drivers/gpu/drm/i915/display/intel_fbdev.c create mode 100644 drivers/gpu/drm/i915/display/intel_fbdev.h create mode 100644 drivers/gpu/drm/i915/display/intel_fdi.c create mode 100644 drivers/gpu/drm/i915/display/intel_fdi.h create mode 100644 drivers/gpu/drm/i915/display/intel_fifo_underrun.c create mode 100644 drivers/gpu/drm/i915/display/intel_fifo_underrun.h create mode 100644 drivers/gpu/drm/i915/display/intel_frontbuffer.c create mode 100644 drivers/gpu/drm/i915/display/intel_frontbuffer.h create mode 100644 drivers/gpu/drm/i915/display/intel_global_state.c create mode 100644 drivers/gpu/drm/i915/display/intel_global_state.h create mode 100644 drivers/gpu/drm/i915/display/intel_gmbus.c create mode 100644 drivers/gpu/drm/i915/display/intel_gmbus.h create mode 100644 drivers/gpu/drm/i915/display/intel_gmbus_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_hdcp.c create mode 100644 drivers/gpu/drm/i915/display/intel_hdcp.h create mode 100644 drivers/gpu/drm/i915/display/intel_hdcp_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_hdmi.c create mode 100644 drivers/gpu/drm/i915/display/intel_hdmi.h create mode 100644 drivers/gpu/drm/i915/display/intel_hotplug.c create mode 100644 drivers/gpu/drm/i915/display/intel_hotplug.h create mode 100644 drivers/gpu/drm/i915/display/intel_lpe_audio.c create mode 100644 drivers/gpu/drm/i915/display/intel_lpe_audio.h create mode 100644 drivers/gpu/drm/i915/display/intel_lspcon.c create mode 100644 drivers/gpu/drm/i915/display/intel_lspcon.h create mode 100644 drivers/gpu/drm/i915/display/intel_lvds.c create mode 100644 drivers/gpu/drm/i915/display/intel_lvds.h create mode 100644 drivers/gpu/drm/i915/display/intel_modeset_setup.c create mode 100644 drivers/gpu/drm/i915/display/intel_modeset_setup.h create mode 100644 drivers/gpu/drm/i915/display/intel_modeset_verify.c create mode 100644 drivers/gpu/drm/i915/display/intel_modeset_verify.h create mode 100644 drivers/gpu/drm/i915/display/intel_opregion.c create mode 100644 drivers/gpu/drm/i915/display/intel_opregion.h create mode 100644 drivers/gpu/drm/i915/display/intel_overlay.c create mode 100644 drivers/gpu/drm/i915/display/intel_overlay.h create mode 100644 drivers/gpu/drm/i915/display/intel_panel.c create mode 100644 drivers/gpu/drm/i915/display/intel_panel.h create mode 100644 drivers/gpu/drm/i915/display/intel_pch_display.c create mode 100644 drivers/gpu/drm/i915/display/intel_pch_display.h create mode 100644 drivers/gpu/drm/i915/display/intel_pch_refclk.c create mode 100644 drivers/gpu/drm/i915/display/intel_pch_refclk.h create mode 100644 drivers/gpu/drm/i915/display/intel_pipe_crc.c create mode 100644 drivers/gpu/drm/i915/display/intel_pipe_crc.h create mode 100644 drivers/gpu/drm/i915/display/intel_plane_initial.c create mode 100644 drivers/gpu/drm/i915/display/intel_plane_initial.h create mode 100644 drivers/gpu/drm/i915/display/intel_pps.c create mode 100644 drivers/gpu/drm/i915/display/intel_pps.h create mode 100644 drivers/gpu/drm/i915/display/intel_psr.c create mode 100644 drivers/gpu/drm/i915/display/intel_psr.h create mode 100644 drivers/gpu/drm/i915/display/intel_qp_tables.c create mode 100644 drivers/gpu/drm/i915/display/intel_qp_tables.h create mode 100644 drivers/gpu/drm/i915/display/intel_quirks.c create mode 100644 drivers/gpu/drm/i915/display/intel_quirks.h create mode 100644 drivers/gpu/drm/i915/display/intel_sdvo.c create mode 100644 drivers/gpu/drm/i915/display/intel_sdvo.h create mode 100644 drivers/gpu/drm/i915/display/intel_sdvo_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_snps_phy.c create mode 100644 drivers/gpu/drm/i915/display/intel_snps_phy.h create mode 100644 drivers/gpu/drm/i915/display/intel_snps_phy_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_sprite.c create mode 100644 drivers/gpu/drm/i915/display/intel_sprite.h create mode 100644 drivers/gpu/drm/i915/display/intel_tc.c create mode 100644 drivers/gpu/drm/i915/display/intel_tc.h create mode 100644 drivers/gpu/drm/i915/display/intel_tc_phy_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_tv.c create mode 100644 drivers/gpu/drm/i915/display/intel_tv.h create mode 100644 drivers/gpu/drm/i915/display/intel_vbt_defs.h create mode 100644 drivers/gpu/drm/i915/display/intel_vdsc.c create mode 100644 drivers/gpu/drm/i915/display/intel_vdsc.h create mode 100644 drivers/gpu/drm/i915/display/intel_vga.c create mode 100644 drivers/gpu/drm/i915/display/intel_vga.h create mode 100644 drivers/gpu/drm/i915/display/intel_vrr.c create mode 100644 drivers/gpu/drm/i915/display/intel_vrr.h create mode 100644 drivers/gpu/drm/i915/display/skl_scaler.c create mode 100644 drivers/gpu/drm/i915/display/skl_scaler.h create mode 100644 drivers/gpu/drm/i915/display/skl_universal_plane.c create mode 100644 drivers/gpu/drm/i915/display/skl_universal_plane.h create mode 100644 drivers/gpu/drm/i915/display/skl_watermark.c create mode 100644 drivers/gpu/drm/i915/display/skl_watermark.h create mode 100644 drivers/gpu/drm/i915/display/vlv_dsi.c create mode 100644 drivers/gpu/drm/i915/display/vlv_dsi.h create mode 100644 drivers/gpu/drm/i915/display/vlv_dsi_pll.c create mode 100644 drivers/gpu/drm/i915/display/vlv_dsi_pll.h create mode 100644 drivers/gpu/drm/i915/display/vlv_dsi_pll_regs.h create mode 100644 drivers/gpu/drm/i915/display/vlv_dsi_regs.h (limited to 'drivers/gpu/drm/i915/display') diff --git a/drivers/gpu/drm/i915/display/dvo_ch7017.c b/drivers/gpu/drm/i915/display/dvo_ch7017.c new file mode 100644 index 000000000..0589994dd --- /dev/null +++ b/drivers/gpu/drm/i915/display/dvo_ch7017.c @@ -0,0 +1,415 @@ +/* + * Copyright © 2006 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Eric Anholt + * + */ + +#include "intel_display_types.h" +#include "intel_dvo_dev.h" + +#define CH7017_TV_DISPLAY_MODE 0x00 +#define CH7017_FLICKER_FILTER 0x01 +#define CH7017_VIDEO_BANDWIDTH 0x02 +#define CH7017_TEXT_ENHANCEMENT 0x03 +#define CH7017_START_ACTIVE_VIDEO 0x04 +#define CH7017_HORIZONTAL_POSITION 0x05 +#define CH7017_VERTICAL_POSITION 0x06 +#define CH7017_BLACK_LEVEL 0x07 +#define CH7017_CONTRAST_ENHANCEMENT 0x08 +#define CH7017_TV_PLL 0x09 +#define CH7017_TV_PLL_M 0x0a +#define CH7017_TV_PLL_N 0x0b +#define CH7017_SUB_CARRIER_0 0x0c +#define CH7017_CIV_CONTROL 0x10 +#define CH7017_CIV_0 0x11 +#define CH7017_CHROMA_BOOST 0x14 +#define CH7017_CLOCK_MODE 0x1c +#define CH7017_INPUT_CLOCK 0x1d +#define CH7017_GPIO_CONTROL 0x1e +#define CH7017_INPUT_DATA_FORMAT 0x1f +#define CH7017_CONNECTION_DETECT 0x20 +#define CH7017_DAC_CONTROL 0x21 +#define CH7017_BUFFERED_CLOCK_OUTPUT 0x22 +#define CH7017_DEFEAT_VSYNC 0x47 +#define CH7017_TEST_PATTERN 0x48 + +#define CH7017_POWER_MANAGEMENT 0x49 +/** Enables the TV output path. */ +#define CH7017_TV_EN (1 << 0) +#define CH7017_DAC0_POWER_DOWN (1 << 1) +#define CH7017_DAC1_POWER_DOWN (1 << 2) +#define CH7017_DAC2_POWER_DOWN (1 << 3) +#define CH7017_DAC3_POWER_DOWN (1 << 4) +/** Powers down the TV out block, and DAC0-3 */ +#define CH7017_TV_POWER_DOWN_EN (1 << 5) + +#define CH7017_VERSION_ID 0x4a + +#define CH7017_DEVICE_ID 0x4b +#define CH7017_DEVICE_ID_VALUE 0x1b +#define CH7018_DEVICE_ID_VALUE 0x1a +#define CH7019_DEVICE_ID_VALUE 0x19 + +#define CH7017_XCLK_D2_ADJUST 0x53 +#define CH7017_UP_SCALER_COEFF_0 0x55 +#define CH7017_UP_SCALER_COEFF_1 0x56 +#define CH7017_UP_SCALER_COEFF_2 0x57 +#define CH7017_UP_SCALER_COEFF_3 0x58 +#define CH7017_UP_SCALER_COEFF_4 0x59 +#define CH7017_UP_SCALER_VERTICAL_INC_0 0x5a +#define CH7017_UP_SCALER_VERTICAL_INC_1 0x5b +#define CH7017_GPIO_INVERT 0x5c +#define CH7017_UP_SCALER_HORIZONTAL_INC_0 0x5d +#define CH7017_UP_SCALER_HORIZONTAL_INC_1 0x5e + +#define CH7017_HORIZONTAL_ACTIVE_PIXEL_INPUT 0x5f +/**< Low bits of horizontal active pixel input */ + +#define CH7017_ACTIVE_INPUT_LINE_OUTPUT 0x60 +/** High bits of horizontal active pixel input */ +#define CH7017_LVDS_HAP_INPUT_MASK (0x7 << 0) +/** High bits of vertical active line output */ +#define CH7017_LVDS_VAL_HIGH_MASK (0x7 << 3) + +#define CH7017_VERTICAL_ACTIVE_LINE_OUTPUT 0x61 +/**< Low bits of vertical active line output */ + +#define CH7017_HORIZONTAL_ACTIVE_PIXEL_OUTPUT 0x62 +/**< Low bits of horizontal active pixel output */ + +#define CH7017_LVDS_POWER_DOWN 0x63 +/** High bits of horizontal active pixel output */ +#define CH7017_LVDS_HAP_HIGH_MASK (0x7 << 0) +/** Enables the LVDS power down state transition */ +#define CH7017_LVDS_POWER_DOWN_EN (1 << 6) +/** Enables the LVDS upscaler */ +#define CH7017_LVDS_UPSCALER_EN (1 << 7) +#define CH7017_LVDS_POWER_DOWN_DEFAULT_RESERVED 0x08 + +#define CH7017_LVDS_ENCODING 0x64 +#define CH7017_LVDS_DITHER_2D (1 << 2) +#define CH7017_LVDS_DITHER_DIS (1 << 3) +#define CH7017_LVDS_DUAL_CHANNEL_EN (1 << 4) +#define CH7017_LVDS_24_BIT (1 << 5) + +#define CH7017_LVDS_ENCODING_2 0x65 + +#define CH7017_LVDS_PLL_CONTROL 0x66 +/** Enables the LVDS panel output path */ +#define CH7017_LVDS_PANEN (1 << 0) +/** Enables the LVDS panel backlight */ +#define CH7017_LVDS_BKLEN (1 << 3) + +#define CH7017_POWER_SEQUENCING_T1 0x67 +#define CH7017_POWER_SEQUENCING_T2 0x68 +#define CH7017_POWER_SEQUENCING_T3 0x69 +#define CH7017_POWER_SEQUENCING_T4 0x6a +#define CH7017_POWER_SEQUENCING_T5 0x6b +#define CH7017_GPIO_DRIVER_TYPE 0x6c +#define CH7017_GPIO_DATA 0x6d +#define CH7017_GPIO_DIRECTION_CONTROL 0x6e + +#define CH7017_LVDS_PLL_FEEDBACK_DIV 0x71 +# define CH7017_LVDS_PLL_FEED_BACK_DIVIDER_SHIFT 4 +# define CH7017_LVDS_PLL_FEED_FORWARD_DIVIDER_SHIFT 0 +# define CH7017_LVDS_PLL_FEEDBACK_DEFAULT_RESERVED 0x80 + +#define CH7017_LVDS_PLL_VCO_CONTROL 0x72 +# define CH7017_LVDS_PLL_VCO_DEFAULT_RESERVED 0x80 +# define CH7017_LVDS_PLL_VCO_SHIFT 4 +# define CH7017_LVDS_PLL_POST_SCALE_DIV_SHIFT 0 + +#define CH7017_OUTPUTS_ENABLE 0x73 +# define CH7017_CHARGE_PUMP_LOW 0x0 +# define CH7017_CHARGE_PUMP_HIGH 0x3 +# define CH7017_LVDS_CHANNEL_A (1 << 3) +# define CH7017_LVDS_CHANNEL_B (1 << 4) +# define CH7017_TV_DAC_A (1 << 5) +# define CH7017_TV_DAC_B (1 << 6) +# define CH7017_DDC_SELECT_DC2 (1 << 7) + +#define CH7017_LVDS_OUTPUT_AMPLITUDE 0x74 +#define CH7017_LVDS_PLL_EMI_REDUCTION 0x75 +#define CH7017_LVDS_POWER_DOWN_FLICKER 0x76 + +#define CH7017_LVDS_CONTROL_2 0x78 +# define CH7017_LOOP_FILTER_SHIFT 5 +# define CH7017_PHASE_DETECTOR_SHIFT 0 + +#define CH7017_BANG_LIMIT_CONTROL 0x7f + +struct ch7017_priv { + u8 dummy; +}; + +static void ch7017_dump_regs(struct intel_dvo_device *dvo); +static void ch7017_dpms(struct intel_dvo_device *dvo, bool enable); + +static bool ch7017_read(struct intel_dvo_device *dvo, u8 addr, u8 *val) +{ + struct i2c_msg msgs[] = { + { + .addr = dvo->slave_addr, + .flags = 0, + .len = 1, + .buf = &addr, + }, + { + .addr = dvo->slave_addr, + .flags = I2C_M_RD, + .len = 1, + .buf = val, + } + }; + return i2c_transfer(dvo->i2c_bus, msgs, 2) == 2; +} + +static bool ch7017_write(struct intel_dvo_device *dvo, u8 addr, u8 val) +{ + u8 buf[2] = { addr, val }; + struct i2c_msg msg = { + .addr = dvo->slave_addr, + .flags = 0, + .len = 2, + .buf = buf, + }; + return i2c_transfer(dvo->i2c_bus, &msg, 1) == 1; +} + +/** Probes for a CH7017 on the given bus and slave address. */ +static bool ch7017_init(struct intel_dvo_device *dvo, + struct i2c_adapter *adapter) +{ + struct ch7017_priv *priv; + const char *str; + u8 val; + + priv = kzalloc(sizeof(struct ch7017_priv), GFP_KERNEL); + if (priv == NULL) + return false; + + dvo->i2c_bus = adapter; + dvo->dev_priv = priv; + + if (!ch7017_read(dvo, CH7017_DEVICE_ID, &val)) + goto fail; + + switch (val) { + case CH7017_DEVICE_ID_VALUE: + str = "ch7017"; + break; + case CH7018_DEVICE_ID_VALUE: + str = "ch7018"; + break; + case CH7019_DEVICE_ID_VALUE: + str = "ch7019"; + break; + default: + DRM_DEBUG_KMS("ch701x not detected, got %d: from %s " + "slave %d.\n", + val, adapter->name, dvo->slave_addr); + goto fail; + } + + DRM_DEBUG_KMS("%s detected on %s, addr %d\n", + str, adapter->name, dvo->slave_addr); + return true; + +fail: + kfree(priv); + return false; +} + +static enum drm_connector_status ch7017_detect(struct intel_dvo_device *dvo) +{ + return connector_status_connected; +} + +static enum drm_mode_status ch7017_mode_valid(struct intel_dvo_device *dvo, + struct drm_display_mode *mode) +{ + if (mode->clock > 160000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static void ch7017_mode_set(struct intel_dvo_device *dvo, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + u8 lvds_pll_feedback_div, lvds_pll_vco_control; + u8 outputs_enable, lvds_control_2, lvds_power_down; + u8 horizontal_active_pixel_input; + u8 horizontal_active_pixel_output, vertical_active_line_output; + u8 active_input_line_output; + + DRM_DEBUG_KMS("Registers before mode setting\n"); + ch7017_dump_regs(dvo); + + /* LVDS PLL settings from page 75 of 7017-7017ds.pdf*/ + if (mode->clock < 100000) { + outputs_enable = CH7017_LVDS_CHANNEL_A | CH7017_CHARGE_PUMP_LOW; + lvds_pll_feedback_div = CH7017_LVDS_PLL_FEEDBACK_DEFAULT_RESERVED | + (2 << CH7017_LVDS_PLL_FEED_BACK_DIVIDER_SHIFT) | + (13 << CH7017_LVDS_PLL_FEED_FORWARD_DIVIDER_SHIFT); + lvds_pll_vco_control = CH7017_LVDS_PLL_VCO_DEFAULT_RESERVED | + (2 << CH7017_LVDS_PLL_VCO_SHIFT) | + (3 << CH7017_LVDS_PLL_POST_SCALE_DIV_SHIFT); + lvds_control_2 = (1 << CH7017_LOOP_FILTER_SHIFT) | + (0 << CH7017_PHASE_DETECTOR_SHIFT); + } else { + outputs_enable = CH7017_LVDS_CHANNEL_A | CH7017_CHARGE_PUMP_HIGH; + lvds_pll_feedback_div = + CH7017_LVDS_PLL_FEEDBACK_DEFAULT_RESERVED | + (2 << CH7017_LVDS_PLL_FEED_BACK_DIVIDER_SHIFT) | + (3 << CH7017_LVDS_PLL_FEED_FORWARD_DIVIDER_SHIFT); + lvds_control_2 = (3 << CH7017_LOOP_FILTER_SHIFT) | + (0 << CH7017_PHASE_DETECTOR_SHIFT); + if (1) { /* XXX: dual channel panel detection. Assume yes for now. */ + outputs_enable |= CH7017_LVDS_CHANNEL_B; + lvds_pll_vco_control = CH7017_LVDS_PLL_VCO_DEFAULT_RESERVED | + (2 << CH7017_LVDS_PLL_VCO_SHIFT) | + (13 << CH7017_LVDS_PLL_POST_SCALE_DIV_SHIFT); + } else { + lvds_pll_vco_control = CH7017_LVDS_PLL_VCO_DEFAULT_RESERVED | + (1 << CH7017_LVDS_PLL_VCO_SHIFT) | + (13 << CH7017_LVDS_PLL_POST_SCALE_DIV_SHIFT); + } + } + + horizontal_active_pixel_input = mode->hdisplay & 0x00ff; + + vertical_active_line_output = mode->vdisplay & 0x00ff; + horizontal_active_pixel_output = mode->hdisplay & 0x00ff; + + active_input_line_output = ((mode->hdisplay & 0x0700) >> 8) | + (((mode->vdisplay & 0x0700) >> 8) << 3); + + lvds_power_down = CH7017_LVDS_POWER_DOWN_DEFAULT_RESERVED | + (mode->hdisplay & 0x0700) >> 8; + + ch7017_dpms(dvo, false); + ch7017_write(dvo, CH7017_HORIZONTAL_ACTIVE_PIXEL_INPUT, + horizontal_active_pixel_input); + ch7017_write(dvo, CH7017_HORIZONTAL_ACTIVE_PIXEL_OUTPUT, + horizontal_active_pixel_output); + ch7017_write(dvo, CH7017_VERTICAL_ACTIVE_LINE_OUTPUT, + vertical_active_line_output); + ch7017_write(dvo, CH7017_ACTIVE_INPUT_LINE_OUTPUT, + active_input_line_output); + ch7017_write(dvo, CH7017_LVDS_PLL_VCO_CONTROL, lvds_pll_vco_control); + ch7017_write(dvo, CH7017_LVDS_PLL_FEEDBACK_DIV, lvds_pll_feedback_div); + ch7017_write(dvo, CH7017_LVDS_CONTROL_2, lvds_control_2); + ch7017_write(dvo, CH7017_OUTPUTS_ENABLE, outputs_enable); + + /* Turn the LVDS back on with new settings. */ + ch7017_write(dvo, CH7017_LVDS_POWER_DOWN, lvds_power_down); + + DRM_DEBUG_KMS("Registers after mode setting\n"); + ch7017_dump_regs(dvo); +} + +/* set the CH7017 power state */ +static void ch7017_dpms(struct intel_dvo_device *dvo, bool enable) +{ + u8 val; + + ch7017_read(dvo, CH7017_LVDS_POWER_DOWN, &val); + + /* Turn off TV/VGA, and never turn it on since we don't support it. */ + ch7017_write(dvo, CH7017_POWER_MANAGEMENT, + CH7017_DAC0_POWER_DOWN | + CH7017_DAC1_POWER_DOWN | + CH7017_DAC2_POWER_DOWN | + CH7017_DAC3_POWER_DOWN | + CH7017_TV_POWER_DOWN_EN); + + if (enable) { + /* Turn on the LVDS */ + ch7017_write(dvo, CH7017_LVDS_POWER_DOWN, + val & ~CH7017_LVDS_POWER_DOWN_EN); + } else { + /* Turn off the LVDS */ + ch7017_write(dvo, CH7017_LVDS_POWER_DOWN, + val | CH7017_LVDS_POWER_DOWN_EN); + } + + /* XXX: Should actually wait for update power status somehow */ + msleep(20); +} + +static bool ch7017_get_hw_state(struct intel_dvo_device *dvo) +{ + u8 val; + + ch7017_read(dvo, CH7017_LVDS_POWER_DOWN, &val); + + if (val & CH7017_LVDS_POWER_DOWN_EN) + return false; + else + return true; +} + +static void ch7017_dump_regs(struct intel_dvo_device *dvo) +{ + u8 val; + +#define DUMP(reg) \ +do { \ + ch7017_read(dvo, reg, &val); \ + DRM_DEBUG_KMS(#reg ": %02x\n", val); \ +} while (0) + + DUMP(CH7017_HORIZONTAL_ACTIVE_PIXEL_INPUT); + DUMP(CH7017_HORIZONTAL_ACTIVE_PIXEL_OUTPUT); + DUMP(CH7017_VERTICAL_ACTIVE_LINE_OUTPUT); + DUMP(CH7017_ACTIVE_INPUT_LINE_OUTPUT); + DUMP(CH7017_LVDS_PLL_VCO_CONTROL); + DUMP(CH7017_LVDS_PLL_FEEDBACK_DIV); + DUMP(CH7017_LVDS_CONTROL_2); + DUMP(CH7017_OUTPUTS_ENABLE); + DUMP(CH7017_LVDS_POWER_DOWN); +} + +static void ch7017_destroy(struct intel_dvo_device *dvo) +{ + struct ch7017_priv *priv = dvo->dev_priv; + + if (priv) { + kfree(priv); + dvo->dev_priv = NULL; + } +} + +const struct intel_dvo_dev_ops ch7017_ops = { + .init = ch7017_init, + .detect = ch7017_detect, + .mode_valid = ch7017_mode_valid, + .mode_set = ch7017_mode_set, + .dpms = ch7017_dpms, + .get_hw_state = ch7017_get_hw_state, + .dump_regs = ch7017_dump_regs, + .destroy = ch7017_destroy, +}; diff --git a/drivers/gpu/drm/i915/display/dvo_ch7xxx.c b/drivers/gpu/drm/i915/display/dvo_ch7xxx.c new file mode 100644 index 000000000..54f58ba44 --- /dev/null +++ b/drivers/gpu/drm/i915/display/dvo_ch7xxx.c @@ -0,0 +1,367 @@ +/************************************************************************** + +Copyright © 2006 Dave Airlie + +All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sub license, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice (including the +next paragraph) shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +**************************************************************************/ + +#include "intel_display_types.h" +#include "intel_dvo_dev.h" + +#define CH7xxx_REG_VID 0x4a +#define CH7xxx_REG_DID 0x4b + +#define CH7011_VID 0x83 /* 7010 as well */ +#define CH7010B_VID 0x05 +#define CH7009A_VID 0x84 +#define CH7009B_VID 0x85 +#define CH7301_VID 0x95 + +#define CH7xxx_VID 0x84 +#define CH7xxx_DID 0x17 +#define CH7010_DID 0x16 + +#define CH7xxx_NUM_REGS 0x4c + +#define CH7xxx_CM 0x1c +#define CH7xxx_CM_XCM (1<<0) +#define CH7xxx_CM_MCP (1<<2) +#define CH7xxx_INPUT_CLOCK 0x1d +#define CH7xxx_GPIO 0x1e +#define CH7xxx_GPIO_HPIR (1<<3) +#define CH7xxx_IDF 0x1f + +#define CH7xxx_IDF_HSP (1<<3) +#define CH7xxx_IDF_VSP (1<<4) + +#define CH7xxx_CONNECTION_DETECT 0x20 +#define CH7xxx_CDET_DVI (1<<5) + +#define CH7301_DAC_CNTL 0x21 +#define CH7301_HOTPLUG 0x23 +#define CH7xxx_TCTL 0x31 +#define CH7xxx_TVCO 0x32 +#define CH7xxx_TPCP 0x33 +#define CH7xxx_TPD 0x34 +#define CH7xxx_TPVT 0x35 +#define CH7xxx_TLPF 0x36 +#define CH7xxx_TCT 0x37 +#define CH7301_TEST_PATTERN 0x48 + +#define CH7xxx_PM 0x49 +#define CH7xxx_PM_FPD (1<<0) +#define CH7301_PM_DACPD0 (1<<1) +#define CH7301_PM_DACPD1 (1<<2) +#define CH7301_PM_DACPD2 (1<<3) +#define CH7xxx_PM_DVIL (1<<6) +#define CH7xxx_PM_DVIP (1<<7) + +#define CH7301_SYNC_POLARITY 0x56 +#define CH7301_SYNC_RGB_YUV (1<<0) +#define CH7301_SYNC_POL_DVI (1<<5) + +/** @file + * driver for the Chrontel 7xxx DVI chip over DVO. + */ + +static struct ch7xxx_id_struct { + u8 vid; + char *name; +} ch7xxx_ids[] = { + { CH7011_VID, "CH7011" }, + { CH7010B_VID, "CH7010B" }, + { CH7009A_VID, "CH7009A" }, + { CH7009B_VID, "CH7009B" }, + { CH7301_VID, "CH7301" }, +}; + +static struct ch7xxx_did_struct { + u8 did; + char *name; +} ch7xxx_dids[] = { + { CH7xxx_DID, "CH7XXX" }, + { CH7010_DID, "CH7010B" }, +}; + +struct ch7xxx_priv { + bool quiet; +}; + +static char *ch7xxx_get_id(u8 vid) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ch7xxx_ids); i++) { + if (ch7xxx_ids[i].vid == vid) + return ch7xxx_ids[i].name; + } + + return NULL; +} + +static char *ch7xxx_get_did(u8 did) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ch7xxx_dids); i++) { + if (ch7xxx_dids[i].did == did) + return ch7xxx_dids[i].name; + } + + return NULL; +} + +/** Reads an 8 bit register */ +static bool ch7xxx_readb(struct intel_dvo_device *dvo, int addr, u8 *ch) +{ + struct ch7xxx_priv *ch7xxx = dvo->dev_priv; + struct i2c_adapter *adapter = dvo->i2c_bus; + u8 out_buf[2]; + u8 in_buf[2]; + + struct i2c_msg msgs[] = { + { + .addr = dvo->slave_addr, + .flags = 0, + .len = 1, + .buf = out_buf, + }, + { + .addr = dvo->slave_addr, + .flags = I2C_M_RD, + .len = 1, + .buf = in_buf, + } + }; + + out_buf[0] = addr; + out_buf[1] = 0; + + if (i2c_transfer(adapter, msgs, 2) == 2) { + *ch = in_buf[0]; + return true; + } + + if (!ch7xxx->quiet) { + DRM_DEBUG_KMS("Unable to read register 0x%02x from %s:%02x.\n", + addr, adapter->name, dvo->slave_addr); + } + return false; +} + +/** Writes an 8 bit register */ +static bool ch7xxx_writeb(struct intel_dvo_device *dvo, int addr, u8 ch) +{ + struct ch7xxx_priv *ch7xxx = dvo->dev_priv; + struct i2c_adapter *adapter = dvo->i2c_bus; + u8 out_buf[2]; + struct i2c_msg msg = { + .addr = dvo->slave_addr, + .flags = 0, + .len = 2, + .buf = out_buf, + }; + + out_buf[0] = addr; + out_buf[1] = ch; + + if (i2c_transfer(adapter, &msg, 1) == 1) + return true; + + if (!ch7xxx->quiet) { + DRM_DEBUG_KMS("Unable to write register 0x%02x to %s:%d.\n", + addr, adapter->name, dvo->slave_addr); + } + + return false; +} + +static bool ch7xxx_init(struct intel_dvo_device *dvo, + struct i2c_adapter *adapter) +{ + /* this will detect the CH7xxx chip on the specified i2c bus */ + struct ch7xxx_priv *ch7xxx; + u8 vendor, device; + char *name, *devid; + + ch7xxx = kzalloc(sizeof(struct ch7xxx_priv), GFP_KERNEL); + if (ch7xxx == NULL) + return false; + + dvo->i2c_bus = adapter; + dvo->dev_priv = ch7xxx; + ch7xxx->quiet = true; + + if (!ch7xxx_readb(dvo, CH7xxx_REG_VID, &vendor)) + goto out; + + name = ch7xxx_get_id(vendor); + if (!name) { + DRM_DEBUG_KMS("ch7xxx not detected; got VID 0x%02x from %s slave %d.\n", + vendor, adapter->name, dvo->slave_addr); + goto out; + } + + + if (!ch7xxx_readb(dvo, CH7xxx_REG_DID, &device)) + goto out; + + devid = ch7xxx_get_did(device); + if (!devid) { + DRM_DEBUG_KMS("ch7xxx not detected; got DID 0x%02x from %s slave %d.\n", + device, adapter->name, dvo->slave_addr); + goto out; + } + + ch7xxx->quiet = false; + DRM_DEBUG_KMS("Detected %s chipset, vendor/device ID 0x%02x/0x%02x\n", + name, vendor, device); + return true; +out: + kfree(ch7xxx); + return false; +} + +static enum drm_connector_status ch7xxx_detect(struct intel_dvo_device *dvo) +{ + u8 cdet, orig_pm, pm; + + ch7xxx_readb(dvo, CH7xxx_PM, &orig_pm); + + pm = orig_pm; + pm &= ~CH7xxx_PM_FPD; + pm |= CH7xxx_PM_DVIL | CH7xxx_PM_DVIP; + + ch7xxx_writeb(dvo, CH7xxx_PM, pm); + + ch7xxx_readb(dvo, CH7xxx_CONNECTION_DETECT, &cdet); + + ch7xxx_writeb(dvo, CH7xxx_PM, orig_pm); + + if (cdet & CH7xxx_CDET_DVI) + return connector_status_connected; + return connector_status_disconnected; +} + +static enum drm_mode_status ch7xxx_mode_valid(struct intel_dvo_device *dvo, + struct drm_display_mode *mode) +{ + if (mode->clock > 165000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static void ch7xxx_mode_set(struct intel_dvo_device *dvo, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + u8 tvco, tpcp, tpd, tlpf, idf; + + if (mode->clock <= 65000) { + tvco = 0x23; + tpcp = 0x08; + tpd = 0x16; + tlpf = 0x60; + } else { + tvco = 0x2d; + tpcp = 0x06; + tpd = 0x26; + tlpf = 0xa0; + } + + ch7xxx_writeb(dvo, CH7xxx_TCTL, 0x00); + ch7xxx_writeb(dvo, CH7xxx_TVCO, tvco); + ch7xxx_writeb(dvo, CH7xxx_TPCP, tpcp); + ch7xxx_writeb(dvo, CH7xxx_TPD, tpd); + ch7xxx_writeb(dvo, CH7xxx_TPVT, 0x30); + ch7xxx_writeb(dvo, CH7xxx_TLPF, tlpf); + ch7xxx_writeb(dvo, CH7xxx_TCT, 0x00); + + ch7xxx_readb(dvo, CH7xxx_IDF, &idf); + + idf &= ~(CH7xxx_IDF_HSP | CH7xxx_IDF_VSP); + if (mode->flags & DRM_MODE_FLAG_PHSYNC) + idf |= CH7xxx_IDF_HSP; + + if (mode->flags & DRM_MODE_FLAG_PVSYNC) + idf |= CH7xxx_IDF_VSP; + + ch7xxx_writeb(dvo, CH7xxx_IDF, idf); +} + +/* set the CH7xxx power state */ +static void ch7xxx_dpms(struct intel_dvo_device *dvo, bool enable) +{ + if (enable) + ch7xxx_writeb(dvo, CH7xxx_PM, CH7xxx_PM_DVIL | CH7xxx_PM_DVIP); + else + ch7xxx_writeb(dvo, CH7xxx_PM, CH7xxx_PM_FPD); +} + +static bool ch7xxx_get_hw_state(struct intel_dvo_device *dvo) +{ + u8 val; + + ch7xxx_readb(dvo, CH7xxx_PM, &val); + + if (val & (CH7xxx_PM_DVIL | CH7xxx_PM_DVIP)) + return true; + else + return false; +} + +static void ch7xxx_dump_regs(struct intel_dvo_device *dvo) +{ + int i; + + for (i = 0; i < CH7xxx_NUM_REGS; i++) { + u8 val; + if ((i % 8) == 0) + DRM_DEBUG_KMS("\n %02X: ", i); + ch7xxx_readb(dvo, i, &val); + DRM_DEBUG_KMS("%02X ", val); + } +} + +static void ch7xxx_destroy(struct intel_dvo_device *dvo) +{ + struct ch7xxx_priv *ch7xxx = dvo->dev_priv; + + if (ch7xxx) { + kfree(ch7xxx); + dvo->dev_priv = NULL; + } +} + +const struct intel_dvo_dev_ops ch7xxx_ops = { + .init = ch7xxx_init, + .detect = ch7xxx_detect, + .mode_valid = ch7xxx_mode_valid, + .mode_set = ch7xxx_mode_set, + .dpms = ch7xxx_dpms, + .get_hw_state = ch7xxx_get_hw_state, + .dump_regs = ch7xxx_dump_regs, + .destroy = ch7xxx_destroy, +}; diff --git a/drivers/gpu/drm/i915/display/dvo_ivch.c b/drivers/gpu/drm/i915/display/dvo_ivch.c new file mode 100644 index 000000000..f43d8c610 --- /dev/null +++ b/drivers/gpu/drm/i915/display/dvo_ivch.c @@ -0,0 +1,503 @@ +/* + * Copyright © 2006 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Eric Anholt + * Thomas Richter + * + * Minor modifications (Dithering enable): + * Thomas Richter + * + */ + +#include "intel_display_types.h" +#include "intel_dvo_dev.h" + +/* + * register definitions for the i82807aa. + * + * Documentation on this chipset can be found in datasheet #29069001 at + * intel.com. + */ + +/* + * VCH Revision & GMBus Base Addr + */ +#define VR00 0x00 +# define VR00_BASE_ADDRESS_MASK 0x007f + +/* + * Functionality Enable + */ +#define VR01 0x01 + +/* + * Enable the panel fitter + */ +# define VR01_PANEL_FIT_ENABLE (1 << 3) +/* + * Enables the LCD display. + * + * This must not be set while VR01_DVO_BYPASS_ENABLE is set. + */ +# define VR01_LCD_ENABLE (1 << 2) +/* Enables the DVO repeater. */ +# define VR01_DVO_BYPASS_ENABLE (1 << 1) +/* Enables the DVO clock */ +# define VR01_DVO_ENABLE (1 << 0) +/* Enable dithering for 18bpp panels. Not documented. */ +# define VR01_DITHER_ENABLE (1 << 4) + +/* + * LCD Interface Format + */ +#define VR10 0x10 +/* Enables LVDS output instead of CMOS */ +# define VR10_LVDS_ENABLE (1 << 4) +/* Enables 18-bit LVDS output. */ +# define VR10_INTERFACE_1X18 (0 << 2) +/* Enables 24-bit LVDS or CMOS output */ +# define VR10_INTERFACE_1X24 (1 << 2) +/* Enables 2x18-bit LVDS or CMOS output. */ +# define VR10_INTERFACE_2X18 (2 << 2) +/* Enables 2x24-bit LVDS output */ +# define VR10_INTERFACE_2X24 (3 << 2) +/* Mask that defines the depth of the pipeline */ +# define VR10_INTERFACE_DEPTH_MASK (3 << 2) + +/* + * VR20 LCD Horizontal Display Size + */ +#define VR20 0x20 + +/* + * LCD Vertical Display Size + */ +#define VR21 0x21 + +/* + * Panel power down status + */ +#define VR30 0x30 +/* Read only bit indicating that the panel is not in a safe poweroff state. */ +# define VR30_PANEL_ON (1 << 15) + +#define VR40 0x40 +# define VR40_STALL_ENABLE (1 << 13) +# define VR40_VERTICAL_INTERP_ENABLE (1 << 12) +# define VR40_ENHANCED_PANEL_FITTING (1 << 11) +# define VR40_HORIZONTAL_INTERP_ENABLE (1 << 10) +# define VR40_AUTO_RATIO_ENABLE (1 << 9) +# define VR40_CLOCK_GATING_ENABLE (1 << 8) + +/* + * Panel Fitting Vertical Ratio + * (((image_height - 1) << 16) / ((panel_height - 1))) >> 2 + */ +#define VR41 0x41 + +/* + * Panel Fitting Horizontal Ratio + * (((image_width - 1) << 16) / ((panel_width - 1))) >> 2 + */ +#define VR42 0x42 + +/* + * Horizontal Image Size + */ +#define VR43 0x43 + +/* VR80 GPIO 0 + */ +#define VR80 0x80 +#define VR81 0x81 +#define VR82 0x82 +#define VR83 0x83 +#define VR84 0x84 +#define VR85 0x85 +#define VR86 0x86 +#define VR87 0x87 + +/* VR88 GPIO 8 + */ +#define VR88 0x88 + +/* Graphics BIOS scratch 0 + */ +#define VR8E 0x8E +# define VR8E_PANEL_TYPE_MASK (0xf << 0) +# define VR8E_PANEL_INTERFACE_CMOS (0 << 4) +# define VR8E_PANEL_INTERFACE_LVDS (1 << 4) +# define VR8E_FORCE_DEFAULT_PANEL (1 << 5) + +/* Graphics BIOS scratch 1 + */ +#define VR8F 0x8F +# define VR8F_VCH_PRESENT (1 << 0) +# define VR8F_DISPLAY_CONN (1 << 1) +# define VR8F_POWER_MASK (0x3c) +# define VR8F_POWER_POS (2) + +/* Some Bios implementations do not restore the DVO state upon + * resume from standby. Thus, this driver has to handle it + * instead. The following list contains all registers that + * require saving. + */ +static const u16 backup_addresses[] = { + 0x11, 0x12, + 0x18, 0x19, 0x1a, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x8e, 0x8f, + 0x10 /* this must come last */ +}; + + +struct ivch_priv { + bool quiet; + + u16 width, height; + + /* Register backup */ + + u16 reg_backup[ARRAY_SIZE(backup_addresses)]; +}; + + +static void ivch_dump_regs(struct intel_dvo_device *dvo); +/* + * Reads a register on the ivch. + * + * Each of the 256 registers are 16 bits long. + */ +static bool ivch_read(struct intel_dvo_device *dvo, int addr, u16 *data) +{ + struct ivch_priv *priv = dvo->dev_priv; + struct i2c_adapter *adapter = dvo->i2c_bus; + u8 out_buf[1]; + u8 in_buf[2]; + + struct i2c_msg msgs[] = { + { + .addr = dvo->slave_addr, + .flags = I2C_M_RD, + .len = 0, + }, + { + .addr = 0, + .flags = I2C_M_NOSTART, + .len = 1, + .buf = out_buf, + }, + { + .addr = dvo->slave_addr, + .flags = I2C_M_RD | I2C_M_NOSTART, + .len = 2, + .buf = in_buf, + } + }; + + out_buf[0] = addr; + + if (i2c_transfer(adapter, msgs, 3) == 3) { + *data = (in_buf[1] << 8) | in_buf[0]; + return true; + } + + if (!priv->quiet) { + DRM_DEBUG_KMS("Unable to read register 0x%02x from " + "%s:%02x.\n", + addr, adapter->name, dvo->slave_addr); + } + return false; +} + +/* Writes a 16-bit register on the ivch */ +static bool ivch_write(struct intel_dvo_device *dvo, int addr, u16 data) +{ + struct ivch_priv *priv = dvo->dev_priv; + struct i2c_adapter *adapter = dvo->i2c_bus; + u8 out_buf[3]; + struct i2c_msg msg = { + .addr = dvo->slave_addr, + .flags = 0, + .len = 3, + .buf = out_buf, + }; + + out_buf[0] = addr; + out_buf[1] = data & 0xff; + out_buf[2] = data >> 8; + + if (i2c_transfer(adapter, &msg, 1) == 1) + return true; + + if (!priv->quiet) { + DRM_DEBUG_KMS("Unable to write register 0x%02x to %s:%d.\n", + addr, adapter->name, dvo->slave_addr); + } + + return false; +} + +/* Probes the given bus and slave address for an ivch */ +static bool ivch_init(struct intel_dvo_device *dvo, + struct i2c_adapter *adapter) +{ + struct ivch_priv *priv; + u16 temp; + int i; + + priv = kzalloc(sizeof(struct ivch_priv), GFP_KERNEL); + if (priv == NULL) + return false; + + dvo->i2c_bus = adapter; + dvo->dev_priv = priv; + priv->quiet = true; + + if (!ivch_read(dvo, VR00, &temp)) + goto out; + priv->quiet = false; + + /* Since the identification bits are probably zeroes, which doesn't seem + * very unique, check that the value in the base address field matches + * the address it's responding on. + */ + if ((temp & VR00_BASE_ADDRESS_MASK) != dvo->slave_addr) { + DRM_DEBUG_KMS("ivch detect failed due to address mismatch " + "(%d vs %d)\n", + (temp & VR00_BASE_ADDRESS_MASK), dvo->slave_addr); + goto out; + } + + ivch_read(dvo, VR20, &priv->width); + ivch_read(dvo, VR21, &priv->height); + + /* Make a backup of the registers to be able to restore them + * upon suspend. + */ + for (i = 0; i < ARRAY_SIZE(backup_addresses); i++) + ivch_read(dvo, backup_addresses[i], priv->reg_backup + i); + + ivch_dump_regs(dvo); + + return true; + +out: + kfree(priv); + return false; +} + +static enum drm_connector_status ivch_detect(struct intel_dvo_device *dvo) +{ + return connector_status_connected; +} + +static enum drm_mode_status ivch_mode_valid(struct intel_dvo_device *dvo, + struct drm_display_mode *mode) +{ + if (mode->clock > 112000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +/* Restore the DVO registers after a resume + * from RAM. Registers have been saved during + * the initialization. + */ +static void ivch_reset(struct intel_dvo_device *dvo) +{ + struct ivch_priv *priv = dvo->dev_priv; + int i; + + DRM_DEBUG_KMS("Resetting the IVCH registers\n"); + + ivch_write(dvo, VR10, 0x0000); + + for (i = 0; i < ARRAY_SIZE(backup_addresses); i++) + ivch_write(dvo, backup_addresses[i], priv->reg_backup[i]); +} + +/* Sets the power state of the panel connected to the ivch */ +static void ivch_dpms(struct intel_dvo_device *dvo, bool enable) +{ + int i; + u16 vr01, vr30, backlight; + + ivch_reset(dvo); + + /* Set the new power state of the panel. */ + if (!ivch_read(dvo, VR01, &vr01)) + return; + + if (enable) + backlight = 1; + else + backlight = 0; + + ivch_write(dvo, VR80, backlight); + + if (enable) + vr01 |= VR01_LCD_ENABLE | VR01_DVO_ENABLE; + else + vr01 &= ~(VR01_LCD_ENABLE | VR01_DVO_ENABLE); + + ivch_write(dvo, VR01, vr01); + + /* Wait for the panel to make its state transition */ + for (i = 0; i < 100; i++) { + if (!ivch_read(dvo, VR30, &vr30)) + break; + + if (((vr30 & VR30_PANEL_ON) != 0) == enable) + break; + udelay(1000); + } + /* wait some more; vch may fail to resync sometimes without this */ + udelay(16 * 1000); +} + +static bool ivch_get_hw_state(struct intel_dvo_device *dvo) +{ + u16 vr01; + + ivch_reset(dvo); + + /* Set the new power state of the panel. */ + if (!ivch_read(dvo, VR01, &vr01)) + return false; + + if (vr01 & VR01_LCD_ENABLE) + return true; + else + return false; +} + +static void ivch_mode_set(struct intel_dvo_device *dvo, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct ivch_priv *priv = dvo->dev_priv; + u16 vr40 = 0; + u16 vr01 = 0; + u16 vr10; + + ivch_reset(dvo); + + vr10 = priv->reg_backup[ARRAY_SIZE(backup_addresses) - 1]; + + /* Enable dithering for 18 bpp pipelines */ + vr10 &= VR10_INTERFACE_DEPTH_MASK; + if (vr10 == VR10_INTERFACE_2X18 || vr10 == VR10_INTERFACE_1X18) + vr01 = VR01_DITHER_ENABLE; + + vr40 = (VR40_STALL_ENABLE | VR40_VERTICAL_INTERP_ENABLE | + VR40_HORIZONTAL_INTERP_ENABLE); + + if (mode->hdisplay != adjusted_mode->crtc_hdisplay || + mode->vdisplay != adjusted_mode->crtc_vdisplay) { + u16 x_ratio, y_ratio; + + vr01 |= VR01_PANEL_FIT_ENABLE; + vr40 |= VR40_CLOCK_GATING_ENABLE; + x_ratio = (((mode->hdisplay - 1) << 16) / + (adjusted_mode->crtc_hdisplay - 1)) >> 2; + y_ratio = (((mode->vdisplay - 1) << 16) / + (adjusted_mode->crtc_vdisplay - 1)) >> 2; + ivch_write(dvo, VR42, x_ratio); + ivch_write(dvo, VR41, y_ratio); + } else { + vr01 &= ~VR01_PANEL_FIT_ENABLE; + vr40 &= ~VR40_CLOCK_GATING_ENABLE; + } + vr40 &= ~VR40_AUTO_RATIO_ENABLE; + + ivch_write(dvo, VR01, vr01); + ivch_write(dvo, VR40, vr40); +} + +static void ivch_dump_regs(struct intel_dvo_device *dvo) +{ + u16 val; + + ivch_read(dvo, VR00, &val); + DRM_DEBUG_KMS("VR00: 0x%04x\n", val); + ivch_read(dvo, VR01, &val); + DRM_DEBUG_KMS("VR01: 0x%04x\n", val); + ivch_read(dvo, VR10, &val); + DRM_DEBUG_KMS("VR10: 0x%04x\n", val); + ivch_read(dvo, VR30, &val); + DRM_DEBUG_KMS("VR30: 0x%04x\n", val); + ivch_read(dvo, VR40, &val); + DRM_DEBUG_KMS("VR40: 0x%04x\n", val); + + /* GPIO registers */ + ivch_read(dvo, VR80, &val); + DRM_DEBUG_KMS("VR80: 0x%04x\n", val); + ivch_read(dvo, VR81, &val); + DRM_DEBUG_KMS("VR81: 0x%04x\n", val); + ivch_read(dvo, VR82, &val); + DRM_DEBUG_KMS("VR82: 0x%04x\n", val); + ivch_read(dvo, VR83, &val); + DRM_DEBUG_KMS("VR83: 0x%04x\n", val); + ivch_read(dvo, VR84, &val); + DRM_DEBUG_KMS("VR84: 0x%04x\n", val); + ivch_read(dvo, VR85, &val); + DRM_DEBUG_KMS("VR85: 0x%04x\n", val); + ivch_read(dvo, VR86, &val); + DRM_DEBUG_KMS("VR86: 0x%04x\n", val); + ivch_read(dvo, VR87, &val); + DRM_DEBUG_KMS("VR87: 0x%04x\n", val); + ivch_read(dvo, VR88, &val); + DRM_DEBUG_KMS("VR88: 0x%04x\n", val); + + /* Scratch register 0 - AIM Panel type */ + ivch_read(dvo, VR8E, &val); + DRM_DEBUG_KMS("VR8E: 0x%04x\n", val); + + /* Scratch register 1 - Status register */ + ivch_read(dvo, VR8F, &val); + DRM_DEBUG_KMS("VR8F: 0x%04x\n", val); +} + +static void ivch_destroy(struct intel_dvo_device *dvo) +{ + struct ivch_priv *priv = dvo->dev_priv; + + if (priv) { + kfree(priv); + dvo->dev_priv = NULL; + } +} + +const struct intel_dvo_dev_ops ivch_ops = { + .init = ivch_init, + .dpms = ivch_dpms, + .get_hw_state = ivch_get_hw_state, + .mode_valid = ivch_mode_valid, + .mode_set = ivch_mode_set, + .detect = ivch_detect, + .dump_regs = ivch_dump_regs, + .destroy = ivch_destroy, +}; diff --git a/drivers/gpu/drm/i915/display/dvo_ns2501.c b/drivers/gpu/drm/i915/display/dvo_ns2501.c new file mode 100644 index 000000000..a724a8755 --- /dev/null +++ b/drivers/gpu/drm/i915/display/dvo_ns2501.c @@ -0,0 +1,710 @@ +/* + * + * Copyright (c) 2012 Gilles Dartiguelongue, Thomas Richter + * + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sub license, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "i915_drv.h" +#include "i915_reg.h" +#include "intel_display_types.h" +#include "intel_dvo_dev.h" + +#define NS2501_VID 0x1305 +#define NS2501_DID 0x6726 + +#define NS2501_VID_LO 0x00 +#define NS2501_VID_HI 0x01 +#define NS2501_DID_LO 0x02 +#define NS2501_DID_HI 0x03 +#define NS2501_REV 0x04 +#define NS2501_RSVD 0x05 +#define NS2501_FREQ_LO 0x06 +#define NS2501_FREQ_HI 0x07 + +#define NS2501_REG8 0x08 +#define NS2501_8_VEN (1<<5) +#define NS2501_8_HEN (1<<4) +#define NS2501_8_DSEL (1<<3) +#define NS2501_8_BPAS (1<<2) +#define NS2501_8_RSVD (1<<1) +#define NS2501_8_PD (1<<0) + +#define NS2501_REG9 0x09 +#define NS2501_9_VLOW (1<<7) +#define NS2501_9_MSEL_MASK (0x7<<4) +#define NS2501_9_TSEL (1<<3) +#define NS2501_9_RSEN (1<<2) +#define NS2501_9_RSVD (1<<1) +#define NS2501_9_MDI (1<<0) + +#define NS2501_REGC 0x0c + +/* + * The following registers are not part of the official datasheet + * and are the result of reverse engineering. + */ + +/* + * Register c0 controls how the DVO synchronizes with + * its input. + */ +#define NS2501_REGC0 0xc0 +#define NS2501_C0_ENABLE (1<<0) /* enable the DVO sync in general */ +#define NS2501_C0_HSYNC (1<<1) /* synchronize horizontal with input */ +#define NS2501_C0_VSYNC (1<<2) /* synchronize vertical with input */ +#define NS2501_C0_RESET (1<<7) /* reset the synchronization flip/flops */ + +/* + * Register 41 is somehow related to the sync register and sync + * configuration. It should be 0x32 whenever regC0 is 0x05 (hsync off) + * and 0x00 otherwise. + */ +#define NS2501_REG41 0x41 + +/* + * this register controls the dithering of the DVO + * One bit enables it, the other define the dithering depth. + * The higher the value, the lower the dithering depth. + */ +#define NS2501_F9_REG 0xf9 +#define NS2501_F9_ENABLE (1<<0) /* if set, dithering is enabled */ +#define NS2501_F9_DITHER_MASK (0x7f<<1) /* controls the dither depth */ +#define NS2501_F9_DITHER_SHIFT 1 /* shifts the dither mask */ + +/* + * PLL configuration register. This is a pair of registers, + * one single byte register at 1B, and a pair at 1C,1D. + * These registers are counters/dividers. + */ +#define NS2501_REG1B 0x1b /* one byte PLL control register */ +#define NS2501_REG1C 0x1c /* low-part of the second register */ +#define NS2501_REG1D 0x1d /* high-part of the second register */ + +/* + * Scaler control registers. Horizontal at b8,b9, + * vertical at 10,11. The scale factor is computed as + * 2^16/control-value. The low-byte comes first. + */ +#define NS2501_REG10 0x10 /* low-byte vertical scaler */ +#define NS2501_REG11 0x11 /* high-byte vertical scaler */ +#define NS2501_REGB8 0xb8 /* low-byte horizontal scaler */ +#define NS2501_REGB9 0xb9 /* high-byte horizontal scaler */ + +/* + * Display window definition. This consists of four registers + * per dimension. One register pair defines the start of the + * display, one the end. + * As far as I understand, this defines the window within which + * the scaler samples the input. + */ +#define NS2501_REGC1 0xc1 /* low-byte horizontal display start */ +#define NS2501_REGC2 0xc2 /* high-byte horizontal display start */ +#define NS2501_REGC3 0xc3 /* low-byte horizontal display stop */ +#define NS2501_REGC4 0xc4 /* high-byte horizontal display stop */ +#define NS2501_REGC5 0xc5 /* low-byte vertical display start */ +#define NS2501_REGC6 0xc6 /* high-byte vertical display start */ +#define NS2501_REGC7 0xc7 /* low-byte vertical display stop */ +#define NS2501_REGC8 0xc8 /* high-byte vertical display stop */ + +/* + * The following register pair seems to define the start of + * the vertical sync. If automatic syncing is enabled, and the + * register value defines a sync pulse that is later than the + * incoming sync, then the register value is ignored and the + * external hsync triggers the synchronization. + */ +#define NS2501_REG80 0x80 /* low-byte vsync-start */ +#define NS2501_REG81 0x81 /* high-byte vsync-start */ + +/* + * The following register pair seems to define the total number + * of lines created at the output side of the scaler. + * This is again a low-high register pair. + */ +#define NS2501_REG82 0x82 /* output display height, low byte */ +#define NS2501_REG83 0x83 /* output display height, high byte */ + +/* + * The following registers define the end of the front-porch + * in horizontal and vertical position and hence allow to shift + * the image left/right or up/down. + */ +#define NS2501_REG98 0x98 /* horizontal start of display + 256, low */ +#define NS2501_REG99 0x99 /* horizontal start of display + 256, high */ +#define NS2501_REG8E 0x8e /* vertical start of the display, low byte */ +#define NS2501_REG8F 0x8f /* vertical start of the display, high byte */ + +/* + * The following register pair control the function of the + * backlight and the DVO output. To enable the corresponding + * function, the corresponding bit must be set in both registers. + */ +#define NS2501_REG34 0x34 /* DVO enable functions, first register */ +#define NS2501_REG35 0x35 /* DVO enable functions, second register */ +#define NS2501_34_ENABLE_OUTPUT (1<<0) /* enable DVO output */ +#define NS2501_34_ENABLE_BACKLIGHT (1<<1) /* enable backlight */ + +/* + * Registers 9C and 9D define the vertical output offset + * of the visible region. + */ +#define NS2501_REG9C 0x9c +#define NS2501_REG9D 0x9d + +/* + * The register 9F defines the dithering. This requires the + * scaler to be ON. Bit 0 enables dithering, the remaining + * bits control the depth of the dither. The higher the value, + * the LOWER the dithering amplitude. A good value seems to be + * 15 (total register value). + */ +#define NS2501_REGF9 0xf9 +#define NS2501_F9_ENABLE_DITHER (1<<0) /* enable dithering */ +#define NS2501_F9_DITHER_MASK (0x7f<<1) /* dither masking */ +#define NS2501_F9_DITHER_SHIFT 1 /* upshift of the dither mask */ + +enum { + MODE_640x480, + MODE_800x600, + MODE_1024x768, +}; + +struct ns2501_reg { + u8 offset; + u8 value; +}; + +/* + * The following structure keeps the complete configuration of + * the DVO, given a specific output configuration. + * This is pretty much guess-work from reverse-engineering, so + * read all this with a grain of salt. + */ +struct ns2501_configuration { + u8 sync; /* configuration of the C0 register */ + u8 conf; /* configuration register 8 */ + u8 syncb; /* configuration register 41 */ + u8 dither; /* configuration of the dithering */ + u8 pll_a; /* PLL configuration, register A, 1B */ + u16 pll_b; /* PLL configuration, register B, 1C/1D */ + u16 hstart; /* horizontal start, registers C1/C2 */ + u16 hstop; /* horizontal total, registers C3/C4 */ + u16 vstart; /* vertical start, registers C5/C6 */ + u16 vstop; /* vertical total, registers C7/C8 */ + u16 vsync; /* manual vertical sync start, 80/81 */ + u16 vtotal; /* number of lines generated, 82/83 */ + u16 hpos; /* horizontal position + 256, 98/99 */ + u16 vpos; /* vertical position, 8e/8f */ + u16 voffs; /* vertical output offset, 9c/9d */ + u16 hscale; /* horizontal scaling factor, b8/b9 */ + u16 vscale; /* vertical scaling factor, 10/11 */ +}; + +/* + * DVO configuration values, partially based on what the BIOS + * of the Fujitsu Lifebook S6010 writes into registers, + * partially found by manual tweaking. These configurations assume + * a 1024x768 panel. + */ +static const struct ns2501_configuration ns2501_modes[] = { + [MODE_640x480] = { + .sync = NS2501_C0_ENABLE | NS2501_C0_VSYNC, + .conf = NS2501_8_VEN | NS2501_8_HEN | NS2501_8_PD, + .syncb = 0x32, + .dither = 0x0f, + .pll_a = 17, + .pll_b = 852, + .hstart = 144, + .hstop = 783, + .vstart = 22, + .vstop = 514, + .vsync = 2047, /* actually, ignored with this config */ + .vtotal = 1341, + .hpos = 0, + .vpos = 16, + .voffs = 36, + .hscale = 40960, + .vscale = 40960 + }, + [MODE_800x600] = { + .sync = NS2501_C0_ENABLE | + NS2501_C0_HSYNC | NS2501_C0_VSYNC, + .conf = NS2501_8_VEN | NS2501_8_HEN | NS2501_8_PD, + .syncb = 0x00, + .dither = 0x0f, + .pll_a = 25, + .pll_b = 612, + .hstart = 215, + .hstop = 1016, + .vstart = 26, + .vstop = 627, + .vsync = 807, + .vtotal = 1341, + .hpos = 0, + .vpos = 4, + .voffs = 35, + .hscale = 51248, + .vscale = 51232 + }, + [MODE_1024x768] = { + .sync = NS2501_C0_ENABLE | NS2501_C0_VSYNC, + .conf = NS2501_8_VEN | NS2501_8_HEN | NS2501_8_PD, + .syncb = 0x32, + .dither = 0x0f, + .pll_a = 11, + .pll_b = 1350, + .hstart = 276, + .hstop = 1299, + .vstart = 15, + .vstop = 1056, + .vsync = 2047, + .vtotal = 1341, + .hpos = 0, + .vpos = 7, + .voffs = 27, + .hscale = 65535, + .vscale = 65535 + } +}; + +/* + * Other configuration values left by the BIOS of the + * Fujitsu S6010 in the DVO control registers. Their + * value does not depend on the BIOS and their meaning + * is unknown. + */ + +static const struct ns2501_reg mode_agnostic_values[] = { + /* 08 is mode specific */ + [0] = { .offset = 0x0a, .value = 0x81, }, + /* 10,11 are part of the mode specific configuration */ + [1] = { .offset = 0x12, .value = 0x02, }, + [2] = { .offset = 0x18, .value = 0x07, }, + [3] = { .offset = 0x19, .value = 0x00, }, + [4] = { .offset = 0x1a, .value = 0x00, }, /* PLL?, ignored */ + /* 1b,1c,1d are part of the mode specific configuration */ + [5] = { .offset = 0x1e, .value = 0x02, }, + [6] = { .offset = 0x1f, .value = 0x40, }, + [7] = { .offset = 0x20, .value = 0x00, }, + [8] = { .offset = 0x21, .value = 0x00, }, + [9] = { .offset = 0x22, .value = 0x00, }, + [10] = { .offset = 0x23, .value = 0x00, }, + [11] = { .offset = 0x24, .value = 0x00, }, + [12] = { .offset = 0x25, .value = 0x00, }, + [13] = { .offset = 0x26, .value = 0x00, }, + [14] = { .offset = 0x27, .value = 0x00, }, + [15] = { .offset = 0x7e, .value = 0x18, }, + /* 80-84 are part of the mode-specific configuration */ + [16] = { .offset = 0x84, .value = 0x00, }, + [17] = { .offset = 0x85, .value = 0x00, }, + [18] = { .offset = 0x86, .value = 0x00, }, + [19] = { .offset = 0x87, .value = 0x00, }, + [20] = { .offset = 0x88, .value = 0x00, }, + [21] = { .offset = 0x89, .value = 0x00, }, + [22] = { .offset = 0x8a, .value = 0x00, }, + [23] = { .offset = 0x8b, .value = 0x00, }, + [24] = { .offset = 0x8c, .value = 0x10, }, + [25] = { .offset = 0x8d, .value = 0x02, }, + /* 8e,8f are part of the mode-specific configuration */ + [26] = { .offset = 0x90, .value = 0xff, }, + [27] = { .offset = 0x91, .value = 0x07, }, + [28] = { .offset = 0x92, .value = 0xa0, }, + [29] = { .offset = 0x93, .value = 0x02, }, + [30] = { .offset = 0x94, .value = 0x00, }, + [31] = { .offset = 0x95, .value = 0x00, }, + [32] = { .offset = 0x96, .value = 0x05, }, + [33] = { .offset = 0x97, .value = 0x00, }, + /* 98,99 are part of the mode-specific configuration */ + [34] = { .offset = 0x9a, .value = 0x88, }, + [35] = { .offset = 0x9b, .value = 0x00, }, + /* 9c,9d are part of the mode-specific configuration */ + [36] = { .offset = 0x9e, .value = 0x25, }, + [37] = { .offset = 0x9f, .value = 0x03, }, + [38] = { .offset = 0xa0, .value = 0x28, }, + [39] = { .offset = 0xa1, .value = 0x01, }, + [40] = { .offset = 0xa2, .value = 0x28, }, + [41] = { .offset = 0xa3, .value = 0x05, }, + /* register 0xa4 is mode specific, but 0x80..0x84 works always */ + [42] = { .offset = 0xa4, .value = 0x84, }, + [43] = { .offset = 0xa5, .value = 0x00, }, + [44] = { .offset = 0xa6, .value = 0x00, }, + [45] = { .offset = 0xa7, .value = 0x00, }, + [46] = { .offset = 0xa8, .value = 0x00, }, + /* 0xa9 to 0xab are mode specific, but have no visible effect */ + [47] = { .offset = 0xa9, .value = 0x04, }, + [48] = { .offset = 0xaa, .value = 0x70, }, + [49] = { .offset = 0xab, .value = 0x4f, }, + [50] = { .offset = 0xac, .value = 0x00, }, + [51] = { .offset = 0xad, .value = 0x00, }, + [52] = { .offset = 0xb6, .value = 0x09, }, + [53] = { .offset = 0xb7, .value = 0x03, }, + /* b8,b9 are part of the mode-specific configuration */ + [54] = { .offset = 0xba, .value = 0x00, }, + [55] = { .offset = 0xbb, .value = 0x20, }, + [56] = { .offset = 0xf3, .value = 0x90, }, + [57] = { .offset = 0xf4, .value = 0x00, }, + [58] = { .offset = 0xf7, .value = 0x88, }, + /* f8 is mode specific, but the value does not matter */ + [59] = { .offset = 0xf8, .value = 0x0a, }, + [60] = { .offset = 0xf9, .value = 0x00, } +}; + +static const struct ns2501_reg regs_init[] = { + [0] = { .offset = 0x35, .value = 0xff, }, + [1] = { .offset = 0x34, .value = 0x00, }, + [2] = { .offset = 0x08, .value = 0x30, }, +}; + +struct ns2501_priv { + bool quiet; + const struct ns2501_configuration *conf; +}; + +#define NSPTR(d) ((NS2501Ptr)(d->DriverPrivate.ptr)) + +/* +** Read a register from the ns2501. +** Returns true if successful, false otherwise. +** If it returns false, it might be wise to enable the +** DVO with the above function. +*/ +static bool ns2501_readb(struct intel_dvo_device *dvo, int addr, u8 *ch) +{ + struct ns2501_priv *ns = dvo->dev_priv; + struct i2c_adapter *adapter = dvo->i2c_bus; + u8 out_buf[2]; + u8 in_buf[2]; + + struct i2c_msg msgs[] = { + { + .addr = dvo->slave_addr, + .flags = 0, + .len = 1, + .buf = out_buf, + }, + { + .addr = dvo->slave_addr, + .flags = I2C_M_RD, + .len = 1, + .buf = in_buf, + } + }; + + out_buf[0] = addr; + out_buf[1] = 0; + + if (i2c_transfer(adapter, msgs, 2) == 2) { + *ch = in_buf[0]; + return true; + } + + if (!ns->quiet) { + DRM_DEBUG_KMS + ("Unable to read register 0x%02x from %s:0x%02x.\n", addr, + adapter->name, dvo->slave_addr); + } + + return false; +} + +/* +** Write a register to the ns2501. +** Returns true if successful, false otherwise. +** If it returns false, it might be wise to enable the +** DVO with the above function. +*/ +static bool ns2501_writeb(struct intel_dvo_device *dvo, int addr, u8 ch) +{ + struct ns2501_priv *ns = dvo->dev_priv; + struct i2c_adapter *adapter = dvo->i2c_bus; + u8 out_buf[2]; + + struct i2c_msg msg = { + .addr = dvo->slave_addr, + .flags = 0, + .len = 2, + .buf = out_buf, + }; + + out_buf[0] = addr; + out_buf[1] = ch; + + if (i2c_transfer(adapter, &msg, 1) == 1) { + return true; + } + + if (!ns->quiet) { + DRM_DEBUG_KMS("Unable to write register 0x%02x to %s:%d\n", + addr, adapter->name, dvo->slave_addr); + } + + return false; +} + +/* National Semiconductor 2501 driver for chip on i2c bus + * scan for the chip on the bus. + * Hope the VBIOS initialized the PLL correctly so we can + * talk to it. If not, it will not be seen and not detected. + * Bummer! + */ +static bool ns2501_init(struct intel_dvo_device *dvo, + struct i2c_adapter *adapter) +{ + /* this will detect the NS2501 chip on the specified i2c bus */ + struct ns2501_priv *ns; + unsigned char ch; + + ns = kzalloc(sizeof(struct ns2501_priv), GFP_KERNEL); + if (ns == NULL) + return false; + + dvo->i2c_bus = adapter; + dvo->dev_priv = ns; + ns->quiet = true; + + if (!ns2501_readb(dvo, NS2501_VID_LO, &ch)) + goto out; + + if (ch != (NS2501_VID & 0xff)) { + DRM_DEBUG_KMS("ns2501 not detected got %d: from %s Slave %d.\n", + ch, adapter->name, dvo->slave_addr); + goto out; + } + + if (!ns2501_readb(dvo, NS2501_DID_LO, &ch)) + goto out; + + if (ch != (NS2501_DID & 0xff)) { + DRM_DEBUG_KMS("ns2501 not detected got %d: from %s Slave %d.\n", + ch, adapter->name, dvo->slave_addr); + goto out; + } + ns->quiet = false; + + DRM_DEBUG_KMS("init ns2501 dvo controller successfully!\n"); + + return true; + +out: + kfree(ns); + return false; +} + +static enum drm_connector_status ns2501_detect(struct intel_dvo_device *dvo) +{ + /* + * This is a Laptop display, it doesn't have hotplugging. + * Even if not, the detection bit of the 2501 is unreliable as + * it only works for some display types. + * It is even more unreliable as the PLL must be active for + * allowing reading from the chiop. + */ + return connector_status_connected; +} + +static enum drm_mode_status ns2501_mode_valid(struct intel_dvo_device *dvo, + struct drm_display_mode *mode) +{ + DRM_DEBUG_KMS + ("is mode valid (hdisplay=%d,htotal=%d,vdisplay=%d,vtotal=%d)\n", + mode->hdisplay, mode->htotal, mode->vdisplay, mode->vtotal); + + /* + * Currently, these are all the modes I have data from. + * More might exist. Unclear how to find the native resolution + * of the panel in here so we could always accept it + * by disabling the scaler. + */ + if ((mode->hdisplay == 640 && mode->vdisplay == 480 && mode->clock == 25175) || + (mode->hdisplay == 800 && mode->vdisplay == 600 && mode->clock == 40000) || + (mode->hdisplay == 1024 && mode->vdisplay == 768 && mode->clock == 65000)) { + return MODE_OK; + } else { + return MODE_ONE_SIZE; /* Is this a reasonable error? */ + } +} + +static void ns2501_mode_set(struct intel_dvo_device *dvo, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + const struct ns2501_configuration *conf; + struct ns2501_priv *ns = (struct ns2501_priv *)(dvo->dev_priv); + int mode_idx, i; + + DRM_DEBUG_KMS + ("set mode (hdisplay=%d,htotal=%d,vdisplay=%d,vtotal=%d).\n", + mode->hdisplay, mode->htotal, mode->vdisplay, mode->vtotal); + + DRM_DEBUG_KMS("Detailed requested mode settings are:\n" + "clock : %d kHz\n" + "hdisplay : %d\n" + "hblank start : %d\n" + "hblank end : %d\n" + "hsync start : %d\n" + "hsync end : %d\n" + "htotal : %d\n" + "hskew : %d\n" + "vdisplay : %d\n" + "vblank start : %d\n" + "hblank end : %d\n" + "vsync start : %d\n" + "vsync end : %d\n" + "vtotal : %d\n", + adjusted_mode->crtc_clock, + adjusted_mode->crtc_hdisplay, + adjusted_mode->crtc_hblank_start, + adjusted_mode->crtc_hblank_end, + adjusted_mode->crtc_hsync_start, + adjusted_mode->crtc_hsync_end, + adjusted_mode->crtc_htotal, + adjusted_mode->crtc_hskew, + adjusted_mode->crtc_vdisplay, + adjusted_mode->crtc_vblank_start, + adjusted_mode->crtc_vblank_end, + adjusted_mode->crtc_vsync_start, + adjusted_mode->crtc_vsync_end, + adjusted_mode->crtc_vtotal); + + if (mode->hdisplay == 640 && mode->vdisplay == 480) + mode_idx = MODE_640x480; + else if (mode->hdisplay == 800 && mode->vdisplay == 600) + mode_idx = MODE_800x600; + else if (mode->hdisplay == 1024 && mode->vdisplay == 768) + mode_idx = MODE_1024x768; + else + return; + + /* Hopefully doing it every time won't hurt... */ + for (i = 0; i < ARRAY_SIZE(regs_init); i++) + ns2501_writeb(dvo, regs_init[i].offset, regs_init[i].value); + + /* Write the mode-agnostic values */ + for (i = 0; i < ARRAY_SIZE(mode_agnostic_values); i++) + ns2501_writeb(dvo, mode_agnostic_values[i].offset, + mode_agnostic_values[i].value); + + /* Write now the mode-specific configuration */ + conf = ns2501_modes + mode_idx; + ns->conf = conf; + + ns2501_writeb(dvo, NS2501_REG8, conf->conf); + ns2501_writeb(dvo, NS2501_REG1B, conf->pll_a); + ns2501_writeb(dvo, NS2501_REG1C, conf->pll_b & 0xff); + ns2501_writeb(dvo, NS2501_REG1D, conf->pll_b >> 8); + ns2501_writeb(dvo, NS2501_REGC1, conf->hstart & 0xff); + ns2501_writeb(dvo, NS2501_REGC2, conf->hstart >> 8); + ns2501_writeb(dvo, NS2501_REGC3, conf->hstop & 0xff); + ns2501_writeb(dvo, NS2501_REGC4, conf->hstop >> 8); + ns2501_writeb(dvo, NS2501_REGC5, conf->vstart & 0xff); + ns2501_writeb(dvo, NS2501_REGC6, conf->vstart >> 8); + ns2501_writeb(dvo, NS2501_REGC7, conf->vstop & 0xff); + ns2501_writeb(dvo, NS2501_REGC8, conf->vstop >> 8); + ns2501_writeb(dvo, NS2501_REG80, conf->vsync & 0xff); + ns2501_writeb(dvo, NS2501_REG81, conf->vsync >> 8); + ns2501_writeb(dvo, NS2501_REG82, conf->vtotal & 0xff); + ns2501_writeb(dvo, NS2501_REG83, conf->vtotal >> 8); + ns2501_writeb(dvo, NS2501_REG98, conf->hpos & 0xff); + ns2501_writeb(dvo, NS2501_REG99, conf->hpos >> 8); + ns2501_writeb(dvo, NS2501_REG8E, conf->vpos & 0xff); + ns2501_writeb(dvo, NS2501_REG8F, conf->vpos >> 8); + ns2501_writeb(dvo, NS2501_REG9C, conf->voffs & 0xff); + ns2501_writeb(dvo, NS2501_REG9D, conf->voffs >> 8); + ns2501_writeb(dvo, NS2501_REGB8, conf->hscale & 0xff); + ns2501_writeb(dvo, NS2501_REGB9, conf->hscale >> 8); + ns2501_writeb(dvo, NS2501_REG10, conf->vscale & 0xff); + ns2501_writeb(dvo, NS2501_REG11, conf->vscale >> 8); + ns2501_writeb(dvo, NS2501_REGF9, conf->dither); + ns2501_writeb(dvo, NS2501_REG41, conf->syncb); + ns2501_writeb(dvo, NS2501_REGC0, conf->sync); +} + +/* set the NS2501 power state */ +static bool ns2501_get_hw_state(struct intel_dvo_device *dvo) +{ + unsigned char ch; + + if (!ns2501_readb(dvo, NS2501_REG8, &ch)) + return false; + + return ch & NS2501_8_PD; +} + +/* set the NS2501 power state */ +static void ns2501_dpms(struct intel_dvo_device *dvo, bool enable) +{ + struct ns2501_priv *ns = (struct ns2501_priv *)(dvo->dev_priv); + + DRM_DEBUG_KMS("Trying set the dpms of the DVO to %i\n", enable); + + if (enable) { + ns2501_writeb(dvo, NS2501_REGC0, ns->conf->sync | 0x08); + + ns2501_writeb(dvo, NS2501_REG41, ns->conf->syncb); + + ns2501_writeb(dvo, NS2501_REG34, NS2501_34_ENABLE_OUTPUT); + msleep(15); + + ns2501_writeb(dvo, NS2501_REG8, + ns->conf->conf | NS2501_8_BPAS); + if (!(ns->conf->conf & NS2501_8_BPAS)) + ns2501_writeb(dvo, NS2501_REG8, ns->conf->conf); + msleep(200); + + ns2501_writeb(dvo, NS2501_REG34, + NS2501_34_ENABLE_OUTPUT | NS2501_34_ENABLE_BACKLIGHT); + + ns2501_writeb(dvo, NS2501_REGC0, ns->conf->sync); + } else { + ns2501_writeb(dvo, NS2501_REG34, NS2501_34_ENABLE_OUTPUT); + msleep(200); + + ns2501_writeb(dvo, NS2501_REG8, NS2501_8_VEN | NS2501_8_HEN | + NS2501_8_BPAS); + msleep(15); + + ns2501_writeb(dvo, NS2501_REG34, 0x00); + } +} + +static void ns2501_destroy(struct intel_dvo_device *dvo) +{ + struct ns2501_priv *ns = dvo->dev_priv; + + if (ns) { + kfree(ns); + dvo->dev_priv = NULL; + } +} + +const struct intel_dvo_dev_ops ns2501_ops = { + .init = ns2501_init, + .detect = ns2501_detect, + .mode_valid = ns2501_mode_valid, + .mode_set = ns2501_mode_set, + .dpms = ns2501_dpms, + .get_hw_state = ns2501_get_hw_state, + .destroy = ns2501_destroy, +}; diff --git a/drivers/gpu/drm/i915/display/dvo_sil164.c b/drivers/gpu/drm/i915/display/dvo_sil164.c new file mode 100644 index 000000000..0dfa0a020 --- /dev/null +++ b/drivers/gpu/drm/i915/display/dvo_sil164.c @@ -0,0 +1,280 @@ +/************************************************************************** + +Copyright © 2006 Dave Airlie + +All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sub license, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice (including the +next paragraph) shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +**************************************************************************/ + +#include "intel_display_types.h" +#include "intel_dvo_dev.h" + +#define SIL164_VID 0x0001 +#define SIL164_DID 0x0006 + +#define SIL164_VID_LO 0x00 +#define SIL164_VID_HI 0x01 +#define SIL164_DID_LO 0x02 +#define SIL164_DID_HI 0x03 +#define SIL164_REV 0x04 +#define SIL164_RSVD 0x05 +#define SIL164_FREQ_LO 0x06 +#define SIL164_FREQ_HI 0x07 + +#define SIL164_REG8 0x08 +#define SIL164_8_VEN (1<<5) +#define SIL164_8_HEN (1<<4) +#define SIL164_8_DSEL (1<<3) +#define SIL164_8_BSEL (1<<2) +#define SIL164_8_EDGE (1<<1) +#define SIL164_8_PD (1<<0) + +#define SIL164_REG9 0x09 +#define SIL164_9_VLOW (1<<7) +#define SIL164_9_MSEL_MASK (0x7<<4) +#define SIL164_9_TSEL (1<<3) +#define SIL164_9_RSEN (1<<2) +#define SIL164_9_HTPLG (1<<1) +#define SIL164_9_MDI (1<<0) + +#define SIL164_REGC 0x0c + +struct sil164_priv { + //I2CDevRec d; + bool quiet; +}; + +#define SILPTR(d) ((SIL164Ptr)(d->DriverPrivate.ptr)) + +static bool sil164_readb(struct intel_dvo_device *dvo, int addr, u8 *ch) +{ + struct sil164_priv *sil = dvo->dev_priv; + struct i2c_adapter *adapter = dvo->i2c_bus; + u8 out_buf[2]; + u8 in_buf[2]; + + struct i2c_msg msgs[] = { + { + .addr = dvo->slave_addr, + .flags = 0, + .len = 1, + .buf = out_buf, + }, + { + .addr = dvo->slave_addr, + .flags = I2C_M_RD, + .len = 1, + .buf = in_buf, + } + }; + + out_buf[0] = addr; + out_buf[1] = 0; + + if (i2c_transfer(adapter, msgs, 2) == 2) { + *ch = in_buf[0]; + return true; + } + + if (!sil->quiet) { + DRM_DEBUG_KMS("Unable to read register 0x%02x from %s:%02x.\n", + addr, adapter->name, dvo->slave_addr); + } + return false; +} + +static bool sil164_writeb(struct intel_dvo_device *dvo, int addr, u8 ch) +{ + struct sil164_priv *sil = dvo->dev_priv; + struct i2c_adapter *adapter = dvo->i2c_bus; + u8 out_buf[2]; + struct i2c_msg msg = { + .addr = dvo->slave_addr, + .flags = 0, + .len = 2, + .buf = out_buf, + }; + + out_buf[0] = addr; + out_buf[1] = ch; + + if (i2c_transfer(adapter, &msg, 1) == 1) + return true; + + if (!sil->quiet) { + DRM_DEBUG_KMS("Unable to write register 0x%02x to %s:%d.\n", + addr, adapter->name, dvo->slave_addr); + } + + return false; +} + +/* Silicon Image 164 driver for chip on i2c bus */ +static bool sil164_init(struct intel_dvo_device *dvo, + struct i2c_adapter *adapter) +{ + /* this will detect the SIL164 chip on the specified i2c bus */ + struct sil164_priv *sil; + unsigned char ch; + + sil = kzalloc(sizeof(struct sil164_priv), GFP_KERNEL); + if (sil == NULL) + return false; + + dvo->i2c_bus = adapter; + dvo->dev_priv = sil; + sil->quiet = true; + + if (!sil164_readb(dvo, SIL164_VID_LO, &ch)) + goto out; + + if (ch != (SIL164_VID & 0xff)) { + DRM_DEBUG_KMS("sil164 not detected got %d: from %s Slave %d.\n", + ch, adapter->name, dvo->slave_addr); + goto out; + } + + if (!sil164_readb(dvo, SIL164_DID_LO, &ch)) + goto out; + + if (ch != (SIL164_DID & 0xff)) { + DRM_DEBUG_KMS("sil164 not detected got %d: from %s Slave %d.\n", + ch, adapter->name, dvo->slave_addr); + goto out; + } + sil->quiet = false; + + DRM_DEBUG_KMS("init sil164 dvo controller successfully!\n"); + return true; + +out: + kfree(sil); + return false; +} + +static enum drm_connector_status sil164_detect(struct intel_dvo_device *dvo) +{ + u8 reg9; + + sil164_readb(dvo, SIL164_REG9, ®9); + + if (reg9 & SIL164_9_HTPLG) + return connector_status_connected; + else + return connector_status_disconnected; +} + +static enum drm_mode_status sil164_mode_valid(struct intel_dvo_device *dvo, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static void sil164_mode_set(struct intel_dvo_device *dvo, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + /* As long as the basics are set up, since we don't have clock + * dependencies in the mode setup, we can just leave the + * registers alone and everything will work fine. + */ + /* recommended programming sequence from doc */ + /*sil164_writeb(sil, 0x08, 0x30); + sil164_writeb(sil, 0x09, 0x00); + sil164_writeb(sil, 0x0a, 0x90); + sil164_writeb(sil, 0x0c, 0x89); + sil164_writeb(sil, 0x08, 0x31);*/ + /* don't do much */ + return; +} + +/* set the SIL164 power state */ +static void sil164_dpms(struct intel_dvo_device *dvo, bool enable) +{ + int ret; + unsigned char ch; + + ret = sil164_readb(dvo, SIL164_REG8, &ch); + if (ret == false) + return; + + if (enable) + ch |= SIL164_8_PD; + else + ch &= ~SIL164_8_PD; + + sil164_writeb(dvo, SIL164_REG8, ch); + return; +} + +static bool sil164_get_hw_state(struct intel_dvo_device *dvo) +{ + int ret; + unsigned char ch; + + ret = sil164_readb(dvo, SIL164_REG8, &ch); + if (ret == false) + return false; + + if (ch & SIL164_8_PD) + return true; + else + return false; +} + +static void sil164_dump_regs(struct intel_dvo_device *dvo) +{ + u8 val; + + sil164_readb(dvo, SIL164_FREQ_LO, &val); + DRM_DEBUG_KMS("SIL164_FREQ_LO: 0x%02x\n", val); + sil164_readb(dvo, SIL164_FREQ_HI, &val); + DRM_DEBUG_KMS("SIL164_FREQ_HI: 0x%02x\n", val); + sil164_readb(dvo, SIL164_REG8, &val); + DRM_DEBUG_KMS("SIL164_REG8: 0x%02x\n", val); + sil164_readb(dvo, SIL164_REG9, &val); + DRM_DEBUG_KMS("SIL164_REG9: 0x%02x\n", val); + sil164_readb(dvo, SIL164_REGC, &val); + DRM_DEBUG_KMS("SIL164_REGC: 0x%02x\n", val); +} + +static void sil164_destroy(struct intel_dvo_device *dvo) +{ + struct sil164_priv *sil = dvo->dev_priv; + + if (sil) { + kfree(sil); + dvo->dev_priv = NULL; + } +} + +const struct intel_dvo_dev_ops sil164_ops = { + .init = sil164_init, + .detect = sil164_detect, + .mode_valid = sil164_mode_valid, + .mode_set = sil164_mode_set, + .dpms = sil164_dpms, + .get_hw_state = sil164_get_hw_state, + .dump_regs = sil164_dump_regs, + .destroy = sil164_destroy, +}; diff --git a/drivers/gpu/drm/i915/display/dvo_tfp410.c b/drivers/gpu/drm/i915/display/dvo_tfp410.c new file mode 100644 index 000000000..009d65b0f --- /dev/null +++ b/drivers/gpu/drm/i915/display/dvo_tfp410.c @@ -0,0 +1,319 @@ +/* + * Copyright © 2007 Dave Mueller + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Authors: + * Dave Mueller + * + */ + +#include "intel_display_types.h" +#include "intel_dvo_dev.h" + +/* register definitions according to the TFP410 data sheet */ +#define TFP410_VID 0x014C +#define TFP410_DID 0x0410 + +#define TFP410_VID_LO 0x00 +#define TFP410_VID_HI 0x01 +#define TFP410_DID_LO 0x02 +#define TFP410_DID_HI 0x03 +#define TFP410_REV 0x04 + +#define TFP410_CTL_1 0x08 +#define TFP410_CTL_1_TDIS (1<<6) +#define TFP410_CTL_1_VEN (1<<5) +#define TFP410_CTL_1_HEN (1<<4) +#define TFP410_CTL_1_DSEL (1<<3) +#define TFP410_CTL_1_BSEL (1<<2) +#define TFP410_CTL_1_EDGE (1<<1) +#define TFP410_CTL_1_PD (1<<0) + +#define TFP410_CTL_2 0x09 +#define TFP410_CTL_2_VLOW (1<<7) +#define TFP410_CTL_2_MSEL_MASK (0x7<<4) +#define TFP410_CTL_2_MSEL (1<<4) +#define TFP410_CTL_2_TSEL (1<<3) +#define TFP410_CTL_2_RSEN (1<<2) +#define TFP410_CTL_2_HTPLG (1<<1) +#define TFP410_CTL_2_MDI (1<<0) + +#define TFP410_CTL_3 0x0A +#define TFP410_CTL_3_DK_MASK (0x7<<5) +#define TFP410_CTL_3_DK (1<<5) +#define TFP410_CTL_3_DKEN (1<<4) +#define TFP410_CTL_3_CTL_MASK (0x7<<1) +#define TFP410_CTL_3_CTL (1<<1) + +#define TFP410_USERCFG 0x0B + +#define TFP410_DE_DLY 0x32 + +#define TFP410_DE_CTL 0x33 +#define TFP410_DE_CTL_DEGEN (1<<6) +#define TFP410_DE_CTL_VSPOL (1<<5) +#define TFP410_DE_CTL_HSPOL (1<<4) +#define TFP410_DE_CTL_DEDLY8 (1<<0) + +#define TFP410_DE_TOP 0x34 + +#define TFP410_DE_CNT_LO 0x36 +#define TFP410_DE_CNT_HI 0x37 + +#define TFP410_DE_LIN_LO 0x38 +#define TFP410_DE_LIN_HI 0x39 + +#define TFP410_H_RES_LO 0x3A +#define TFP410_H_RES_HI 0x3B + +#define TFP410_V_RES_LO 0x3C +#define TFP410_V_RES_HI 0x3D + +struct tfp410_priv { + bool quiet; +}; + +static bool tfp410_readb(struct intel_dvo_device *dvo, int addr, u8 *ch) +{ + struct tfp410_priv *tfp = dvo->dev_priv; + struct i2c_adapter *adapter = dvo->i2c_bus; + u8 out_buf[2]; + u8 in_buf[2]; + + struct i2c_msg msgs[] = { + { + .addr = dvo->slave_addr, + .flags = 0, + .len = 1, + .buf = out_buf, + }, + { + .addr = dvo->slave_addr, + .flags = I2C_M_RD, + .len = 1, + .buf = in_buf, + } + }; + + out_buf[0] = addr; + out_buf[1] = 0; + + if (i2c_transfer(adapter, msgs, 2) == 2) { + *ch = in_buf[0]; + return true; + } + + if (!tfp->quiet) { + DRM_DEBUG_KMS("Unable to read register 0x%02x from %s:%02x.\n", + addr, adapter->name, dvo->slave_addr); + } + return false; +} + +static bool tfp410_writeb(struct intel_dvo_device *dvo, int addr, u8 ch) +{ + struct tfp410_priv *tfp = dvo->dev_priv; + struct i2c_adapter *adapter = dvo->i2c_bus; + u8 out_buf[2]; + struct i2c_msg msg = { + .addr = dvo->slave_addr, + .flags = 0, + .len = 2, + .buf = out_buf, + }; + + out_buf[0] = addr; + out_buf[1] = ch; + + if (i2c_transfer(adapter, &msg, 1) == 1) + return true; + + if (!tfp->quiet) { + DRM_DEBUG_KMS("Unable to write register 0x%02x to %s:%d.\n", + addr, adapter->name, dvo->slave_addr); + } + + return false; +} + +static int tfp410_getid(struct intel_dvo_device *dvo, int addr) +{ + u8 ch1, ch2; + + if (tfp410_readb(dvo, addr+0, &ch1) && + tfp410_readb(dvo, addr+1, &ch2)) + return ((ch2 << 8) & 0xFF00) | (ch1 & 0x00FF); + + return -1; +} + +/* Ti TFP410 driver for chip on i2c bus */ +static bool tfp410_init(struct intel_dvo_device *dvo, + struct i2c_adapter *adapter) +{ + /* this will detect the tfp410 chip on the specified i2c bus */ + struct tfp410_priv *tfp; + int id; + + tfp = kzalloc(sizeof(struct tfp410_priv), GFP_KERNEL); + if (tfp == NULL) + return false; + + dvo->i2c_bus = adapter; + dvo->dev_priv = tfp; + tfp->quiet = true; + + if ((id = tfp410_getid(dvo, TFP410_VID_LO)) != TFP410_VID) { + DRM_DEBUG_KMS("tfp410 not detected got VID %X: from %s " + "Slave %d.\n", + id, adapter->name, dvo->slave_addr); + goto out; + } + + if ((id = tfp410_getid(dvo, TFP410_DID_LO)) != TFP410_DID) { + DRM_DEBUG_KMS("tfp410 not detected got DID %X: from %s " + "Slave %d.\n", + id, adapter->name, dvo->slave_addr); + goto out; + } + tfp->quiet = false; + return true; +out: + kfree(tfp); + return false; +} + +static enum drm_connector_status tfp410_detect(struct intel_dvo_device *dvo) +{ + enum drm_connector_status ret = connector_status_disconnected; + u8 ctl2; + + if (tfp410_readb(dvo, TFP410_CTL_2, &ctl2)) { + if (ctl2 & TFP410_CTL_2_RSEN) + ret = connector_status_connected; + else + ret = connector_status_disconnected; + } + + return ret; +} + +static enum drm_mode_status tfp410_mode_valid(struct intel_dvo_device *dvo, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static void tfp410_mode_set(struct intel_dvo_device *dvo, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + /* As long as the basics are set up, since we don't have clock dependencies + * in the mode setup, we can just leave the registers alone and everything + * will work fine. + */ + /* don't do much */ + return; +} + +/* set the tfp410 power state */ +static void tfp410_dpms(struct intel_dvo_device *dvo, bool enable) +{ + u8 ctl1; + + if (!tfp410_readb(dvo, TFP410_CTL_1, &ctl1)) + return; + + if (enable) + ctl1 |= TFP410_CTL_1_PD; + else + ctl1 &= ~TFP410_CTL_1_PD; + + tfp410_writeb(dvo, TFP410_CTL_1, ctl1); +} + +static bool tfp410_get_hw_state(struct intel_dvo_device *dvo) +{ + u8 ctl1; + + if (!tfp410_readb(dvo, TFP410_CTL_1, &ctl1)) + return false; + + if (ctl1 & TFP410_CTL_1_PD) + return true; + else + return false; +} + +static void tfp410_dump_regs(struct intel_dvo_device *dvo) +{ + u8 val, val2; + + tfp410_readb(dvo, TFP410_REV, &val); + DRM_DEBUG_KMS("TFP410_REV: 0x%02X\n", val); + tfp410_readb(dvo, TFP410_CTL_1, &val); + DRM_DEBUG_KMS("TFP410_CTL1: 0x%02X\n", val); + tfp410_readb(dvo, TFP410_CTL_2, &val); + DRM_DEBUG_KMS("TFP410_CTL2: 0x%02X\n", val); + tfp410_readb(dvo, TFP410_CTL_3, &val); + DRM_DEBUG_KMS("TFP410_CTL3: 0x%02X\n", val); + tfp410_readb(dvo, TFP410_USERCFG, &val); + DRM_DEBUG_KMS("TFP410_USERCFG: 0x%02X\n", val); + tfp410_readb(dvo, TFP410_DE_DLY, &val); + DRM_DEBUG_KMS("TFP410_DE_DLY: 0x%02X\n", val); + tfp410_readb(dvo, TFP410_DE_CTL, &val); + DRM_DEBUG_KMS("TFP410_DE_CTL: 0x%02X\n", val); + tfp410_readb(dvo, TFP410_DE_TOP, &val); + DRM_DEBUG_KMS("TFP410_DE_TOP: 0x%02X\n", val); + tfp410_readb(dvo, TFP410_DE_CNT_LO, &val); + tfp410_readb(dvo, TFP410_DE_CNT_HI, &val2); + DRM_DEBUG_KMS("TFP410_DE_CNT: 0x%02X%02X\n", val2, val); + tfp410_readb(dvo, TFP410_DE_LIN_LO, &val); + tfp410_readb(dvo, TFP410_DE_LIN_HI, &val2); + DRM_DEBUG_KMS("TFP410_DE_LIN: 0x%02X%02X\n", val2, val); + tfp410_readb(dvo, TFP410_H_RES_LO, &val); + tfp410_readb(dvo, TFP410_H_RES_HI, &val2); + DRM_DEBUG_KMS("TFP410_H_RES: 0x%02X%02X\n", val2, val); + tfp410_readb(dvo, TFP410_V_RES_LO, &val); + tfp410_readb(dvo, TFP410_V_RES_HI, &val2); + DRM_DEBUG_KMS("TFP410_V_RES: 0x%02X%02X\n", val2, val); +} + +static void tfp410_destroy(struct intel_dvo_device *dvo) +{ + struct tfp410_priv *tfp = dvo->dev_priv; + + if (tfp) { + kfree(tfp); + dvo->dev_priv = NULL; + } +} + +const struct intel_dvo_dev_ops tfp410_ops = { + .init = tfp410_init, + .detect = tfp410_detect, + .mode_valid = tfp410_mode_valid, + .mode_set = tfp410_mode_set, + .dpms = tfp410_dpms, + .get_hw_state = tfp410_get_hw_state, + .dump_regs = tfp410_dump_regs, + .destroy = tfp410_destroy, +}; diff --git a/drivers/gpu/drm/i915/display/g4x_dp.c b/drivers/gpu/drm/i915/display/g4x_dp.c new file mode 100644 index 000000000..e3e3d27ff --- /dev/null +++ b/drivers/gpu/drm/i915/display/g4x_dp.c @@ -0,0 +1,1404 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2020 Intel Corporation + * + * DisplayPort support for G4x,ILK,SNB,IVB,VLV,CHV (HSW+ handled by the DDI code). + */ + +#include + +#include "g4x_dp.h" +#include "intel_audio.h" +#include "intel_backlight.h" +#include "intel_connector.h" +#include "intel_crtc.h" +#include "intel_de.h" +#include "intel_display_power.h" +#include "intel_display_types.h" +#include "intel_dp.h" +#include "intel_dp_link_training.h" +#include "intel_dpio_phy.h" +#include "intel_fifo_underrun.h" +#include "intel_hdmi.h" +#include "intel_hotplug.h" +#include "intel_pch_display.h" +#include "intel_pps.h" +#include "vlv_sideband.h" + +static const struct dpll g4x_dpll[] = { + { .dot = 162000, .p1 = 2, .p2 = 10, .n = 2, .m1 = 23, .m2 = 8, }, + { .dot = 270000, .p1 = 1, .p2 = 10, .n = 1, .m1 = 14, .m2 = 2, }, +}; + +static const struct dpll pch_dpll[] = { + { .dot = 162000, .p1 = 2, .p2 = 10, .n = 1, .m1 = 12, .m2 = 9, }, + { .dot = 270000, .p1 = 1, .p2 = 10, .n = 2, .m1 = 14, .m2 = 8, }, +}; + +static const struct dpll vlv_dpll[] = { + { .dot = 162000, .p1 = 3, .p2 = 2, .n = 5, .m1 = 3, .m2 = 81, }, + { .dot = 270000, .p1 = 2, .p2 = 2, .n = 1, .m1 = 2, .m2 = 27, }, +}; + +static const struct dpll chv_dpll[] = { + /* m2 is .22 binary fixed point */ + { .dot = 162000, .p1 = 4, .p2 = 2, .n = 1, .m1 = 2, .m2 = 0x819999a /* 32.4 */ }, + { .dot = 270000, .p1 = 4, .p2 = 1, .n = 1, .m1 = 2, .m2 = 0x6c00000 /* 27.0 */ }, +}; + +const struct dpll *vlv_get_dpll(struct drm_i915_private *i915) +{ + return IS_CHERRYVIEW(i915) ? &chv_dpll[0] : &vlv_dpll[0]; +} + +void g4x_dp_set_clock(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + const struct dpll *divisor = NULL; + int i, count = 0; + + if (IS_G4X(dev_priv)) { + divisor = g4x_dpll; + count = ARRAY_SIZE(g4x_dpll); + } else if (HAS_PCH_SPLIT(dev_priv)) { + divisor = pch_dpll; + count = ARRAY_SIZE(pch_dpll); + } else if (IS_CHERRYVIEW(dev_priv)) { + divisor = chv_dpll; + count = ARRAY_SIZE(chv_dpll); + } else if (IS_VALLEYVIEW(dev_priv)) { + divisor = vlv_dpll; + count = ARRAY_SIZE(vlv_dpll); + } + + if (divisor && count) { + for (i = 0; i < count; i++) { + if (pipe_config->port_clock == divisor[i].dot) { + pipe_config->dpll = divisor[i]; + pipe_config->clock_set = true; + break; + } + } + } +} + +static void intel_dp_prepare(struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + enum port port = encoder->port; + struct intel_crtc *crtc = to_intel_crtc(pipe_config->uapi.crtc); + const struct drm_display_mode *adjusted_mode = &pipe_config->hw.adjusted_mode; + + intel_dp_set_link_params(intel_dp, + pipe_config->port_clock, + pipe_config->lane_count); + + /* + * There are four kinds of DP registers: + * IBX PCH + * SNB CPU + * IVB CPU + * CPT PCH + * + * IBX PCH and CPU are the same for almost everything, + * except that the CPU DP PLL is configured in this + * register + * + * CPT PCH is quite different, having many bits moved + * to the TRANS_DP_CTL register instead. That + * configuration happens (oddly) in ilk_pch_enable + */ + + /* Preserve the BIOS-computed detected bit. This is + * supposed to be read-only. + */ + intel_dp->DP = intel_de_read(dev_priv, intel_dp->output_reg) & DP_DETECTED; + + /* Handle DP bits in common between all three register formats */ + intel_dp->DP |= DP_VOLTAGE_0_4 | DP_PRE_EMPHASIS_0; + intel_dp->DP |= DP_PORT_WIDTH(pipe_config->lane_count); + + /* Split out the IBX/CPU vs CPT settings */ + + if (IS_IVYBRIDGE(dev_priv) && port == PORT_A) { + if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) + intel_dp->DP |= DP_SYNC_HS_HIGH; + if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) + intel_dp->DP |= DP_SYNC_VS_HIGH; + intel_dp->DP |= DP_LINK_TRAIN_OFF_CPT; + + if (drm_dp_enhanced_frame_cap(intel_dp->dpcd)) + intel_dp->DP |= DP_ENHANCED_FRAMING; + + intel_dp->DP |= DP_PIPE_SEL_IVB(crtc->pipe); + } else if (HAS_PCH_CPT(dev_priv) && port != PORT_A) { + u32 trans_dp; + + intel_dp->DP |= DP_LINK_TRAIN_OFF_CPT; + + trans_dp = intel_de_read(dev_priv, TRANS_DP_CTL(crtc->pipe)); + if (drm_dp_enhanced_frame_cap(intel_dp->dpcd)) + trans_dp |= TRANS_DP_ENH_FRAMING; + else + trans_dp &= ~TRANS_DP_ENH_FRAMING; + intel_de_write(dev_priv, TRANS_DP_CTL(crtc->pipe), trans_dp); + } else { + if (IS_G4X(dev_priv) && pipe_config->limited_color_range) + intel_dp->DP |= DP_COLOR_RANGE_16_235; + + if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) + intel_dp->DP |= DP_SYNC_HS_HIGH; + if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) + intel_dp->DP |= DP_SYNC_VS_HIGH; + intel_dp->DP |= DP_LINK_TRAIN_OFF; + + if (drm_dp_enhanced_frame_cap(intel_dp->dpcd)) + intel_dp->DP |= DP_ENHANCED_FRAMING; + + if (IS_CHERRYVIEW(dev_priv)) + intel_dp->DP |= DP_PIPE_SEL_CHV(crtc->pipe); + else + intel_dp->DP |= DP_PIPE_SEL(crtc->pipe); + } +} + +static void assert_dp_port(struct intel_dp *intel_dp, bool state) +{ + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev); + bool cur_state = intel_de_read(dev_priv, intel_dp->output_reg) & DP_PORT_EN; + + I915_STATE_WARN(cur_state != state, + "[ENCODER:%d:%s] state assertion failure (expected %s, current %s)\n", + dig_port->base.base.base.id, dig_port->base.base.name, + str_on_off(state), str_on_off(cur_state)); +} +#define assert_dp_port_disabled(d) assert_dp_port((d), false) + +static void assert_edp_pll(struct drm_i915_private *dev_priv, bool state) +{ + bool cur_state = intel_de_read(dev_priv, DP_A) & DP_PLL_ENABLE; + + I915_STATE_WARN(cur_state != state, + "eDP PLL state assertion failure (expected %s, current %s)\n", + str_on_off(state), str_on_off(cur_state)); +} +#define assert_edp_pll_enabled(d) assert_edp_pll((d), true) +#define assert_edp_pll_disabled(d) assert_edp_pll((d), false) + +static void ilk_edp_pll_on(struct intel_dp *intel_dp, + const struct intel_crtc_state *pipe_config) +{ + struct intel_crtc *crtc = to_intel_crtc(pipe_config->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + + assert_transcoder_disabled(dev_priv, pipe_config->cpu_transcoder); + assert_dp_port_disabled(intel_dp); + assert_edp_pll_disabled(dev_priv); + + drm_dbg_kms(&dev_priv->drm, "enabling eDP PLL for clock %d\n", + pipe_config->port_clock); + + intel_dp->DP &= ~DP_PLL_FREQ_MASK; + + if (pipe_config->port_clock == 162000) + intel_dp->DP |= DP_PLL_FREQ_162MHZ; + else + intel_dp->DP |= DP_PLL_FREQ_270MHZ; + + intel_de_write(dev_priv, DP_A, intel_dp->DP); + intel_de_posting_read(dev_priv, DP_A); + udelay(500); + + /* + * [DevILK] Work around required when enabling DP PLL + * while a pipe is enabled going to FDI: + * 1. Wait for the start of vertical blank on the enabled pipe going to FDI + * 2. Program DP PLL enable + */ + if (IS_IRONLAKE(dev_priv)) + intel_wait_for_vblank_if_active(dev_priv, !crtc->pipe); + + intel_dp->DP |= DP_PLL_ENABLE; + + intel_de_write(dev_priv, DP_A, intel_dp->DP); + intel_de_posting_read(dev_priv, DP_A); + udelay(200); +} + +static void ilk_edp_pll_off(struct intel_dp *intel_dp, + const struct intel_crtc_state *old_crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + + assert_transcoder_disabled(dev_priv, old_crtc_state->cpu_transcoder); + assert_dp_port_disabled(intel_dp); + assert_edp_pll_enabled(dev_priv); + + drm_dbg_kms(&dev_priv->drm, "disabling eDP PLL\n"); + + intel_dp->DP &= ~DP_PLL_ENABLE; + + intel_de_write(dev_priv, DP_A, intel_dp->DP); + intel_de_posting_read(dev_priv, DP_A); + udelay(200); +} + +static bool cpt_dp_port_selected(struct drm_i915_private *dev_priv, + enum port port, enum pipe *pipe) +{ + enum pipe p; + + for_each_pipe(dev_priv, p) { + u32 val = intel_de_read(dev_priv, TRANS_DP_CTL(p)); + + if ((val & TRANS_DP_PORT_SEL_MASK) == TRANS_DP_PORT_SEL(port)) { + *pipe = p; + return true; + } + } + + drm_dbg_kms(&dev_priv->drm, "No pipe for DP port %c found\n", + port_name(port)); + + /* must initialize pipe to something for the asserts */ + *pipe = PIPE_A; + + return false; +} + +bool g4x_dp_port_enabled(struct drm_i915_private *dev_priv, + i915_reg_t dp_reg, enum port port, + enum pipe *pipe) +{ + bool ret; + u32 val; + + val = intel_de_read(dev_priv, dp_reg); + + ret = val & DP_PORT_EN; + + /* asserts want to know the pipe even if the port is disabled */ + if (IS_IVYBRIDGE(dev_priv) && port == PORT_A) + *pipe = (val & DP_PIPE_SEL_MASK_IVB) >> DP_PIPE_SEL_SHIFT_IVB; + else if (HAS_PCH_CPT(dev_priv) && port != PORT_A) + ret &= cpt_dp_port_selected(dev_priv, port, pipe); + else if (IS_CHERRYVIEW(dev_priv)) + *pipe = (val & DP_PIPE_SEL_MASK_CHV) >> DP_PIPE_SEL_SHIFT_CHV; + else + *pipe = (val & DP_PIPE_SEL_MASK) >> DP_PIPE_SEL_SHIFT; + + return ret; +} + +static bool intel_dp_get_hw_state(struct intel_encoder *encoder, + enum pipe *pipe) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + intel_wakeref_t wakeref; + bool ret; + + wakeref = intel_display_power_get_if_enabled(dev_priv, + encoder->power_domain); + if (!wakeref) + return false; + + ret = g4x_dp_port_enabled(dev_priv, intel_dp->output_reg, + encoder->port, pipe); + + intel_display_power_put(dev_priv, encoder->power_domain, wakeref); + + return ret; +} + +static void g4x_dp_get_m_n(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + if (crtc_state->has_pch_encoder) { + intel_pch_transcoder_get_m1_n1(crtc, &crtc_state->dp_m_n); + intel_pch_transcoder_get_m2_n2(crtc, &crtc_state->dp_m2_n2); + } else { + intel_cpu_transcoder_get_m1_n1(crtc, crtc_state->cpu_transcoder, + &crtc_state->dp_m_n); + intel_cpu_transcoder_get_m2_n2(crtc, crtc_state->cpu_transcoder, + &crtc_state->dp_m2_n2); + } +} + +static void intel_dp_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + u32 tmp, flags = 0; + enum port port = encoder->port; + struct intel_crtc *crtc = to_intel_crtc(pipe_config->uapi.crtc); + + if (encoder->type == INTEL_OUTPUT_EDP) + pipe_config->output_types |= BIT(INTEL_OUTPUT_EDP); + else + pipe_config->output_types |= BIT(INTEL_OUTPUT_DP); + + tmp = intel_de_read(dev_priv, intel_dp->output_reg); + + pipe_config->has_audio = tmp & DP_AUDIO_OUTPUT_ENABLE && port != PORT_A; + + if (HAS_PCH_CPT(dev_priv) && port != PORT_A) { + u32 trans_dp = intel_de_read(dev_priv, + TRANS_DP_CTL(crtc->pipe)); + + if (trans_dp & TRANS_DP_HSYNC_ACTIVE_HIGH) + flags |= DRM_MODE_FLAG_PHSYNC; + else + flags |= DRM_MODE_FLAG_NHSYNC; + + if (trans_dp & TRANS_DP_VSYNC_ACTIVE_HIGH) + flags |= DRM_MODE_FLAG_PVSYNC; + else + flags |= DRM_MODE_FLAG_NVSYNC; + } else { + if (tmp & DP_SYNC_HS_HIGH) + flags |= DRM_MODE_FLAG_PHSYNC; + else + flags |= DRM_MODE_FLAG_NHSYNC; + + if (tmp & DP_SYNC_VS_HIGH) + flags |= DRM_MODE_FLAG_PVSYNC; + else + flags |= DRM_MODE_FLAG_NVSYNC; + } + + pipe_config->hw.adjusted_mode.flags |= flags; + + if (IS_G4X(dev_priv) && tmp & DP_COLOR_RANGE_16_235) + pipe_config->limited_color_range = true; + + pipe_config->lane_count = + ((tmp & DP_PORT_WIDTH_MASK) >> DP_PORT_WIDTH_SHIFT) + 1; + + g4x_dp_get_m_n(pipe_config); + + if (port == PORT_A) { + if ((intel_de_read(dev_priv, DP_A) & DP_PLL_FREQ_MASK) == DP_PLL_FREQ_162MHZ) + pipe_config->port_clock = 162000; + else + pipe_config->port_clock = 270000; + } + + pipe_config->hw.adjusted_mode.crtc_clock = + intel_dotclock_calculate(pipe_config->port_clock, + &pipe_config->dp_m_n); + + if (intel_dp_is_edp(intel_dp)) + intel_edp_fixup_vbt_bpp(encoder, pipe_config->pipe_bpp); +} + +static void +intel_dp_link_down(struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->uapi.crtc); + enum port port = encoder->port; + + if (drm_WARN_ON(&dev_priv->drm, + (intel_de_read(dev_priv, intel_dp->output_reg) & + DP_PORT_EN) == 0)) + return; + + drm_dbg_kms(&dev_priv->drm, "\n"); + + if ((IS_IVYBRIDGE(dev_priv) && port == PORT_A) || + (HAS_PCH_CPT(dev_priv) && port != PORT_A)) { + intel_dp->DP &= ~DP_LINK_TRAIN_MASK_CPT; + intel_dp->DP |= DP_LINK_TRAIN_PAT_IDLE_CPT; + } else { + intel_dp->DP &= ~DP_LINK_TRAIN_MASK; + intel_dp->DP |= DP_LINK_TRAIN_PAT_IDLE; + } + intel_de_write(dev_priv, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(dev_priv, intel_dp->output_reg); + + intel_dp->DP &= ~(DP_PORT_EN | DP_AUDIO_OUTPUT_ENABLE); + intel_de_write(dev_priv, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(dev_priv, intel_dp->output_reg); + + /* + * HW workaround for IBX, we need to move the port + * to transcoder A after disabling it to allow the + * matching HDMI port to be enabled on transcoder A. + */ + if (HAS_PCH_IBX(dev_priv) && crtc->pipe == PIPE_B && port != PORT_A) { + /* + * We get CPU/PCH FIFO underruns on the other pipe when + * doing the workaround. Sweep them under the rug. + */ + intel_set_cpu_fifo_underrun_reporting(dev_priv, PIPE_A, false); + intel_set_pch_fifo_underrun_reporting(dev_priv, PIPE_A, false); + + /* always enable with pattern 1 (as per spec) */ + intel_dp->DP &= ~(DP_PIPE_SEL_MASK | DP_LINK_TRAIN_MASK); + intel_dp->DP |= DP_PORT_EN | DP_PIPE_SEL(PIPE_A) | + DP_LINK_TRAIN_PAT_1; + intel_de_write(dev_priv, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(dev_priv, intel_dp->output_reg); + + intel_dp->DP &= ~DP_PORT_EN; + intel_de_write(dev_priv, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(dev_priv, intel_dp->output_reg); + + intel_wait_for_vblank_if_active(dev_priv, PIPE_A); + intel_set_cpu_fifo_underrun_reporting(dev_priv, PIPE_A, true); + intel_set_pch_fifo_underrun_reporting(dev_priv, PIPE_A, true); + } + + msleep(intel_dp->pps.panel_power_down_delay); + + if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { + intel_wakeref_t wakeref; + + with_intel_pps_lock(intel_dp, wakeref) + intel_dp->pps.active_pipe = INVALID_PIPE; + } +} + +static void intel_disable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + + intel_dp->link_trained = false; + + intel_audio_codec_disable(encoder, old_crtc_state, old_conn_state); + + /* + * Make sure the panel is off before trying to change the mode. + * But also ensure that we have vdd while we switch off the panel. + */ + intel_pps_vdd_on(intel_dp); + intel_edp_backlight_off(old_conn_state); + intel_dp_set_power(intel_dp, DP_SET_POWER_D3); + intel_pps_off(intel_dp); +} + +static void g4x_disable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + intel_disable_dp(state, encoder, old_crtc_state, old_conn_state); +} + +static void vlv_disable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + intel_disable_dp(state, encoder, old_crtc_state, old_conn_state); +} + +static void g4x_post_disable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + enum port port = encoder->port; + + /* + * Bspec does not list a specific disable sequence for g4x DP. + * Follow the ilk+ sequence (disable pipe before the port) for + * g4x DP as it does not suffer from underruns like the normal + * g4x modeset sequence (disable pipe after the port). + */ + intel_dp_link_down(encoder, old_crtc_state); + + /* Only ilk+ has port A */ + if (port == PORT_A) + ilk_edp_pll_off(intel_dp, old_crtc_state); +} + +static void vlv_post_disable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + intel_dp_link_down(encoder, old_crtc_state); +} + +static void chv_post_disable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + + intel_dp_link_down(encoder, old_crtc_state); + + vlv_dpio_get(dev_priv); + + /* Assert data lane reset */ + chv_data_lane_soft_reset(encoder, old_crtc_state, true); + + vlv_dpio_put(dev_priv); +} + +static void +cpt_set_link_train(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + u8 dp_train_pat) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + + intel_dp->DP &= ~DP_LINK_TRAIN_MASK_CPT; + + switch (intel_dp_training_pattern_symbol(dp_train_pat)) { + case DP_TRAINING_PATTERN_DISABLE: + intel_dp->DP |= DP_LINK_TRAIN_OFF_CPT; + break; + case DP_TRAINING_PATTERN_1: + intel_dp->DP |= DP_LINK_TRAIN_PAT_1_CPT; + break; + case DP_TRAINING_PATTERN_2: + intel_dp->DP |= DP_LINK_TRAIN_PAT_2_CPT; + break; + default: + MISSING_CASE(intel_dp_training_pattern_symbol(dp_train_pat)); + return; + } + + intel_de_write(dev_priv, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(dev_priv, intel_dp->output_reg); +} + +static void +g4x_set_link_train(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + u8 dp_train_pat) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + + intel_dp->DP &= ~DP_LINK_TRAIN_MASK; + + switch (intel_dp_training_pattern_symbol(dp_train_pat)) { + case DP_TRAINING_PATTERN_DISABLE: + intel_dp->DP |= DP_LINK_TRAIN_OFF; + break; + case DP_TRAINING_PATTERN_1: + intel_dp->DP |= DP_LINK_TRAIN_PAT_1; + break; + case DP_TRAINING_PATTERN_2: + intel_dp->DP |= DP_LINK_TRAIN_PAT_2; + break; + default: + MISSING_CASE(intel_dp_training_pattern_symbol(dp_train_pat)); + return; + } + + intel_de_write(dev_priv, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(dev_priv, intel_dp->output_reg); +} + +static void intel_dp_enable_port(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + + /* enable with pattern 1 (as per spec) */ + + intel_dp_program_link_training_pattern(intel_dp, crtc_state, + DP_PHY_DPRX, DP_TRAINING_PATTERN_1); + + /* + * Magic for VLV/CHV. We _must_ first set up the register + * without actually enabling the port, and then do another + * write to enable the port. Otherwise link training will + * fail when the power sequencer is freshly used for this port. + */ + intel_dp->DP |= DP_PORT_EN; + if (crtc_state->has_audio) + intel_dp->DP |= DP_AUDIO_OUTPUT_ENABLE; + + intel_de_write(dev_priv, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(dev_priv, intel_dp->output_reg); +} + +static void intel_enable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + u32 dp_reg = intel_de_read(dev_priv, intel_dp->output_reg); + intel_wakeref_t wakeref; + + if (drm_WARN_ON(&dev_priv->drm, dp_reg & DP_PORT_EN)) + return; + + with_intel_pps_lock(intel_dp, wakeref) { + if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) + vlv_pps_init(encoder, pipe_config); + + intel_dp_enable_port(intel_dp, pipe_config); + + intel_pps_vdd_on_unlocked(intel_dp); + intel_pps_on_unlocked(intel_dp); + intel_pps_vdd_off_unlocked(intel_dp, true); + } + + if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { + unsigned int lane_mask = 0x0; + + if (IS_CHERRYVIEW(dev_priv)) + lane_mask = intel_dp_unused_lane_mask(pipe_config->lane_count); + + vlv_wait_port_ready(dev_priv, dp_to_dig_port(intel_dp), + lane_mask); + } + + intel_dp_set_power(intel_dp, DP_SET_POWER_D0); + intel_dp_configure_protocol_converter(intel_dp, pipe_config); + intel_dp_check_frl_training(intel_dp); + intel_dp_pcon_dsc_configure(intel_dp, pipe_config); + intel_dp_start_link_train(intel_dp, pipe_config); + intel_dp_stop_link_train(intel_dp, pipe_config); + + intel_audio_codec_enable(encoder, pipe_config, conn_state); +} + +static void g4x_enable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ + intel_enable_dp(state, encoder, pipe_config, conn_state); + intel_edp_backlight_on(pipe_config, conn_state); +} + +static void vlv_enable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ + intel_edp_backlight_on(pipe_config, conn_state); +} + +static void g4x_pre_enable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + enum port port = encoder->port; + + intel_dp_prepare(encoder, pipe_config); + + /* Only ilk+ has port A */ + if (port == PORT_A) + ilk_edp_pll_on(intel_dp, pipe_config); +} + +static void vlv_pre_enable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ + vlv_phy_pre_encoder_enable(encoder, pipe_config); + + intel_enable_dp(state, encoder, pipe_config, conn_state); +} + +static void vlv_dp_pre_pll_enable(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ + intel_dp_prepare(encoder, pipe_config); + + vlv_phy_pre_pll_enable(encoder, pipe_config); +} + +static void chv_pre_enable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ + chv_phy_pre_encoder_enable(encoder, pipe_config); + + intel_enable_dp(state, encoder, pipe_config, conn_state); + + /* Second common lane will stay alive on its own now */ + chv_phy_release_cl2_override(encoder); +} + +static void chv_dp_pre_pll_enable(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ + intel_dp_prepare(encoder, pipe_config); + + chv_phy_pre_pll_enable(encoder, pipe_config); +} + +static void chv_dp_post_pll_disable(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + chv_phy_post_pll_disable(encoder, old_crtc_state); +} + +static u8 intel_dp_voltage_max_2(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + return DP_TRAIN_VOLTAGE_SWING_LEVEL_2; +} + +static u8 intel_dp_voltage_max_3(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + return DP_TRAIN_VOLTAGE_SWING_LEVEL_3; +} + +static u8 intel_dp_preemph_max_2(struct intel_dp *intel_dp) +{ + return DP_TRAIN_PRE_EMPH_LEVEL_2; +} + +static u8 intel_dp_preemph_max_3(struct intel_dp *intel_dp) +{ + return DP_TRAIN_PRE_EMPH_LEVEL_3; +} + +static void vlv_set_signal_levels(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + unsigned long demph_reg_value, preemph_reg_value, + uniqtranscale_reg_value; + u8 train_set = intel_dp->train_set[0]; + + switch (train_set & DP_TRAIN_PRE_EMPHASIS_MASK) { + case DP_TRAIN_PRE_EMPH_LEVEL_0: + preemph_reg_value = 0x0004000; + switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { + case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: + demph_reg_value = 0x2B405555; + uniqtranscale_reg_value = 0x552AB83A; + break; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: + demph_reg_value = 0x2B404040; + uniqtranscale_reg_value = 0x5548B83A; + break; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_2: + demph_reg_value = 0x2B245555; + uniqtranscale_reg_value = 0x5560B83A; + break; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_3: + demph_reg_value = 0x2B405555; + uniqtranscale_reg_value = 0x5598DA3A; + break; + default: + return; + } + break; + case DP_TRAIN_PRE_EMPH_LEVEL_1: + preemph_reg_value = 0x0002000; + switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { + case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: + demph_reg_value = 0x2B404040; + uniqtranscale_reg_value = 0x5552B83A; + break; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: + demph_reg_value = 0x2B404848; + uniqtranscale_reg_value = 0x5580B83A; + break; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_2: + demph_reg_value = 0x2B404040; + uniqtranscale_reg_value = 0x55ADDA3A; + break; + default: + return; + } + break; + case DP_TRAIN_PRE_EMPH_LEVEL_2: + preemph_reg_value = 0x0000000; + switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { + case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: + demph_reg_value = 0x2B305555; + uniqtranscale_reg_value = 0x5570B83A; + break; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: + demph_reg_value = 0x2B2B4040; + uniqtranscale_reg_value = 0x55ADDA3A; + break; + default: + return; + } + break; + case DP_TRAIN_PRE_EMPH_LEVEL_3: + preemph_reg_value = 0x0006000; + switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { + case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: + demph_reg_value = 0x1B405555; + uniqtranscale_reg_value = 0x55ADDA3A; + break; + default: + return; + } + break; + default: + return; + } + + vlv_set_phy_signal_level(encoder, crtc_state, + demph_reg_value, preemph_reg_value, + uniqtranscale_reg_value, 0); +} + +static void chv_set_signal_levels(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + u32 deemph_reg_value, margin_reg_value; + bool uniq_trans_scale = false; + u8 train_set = intel_dp->train_set[0]; + + switch (train_set & DP_TRAIN_PRE_EMPHASIS_MASK) { + case DP_TRAIN_PRE_EMPH_LEVEL_0: + switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { + case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: + deemph_reg_value = 128; + margin_reg_value = 52; + break; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: + deemph_reg_value = 128; + margin_reg_value = 77; + break; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_2: + deemph_reg_value = 128; + margin_reg_value = 102; + break; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_3: + deemph_reg_value = 128; + margin_reg_value = 154; + uniq_trans_scale = true; + break; + default: + return; + } + break; + case DP_TRAIN_PRE_EMPH_LEVEL_1: + switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { + case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: + deemph_reg_value = 85; + margin_reg_value = 78; + break; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: + deemph_reg_value = 85; + margin_reg_value = 116; + break; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_2: + deemph_reg_value = 85; + margin_reg_value = 154; + break; + default: + return; + } + break; + case DP_TRAIN_PRE_EMPH_LEVEL_2: + switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { + case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: + deemph_reg_value = 64; + margin_reg_value = 104; + break; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: + deemph_reg_value = 64; + margin_reg_value = 154; + break; + default: + return; + } + break; + case DP_TRAIN_PRE_EMPH_LEVEL_3: + switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { + case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: + deemph_reg_value = 43; + margin_reg_value = 154; + break; + default: + return; + } + break; + default: + return; + } + + chv_set_phy_signal_level(encoder, crtc_state, + deemph_reg_value, margin_reg_value, + uniq_trans_scale); +} + +static u32 g4x_signal_levels(u8 train_set) +{ + u32 signal_levels = 0; + + switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { + case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: + default: + signal_levels |= DP_VOLTAGE_0_4; + break; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: + signal_levels |= DP_VOLTAGE_0_6; + break; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_2: + signal_levels |= DP_VOLTAGE_0_8; + break; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_3: + signal_levels |= DP_VOLTAGE_1_2; + break; + } + switch (train_set & DP_TRAIN_PRE_EMPHASIS_MASK) { + case DP_TRAIN_PRE_EMPH_LEVEL_0: + default: + signal_levels |= DP_PRE_EMPHASIS_0; + break; + case DP_TRAIN_PRE_EMPH_LEVEL_1: + signal_levels |= DP_PRE_EMPHASIS_3_5; + break; + case DP_TRAIN_PRE_EMPH_LEVEL_2: + signal_levels |= DP_PRE_EMPHASIS_6; + break; + case DP_TRAIN_PRE_EMPH_LEVEL_3: + signal_levels |= DP_PRE_EMPHASIS_9_5; + break; + } + return signal_levels; +} + +static void +g4x_set_signal_levels(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + u8 train_set = intel_dp->train_set[0]; + u32 signal_levels; + + signal_levels = g4x_signal_levels(train_set); + + drm_dbg_kms(&dev_priv->drm, "Using signal levels %08x\n", + signal_levels); + + intel_dp->DP &= ~(DP_VOLTAGE_MASK | DP_PRE_EMPHASIS_MASK); + intel_dp->DP |= signal_levels; + + intel_de_write(dev_priv, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(dev_priv, intel_dp->output_reg); +} + +/* SNB CPU eDP voltage swing and pre-emphasis control */ +static u32 snb_cpu_edp_signal_levels(u8 train_set) +{ + u8 signal_levels = train_set & (DP_TRAIN_VOLTAGE_SWING_MASK | + DP_TRAIN_PRE_EMPHASIS_MASK); + + switch (signal_levels) { + case DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | DP_TRAIN_PRE_EMPH_LEVEL_0: + case DP_TRAIN_VOLTAGE_SWING_LEVEL_1 | DP_TRAIN_PRE_EMPH_LEVEL_0: + return EDP_LINK_TRAIN_400_600MV_0DB_SNB_B; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | DP_TRAIN_PRE_EMPH_LEVEL_1: + return EDP_LINK_TRAIN_400MV_3_5DB_SNB_B; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | DP_TRAIN_PRE_EMPH_LEVEL_2: + case DP_TRAIN_VOLTAGE_SWING_LEVEL_1 | DP_TRAIN_PRE_EMPH_LEVEL_2: + return EDP_LINK_TRAIN_400_600MV_6DB_SNB_B; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_1 | DP_TRAIN_PRE_EMPH_LEVEL_1: + case DP_TRAIN_VOLTAGE_SWING_LEVEL_2 | DP_TRAIN_PRE_EMPH_LEVEL_1: + return EDP_LINK_TRAIN_600_800MV_3_5DB_SNB_B; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_2 | DP_TRAIN_PRE_EMPH_LEVEL_0: + case DP_TRAIN_VOLTAGE_SWING_LEVEL_3 | DP_TRAIN_PRE_EMPH_LEVEL_0: + return EDP_LINK_TRAIN_800_1200MV_0DB_SNB_B; + default: + MISSING_CASE(signal_levels); + return EDP_LINK_TRAIN_400_600MV_0DB_SNB_B; + } +} + +static void +snb_cpu_edp_set_signal_levels(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + u8 train_set = intel_dp->train_set[0]; + u32 signal_levels; + + signal_levels = snb_cpu_edp_signal_levels(train_set); + + drm_dbg_kms(&dev_priv->drm, "Using signal levels %08x\n", + signal_levels); + + intel_dp->DP &= ~EDP_LINK_TRAIN_VOL_EMP_MASK_SNB; + intel_dp->DP |= signal_levels; + + intel_de_write(dev_priv, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(dev_priv, intel_dp->output_reg); +} + +/* IVB CPU eDP voltage swing and pre-emphasis control */ +static u32 ivb_cpu_edp_signal_levels(u8 train_set) +{ + u8 signal_levels = train_set & (DP_TRAIN_VOLTAGE_SWING_MASK | + DP_TRAIN_PRE_EMPHASIS_MASK); + + switch (signal_levels) { + case DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | DP_TRAIN_PRE_EMPH_LEVEL_0: + return EDP_LINK_TRAIN_400MV_0DB_IVB; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | DP_TRAIN_PRE_EMPH_LEVEL_1: + return EDP_LINK_TRAIN_400MV_3_5DB_IVB; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | DP_TRAIN_PRE_EMPH_LEVEL_2: + case DP_TRAIN_VOLTAGE_SWING_LEVEL_1 | DP_TRAIN_PRE_EMPH_LEVEL_2: + return EDP_LINK_TRAIN_400MV_6DB_IVB; + + case DP_TRAIN_VOLTAGE_SWING_LEVEL_1 | DP_TRAIN_PRE_EMPH_LEVEL_0: + return EDP_LINK_TRAIN_600MV_0DB_IVB; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_1 | DP_TRAIN_PRE_EMPH_LEVEL_1: + return EDP_LINK_TRAIN_600MV_3_5DB_IVB; + + case DP_TRAIN_VOLTAGE_SWING_LEVEL_2 | DP_TRAIN_PRE_EMPH_LEVEL_0: + return EDP_LINK_TRAIN_800MV_0DB_IVB; + case DP_TRAIN_VOLTAGE_SWING_LEVEL_2 | DP_TRAIN_PRE_EMPH_LEVEL_1: + return EDP_LINK_TRAIN_800MV_3_5DB_IVB; + + default: + MISSING_CASE(signal_levels); + return EDP_LINK_TRAIN_500MV_0DB_IVB; + } +} + +static void +ivb_cpu_edp_set_signal_levels(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + u8 train_set = intel_dp->train_set[0]; + u32 signal_levels; + + signal_levels = ivb_cpu_edp_signal_levels(train_set); + + drm_dbg_kms(&dev_priv->drm, "Using signal levels %08x\n", + signal_levels); + + intel_dp->DP &= ~EDP_LINK_TRAIN_VOL_EMP_MASK_IVB; + intel_dp->DP |= signal_levels; + + intel_de_write(dev_priv, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(dev_priv, intel_dp->output_reg); +} + +/* + * If display is now connected check links status, + * there has been known issues of link loss triggering + * long pulse. + * + * Some sinks (eg. ASUS PB287Q) seem to perform some + * weird HPD ping pong during modesets. So we can apparently + * end up with HPD going low during a modeset, and then + * going back up soon after. And once that happens we must + * retrain the link to get a picture. That's in case no + * userspace component reacted to intermittent HPD dip. + */ +static enum intel_hotplug_state +intel_dp_hotplug(struct intel_encoder *encoder, + struct intel_connector *connector) +{ + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + struct drm_modeset_acquire_ctx ctx; + enum intel_hotplug_state state; + int ret; + + if (intel_dp->compliance.test_active && + intel_dp->compliance.test_type == DP_TEST_LINK_PHY_TEST_PATTERN) { + intel_dp_phy_test(encoder); + /* just do the PHY test and nothing else */ + return INTEL_HOTPLUG_UNCHANGED; + } + + state = intel_encoder_hotplug(encoder, connector); + + drm_modeset_acquire_init(&ctx, 0); + + for (;;) { + ret = intel_dp_retrain_link(encoder, &ctx); + + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + continue; + } + + break; + } + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); + drm_WARN(encoder->base.dev, ret, + "Acquiring modeset locks failed with %i\n", ret); + + /* + * Keeping it consistent with intel_ddi_hotplug() and + * intel_hdmi_hotplug(). + */ + if (state == INTEL_HOTPLUG_UNCHANGED && !connector->hotplug_retries) + state = INTEL_HOTPLUG_RETRY; + + return state; +} + +static bool ibx_digital_port_connected(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + u32 bit = dev_priv->display.hotplug.pch_hpd[encoder->hpd_pin]; + + return intel_de_read(dev_priv, SDEISR) & bit; +} + +static bool g4x_digital_port_connected(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + u32 bit; + + switch (encoder->hpd_pin) { + case HPD_PORT_B: + bit = PORTB_HOTPLUG_LIVE_STATUS_G4X; + break; + case HPD_PORT_C: + bit = PORTC_HOTPLUG_LIVE_STATUS_G4X; + break; + case HPD_PORT_D: + bit = PORTD_HOTPLUG_LIVE_STATUS_G4X; + break; + default: + MISSING_CASE(encoder->hpd_pin); + return false; + } + + return intel_de_read(dev_priv, PORT_HOTPLUG_STAT) & bit; +} + +static bool gm45_digital_port_connected(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + u32 bit; + + switch (encoder->hpd_pin) { + case HPD_PORT_B: + bit = PORTB_HOTPLUG_LIVE_STATUS_GM45; + break; + case HPD_PORT_C: + bit = PORTC_HOTPLUG_LIVE_STATUS_GM45; + break; + case HPD_PORT_D: + bit = PORTD_HOTPLUG_LIVE_STATUS_GM45; + break; + default: + MISSING_CASE(encoder->hpd_pin); + return false; + } + + return intel_de_read(dev_priv, PORT_HOTPLUG_STAT) & bit; +} + +static bool ilk_digital_port_connected(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + u32 bit = dev_priv->display.hotplug.hpd[encoder->hpd_pin]; + + return intel_de_read(dev_priv, DEISR) & bit; +} + +static void intel_dp_encoder_destroy(struct drm_encoder *encoder) +{ + intel_dp_encoder_flush_work(encoder); + + drm_encoder_cleanup(encoder); + kfree(enc_to_dig_port(to_intel_encoder(encoder))); +} + +enum pipe vlv_active_pipe(struct intel_dp *intel_dp) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + enum pipe pipe; + + if (g4x_dp_port_enabled(dev_priv, intel_dp->output_reg, + encoder->port, &pipe)) + return pipe; + + return INVALID_PIPE; +} + +static void intel_dp_encoder_reset(struct drm_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->dev); + struct intel_dp *intel_dp = enc_to_intel_dp(to_intel_encoder(encoder)); + + intel_dp->DP = intel_de_read(dev_priv, intel_dp->output_reg); + + intel_dp->reset_link_params = true; + + if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { + intel_wakeref_t wakeref; + + with_intel_pps_lock(intel_dp, wakeref) + intel_dp->pps.active_pipe = vlv_active_pipe(intel_dp); + } + + intel_pps_encoder_reset(intel_dp); +} + +static const struct drm_encoder_funcs intel_dp_enc_funcs = { + .reset = intel_dp_encoder_reset, + .destroy = intel_dp_encoder_destroy, +}; + +bool g4x_dp_init(struct drm_i915_private *dev_priv, + i915_reg_t output_reg, enum port port) +{ + struct intel_digital_port *dig_port; + struct intel_encoder *intel_encoder; + struct drm_encoder *encoder; + struct intel_connector *intel_connector; + + dig_port = kzalloc(sizeof(*dig_port), GFP_KERNEL); + if (!dig_port) + return false; + + intel_connector = intel_connector_alloc(); + if (!intel_connector) + goto err_connector_alloc; + + intel_encoder = &dig_port->base; + encoder = &intel_encoder->base; + + mutex_init(&dig_port->hdcp_mutex); + + if (drm_encoder_init(&dev_priv->drm, &intel_encoder->base, + &intel_dp_enc_funcs, DRM_MODE_ENCODER_TMDS, + "DP %c", port_name(port))) + goto err_encoder_init; + + intel_encoder->hotplug = intel_dp_hotplug; + intel_encoder->compute_config = intel_dp_compute_config; + intel_encoder->get_hw_state = intel_dp_get_hw_state; + intel_encoder->get_config = intel_dp_get_config; + intel_encoder->sync_state = intel_dp_sync_state; + intel_encoder->initial_fastset_check = intel_dp_initial_fastset_check; + intel_encoder->update_pipe = intel_backlight_update; + intel_encoder->suspend = intel_dp_encoder_suspend; + intel_encoder->shutdown = intel_dp_encoder_shutdown; + if (IS_CHERRYVIEW(dev_priv)) { + intel_encoder->pre_pll_enable = chv_dp_pre_pll_enable; + intel_encoder->pre_enable = chv_pre_enable_dp; + intel_encoder->enable = vlv_enable_dp; + intel_encoder->disable = vlv_disable_dp; + intel_encoder->post_disable = chv_post_disable_dp; + intel_encoder->post_pll_disable = chv_dp_post_pll_disable; + } else if (IS_VALLEYVIEW(dev_priv)) { + intel_encoder->pre_pll_enable = vlv_dp_pre_pll_enable; + intel_encoder->pre_enable = vlv_pre_enable_dp; + intel_encoder->enable = vlv_enable_dp; + intel_encoder->disable = vlv_disable_dp; + intel_encoder->post_disable = vlv_post_disable_dp; + } else { + intel_encoder->pre_enable = g4x_pre_enable_dp; + intel_encoder->enable = g4x_enable_dp; + intel_encoder->disable = g4x_disable_dp; + intel_encoder->post_disable = g4x_post_disable_dp; + } + + if ((IS_IVYBRIDGE(dev_priv) && port == PORT_A) || + (HAS_PCH_CPT(dev_priv) && port != PORT_A)) + dig_port->dp.set_link_train = cpt_set_link_train; + else + dig_port->dp.set_link_train = g4x_set_link_train; + + if (IS_CHERRYVIEW(dev_priv)) + intel_encoder->set_signal_levels = chv_set_signal_levels; + else if (IS_VALLEYVIEW(dev_priv)) + intel_encoder->set_signal_levels = vlv_set_signal_levels; + else if (IS_IVYBRIDGE(dev_priv) && port == PORT_A) + intel_encoder->set_signal_levels = ivb_cpu_edp_set_signal_levels; + else if (IS_SANDYBRIDGE(dev_priv) && port == PORT_A) + intel_encoder->set_signal_levels = snb_cpu_edp_set_signal_levels; + else + intel_encoder->set_signal_levels = g4x_set_signal_levels; + + if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv) || + (HAS_PCH_SPLIT(dev_priv) && port != PORT_A)) { + dig_port->dp.preemph_max = intel_dp_preemph_max_3; + dig_port->dp.voltage_max = intel_dp_voltage_max_3; + } else { + dig_port->dp.preemph_max = intel_dp_preemph_max_2; + dig_port->dp.voltage_max = intel_dp_voltage_max_2; + } + + dig_port->dp.output_reg = output_reg; + dig_port->max_lanes = 4; + + intel_encoder->type = INTEL_OUTPUT_DP; + intel_encoder->power_domain = intel_display_power_ddi_lanes_domain(dev_priv, port); + if (IS_CHERRYVIEW(dev_priv)) { + if (port == PORT_D) + intel_encoder->pipe_mask = BIT(PIPE_C); + else + intel_encoder->pipe_mask = BIT(PIPE_A) | BIT(PIPE_B); + } else { + intel_encoder->pipe_mask = ~0; + } + intel_encoder->cloneable = 0; + intel_encoder->port = port; + intel_encoder->hpd_pin = intel_hpd_pin_default(dev_priv, port); + + dig_port->hpd_pulse = intel_dp_hpd_pulse; + + if (HAS_GMCH(dev_priv)) { + if (IS_GM45(dev_priv)) + dig_port->connected = gm45_digital_port_connected; + else + dig_port->connected = g4x_digital_port_connected; + } else { + if (port == PORT_A) + dig_port->connected = ilk_digital_port_connected; + else + dig_port->connected = ibx_digital_port_connected; + } + + if (port != PORT_A) + intel_infoframe_init(dig_port); + + dig_port->aux_ch = intel_bios_port_aux_ch(dev_priv, port); + if (!intel_dp_init_connector(dig_port, intel_connector)) + goto err_init_connector; + + return true; + +err_init_connector: + drm_encoder_cleanup(encoder); +err_encoder_init: + kfree(intel_connector); +err_connector_alloc: + kfree(dig_port); + return false; +} diff --git a/drivers/gpu/drm/i915/display/g4x_dp.h b/drivers/gpu/drm/i915/display/g4x_dp.h new file mode 100644 index 000000000..e1f50263a --- /dev/null +++ b/drivers/gpu/drm/i915/display/g4x_dp.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2020 Intel Corporation + */ + +#ifndef _G4X_DP_H_ +#define _G4X_DP_H_ + +#include + +#include "i915_reg.h" + +enum pipe; +enum port; +struct drm_i915_private; +struct intel_crtc_state; +struct intel_dp; +struct intel_encoder; + +const struct dpll *vlv_get_dpll(struct drm_i915_private *i915); +enum pipe vlv_active_pipe(struct intel_dp *intel_dp); +void g4x_dp_set_clock(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config); +bool g4x_dp_port_enabled(struct drm_i915_private *dev_priv, + i915_reg_t dp_reg, enum port port, + enum pipe *pipe); +bool g4x_dp_init(struct drm_i915_private *dev_priv, + i915_reg_t output_reg, enum port port); + +#endif diff --git a/drivers/gpu/drm/i915/display/g4x_hdmi.c b/drivers/gpu/drm/i915/display/g4x_hdmi.c new file mode 100644 index 000000000..2b73f5ff0 --- /dev/null +++ b/drivers/gpu/drm/i915/display/g4x_hdmi.c @@ -0,0 +1,606 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2020 Intel Corporation + * + * HDMI support for G4x,ILK,SNB,IVB,VLV,CHV (HSW+ handled by the DDI code). + */ + +#include "g4x_hdmi.h" +#include "intel_audio.h" +#include "intel_connector.h" +#include "intel_crtc.h" +#include "intel_de.h" +#include "intel_display_power.h" +#include "intel_display_types.h" +#include "intel_dpio_phy.h" +#include "intel_fifo_underrun.h" +#include "intel_hdmi.h" +#include "intel_hotplug.h" +#include "intel_sdvo.h" +#include "vlv_sideband.h" + +static void intel_hdmi_prepare(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_device *dev = encoder->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder); + const struct drm_display_mode *adjusted_mode = &crtc_state->hw.adjusted_mode; + u32 hdmi_val; + + intel_dp_dual_mode_set_tmds_output(intel_hdmi, true); + + hdmi_val = SDVO_ENCODING_HDMI; + if (!HAS_PCH_SPLIT(dev_priv) && crtc_state->limited_color_range) + hdmi_val |= HDMI_COLOR_RANGE_16_235; + if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) + hdmi_val |= SDVO_VSYNC_ACTIVE_HIGH; + if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) + hdmi_val |= SDVO_HSYNC_ACTIVE_HIGH; + + if (crtc_state->pipe_bpp > 24) + hdmi_val |= HDMI_COLOR_FORMAT_12bpc; + else + hdmi_val |= SDVO_COLOR_FORMAT_8bpc; + + if (crtc_state->has_hdmi_sink) + hdmi_val |= HDMI_MODE_SELECT_HDMI; + + if (HAS_PCH_CPT(dev_priv)) + hdmi_val |= SDVO_PIPE_SEL_CPT(crtc->pipe); + else if (IS_CHERRYVIEW(dev_priv)) + hdmi_val |= SDVO_PIPE_SEL_CHV(crtc->pipe); + else + hdmi_val |= SDVO_PIPE_SEL(crtc->pipe); + + intel_de_write(dev_priv, intel_hdmi->hdmi_reg, hdmi_val); + intel_de_posting_read(dev_priv, intel_hdmi->hdmi_reg); +} + +static bool intel_hdmi_get_hw_state(struct intel_encoder *encoder, + enum pipe *pipe) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder); + intel_wakeref_t wakeref; + bool ret; + + wakeref = intel_display_power_get_if_enabled(dev_priv, + encoder->power_domain); + if (!wakeref) + return false; + + ret = intel_sdvo_port_enabled(dev_priv, intel_hdmi->hdmi_reg, pipe); + + intel_display_power_put(dev_priv, encoder->power_domain, wakeref); + + return ret; +} + +static void intel_hdmi_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config) +{ + struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder); + struct drm_device *dev = encoder->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + u32 tmp, flags = 0; + int dotclock; + + pipe_config->output_types |= BIT(INTEL_OUTPUT_HDMI); + + tmp = intel_de_read(dev_priv, intel_hdmi->hdmi_reg); + + if (tmp & SDVO_HSYNC_ACTIVE_HIGH) + flags |= DRM_MODE_FLAG_PHSYNC; + else + flags |= DRM_MODE_FLAG_NHSYNC; + + if (tmp & SDVO_VSYNC_ACTIVE_HIGH) + flags |= DRM_MODE_FLAG_PVSYNC; + else + flags |= DRM_MODE_FLAG_NVSYNC; + + if (tmp & HDMI_MODE_SELECT_HDMI) + pipe_config->has_hdmi_sink = true; + + pipe_config->infoframes.enable |= + intel_hdmi_infoframes_enabled(encoder, pipe_config); + + if (pipe_config->infoframes.enable) + pipe_config->has_infoframe = true; + + if (tmp & HDMI_AUDIO_ENABLE) + pipe_config->has_audio = true; + + if (!HAS_PCH_SPLIT(dev_priv) && + tmp & HDMI_COLOR_RANGE_16_235) + pipe_config->limited_color_range = true; + + pipe_config->hw.adjusted_mode.flags |= flags; + + if ((tmp & SDVO_COLOR_FORMAT_MASK) == HDMI_COLOR_FORMAT_12bpc) + dotclock = DIV_ROUND_CLOSEST(pipe_config->port_clock * 2, 3); + else + dotclock = pipe_config->port_clock; + + if (pipe_config->pixel_multiplier) + dotclock /= pipe_config->pixel_multiplier; + + pipe_config->hw.adjusted_mode.crtc_clock = dotclock; + + pipe_config->lane_count = 4; + + intel_hdmi_read_gcp_infoframe(encoder, pipe_config); + + intel_read_infoframe(encoder, pipe_config, + HDMI_INFOFRAME_TYPE_AVI, + &pipe_config->infoframes.avi); + intel_read_infoframe(encoder, pipe_config, + HDMI_INFOFRAME_TYPE_SPD, + &pipe_config->infoframes.spd); + intel_read_infoframe(encoder, pipe_config, + HDMI_INFOFRAME_TYPE_VENDOR, + &pipe_config->infoframes.hdmi); +} + +static void g4x_enable_hdmi(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ + struct drm_device *dev = encoder->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder); + u32 temp; + + temp = intel_de_read(dev_priv, intel_hdmi->hdmi_reg); + + temp |= SDVO_ENABLE; + if (pipe_config->has_audio) + temp |= HDMI_AUDIO_ENABLE; + + intel_de_write(dev_priv, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(dev_priv, intel_hdmi->hdmi_reg); + + drm_WARN_ON(&dev_priv->drm, pipe_config->has_audio && + !pipe_config->has_hdmi_sink); + intel_audio_codec_enable(encoder, pipe_config, conn_state); +} + +static void ibx_enable_hdmi(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ + struct drm_device *dev = encoder->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder); + u32 temp; + + temp = intel_de_read(dev_priv, intel_hdmi->hdmi_reg); + + temp |= SDVO_ENABLE; + if (pipe_config->has_audio) + temp |= HDMI_AUDIO_ENABLE; + + /* + * HW workaround, need to write this twice for issue + * that may result in first write getting masked. + */ + intel_de_write(dev_priv, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(dev_priv, intel_hdmi->hdmi_reg); + intel_de_write(dev_priv, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(dev_priv, intel_hdmi->hdmi_reg); + + /* + * HW workaround, need to toggle enable bit off and on + * for 12bpc with pixel repeat. + * + * FIXME: BSpec says this should be done at the end of + * the modeset sequence, so not sure if this isn't too soon. + */ + if (pipe_config->pipe_bpp > 24 && + pipe_config->pixel_multiplier > 1) { + intel_de_write(dev_priv, intel_hdmi->hdmi_reg, + temp & ~SDVO_ENABLE); + intel_de_posting_read(dev_priv, intel_hdmi->hdmi_reg); + + /* + * HW workaround, need to write this twice for issue + * that may result in first write getting masked. + */ + intel_de_write(dev_priv, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(dev_priv, intel_hdmi->hdmi_reg); + intel_de_write(dev_priv, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(dev_priv, intel_hdmi->hdmi_reg); + } + + drm_WARN_ON(&dev_priv->drm, pipe_config->has_audio && + !pipe_config->has_hdmi_sink); + intel_audio_codec_enable(encoder, pipe_config, conn_state); +} + +static void cpt_enable_hdmi(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ + struct drm_device *dev = encoder->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + struct intel_crtc *crtc = to_intel_crtc(pipe_config->uapi.crtc); + struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder); + enum pipe pipe = crtc->pipe; + u32 temp; + + temp = intel_de_read(dev_priv, intel_hdmi->hdmi_reg); + + temp |= SDVO_ENABLE; + if (pipe_config->has_audio) + temp |= HDMI_AUDIO_ENABLE; + + /* + * WaEnableHDMI8bpcBefore12bpc:snb,ivb + * + * The procedure for 12bpc is as follows: + * 1. disable HDMI clock gating + * 2. enable HDMI with 8bpc + * 3. enable HDMI with 12bpc + * 4. enable HDMI clock gating + */ + + if (pipe_config->pipe_bpp > 24) { + intel_de_write(dev_priv, TRANS_CHICKEN1(pipe), + intel_de_read(dev_priv, TRANS_CHICKEN1(pipe)) | TRANS_CHICKEN1_HDMIUNIT_GC_DISABLE); + + temp &= ~SDVO_COLOR_FORMAT_MASK; + temp |= SDVO_COLOR_FORMAT_8bpc; + } + + intel_de_write(dev_priv, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(dev_priv, intel_hdmi->hdmi_reg); + + if (pipe_config->pipe_bpp > 24) { + temp &= ~SDVO_COLOR_FORMAT_MASK; + temp |= HDMI_COLOR_FORMAT_12bpc; + + intel_de_write(dev_priv, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(dev_priv, intel_hdmi->hdmi_reg); + + intel_de_write(dev_priv, TRANS_CHICKEN1(pipe), + intel_de_read(dev_priv, TRANS_CHICKEN1(pipe)) & ~TRANS_CHICKEN1_HDMIUNIT_GC_DISABLE); + } + + drm_WARN_ON(&dev_priv->drm, pipe_config->has_audio && + !pipe_config->has_hdmi_sink); + intel_audio_codec_enable(encoder, pipe_config, conn_state); +} + +static void vlv_enable_hdmi(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ +} + +static void intel_disable_hdmi(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct drm_device *dev = encoder->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder); + struct intel_digital_port *dig_port = + hdmi_to_dig_port(intel_hdmi); + struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->uapi.crtc); + u32 temp; + + temp = intel_de_read(dev_priv, intel_hdmi->hdmi_reg); + + temp &= ~(SDVO_ENABLE | HDMI_AUDIO_ENABLE); + intel_de_write(dev_priv, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(dev_priv, intel_hdmi->hdmi_reg); + + /* + * HW workaround for IBX, we need to move the port + * to transcoder A after disabling it to allow the + * matching DP port to be enabled on transcoder A. + */ + if (HAS_PCH_IBX(dev_priv) && crtc->pipe == PIPE_B) { + /* + * We get CPU/PCH FIFO underruns on the other pipe when + * doing the workaround. Sweep them under the rug. + */ + intel_set_cpu_fifo_underrun_reporting(dev_priv, PIPE_A, false); + intel_set_pch_fifo_underrun_reporting(dev_priv, PIPE_A, false); + + temp &= ~SDVO_PIPE_SEL_MASK; + temp |= SDVO_ENABLE | SDVO_PIPE_SEL(PIPE_A); + /* + * HW workaround, need to write this twice for issue + * that may result in first write getting masked. + */ + intel_de_write(dev_priv, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(dev_priv, intel_hdmi->hdmi_reg); + intel_de_write(dev_priv, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(dev_priv, intel_hdmi->hdmi_reg); + + temp &= ~SDVO_ENABLE; + intel_de_write(dev_priv, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(dev_priv, intel_hdmi->hdmi_reg); + + intel_wait_for_vblank_if_active(dev_priv, PIPE_A); + intel_set_cpu_fifo_underrun_reporting(dev_priv, PIPE_A, true); + intel_set_pch_fifo_underrun_reporting(dev_priv, PIPE_A, true); + } + + dig_port->set_infoframes(encoder, + false, + old_crtc_state, old_conn_state); + + intel_dp_dual_mode_set_tmds_output(intel_hdmi, false); +} + +static void g4x_disable_hdmi(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + intel_audio_codec_disable(encoder, old_crtc_state, old_conn_state); + + intel_disable_hdmi(state, encoder, old_crtc_state, old_conn_state); +} + +static void pch_disable_hdmi(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + intel_audio_codec_disable(encoder, old_crtc_state, old_conn_state); +} + +static void pch_post_disable_hdmi(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + intel_disable_hdmi(state, encoder, old_crtc_state, old_conn_state); +} + +static void intel_hdmi_pre_enable(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ + struct intel_digital_port *dig_port = + enc_to_dig_port(encoder); + + intel_hdmi_prepare(encoder, pipe_config); + + dig_port->set_infoframes(encoder, + pipe_config->has_infoframe, + pipe_config, conn_state); +} + +static void vlv_hdmi_pre_enable(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + + vlv_phy_pre_encoder_enable(encoder, pipe_config); + + /* HDMI 1.0V-2dB */ + vlv_set_phy_signal_level(encoder, pipe_config, + 0x2b245f5f, 0x00002000, + 0x5578b83a, 0x2b247878); + + dig_port->set_infoframes(encoder, + pipe_config->has_infoframe, + pipe_config, conn_state); + + g4x_enable_hdmi(state, encoder, pipe_config, conn_state); + + vlv_wait_port_ready(dev_priv, dig_port, 0x0); +} + +static void vlv_hdmi_pre_pll_enable(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ + intel_hdmi_prepare(encoder, pipe_config); + + vlv_phy_pre_pll_enable(encoder, pipe_config); +} + +static void chv_hdmi_pre_pll_enable(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ + intel_hdmi_prepare(encoder, pipe_config); + + chv_phy_pre_pll_enable(encoder, pipe_config); +} + +static void chv_hdmi_post_pll_disable(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + chv_phy_post_pll_disable(encoder, old_crtc_state); +} + +static void vlv_hdmi_post_disable(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + /* Reset lanes to avoid HDMI flicker (VLV w/a) */ + vlv_phy_reset_lanes(encoder, old_crtc_state); +} + +static void chv_hdmi_post_disable(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct drm_device *dev = encoder->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + + vlv_dpio_get(dev_priv); + + /* Assert data lane reset */ + chv_data_lane_soft_reset(encoder, old_crtc_state, true); + + vlv_dpio_put(dev_priv); +} + +static void chv_hdmi_pre_enable(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + struct drm_device *dev = encoder->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + + chv_phy_pre_encoder_enable(encoder, pipe_config); + + /* FIXME: Program the support xxx V-dB */ + /* Use 800mV-0dB */ + chv_set_phy_signal_level(encoder, pipe_config, 128, 102, false); + + dig_port->set_infoframes(encoder, + pipe_config->has_infoframe, + pipe_config, conn_state); + + g4x_enable_hdmi(state, encoder, pipe_config, conn_state); + + vlv_wait_port_ready(dev_priv, dig_port, 0x0); + + /* Second common lane will stay alive on its own now */ + chv_phy_release_cl2_override(encoder); +} + +static const struct drm_encoder_funcs intel_hdmi_enc_funcs = { + .destroy = intel_encoder_destroy, +}; + +static enum intel_hotplug_state +intel_hdmi_hotplug(struct intel_encoder *encoder, + struct intel_connector *connector) +{ + enum intel_hotplug_state state; + + state = intel_encoder_hotplug(encoder, connector); + + /* + * On many platforms the HDMI live state signal is known to be + * unreliable, so we can't use it to detect if a sink is connected or + * not. Instead we detect if it's connected based on whether we can + * read the EDID or not. That in turn has a problem during disconnect, + * since the HPD interrupt may be raised before the DDC lines get + * disconnected (due to how the required length of DDC vs. HPD + * connector pins are specified) and so we'll still be able to get a + * valid EDID. To solve this schedule another detection cycle if this + * time around we didn't detect any change in the sink's connection + * status. + */ + if (state == INTEL_HOTPLUG_UNCHANGED && !connector->hotplug_retries) + state = INTEL_HOTPLUG_RETRY; + + return state; +} + +void g4x_hdmi_init(struct drm_i915_private *dev_priv, + i915_reg_t hdmi_reg, enum port port) +{ + struct intel_digital_port *dig_port; + struct intel_encoder *intel_encoder; + struct intel_connector *intel_connector; + + dig_port = kzalloc(sizeof(*dig_port), GFP_KERNEL); + if (!dig_port) + return; + + intel_connector = intel_connector_alloc(); + if (!intel_connector) { + kfree(dig_port); + return; + } + + intel_encoder = &dig_port->base; + + mutex_init(&dig_port->hdcp_mutex); + + drm_encoder_init(&dev_priv->drm, &intel_encoder->base, + &intel_hdmi_enc_funcs, DRM_MODE_ENCODER_TMDS, + "HDMI %c", port_name(port)); + + intel_encoder->hotplug = intel_hdmi_hotplug; + intel_encoder->compute_config = intel_hdmi_compute_config; + if (HAS_PCH_SPLIT(dev_priv)) { + intel_encoder->disable = pch_disable_hdmi; + intel_encoder->post_disable = pch_post_disable_hdmi; + } else { + intel_encoder->disable = g4x_disable_hdmi; + } + intel_encoder->get_hw_state = intel_hdmi_get_hw_state; + intel_encoder->get_config = intel_hdmi_get_config; + if (IS_CHERRYVIEW(dev_priv)) { + intel_encoder->pre_pll_enable = chv_hdmi_pre_pll_enable; + intel_encoder->pre_enable = chv_hdmi_pre_enable; + intel_encoder->enable = vlv_enable_hdmi; + intel_encoder->post_disable = chv_hdmi_post_disable; + intel_encoder->post_pll_disable = chv_hdmi_post_pll_disable; + } else if (IS_VALLEYVIEW(dev_priv)) { + intel_encoder->pre_pll_enable = vlv_hdmi_pre_pll_enable; + intel_encoder->pre_enable = vlv_hdmi_pre_enable; + intel_encoder->enable = vlv_enable_hdmi; + intel_encoder->post_disable = vlv_hdmi_post_disable; + } else { + intel_encoder->pre_enable = intel_hdmi_pre_enable; + if (HAS_PCH_CPT(dev_priv)) + intel_encoder->enable = cpt_enable_hdmi; + else if (HAS_PCH_IBX(dev_priv)) + intel_encoder->enable = ibx_enable_hdmi; + else + intel_encoder->enable = g4x_enable_hdmi; + } + intel_encoder->shutdown = intel_hdmi_encoder_shutdown; + + intel_encoder->type = INTEL_OUTPUT_HDMI; + intel_encoder->power_domain = intel_display_power_ddi_lanes_domain(dev_priv, port); + intel_encoder->port = port; + if (IS_CHERRYVIEW(dev_priv)) { + if (port == PORT_D) + intel_encoder->pipe_mask = BIT(PIPE_C); + else + intel_encoder->pipe_mask = BIT(PIPE_A) | BIT(PIPE_B); + } else { + intel_encoder->pipe_mask = ~0; + } + intel_encoder->cloneable = 1 << INTEL_OUTPUT_ANALOG; + intel_encoder->hpd_pin = intel_hpd_pin_default(dev_priv, port); + /* + * BSpec is unclear about HDMI+HDMI cloning on g4x, but it seems + * to work on real hardware. And since g4x can send infoframes to + * only one port anyway, nothing is lost by allowing it. + */ + if (IS_G4X(dev_priv)) + intel_encoder->cloneable |= 1 << INTEL_OUTPUT_HDMI; + + dig_port->hdmi.hdmi_reg = hdmi_reg; + dig_port->dp.output_reg = INVALID_MMIO_REG; + dig_port->max_lanes = 4; + + intel_infoframe_init(dig_port); + + dig_port->aux_ch = intel_bios_port_aux_ch(dev_priv, port); + intel_hdmi_init_connector(dig_port, intel_connector); +} diff --git a/drivers/gpu/drm/i915/display/g4x_hdmi.h b/drivers/gpu/drm/i915/display/g4x_hdmi.h new file mode 100644 index 000000000..db9a93bc9 --- /dev/null +++ b/drivers/gpu/drm/i915/display/g4x_hdmi.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2020 Intel Corporation + */ + +#ifndef _G4X_HDMI_H_ +#define _G4X_HDMI_H_ + +#include + +#include "i915_reg_defs.h" + +enum port; +struct drm_i915_private; + +void g4x_hdmi_init(struct drm_i915_private *dev_priv, + i915_reg_t hdmi_reg, enum port port); + +#endif diff --git a/drivers/gpu/drm/i915/display/hsw_ips.c b/drivers/gpu/drm/i915/display/hsw_ips.c new file mode 100644 index 000000000..a5be4af79 --- /dev/null +++ b/drivers/gpu/drm/i915/display/hsw_ips.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2022 Intel Corporation + */ + +#include "hsw_ips.h" +#include "i915_drv.h" +#include "i915_reg.h" +#include "intel_de.h" +#include "intel_display_types.h" +#include "intel_pcode.h" + +static void hsw_ips_enable(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + + if (!crtc_state->ips_enabled) + return; + + /* + * We can only enable IPS after we enable a plane and wait for a vblank + * This function is called from post_plane_update, which is run after + * a vblank wait. + */ + drm_WARN_ON(&i915->drm, + !(crtc_state->active_planes & ~BIT(PLANE_CURSOR))); + + if (IS_BROADWELL(i915)) { + drm_WARN_ON(&i915->drm, + snb_pcode_write(&i915->uncore, DISPLAY_IPS_CONTROL, + IPS_ENABLE | IPS_PCODE_CONTROL)); + /* + * Quoting Art Runyan: "its not safe to expect any particular + * value in IPS_CTL bit 31 after enabling IPS through the + * mailbox." Moreover, the mailbox may return a bogus state, + * so we need to just enable it and continue on. + */ + } else { + intel_de_write(i915, IPS_CTL, IPS_ENABLE); + /* + * The bit only becomes 1 in the next vblank, so this wait here + * is essentially intel_wait_for_vblank. If we don't have this + * and don't wait for vblanks until the end of crtc_enable, then + * the HW state readout code will complain that the expected + * IPS_CTL value is not the one we read. + */ + if (intel_de_wait_for_set(i915, IPS_CTL, IPS_ENABLE, 50)) + drm_err(&i915->drm, + "Timed out waiting for IPS enable\n"); + } +} + +bool hsw_ips_disable(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + bool need_vblank_wait = false; + + if (!crtc_state->ips_enabled) + return need_vblank_wait; + + if (IS_BROADWELL(i915)) { + drm_WARN_ON(&i915->drm, + snb_pcode_write(&i915->uncore, DISPLAY_IPS_CONTROL, 0)); + /* + * Wait for PCODE to finish disabling IPS. The BSpec specified + * 42ms timeout value leads to occasional timeouts so use 100ms + * instead. + */ + if (intel_de_wait_for_clear(i915, IPS_CTL, IPS_ENABLE, 100)) + drm_err(&i915->drm, + "Timed out waiting for IPS disable\n"); + } else { + intel_de_write(i915, IPS_CTL, 0); + intel_de_posting_read(i915, IPS_CTL); + } + + /* We need to wait for a vblank before we can disable the plane. */ + need_vblank_wait = true; + + return need_vblank_wait; +} + +static bool hsw_ips_need_disable(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *i915 = to_i915(state->base.dev); + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + const struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + if (!old_crtc_state->ips_enabled) + return false; + + if (intel_crtc_needs_modeset(new_crtc_state)) + return true; + + /* + * Workaround : Do not read or write the pipe palette/gamma data while + * GAMMA_MODE is configured for split gamma and IPS_CTL has IPS enabled. + * + * Disable IPS before we program the LUT. + */ + if (IS_HASWELL(i915) && + (new_crtc_state->uapi.color_mgmt_changed || + new_crtc_state->update_pipe) && + new_crtc_state->gamma_mode == GAMMA_MODE_MODE_SPLIT) + return true; + + return !new_crtc_state->ips_enabled; +} + +bool hsw_ips_pre_update(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + + if (!hsw_ips_need_disable(state, crtc)) + return false; + + return hsw_ips_disable(old_crtc_state); +} + +static bool hsw_ips_need_enable(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *i915 = to_i915(state->base.dev); + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + const struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + if (!new_crtc_state->ips_enabled) + return false; + + if (intel_crtc_needs_modeset(new_crtc_state)) + return true; + + /* + * Workaround : Do not read or write the pipe palette/gamma data while + * GAMMA_MODE is configured for split gamma and IPS_CTL has IPS enabled. + * + * Re-enable IPS after the LUT has been programmed. + */ + if (IS_HASWELL(i915) && + (new_crtc_state->uapi.color_mgmt_changed || + new_crtc_state->update_pipe) && + new_crtc_state->gamma_mode == GAMMA_MODE_MODE_SPLIT) + return true; + + /* + * We can't read out IPS on broadwell, assume the worst and + * forcibly enable IPS on the first fastset. + */ + if (new_crtc_state->update_pipe && old_crtc_state->inherited) + return true; + + return !old_crtc_state->ips_enabled; +} + +void hsw_ips_post_update(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + if (!hsw_ips_need_enable(state, crtc)) + return; + + hsw_ips_enable(new_crtc_state); +} + +/* IPS only exists on ULT machines and is tied to pipe A. */ +bool hsw_crtc_supports_ips(struct intel_crtc *crtc) +{ + return HAS_IPS(to_i915(crtc->base.dev)) && crtc->pipe == PIPE_A; +} + +bool hsw_crtc_state_ips_capable(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + + /* IPS only exists on ULT machines and is tied to pipe A. */ + if (!hsw_crtc_supports_ips(crtc)) + return false; + + if (!i915->params.enable_ips) + return false; + + if (crtc_state->pipe_bpp > 24) + return false; + + /* + * We compare against max which means we must take + * the increased cdclk requirement into account when + * calculating the new cdclk. + * + * Should measure whether using a lower cdclk w/o IPS + */ + if (IS_BROADWELL(i915) && + crtc_state->pixel_rate > i915->display.cdclk.max_cdclk_freq * 95 / 100) + return false; + + return true; +} + +int hsw_ips_compute_config(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *i915 = to_i915(state->base.dev); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + crtc_state->ips_enabled = false; + + if (!hsw_crtc_state_ips_capable(crtc_state)) + return 0; + + /* + * When IPS gets enabled, the pipe CRC changes. Since IPS gets + * enabled and disabled dynamically based on package C states, + * user space can't make reliable use of the CRCs, so let's just + * completely disable it. + */ + if (crtc_state->crc_enabled) + return 0; + + /* IPS should be fine as long as at least one plane is enabled. */ + if (!(crtc_state->active_planes & ~BIT(PLANE_CURSOR))) + return 0; + + if (IS_BROADWELL(i915)) { + const struct intel_cdclk_state *cdclk_state; + + cdclk_state = intel_atomic_get_cdclk_state(state); + if (IS_ERR(cdclk_state)) + return PTR_ERR(cdclk_state); + + /* pixel rate mustn't exceed 95% of cdclk with IPS on BDW */ + if (crtc_state->pixel_rate > cdclk_state->logical.cdclk * 95 / 100) + return 0; + } + + crtc_state->ips_enabled = true; + + return 0; +} + +void hsw_ips_get_config(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + + if (!hsw_crtc_supports_ips(crtc)) + return; + + if (IS_HASWELL(i915)) { + crtc_state->ips_enabled = intel_de_read(i915, IPS_CTL) & IPS_ENABLE; + } else { + /* + * We cannot readout IPS state on broadwell, set to + * true so we can set it to a defined state on first + * commit. + */ + crtc_state->ips_enabled = true; + } +} diff --git a/drivers/gpu/drm/i915/display/hsw_ips.h b/drivers/gpu/drm/i915/display/hsw_ips.h new file mode 100644 index 000000000..4564dee49 --- /dev/null +++ b/drivers/gpu/drm/i915/display/hsw_ips.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2022 Intel Corporation + */ + +#ifndef __HSW_IPS_H__ +#define __HSW_IPS_H__ + +#include + +struct intel_atomic_state; +struct intel_crtc; +struct intel_crtc_state; + +bool hsw_ips_disable(const struct intel_crtc_state *crtc_state); +bool hsw_ips_pre_update(struct intel_atomic_state *state, + struct intel_crtc *crtc); +void hsw_ips_post_update(struct intel_atomic_state *state, + struct intel_crtc *crtc); +bool hsw_crtc_supports_ips(struct intel_crtc *crtc); +bool hsw_crtc_state_ips_capable(const struct intel_crtc_state *crtc_state); +int hsw_ips_compute_config(struct intel_atomic_state *state, + struct intel_crtc *crtc); +void hsw_ips_get_config(struct intel_crtc_state *crtc_state); + +#endif /* __HSW_IPS_H__ */ diff --git a/drivers/gpu/drm/i915/display/i9xx_plane.c b/drivers/gpu/drm/i915/display/i9xx_plane.c new file mode 100644 index 000000000..5afbe3e98 --- /dev/null +++ b/drivers/gpu/drm/i915/display/i9xx_plane.c @@ -0,0 +1,1056 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2020 Intel Corporation + */ +#include + +#include +#include +#include + +#include "intel_atomic.h" +#include "intel_atomic_plane.h" +#include "intel_de.h" +#include "intel_display_types.h" +#include "intel_fb.h" +#include "intel_fbc.h" +#include "intel_sprite.h" +#include "i9xx_plane.h" + +/* Primary plane formats for gen <= 3 */ +static const u32 i8xx_primary_formats[] = { + DRM_FORMAT_C8, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_RGB565, + DRM_FORMAT_XRGB8888, +}; + +/* Primary plane formats for ivb (no fp16 due to hw issue) */ +static const u32 ivb_primary_formats[] = { + DRM_FORMAT_C8, + DRM_FORMAT_RGB565, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_XRGB2101010, + DRM_FORMAT_XBGR2101010, +}; + +/* Primary plane formats for gen >= 4, except ivb */ +static const u32 i965_primary_formats[] = { + DRM_FORMAT_C8, + DRM_FORMAT_RGB565, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_XRGB2101010, + DRM_FORMAT_XBGR2101010, + DRM_FORMAT_XBGR16161616F, +}; + +/* Primary plane formats for vlv/chv */ +static const u32 vlv_primary_formats[] = { + DRM_FORMAT_C8, + DRM_FORMAT_RGB565, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_XRGB2101010, + DRM_FORMAT_XBGR2101010, + DRM_FORMAT_ARGB2101010, + DRM_FORMAT_ABGR2101010, + DRM_FORMAT_XBGR16161616F, +}; + +static bool i8xx_plane_format_mod_supported(struct drm_plane *_plane, + u32 format, u64 modifier) +{ + if (!intel_fb_plane_supports_modifier(to_intel_plane(_plane), modifier)) + return false; + + switch (format) { + case DRM_FORMAT_C8: + case DRM_FORMAT_RGB565: + case DRM_FORMAT_XRGB1555: + case DRM_FORMAT_XRGB8888: + return modifier == DRM_FORMAT_MOD_LINEAR || + modifier == I915_FORMAT_MOD_X_TILED; + default: + return false; + } +} + +static bool i965_plane_format_mod_supported(struct drm_plane *_plane, + u32 format, u64 modifier) +{ + if (!intel_fb_plane_supports_modifier(to_intel_plane(_plane), modifier)) + return false; + + switch (format) { + case DRM_FORMAT_C8: + case DRM_FORMAT_RGB565: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_XBGR2101010: + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_ABGR2101010: + case DRM_FORMAT_XBGR16161616F: + return modifier == DRM_FORMAT_MOD_LINEAR || + modifier == I915_FORMAT_MOD_X_TILED; + default: + return false; + } +} + +static bool i9xx_plane_has_fbc(struct drm_i915_private *dev_priv, + enum i9xx_plane_id i9xx_plane) +{ + if (!HAS_FBC(dev_priv)) + return false; + + if (IS_BROADWELL(dev_priv) || IS_HASWELL(dev_priv)) + return i9xx_plane == PLANE_A; /* tied to pipe A */ + else if (IS_IVYBRIDGE(dev_priv)) + return i9xx_plane == PLANE_A || i9xx_plane == PLANE_B || + i9xx_plane == PLANE_C; + else if (DISPLAY_VER(dev_priv) >= 4) + return i9xx_plane == PLANE_A || i9xx_plane == PLANE_B; + else + return i9xx_plane == PLANE_A; +} + +static struct intel_fbc *i9xx_plane_fbc(struct drm_i915_private *dev_priv, + enum i9xx_plane_id i9xx_plane) +{ + if (i9xx_plane_has_fbc(dev_priv, i9xx_plane)) + return dev_priv->display.fbc[INTEL_FBC_A]; + else + return NULL; +} + +static bool i9xx_plane_has_windowing(struct intel_plane *plane) +{ + struct drm_i915_private *dev_priv = to_i915(plane->base.dev); + enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; + + if (IS_CHERRYVIEW(dev_priv)) + return i9xx_plane == PLANE_B; + else if (DISPLAY_VER(dev_priv) >= 5 || IS_G4X(dev_priv)) + return false; + else if (DISPLAY_VER(dev_priv) == 4) + return i9xx_plane == PLANE_C; + else + return i9xx_plane == PLANE_B || + i9xx_plane == PLANE_C; +} + +static u32 i9xx_plane_ctl(const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state) +{ + struct drm_i915_private *dev_priv = + to_i915(plane_state->uapi.plane->dev); + const struct drm_framebuffer *fb = plane_state->hw.fb; + unsigned int rotation = plane_state->hw.rotation; + u32 dspcntr; + + dspcntr = DISP_ENABLE; + + if (IS_G4X(dev_priv) || IS_IRONLAKE(dev_priv) || + IS_SANDYBRIDGE(dev_priv) || IS_IVYBRIDGE(dev_priv)) + dspcntr |= DISP_TRICKLE_FEED_DISABLE; + + switch (fb->format->format) { + case DRM_FORMAT_C8: + dspcntr |= DISP_FORMAT_8BPP; + break; + case DRM_FORMAT_XRGB1555: + dspcntr |= DISP_FORMAT_BGRX555; + break; + case DRM_FORMAT_ARGB1555: + dspcntr |= DISP_FORMAT_BGRA555; + break; + case DRM_FORMAT_RGB565: + dspcntr |= DISP_FORMAT_BGRX565; + break; + case DRM_FORMAT_XRGB8888: + dspcntr |= DISP_FORMAT_BGRX888; + break; + case DRM_FORMAT_XBGR8888: + dspcntr |= DISP_FORMAT_RGBX888; + break; + case DRM_FORMAT_ARGB8888: + dspcntr |= DISP_FORMAT_BGRA888; + break; + case DRM_FORMAT_ABGR8888: + dspcntr |= DISP_FORMAT_RGBA888; + break; + case DRM_FORMAT_XRGB2101010: + dspcntr |= DISP_FORMAT_BGRX101010; + break; + case DRM_FORMAT_XBGR2101010: + dspcntr |= DISP_FORMAT_RGBX101010; + break; + case DRM_FORMAT_ARGB2101010: + dspcntr |= DISP_FORMAT_BGRA101010; + break; + case DRM_FORMAT_ABGR2101010: + dspcntr |= DISP_FORMAT_RGBA101010; + break; + case DRM_FORMAT_XBGR16161616F: + dspcntr |= DISP_FORMAT_RGBX161616; + break; + default: + MISSING_CASE(fb->format->format); + return 0; + } + + if (DISPLAY_VER(dev_priv) >= 4 && + fb->modifier == I915_FORMAT_MOD_X_TILED) + dspcntr |= DISP_TILED; + + if (rotation & DRM_MODE_ROTATE_180) + dspcntr |= DISP_ROTATE_180; + + if (rotation & DRM_MODE_REFLECT_X) + dspcntr |= DISP_MIRROR; + + return dspcntr; +} + +int i9xx_check_plane_surface(struct intel_plane_state *plane_state) +{ + struct drm_i915_private *dev_priv = + to_i915(plane_state->uapi.plane->dev); + const struct drm_framebuffer *fb = plane_state->hw.fb; + int src_x, src_y, src_w; + u32 offset; + int ret; + + ret = intel_plane_compute_gtt(plane_state); + if (ret) + return ret; + + if (!plane_state->uapi.visible) + return 0; + + src_w = drm_rect_width(&plane_state->uapi.src) >> 16; + src_x = plane_state->uapi.src.x1 >> 16; + src_y = plane_state->uapi.src.y1 >> 16; + + /* Undocumented hardware limit on i965/g4x/vlv/chv */ + if (HAS_GMCH(dev_priv) && fb->format->cpp[0] == 8 && src_w > 2048) + return -EINVAL; + + intel_add_fb_offsets(&src_x, &src_y, plane_state, 0); + + if (DISPLAY_VER(dev_priv) >= 4) + offset = intel_plane_compute_aligned_offset(&src_x, &src_y, + plane_state, 0); + else + offset = 0; + + /* + * When using an X-tiled surface the plane starts to + * misbehave if the x offset + width exceeds the stride. + * hsw/bdw: underrun galore + * ilk/snb/ivb: wrap to the next tile row mid scanout + * i965/g4x: so far appear immune to this + * vlv/chv: TODO check + * + * Linear surfaces seem to work just fine, even on hsw/bdw + * despite them not using the linear offset anymore. + */ + if (DISPLAY_VER(dev_priv) >= 4 && fb->modifier == I915_FORMAT_MOD_X_TILED) { + u32 alignment = intel_surf_alignment(fb, 0); + int cpp = fb->format->cpp[0]; + + while ((src_x + src_w) * cpp > plane_state->view.color_plane[0].mapping_stride) { + if (offset == 0) { + drm_dbg_kms(&dev_priv->drm, + "Unable to find suitable display surface offset due to X-tiling\n"); + return -EINVAL; + } + + offset = intel_plane_adjust_aligned_offset(&src_x, &src_y, plane_state, 0, + offset, offset - alignment); + } + } + + /* + * Put the final coordinates back so that the src + * coordinate checks will see the right values. + */ + drm_rect_translate_to(&plane_state->uapi.src, + src_x << 16, src_y << 16); + + /* HSW/BDW do this automagically in hardware */ + if (!IS_HASWELL(dev_priv) && !IS_BROADWELL(dev_priv)) { + unsigned int rotation = plane_state->hw.rotation; + int src_w = drm_rect_width(&plane_state->uapi.src) >> 16; + int src_h = drm_rect_height(&plane_state->uapi.src) >> 16; + + if (rotation & DRM_MODE_ROTATE_180) { + src_x += src_w - 1; + src_y += src_h - 1; + } else if (rotation & DRM_MODE_REFLECT_X) { + src_x += src_w - 1; + } + } + + if (IS_HASWELL(dev_priv) || IS_BROADWELL(dev_priv)) { + drm_WARN_ON(&dev_priv->drm, src_x > 8191 || src_y > 4095); + } else if (DISPLAY_VER(dev_priv) >= 4 && + fb->modifier == I915_FORMAT_MOD_X_TILED) { + drm_WARN_ON(&dev_priv->drm, src_x > 4095 || src_y > 4095); + } + + plane_state->view.color_plane[0].offset = offset; + plane_state->view.color_plane[0].x = src_x; + plane_state->view.color_plane[0].y = src_y; + + return 0; +} + +static int +i9xx_plane_check(struct intel_crtc_state *crtc_state, + struct intel_plane_state *plane_state) +{ + struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane); + int ret; + + ret = chv_plane_check_rotation(plane_state); + if (ret) + return ret; + + ret = intel_atomic_plane_check_clipping(plane_state, crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + i9xx_plane_has_windowing(plane)); + if (ret) + return ret; + + ret = i9xx_check_plane_surface(plane_state); + if (ret) + return ret; + + if (!plane_state->uapi.visible) + return 0; + + ret = intel_plane_check_src_coordinates(plane_state); + if (ret) + return ret; + + plane_state->ctl = i9xx_plane_ctl(crtc_state, plane_state); + + return 0; +} + +static u32 i9xx_plane_ctl_crtc(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + u32 dspcntr = 0; + + if (crtc_state->gamma_enable) + dspcntr |= DISP_PIPE_GAMMA_ENABLE; + + if (crtc_state->csc_enable) + dspcntr |= DISP_PIPE_CSC_ENABLE; + + if (DISPLAY_VER(dev_priv) < 5) + dspcntr |= DISP_PIPE_SEL(crtc->pipe); + + return dspcntr; +} + +static void i9xx_plane_ratio(const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state, + unsigned int *num, unsigned int *den) +{ + const struct drm_framebuffer *fb = plane_state->hw.fb; + unsigned int cpp = fb->format->cpp[0]; + + /* + * g4x bspec says 64bpp pixel rate can't exceed 80% + * of cdclk when the sprite plane is enabled on the + * same pipe. ilk/snb bspec says 64bpp pixel rate is + * never allowed to exceed 80% of cdclk. Let's just go + * with the ilk/snb limit always. + */ + if (cpp == 8) { + *num = 10; + *den = 8; + } else { + *num = 1; + *den = 1; + } +} + +static int i9xx_plane_min_cdclk(const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state) +{ + unsigned int pixel_rate; + unsigned int num, den; + + /* + * Note that crtc_state->pixel_rate accounts for both + * horizontal and vertical panel fitter downscaling factors. + * Pre-HSW bspec tells us to only consider the horizontal + * downscaling factor here. We ignore that and just consider + * both for simplicity. + */ + pixel_rate = crtc_state->pixel_rate; + + i9xx_plane_ratio(crtc_state, plane_state, &num, &den); + + /* two pixels per clock with double wide pipe */ + if (crtc_state->double_wide) + den *= 2; + + return DIV_ROUND_UP(pixel_rate * num, den); +} + +static void i9xx_plane_update_noarm(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state) +{ + struct drm_i915_private *dev_priv = to_i915(plane->base.dev); + enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; + + intel_de_write_fw(dev_priv, DSPSTRIDE(i9xx_plane), + plane_state->view.color_plane[0].mapping_stride); + + if (DISPLAY_VER(dev_priv) < 4) { + int crtc_x = plane_state->uapi.dst.x1; + int crtc_y = plane_state->uapi.dst.y1; + int crtc_w = drm_rect_width(&plane_state->uapi.dst); + int crtc_h = drm_rect_height(&plane_state->uapi.dst); + + /* + * PLANE_A doesn't actually have a full window + * generator but let's assume we still need to + * program whatever is there. + */ + intel_de_write_fw(dev_priv, DSPPOS(i9xx_plane), + DISP_POS_Y(crtc_y) | DISP_POS_X(crtc_x)); + intel_de_write_fw(dev_priv, DSPSIZE(i9xx_plane), + DISP_HEIGHT(crtc_h - 1) | DISP_WIDTH(crtc_w - 1)); + } +} + +static void i9xx_plane_update_arm(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state) +{ + struct drm_i915_private *dev_priv = to_i915(plane->base.dev); + enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; + int x = plane_state->view.color_plane[0].x; + int y = plane_state->view.color_plane[0].y; + u32 dspcntr, dspaddr_offset, linear_offset; + + dspcntr = plane_state->ctl | i9xx_plane_ctl_crtc(crtc_state); + + linear_offset = intel_fb_xy_to_linear(x, y, plane_state, 0); + + if (DISPLAY_VER(dev_priv) >= 4) + dspaddr_offset = plane_state->view.color_plane[0].offset; + else + dspaddr_offset = linear_offset; + + if (IS_CHERRYVIEW(dev_priv) && i9xx_plane == PLANE_B) { + int crtc_x = plane_state->uapi.dst.x1; + int crtc_y = plane_state->uapi.dst.y1; + int crtc_w = drm_rect_width(&plane_state->uapi.dst); + int crtc_h = drm_rect_height(&plane_state->uapi.dst); + + intel_de_write_fw(dev_priv, PRIMPOS(i9xx_plane), + PRIM_POS_Y(crtc_y) | PRIM_POS_X(crtc_x)); + intel_de_write_fw(dev_priv, PRIMSIZE(i9xx_plane), + PRIM_HEIGHT(crtc_h - 1) | PRIM_WIDTH(crtc_w - 1)); + intel_de_write_fw(dev_priv, PRIMCNSTALPHA(i9xx_plane), 0); + } + + if (IS_HASWELL(dev_priv) || IS_BROADWELL(dev_priv)) { + intel_de_write_fw(dev_priv, DSPOFFSET(i9xx_plane), + DISP_OFFSET_Y(y) | DISP_OFFSET_X(x)); + } else if (DISPLAY_VER(dev_priv) >= 4) { + intel_de_write_fw(dev_priv, DSPLINOFF(i9xx_plane), + linear_offset); + intel_de_write_fw(dev_priv, DSPTILEOFF(i9xx_plane), + DISP_OFFSET_Y(y) | DISP_OFFSET_X(x)); + } + + /* + * The control register self-arms if the plane was previously + * disabled. Try to make the plane enable atomic by writing + * the control register just before the surface register. + */ + intel_de_write_fw(dev_priv, DSPCNTR(i9xx_plane), dspcntr); + + if (DISPLAY_VER(dev_priv) >= 4) + intel_de_write_fw(dev_priv, DSPSURF(i9xx_plane), + intel_plane_ggtt_offset(plane_state) + dspaddr_offset); + else + intel_de_write_fw(dev_priv, DSPADDR(i9xx_plane), + intel_plane_ggtt_offset(plane_state) + dspaddr_offset); +} + +static void i830_plane_update_arm(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state) +{ + /* + * On i830/i845 all registers are self-arming [ALM040]. + * + * Additional breakage on i830 causes register reads to return + * the last latched value instead of the last written value [ALM026]. + */ + i9xx_plane_update_noarm(plane, crtc_state, plane_state); + i9xx_plane_update_arm(plane, crtc_state, plane_state); +} + +static void i9xx_plane_disable_arm(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(plane->base.dev); + enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; + u32 dspcntr; + + /* + * DSPCNTR pipe gamma enable on g4x+ and pipe csc + * enable on ilk+ affect the pipe bottom color as + * well, so we must configure them even if the plane + * is disabled. + * + * On pre-g4x there is no way to gamma correct the + * pipe bottom color but we'll keep on doing this + * anyway so that the crtc state readout works correctly. + */ + dspcntr = i9xx_plane_ctl_crtc(crtc_state); + + intel_de_write_fw(dev_priv, DSPCNTR(i9xx_plane), dspcntr); + + if (DISPLAY_VER(dev_priv) >= 4) + intel_de_write_fw(dev_priv, DSPSURF(i9xx_plane), 0); + else + intel_de_write_fw(dev_priv, DSPADDR(i9xx_plane), 0); +} + +static void +g4x_primary_async_flip(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state, + bool async_flip) +{ + struct drm_i915_private *dev_priv = to_i915(plane->base.dev); + u32 dspcntr = plane_state->ctl | i9xx_plane_ctl_crtc(crtc_state); + u32 dspaddr_offset = plane_state->view.color_plane[0].offset; + enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; + + if (async_flip) + dspcntr |= DISP_ASYNC_FLIP; + + intel_de_write_fw(dev_priv, DSPCNTR(i9xx_plane), dspcntr); + + intel_de_write_fw(dev_priv, DSPSURF(i9xx_plane), + intel_plane_ggtt_offset(plane_state) + dspaddr_offset); +} + +static void +vlv_primary_async_flip(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state, + bool async_flip) +{ + struct drm_i915_private *dev_priv = to_i915(plane->base.dev); + u32 dspaddr_offset = plane_state->view.color_plane[0].offset; + enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; + + intel_de_write_fw(dev_priv, DSPADDR_VLV(i9xx_plane), + intel_plane_ggtt_offset(plane_state) + dspaddr_offset); +} + +static void +bdw_primary_enable_flip_done(struct intel_plane *plane) +{ + struct drm_i915_private *i915 = to_i915(plane->base.dev); + enum pipe pipe = plane->pipe; + + spin_lock_irq(&i915->irq_lock); + bdw_enable_pipe_irq(i915, pipe, GEN8_PIPE_PRIMARY_FLIP_DONE); + spin_unlock_irq(&i915->irq_lock); +} + +static void +bdw_primary_disable_flip_done(struct intel_plane *plane) +{ + struct drm_i915_private *i915 = to_i915(plane->base.dev); + enum pipe pipe = plane->pipe; + + spin_lock_irq(&i915->irq_lock); + bdw_disable_pipe_irq(i915, pipe, GEN8_PIPE_PRIMARY_FLIP_DONE); + spin_unlock_irq(&i915->irq_lock); +} + +static void +ivb_primary_enable_flip_done(struct intel_plane *plane) +{ + struct drm_i915_private *i915 = to_i915(plane->base.dev); + + spin_lock_irq(&i915->irq_lock); + ilk_enable_display_irq(i915, DE_PLANE_FLIP_DONE_IVB(plane->i9xx_plane)); + spin_unlock_irq(&i915->irq_lock); +} + +static void +ivb_primary_disable_flip_done(struct intel_plane *plane) +{ + struct drm_i915_private *i915 = to_i915(plane->base.dev); + + spin_lock_irq(&i915->irq_lock); + ilk_disable_display_irq(i915, DE_PLANE_FLIP_DONE_IVB(plane->i9xx_plane)); + spin_unlock_irq(&i915->irq_lock); +} + +static void +ilk_primary_enable_flip_done(struct intel_plane *plane) +{ + struct drm_i915_private *i915 = to_i915(plane->base.dev); + + spin_lock_irq(&i915->irq_lock); + ilk_enable_display_irq(i915, DE_PLANE_FLIP_DONE(plane->i9xx_plane)); + spin_unlock_irq(&i915->irq_lock); +} + +static void +ilk_primary_disable_flip_done(struct intel_plane *plane) +{ + struct drm_i915_private *i915 = to_i915(plane->base.dev); + + spin_lock_irq(&i915->irq_lock); + ilk_disable_display_irq(i915, DE_PLANE_FLIP_DONE(plane->i9xx_plane)); + spin_unlock_irq(&i915->irq_lock); +} + +static void +vlv_primary_enable_flip_done(struct intel_plane *plane) +{ + struct drm_i915_private *i915 = to_i915(plane->base.dev); + enum pipe pipe = plane->pipe; + + spin_lock_irq(&i915->irq_lock); + i915_enable_pipestat(i915, pipe, PLANE_FLIP_DONE_INT_STATUS_VLV); + spin_unlock_irq(&i915->irq_lock); +} + +static void +vlv_primary_disable_flip_done(struct intel_plane *plane) +{ + struct drm_i915_private *i915 = to_i915(plane->base.dev); + enum pipe pipe = plane->pipe; + + spin_lock_irq(&i915->irq_lock); + i915_disable_pipestat(i915, pipe, PLANE_FLIP_DONE_INT_STATUS_VLV); + spin_unlock_irq(&i915->irq_lock); +} + +static bool i9xx_plane_get_hw_state(struct intel_plane *plane, + enum pipe *pipe) +{ + struct drm_i915_private *dev_priv = to_i915(plane->base.dev); + enum intel_display_power_domain power_domain; + enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; + intel_wakeref_t wakeref; + bool ret; + u32 val; + + /* + * Not 100% correct for planes that can move between pipes, + * but that's only the case for gen2-4 which don't have any + * display power wells. + */ + power_domain = POWER_DOMAIN_PIPE(plane->pipe); + wakeref = intel_display_power_get_if_enabled(dev_priv, power_domain); + if (!wakeref) + return false; + + val = intel_de_read(dev_priv, DSPCNTR(i9xx_plane)); + + ret = val & DISP_ENABLE; + + if (DISPLAY_VER(dev_priv) >= 5) + *pipe = plane->pipe; + else + *pipe = REG_FIELD_GET(DISP_PIPE_SEL_MASK, val); + + intel_display_power_put(dev_priv, power_domain, wakeref); + + return ret; +} + +static unsigned int +hsw_primary_max_stride(struct intel_plane *plane, + u32 pixel_format, u64 modifier, + unsigned int rotation) +{ + const struct drm_format_info *info = drm_format_info(pixel_format); + int cpp = info->cpp[0]; + + /* Limit to 8k pixels to guarantee OFFSET.x doesn't get too big. */ + return min(8192 * cpp, 32 * 1024); +} + +static unsigned int +ilk_primary_max_stride(struct intel_plane *plane, + u32 pixel_format, u64 modifier, + unsigned int rotation) +{ + const struct drm_format_info *info = drm_format_info(pixel_format); + int cpp = info->cpp[0]; + + /* Limit to 4k pixels to guarantee TILEOFF.x doesn't get too big. */ + if (modifier == I915_FORMAT_MOD_X_TILED) + return min(4096 * cpp, 32 * 1024); + else + return 32 * 1024; +} + +unsigned int +i965_plane_max_stride(struct intel_plane *plane, + u32 pixel_format, u64 modifier, + unsigned int rotation) +{ + const struct drm_format_info *info = drm_format_info(pixel_format); + int cpp = info->cpp[0]; + + /* Limit to 4k pixels to guarantee TILEOFF.x doesn't get too big. */ + if (modifier == I915_FORMAT_MOD_X_TILED) + return min(4096 * cpp, 16 * 1024); + else + return 32 * 1024; +} + +static unsigned int +i9xx_plane_max_stride(struct intel_plane *plane, + u32 pixel_format, u64 modifier, + unsigned int rotation) +{ + struct drm_i915_private *dev_priv = to_i915(plane->base.dev); + + if (DISPLAY_VER(dev_priv) >= 3) { + if (modifier == I915_FORMAT_MOD_X_TILED) + return 8*1024; + else + return 16*1024; + } else { + if (plane->i9xx_plane == PLANE_C) + return 4*1024; + else + return 8*1024; + } +} + +static const struct drm_plane_funcs i965_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = intel_plane_destroy, + .atomic_duplicate_state = intel_plane_duplicate_state, + .atomic_destroy_state = intel_plane_destroy_state, + .format_mod_supported = i965_plane_format_mod_supported, +}; + +static const struct drm_plane_funcs i8xx_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = intel_plane_destroy, + .atomic_duplicate_state = intel_plane_duplicate_state, + .atomic_destroy_state = intel_plane_destroy_state, + .format_mod_supported = i8xx_plane_format_mod_supported, +}; + +struct intel_plane * +intel_primary_plane_create(struct drm_i915_private *dev_priv, enum pipe pipe) +{ + struct intel_plane *plane; + const struct drm_plane_funcs *plane_funcs; + unsigned int supported_rotations; + const u64 *modifiers; + const u32 *formats; + int num_formats; + int ret, zpos; + + plane = intel_plane_alloc(); + if (IS_ERR(plane)) + return plane; + + plane->pipe = pipe; + /* + * On gen2/3 only plane A can do FBC, but the panel fitter and LVDS + * port is hooked to pipe B. Hence we want plane A feeding pipe B. + */ + if (HAS_FBC(dev_priv) && DISPLAY_VER(dev_priv) < 4 && + INTEL_NUM_PIPES(dev_priv) == 2) + plane->i9xx_plane = (enum i9xx_plane_id) !pipe; + else + plane->i9xx_plane = (enum i9xx_plane_id) pipe; + plane->id = PLANE_PRIMARY; + plane->frontbuffer_bit = INTEL_FRONTBUFFER(pipe, plane->id); + + intel_fbc_add_plane(i9xx_plane_fbc(dev_priv, plane->i9xx_plane), plane); + + if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { + formats = vlv_primary_formats; + num_formats = ARRAY_SIZE(vlv_primary_formats); + } else if (DISPLAY_VER(dev_priv) >= 4) { + /* + * WaFP16GammaEnabling:ivb + * "Workaround : When using the 64-bit format, the plane + * output on each color channel has one quarter amplitude. + * It can be brought up to full amplitude by using pipe + * gamma correction or pipe color space conversion to + * multiply the plane output by four." + * + * There is no dedicated plane gamma for the primary plane, + * and using the pipe gamma/csc could conflict with other + * planes, so we choose not to expose fp16 on IVB primary + * planes. HSW primary planes no longer have this problem. + */ + if (IS_IVYBRIDGE(dev_priv)) { + formats = ivb_primary_formats; + num_formats = ARRAY_SIZE(ivb_primary_formats); + } else { + formats = i965_primary_formats; + num_formats = ARRAY_SIZE(i965_primary_formats); + } + } else { + formats = i8xx_primary_formats; + num_formats = ARRAY_SIZE(i8xx_primary_formats); + } + + if (DISPLAY_VER(dev_priv) >= 4) + plane_funcs = &i965_plane_funcs; + else + plane_funcs = &i8xx_plane_funcs; + + if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) + plane->min_cdclk = vlv_plane_min_cdclk; + else if (IS_BROADWELL(dev_priv) || IS_HASWELL(dev_priv)) + plane->min_cdclk = hsw_plane_min_cdclk; + else if (IS_IVYBRIDGE(dev_priv)) + plane->min_cdclk = ivb_plane_min_cdclk; + else + plane->min_cdclk = i9xx_plane_min_cdclk; + + if (HAS_GMCH(dev_priv)) { + if (DISPLAY_VER(dev_priv) >= 4) + plane->max_stride = i965_plane_max_stride; + else + plane->max_stride = i9xx_plane_max_stride; + } else { + if (IS_BROADWELL(dev_priv) || IS_HASWELL(dev_priv)) + plane->max_stride = hsw_primary_max_stride; + else + plane->max_stride = ilk_primary_max_stride; + } + + if (IS_I830(dev_priv) || IS_I845G(dev_priv)) { + plane->update_arm = i830_plane_update_arm; + } else { + plane->update_noarm = i9xx_plane_update_noarm; + plane->update_arm = i9xx_plane_update_arm; + } + plane->disable_arm = i9xx_plane_disable_arm; + plane->get_hw_state = i9xx_plane_get_hw_state; + plane->check_plane = i9xx_plane_check; + + if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { + plane->async_flip = vlv_primary_async_flip; + plane->enable_flip_done = vlv_primary_enable_flip_done; + plane->disable_flip_done = vlv_primary_disable_flip_done; + } else if (IS_BROADWELL(dev_priv)) { + plane->need_async_flip_disable_wa = true; + plane->async_flip = g4x_primary_async_flip; + plane->enable_flip_done = bdw_primary_enable_flip_done; + plane->disable_flip_done = bdw_primary_disable_flip_done; + } else if (DISPLAY_VER(dev_priv) >= 7) { + plane->async_flip = g4x_primary_async_flip; + plane->enable_flip_done = ivb_primary_enable_flip_done; + plane->disable_flip_done = ivb_primary_disable_flip_done; + } else if (DISPLAY_VER(dev_priv) >= 5) { + plane->async_flip = g4x_primary_async_flip; + plane->enable_flip_done = ilk_primary_enable_flip_done; + plane->disable_flip_done = ilk_primary_disable_flip_done; + } + + modifiers = intel_fb_plane_get_modifiers(dev_priv, INTEL_PLANE_CAP_TILING_X); + + if (DISPLAY_VER(dev_priv) >= 5 || IS_G4X(dev_priv)) + ret = drm_universal_plane_init(&dev_priv->drm, &plane->base, + 0, plane_funcs, + formats, num_formats, + modifiers, + DRM_PLANE_TYPE_PRIMARY, + "primary %c", pipe_name(pipe)); + else + ret = drm_universal_plane_init(&dev_priv->drm, &plane->base, + 0, plane_funcs, + formats, num_formats, + modifiers, + DRM_PLANE_TYPE_PRIMARY, + "plane %c", + plane_name(plane->i9xx_plane)); + + kfree(modifiers); + + if (ret) + goto fail; + + if (IS_CHERRYVIEW(dev_priv) && pipe == PIPE_B) { + supported_rotations = + DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_180 | + DRM_MODE_REFLECT_X; + } else if (DISPLAY_VER(dev_priv) >= 4) { + supported_rotations = + DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_180; + } else { + supported_rotations = DRM_MODE_ROTATE_0; + } + + if (DISPLAY_VER(dev_priv) >= 4) + drm_plane_create_rotation_property(&plane->base, + DRM_MODE_ROTATE_0, + supported_rotations); + + zpos = 0; + drm_plane_create_zpos_immutable_property(&plane->base, zpos); + + intel_plane_helper_add(plane); + + return plane; + +fail: + intel_plane_free(plane); + + return ERR_PTR(ret); +} + +static int i9xx_format_to_fourcc(int format) +{ + switch (format) { + case DISP_FORMAT_8BPP: + return DRM_FORMAT_C8; + case DISP_FORMAT_BGRA555: + return DRM_FORMAT_ARGB1555; + case DISP_FORMAT_BGRX555: + return DRM_FORMAT_XRGB1555; + case DISP_FORMAT_BGRX565: + return DRM_FORMAT_RGB565; + default: + case DISP_FORMAT_BGRX888: + return DRM_FORMAT_XRGB8888; + case DISP_FORMAT_RGBX888: + return DRM_FORMAT_XBGR8888; + case DISP_FORMAT_BGRA888: + return DRM_FORMAT_ARGB8888; + case DISP_FORMAT_RGBA888: + return DRM_FORMAT_ABGR8888; + case DISP_FORMAT_BGRX101010: + return DRM_FORMAT_XRGB2101010; + case DISP_FORMAT_RGBX101010: + return DRM_FORMAT_XBGR2101010; + case DISP_FORMAT_BGRA101010: + return DRM_FORMAT_ARGB2101010; + case DISP_FORMAT_RGBA101010: + return DRM_FORMAT_ABGR2101010; + case DISP_FORMAT_RGBX161616: + return DRM_FORMAT_XBGR16161616F; + } +} + +void +i9xx_get_initial_plane_config(struct intel_crtc *crtc, + struct intel_initial_plane_config *plane_config) +{ + struct drm_device *dev = crtc->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + struct intel_plane *plane = to_intel_plane(crtc->base.primary); + enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; + enum pipe pipe; + u32 val, base, offset; + int fourcc, pixel_format; + unsigned int aligned_height; + struct drm_framebuffer *fb; + struct intel_framebuffer *intel_fb; + + if (!plane->get_hw_state(plane, &pipe)) + return; + + drm_WARN_ON(dev, pipe != crtc->pipe); + + intel_fb = kzalloc(sizeof(*intel_fb), GFP_KERNEL); + if (!intel_fb) { + drm_dbg_kms(&dev_priv->drm, "failed to alloc fb\n"); + return; + } + + fb = &intel_fb->base; + + fb->dev = dev; + + val = intel_de_read(dev_priv, DSPCNTR(i9xx_plane)); + + if (DISPLAY_VER(dev_priv) >= 4) { + if (val & DISP_TILED) { + plane_config->tiling = I915_TILING_X; + fb->modifier = I915_FORMAT_MOD_X_TILED; + } + + if (val & DISP_ROTATE_180) + plane_config->rotation = DRM_MODE_ROTATE_180; + } + + if (IS_CHERRYVIEW(dev_priv) && pipe == PIPE_B && + val & DISP_MIRROR) + plane_config->rotation |= DRM_MODE_REFLECT_X; + + pixel_format = val & DISP_FORMAT_MASK; + fourcc = i9xx_format_to_fourcc(pixel_format); + fb->format = drm_format_info(fourcc); + + if (IS_HASWELL(dev_priv) || IS_BROADWELL(dev_priv)) { + offset = intel_de_read(dev_priv, DSPOFFSET(i9xx_plane)); + base = intel_de_read(dev_priv, DSPSURF(i9xx_plane)) & DISP_ADDR_MASK; + } else if (DISPLAY_VER(dev_priv) >= 4) { + if (plane_config->tiling) + offset = intel_de_read(dev_priv, + DSPTILEOFF(i9xx_plane)); + else + offset = intel_de_read(dev_priv, + DSPLINOFF(i9xx_plane)); + base = intel_de_read(dev_priv, DSPSURF(i9xx_plane)) & DISP_ADDR_MASK; + } else { + base = intel_de_read(dev_priv, DSPADDR(i9xx_plane)); + } + plane_config->base = base; + + val = intel_de_read(dev_priv, PIPESRC(pipe)); + fb->width = REG_FIELD_GET(PIPESRC_WIDTH_MASK, val) + 1; + fb->height = REG_FIELD_GET(PIPESRC_HEIGHT_MASK, val) + 1; + + val = intel_de_read(dev_priv, DSPSTRIDE(i9xx_plane)); + fb->pitches[0] = val & 0xffffffc0; + + aligned_height = intel_fb_align_height(fb, 0, fb->height); + + plane_config->size = fb->pitches[0] * aligned_height; + + drm_dbg_kms(&dev_priv->drm, + "%s/%s with fb: size=%dx%d@%d, offset=%x, pitch %d, size 0x%x\n", + crtc->base.name, plane->base.name, fb->width, fb->height, + fb->format->cpp[0] * 8, base, fb->pitches[0], + plane_config->size); + + plane_config->fb = intel_fb; +} diff --git a/drivers/gpu/drm/i915/display/i9xx_plane.h b/drivers/gpu/drm/i915/display/i9xx_plane.h new file mode 100644 index 000000000..027b66053 --- /dev/null +++ b/drivers/gpu/drm/i915/display/i9xx_plane.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2020 Intel Corporation + */ + +#ifndef _I9XX_PLANE_H_ +#define _I9XX_PLANE_H_ + +#include + +enum pipe; +struct drm_i915_private; +struct intel_crtc; +struct intel_initial_plane_config; +struct intel_plane; +struct intel_plane_state; + +unsigned int i965_plane_max_stride(struct intel_plane *plane, + u32 pixel_format, u64 modifier, + unsigned int rotation); +int i9xx_check_plane_surface(struct intel_plane_state *plane_state); + +struct intel_plane * +intel_primary_plane_create(struct drm_i915_private *dev_priv, enum pipe pipe); + +void i9xx_get_initial_plane_config(struct intel_crtc *crtc, + struct intel_initial_plane_config *plane_config); +#endif diff --git a/drivers/gpu/drm/i915/display/icl_dsi.c b/drivers/gpu/drm/i915/display/icl_dsi.c new file mode 100644 index 000000000..f7422f0cf --- /dev/null +++ b/drivers/gpu/drm/i915/display/icl_dsi.c @@ -0,0 +1,2127 @@ +/* + * Copyright © 2018 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Madhav Chauhan + * Jani Nikula + */ + +#include +#include +#include + +#include "icl_dsi.h" +#include "icl_dsi_regs.h" +#include "intel_atomic.h" +#include "intel_backlight.h" +#include "intel_backlight_regs.h" +#include "intel_combo_phy.h" +#include "intel_combo_phy_regs.h" +#include "intel_connector.h" +#include "intel_crtc.h" +#include "intel_ddi.h" +#include "intel_de.h" +#include "intel_dsi.h" +#include "intel_dsi_vbt.h" +#include "intel_panel.h" +#include "intel_vdsc.h" +#include "skl_scaler.h" +#include "skl_universal_plane.h" + +static int header_credits_available(struct drm_i915_private *dev_priv, + enum transcoder dsi_trans) +{ + return (intel_de_read(dev_priv, DSI_CMD_TXCTL(dsi_trans)) & FREE_HEADER_CREDIT_MASK) + >> FREE_HEADER_CREDIT_SHIFT; +} + +static int payload_credits_available(struct drm_i915_private *dev_priv, + enum transcoder dsi_trans) +{ + return (intel_de_read(dev_priv, DSI_CMD_TXCTL(dsi_trans)) & FREE_PLOAD_CREDIT_MASK) + >> FREE_PLOAD_CREDIT_SHIFT; +} + +static bool wait_for_header_credits(struct drm_i915_private *dev_priv, + enum transcoder dsi_trans, int hdr_credit) +{ + if (wait_for_us(header_credits_available(dev_priv, dsi_trans) >= + hdr_credit, 100)) { + drm_err(&dev_priv->drm, "DSI header credits not released\n"); + return false; + } + + return true; +} + +static bool wait_for_payload_credits(struct drm_i915_private *dev_priv, + enum transcoder dsi_trans, int payld_credit) +{ + if (wait_for_us(payload_credits_available(dev_priv, dsi_trans) >= + payld_credit, 100)) { + drm_err(&dev_priv->drm, "DSI payload credits not released\n"); + return false; + } + + return true; +} + +static enum transcoder dsi_port_to_transcoder(enum port port) +{ + if (port == PORT_A) + return TRANSCODER_DSI_0; + else + return TRANSCODER_DSI_1; +} + +static void wait_for_cmds_dispatched_to_panel(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + struct mipi_dsi_device *dsi; + enum port port; + enum transcoder dsi_trans; + int ret; + + /* wait for header/payload credits to be released */ + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + wait_for_header_credits(dev_priv, dsi_trans, MAX_HEADER_CREDIT); + wait_for_payload_credits(dev_priv, dsi_trans, MAX_PLOAD_CREDIT); + } + + /* send nop DCS command */ + for_each_dsi_port(port, intel_dsi->ports) { + dsi = intel_dsi->dsi_hosts[port]->device; + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + dsi->channel = 0; + ret = mipi_dsi_dcs_nop(dsi); + if (ret < 0) + drm_err(&dev_priv->drm, + "error sending DCS NOP command\n"); + } + + /* wait for header credits to be released */ + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + wait_for_header_credits(dev_priv, dsi_trans, MAX_HEADER_CREDIT); + } + + /* wait for LP TX in progress bit to be cleared */ + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + if (wait_for_us(!(intel_de_read(dev_priv, DSI_LP_MSG(dsi_trans)) & + LPTX_IN_PROGRESS), 20)) + drm_err(&dev_priv->drm, "LPTX bit not cleared\n"); + } +} + +static int dsi_send_pkt_payld(struct intel_dsi_host *host, + const struct mipi_dsi_packet *packet) +{ + struct intel_dsi *intel_dsi = host->intel_dsi; + struct drm_i915_private *i915 = to_i915(intel_dsi->base.base.dev); + enum transcoder dsi_trans = dsi_port_to_transcoder(host->port); + const u8 *data = packet->payload; + u32 len = packet->payload_length; + int i, j; + + /* payload queue can accept *256 bytes*, check limit */ + if (len > MAX_PLOAD_CREDIT * 4) { + drm_err(&i915->drm, "payload size exceeds max queue limit\n"); + return -EINVAL; + } + + for (i = 0; i < len; i += 4) { + u32 tmp = 0; + + if (!wait_for_payload_credits(i915, dsi_trans, 1)) + return -EBUSY; + + for (j = 0; j < min_t(u32, len - i, 4); j++) + tmp |= *data++ << 8 * j; + + intel_de_write(i915, DSI_CMD_TXPYLD(dsi_trans), tmp); + } + + return 0; +} + +static int dsi_send_pkt_hdr(struct intel_dsi_host *host, + const struct mipi_dsi_packet *packet, + bool enable_lpdt) +{ + struct intel_dsi *intel_dsi = host->intel_dsi; + struct drm_i915_private *dev_priv = to_i915(intel_dsi->base.base.dev); + enum transcoder dsi_trans = dsi_port_to_transcoder(host->port); + u32 tmp; + + if (!wait_for_header_credits(dev_priv, dsi_trans, 1)) + return -EBUSY; + + tmp = intel_de_read(dev_priv, DSI_CMD_TXHDR(dsi_trans)); + + if (packet->payload) + tmp |= PAYLOAD_PRESENT; + else + tmp &= ~PAYLOAD_PRESENT; + + tmp &= ~VBLANK_FENCE; + + if (enable_lpdt) + tmp |= LP_DATA_TRANSFER; + else + tmp &= ~LP_DATA_TRANSFER; + + tmp &= ~(PARAM_WC_MASK | VC_MASK | DT_MASK); + tmp |= ((packet->header[0] & VC_MASK) << VC_SHIFT); + tmp |= ((packet->header[0] & DT_MASK) << DT_SHIFT); + tmp |= (packet->header[1] << PARAM_WC_LOWER_SHIFT); + tmp |= (packet->header[2] << PARAM_WC_UPPER_SHIFT); + intel_de_write(dev_priv, DSI_CMD_TXHDR(dsi_trans), tmp); + + return 0; +} + +void icl_dsi_frame_update(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + u32 tmp, mode_flags; + enum port port; + + mode_flags = crtc_state->mode_flags; + + /* + * case 1 also covers dual link + * In case of dual link, frame update should be set on + * DSI_0 + */ + if (mode_flags & I915_MODE_FLAG_DSI_USE_TE0) + port = PORT_A; + else if (mode_flags & I915_MODE_FLAG_DSI_USE_TE1) + port = PORT_B; + else + return; + + tmp = intel_de_read(dev_priv, DSI_CMD_FRMCTL(port)); + tmp |= DSI_FRAME_UPDATE_REQUEST; + intel_de_write(dev_priv, DSI_CMD_FRMCTL(port), tmp); +} + +static void dsi_program_swing_and_deemphasis(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum phy phy; + u32 tmp; + int lane; + + for_each_dsi_phy(phy, intel_dsi->phys) { + /* + * Program voltage swing and pre-emphasis level values as per + * table in BSPEC under DDI buffer programing + */ + tmp = intel_de_read(dev_priv, ICL_PORT_TX_DW5_LN(0, phy)); + tmp &= ~(SCALING_MODE_SEL_MASK | RTERM_SELECT_MASK); + tmp |= SCALING_MODE_SEL(0x2); + tmp |= TAP2_DISABLE | TAP3_DISABLE; + tmp |= RTERM_SELECT(0x6); + intel_de_write(dev_priv, ICL_PORT_TX_DW5_GRP(phy), tmp); + + tmp = intel_de_read(dev_priv, ICL_PORT_TX_DW5_AUX(phy)); + tmp &= ~(SCALING_MODE_SEL_MASK | RTERM_SELECT_MASK); + tmp |= SCALING_MODE_SEL(0x2); + tmp |= TAP2_DISABLE | TAP3_DISABLE; + tmp |= RTERM_SELECT(0x6); + intel_de_write(dev_priv, ICL_PORT_TX_DW5_AUX(phy), tmp); + + tmp = intel_de_read(dev_priv, ICL_PORT_TX_DW2_LN(0, phy)); + tmp &= ~(SWING_SEL_LOWER_MASK | SWING_SEL_UPPER_MASK | + RCOMP_SCALAR_MASK); + tmp |= SWING_SEL_UPPER(0x2); + tmp |= SWING_SEL_LOWER(0x2); + tmp |= RCOMP_SCALAR(0x98); + intel_de_write(dev_priv, ICL_PORT_TX_DW2_GRP(phy), tmp); + + tmp = intel_de_read(dev_priv, ICL_PORT_TX_DW2_AUX(phy)); + tmp &= ~(SWING_SEL_LOWER_MASK | SWING_SEL_UPPER_MASK | + RCOMP_SCALAR_MASK); + tmp |= SWING_SEL_UPPER(0x2); + tmp |= SWING_SEL_LOWER(0x2); + tmp |= RCOMP_SCALAR(0x98); + intel_de_write(dev_priv, ICL_PORT_TX_DW2_AUX(phy), tmp); + + tmp = intel_de_read(dev_priv, ICL_PORT_TX_DW4_AUX(phy)); + tmp &= ~(POST_CURSOR_1_MASK | POST_CURSOR_2_MASK | + CURSOR_COEFF_MASK); + tmp |= POST_CURSOR_1(0x0); + tmp |= POST_CURSOR_2(0x0); + tmp |= CURSOR_COEFF(0x3f); + intel_de_write(dev_priv, ICL_PORT_TX_DW4_AUX(phy), tmp); + + for (lane = 0; lane <= 3; lane++) { + /* Bspec: must not use GRP register for write */ + tmp = intel_de_read(dev_priv, + ICL_PORT_TX_DW4_LN(lane, phy)); + tmp &= ~(POST_CURSOR_1_MASK | POST_CURSOR_2_MASK | + CURSOR_COEFF_MASK); + tmp |= POST_CURSOR_1(0x0); + tmp |= POST_CURSOR_2(0x0); + tmp |= CURSOR_COEFF(0x3f); + intel_de_write(dev_priv, + ICL_PORT_TX_DW4_LN(lane, phy), tmp); + } + } +} + +static void configure_dual_link_mode(struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + i915_reg_t dss_ctl1_reg, dss_ctl2_reg; + u32 dss_ctl1; + + /* FIXME: Move all DSS handling to intel_vdsc.c */ + if (DISPLAY_VER(dev_priv) >= 12) { + struct intel_crtc *crtc = to_intel_crtc(pipe_config->uapi.crtc); + + dss_ctl1_reg = ICL_PIPE_DSS_CTL1(crtc->pipe); + dss_ctl2_reg = ICL_PIPE_DSS_CTL2(crtc->pipe); + } else { + dss_ctl1_reg = DSS_CTL1; + dss_ctl2_reg = DSS_CTL2; + } + + dss_ctl1 = intel_de_read(dev_priv, dss_ctl1_reg); + dss_ctl1 |= SPLITTER_ENABLE; + dss_ctl1 &= ~OVERLAP_PIXELS_MASK; + dss_ctl1 |= OVERLAP_PIXELS(intel_dsi->pixel_overlap); + + if (intel_dsi->dual_link == DSI_DUAL_LINK_FRONT_BACK) { + const struct drm_display_mode *adjusted_mode = + &pipe_config->hw.adjusted_mode; + u32 dss_ctl2; + u16 hactive = adjusted_mode->crtc_hdisplay; + u16 dl_buffer_depth; + + dss_ctl1 &= ~DUAL_LINK_MODE_INTERLEAVE; + dl_buffer_depth = hactive / 2 + intel_dsi->pixel_overlap; + + if (dl_buffer_depth > MAX_DL_BUFFER_TARGET_DEPTH) + drm_err(&dev_priv->drm, + "DL buffer depth exceed max value\n"); + + dss_ctl1 &= ~LEFT_DL_BUF_TARGET_DEPTH_MASK; + dss_ctl1 |= LEFT_DL_BUF_TARGET_DEPTH(dl_buffer_depth); + dss_ctl2 = intel_de_read(dev_priv, dss_ctl2_reg); + dss_ctl2 &= ~RIGHT_DL_BUF_TARGET_DEPTH_MASK; + dss_ctl2 |= RIGHT_DL_BUF_TARGET_DEPTH(dl_buffer_depth); + intel_de_write(dev_priv, dss_ctl2_reg, dss_ctl2); + } else { + /* Interleave */ + dss_ctl1 |= DUAL_LINK_MODE_INTERLEAVE; + } + + intel_de_write(dev_priv, dss_ctl1_reg, dss_ctl1); +} + +/* aka DSI 8X clock */ +static int afe_clk(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + int bpp; + + if (crtc_state->dsc.compression_enable) + bpp = crtc_state->dsc.compressed_bpp; + else + bpp = mipi_dsi_pixel_format_to_bpp(intel_dsi->pixel_format); + + return DIV_ROUND_CLOSEST(intel_dsi->pclk * bpp, intel_dsi->lane_count); +} + +static void gen11_dsi_program_esc_clk_div(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum port port; + int afe_clk_khz; + int theo_word_clk, act_word_clk; + u32 esc_clk_div_m, esc_clk_div_m_phy; + + afe_clk_khz = afe_clk(encoder, crtc_state); + + if (IS_ALDERLAKE_S(dev_priv) || IS_ALDERLAKE_P(dev_priv)) { + theo_word_clk = DIV_ROUND_UP(afe_clk_khz, 8 * DSI_MAX_ESC_CLK); + act_word_clk = max(3, theo_word_clk + (theo_word_clk + 1) % 2); + esc_clk_div_m = act_word_clk * 8; + esc_clk_div_m_phy = (act_word_clk - 1) / 2; + } else { + esc_clk_div_m = DIV_ROUND_UP(afe_clk_khz, DSI_MAX_ESC_CLK); + } + + for_each_dsi_port(port, intel_dsi->ports) { + intel_de_write(dev_priv, ICL_DSI_ESC_CLK_DIV(port), + esc_clk_div_m & ICL_ESC_CLK_DIV_MASK); + intel_de_posting_read(dev_priv, ICL_DSI_ESC_CLK_DIV(port)); + } + + for_each_dsi_port(port, intel_dsi->ports) { + intel_de_write(dev_priv, ICL_DPHY_ESC_CLK_DIV(port), + esc_clk_div_m & ICL_ESC_CLK_DIV_MASK); + intel_de_posting_read(dev_priv, ICL_DPHY_ESC_CLK_DIV(port)); + } + + if (IS_ALDERLAKE_S(dev_priv) || IS_ALDERLAKE_P(dev_priv)) { + for_each_dsi_port(port, intel_dsi->ports) { + intel_de_write(dev_priv, ADL_MIPIO_DW(port, 8), + esc_clk_div_m_phy & TX_ESC_CLK_DIV_PHY); + intel_de_posting_read(dev_priv, ADL_MIPIO_DW(port, 8)); + } + } +} + +static void get_dsi_io_power_domains(struct drm_i915_private *dev_priv, + struct intel_dsi *intel_dsi) +{ + enum port port; + + for_each_dsi_port(port, intel_dsi->ports) { + drm_WARN_ON(&dev_priv->drm, intel_dsi->io_wakeref[port]); + intel_dsi->io_wakeref[port] = + intel_display_power_get(dev_priv, + port == PORT_A ? + POWER_DOMAIN_PORT_DDI_IO_A : + POWER_DOMAIN_PORT_DDI_IO_B); + } +} + +static void gen11_dsi_enable_io_power(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum port port; + u32 tmp; + + for_each_dsi_port(port, intel_dsi->ports) { + tmp = intel_de_read(dev_priv, ICL_DSI_IO_MODECTL(port)); + tmp |= COMBO_PHY_MODE_DSI; + intel_de_write(dev_priv, ICL_DSI_IO_MODECTL(port), tmp); + } + + get_dsi_io_power_domains(dev_priv, intel_dsi); +} + +static void gen11_dsi_power_up_lanes(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum phy phy; + + for_each_dsi_phy(phy, intel_dsi->phys) + intel_combo_phy_power_up_lanes(dev_priv, phy, true, + intel_dsi->lane_count, false); +} + +static void gen11_dsi_config_phy_lanes_sequence(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum phy phy; + u32 tmp; + int lane; + + /* Step 4b(i) set loadgen select for transmit and aux lanes */ + for_each_dsi_phy(phy, intel_dsi->phys) { + tmp = intel_de_read(dev_priv, ICL_PORT_TX_DW4_AUX(phy)); + tmp &= ~LOADGEN_SELECT; + intel_de_write(dev_priv, ICL_PORT_TX_DW4_AUX(phy), tmp); + for (lane = 0; lane <= 3; lane++) { + tmp = intel_de_read(dev_priv, + ICL_PORT_TX_DW4_LN(lane, phy)); + tmp &= ~LOADGEN_SELECT; + if (lane != 2) + tmp |= LOADGEN_SELECT; + intel_de_write(dev_priv, + ICL_PORT_TX_DW4_LN(lane, phy), tmp); + } + } + + /* Step 4b(ii) set latency optimization for transmit and aux lanes */ + for_each_dsi_phy(phy, intel_dsi->phys) { + tmp = intel_de_read(dev_priv, ICL_PORT_TX_DW2_AUX(phy)); + tmp &= ~FRC_LATENCY_OPTIM_MASK; + tmp |= FRC_LATENCY_OPTIM_VAL(0x5); + intel_de_write(dev_priv, ICL_PORT_TX_DW2_AUX(phy), tmp); + tmp = intel_de_read(dev_priv, ICL_PORT_TX_DW2_LN(0, phy)); + tmp &= ~FRC_LATENCY_OPTIM_MASK; + tmp |= FRC_LATENCY_OPTIM_VAL(0x5); + intel_de_write(dev_priv, ICL_PORT_TX_DW2_GRP(phy), tmp); + + /* For EHL, TGL, set latency optimization for PCS_DW1 lanes */ + if (IS_JSL_EHL(dev_priv) || (DISPLAY_VER(dev_priv) >= 12)) { + tmp = intel_de_read(dev_priv, + ICL_PORT_PCS_DW1_AUX(phy)); + tmp &= ~LATENCY_OPTIM_MASK; + tmp |= LATENCY_OPTIM_VAL(0); + intel_de_write(dev_priv, ICL_PORT_PCS_DW1_AUX(phy), + tmp); + + tmp = intel_de_read(dev_priv, + ICL_PORT_PCS_DW1_LN(0, phy)); + tmp &= ~LATENCY_OPTIM_MASK; + tmp |= LATENCY_OPTIM_VAL(0x1); + intel_de_write(dev_priv, ICL_PORT_PCS_DW1_GRP(phy), + tmp); + } + } + +} + +static void gen11_dsi_voltage_swing_program_seq(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + u32 tmp; + enum phy phy; + + /* clear common keeper enable bit */ + for_each_dsi_phy(phy, intel_dsi->phys) { + tmp = intel_de_read(dev_priv, ICL_PORT_PCS_DW1_LN(0, phy)); + tmp &= ~COMMON_KEEPER_EN; + intel_de_write(dev_priv, ICL_PORT_PCS_DW1_GRP(phy), tmp); + tmp = intel_de_read(dev_priv, ICL_PORT_PCS_DW1_AUX(phy)); + tmp &= ~COMMON_KEEPER_EN; + intel_de_write(dev_priv, ICL_PORT_PCS_DW1_AUX(phy), tmp); + } + + /* + * Set SUS Clock Config bitfield to 11b + * Note: loadgen select program is done + * as part of lane phy sequence configuration + */ + for_each_dsi_phy(phy, intel_dsi->phys) { + tmp = intel_de_read(dev_priv, ICL_PORT_CL_DW5(phy)); + tmp |= SUS_CLOCK_CONFIG; + intel_de_write(dev_priv, ICL_PORT_CL_DW5(phy), tmp); + } + + /* Clear training enable to change swing values */ + for_each_dsi_phy(phy, intel_dsi->phys) { + tmp = intel_de_read(dev_priv, ICL_PORT_TX_DW5_LN(0, phy)); + tmp &= ~TX_TRAINING_EN; + intel_de_write(dev_priv, ICL_PORT_TX_DW5_GRP(phy), tmp); + tmp = intel_de_read(dev_priv, ICL_PORT_TX_DW5_AUX(phy)); + tmp &= ~TX_TRAINING_EN; + intel_de_write(dev_priv, ICL_PORT_TX_DW5_AUX(phy), tmp); + } + + /* Program swing and de-emphasis */ + dsi_program_swing_and_deemphasis(encoder); + + /* Set training enable to trigger update */ + for_each_dsi_phy(phy, intel_dsi->phys) { + tmp = intel_de_read(dev_priv, ICL_PORT_TX_DW5_LN(0, phy)); + tmp |= TX_TRAINING_EN; + intel_de_write(dev_priv, ICL_PORT_TX_DW5_GRP(phy), tmp); + tmp = intel_de_read(dev_priv, ICL_PORT_TX_DW5_AUX(phy)); + tmp |= TX_TRAINING_EN; + intel_de_write(dev_priv, ICL_PORT_TX_DW5_AUX(phy), tmp); + } +} + +static void gen11_dsi_enable_ddi_buffer(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + u32 tmp; + enum port port; + + for_each_dsi_port(port, intel_dsi->ports) { + tmp = intel_de_read(dev_priv, DDI_BUF_CTL(port)); + tmp |= DDI_BUF_CTL_ENABLE; + intel_de_write(dev_priv, DDI_BUF_CTL(port), tmp); + + if (wait_for_us(!(intel_de_read(dev_priv, DDI_BUF_CTL(port)) & + DDI_BUF_IS_IDLE), + 500)) + drm_err(&dev_priv->drm, "DDI port:%c buffer idle\n", + port_name(port)); + } +} + +static void +gen11_dsi_setup_dphy_timings(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + u32 tmp; + enum port port; + enum phy phy; + + /* Program T-INIT master registers */ + for_each_dsi_port(port, intel_dsi->ports) { + tmp = intel_de_read(dev_priv, ICL_DSI_T_INIT_MASTER(port)); + tmp &= ~DSI_T_INIT_MASTER_MASK; + tmp |= intel_dsi->init_count; + intel_de_write(dev_priv, ICL_DSI_T_INIT_MASTER(port), tmp); + } + + /* Program DPHY clock lanes timings */ + for_each_dsi_port(port, intel_dsi->ports) { + intel_de_write(dev_priv, DPHY_CLK_TIMING_PARAM(port), + intel_dsi->dphy_reg); + + /* shadow register inside display core */ + intel_de_write(dev_priv, DSI_CLK_TIMING_PARAM(port), + intel_dsi->dphy_reg); + } + + /* Program DPHY data lanes timings */ + for_each_dsi_port(port, intel_dsi->ports) { + intel_de_write(dev_priv, DPHY_DATA_TIMING_PARAM(port), + intel_dsi->dphy_data_lane_reg); + + /* shadow register inside display core */ + intel_de_write(dev_priv, DSI_DATA_TIMING_PARAM(port), + intel_dsi->dphy_data_lane_reg); + } + + /* + * If DSI link operating at or below an 800 MHz, + * TA_SURE should be override and programmed to + * a value '0' inside TA_PARAM_REGISTERS otherwise + * leave all fields at HW default values. + */ + if (DISPLAY_VER(dev_priv) == 11) { + if (afe_clk(encoder, crtc_state) <= 800000) { + for_each_dsi_port(port, intel_dsi->ports) { + tmp = intel_de_read(dev_priv, + DPHY_TA_TIMING_PARAM(port)); + tmp &= ~TA_SURE_MASK; + tmp |= TA_SURE_OVERRIDE | TA_SURE(0); + intel_de_write(dev_priv, + DPHY_TA_TIMING_PARAM(port), + tmp); + + /* shadow register inside display core */ + tmp = intel_de_read(dev_priv, + DSI_TA_TIMING_PARAM(port)); + tmp &= ~TA_SURE_MASK; + tmp |= TA_SURE_OVERRIDE | TA_SURE(0); + intel_de_write(dev_priv, + DSI_TA_TIMING_PARAM(port), tmp); + } + } + } + + if (IS_JSL_EHL(dev_priv)) { + for_each_dsi_phy(phy, intel_dsi->phys) { + tmp = intel_de_read(dev_priv, ICL_DPHY_CHKN(phy)); + tmp |= ICL_DPHY_CHKN_AFE_OVER_PPI_STRAP; + intel_de_write(dev_priv, ICL_DPHY_CHKN(phy), tmp); + } + } +} + +static void gen11_dsi_gate_clocks(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + u32 tmp; + enum phy phy; + + mutex_lock(&dev_priv->display.dpll.lock); + tmp = intel_de_read(dev_priv, ICL_DPCLKA_CFGCR0); + for_each_dsi_phy(phy, intel_dsi->phys) + tmp |= ICL_DPCLKA_CFGCR0_DDI_CLK_OFF(phy); + + intel_de_write(dev_priv, ICL_DPCLKA_CFGCR0, tmp); + mutex_unlock(&dev_priv->display.dpll.lock); +} + +static void gen11_dsi_ungate_clocks(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + u32 tmp; + enum phy phy; + + mutex_lock(&dev_priv->display.dpll.lock); + tmp = intel_de_read(dev_priv, ICL_DPCLKA_CFGCR0); + for_each_dsi_phy(phy, intel_dsi->phys) + tmp &= ~ICL_DPCLKA_CFGCR0_DDI_CLK_OFF(phy); + + intel_de_write(dev_priv, ICL_DPCLKA_CFGCR0, tmp); + mutex_unlock(&dev_priv->display.dpll.lock); +} + +static bool gen11_dsi_is_clock_enabled(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + bool clock_enabled = false; + enum phy phy; + u32 tmp; + + tmp = intel_de_read(dev_priv, ICL_DPCLKA_CFGCR0); + + for_each_dsi_phy(phy, intel_dsi->phys) { + if (!(tmp & ICL_DPCLKA_CFGCR0_DDI_CLK_OFF(phy))) + clock_enabled = true; + } + + return clock_enabled; +} + +static void gen11_dsi_map_pll(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + struct intel_shared_dpll *pll = crtc_state->shared_dpll; + enum phy phy; + u32 val; + + mutex_lock(&dev_priv->display.dpll.lock); + + val = intel_de_read(dev_priv, ICL_DPCLKA_CFGCR0); + for_each_dsi_phy(phy, intel_dsi->phys) { + val &= ~ICL_DPCLKA_CFGCR0_DDI_CLK_SEL_MASK(phy); + val |= ICL_DPCLKA_CFGCR0_DDI_CLK_SEL(pll->info->id, phy); + } + intel_de_write(dev_priv, ICL_DPCLKA_CFGCR0, val); + + for_each_dsi_phy(phy, intel_dsi->phys) { + val &= ~ICL_DPCLKA_CFGCR0_DDI_CLK_OFF(phy); + } + intel_de_write(dev_priv, ICL_DPCLKA_CFGCR0, val); + + intel_de_posting_read(dev_priv, ICL_DPCLKA_CFGCR0); + + mutex_unlock(&dev_priv->display.dpll.lock); +} + +static void +gen11_dsi_configure_transcoder(struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + struct intel_crtc *crtc = to_intel_crtc(pipe_config->uapi.crtc); + enum pipe pipe = crtc->pipe; + u32 tmp; + enum port port; + enum transcoder dsi_trans; + + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + tmp = intel_de_read(dev_priv, DSI_TRANS_FUNC_CONF(dsi_trans)); + + if (intel_dsi->eotp_pkt) + tmp &= ~EOTP_DISABLED; + else + tmp |= EOTP_DISABLED; + + /* enable link calibration if freq > 1.5Gbps */ + if (afe_clk(encoder, pipe_config) >= 1500 * 1000) { + tmp &= ~LINK_CALIBRATION_MASK; + tmp |= CALIBRATION_ENABLED_INITIAL_ONLY; + } + + /* configure continuous clock */ + tmp &= ~CONTINUOUS_CLK_MASK; + if (intel_dsi->clock_stop) + tmp |= CLK_ENTER_LP_AFTER_DATA; + else + tmp |= CLK_HS_CONTINUOUS; + + /* configure buffer threshold limit to minimum */ + tmp &= ~PIX_BUF_THRESHOLD_MASK; + tmp |= PIX_BUF_THRESHOLD_1_4; + + /* set virtual channel to '0' */ + tmp &= ~PIX_VIRT_CHAN_MASK; + tmp |= PIX_VIRT_CHAN(0); + + /* program BGR transmission */ + if (intel_dsi->bgr_enabled) + tmp |= BGR_TRANSMISSION; + + /* select pixel format */ + tmp &= ~PIX_FMT_MASK; + if (pipe_config->dsc.compression_enable) { + tmp |= PIX_FMT_COMPRESSED; + } else { + switch (intel_dsi->pixel_format) { + default: + MISSING_CASE(intel_dsi->pixel_format); + fallthrough; + case MIPI_DSI_FMT_RGB565: + tmp |= PIX_FMT_RGB565; + break; + case MIPI_DSI_FMT_RGB666_PACKED: + tmp |= PIX_FMT_RGB666_PACKED; + break; + case MIPI_DSI_FMT_RGB666: + tmp |= PIX_FMT_RGB666_LOOSE; + break; + case MIPI_DSI_FMT_RGB888: + tmp |= PIX_FMT_RGB888; + break; + } + } + + if (DISPLAY_VER(dev_priv) >= 12) { + if (is_vid_mode(intel_dsi)) + tmp |= BLANKING_PACKET_ENABLE; + } + + /* program DSI operation mode */ + if (is_vid_mode(intel_dsi)) { + tmp &= ~OP_MODE_MASK; + switch (intel_dsi->video_mode) { + default: + MISSING_CASE(intel_dsi->video_mode); + fallthrough; + case NON_BURST_SYNC_EVENTS: + tmp |= VIDEO_MODE_SYNC_EVENT; + break; + case NON_BURST_SYNC_PULSE: + tmp |= VIDEO_MODE_SYNC_PULSE; + break; + } + } else { + /* + * FIXME: Retrieve this info from VBT. + * As per the spec when dsi transcoder is operating + * in TE GATE mode, TE comes from GPIO + * which is UTIL PIN for DSI 0. + * Also this GPIO would not be used for other + * purposes is an assumption. + */ + tmp &= ~OP_MODE_MASK; + tmp |= CMD_MODE_TE_GATE; + tmp |= TE_SOURCE_GPIO; + } + + intel_de_write(dev_priv, DSI_TRANS_FUNC_CONF(dsi_trans), tmp); + } + + /* enable port sync mode if dual link */ + if (intel_dsi->dual_link) { + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + tmp = intel_de_read(dev_priv, + TRANS_DDI_FUNC_CTL2(dsi_trans)); + tmp |= PORT_SYNC_MODE_ENABLE; + intel_de_write(dev_priv, + TRANS_DDI_FUNC_CTL2(dsi_trans), tmp); + } + + /* configure stream splitting */ + configure_dual_link_mode(encoder, pipe_config); + } + + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + + /* select data lane width */ + tmp = intel_de_read(dev_priv, TRANS_DDI_FUNC_CTL(dsi_trans)); + tmp &= ~DDI_PORT_WIDTH_MASK; + tmp |= DDI_PORT_WIDTH(intel_dsi->lane_count); + + /* select input pipe */ + tmp &= ~TRANS_DDI_EDP_INPUT_MASK; + switch (pipe) { + default: + MISSING_CASE(pipe); + fallthrough; + case PIPE_A: + tmp |= TRANS_DDI_EDP_INPUT_A_ON; + break; + case PIPE_B: + tmp |= TRANS_DDI_EDP_INPUT_B_ONOFF; + break; + case PIPE_C: + tmp |= TRANS_DDI_EDP_INPUT_C_ONOFF; + break; + case PIPE_D: + tmp |= TRANS_DDI_EDP_INPUT_D_ONOFF; + break; + } + + /* enable DDI buffer */ + tmp |= TRANS_DDI_FUNC_ENABLE; + intel_de_write(dev_priv, TRANS_DDI_FUNC_CTL(dsi_trans), tmp); + } + + /* wait for link ready */ + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + if (wait_for_us((intel_de_read(dev_priv, DSI_TRANS_FUNC_CONF(dsi_trans)) & + LINK_READY), 2500)) + drm_err(&dev_priv->drm, "DSI link not ready\n"); + } +} + +static void +gen11_dsi_set_transcoder_timings(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + const struct drm_display_mode *adjusted_mode = + &crtc_state->hw.adjusted_mode; + enum port port; + enum transcoder dsi_trans; + /* horizontal timings */ + u16 htotal, hactive, hsync_start, hsync_end, hsync_size; + u16 hback_porch; + /* vertical timings */ + u16 vtotal, vactive, vsync_start, vsync_end, vsync_shift; + int mul = 1, div = 1; + + /* + * Adjust horizontal timings (htotal, hsync_start, hsync_end) to account + * for slower link speed if DSC is enabled. + * + * The compression frequency ratio is the ratio between compressed and + * non-compressed link speeds, and simplifies down to the ratio between + * compressed and non-compressed bpp. + */ + if (crtc_state->dsc.compression_enable) { + mul = crtc_state->dsc.compressed_bpp; + div = mipi_dsi_pixel_format_to_bpp(intel_dsi->pixel_format); + } + + hactive = adjusted_mode->crtc_hdisplay; + + if (is_vid_mode(intel_dsi)) + htotal = DIV_ROUND_UP(adjusted_mode->crtc_htotal * mul, div); + else + htotal = DIV_ROUND_UP((hactive + 160) * mul, div); + + hsync_start = DIV_ROUND_UP(adjusted_mode->crtc_hsync_start * mul, div); + hsync_end = DIV_ROUND_UP(adjusted_mode->crtc_hsync_end * mul, div); + hsync_size = hsync_end - hsync_start; + hback_porch = (adjusted_mode->crtc_htotal - + adjusted_mode->crtc_hsync_end); + vactive = adjusted_mode->crtc_vdisplay; + + if (is_vid_mode(intel_dsi)) { + vtotal = adjusted_mode->crtc_vtotal; + } else { + int bpp, line_time_us, byte_clk_period_ns; + + if (crtc_state->dsc.compression_enable) + bpp = crtc_state->dsc.compressed_bpp; + else + bpp = mipi_dsi_pixel_format_to_bpp(intel_dsi->pixel_format); + + byte_clk_period_ns = 1000000 / afe_clk(encoder, crtc_state); + line_time_us = (htotal * (bpp / 8) * byte_clk_period_ns) / (1000 * intel_dsi->lane_count); + vtotal = vactive + DIV_ROUND_UP(400, line_time_us); + } + vsync_start = adjusted_mode->crtc_vsync_start; + vsync_end = adjusted_mode->crtc_vsync_end; + vsync_shift = hsync_start - htotal / 2; + + if (intel_dsi->dual_link) { + hactive /= 2; + if (intel_dsi->dual_link == DSI_DUAL_LINK_FRONT_BACK) + hactive += intel_dsi->pixel_overlap; + htotal /= 2; + } + + /* minimum hactive as per bspec: 256 pixels */ + if (adjusted_mode->crtc_hdisplay < 256) + drm_err(&dev_priv->drm, "hactive is less then 256 pixels\n"); + + /* if RGB666 format, then hactive must be multiple of 4 pixels */ + if (intel_dsi->pixel_format == MIPI_DSI_FMT_RGB666 && hactive % 4 != 0) + drm_err(&dev_priv->drm, + "hactive pixels are not multiple of 4\n"); + + /* program TRANS_HTOTAL register */ + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + intel_de_write(dev_priv, HTOTAL(dsi_trans), + (hactive - 1) | ((htotal - 1) << 16)); + } + + /* TRANS_HSYNC register to be programmed only for video mode */ + if (is_vid_mode(intel_dsi)) { + if (intel_dsi->video_mode == NON_BURST_SYNC_PULSE) { + /* BSPEC: hsync size should be atleast 16 pixels */ + if (hsync_size < 16) + drm_err(&dev_priv->drm, + "hsync size < 16 pixels\n"); + } + + if (hback_porch < 16) + drm_err(&dev_priv->drm, "hback porch < 16 pixels\n"); + + if (intel_dsi->dual_link) { + hsync_start /= 2; + hsync_end /= 2; + } + + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + intel_de_write(dev_priv, HSYNC(dsi_trans), + (hsync_start - 1) | ((hsync_end - 1) << 16)); + } + } + + /* program TRANS_VTOTAL register */ + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + /* + * FIXME: Programing this by assuming progressive mode, since + * non-interlaced info from VBT is not saved inside + * struct drm_display_mode. + * For interlace mode: program required pixel minus 2 + */ + intel_de_write(dev_priv, VTOTAL(dsi_trans), + (vactive - 1) | ((vtotal - 1) << 16)); + } + + if (vsync_end < vsync_start || vsync_end > vtotal) + drm_err(&dev_priv->drm, "Invalid vsync_end value\n"); + + if (vsync_start < vactive) + drm_err(&dev_priv->drm, "vsync_start less than vactive\n"); + + /* program TRANS_VSYNC register for video mode only */ + if (is_vid_mode(intel_dsi)) { + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + intel_de_write(dev_priv, VSYNC(dsi_trans), + (vsync_start - 1) | ((vsync_end - 1) << 16)); + } + } + + /* + * FIXME: It has to be programmed only for video modes and interlaced + * modes. Put the check condition here once interlaced + * info available as described above. + * program TRANS_VSYNCSHIFT register + */ + if (is_vid_mode(intel_dsi)) { + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + intel_de_write(dev_priv, VSYNCSHIFT(dsi_trans), + vsync_shift); + } + } + + /* program TRANS_VBLANK register, should be same as vtotal programmed */ + if (DISPLAY_VER(dev_priv) >= 12) { + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + intel_de_write(dev_priv, VBLANK(dsi_trans), + (vactive - 1) | ((vtotal - 1) << 16)); + } + } +} + +static void gen11_dsi_enable_transcoder(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum port port; + enum transcoder dsi_trans; + u32 tmp; + + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + tmp = intel_de_read(dev_priv, PIPECONF(dsi_trans)); + tmp |= PIPECONF_ENABLE; + intel_de_write(dev_priv, PIPECONF(dsi_trans), tmp); + + /* wait for transcoder to be enabled */ + if (intel_de_wait_for_set(dev_priv, PIPECONF(dsi_trans), + PIPECONF_STATE_ENABLE, 10)) + drm_err(&dev_priv->drm, + "DSI transcoder not enabled\n"); + } +} + +static void gen11_dsi_setup_timeouts(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum port port; + enum transcoder dsi_trans; + u32 tmp, hs_tx_timeout, lp_rx_timeout, ta_timeout, divisor, mul; + + /* + * escape clock count calculation: + * BYTE_CLK_COUNT = TIME_NS/(8 * UI) + * UI (nsec) = (10^6)/Bitrate + * TIME_NS = (BYTE_CLK_COUNT * 8 * 10^6)/ Bitrate + * ESCAPE_CLK_COUNT = TIME_NS/ESC_CLK_NS + */ + divisor = intel_dsi_tlpx_ns(intel_dsi) * afe_clk(encoder, crtc_state) * 1000; + mul = 8 * 1000000; + hs_tx_timeout = DIV_ROUND_UP(intel_dsi->hs_tx_timeout * mul, + divisor); + lp_rx_timeout = DIV_ROUND_UP(intel_dsi->lp_rx_timeout * mul, divisor); + ta_timeout = DIV_ROUND_UP(intel_dsi->turn_arnd_val * mul, divisor); + + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + + /* program hst_tx_timeout */ + tmp = intel_de_read(dev_priv, DSI_HSTX_TO(dsi_trans)); + tmp &= ~HSTX_TIMEOUT_VALUE_MASK; + tmp |= HSTX_TIMEOUT_VALUE(hs_tx_timeout); + intel_de_write(dev_priv, DSI_HSTX_TO(dsi_trans), tmp); + + /* FIXME: DSI_CALIB_TO */ + + /* program lp_rx_host timeout */ + tmp = intel_de_read(dev_priv, DSI_LPRX_HOST_TO(dsi_trans)); + tmp &= ~LPRX_TIMEOUT_VALUE_MASK; + tmp |= LPRX_TIMEOUT_VALUE(lp_rx_timeout); + intel_de_write(dev_priv, DSI_LPRX_HOST_TO(dsi_trans), tmp); + + /* FIXME: DSI_PWAIT_TO */ + + /* program turn around timeout */ + tmp = intel_de_read(dev_priv, DSI_TA_TO(dsi_trans)); + tmp &= ~TA_TIMEOUT_VALUE_MASK; + tmp |= TA_TIMEOUT_VALUE(ta_timeout); + intel_de_write(dev_priv, DSI_TA_TO(dsi_trans), tmp); + } +} + +static void gen11_dsi_config_util_pin(struct intel_encoder *encoder, + bool enable) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + u32 tmp; + + /* + * used as TE i/p for DSI0, + * for dual link/DSI1 TE is from slave DSI1 + * through GPIO. + */ + if (is_vid_mode(intel_dsi) || (intel_dsi->ports & BIT(PORT_B))) + return; + + tmp = intel_de_read(dev_priv, UTIL_PIN_CTL); + + if (enable) { + tmp |= UTIL_PIN_DIRECTION_INPUT; + tmp |= UTIL_PIN_ENABLE; + } else { + tmp &= ~UTIL_PIN_ENABLE; + } + intel_de_write(dev_priv, UTIL_PIN_CTL, tmp); +} + +static void +gen11_dsi_enable_port_and_phy(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + /* step 4a: power up all lanes of the DDI used by DSI */ + gen11_dsi_power_up_lanes(encoder); + + /* step 4b: configure lane sequencing of the Combo-PHY transmitters */ + gen11_dsi_config_phy_lanes_sequence(encoder); + + /* step 4c: configure voltage swing and skew */ + gen11_dsi_voltage_swing_program_seq(encoder); + + /* enable DDI buffer */ + gen11_dsi_enable_ddi_buffer(encoder); + + /* setup D-PHY timings */ + gen11_dsi_setup_dphy_timings(encoder, crtc_state); + + /* Since transcoder is configured to take events from GPIO */ + gen11_dsi_config_util_pin(encoder, true); + + /* step 4h: setup DSI protocol timeouts */ + gen11_dsi_setup_timeouts(encoder, crtc_state); + + /* Step (4h, 4i, 4j, 4k): Configure transcoder */ + gen11_dsi_configure_transcoder(encoder, crtc_state); + + /* Step 4l: Gate DDI clocks */ + gen11_dsi_gate_clocks(encoder); +} + +static void gen11_dsi_powerup_panel(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + struct mipi_dsi_device *dsi; + enum port port; + enum transcoder dsi_trans; + u32 tmp; + int ret; + + /* set maximum return packet size */ + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + + /* + * FIXME: This uses the number of DW's currently in the payload + * receive queue. This is probably not what we want here. + */ + tmp = intel_de_read(dev_priv, DSI_CMD_RXCTL(dsi_trans)); + tmp &= NUMBER_RX_PLOAD_DW_MASK; + /* multiply "Number Rx Payload DW" by 4 to get max value */ + tmp = tmp * 4; + dsi = intel_dsi->dsi_hosts[port]->device; + ret = mipi_dsi_set_maximum_return_packet_size(dsi, tmp); + if (ret < 0) + drm_err(&dev_priv->drm, + "error setting max return pkt size%d\n", tmp); + } + + /* panel power on related mipi dsi vbt sequences */ + intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_POWER_ON); + msleep(intel_dsi->panel_on_delay); + intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_DEASSERT_RESET); + intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_INIT_OTP); + intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_DISPLAY_ON); + + /* ensure all panel commands dispatched before enabling transcoder */ + wait_for_cmds_dispatched_to_panel(encoder); +} + +static void gen11_dsi_pre_pll_enable(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + /* step2: enable IO power */ + gen11_dsi_enable_io_power(encoder); + + /* step3: enable DSI PLL */ + gen11_dsi_program_esc_clk_div(encoder, crtc_state); +} + +static void gen11_dsi_pre_enable(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ + /* step3b */ + gen11_dsi_map_pll(encoder, pipe_config); + + /* step4: enable DSI port and DPHY */ + gen11_dsi_enable_port_and_phy(encoder, pipe_config); + + /* step5: program and powerup panel */ + gen11_dsi_powerup_panel(encoder); + + intel_dsc_dsi_pps_write(encoder, pipe_config); + + /* step6c: configure transcoder timings */ + gen11_dsi_set_transcoder_timings(encoder, pipe_config); +} + +/* + * Wa_1409054076:icl,jsl,ehl + * When pipe A is disabled and MIPI DSI is enabled on pipe B, + * the AMT KVMR feature will incorrectly see pipe A as enabled. + * Set 0x42080 bit 23=1 before enabling DSI on pipe B and leave + * it set while DSI is enabled on pipe B + */ +static void icl_apply_kvmr_pipe_a_wa(struct intel_encoder *encoder, + enum pipe pipe, bool enable) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + + if (DISPLAY_VER(dev_priv) == 11 && pipe == PIPE_B) + intel_de_rmw(dev_priv, CHICKEN_PAR1_1, + IGNORE_KVMR_PIPE_A, + enable ? IGNORE_KVMR_PIPE_A : 0); +} + +/* + * Wa_16012360555:adl-p + * SW will have to program the "LP to HS Wakeup Guardband" + * to account for the repeaters on the HS Request/Ready + * PPI signaling between the Display engine and the DPHY. + */ +static void adlp_set_lp_hs_wakeup_gb(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum port port; + + if (DISPLAY_VER(i915) == 13) { + for_each_dsi_port(port, intel_dsi->ports) + intel_de_rmw(i915, TGL_DSI_CHKN_REG(port), + TGL_DSI_CHKN_LSHS_GB_MASK, + TGL_DSI_CHKN_LSHS_GB(4)); + } +} + +static void gen11_dsi_enable(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + struct intel_crtc *crtc = to_intel_crtc(conn_state->crtc); + + drm_WARN_ON(state->base.dev, crtc_state->has_pch_encoder); + + /* Wa_1409054076:icl,jsl,ehl */ + icl_apply_kvmr_pipe_a_wa(encoder, crtc->pipe, true); + + /* Wa_16012360555:adl-p */ + adlp_set_lp_hs_wakeup_gb(encoder); + + /* step6d: enable dsi transcoder */ + gen11_dsi_enable_transcoder(encoder); + + /* step7: enable backlight */ + intel_backlight_enable(crtc_state, conn_state); + intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_BACKLIGHT_ON); + + intel_crtc_vblank_on(crtc_state); +} + +static void gen11_dsi_disable_transcoder(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum port port; + enum transcoder dsi_trans; + u32 tmp; + + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + + /* disable transcoder */ + tmp = intel_de_read(dev_priv, PIPECONF(dsi_trans)); + tmp &= ~PIPECONF_ENABLE; + intel_de_write(dev_priv, PIPECONF(dsi_trans), tmp); + + /* wait for transcoder to be disabled */ + if (intel_de_wait_for_clear(dev_priv, PIPECONF(dsi_trans), + PIPECONF_STATE_ENABLE, 50)) + drm_err(&dev_priv->drm, + "DSI trancoder not disabled\n"); + } +} + +static void gen11_dsi_powerdown_panel(struct intel_encoder *encoder) +{ + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + + intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_DISPLAY_OFF); + intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_ASSERT_RESET); + intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_POWER_OFF); + + /* ensure cmds dispatched to panel */ + wait_for_cmds_dispatched_to_panel(encoder); +} + +static void gen11_dsi_deconfigure_trancoder(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum port port; + enum transcoder dsi_trans; + u32 tmp; + + /* disable periodic update mode */ + if (is_cmd_mode(intel_dsi)) { + for_each_dsi_port(port, intel_dsi->ports) { + tmp = intel_de_read(dev_priv, DSI_CMD_FRMCTL(port)); + tmp &= ~DSI_PERIODIC_FRAME_UPDATE_ENABLE; + intel_de_write(dev_priv, DSI_CMD_FRMCTL(port), tmp); + } + } + + /* put dsi link in ULPS */ + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + tmp = intel_de_read(dev_priv, DSI_LP_MSG(dsi_trans)); + tmp |= LINK_ENTER_ULPS; + tmp &= ~LINK_ULPS_TYPE_LP11; + intel_de_write(dev_priv, DSI_LP_MSG(dsi_trans), tmp); + + if (wait_for_us((intel_de_read(dev_priv, DSI_LP_MSG(dsi_trans)) & + LINK_IN_ULPS), + 10)) + drm_err(&dev_priv->drm, "DSI link not in ULPS\n"); + } + + /* disable ddi function */ + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + tmp = intel_de_read(dev_priv, TRANS_DDI_FUNC_CTL(dsi_trans)); + tmp &= ~TRANS_DDI_FUNC_ENABLE; + intel_de_write(dev_priv, TRANS_DDI_FUNC_CTL(dsi_trans), tmp); + } + + /* disable port sync mode if dual link */ + if (intel_dsi->dual_link) { + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + tmp = intel_de_read(dev_priv, + TRANS_DDI_FUNC_CTL2(dsi_trans)); + tmp &= ~PORT_SYNC_MODE_ENABLE; + intel_de_write(dev_priv, + TRANS_DDI_FUNC_CTL2(dsi_trans), tmp); + } + } +} + +static void gen11_dsi_disable_port(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + u32 tmp; + enum port port; + + gen11_dsi_ungate_clocks(encoder); + for_each_dsi_port(port, intel_dsi->ports) { + tmp = intel_de_read(dev_priv, DDI_BUF_CTL(port)); + tmp &= ~DDI_BUF_CTL_ENABLE; + intel_de_write(dev_priv, DDI_BUF_CTL(port), tmp); + + if (wait_for_us((intel_de_read(dev_priv, DDI_BUF_CTL(port)) & + DDI_BUF_IS_IDLE), + 8)) + drm_err(&dev_priv->drm, + "DDI port:%c buffer not idle\n", + port_name(port)); + } + gen11_dsi_gate_clocks(encoder); +} + +static void gen11_dsi_disable_io_power(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum port port; + u32 tmp; + + for_each_dsi_port(port, intel_dsi->ports) { + intel_wakeref_t wakeref; + + wakeref = fetch_and_zero(&intel_dsi->io_wakeref[port]); + intel_display_power_put(dev_priv, + port == PORT_A ? + POWER_DOMAIN_PORT_DDI_IO_A : + POWER_DOMAIN_PORT_DDI_IO_B, + wakeref); + } + + /* set mode to DDI */ + for_each_dsi_port(port, intel_dsi->ports) { + tmp = intel_de_read(dev_priv, ICL_DSI_IO_MODECTL(port)); + tmp &= ~COMBO_PHY_MODE_DSI; + intel_de_write(dev_priv, ICL_DSI_IO_MODECTL(port), tmp); + } +} + +static void gen11_dsi_disable(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + struct intel_crtc *crtc = to_intel_crtc(old_conn_state->crtc); + + /* step1: turn off backlight */ + intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_BACKLIGHT_OFF); + intel_backlight_disable(old_conn_state); + + /* step2d,e: disable transcoder and wait */ + gen11_dsi_disable_transcoder(encoder); + + /* Wa_1409054076:icl,jsl,ehl */ + icl_apply_kvmr_pipe_a_wa(encoder, crtc->pipe, false); + + /* step2f,g: powerdown panel */ + gen11_dsi_powerdown_panel(encoder); + + /* step2h,i,j: deconfig trancoder */ + gen11_dsi_deconfigure_trancoder(encoder); + + /* step3: disable port */ + gen11_dsi_disable_port(encoder); + + gen11_dsi_config_util_pin(encoder, false); + + /* step4: disable IO power */ + gen11_dsi_disable_io_power(encoder); +} + +static void gen11_dsi_post_disable(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + intel_crtc_vblank_off(old_crtc_state); + + intel_dsc_disable(old_crtc_state); + + skl_scaler_disable(old_crtc_state); +} + +static enum drm_mode_status gen11_dsi_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct drm_i915_private *i915 = to_i915(connector->dev); + enum drm_mode_status status; + + status = intel_cpu_transcoder_mode_valid(i915, mode); + if (status != MODE_OK) + return status; + + /* FIXME: DSC? */ + return intel_dsi_mode_valid(connector, mode); +} + +static void gen11_dsi_get_timings(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config) +{ + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + struct drm_display_mode *adjusted_mode = + &pipe_config->hw.adjusted_mode; + + if (pipe_config->dsc.compressed_bpp) { + int div = pipe_config->dsc.compressed_bpp; + int mul = mipi_dsi_pixel_format_to_bpp(intel_dsi->pixel_format); + + adjusted_mode->crtc_htotal = + DIV_ROUND_UP(adjusted_mode->crtc_htotal * mul, div); + adjusted_mode->crtc_hsync_start = + DIV_ROUND_UP(adjusted_mode->crtc_hsync_start * mul, div); + adjusted_mode->crtc_hsync_end = + DIV_ROUND_UP(adjusted_mode->crtc_hsync_end * mul, div); + } + + if (intel_dsi->dual_link) { + adjusted_mode->crtc_hdisplay *= 2; + if (intel_dsi->dual_link == DSI_DUAL_LINK_FRONT_BACK) + adjusted_mode->crtc_hdisplay -= + intel_dsi->pixel_overlap; + adjusted_mode->crtc_htotal *= 2; + } + adjusted_mode->crtc_hblank_start = adjusted_mode->crtc_hdisplay; + adjusted_mode->crtc_hblank_end = adjusted_mode->crtc_htotal; + + if (intel_dsi->operation_mode == INTEL_DSI_VIDEO_MODE) { + if (intel_dsi->dual_link) { + adjusted_mode->crtc_hsync_start *= 2; + adjusted_mode->crtc_hsync_end *= 2; + } + } + adjusted_mode->crtc_vblank_start = adjusted_mode->crtc_vdisplay; + adjusted_mode->crtc_vblank_end = adjusted_mode->crtc_vtotal; +} + +static bool gen11_dsi_is_periodic_cmd_mode(struct intel_dsi *intel_dsi) +{ + struct drm_device *dev = intel_dsi->base.base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + enum transcoder dsi_trans; + u32 val; + + if (intel_dsi->ports == BIT(PORT_B)) + dsi_trans = TRANSCODER_DSI_1; + else + dsi_trans = TRANSCODER_DSI_0; + + val = intel_de_read(dev_priv, DSI_TRANS_FUNC_CONF(dsi_trans)); + return (val & DSI_PERIODIC_FRAME_UPDATE_ENABLE); +} + +static void gen11_dsi_get_cmd_mode_config(struct intel_dsi *intel_dsi, + struct intel_crtc_state *pipe_config) +{ + if (intel_dsi->ports == (BIT(PORT_B) | BIT(PORT_A))) + pipe_config->mode_flags |= I915_MODE_FLAG_DSI_USE_TE1 | + I915_MODE_FLAG_DSI_USE_TE0; + else if (intel_dsi->ports == BIT(PORT_B)) + pipe_config->mode_flags |= I915_MODE_FLAG_DSI_USE_TE1; + else + pipe_config->mode_flags |= I915_MODE_FLAG_DSI_USE_TE0; +} + +static void gen11_dsi_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config) +{ + struct intel_crtc *crtc = to_intel_crtc(pipe_config->uapi.crtc); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + + intel_ddi_get_clock(encoder, pipe_config, icl_ddi_combo_get_pll(encoder)); + + pipe_config->hw.adjusted_mode.crtc_clock = intel_dsi->pclk; + if (intel_dsi->dual_link) + pipe_config->hw.adjusted_mode.crtc_clock *= 2; + + gen11_dsi_get_timings(encoder, pipe_config); + pipe_config->output_types |= BIT(INTEL_OUTPUT_DSI); + pipe_config->pipe_bpp = bdw_get_pipemisc_bpp(crtc); + + /* Get the details on which TE should be enabled */ + if (is_cmd_mode(intel_dsi)) + gen11_dsi_get_cmd_mode_config(intel_dsi, pipe_config); + + if (gen11_dsi_is_periodic_cmd_mode(intel_dsi)) + pipe_config->mode_flags |= I915_MODE_FLAG_DSI_PERIODIC_CMD_MODE; +} + +static void gen11_dsi_sync_state(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_crtc *intel_crtc; + enum pipe pipe; + + if (!crtc_state) + return; + + intel_crtc = to_intel_crtc(crtc_state->uapi.crtc); + pipe = intel_crtc->pipe; + + /* wa verify 1409054076:icl,jsl,ehl */ + if (DISPLAY_VER(dev_priv) == 11 && pipe == PIPE_B && + !(intel_de_read(dev_priv, CHICKEN_PAR1_1) & IGNORE_KVMR_PIPE_A)) + drm_dbg_kms(&dev_priv->drm, + "[ENCODER:%d:%s] BIOS left IGNORE_KVMR_PIPE_A cleared with pipe B enabled\n", + encoder->base.base.id, + encoder->base.name); +} + +static int gen11_dsi_dsc_compute_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct drm_dsc_config *vdsc_cfg = &crtc_state->dsc.config; + int dsc_max_bpc = DISPLAY_VER(dev_priv) >= 12 ? 12 : 10; + bool use_dsc; + int ret; + + use_dsc = intel_bios_get_dsc_params(encoder, crtc_state, dsc_max_bpc); + if (!use_dsc) + return 0; + + if (crtc_state->pipe_bpp < 8 * 3) + return -EINVAL; + + /* FIXME: split only when necessary */ + if (crtc_state->dsc.slice_count > 1) + crtc_state->dsc.dsc_split = true; + + vdsc_cfg->convert_rgb = true; + + /* FIXME: initialize from VBT */ + vdsc_cfg->rc_model_size = DSC_RC_MODEL_SIZE_CONST; + + vdsc_cfg->pic_height = crtc_state->hw.adjusted_mode.crtc_vdisplay; + + ret = intel_dsc_compute_params(crtc_state); + if (ret) + return ret; + + /* DSI specific sanity checks on the common code */ + drm_WARN_ON(&dev_priv->drm, vdsc_cfg->vbr_enable); + drm_WARN_ON(&dev_priv->drm, vdsc_cfg->simple_422); + drm_WARN_ON(&dev_priv->drm, + vdsc_cfg->pic_width % vdsc_cfg->slice_width); + drm_WARN_ON(&dev_priv->drm, vdsc_cfg->slice_height < 8); + drm_WARN_ON(&dev_priv->drm, + vdsc_cfg->pic_height % vdsc_cfg->slice_height); + + ret = drm_dsc_compute_rc_parameters(vdsc_cfg); + if (ret) + return ret; + + crtc_state->dsc.compression_enable = true; + + return 0; +} + +static int gen11_dsi_compute_config(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config, + struct drm_connector_state *conn_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = container_of(encoder, struct intel_dsi, + base); + struct intel_connector *intel_connector = intel_dsi->attached_connector; + struct drm_display_mode *adjusted_mode = + &pipe_config->hw.adjusted_mode; + int ret; + + pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB; + + ret = intel_panel_compute_config(intel_connector, adjusted_mode); + if (ret) + return ret; + + ret = intel_panel_fitting(pipe_config, conn_state); + if (ret) + return ret; + + adjusted_mode->flags = 0; + + /* Dual link goes to trancoder DSI'0' */ + if (intel_dsi->ports == BIT(PORT_B)) + pipe_config->cpu_transcoder = TRANSCODER_DSI_1; + else + pipe_config->cpu_transcoder = TRANSCODER_DSI_0; + + if (intel_dsi->pixel_format == MIPI_DSI_FMT_RGB888) + pipe_config->pipe_bpp = 24; + else + pipe_config->pipe_bpp = 18; + + pipe_config->clock_set = true; + + if (gen11_dsi_dsc_compute_config(encoder, pipe_config)) + drm_dbg_kms(&i915->drm, "Attempting to use DSC failed\n"); + + pipe_config->port_clock = afe_clk(encoder, pipe_config) / 5; + + /* + * In case of TE GATE cmd mode, we + * receive TE from the slave if + * dual link is enabled + */ + if (is_cmd_mode(intel_dsi)) + gen11_dsi_get_cmd_mode_config(intel_dsi, pipe_config); + + return 0; +} + +static void gen11_dsi_get_power_domains(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + + get_dsi_io_power_domains(i915, + enc_to_intel_dsi(encoder)); +} + +static bool gen11_dsi_get_hw_state(struct intel_encoder *encoder, + enum pipe *pipe) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum transcoder dsi_trans; + intel_wakeref_t wakeref; + enum port port; + bool ret = false; + u32 tmp; + + wakeref = intel_display_power_get_if_enabled(dev_priv, + encoder->power_domain); + if (!wakeref) + return false; + + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + tmp = intel_de_read(dev_priv, TRANS_DDI_FUNC_CTL(dsi_trans)); + switch (tmp & TRANS_DDI_EDP_INPUT_MASK) { + case TRANS_DDI_EDP_INPUT_A_ON: + *pipe = PIPE_A; + break; + case TRANS_DDI_EDP_INPUT_B_ONOFF: + *pipe = PIPE_B; + break; + case TRANS_DDI_EDP_INPUT_C_ONOFF: + *pipe = PIPE_C; + break; + case TRANS_DDI_EDP_INPUT_D_ONOFF: + *pipe = PIPE_D; + break; + default: + drm_err(&dev_priv->drm, "Invalid PIPE input\n"); + goto out; + } + + tmp = intel_de_read(dev_priv, PIPECONF(dsi_trans)); + ret = tmp & PIPECONF_ENABLE; + } +out: + intel_display_power_put(dev_priv, encoder->power_domain, wakeref); + return ret; +} + +static bool gen11_dsi_initial_fastset_check(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + if (crtc_state->dsc.compression_enable) { + drm_dbg_kms(encoder->base.dev, "Forcing full modeset due to DSC being enabled\n"); + crtc_state->uapi.mode_changed = true; + + return false; + } + + return true; +} + +static void gen11_dsi_encoder_destroy(struct drm_encoder *encoder) +{ + intel_encoder_destroy(encoder); +} + +static const struct drm_encoder_funcs gen11_dsi_encoder_funcs = { + .destroy = gen11_dsi_encoder_destroy, +}; + +static const struct drm_connector_funcs gen11_dsi_connector_funcs = { + .detect = intel_panel_detect, + .late_register = intel_connector_register, + .early_unregister = intel_connector_unregister, + .destroy = intel_connector_destroy, + .fill_modes = drm_helper_probe_single_connector_modes, + .atomic_get_property = intel_digital_connector_atomic_get_property, + .atomic_set_property = intel_digital_connector_atomic_set_property, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .atomic_duplicate_state = intel_digital_connector_duplicate_state, +}; + +static const struct drm_connector_helper_funcs gen11_dsi_connector_helper_funcs = { + .get_modes = intel_dsi_get_modes, + .mode_valid = gen11_dsi_mode_valid, + .atomic_check = intel_digital_connector_atomic_check, +}; + +static int gen11_dsi_host_attach(struct mipi_dsi_host *host, + struct mipi_dsi_device *dsi) +{ + return 0; +} + +static int gen11_dsi_host_detach(struct mipi_dsi_host *host, + struct mipi_dsi_device *dsi) +{ + return 0; +} + +static ssize_t gen11_dsi_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct intel_dsi_host *intel_dsi_host = to_intel_dsi_host(host); + struct mipi_dsi_packet dsi_pkt; + ssize_t ret; + bool enable_lpdt = false; + + ret = mipi_dsi_create_packet(&dsi_pkt, msg); + if (ret < 0) + return ret; + + if (msg->flags & MIPI_DSI_MSG_USE_LPM) + enable_lpdt = true; + + /* only long packet contains payload */ + if (mipi_dsi_packet_format_is_long(msg->type)) { + ret = dsi_send_pkt_payld(intel_dsi_host, &dsi_pkt); + if (ret < 0) + return ret; + } + + /* send packet header */ + ret = dsi_send_pkt_hdr(intel_dsi_host, &dsi_pkt, enable_lpdt); + if (ret < 0) + return ret; + + //TODO: add payload receive code if needed + + ret = sizeof(dsi_pkt.header) + dsi_pkt.payload_length; + + return ret; +} + +static const struct mipi_dsi_host_ops gen11_dsi_host_ops = { + .attach = gen11_dsi_host_attach, + .detach = gen11_dsi_host_detach, + .transfer = gen11_dsi_host_transfer, +}; + +#define ICL_PREPARE_CNT_MAX 0x7 +#define ICL_CLK_ZERO_CNT_MAX 0xf +#define ICL_TRAIL_CNT_MAX 0x7 +#define ICL_TCLK_PRE_CNT_MAX 0x3 +#define ICL_TCLK_POST_CNT_MAX 0x7 +#define ICL_HS_ZERO_CNT_MAX 0xf +#define ICL_EXIT_ZERO_CNT_MAX 0x7 + +static void icl_dphy_param_init(struct intel_dsi *intel_dsi) +{ + struct drm_device *dev = intel_dsi->base.base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + struct intel_connector *connector = intel_dsi->attached_connector; + struct mipi_config *mipi_config = connector->panel.vbt.dsi.config; + u32 tlpx_ns; + u32 prepare_cnt, exit_zero_cnt, clk_zero_cnt, trail_cnt; + u32 ths_prepare_ns, tclk_trail_ns; + u32 hs_zero_cnt; + u32 tclk_pre_cnt, tclk_post_cnt; + + tlpx_ns = intel_dsi_tlpx_ns(intel_dsi); + + tclk_trail_ns = max(mipi_config->tclk_trail, mipi_config->ths_trail); + ths_prepare_ns = max(mipi_config->ths_prepare, + mipi_config->tclk_prepare); + + /* + * prepare cnt in escape clocks + * this field represents a hexadecimal value with a precision + * of 1.2 – i.e. the most significant bit is the integer + * and the least significant 2 bits are fraction bits. + * so, the field can represent a range of 0.25 to 1.75 + */ + prepare_cnt = DIV_ROUND_UP(ths_prepare_ns * 4, tlpx_ns); + if (prepare_cnt > ICL_PREPARE_CNT_MAX) { + drm_dbg_kms(&dev_priv->drm, "prepare_cnt out of range (%d)\n", + prepare_cnt); + prepare_cnt = ICL_PREPARE_CNT_MAX; + } + + /* clk zero count in escape clocks */ + clk_zero_cnt = DIV_ROUND_UP(mipi_config->tclk_prepare_clkzero - + ths_prepare_ns, tlpx_ns); + if (clk_zero_cnt > ICL_CLK_ZERO_CNT_MAX) { + drm_dbg_kms(&dev_priv->drm, + "clk_zero_cnt out of range (%d)\n", clk_zero_cnt); + clk_zero_cnt = ICL_CLK_ZERO_CNT_MAX; + } + + /* trail cnt in escape clocks*/ + trail_cnt = DIV_ROUND_UP(tclk_trail_ns, tlpx_ns); + if (trail_cnt > ICL_TRAIL_CNT_MAX) { + drm_dbg_kms(&dev_priv->drm, "trail_cnt out of range (%d)\n", + trail_cnt); + trail_cnt = ICL_TRAIL_CNT_MAX; + } + + /* tclk pre count in escape clocks */ + tclk_pre_cnt = DIV_ROUND_UP(mipi_config->tclk_pre, tlpx_ns); + if (tclk_pre_cnt > ICL_TCLK_PRE_CNT_MAX) { + drm_dbg_kms(&dev_priv->drm, + "tclk_pre_cnt out of range (%d)\n", tclk_pre_cnt); + tclk_pre_cnt = ICL_TCLK_PRE_CNT_MAX; + } + + /* tclk post count in escape clocks */ + tclk_post_cnt = DIV_ROUND_UP(mipi_config->tclk_post, tlpx_ns); + if (tclk_post_cnt > ICL_TCLK_POST_CNT_MAX) { + drm_dbg_kms(&dev_priv->drm, + "tclk_post_cnt out of range (%d)\n", + tclk_post_cnt); + tclk_post_cnt = ICL_TCLK_POST_CNT_MAX; + } + + /* hs zero cnt in escape clocks */ + hs_zero_cnt = DIV_ROUND_UP(mipi_config->ths_prepare_hszero - + ths_prepare_ns, tlpx_ns); + if (hs_zero_cnt > ICL_HS_ZERO_CNT_MAX) { + drm_dbg_kms(&dev_priv->drm, "hs_zero_cnt out of range (%d)\n", + hs_zero_cnt); + hs_zero_cnt = ICL_HS_ZERO_CNT_MAX; + } + + /* hs exit zero cnt in escape clocks */ + exit_zero_cnt = DIV_ROUND_UP(mipi_config->ths_exit, tlpx_ns); + if (exit_zero_cnt > ICL_EXIT_ZERO_CNT_MAX) { + drm_dbg_kms(&dev_priv->drm, + "exit_zero_cnt out of range (%d)\n", + exit_zero_cnt); + exit_zero_cnt = ICL_EXIT_ZERO_CNT_MAX; + } + + /* clock lane dphy timings */ + intel_dsi->dphy_reg = (CLK_PREPARE_OVERRIDE | + CLK_PREPARE(prepare_cnt) | + CLK_ZERO_OVERRIDE | + CLK_ZERO(clk_zero_cnt) | + CLK_PRE_OVERRIDE | + CLK_PRE(tclk_pre_cnt) | + CLK_POST_OVERRIDE | + CLK_POST(tclk_post_cnt) | + CLK_TRAIL_OVERRIDE | + CLK_TRAIL(trail_cnt)); + + /* data lanes dphy timings */ + intel_dsi->dphy_data_lane_reg = (HS_PREPARE_OVERRIDE | + HS_PREPARE(prepare_cnt) | + HS_ZERO_OVERRIDE | + HS_ZERO(hs_zero_cnt) | + HS_TRAIL_OVERRIDE | + HS_TRAIL(trail_cnt) | + HS_EXIT_OVERRIDE | + HS_EXIT(exit_zero_cnt)); + + intel_dsi_log_params(intel_dsi); +} + +static void icl_dsi_add_properties(struct intel_connector *connector) +{ + const struct drm_display_mode *fixed_mode = + intel_panel_preferred_fixed_mode(connector); + u32 allowed_scalers; + + allowed_scalers = BIT(DRM_MODE_SCALE_ASPECT) | + BIT(DRM_MODE_SCALE_FULLSCREEN) | + BIT(DRM_MODE_SCALE_CENTER); + + drm_connector_attach_scaling_mode_property(&connector->base, + allowed_scalers); + + connector->base.state->scaling_mode = DRM_MODE_SCALE_ASPECT; + + drm_connector_set_panel_orientation_with_quirk(&connector->base, + intel_dsi_get_panel_orientation(connector), + fixed_mode->hdisplay, + fixed_mode->vdisplay); +} + +void icl_dsi_init(struct drm_i915_private *dev_priv) +{ + struct drm_device *dev = &dev_priv->drm; + struct intel_dsi *intel_dsi; + struct intel_encoder *encoder; + struct intel_connector *intel_connector; + struct drm_connector *connector; + enum port port; + + if (!intel_bios_is_dsi_present(dev_priv, &port)) + return; + + intel_dsi = kzalloc(sizeof(*intel_dsi), GFP_KERNEL); + if (!intel_dsi) + return; + + intel_connector = intel_connector_alloc(); + if (!intel_connector) { + kfree(intel_dsi); + return; + } + + encoder = &intel_dsi->base; + intel_dsi->attached_connector = intel_connector; + connector = &intel_connector->base; + + /* register DSI encoder with DRM subsystem */ + drm_encoder_init(dev, &encoder->base, &gen11_dsi_encoder_funcs, + DRM_MODE_ENCODER_DSI, "DSI %c", port_name(port)); + + encoder->pre_pll_enable = gen11_dsi_pre_pll_enable; + encoder->pre_enable = gen11_dsi_pre_enable; + encoder->enable = gen11_dsi_enable; + encoder->disable = gen11_dsi_disable; + encoder->post_disable = gen11_dsi_post_disable; + encoder->port = port; + encoder->get_config = gen11_dsi_get_config; + encoder->sync_state = gen11_dsi_sync_state; + encoder->update_pipe = intel_backlight_update; + encoder->compute_config = gen11_dsi_compute_config; + encoder->get_hw_state = gen11_dsi_get_hw_state; + encoder->initial_fastset_check = gen11_dsi_initial_fastset_check; + encoder->type = INTEL_OUTPUT_DSI; + encoder->cloneable = 0; + encoder->pipe_mask = ~0; + encoder->power_domain = POWER_DOMAIN_PORT_DSI; + encoder->get_power_domains = gen11_dsi_get_power_domains; + encoder->disable_clock = gen11_dsi_gate_clocks; + encoder->is_clock_enabled = gen11_dsi_is_clock_enabled; + + /* register DSI connector with DRM subsystem */ + drm_connector_init(dev, connector, &gen11_dsi_connector_funcs, + DRM_MODE_CONNECTOR_DSI); + drm_connector_helper_add(connector, &gen11_dsi_connector_helper_funcs); + connector->display_info.subpixel_order = SubPixelHorizontalRGB; + connector->interlace_allowed = false; + connector->doublescan_allowed = false; + intel_connector->get_hw_state = intel_connector_get_hw_state; + + /* attach connector to encoder */ + intel_connector_attach_encoder(intel_connector, encoder); + + encoder->devdata = intel_bios_encoder_data_lookup(dev_priv, port); + intel_bios_init_panel_late(dev_priv, &intel_connector->panel, encoder->devdata, NULL); + + mutex_lock(&dev->mode_config.mutex); + intel_panel_add_vbt_lfp_fixed_mode(intel_connector); + mutex_unlock(&dev->mode_config.mutex); + + if (!intel_panel_preferred_fixed_mode(intel_connector)) { + drm_err(&dev_priv->drm, "DSI fixed mode info missing\n"); + goto err; + } + + intel_panel_init(intel_connector); + + intel_backlight_setup(intel_connector, INVALID_PIPE); + + if (intel_connector->panel.vbt.dsi.config->dual_link) + intel_dsi->ports = BIT(PORT_A) | BIT(PORT_B); + else + intel_dsi->ports = BIT(port); + + if (drm_WARN_ON(&dev_priv->drm, intel_connector->panel.vbt.dsi.bl_ports & ~intel_dsi->ports)) + intel_connector->panel.vbt.dsi.bl_ports &= intel_dsi->ports; + + if (drm_WARN_ON(&dev_priv->drm, intel_connector->panel.vbt.dsi.cabc_ports & ~intel_dsi->ports)) + intel_connector->panel.vbt.dsi.cabc_ports &= intel_dsi->ports; + + for_each_dsi_port(port, intel_dsi->ports) { + struct intel_dsi_host *host; + + host = intel_dsi_host_init(intel_dsi, &gen11_dsi_host_ops, port); + if (!host) + goto err; + + intel_dsi->dsi_hosts[port] = host; + } + + if (!intel_dsi_vbt_init(intel_dsi, MIPI_DSI_GENERIC_PANEL_ID)) { + drm_dbg_kms(&dev_priv->drm, "no device found\n"); + goto err; + } + + icl_dphy_param_init(intel_dsi); + + icl_dsi_add_properties(intel_connector); + return; + +err: + drm_connector_cleanup(connector); + drm_encoder_cleanup(&encoder->base); + kfree(intel_dsi); + kfree(intel_connector); +} diff --git a/drivers/gpu/drm/i915/display/icl_dsi.h b/drivers/gpu/drm/i915/display/icl_dsi.h new file mode 100644 index 000000000..b4861b56b --- /dev/null +++ b/drivers/gpu/drm/i915/display/icl_dsi.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2021 Intel Corporation + */ + +#ifndef __ICL_DSI_H__ +#define __ICL_DSI_H__ + +struct drm_i915_private; +struct intel_crtc_state; + +void icl_dsi_init(struct drm_i915_private *i915); +void icl_dsi_frame_update(struct intel_crtc_state *crtc_state); + +#endif /* __ICL_DSI_H__ */ diff --git a/drivers/gpu/drm/i915/display/icl_dsi_regs.h b/drivers/gpu/drm/i915/display/icl_dsi_regs.h new file mode 100644 index 000000000..f78f28b8d --- /dev/null +++ b/drivers/gpu/drm/i915/display/icl_dsi_regs.h @@ -0,0 +1,342 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2022 Intel Corporation + */ + +#ifndef __ICL_DSI_REGS_H__ +#define __ICL_DSI_REGS_H__ + +#include "i915_reg_defs.h" + +/* Gen11 DSI */ +#define _MMIO_DSI(tc, dsi0, dsi1) _MMIO_TRANS((tc) - TRANSCODER_DSI_0, \ + dsi0, dsi1) +#define _ICL_DSI_ESC_CLK_DIV0 0x6b090 +#define _ICL_DSI_ESC_CLK_DIV1 0x6b890 +#define ICL_DSI_ESC_CLK_DIV(port) _MMIO_PORT((port), \ + _ICL_DSI_ESC_CLK_DIV0, \ + _ICL_DSI_ESC_CLK_DIV1) +#define _ICL_DPHY_ESC_CLK_DIV0 0x162190 +#define _ICL_DPHY_ESC_CLK_DIV1 0x6C190 +#define ICL_DPHY_ESC_CLK_DIV(port) _MMIO_PORT((port), \ + _ICL_DPHY_ESC_CLK_DIV0, \ + _ICL_DPHY_ESC_CLK_DIV1) +#define ICL_BYTE_CLK_PER_ESC_CLK_MASK (0x1f << 16) +#define ICL_BYTE_CLK_PER_ESC_CLK_SHIFT 16 +#define ICL_ESC_CLK_DIV_MASK 0x1ff +#define ICL_ESC_CLK_DIV_SHIFT 0 +#define DSI_MAX_ESC_CLK 20000 /* in KHz */ + +#define _ADL_MIPIO_REG 0x180 +#define ADL_MIPIO_DW(port, dw) _MMIO(_ICL_COMBOPHY(port) + _ADL_MIPIO_REG + 4 * (dw)) +#define TX_ESC_CLK_DIV_PHY_SEL REGBIT(16) +#define TX_ESC_CLK_DIV_PHY_MASK REG_GENMASK(23, 16) +#define TX_ESC_CLK_DIV_PHY REG_FIELD_PREP(TX_ESC_CLK_DIV_PHY_MASK, 0x7f) + +#define _DSI_CMD_FRMCTL_0 0x6b034 +#define _DSI_CMD_FRMCTL_1 0x6b834 +#define DSI_CMD_FRMCTL(port) _MMIO_PORT(port, \ + _DSI_CMD_FRMCTL_0,\ + _DSI_CMD_FRMCTL_1) +#define DSI_FRAME_UPDATE_REQUEST (1 << 31) +#define DSI_PERIODIC_FRAME_UPDATE_ENABLE (1 << 29) +#define DSI_NULL_PACKET_ENABLE (1 << 28) +#define DSI_FRAME_IN_PROGRESS (1 << 0) + +#define _DSI_INTR_MASK_REG_0 0x6b070 +#define _DSI_INTR_MASK_REG_1 0x6b870 +#define DSI_INTR_MASK_REG(port) _MMIO_PORT(port, \ + _DSI_INTR_MASK_REG_0,\ + _DSI_INTR_MASK_REG_1) + +#define _DSI_INTR_IDENT_REG_0 0x6b074 +#define _DSI_INTR_IDENT_REG_1 0x6b874 +#define DSI_INTR_IDENT_REG(port) _MMIO_PORT(port, \ + _DSI_INTR_IDENT_REG_0,\ + _DSI_INTR_IDENT_REG_1) +#define DSI_TE_EVENT (1 << 31) +#define DSI_RX_DATA_OR_BTA_TERMINATED (1 << 30) +#define DSI_TX_DATA (1 << 29) +#define DSI_ULPS_ENTRY_DONE (1 << 28) +#define DSI_NON_TE_TRIGGER_RECEIVED (1 << 27) +#define DSI_HOST_CHKSUM_ERROR (1 << 26) +#define DSI_HOST_MULTI_ECC_ERROR (1 << 25) +#define DSI_HOST_SINGL_ECC_ERROR (1 << 24) +#define DSI_HOST_CONTENTION_DETECTED (1 << 23) +#define DSI_HOST_FALSE_CONTROL_ERROR (1 << 22) +#define DSI_HOST_TIMEOUT_ERROR (1 << 21) +#define DSI_HOST_LOW_POWER_TX_SYNC_ERROR (1 << 20) +#define DSI_HOST_ESCAPE_MODE_ENTRY_ERROR (1 << 19) +#define DSI_FRAME_UPDATE_DONE (1 << 16) +#define DSI_PROTOCOL_VIOLATION_REPORTED (1 << 15) +#define DSI_INVALID_TX_LENGTH (1 << 13) +#define DSI_INVALID_VC (1 << 12) +#define DSI_INVALID_DATA_TYPE (1 << 11) +#define DSI_PERIPHERAL_CHKSUM_ERROR (1 << 10) +#define DSI_PERIPHERAL_MULTI_ECC_ERROR (1 << 9) +#define DSI_PERIPHERAL_SINGLE_ECC_ERROR (1 << 8) +#define DSI_PERIPHERAL_CONTENTION_DETECTED (1 << 7) +#define DSI_PERIPHERAL_FALSE_CTRL_ERROR (1 << 6) +#define DSI_PERIPHERAL_TIMEOUT_ERROR (1 << 5) +#define DSI_PERIPHERAL_LP_TX_SYNC_ERROR (1 << 4) +#define DSI_PERIPHERAL_ESC_MODE_ENTRY_CMD_ERR (1 << 3) +#define DSI_EOT_SYNC_ERROR (1 << 2) +#define DSI_SOT_SYNC_ERROR (1 << 1) +#define DSI_SOT_ERROR (1 << 0) + +/* ICL DSI MODE control */ +#define _ICL_DSI_IO_MODECTL_0 0x6B094 +#define _ICL_DSI_IO_MODECTL_1 0x6B894 +#define ICL_DSI_IO_MODECTL(port) _MMIO_PORT(port, \ + _ICL_DSI_IO_MODECTL_0, \ + _ICL_DSI_IO_MODECTL_1) +#define COMBO_PHY_MODE_DSI (1 << 0) + +/* TGL DSI Chicken register */ +#define _TGL_DSI_CHKN_REG_0 0x6B0C0 +#define _TGL_DSI_CHKN_REG_1 0x6B8C0 +#define TGL_DSI_CHKN_REG(port) _MMIO_PORT(port, \ + _TGL_DSI_CHKN_REG_0, \ + _TGL_DSI_CHKN_REG_1) +#define TGL_DSI_CHKN_LSHS_GB_MASK REG_GENMASK(15, 12) +#define TGL_DSI_CHKN_LSHS_GB(byte_clocks) REG_FIELD_PREP(TGL_DSI_CHKN_LSHS_GB_MASK, \ + (byte_clocks)) +#define _ICL_DSI_T_INIT_MASTER_0 0x6b088 +#define _ICL_DSI_T_INIT_MASTER_1 0x6b888 +#define ICL_DSI_T_INIT_MASTER(port) _MMIO_PORT(port, \ + _ICL_DSI_T_INIT_MASTER_0,\ + _ICL_DSI_T_INIT_MASTER_1) +#define DSI_T_INIT_MASTER_MASK REG_GENMASK(15, 0) + +#define _DPHY_CLK_TIMING_PARAM_0 0x162180 +#define _DPHY_CLK_TIMING_PARAM_1 0x6c180 +#define DPHY_CLK_TIMING_PARAM(port) _MMIO_PORT(port, \ + _DPHY_CLK_TIMING_PARAM_0,\ + _DPHY_CLK_TIMING_PARAM_1) +#define _DSI_CLK_TIMING_PARAM_0 0x6b080 +#define _DSI_CLK_TIMING_PARAM_1 0x6b880 +#define DSI_CLK_TIMING_PARAM(port) _MMIO_PORT(port, \ + _DSI_CLK_TIMING_PARAM_0,\ + _DSI_CLK_TIMING_PARAM_1) +#define CLK_PREPARE_OVERRIDE (1 << 31) +#define CLK_PREPARE(x) ((x) << 28) +#define CLK_PREPARE_MASK (0x7 << 28) +#define CLK_PREPARE_SHIFT 28 +#define CLK_ZERO_OVERRIDE (1 << 27) +#define CLK_ZERO(x) ((x) << 20) +#define CLK_ZERO_MASK (0xf << 20) +#define CLK_ZERO_SHIFT 20 +#define CLK_PRE_OVERRIDE (1 << 19) +#define CLK_PRE(x) ((x) << 16) +#define CLK_PRE_MASK (0x3 << 16) +#define CLK_PRE_SHIFT 16 +#define CLK_POST_OVERRIDE (1 << 15) +#define CLK_POST(x) ((x) << 8) +#define CLK_POST_MASK (0x7 << 8) +#define CLK_POST_SHIFT 8 +#define CLK_TRAIL_OVERRIDE (1 << 7) +#define CLK_TRAIL(x) ((x) << 0) +#define CLK_TRAIL_MASK (0xf << 0) +#define CLK_TRAIL_SHIFT 0 + +#define _DPHY_DATA_TIMING_PARAM_0 0x162184 +#define _DPHY_DATA_TIMING_PARAM_1 0x6c184 +#define DPHY_DATA_TIMING_PARAM(port) _MMIO_PORT(port, \ + _DPHY_DATA_TIMING_PARAM_0,\ + _DPHY_DATA_TIMING_PARAM_1) +#define _DSI_DATA_TIMING_PARAM_0 0x6B084 +#define _DSI_DATA_TIMING_PARAM_1 0x6B884 +#define DSI_DATA_TIMING_PARAM(port) _MMIO_PORT(port, \ + _DSI_DATA_TIMING_PARAM_0,\ + _DSI_DATA_TIMING_PARAM_1) +#define HS_PREPARE_OVERRIDE (1 << 31) +#define HS_PREPARE(x) ((x) << 24) +#define HS_PREPARE_MASK (0x7 << 24) +#define HS_PREPARE_SHIFT 24 +#define HS_ZERO_OVERRIDE (1 << 23) +#define HS_ZERO(x) ((x) << 16) +#define HS_ZERO_MASK (0xf << 16) +#define HS_ZERO_SHIFT 16 +#define HS_TRAIL_OVERRIDE (1 << 15) +#define HS_TRAIL(x) ((x) << 8) +#define HS_TRAIL_MASK (0x7 << 8) +#define HS_TRAIL_SHIFT 8 +#define HS_EXIT_OVERRIDE (1 << 7) +#define HS_EXIT(x) ((x) << 0) +#define HS_EXIT_MASK (0x7 << 0) +#define HS_EXIT_SHIFT 0 + +#define _DPHY_TA_TIMING_PARAM_0 0x162188 +#define _DPHY_TA_TIMING_PARAM_1 0x6c188 +#define DPHY_TA_TIMING_PARAM(port) _MMIO_PORT(port, \ + _DPHY_TA_TIMING_PARAM_0,\ + _DPHY_TA_TIMING_PARAM_1) +#define _DSI_TA_TIMING_PARAM_0 0x6b098 +#define _DSI_TA_TIMING_PARAM_1 0x6b898 +#define DSI_TA_TIMING_PARAM(port) _MMIO_PORT(port, \ + _DSI_TA_TIMING_PARAM_0,\ + _DSI_TA_TIMING_PARAM_1) +#define TA_SURE_OVERRIDE (1 << 31) +#define TA_SURE(x) ((x) << 16) +#define TA_SURE_MASK (0x1f << 16) +#define TA_SURE_SHIFT 16 +#define TA_GO_OVERRIDE (1 << 15) +#define TA_GO(x) ((x) << 8) +#define TA_GO_MASK (0xf << 8) +#define TA_GO_SHIFT 8 +#define TA_GET_OVERRIDE (1 << 7) +#define TA_GET(x) ((x) << 0) +#define TA_GET_MASK (0xf << 0) +#define TA_GET_SHIFT 0 + +/* DSI transcoder configuration */ +#define _DSI_TRANS_FUNC_CONF_0 0x6b030 +#define _DSI_TRANS_FUNC_CONF_1 0x6b830 +#define DSI_TRANS_FUNC_CONF(tc) _MMIO_DSI(tc, \ + _DSI_TRANS_FUNC_CONF_0,\ + _DSI_TRANS_FUNC_CONF_1) +#define OP_MODE_MASK (0x3 << 28) +#define OP_MODE_SHIFT 28 +#define CMD_MODE_NO_GATE (0x0 << 28) +#define CMD_MODE_TE_GATE (0x1 << 28) +#define VIDEO_MODE_SYNC_EVENT (0x2 << 28) +#define VIDEO_MODE_SYNC_PULSE (0x3 << 28) +#define TE_SOURCE_GPIO (1 << 27) +#define LINK_READY (1 << 20) +#define PIX_FMT_MASK (0x3 << 16) +#define PIX_FMT_SHIFT 16 +#define PIX_FMT_RGB565 (0x0 << 16) +#define PIX_FMT_RGB666_PACKED (0x1 << 16) +#define PIX_FMT_RGB666_LOOSE (0x2 << 16) +#define PIX_FMT_RGB888 (0x3 << 16) +#define PIX_FMT_RGB101010 (0x4 << 16) +#define PIX_FMT_RGB121212 (0x5 << 16) +#define PIX_FMT_COMPRESSED (0x6 << 16) +#define BGR_TRANSMISSION (1 << 15) +#define PIX_VIRT_CHAN(x) ((x) << 12) +#define PIX_VIRT_CHAN_MASK (0x3 << 12) +#define PIX_VIRT_CHAN_SHIFT 12 +#define PIX_BUF_THRESHOLD_MASK (0x3 << 10) +#define PIX_BUF_THRESHOLD_SHIFT 10 +#define PIX_BUF_THRESHOLD_1_4 (0x0 << 10) +#define PIX_BUF_THRESHOLD_1_2 (0x1 << 10) +#define PIX_BUF_THRESHOLD_3_4 (0x2 << 10) +#define PIX_BUF_THRESHOLD_FULL (0x3 << 10) +#define CONTINUOUS_CLK_MASK (0x3 << 8) +#define CONTINUOUS_CLK_SHIFT 8 +#define CLK_ENTER_LP_AFTER_DATA (0x0 << 8) +#define CLK_HS_OR_LP (0x2 << 8) +#define CLK_HS_CONTINUOUS (0x3 << 8) +#define LINK_CALIBRATION_MASK (0x3 << 4) +#define LINK_CALIBRATION_SHIFT 4 +#define CALIBRATION_DISABLED (0x0 << 4) +#define CALIBRATION_ENABLED_INITIAL_ONLY (0x2 << 4) +#define CALIBRATION_ENABLED_INITIAL_PERIODIC (0x3 << 4) +#define BLANKING_PACKET_ENABLE (1 << 2) +#define S3D_ORIENTATION_LANDSCAPE (1 << 1) +#define EOTP_DISABLED (1 << 0) + +#define _DSI_CMD_RXCTL_0 0x6b0d4 +#define _DSI_CMD_RXCTL_1 0x6b8d4 +#define DSI_CMD_RXCTL(tc) _MMIO_DSI(tc, \ + _DSI_CMD_RXCTL_0,\ + _DSI_CMD_RXCTL_1) +#define READ_UNLOADS_DW (1 << 16) +#define RECEIVED_UNASSIGNED_TRIGGER (1 << 15) +#define RECEIVED_ACKNOWLEDGE_TRIGGER (1 << 14) +#define RECEIVED_TEAR_EFFECT_TRIGGER (1 << 13) +#define RECEIVED_RESET_TRIGGER (1 << 12) +#define RECEIVED_PAYLOAD_WAS_LOST (1 << 11) +#define RECEIVED_CRC_WAS_LOST (1 << 10) +#define NUMBER_RX_PLOAD_DW_MASK (0xff << 0) +#define NUMBER_RX_PLOAD_DW_SHIFT 0 + +#define _DSI_CMD_TXCTL_0 0x6b0d0 +#define _DSI_CMD_TXCTL_1 0x6b8d0 +#define DSI_CMD_TXCTL(tc) _MMIO_DSI(tc, \ + _DSI_CMD_TXCTL_0,\ + _DSI_CMD_TXCTL_1) +#define KEEP_LINK_IN_HS (1 << 24) +#define FREE_HEADER_CREDIT_MASK (0x1f << 8) +#define FREE_HEADER_CREDIT_SHIFT 0x8 +#define FREE_PLOAD_CREDIT_MASK (0xff << 0) +#define FREE_PLOAD_CREDIT_SHIFT 0 +#define MAX_HEADER_CREDIT 0x10 +#define MAX_PLOAD_CREDIT 0x40 + +#define _DSI_CMD_TXHDR_0 0x6b100 +#define _DSI_CMD_TXHDR_1 0x6b900 +#define DSI_CMD_TXHDR(tc) _MMIO_DSI(tc, \ + _DSI_CMD_TXHDR_0,\ + _DSI_CMD_TXHDR_1) +#define PAYLOAD_PRESENT (1 << 31) +#define LP_DATA_TRANSFER (1 << 30) +#define VBLANK_FENCE (1 << 29) +#define PARAM_WC_MASK (0xffff << 8) +#define PARAM_WC_LOWER_SHIFT 8 +#define PARAM_WC_UPPER_SHIFT 16 +#define VC_MASK (0x3 << 6) +#define VC_SHIFT 6 +#define DT_MASK (0x3f << 0) +#define DT_SHIFT 0 + +#define _DSI_CMD_TXPYLD_0 0x6b104 +#define _DSI_CMD_TXPYLD_1 0x6b904 +#define DSI_CMD_TXPYLD(tc) _MMIO_DSI(tc, \ + _DSI_CMD_TXPYLD_0,\ + _DSI_CMD_TXPYLD_1) + +#define _DSI_LP_MSG_0 0x6b0d8 +#define _DSI_LP_MSG_1 0x6b8d8 +#define DSI_LP_MSG(tc) _MMIO_DSI(tc, \ + _DSI_LP_MSG_0,\ + _DSI_LP_MSG_1) +#define LPTX_IN_PROGRESS (1 << 17) +#define LINK_IN_ULPS (1 << 16) +#define LINK_ULPS_TYPE_LP11 (1 << 8) +#define LINK_ENTER_ULPS (1 << 0) + +/* DSI timeout registers */ +#define _DSI_HSTX_TO_0 0x6b044 +#define _DSI_HSTX_TO_1 0x6b844 +#define DSI_HSTX_TO(tc) _MMIO_DSI(tc, \ + _DSI_HSTX_TO_0,\ + _DSI_HSTX_TO_1) +#define HSTX_TIMEOUT_VALUE_MASK (0xffff << 16) +#define HSTX_TIMEOUT_VALUE_SHIFT 16 +#define HSTX_TIMEOUT_VALUE(x) ((x) << 16) +#define HSTX_TIMED_OUT (1 << 0) + +#define _DSI_LPRX_HOST_TO_0 0x6b048 +#define _DSI_LPRX_HOST_TO_1 0x6b848 +#define DSI_LPRX_HOST_TO(tc) _MMIO_DSI(tc, \ + _DSI_LPRX_HOST_TO_0,\ + _DSI_LPRX_HOST_TO_1) +#define LPRX_TIMED_OUT (1 << 16) +#define LPRX_TIMEOUT_VALUE_MASK (0xffff << 0) +#define LPRX_TIMEOUT_VALUE_SHIFT 0 +#define LPRX_TIMEOUT_VALUE(x) ((x) << 0) + +#define _DSI_PWAIT_TO_0 0x6b040 +#define _DSI_PWAIT_TO_1 0x6b840 +#define DSI_PWAIT_TO(tc) _MMIO_DSI(tc, \ + _DSI_PWAIT_TO_0,\ + _DSI_PWAIT_TO_1) +#define PRESET_TIMEOUT_VALUE_MASK (0xffff << 16) +#define PRESET_TIMEOUT_VALUE_SHIFT 16 +#define PRESET_TIMEOUT_VALUE(x) ((x) << 16) +#define PRESPONSE_TIMEOUT_VALUE_MASK (0xffff << 0) +#define PRESPONSE_TIMEOUT_VALUE_SHIFT 0 +#define PRESPONSE_TIMEOUT_VALUE(x) ((x) << 0) + +#define _DSI_TA_TO_0 0x6b04c +#define _DSI_TA_TO_1 0x6b84c +#define DSI_TA_TO(tc) _MMIO_DSI(tc, \ + _DSI_TA_TO_0,\ + _DSI_TA_TO_1) +#define TA_TIMED_OUT (1 << 16) +#define TA_TIMEOUT_VALUE_MASK (0xffff << 0) +#define TA_TIMEOUT_VALUE_SHIFT 0 +#define TA_TIMEOUT_VALUE(x) ((x) << 0) + +#endif /* __ICL_DSI_REGS_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_acpi.c b/drivers/gpu/drm/i915/display/intel_acpi.c new file mode 100644 index 000000000..9df78e7ca --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_acpi.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel ACPI functions + * + * _DSM related code stolen from nouveau_acpi.c. + */ + +#include +#include +#include + +#include "i915_drv.h" +#include "intel_acpi.h" +#include "intel_display_types.h" + +#define INTEL_DSM_REVISION_ID 1 /* For Calpella anyway... */ +#define INTEL_DSM_FN_PLATFORM_MUX_INFO 1 /* No args */ + +static const guid_t intel_dsm_guid = + GUID_INIT(0x7ed873d3, 0xc2d0, 0x4e4f, + 0xa8, 0x54, 0x0f, 0x13, 0x17, 0xb0, 0x1c, 0x2c); + +#define INTEL_DSM_FN_GET_BIOS_DATA_FUNCS_SUPPORTED 0 /* No args */ + +static const guid_t intel_dsm_guid2 = + GUID_INIT(0x3e5b41c6, 0xeb1d, 0x4260, + 0x9d, 0x15, 0xc7, 0x1f, 0xba, 0xda, 0xe4, 0x14); + +static char *intel_dsm_port_name(u8 id) +{ + switch (id) { + case 0: + return "Reserved"; + case 1: + return "Analog VGA"; + case 2: + return "LVDS"; + case 3: + return "Reserved"; + case 4: + return "HDMI/DVI_B"; + case 5: + return "HDMI/DVI_C"; + case 6: + return "HDMI/DVI_D"; + case 7: + return "DisplayPort_A"; + case 8: + return "DisplayPort_B"; + case 9: + return "DisplayPort_C"; + case 0xa: + return "DisplayPort_D"; + case 0xb: + case 0xc: + case 0xd: + return "Reserved"; + case 0xe: + return "WiDi"; + default: + return "bad type"; + } +} + +static char *intel_dsm_mux_type(u8 type) +{ + switch (type) { + case 0: + return "unknown"; + case 1: + return "No MUX, iGPU only"; + case 2: + return "No MUX, dGPU only"; + case 3: + return "MUXed between iGPU and dGPU"; + default: + return "bad type"; + } +} + +static void intel_dsm_platform_mux_info(acpi_handle dhandle) +{ + int i; + union acpi_object *pkg, *connector_count; + + pkg = acpi_evaluate_dsm_typed(dhandle, &intel_dsm_guid, + INTEL_DSM_REVISION_ID, INTEL_DSM_FN_PLATFORM_MUX_INFO, + NULL, ACPI_TYPE_PACKAGE); + if (!pkg) { + DRM_DEBUG_DRIVER("failed to evaluate _DSM\n"); + return; + } + + if (!pkg->package.count) { + DRM_DEBUG_DRIVER("no connection in _DSM\n"); + return; + } + + connector_count = &pkg->package.elements[0]; + DRM_DEBUG_DRIVER("MUX info connectors: %lld\n", + (unsigned long long)connector_count->integer.value); + for (i = 1; i < pkg->package.count; i++) { + union acpi_object *obj = &pkg->package.elements[i]; + union acpi_object *connector_id; + union acpi_object *info; + + if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count < 2) { + DRM_DEBUG_DRIVER("Invalid object for MUX #%d\n", i); + continue; + } + + connector_id = &obj->package.elements[0]; + info = &obj->package.elements[1]; + if (info->type != ACPI_TYPE_BUFFER || info->buffer.length < 4) { + DRM_DEBUG_DRIVER("Invalid info for MUX obj #%d\n", i); + continue; + } + + DRM_DEBUG_DRIVER("Connector id: 0x%016llx\n", + (unsigned long long)connector_id->integer.value); + DRM_DEBUG_DRIVER(" port id: %s\n", + intel_dsm_port_name(info->buffer.pointer[0])); + DRM_DEBUG_DRIVER(" display mux info: %s\n", + intel_dsm_mux_type(info->buffer.pointer[1])); + DRM_DEBUG_DRIVER(" aux/dc mux info: %s\n", + intel_dsm_mux_type(info->buffer.pointer[2])); + DRM_DEBUG_DRIVER(" hpd mux info: %s\n", + intel_dsm_mux_type(info->buffer.pointer[3])); + } + + ACPI_FREE(pkg); +} + +static acpi_handle intel_dsm_pci_probe(struct pci_dev *pdev) +{ + acpi_handle dhandle; + + dhandle = ACPI_HANDLE(&pdev->dev); + if (!dhandle) + return NULL; + + if (!acpi_check_dsm(dhandle, &intel_dsm_guid, INTEL_DSM_REVISION_ID, + 1 << INTEL_DSM_FN_PLATFORM_MUX_INFO)) { + DRM_DEBUG_KMS("no _DSM method for intel device\n"); + return NULL; + } + + intel_dsm_platform_mux_info(dhandle); + + return dhandle; +} + +static bool intel_dsm_detect(void) +{ + acpi_handle dhandle = NULL; + char acpi_method_name[255] = { 0 }; + struct acpi_buffer buffer = {sizeof(acpi_method_name), acpi_method_name}; + struct pci_dev *pdev = NULL; + int vga_count = 0; + + while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev)) != NULL) { + vga_count++; + dhandle = intel_dsm_pci_probe(pdev) ?: dhandle; + } + + if (vga_count == 2 && dhandle) { + acpi_get_name(dhandle, ACPI_FULL_PATHNAME, &buffer); + DRM_DEBUG_DRIVER("vga_switcheroo: detected DSM switching method %s handle\n", + acpi_method_name); + return true; + } + + return false; +} + +void intel_register_dsm_handler(void) +{ + if (!intel_dsm_detect()) + return; +} + +void intel_unregister_dsm_handler(void) +{ +} + +void intel_dsm_get_bios_data_funcs_supported(struct drm_i915_private *i915) +{ + struct pci_dev *pdev = to_pci_dev(i915->drm.dev); + acpi_handle dhandle; + union acpi_object *obj; + + dhandle = ACPI_HANDLE(&pdev->dev); + if (!dhandle) + return; + + obj = acpi_evaluate_dsm(dhandle, &intel_dsm_guid2, INTEL_DSM_REVISION_ID, + INTEL_DSM_FN_GET_BIOS_DATA_FUNCS_SUPPORTED, NULL); + if (obj) + ACPI_FREE(obj); +} + +/* + * ACPI Specification, Revision 5.0, Appendix B.3.2 _DOD (Enumerate All Devices + * Attached to the Display Adapter). + */ +#define ACPI_DISPLAY_INDEX_SHIFT 0 +#define ACPI_DISPLAY_INDEX_MASK (0xf << 0) +#define ACPI_DISPLAY_PORT_ATTACHMENT_SHIFT 4 +#define ACPI_DISPLAY_PORT_ATTACHMENT_MASK (0xf << 4) +#define ACPI_DISPLAY_TYPE_SHIFT 8 +#define ACPI_DISPLAY_TYPE_MASK (0xf << 8) +#define ACPI_DISPLAY_TYPE_OTHER (0 << 8) +#define ACPI_DISPLAY_TYPE_VGA (1 << 8) +#define ACPI_DISPLAY_TYPE_TV (2 << 8) +#define ACPI_DISPLAY_TYPE_EXTERNAL_DIGITAL (3 << 8) +#define ACPI_DISPLAY_TYPE_INTERNAL_DIGITAL (4 << 8) +#define ACPI_VENDOR_SPECIFIC_SHIFT 12 +#define ACPI_VENDOR_SPECIFIC_MASK (0xf << 12) +#define ACPI_BIOS_CAN_DETECT (1 << 16) +#define ACPI_DEPENDS_ON_VGA (1 << 17) +#define ACPI_PIPE_ID_SHIFT 18 +#define ACPI_PIPE_ID_MASK (7 << 18) +#define ACPI_DEVICE_ID_SCHEME (1ULL << 31) + +static u32 acpi_display_type(struct intel_connector *connector) +{ + u32 display_type; + + switch (connector->base.connector_type) { + case DRM_MODE_CONNECTOR_VGA: + case DRM_MODE_CONNECTOR_DVIA: + display_type = ACPI_DISPLAY_TYPE_VGA; + break; + case DRM_MODE_CONNECTOR_Composite: + case DRM_MODE_CONNECTOR_SVIDEO: + case DRM_MODE_CONNECTOR_Component: + case DRM_MODE_CONNECTOR_9PinDIN: + case DRM_MODE_CONNECTOR_TV: + display_type = ACPI_DISPLAY_TYPE_TV; + break; + case DRM_MODE_CONNECTOR_DVII: + case DRM_MODE_CONNECTOR_DVID: + case DRM_MODE_CONNECTOR_DisplayPort: + case DRM_MODE_CONNECTOR_HDMIA: + case DRM_MODE_CONNECTOR_HDMIB: + display_type = ACPI_DISPLAY_TYPE_EXTERNAL_DIGITAL; + break; + case DRM_MODE_CONNECTOR_LVDS: + case DRM_MODE_CONNECTOR_eDP: + case DRM_MODE_CONNECTOR_DSI: + display_type = ACPI_DISPLAY_TYPE_INTERNAL_DIGITAL; + break; + case DRM_MODE_CONNECTOR_Unknown: + case DRM_MODE_CONNECTOR_VIRTUAL: + display_type = ACPI_DISPLAY_TYPE_OTHER; + break; + default: + MISSING_CASE(connector->base.connector_type); + display_type = ACPI_DISPLAY_TYPE_OTHER; + break; + } + + return display_type; +} + +void intel_acpi_device_id_update(struct drm_i915_private *dev_priv) +{ + struct drm_device *drm_dev = &dev_priv->drm; + struct intel_connector *connector; + struct drm_connector_list_iter conn_iter; + u8 display_index[16] = {}; + + /* Populate the ACPI IDs for all connectors for a given drm_device */ + drm_connector_list_iter_begin(drm_dev, &conn_iter); + for_each_intel_connector_iter(connector, &conn_iter) { + u32 device_id, type; + + device_id = acpi_display_type(connector); + + /* Use display type specific display index. */ + type = (device_id & ACPI_DISPLAY_TYPE_MASK) + >> ACPI_DISPLAY_TYPE_SHIFT; + device_id |= display_index[type]++ << ACPI_DISPLAY_INDEX_SHIFT; + + connector->acpi_device_id = device_id; + } + drm_connector_list_iter_end(&conn_iter); +} + +/* NOTE: The connector order must be final before this is called. */ +void intel_acpi_assign_connector_fwnodes(struct drm_i915_private *i915) +{ + struct drm_connector_list_iter conn_iter; + struct drm_device *drm_dev = &i915->drm; + struct fwnode_handle *fwnode = NULL; + struct drm_connector *connector; + struct acpi_device *adev; + + drm_connector_list_iter_begin(drm_dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + /* Always getting the next, even when the last was not used. */ + fwnode = device_get_next_child_node(drm_dev->dev, fwnode); + if (!fwnode) + break; + + switch (connector->connector_type) { + case DRM_MODE_CONNECTOR_LVDS: + case DRM_MODE_CONNECTOR_eDP: + case DRM_MODE_CONNECTOR_DSI: + /* + * Integrated displays have a specific address 0x1f on + * most Intel platforms, but not on all of them. + */ + adev = acpi_find_child_device(ACPI_COMPANION(drm_dev->dev), + 0x1f, 0); + if (adev) { + connector->fwnode = + fwnode_handle_get(acpi_fwnode_handle(adev)); + break; + } + fallthrough; + default: + connector->fwnode = fwnode_handle_get(fwnode); + break; + } + } + drm_connector_list_iter_end(&conn_iter); + /* + * device_get_next_child_node() takes a reference on the fwnode, if + * we stopped iterating because we are out of connectors we need to + * put this, otherwise fwnode is NULL and the put is a no-op. + */ + fwnode_handle_put(fwnode); +} + +void intel_acpi_video_register(struct drm_i915_private *i915) +{ + struct drm_connector_list_iter conn_iter; + struct drm_connector *connector; + + acpi_video_register(); + + /* + * If i915 is driving an internal panel without registering its native + * backlight handler try to register the acpi_video backlight. + * For panels not driven by i915 another GPU driver may still register + * a native backlight later and acpi_video_register_backlight() should + * only be called after any native backlights have been registered. + */ + drm_connector_list_iter_begin(&i915->drm, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + struct intel_panel *panel = &to_intel_connector(connector)->panel; + + if (panel->backlight.funcs && !panel->backlight.device) { + acpi_video_register_backlight(); + break; + } + } + drm_connector_list_iter_end(&conn_iter); +} diff --git a/drivers/gpu/drm/i915/display/intel_acpi.h b/drivers/gpu/drm/i915/display/intel_acpi.h new file mode 100644 index 000000000..6a0007452 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_acpi.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_ACPI_H__ +#define __INTEL_ACPI_H__ + +struct drm_i915_private; + +#ifdef CONFIG_ACPI +void intel_register_dsm_handler(void); +void intel_unregister_dsm_handler(void); +void intel_dsm_get_bios_data_funcs_supported(struct drm_i915_private *i915); +void intel_acpi_device_id_update(struct drm_i915_private *i915); +void intel_acpi_assign_connector_fwnodes(struct drm_i915_private *i915); +void intel_acpi_video_register(struct drm_i915_private *i915); +#else +static inline void intel_register_dsm_handler(void) { return; } +static inline void intel_unregister_dsm_handler(void) { return; } +static inline +void intel_dsm_get_bios_data_funcs_supported(struct drm_i915_private *i915) { return; } +static inline +void intel_acpi_device_id_update(struct drm_i915_private *i915) { return; } +static inline +void intel_acpi_assign_connector_fwnodes(struct drm_i915_private *i915) { return; } +static inline +void intel_acpi_video_register(struct drm_i915_private *i915) { return; } +#endif /* CONFIG_ACPI */ + +#endif /* __INTEL_ACPI_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_atomic.c b/drivers/gpu/drm/i915/display/intel_atomic.c new file mode 100644 index 000000000..a502af0b6 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_atomic.c @@ -0,0 +1,351 @@ +/* + * Copyright © 2015 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/** + * DOC: atomic modeset support + * + * The functions here implement the state management and hardware programming + * dispatch required by the atomic modeset infrastructure. + * See intel_atomic_plane.c for the plane-specific atomic functionality. + */ + +#include +#include +#include + +#include "i915_drv.h" +#include "i915_reg.h" +#include "intel_atomic.h" +#include "intel_cdclk.h" +#include "intel_display_types.h" +#include "intel_global_state.h" +#include "intel_hdcp.h" +#include "intel_psr.h" +#include "intel_fb.h" +#include "skl_universal_plane.h" + +/** + * intel_digital_connector_atomic_get_property - hook for connector->atomic_get_property. + * @connector: Connector to get the property for. + * @state: Connector state to retrieve the property from. + * @property: Property to retrieve. + * @val: Return value for the property. + * + * Returns the atomic property value for a digital connector. + */ +int intel_digital_connector_atomic_get_property(struct drm_connector *connector, + const struct drm_connector_state *state, + struct drm_property *property, + u64 *val) +{ + struct drm_device *dev = connector->dev; + struct drm_i915_private *dev_priv = to_i915(dev); + struct intel_digital_connector_state *intel_conn_state = + to_intel_digital_connector_state(state); + + if (property == dev_priv->display.properties.force_audio) + *val = intel_conn_state->force_audio; + else if (property == dev_priv->display.properties.broadcast_rgb) + *val = intel_conn_state->broadcast_rgb; + else { + drm_dbg_atomic(&dev_priv->drm, + "Unknown property [PROP:%d:%s]\n", + property->base.id, property->name); + return -EINVAL; + } + + return 0; +} + +/** + * intel_digital_connector_atomic_set_property - hook for connector->atomic_set_property. + * @connector: Connector to set the property for. + * @state: Connector state to set the property on. + * @property: Property to set. + * @val: New value for the property. + * + * Sets the atomic property value for a digital connector. + */ +int intel_digital_connector_atomic_set_property(struct drm_connector *connector, + struct drm_connector_state *state, + struct drm_property *property, + u64 val) +{ + struct drm_device *dev = connector->dev; + struct drm_i915_private *dev_priv = to_i915(dev); + struct intel_digital_connector_state *intel_conn_state = + to_intel_digital_connector_state(state); + + if (property == dev_priv->display.properties.force_audio) { + intel_conn_state->force_audio = val; + return 0; + } + + if (property == dev_priv->display.properties.broadcast_rgb) { + intel_conn_state->broadcast_rgb = val; + return 0; + } + + drm_dbg_atomic(&dev_priv->drm, "Unknown property [PROP:%d:%s]\n", + property->base.id, property->name); + return -EINVAL; +} + +int intel_digital_connector_atomic_check(struct drm_connector *conn, + struct drm_atomic_state *state) +{ + struct drm_connector_state *new_state = + drm_atomic_get_new_connector_state(state, conn); + struct intel_digital_connector_state *new_conn_state = + to_intel_digital_connector_state(new_state); + struct drm_connector_state *old_state = + drm_atomic_get_old_connector_state(state, conn); + struct intel_digital_connector_state *old_conn_state = + to_intel_digital_connector_state(old_state); + struct drm_crtc_state *crtc_state; + + intel_hdcp_atomic_check(conn, old_state, new_state); + + if (!new_state->crtc) + return 0; + + crtc_state = drm_atomic_get_new_crtc_state(state, new_state->crtc); + + /* + * These properties are handled by fastset, and might not end + * up in a modeset. + */ + if (new_conn_state->force_audio != old_conn_state->force_audio || + new_conn_state->broadcast_rgb != old_conn_state->broadcast_rgb || + new_conn_state->base.colorspace != old_conn_state->base.colorspace || + new_conn_state->base.picture_aspect_ratio != old_conn_state->base.picture_aspect_ratio || + new_conn_state->base.content_type != old_conn_state->base.content_type || + new_conn_state->base.scaling_mode != old_conn_state->base.scaling_mode || + new_conn_state->base.privacy_screen_sw_state != old_conn_state->base.privacy_screen_sw_state || + !drm_connector_atomic_hdr_metadata_equal(old_state, new_state)) + crtc_state->mode_changed = true; + + return 0; +} + +/** + * intel_digital_connector_duplicate_state - duplicate connector state + * @connector: digital connector + * + * Allocates and returns a copy of the connector state (both common and + * digital connector specific) for the specified connector. + * + * Returns: The newly allocated connector state, or NULL on failure. + */ +struct drm_connector_state * +intel_digital_connector_duplicate_state(struct drm_connector *connector) +{ + struct intel_digital_connector_state *state; + + state = kmemdup(connector->state, sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + + __drm_atomic_helper_connector_duplicate_state(connector, &state->base); + return &state->base; +} + +/** + * intel_connector_needs_modeset - check if connector needs a modeset + * @state: the atomic state corresponding to this modeset + * @connector: the connector + */ +bool +intel_connector_needs_modeset(struct intel_atomic_state *state, + struct drm_connector *connector) +{ + const struct drm_connector_state *old_conn_state, *new_conn_state; + + old_conn_state = drm_atomic_get_old_connector_state(&state->base, connector); + new_conn_state = drm_atomic_get_new_connector_state(&state->base, connector); + + return old_conn_state->crtc != new_conn_state->crtc || + (new_conn_state->crtc && + drm_atomic_crtc_needs_modeset(drm_atomic_get_new_crtc_state(&state->base, + new_conn_state->crtc))); +} + +/** + * intel_any_crtc_needs_modeset - check if any CRTC needs a modeset + * @state: the atomic state corresponding to this modeset + * + * Returns true if any CRTC in @state needs a modeset. + */ +bool intel_any_crtc_needs_modeset(struct intel_atomic_state *state) +{ + struct intel_crtc *crtc; + struct intel_crtc_state *crtc_state; + int i; + + for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) { + if (intel_crtc_needs_modeset(crtc_state)) + return true; + } + + return false; +} + +struct intel_digital_connector_state * +intel_atomic_get_digital_connector_state(struct intel_atomic_state *state, + struct intel_connector *connector) +{ + struct drm_connector_state *conn_state; + + conn_state = drm_atomic_get_connector_state(&state->base, + &connector->base); + if (IS_ERR(conn_state)) + return ERR_CAST(conn_state); + + return to_intel_digital_connector_state(conn_state); +} + +/** + * intel_crtc_duplicate_state - duplicate crtc state + * @crtc: drm crtc + * + * Allocates and returns a copy of the crtc state (both common and + * Intel-specific) for the specified crtc. + * + * Returns: The newly allocated crtc state, or NULL on failure. + */ +struct drm_crtc_state * +intel_crtc_duplicate_state(struct drm_crtc *crtc) +{ + const struct intel_crtc_state *old_crtc_state = to_intel_crtc_state(crtc->state); + struct intel_crtc_state *crtc_state; + + crtc_state = kmemdup(old_crtc_state, sizeof(*crtc_state), GFP_KERNEL); + if (!crtc_state) + return NULL; + + __drm_atomic_helper_crtc_duplicate_state(crtc, &crtc_state->uapi); + + /* copy color blobs */ + if (crtc_state->hw.degamma_lut) + drm_property_blob_get(crtc_state->hw.degamma_lut); + if (crtc_state->hw.ctm) + drm_property_blob_get(crtc_state->hw.ctm); + if (crtc_state->hw.gamma_lut) + drm_property_blob_get(crtc_state->hw.gamma_lut); + + crtc_state->update_pipe = false; + crtc_state->disable_lp_wm = false; + crtc_state->disable_cxsr = false; + crtc_state->update_wm_pre = false; + crtc_state->update_wm_post = false; + crtc_state->fifo_changed = false; + crtc_state->preload_luts = false; + crtc_state->inherited = false; + crtc_state->wm.need_postvbl_update = false; + crtc_state->do_async_flip = false; + crtc_state->fb_bits = 0; + crtc_state->update_planes = 0; + crtc_state->dsb = NULL; + + return &crtc_state->uapi; +} + +static void intel_crtc_put_color_blobs(struct intel_crtc_state *crtc_state) +{ + drm_property_blob_put(crtc_state->hw.degamma_lut); + drm_property_blob_put(crtc_state->hw.gamma_lut); + drm_property_blob_put(crtc_state->hw.ctm); +} + +void intel_crtc_free_hw_state(struct intel_crtc_state *crtc_state) +{ + intel_crtc_put_color_blobs(crtc_state); +} + +/** + * intel_crtc_destroy_state - destroy crtc state + * @crtc: drm crtc + * @state: the state to destroy + * + * Destroys the crtc state (both common and Intel-specific) for the + * specified crtc. + */ +void +intel_crtc_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct intel_crtc_state *crtc_state = to_intel_crtc_state(state); + + drm_WARN_ON(crtc->dev, crtc_state->dsb); + + __drm_atomic_helper_crtc_destroy_state(&crtc_state->uapi); + intel_crtc_free_hw_state(crtc_state); + kfree(crtc_state); +} + +struct drm_atomic_state * +intel_atomic_state_alloc(struct drm_device *dev) +{ + struct intel_atomic_state *state = kzalloc(sizeof(*state), GFP_KERNEL); + + if (!state || drm_atomic_state_init(dev, &state->base) < 0) { + kfree(state); + return NULL; + } + + return &state->base; +} + +void intel_atomic_state_free(struct drm_atomic_state *_state) +{ + struct intel_atomic_state *state = to_intel_atomic_state(_state); + + drm_atomic_state_default_release(&state->base); + kfree(state->global_objs); + + i915_sw_fence_fini(&state->commit_ready); + + kfree(state); +} + +void intel_atomic_state_clear(struct drm_atomic_state *s) +{ + struct intel_atomic_state *state = to_intel_atomic_state(s); + + drm_atomic_state_default_clear(&state->base); + intel_atomic_clear_global_state(state); + + state->dpll_set = state->modeset = false; +} + +struct intel_crtc_state * +intel_atomic_get_crtc_state(struct drm_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_crtc_state *crtc_state; + crtc_state = drm_atomic_get_crtc_state(state, &crtc->base); + if (IS_ERR(crtc_state)) + return ERR_CAST(crtc_state); + + return to_intel_crtc_state(crtc_state); +} diff --git a/drivers/gpu/drm/i915/display/intel_atomic.h b/drivers/gpu/drm/i915/display/intel_atomic.h new file mode 100644 index 000000000..e506f6a87 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_atomic.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_ATOMIC_H__ +#define __INTEL_ATOMIC_H__ + +#include + +struct drm_atomic_state; +struct drm_connector; +struct drm_connector_state; +struct drm_crtc; +struct drm_crtc_state; +struct drm_device; +struct drm_i915_private; +struct drm_property; +struct intel_atomic_state; +struct intel_connector; +struct intel_crtc; +struct intel_crtc_state; + +int intel_digital_connector_atomic_get_property(struct drm_connector *connector, + const struct drm_connector_state *state, + struct drm_property *property, + u64 *val); +int intel_digital_connector_atomic_set_property(struct drm_connector *connector, + struct drm_connector_state *state, + struct drm_property *property, + u64 val); +int intel_digital_connector_atomic_check(struct drm_connector *conn, + struct drm_atomic_state *state); +struct drm_connector_state * +intel_digital_connector_duplicate_state(struct drm_connector *connector); +bool intel_connector_needs_modeset(struct intel_atomic_state *state, + struct drm_connector *connector); +bool intel_any_crtc_needs_modeset(struct intel_atomic_state *state); +struct intel_digital_connector_state * +intel_atomic_get_digital_connector_state(struct intel_atomic_state *state, + struct intel_connector *connector); + +struct drm_crtc_state *intel_crtc_duplicate_state(struct drm_crtc *crtc); +void intel_crtc_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state); +void intel_crtc_free_hw_state(struct intel_crtc_state *crtc_state); +struct drm_atomic_state *intel_atomic_state_alloc(struct drm_device *dev); +void intel_atomic_state_free(struct drm_atomic_state *state); +void intel_atomic_state_clear(struct drm_atomic_state *state); + +struct intel_crtc_state * +intel_atomic_get_crtc_state(struct drm_atomic_state *state, + struct intel_crtc *crtc); + +#endif /* __INTEL_ATOMIC_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_atomic_plane.c b/drivers/gpu/drm/i915/display/intel_atomic_plane.c new file mode 100644 index 000000000..82826454b --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_atomic_plane.c @@ -0,0 +1,1120 @@ +/* + * Copyright © 2014 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/** + * DOC: atomic plane helpers + * + * The functions here are used by the atomic plane helper functions to + * implement legacy plane updates (i.e., drm_plane->update_plane() and + * drm_plane->disable_plane()). This allows plane updates to use the + * atomic state infrastructure and perform plane updates as separate + * prepare/check/commit/cleanup steps. + */ + +#include +#include + +#include "gt/intel_rps.h" + +#include "intel_atomic_plane.h" +#include "intel_cdclk.h" +#include "intel_display_trace.h" +#include "intel_display_types.h" +#include "intel_fb.h" +#include "intel_fb_pin.h" +#include "intel_sprite.h" +#include "skl_scaler.h" +#include "skl_watermark.h" + +static void intel_plane_state_reset(struct intel_plane_state *plane_state, + struct intel_plane *plane) +{ + memset(plane_state, 0, sizeof(*plane_state)); + + __drm_atomic_helper_plane_state_reset(&plane_state->uapi, &plane->base); + + plane_state->scaler_id = -1; +} + +struct intel_plane *intel_plane_alloc(void) +{ + struct intel_plane_state *plane_state; + struct intel_plane *plane; + + plane = kzalloc(sizeof(*plane), GFP_KERNEL); + if (!plane) + return ERR_PTR(-ENOMEM); + + plane_state = kzalloc(sizeof(*plane_state), GFP_KERNEL); + if (!plane_state) { + kfree(plane); + return ERR_PTR(-ENOMEM); + } + + intel_plane_state_reset(plane_state, plane); + + plane->base.state = &plane_state->uapi; + + return plane; +} + +void intel_plane_free(struct intel_plane *plane) +{ + intel_plane_destroy_state(&plane->base, plane->base.state); + kfree(plane); +} + +/** + * intel_plane_duplicate_state - duplicate plane state + * @plane: drm plane + * + * Allocates and returns a copy of the plane state (both common and + * Intel-specific) for the specified plane. + * + * Returns: The newly allocated plane state, or NULL on failure. + */ +struct drm_plane_state * +intel_plane_duplicate_state(struct drm_plane *plane) +{ + struct intel_plane_state *intel_state; + + intel_state = to_intel_plane_state(plane->state); + intel_state = kmemdup(intel_state, sizeof(*intel_state), GFP_KERNEL); + + if (!intel_state) + return NULL; + + __drm_atomic_helper_plane_duplicate_state(plane, &intel_state->uapi); + + intel_state->ggtt_vma = NULL; + intel_state->dpt_vma = NULL; + intel_state->flags = 0; + + /* add reference to fb */ + if (intel_state->hw.fb) + drm_framebuffer_get(intel_state->hw.fb); + + return &intel_state->uapi; +} + +/** + * intel_plane_destroy_state - destroy plane state + * @plane: drm plane + * @state: state object to destroy + * + * Destroys the plane state (both common and Intel-specific) for the + * specified plane. + */ +void +intel_plane_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct intel_plane_state *plane_state = to_intel_plane_state(state); + + drm_WARN_ON(plane->dev, plane_state->ggtt_vma); + drm_WARN_ON(plane->dev, plane_state->dpt_vma); + + __drm_atomic_helper_plane_destroy_state(&plane_state->uapi); + if (plane_state->hw.fb) + drm_framebuffer_put(plane_state->hw.fb); + kfree(plane_state); +} + +unsigned int intel_adjusted_rate(const struct drm_rect *src, + const struct drm_rect *dst, + unsigned int rate) +{ + unsigned int src_w, src_h, dst_w, dst_h; + + src_w = drm_rect_width(src) >> 16; + src_h = drm_rect_height(src) >> 16; + dst_w = drm_rect_width(dst); + dst_h = drm_rect_height(dst); + + /* Downscaling limits the maximum pixel rate */ + dst_w = min(src_w, dst_w); + dst_h = min(src_h, dst_h); + + return DIV_ROUND_UP_ULL(mul_u32_u32(rate, src_w * src_h), + dst_w * dst_h); +} + +unsigned int intel_plane_pixel_rate(const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state) +{ + /* + * Note we don't check for plane visibility here as + * we want to use this when calculating the cursor + * watermarks even if the cursor is fully offscreen. + * That depends on the src/dst rectangles being + * correctly populated whenever the watermark code + * considers the cursor to be visible, whether or not + * it is actually visible. + * + * See: intel_wm_plane_visible() and intel_check_cursor() + */ + + return intel_adjusted_rate(&plane_state->uapi.src, + &plane_state->uapi.dst, + crtc_state->pixel_rate); +} + +unsigned int intel_plane_data_rate(const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state, + int color_plane) +{ + const struct drm_framebuffer *fb = plane_state->hw.fb; + + if (!plane_state->uapi.visible) + return 0; + + return intel_plane_pixel_rate(crtc_state, plane_state) * + fb->format->cpp[color_plane]; +} + +static bool +use_min_ddb(const struct intel_crtc_state *crtc_state, + struct intel_plane *plane) +{ + struct drm_i915_private *i915 = to_i915(plane->base.dev); + + return DISPLAY_VER(i915) >= 13 && + crtc_state->uapi.async_flip && + plane->async_flip; +} + +static unsigned int +intel_plane_relative_data_rate(const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state, + int color_plane) +{ + struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane); + const struct drm_framebuffer *fb = plane_state->hw.fb; + int width, height; + + if (plane->id == PLANE_CURSOR) + return 0; + + if (!plane_state->uapi.visible) + return 0; + + /* + * We calculate extra ddb based on ratio plane rate/total data rate + * in case, in some cases we should not allocate extra ddb for the plane, + * so do not count its data rate, if this is the case. + */ + if (use_min_ddb(crtc_state, plane)) + return 0; + + /* + * Src coordinates are already rotated by 270 degrees for + * the 90/270 degree plane rotation cases (to match the + * GTT mapping), hence no need to account for rotation here. + */ + width = drm_rect_width(&plane_state->uapi.src) >> 16; + height = drm_rect_height(&plane_state->uapi.src) >> 16; + + /* UV plane does 1/2 pixel sub-sampling */ + if (color_plane == 1) { + width /= 2; + height /= 2; + } + + return width * height * fb->format->cpp[color_plane]; +} + +int intel_plane_calc_min_cdclk(struct intel_atomic_state *state, + struct intel_plane *plane, + bool *need_cdclk_calc) +{ + struct drm_i915_private *dev_priv = to_i915(plane->base.dev); + const struct intel_plane_state *plane_state = + intel_atomic_get_new_plane_state(state, plane); + struct intel_crtc *crtc = to_intel_crtc(plane_state->hw.crtc); + const struct intel_cdclk_state *cdclk_state; + const struct intel_crtc_state *old_crtc_state; + struct intel_crtc_state *new_crtc_state; + + if (!plane_state->uapi.visible || !plane->min_cdclk) + return 0; + + old_crtc_state = intel_atomic_get_old_crtc_state(state, crtc); + new_crtc_state = intel_atomic_get_new_crtc_state(state, crtc); + + new_crtc_state->min_cdclk[plane->id] = + plane->min_cdclk(new_crtc_state, plane_state); + + /* + * No need to check against the cdclk state if + * the min cdclk for the plane doesn't increase. + * + * Ie. we only ever increase the cdclk due to plane + * requirements. This can reduce back and forth + * display blinking due to constant cdclk changes. + */ + if (new_crtc_state->min_cdclk[plane->id] <= + old_crtc_state->min_cdclk[plane->id]) + return 0; + + cdclk_state = intel_atomic_get_cdclk_state(state); + if (IS_ERR(cdclk_state)) + return PTR_ERR(cdclk_state); + + /* + * No need to recalculate the cdclk state if + * the min cdclk for the pipe doesn't increase. + * + * Ie. we only ever increase the cdclk due to plane + * requirements. This can reduce back and forth + * display blinking due to constant cdclk changes. + */ + if (new_crtc_state->min_cdclk[plane->id] <= + cdclk_state->min_cdclk[crtc->pipe]) + return 0; + + drm_dbg_kms(&dev_priv->drm, + "[PLANE:%d:%s] min cdclk (%d kHz) > [CRTC:%d:%s] min cdclk (%d kHz)\n", + plane->base.base.id, plane->base.name, + new_crtc_state->min_cdclk[plane->id], + crtc->base.base.id, crtc->base.name, + cdclk_state->min_cdclk[crtc->pipe]); + *need_cdclk_calc = true; + + return 0; +} + +static void intel_plane_clear_hw_state(struct intel_plane_state *plane_state) +{ + if (plane_state->hw.fb) + drm_framebuffer_put(plane_state->hw.fb); + + memset(&plane_state->hw, 0, sizeof(plane_state->hw)); +} + +void intel_plane_copy_uapi_to_hw_state(struct intel_plane_state *plane_state, + const struct intel_plane_state *from_plane_state, + struct intel_crtc *crtc) +{ + intel_plane_clear_hw_state(plane_state); + + /* + * For the bigjoiner slave uapi.crtc will point at + * the master crtc. So we explicitly assign the right + * slave crtc to hw.crtc. uapi.crtc!=NULL simply indicates + * the plane is logically enabled on the uapi level. + */ + plane_state->hw.crtc = from_plane_state->uapi.crtc ? &crtc->base : NULL; + + plane_state->hw.fb = from_plane_state->uapi.fb; + if (plane_state->hw.fb) + drm_framebuffer_get(plane_state->hw.fb); + + plane_state->hw.alpha = from_plane_state->uapi.alpha; + plane_state->hw.pixel_blend_mode = + from_plane_state->uapi.pixel_blend_mode; + plane_state->hw.rotation = from_plane_state->uapi.rotation; + plane_state->hw.color_encoding = from_plane_state->uapi.color_encoding; + plane_state->hw.color_range = from_plane_state->uapi.color_range; + plane_state->hw.scaling_filter = from_plane_state->uapi.scaling_filter; + + plane_state->uapi.src = drm_plane_state_src(&from_plane_state->uapi); + plane_state->uapi.dst = drm_plane_state_dest(&from_plane_state->uapi); +} + +void intel_plane_copy_hw_state(struct intel_plane_state *plane_state, + const struct intel_plane_state *from_plane_state) +{ + intel_plane_clear_hw_state(plane_state); + + memcpy(&plane_state->hw, &from_plane_state->hw, + sizeof(plane_state->hw)); + + if (plane_state->hw.fb) + drm_framebuffer_get(plane_state->hw.fb); +} + +void intel_plane_set_invisible(struct intel_crtc_state *crtc_state, + struct intel_plane_state *plane_state) +{ + struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane); + + crtc_state->active_planes &= ~BIT(plane->id); + crtc_state->scaled_planes &= ~BIT(plane->id); + crtc_state->nv12_planes &= ~BIT(plane->id); + crtc_state->c8_planes &= ~BIT(plane->id); + crtc_state->data_rate[plane->id] = 0; + crtc_state->data_rate_y[plane->id] = 0; + crtc_state->rel_data_rate[plane->id] = 0; + crtc_state->rel_data_rate_y[plane->id] = 0; + crtc_state->min_cdclk[plane->id] = 0; + + plane_state->uapi.visible = false; +} + +/* FIXME nuke when all wm code is atomic */ +static bool intel_wm_need_update(const struct intel_plane_state *cur, + struct intel_plane_state *new) +{ + /* Update watermarks on tiling or size changes. */ + if (new->uapi.visible != cur->uapi.visible) + return true; + + if (!cur->hw.fb || !new->hw.fb) + return false; + + if (cur->hw.fb->modifier != new->hw.fb->modifier || + cur->hw.rotation != new->hw.rotation || + drm_rect_width(&new->uapi.src) != drm_rect_width(&cur->uapi.src) || + drm_rect_height(&new->uapi.src) != drm_rect_height(&cur->uapi.src) || + drm_rect_width(&new->uapi.dst) != drm_rect_width(&cur->uapi.dst) || + drm_rect_height(&new->uapi.dst) != drm_rect_height(&cur->uapi.dst)) + return true; + + return false; +} + +static bool intel_plane_is_scaled(const struct intel_plane_state *plane_state) +{ + int src_w = drm_rect_width(&plane_state->uapi.src) >> 16; + int src_h = drm_rect_height(&plane_state->uapi.src) >> 16; + int dst_w = drm_rect_width(&plane_state->uapi.dst); + int dst_h = drm_rect_height(&plane_state->uapi.dst); + + return src_w != dst_w || src_h != dst_h; +} + +static bool intel_plane_do_async_flip(struct intel_plane *plane, + const struct intel_crtc_state *old_crtc_state, + const struct intel_crtc_state *new_crtc_state) +{ + struct drm_i915_private *i915 = to_i915(plane->base.dev); + + if (!plane->async_flip) + return false; + + if (!new_crtc_state->uapi.async_flip) + return false; + + /* + * In platforms after DISPLAY13, we might need to override + * first async flip in order to change watermark levels + * as part of optimization. + * So for those, we are checking if this is a first async flip. + * For platforms earlier than DISPLAY13 we always do async flip. + */ + return DISPLAY_VER(i915) < 13 || old_crtc_state->uapi.async_flip; +} + +static int intel_plane_atomic_calc_changes(const struct intel_crtc_state *old_crtc_state, + struct intel_crtc_state *new_crtc_state, + const struct intel_plane_state *old_plane_state, + struct intel_plane_state *new_plane_state) +{ + struct intel_crtc *crtc = to_intel_crtc(new_crtc_state->uapi.crtc); + struct intel_plane *plane = to_intel_plane(new_plane_state->uapi.plane); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + bool mode_changed = intel_crtc_needs_modeset(new_crtc_state); + bool was_crtc_enabled = old_crtc_state->hw.active; + bool is_crtc_enabled = new_crtc_state->hw.active; + bool turn_off, turn_on, visible, was_visible; + int ret; + + if (DISPLAY_VER(dev_priv) >= 9 && plane->id != PLANE_CURSOR) { + ret = skl_update_scaler_plane(new_crtc_state, new_plane_state); + if (ret) + return ret; + } + + was_visible = old_plane_state->uapi.visible; + visible = new_plane_state->uapi.visible; + + if (!was_crtc_enabled && drm_WARN_ON(&dev_priv->drm, was_visible)) + was_visible = false; + + /* + * Visibility is calculated as if the crtc was on, but + * after scaler setup everything depends on it being off + * when the crtc isn't active. + * + * FIXME this is wrong for watermarks. Watermarks should also + * be computed as if the pipe would be active. Perhaps move + * per-plane wm computation to the .check_plane() hook, and + * only combine the results from all planes in the current place? + */ + if (!is_crtc_enabled) { + intel_plane_set_invisible(new_crtc_state, new_plane_state); + visible = false; + } + + if (!was_visible && !visible) + return 0; + + turn_off = was_visible && (!visible || mode_changed); + turn_on = visible && (!was_visible || mode_changed); + + drm_dbg_atomic(&dev_priv->drm, + "[CRTC:%d:%s] with [PLANE:%d:%s] visible %i -> %i, off %i, on %i, ms %i\n", + crtc->base.base.id, crtc->base.name, + plane->base.base.id, plane->base.name, + was_visible, visible, + turn_off, turn_on, mode_changed); + + if (turn_on) { + if (DISPLAY_VER(dev_priv) < 5 && !IS_G4X(dev_priv)) + new_crtc_state->update_wm_pre = true; + + /* must disable cxsr around plane enable/disable */ + if (plane->id != PLANE_CURSOR) + new_crtc_state->disable_cxsr = true; + } else if (turn_off) { + if (DISPLAY_VER(dev_priv) < 5 && !IS_G4X(dev_priv)) + new_crtc_state->update_wm_post = true; + + /* must disable cxsr around plane enable/disable */ + if (plane->id != PLANE_CURSOR) + new_crtc_state->disable_cxsr = true; + } else if (intel_wm_need_update(old_plane_state, new_plane_state)) { + if (DISPLAY_VER(dev_priv) < 5 && !IS_G4X(dev_priv)) { + /* FIXME bollocks */ + new_crtc_state->update_wm_pre = true; + new_crtc_state->update_wm_post = true; + } + } + + if (visible || was_visible) + new_crtc_state->fb_bits |= plane->frontbuffer_bit; + + /* + * ILK/SNB DVSACNTR/Sprite Enable + * IVB SPR_CTL/Sprite Enable + * "When in Self Refresh Big FIFO mode, a write to enable the + * plane will be internally buffered and delayed while Big FIFO + * mode is exiting." + * + * Which means that enabling the sprite can take an extra frame + * when we start in big FIFO mode (LP1+). Thus we need to drop + * down to LP0 and wait for vblank in order to make sure the + * sprite gets enabled on the next vblank after the register write. + * Doing otherwise would risk enabling the sprite one frame after + * we've already signalled flip completion. We can resume LP1+ + * once the sprite has been enabled. + * + * + * WaCxSRDisabledForSpriteScaling:ivb + * IVB SPR_SCALE/Scaling Enable + * "Low Power watermarks must be disabled for at least one + * frame before enabling sprite scaling, and kept disabled + * until sprite scaling is disabled." + * + * ILK/SNB DVSASCALE/Scaling Enable + * "When in Self Refresh Big FIFO mode, scaling enable will be + * masked off while Big FIFO mode is exiting." + * + * Despite the w/a only being listed for IVB we assume that + * the ILK/SNB note has similar ramifications, hence we apply + * the w/a on all three platforms. + * + * With experimental results seems this is needed also for primary + * plane, not only sprite plane. + */ + if (plane->id != PLANE_CURSOR && + (IS_IRONLAKE(dev_priv) || IS_SANDYBRIDGE(dev_priv) || + IS_IVYBRIDGE(dev_priv)) && + (turn_on || (!intel_plane_is_scaled(old_plane_state) && + intel_plane_is_scaled(new_plane_state)))) + new_crtc_state->disable_lp_wm = true; + + if (intel_plane_do_async_flip(plane, old_crtc_state, new_crtc_state)) + new_crtc_state->do_async_flip = true; + + return 0; +} + +int intel_plane_atomic_check_with_state(const struct intel_crtc_state *old_crtc_state, + struct intel_crtc_state *new_crtc_state, + const struct intel_plane_state *old_plane_state, + struct intel_plane_state *new_plane_state) +{ + struct intel_plane *plane = to_intel_plane(new_plane_state->uapi.plane); + const struct drm_framebuffer *fb = new_plane_state->hw.fb; + int ret; + + intel_plane_set_invisible(new_crtc_state, new_plane_state); + new_crtc_state->enabled_planes &= ~BIT(plane->id); + + if (!new_plane_state->hw.crtc && !old_plane_state->hw.crtc) + return 0; + + ret = plane->check_plane(new_crtc_state, new_plane_state); + if (ret) + return ret; + + if (fb) + new_crtc_state->enabled_planes |= BIT(plane->id); + + /* FIXME pre-g4x don't work like this */ + if (new_plane_state->uapi.visible) + new_crtc_state->active_planes |= BIT(plane->id); + + if (new_plane_state->uapi.visible && + intel_plane_is_scaled(new_plane_state)) + new_crtc_state->scaled_planes |= BIT(plane->id); + + if (new_plane_state->uapi.visible && + intel_format_info_is_yuv_semiplanar(fb->format, fb->modifier)) + new_crtc_state->nv12_planes |= BIT(plane->id); + + if (new_plane_state->uapi.visible && + fb->format->format == DRM_FORMAT_C8) + new_crtc_state->c8_planes |= BIT(plane->id); + + if (new_plane_state->uapi.visible || old_plane_state->uapi.visible) + new_crtc_state->update_planes |= BIT(plane->id); + + if (new_plane_state->uapi.visible && + intel_format_info_is_yuv_semiplanar(fb->format, fb->modifier)) { + new_crtc_state->data_rate_y[plane->id] = + intel_plane_data_rate(new_crtc_state, new_plane_state, 0); + new_crtc_state->data_rate[plane->id] = + intel_plane_data_rate(new_crtc_state, new_plane_state, 1); + + new_crtc_state->rel_data_rate_y[plane->id] = + intel_plane_relative_data_rate(new_crtc_state, + new_plane_state, 0); + new_crtc_state->rel_data_rate[plane->id] = + intel_plane_relative_data_rate(new_crtc_state, + new_plane_state, 1); + } else if (new_plane_state->uapi.visible) { + new_crtc_state->data_rate[plane->id] = + intel_plane_data_rate(new_crtc_state, new_plane_state, 0); + + new_crtc_state->rel_data_rate[plane->id] = + intel_plane_relative_data_rate(new_crtc_state, + new_plane_state, 0); + } + + return intel_plane_atomic_calc_changes(old_crtc_state, new_crtc_state, + old_plane_state, new_plane_state); +} + +static struct intel_plane * +intel_crtc_get_plane(struct intel_crtc *crtc, enum plane_id plane_id) +{ + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + struct intel_plane *plane; + + for_each_intel_plane_on_crtc(&i915->drm, crtc, plane) { + if (plane->id == plane_id) + return plane; + } + + return NULL; +} + +int intel_plane_atomic_check(struct intel_atomic_state *state, + struct intel_plane *plane) +{ + struct drm_i915_private *i915 = to_i915(state->base.dev); + struct intel_plane_state *new_plane_state = + intel_atomic_get_new_plane_state(state, plane); + const struct intel_plane_state *old_plane_state = + intel_atomic_get_old_plane_state(state, plane); + const struct intel_plane_state *new_master_plane_state; + struct intel_crtc *crtc = intel_crtc_for_pipe(i915, plane->pipe); + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + if (new_crtc_state && intel_crtc_is_bigjoiner_slave(new_crtc_state)) { + struct intel_crtc *master_crtc = + intel_master_crtc(new_crtc_state); + struct intel_plane *master_plane = + intel_crtc_get_plane(master_crtc, plane->id); + + new_master_plane_state = + intel_atomic_get_new_plane_state(state, master_plane); + } else { + new_master_plane_state = new_plane_state; + } + + intel_plane_copy_uapi_to_hw_state(new_plane_state, + new_master_plane_state, + crtc); + + new_plane_state->uapi.visible = false; + if (!new_crtc_state) + return 0; + + return intel_plane_atomic_check_with_state(old_crtc_state, + new_crtc_state, + old_plane_state, + new_plane_state); +} + +static struct intel_plane * +skl_next_plane_to_commit(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct skl_ddb_entry ddb[I915_MAX_PLANES], + struct skl_ddb_entry ddb_y[I915_MAX_PLANES], + unsigned int *update_mask) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct intel_plane_state *plane_state; + struct intel_plane *plane; + int i; + + if (*update_mask == 0) + return NULL; + + for_each_new_intel_plane_in_state(state, plane, plane_state, i) { + enum plane_id plane_id = plane->id; + + if (crtc->pipe != plane->pipe || + !(*update_mask & BIT(plane_id))) + continue; + + if (skl_ddb_allocation_overlaps(&crtc_state->wm.skl.plane_ddb[plane_id], + ddb, I915_MAX_PLANES, plane_id) || + skl_ddb_allocation_overlaps(&crtc_state->wm.skl.plane_ddb_y[plane_id], + ddb_y, I915_MAX_PLANES, plane_id)) + continue; + + *update_mask &= ~BIT(plane_id); + ddb[plane_id] = crtc_state->wm.skl.plane_ddb[plane_id]; + ddb_y[plane_id] = crtc_state->wm.skl.plane_ddb_y[plane_id]; + + return plane; + } + + /* should never happen */ + drm_WARN_ON(state->base.dev, 1); + + return NULL; +} + +void intel_plane_update_noarm(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + trace_intel_plane_update_noarm(&plane->base, crtc); + + if (plane->update_noarm) + plane->update_noarm(plane, crtc_state, plane_state); +} + +void intel_plane_update_arm(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + trace_intel_plane_update_arm(&plane->base, crtc); + + if (crtc_state->do_async_flip && plane->async_flip) + plane->async_flip(plane, crtc_state, plane_state, true); + else + plane->update_arm(plane, crtc_state, plane_state); +} + +void intel_plane_disable_arm(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + trace_intel_plane_disable_arm(&plane->base, crtc); + plane->disable_arm(plane, crtc_state); +} + +void intel_crtc_planes_update_noarm(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + u32 update_mask = new_crtc_state->update_planes; + struct intel_plane_state *new_plane_state; + struct intel_plane *plane; + int i; + + if (new_crtc_state->do_async_flip) + return; + + /* + * Since we only write non-arming registers here, + * the order does not matter even for skl+. + */ + for_each_new_intel_plane_in_state(state, plane, new_plane_state, i) { + if (crtc->pipe != plane->pipe || + !(update_mask & BIT(plane->id))) + continue; + + /* TODO: for mailbox updates this should be skipped */ + if (new_plane_state->uapi.visible || + new_plane_state->planar_slave) + intel_plane_update_noarm(plane, new_crtc_state, new_plane_state); + } +} + +static void skl_crtc_planes_update_arm(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct skl_ddb_entry ddb[I915_MAX_PLANES]; + struct skl_ddb_entry ddb_y[I915_MAX_PLANES]; + u32 update_mask = new_crtc_state->update_planes; + struct intel_plane *plane; + + memcpy(ddb, old_crtc_state->wm.skl.plane_ddb, + sizeof(old_crtc_state->wm.skl.plane_ddb)); + memcpy(ddb_y, old_crtc_state->wm.skl.plane_ddb_y, + sizeof(old_crtc_state->wm.skl.plane_ddb_y)); + + while ((plane = skl_next_plane_to_commit(state, crtc, ddb, ddb_y, &update_mask))) { + struct intel_plane_state *new_plane_state = + intel_atomic_get_new_plane_state(state, plane); + + /* + * TODO: for mailbox updates intel_plane_update_noarm() + * would have to be called here as well. + */ + if (new_plane_state->uapi.visible || + new_plane_state->planar_slave) + intel_plane_update_arm(plane, new_crtc_state, new_plane_state); + else + intel_plane_disable_arm(plane, new_crtc_state); + } +} + +static void i9xx_crtc_planes_update_arm(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + u32 update_mask = new_crtc_state->update_planes; + struct intel_plane_state *new_plane_state; + struct intel_plane *plane; + int i; + + for_each_new_intel_plane_in_state(state, plane, new_plane_state, i) { + if (crtc->pipe != plane->pipe || + !(update_mask & BIT(plane->id))) + continue; + + /* + * TODO: for mailbox updates intel_plane_update_noarm() + * would have to be called here as well. + */ + if (new_plane_state->uapi.visible) + intel_plane_update_arm(plane, new_crtc_state, new_plane_state); + else + intel_plane_disable_arm(plane, new_crtc_state); + } +} + +void intel_crtc_planes_update_arm(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *i915 = to_i915(state->base.dev); + + if (DISPLAY_VER(i915) >= 9) + skl_crtc_planes_update_arm(state, crtc); + else + i9xx_crtc_planes_update_arm(state, crtc); +} + +int intel_atomic_plane_check_clipping(struct intel_plane_state *plane_state, + struct intel_crtc_state *crtc_state, + int min_scale, int max_scale, + bool can_position) +{ + struct drm_i915_private *i915 = to_i915(plane_state->uapi.plane->dev); + struct drm_framebuffer *fb = plane_state->hw.fb; + struct drm_rect *src = &plane_state->uapi.src; + struct drm_rect *dst = &plane_state->uapi.dst; + const struct drm_rect *clip = &crtc_state->pipe_src; + unsigned int rotation = plane_state->hw.rotation; + int hscale, vscale; + + if (!fb) { + plane_state->uapi.visible = false; + return 0; + } + + drm_rect_rotate(src, fb->width << 16, fb->height << 16, rotation); + + /* Check scaling */ + hscale = drm_rect_calc_hscale(src, dst, min_scale, max_scale); + vscale = drm_rect_calc_vscale(src, dst, min_scale, max_scale); + if (hscale < 0 || vscale < 0) { + drm_dbg_kms(&i915->drm, "Invalid scaling of plane\n"); + drm_rect_debug_print("src: ", src, true); + drm_rect_debug_print("dst: ", dst, false); + return -ERANGE; + } + + /* + * FIXME: This might need further adjustment for seamless scaling + * with phase information, for the 2p2 and 2p1 scenarios. + */ + plane_state->uapi.visible = drm_rect_clip_scaled(src, dst, clip); + + drm_rect_rotate_inv(src, fb->width << 16, fb->height << 16, rotation); + + if (!can_position && plane_state->uapi.visible && + !drm_rect_equals(dst, clip)) { + drm_dbg_kms(&i915->drm, "Plane must cover entire CRTC\n"); + drm_rect_debug_print("dst: ", dst, false); + drm_rect_debug_print("clip: ", clip, false); + return -EINVAL; + } + + /* final plane coordinates will be relative to the plane's pipe */ + drm_rect_translate(dst, -clip->x1, -clip->y1); + + return 0; +} + +struct wait_rps_boost { + struct wait_queue_entry wait; + + struct drm_crtc *crtc; + struct i915_request *request; +}; + +static int do_rps_boost(struct wait_queue_entry *_wait, + unsigned mode, int sync, void *key) +{ + struct wait_rps_boost *wait = container_of(_wait, typeof(*wait), wait); + struct i915_request *rq = wait->request; + + /* + * If we missed the vblank, but the request is already running it + * is reasonable to assume that it will complete before the next + * vblank without our intervention, so leave RPS alone. + */ + if (!i915_request_started(rq)) + intel_rps_boost(rq); + i915_request_put(rq); + + drm_crtc_vblank_put(wait->crtc); + + list_del(&wait->wait.entry); + kfree(wait); + return 1; +} + +static void add_rps_boost_after_vblank(struct drm_crtc *crtc, + struct dma_fence *fence) +{ + struct wait_rps_boost *wait; + + if (!dma_fence_is_i915(fence)) + return; + + if (DISPLAY_VER(to_i915(crtc->dev)) < 6) + return; + + if (drm_crtc_vblank_get(crtc)) + return; + + wait = kmalloc(sizeof(*wait), GFP_KERNEL); + if (!wait) { + drm_crtc_vblank_put(crtc); + return; + } + + wait->request = to_request(dma_fence_get(fence)); + wait->crtc = crtc; + + wait->wait.func = do_rps_boost; + wait->wait.flags = 0; + + add_wait_queue(drm_crtc_vblank_waitqueue(crtc), &wait->wait); +} + +/** + * intel_prepare_plane_fb - Prepare fb for usage on plane + * @_plane: drm plane to prepare for + * @_new_plane_state: the plane state being prepared + * + * Prepares a framebuffer for usage on a display plane. Generally this + * involves pinning the underlying object and updating the frontbuffer tracking + * bits. Some older platforms need special physical address handling for + * cursor planes. + * + * Returns 0 on success, negative error code on failure. + */ +static int +intel_prepare_plane_fb(struct drm_plane *_plane, + struct drm_plane_state *_new_plane_state) +{ + struct i915_sched_attr attr = { .priority = I915_PRIORITY_DISPLAY }; + struct intel_plane *plane = to_intel_plane(_plane); + struct intel_plane_state *new_plane_state = + to_intel_plane_state(_new_plane_state); + struct intel_atomic_state *state = + to_intel_atomic_state(new_plane_state->uapi.state); + struct drm_i915_private *dev_priv = to_i915(plane->base.dev); + const struct intel_plane_state *old_plane_state = + intel_atomic_get_old_plane_state(state, plane); + struct drm_i915_gem_object *obj = intel_fb_obj(new_plane_state->hw.fb); + struct drm_i915_gem_object *old_obj = intel_fb_obj(old_plane_state->hw.fb); + int ret; + + if (old_obj) { + const struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, + to_intel_crtc(old_plane_state->hw.crtc)); + + /* Big Hammer, we also need to ensure that any pending + * MI_WAIT_FOR_EVENT inside a user batch buffer on the + * current scanout is retired before unpinning the old + * framebuffer. Note that we rely on userspace rendering + * into the buffer attached to the pipe they are waiting + * on. If not, userspace generates a GPU hang with IPEHR + * point to the MI_WAIT_FOR_EVENT. + * + * This should only fail upon a hung GPU, in which case we + * can safely continue. + */ + if (new_crtc_state && intel_crtc_needs_modeset(new_crtc_state)) { + ret = i915_sw_fence_await_reservation(&state->commit_ready, + old_obj->base.resv, NULL, + false, 0, + GFP_KERNEL); + if (ret < 0) + return ret; + } + } + + if (new_plane_state->uapi.fence) { /* explicit fencing */ + i915_gem_fence_wait_priority(new_plane_state->uapi.fence, + &attr); + ret = i915_sw_fence_await_dma_fence(&state->commit_ready, + new_plane_state->uapi.fence, + i915_fence_timeout(dev_priv), + GFP_KERNEL); + if (ret < 0) + return ret; + } + + if (!obj) + return 0; + + + ret = intel_plane_pin_fb(new_plane_state); + if (ret) + return ret; + + i915_gem_object_wait_priority(obj, 0, &attr); + + if (!new_plane_state->uapi.fence) { /* implicit fencing */ + struct dma_resv_iter cursor; + struct dma_fence *fence; + + ret = i915_sw_fence_await_reservation(&state->commit_ready, + obj->base.resv, NULL, + false, + i915_fence_timeout(dev_priv), + GFP_KERNEL); + if (ret < 0) + goto unpin_fb; + + dma_resv_iter_begin(&cursor, obj->base.resv, + DMA_RESV_USAGE_WRITE); + dma_resv_for_each_fence_unlocked(&cursor, fence) { + add_rps_boost_after_vblank(new_plane_state->hw.crtc, + fence); + } + dma_resv_iter_end(&cursor); + } else { + add_rps_boost_after_vblank(new_plane_state->hw.crtc, + new_plane_state->uapi.fence); + } + + /* + * We declare pageflips to be interactive and so merit a small bias + * towards upclocking to deliver the frame on time. By only changing + * the RPS thresholds to sample more regularly and aim for higher + * clocks we can hopefully deliver low power workloads (like kodi) + * that are not quite steady state without resorting to forcing + * maximum clocks following a vblank miss (see do_rps_boost()). + */ + if (!state->rps_interactive) { + intel_rps_mark_interactive(&to_gt(dev_priv)->rps, true); + state->rps_interactive = true; + } + + return 0; + +unpin_fb: + intel_plane_unpin_fb(new_plane_state); + + return ret; +} + +/** + * intel_cleanup_plane_fb - Cleans up an fb after plane use + * @plane: drm plane to clean up for + * @_old_plane_state: the state from the previous modeset + * + * Cleans up a framebuffer that has just been removed from a plane. + */ +static void +intel_cleanup_plane_fb(struct drm_plane *plane, + struct drm_plane_state *_old_plane_state) +{ + struct intel_plane_state *old_plane_state = + to_intel_plane_state(_old_plane_state); + struct intel_atomic_state *state = + to_intel_atomic_state(old_plane_state->uapi.state); + struct drm_i915_private *dev_priv = to_i915(plane->dev); + struct drm_i915_gem_object *obj = intel_fb_obj(old_plane_state->hw.fb); + + if (!obj) + return; + + if (state->rps_interactive) { + intel_rps_mark_interactive(&to_gt(dev_priv)->rps, false); + state->rps_interactive = false; + } + + /* Should only be called after a successful intel_prepare_plane_fb()! */ + intel_plane_unpin_fb(old_plane_state); +} + +static const struct drm_plane_helper_funcs intel_plane_helper_funcs = { + .prepare_fb = intel_prepare_plane_fb, + .cleanup_fb = intel_cleanup_plane_fb, +}; + +void intel_plane_helper_add(struct intel_plane *plane) +{ + drm_plane_helper_add(&plane->base, &intel_plane_helper_funcs); +} diff --git a/drivers/gpu/drm/i915/display/intel_atomic_plane.h b/drivers/gpu/drm/i915/display/intel_atomic_plane.h new file mode 100644 index 000000000..74b6d3b16 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_atomic_plane.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_ATOMIC_PLANE_H__ +#define __INTEL_ATOMIC_PLANE_H__ + +#include + +struct drm_plane; +struct drm_property; +struct drm_rect; +struct intel_atomic_state; +struct intel_crtc; +struct intel_crtc_state; +struct intel_plane; +struct intel_plane_state; +enum plane_id; + +unsigned int intel_adjusted_rate(const struct drm_rect *src, + const struct drm_rect *dst, + unsigned int rate); +unsigned int intel_plane_pixel_rate(const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state); + +unsigned int intel_plane_data_rate(const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state, + int color_plane); +void intel_plane_copy_uapi_to_hw_state(struct intel_plane_state *plane_state, + const struct intel_plane_state *from_plane_state, + struct intel_crtc *crtc); +void intel_plane_copy_hw_state(struct intel_plane_state *plane_state, + const struct intel_plane_state *from_plane_state); +void intel_plane_update_noarm(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state); +void intel_plane_update_arm(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state); +void intel_plane_disable_arm(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state); +struct intel_plane *intel_plane_alloc(void); +void intel_plane_free(struct intel_plane *plane); +struct drm_plane_state *intel_plane_duplicate_state(struct drm_plane *plane); +void intel_plane_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state); +void intel_crtc_planes_update_noarm(struct intel_atomic_state *state, + struct intel_crtc *crtc); +void intel_crtc_planes_update_arm(struct intel_atomic_state *state, + struct intel_crtc *crtc); +int intel_plane_atomic_check_with_state(const struct intel_crtc_state *old_crtc_state, + struct intel_crtc_state *crtc_state, + const struct intel_plane_state *old_plane_state, + struct intel_plane_state *intel_state); +int intel_plane_atomic_check(struct intel_atomic_state *state, + struct intel_plane *plane); +int intel_plane_calc_min_cdclk(struct intel_atomic_state *state, + struct intel_plane *plane, + bool *need_cdclk_calc); +int intel_atomic_plane_check_clipping(struct intel_plane_state *plane_state, + struct intel_crtc_state *crtc_state, + int min_scale, int max_scale, + bool can_position); +void intel_plane_set_invisible(struct intel_crtc_state *crtc_state, + struct intel_plane_state *plane_state); +void intel_plane_helper_add(struct intel_plane *plane); + +#endif /* __INTEL_ATOMIC_PLANE_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_audio.c b/drivers/gpu/drm/i915/display/intel_audio.c new file mode 100644 index 000000000..aacbc6da8 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_audio.c @@ -0,0 +1,1410 @@ +/* + * Copyright © 2014 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include + +#include +#include + +#include "i915_drv.h" +#include "intel_atomic.h" +#include "intel_audio.h" +#include "intel_audio_regs.h" +#include "intel_cdclk.h" +#include "intel_crtc.h" +#include "intel_de.h" +#include "intel_display_types.h" +#include "intel_lpe_audio.h" + +/** + * DOC: High Definition Audio over HDMI and Display Port + * + * The graphics and audio drivers together support High Definition Audio over + * HDMI and Display Port. The audio programming sequences are divided into audio + * codec and controller enable and disable sequences. The graphics driver + * handles the audio codec sequences, while the audio driver handles the audio + * controller sequences. + * + * The disable sequences must be performed before disabling the transcoder or + * port. The enable sequences may only be performed after enabling the + * transcoder and port, and after completed link training. Therefore the audio + * enable/disable sequences are part of the modeset sequence. + * + * The codec and controller sequences could be done either parallel or serial, + * but generally the ELDV/PD change in the codec sequence indicates to the audio + * driver that the controller sequence should start. Indeed, most of the + * co-operation between the graphics and audio drivers is handled via audio + * related registers. (The notable exception is the power management, not + * covered here.) + * + * The struct &i915_audio_component is used to interact between the graphics + * and audio drivers. The struct &i915_audio_component_ops @ops in it is + * defined in graphics driver and called in audio driver. The + * struct &i915_audio_component_audio_ops @audio_ops is called from i915 driver. + */ + +struct intel_audio_funcs { + void (*audio_codec_enable)(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state); + void (*audio_codec_disable)(struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state); +}; + +/* DP N/M table */ +#define LC_810M 810000 +#define LC_540M 540000 +#define LC_270M 270000 +#define LC_162M 162000 + +struct dp_aud_n_m { + int sample_rate; + int clock; + u16 m; + u16 n; +}; + +struct hdmi_aud_ncts { + int sample_rate; + int clock; + int n; + int cts; +}; + +/* Values according to DP 1.4 Table 2-104 */ +static const struct dp_aud_n_m dp_aud_n_m[] = { + { 32000, LC_162M, 1024, 10125 }, + { 44100, LC_162M, 784, 5625 }, + { 48000, LC_162M, 512, 3375 }, + { 64000, LC_162M, 2048, 10125 }, + { 88200, LC_162M, 1568, 5625 }, + { 96000, LC_162M, 1024, 3375 }, + { 128000, LC_162M, 4096, 10125 }, + { 176400, LC_162M, 3136, 5625 }, + { 192000, LC_162M, 2048, 3375 }, + { 32000, LC_270M, 1024, 16875 }, + { 44100, LC_270M, 784, 9375 }, + { 48000, LC_270M, 512, 5625 }, + { 64000, LC_270M, 2048, 16875 }, + { 88200, LC_270M, 1568, 9375 }, + { 96000, LC_270M, 1024, 5625 }, + { 128000, LC_270M, 4096, 16875 }, + { 176400, LC_270M, 3136, 9375 }, + { 192000, LC_270M, 2048, 5625 }, + { 32000, LC_540M, 1024, 33750 }, + { 44100, LC_540M, 784, 18750 }, + { 48000, LC_540M, 512, 11250 }, + { 64000, LC_540M, 2048, 33750 }, + { 88200, LC_540M, 1568, 18750 }, + { 96000, LC_540M, 1024, 11250 }, + { 128000, LC_540M, 4096, 33750 }, + { 176400, LC_540M, 3136, 18750 }, + { 192000, LC_540M, 2048, 11250 }, + { 32000, LC_810M, 1024, 50625 }, + { 44100, LC_810M, 784, 28125 }, + { 48000, LC_810M, 512, 16875 }, + { 64000, LC_810M, 2048, 50625 }, + { 88200, LC_810M, 1568, 28125 }, + { 96000, LC_810M, 1024, 16875 }, + { 128000, LC_810M, 4096, 50625 }, + { 176400, LC_810M, 3136, 28125 }, + { 192000, LC_810M, 2048, 16875 }, +}; + +static const struct dp_aud_n_m * +audio_config_dp_get_n_m(const struct intel_crtc_state *crtc_state, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dp_aud_n_m); i++) { + if (rate == dp_aud_n_m[i].sample_rate && + crtc_state->port_clock == dp_aud_n_m[i].clock) + return &dp_aud_n_m[i]; + } + + return NULL; +} + +static const struct { + int clock; + u32 config; +} hdmi_audio_clock[] = { + { 25175, AUD_CONFIG_PIXEL_CLOCK_HDMI_25175 }, + { 25200, AUD_CONFIG_PIXEL_CLOCK_HDMI_25200 }, /* default per bspec */ + { 27000, AUD_CONFIG_PIXEL_CLOCK_HDMI_27000 }, + { 27027, AUD_CONFIG_PIXEL_CLOCK_HDMI_27027 }, + { 54000, AUD_CONFIG_PIXEL_CLOCK_HDMI_54000 }, + { 54054, AUD_CONFIG_PIXEL_CLOCK_HDMI_54054 }, + { 74176, AUD_CONFIG_PIXEL_CLOCK_HDMI_74176 }, + { 74250, AUD_CONFIG_PIXEL_CLOCK_HDMI_74250 }, + { 148352, AUD_CONFIG_PIXEL_CLOCK_HDMI_148352 }, + { 148500, AUD_CONFIG_PIXEL_CLOCK_HDMI_148500 }, + { 296703, AUD_CONFIG_PIXEL_CLOCK_HDMI_296703 }, + { 297000, AUD_CONFIG_PIXEL_CLOCK_HDMI_297000 }, + { 593407, AUD_CONFIG_PIXEL_CLOCK_HDMI_593407 }, + { 594000, AUD_CONFIG_PIXEL_CLOCK_HDMI_594000 }, +}; + +/* HDMI N/CTS table */ +#define TMDS_297M 297000 +#define TMDS_296M 296703 +#define TMDS_594M 594000 +#define TMDS_593M 593407 + +static const struct hdmi_aud_ncts hdmi_aud_ncts_24bpp[] = { + { 32000, TMDS_296M, 5824, 421875 }, + { 32000, TMDS_297M, 3072, 222750 }, + { 32000, TMDS_593M, 5824, 843750 }, + { 32000, TMDS_594M, 3072, 445500 }, + { 44100, TMDS_296M, 4459, 234375 }, + { 44100, TMDS_297M, 4704, 247500 }, + { 44100, TMDS_593M, 8918, 937500 }, + { 44100, TMDS_594M, 9408, 990000 }, + { 88200, TMDS_296M, 8918, 234375 }, + { 88200, TMDS_297M, 9408, 247500 }, + { 88200, TMDS_593M, 17836, 937500 }, + { 88200, TMDS_594M, 18816, 990000 }, + { 176400, TMDS_296M, 17836, 234375 }, + { 176400, TMDS_297M, 18816, 247500 }, + { 176400, TMDS_593M, 35672, 937500 }, + { 176400, TMDS_594M, 37632, 990000 }, + { 48000, TMDS_296M, 5824, 281250 }, + { 48000, TMDS_297M, 5120, 247500 }, + { 48000, TMDS_593M, 5824, 562500 }, + { 48000, TMDS_594M, 6144, 594000 }, + { 96000, TMDS_296M, 11648, 281250 }, + { 96000, TMDS_297M, 10240, 247500 }, + { 96000, TMDS_593M, 11648, 562500 }, + { 96000, TMDS_594M, 12288, 594000 }, + { 192000, TMDS_296M, 23296, 281250 }, + { 192000, TMDS_297M, 20480, 247500 }, + { 192000, TMDS_593M, 23296, 562500 }, + { 192000, TMDS_594M, 24576, 594000 }, +}; + +/* Appendix C - N & CTS values for deep color from HDMI 2.0 spec*/ +/* HDMI N/CTS table for 10 bit deep color(30 bpp)*/ +#define TMDS_371M 371250 +#define TMDS_370M 370878 + +static const struct hdmi_aud_ncts hdmi_aud_ncts_30bpp[] = { + { 32000, TMDS_370M, 5824, 527344 }, + { 32000, TMDS_371M, 6144, 556875 }, + { 44100, TMDS_370M, 8918, 585938 }, + { 44100, TMDS_371M, 4704, 309375 }, + { 88200, TMDS_370M, 17836, 585938 }, + { 88200, TMDS_371M, 9408, 309375 }, + { 176400, TMDS_370M, 35672, 585938 }, + { 176400, TMDS_371M, 18816, 309375 }, + { 48000, TMDS_370M, 11648, 703125 }, + { 48000, TMDS_371M, 5120, 309375 }, + { 96000, TMDS_370M, 23296, 703125 }, + { 96000, TMDS_371M, 10240, 309375 }, + { 192000, TMDS_370M, 46592, 703125 }, + { 192000, TMDS_371M, 20480, 309375 }, +}; + +/* HDMI N/CTS table for 12 bit deep color(36 bpp)*/ +#define TMDS_445_5M 445500 +#define TMDS_445M 445054 + +static const struct hdmi_aud_ncts hdmi_aud_ncts_36bpp[] = { + { 32000, TMDS_445M, 5824, 632813 }, + { 32000, TMDS_445_5M, 4096, 445500 }, + { 44100, TMDS_445M, 8918, 703125 }, + { 44100, TMDS_445_5M, 4704, 371250 }, + { 88200, TMDS_445M, 17836, 703125 }, + { 88200, TMDS_445_5M, 9408, 371250 }, + { 176400, TMDS_445M, 35672, 703125 }, + { 176400, TMDS_445_5M, 18816, 371250 }, + { 48000, TMDS_445M, 5824, 421875 }, + { 48000, TMDS_445_5M, 5120, 371250 }, + { 96000, TMDS_445M, 11648, 421875 }, + { 96000, TMDS_445_5M, 10240, 371250 }, + { 192000, TMDS_445M, 23296, 421875 }, + { 192000, TMDS_445_5M, 20480, 371250 }, +}; + +/* get AUD_CONFIG_PIXEL_CLOCK_HDMI_* value for mode */ +static u32 audio_config_hdmi_pixel_clock(const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + const struct drm_display_mode *adjusted_mode = + &crtc_state->hw.adjusted_mode; + int i; + + for (i = 0; i < ARRAY_SIZE(hdmi_audio_clock); i++) { + if (adjusted_mode->crtc_clock == hdmi_audio_clock[i].clock) + break; + } + + if (DISPLAY_VER(dev_priv) < 12 && adjusted_mode->crtc_clock > 148500) + i = ARRAY_SIZE(hdmi_audio_clock); + + if (i == ARRAY_SIZE(hdmi_audio_clock)) { + drm_dbg_kms(&dev_priv->drm, + "HDMI audio pixel clock setting for %d not found, falling back to defaults\n", + adjusted_mode->crtc_clock); + i = 1; + } + + drm_dbg_kms(&dev_priv->drm, + "Configuring HDMI audio for pixel clock %d (0x%08x)\n", + hdmi_audio_clock[i].clock, + hdmi_audio_clock[i].config); + + return hdmi_audio_clock[i].config; +} + +static int audio_config_hdmi_get_n(const struct intel_crtc_state *crtc_state, + int rate) +{ + const struct hdmi_aud_ncts *hdmi_ncts_table; + int i, size; + + if (crtc_state->pipe_bpp == 36) { + hdmi_ncts_table = hdmi_aud_ncts_36bpp; + size = ARRAY_SIZE(hdmi_aud_ncts_36bpp); + } else if (crtc_state->pipe_bpp == 30) { + hdmi_ncts_table = hdmi_aud_ncts_30bpp; + size = ARRAY_SIZE(hdmi_aud_ncts_30bpp); + } else { + hdmi_ncts_table = hdmi_aud_ncts_24bpp; + size = ARRAY_SIZE(hdmi_aud_ncts_24bpp); + } + + for (i = 0; i < size; i++) { + if (rate == hdmi_ncts_table[i].sample_rate && + crtc_state->port_clock == hdmi_ncts_table[i].clock) { + return hdmi_ncts_table[i].n; + } + } + return 0; +} + +static bool intel_eld_uptodate(struct drm_connector *connector, + i915_reg_t reg_eldv, u32 bits_eldv, + i915_reg_t reg_elda, u32 bits_elda, + i915_reg_t reg_edid) +{ + struct drm_i915_private *dev_priv = to_i915(connector->dev); + const u8 *eld = connector->eld; + u32 tmp; + int i; + + tmp = intel_de_read(dev_priv, reg_eldv); + tmp &= bits_eldv; + + if (!tmp) + return false; + + tmp = intel_de_read(dev_priv, reg_elda); + tmp &= ~bits_elda; + intel_de_write(dev_priv, reg_elda, tmp); + + for (i = 0; i < drm_eld_size(eld) / 4; i++) + if (intel_de_read(dev_priv, reg_edid) != *((const u32 *)eld + i)) + return false; + + return true; +} + +static void g4x_audio_codec_disable(struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + u32 eldv, tmp; + + tmp = intel_de_read(dev_priv, G4X_AUD_VID_DID); + if (tmp == INTEL_AUDIO_DEVBLC || tmp == INTEL_AUDIO_DEVCL) + eldv = G4X_ELDV_DEVCL_DEVBLC; + else + eldv = G4X_ELDV_DEVCTG; + + /* Invalidate ELD */ + tmp = intel_de_read(dev_priv, G4X_AUD_CNTL_ST); + tmp &= ~eldv; + intel_de_write(dev_priv, G4X_AUD_CNTL_ST, tmp); +} + +static void g4x_audio_codec_enable(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct drm_connector *connector = conn_state->connector; + const u8 *eld = connector->eld; + u32 eldv; + u32 tmp; + int len, i; + + tmp = intel_de_read(dev_priv, G4X_AUD_VID_DID); + if (tmp == INTEL_AUDIO_DEVBLC || tmp == INTEL_AUDIO_DEVCL) + eldv = G4X_ELDV_DEVCL_DEVBLC; + else + eldv = G4X_ELDV_DEVCTG; + + if (intel_eld_uptodate(connector, + G4X_AUD_CNTL_ST, eldv, + G4X_AUD_CNTL_ST, G4X_ELD_ADDR_MASK, + G4X_HDMIW_HDMIEDID)) + return; + + tmp = intel_de_read(dev_priv, G4X_AUD_CNTL_ST); + tmp &= ~(eldv | G4X_ELD_ADDR_MASK); + len = (tmp >> 9) & 0x1f; /* ELD buffer size */ + intel_de_write(dev_priv, G4X_AUD_CNTL_ST, tmp); + + len = min(drm_eld_size(eld) / 4, len); + for (i = 0; i < len; i++) + intel_de_write(dev_priv, G4X_HDMIW_HDMIEDID, + *((const u32 *)eld + i)); + + tmp = intel_de_read(dev_priv, G4X_AUD_CNTL_ST); + tmp |= eldv; + intel_de_write(dev_priv, G4X_AUD_CNTL_ST, tmp); +} + +static void +hsw_dp_audio_config_update(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct i915_audio_component *acomp = dev_priv->display.audio.component; + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + enum port port = encoder->port; + const struct dp_aud_n_m *nm; + int rate; + u32 tmp; + + rate = acomp ? acomp->aud_sample_rate[port] : 0; + nm = audio_config_dp_get_n_m(crtc_state, rate); + if (nm) + drm_dbg_kms(&dev_priv->drm, "using Maud %u, Naud %u\n", nm->m, + nm->n); + else + drm_dbg_kms(&dev_priv->drm, "using automatic Maud, Naud\n"); + + tmp = intel_de_read(dev_priv, HSW_AUD_CFG(cpu_transcoder)); + tmp &= ~AUD_CONFIG_N_VALUE_INDEX; + tmp &= ~AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK; + tmp &= ~AUD_CONFIG_N_PROG_ENABLE; + tmp |= AUD_CONFIG_N_VALUE_INDEX; + + if (nm) { + tmp &= ~AUD_CONFIG_N_MASK; + tmp |= AUD_CONFIG_N(nm->n); + tmp |= AUD_CONFIG_N_PROG_ENABLE; + } + + intel_de_write(dev_priv, HSW_AUD_CFG(cpu_transcoder), tmp); + + tmp = intel_de_read(dev_priv, HSW_AUD_M_CTS_ENABLE(cpu_transcoder)); + tmp &= ~AUD_CONFIG_M_MASK; + tmp &= ~AUD_M_CTS_M_VALUE_INDEX; + tmp &= ~AUD_M_CTS_M_PROG_ENABLE; + + if (nm) { + tmp |= nm->m; + tmp |= AUD_M_CTS_M_VALUE_INDEX; + tmp |= AUD_M_CTS_M_PROG_ENABLE; + } + + intel_de_write(dev_priv, HSW_AUD_M_CTS_ENABLE(cpu_transcoder), tmp); +} + +static void +hsw_hdmi_audio_config_update(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct i915_audio_component *acomp = dev_priv->display.audio.component; + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + enum port port = encoder->port; + int n, rate; + u32 tmp; + + rate = acomp ? acomp->aud_sample_rate[port] : 0; + + tmp = intel_de_read(dev_priv, HSW_AUD_CFG(cpu_transcoder)); + tmp &= ~AUD_CONFIG_N_VALUE_INDEX; + tmp &= ~AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK; + tmp &= ~AUD_CONFIG_N_PROG_ENABLE; + tmp |= audio_config_hdmi_pixel_clock(crtc_state); + + n = audio_config_hdmi_get_n(crtc_state, rate); + if (n != 0) { + drm_dbg_kms(&dev_priv->drm, "using N %d\n", n); + + tmp &= ~AUD_CONFIG_N_MASK; + tmp |= AUD_CONFIG_N(n); + tmp |= AUD_CONFIG_N_PROG_ENABLE; + } else { + drm_dbg_kms(&dev_priv->drm, "using automatic N\n"); + } + + intel_de_write(dev_priv, HSW_AUD_CFG(cpu_transcoder), tmp); + + /* + * Let's disable "Enable CTS or M Prog bit" + * and let HW calculate the value + */ + tmp = intel_de_read(dev_priv, HSW_AUD_M_CTS_ENABLE(cpu_transcoder)); + tmp &= ~AUD_M_CTS_M_PROG_ENABLE; + tmp &= ~AUD_M_CTS_M_VALUE_INDEX; + intel_de_write(dev_priv, HSW_AUD_M_CTS_ENABLE(cpu_transcoder), tmp); +} + +static void +hsw_audio_config_update(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + if (intel_crtc_has_dp_encoder(crtc_state)) + hsw_dp_audio_config_update(encoder, crtc_state); + else + hsw_hdmi_audio_config_update(encoder, crtc_state); +} + +static void hsw_audio_codec_disable(struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + enum transcoder cpu_transcoder = old_crtc_state->cpu_transcoder; + u32 tmp; + + mutex_lock(&dev_priv->display.audio.mutex); + + /* Disable timestamps */ + tmp = intel_de_read(dev_priv, HSW_AUD_CFG(cpu_transcoder)); + tmp &= ~AUD_CONFIG_N_VALUE_INDEX; + tmp |= AUD_CONFIG_N_PROG_ENABLE; + tmp &= ~AUD_CONFIG_UPPER_N_MASK; + tmp &= ~AUD_CONFIG_LOWER_N_MASK; + if (intel_crtc_has_dp_encoder(old_crtc_state)) + tmp |= AUD_CONFIG_N_VALUE_INDEX; + intel_de_write(dev_priv, HSW_AUD_CFG(cpu_transcoder), tmp); + + /* Invalidate ELD */ + tmp = intel_de_read(dev_priv, HSW_AUD_PIN_ELD_CP_VLD); + tmp &= ~AUDIO_ELD_VALID(cpu_transcoder); + tmp &= ~AUDIO_OUTPUT_ENABLE(cpu_transcoder); + intel_de_write(dev_priv, HSW_AUD_PIN_ELD_CP_VLD, tmp); + + mutex_unlock(&dev_priv->display.audio.mutex); +} + +static unsigned int calc_hblank_early_prog(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + unsigned int link_clks_available, link_clks_required; + unsigned int tu_data, tu_line, link_clks_active; + unsigned int h_active, h_total, hblank_delta, pixel_clk; + unsigned int fec_coeff, cdclk, vdsc_bpp; + unsigned int link_clk, lanes; + unsigned int hblank_rise; + + h_active = crtc_state->hw.adjusted_mode.crtc_hdisplay; + h_total = crtc_state->hw.adjusted_mode.crtc_htotal; + pixel_clk = crtc_state->hw.adjusted_mode.crtc_clock; + vdsc_bpp = crtc_state->dsc.compressed_bpp; + cdclk = i915->display.cdclk.hw.cdclk; + /* fec= 0.972261, using rounding multiplier of 1000000 */ + fec_coeff = 972261; + link_clk = crtc_state->port_clock; + lanes = crtc_state->lane_count; + + drm_dbg_kms(&i915->drm, "h_active = %u link_clk = %u :" + "lanes = %u vdsc_bpp = %u cdclk = %u\n", + h_active, link_clk, lanes, vdsc_bpp, cdclk); + + if (WARN_ON(!link_clk || !pixel_clk || !lanes || !vdsc_bpp || !cdclk)) + return 0; + + link_clks_available = (h_total - h_active) * link_clk / pixel_clk - 28; + link_clks_required = DIV_ROUND_UP(192000 * h_total, 1000 * pixel_clk) * (48 / lanes + 2); + + if (link_clks_available > link_clks_required) + hblank_delta = 32; + else + hblank_delta = DIV64_U64_ROUND_UP(mul_u32_u32(5 * (link_clk + cdclk), pixel_clk), + mul_u32_u32(link_clk, cdclk)); + + tu_data = div64_u64(mul_u32_u32(pixel_clk * vdsc_bpp * 8, 1000000), + mul_u32_u32(link_clk * lanes, fec_coeff)); + tu_line = div64_u64(h_active * mul_u32_u32(link_clk, fec_coeff), + mul_u32_u32(64 * pixel_clk, 1000000)); + link_clks_active = (tu_line - 1) * 64 + tu_data; + + hblank_rise = (link_clks_active + 6 * DIV_ROUND_UP(link_clks_active, 250) + 4) * pixel_clk / link_clk; + + return h_active - hblank_rise + hblank_delta; +} + +static unsigned int calc_samples_room(const struct intel_crtc_state *crtc_state) +{ + unsigned int h_active, h_total, pixel_clk; + unsigned int link_clk, lanes; + + h_active = crtc_state->hw.adjusted_mode.hdisplay; + h_total = crtc_state->hw.adjusted_mode.htotal; + pixel_clk = crtc_state->hw.adjusted_mode.clock; + link_clk = crtc_state->port_clock; + lanes = crtc_state->lane_count; + + return ((h_total - h_active) * link_clk - 12 * pixel_clk) / + (pixel_clk * (48 / lanes + 2)); +} + +static void enable_audio_dsc_wa(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + enum pipe pipe = crtc->pipe; + unsigned int hblank_early_prog, samples_room; + unsigned int val; + + if (DISPLAY_VER(i915) < 11) + return; + + val = intel_de_read(i915, AUD_CONFIG_BE); + + if (DISPLAY_VER(i915) == 11) + val |= HBLANK_EARLY_ENABLE_ICL(pipe); + else if (DISPLAY_VER(i915) >= 12) + val |= HBLANK_EARLY_ENABLE_TGL(pipe); + + if (crtc_state->dsc.compression_enable && + crtc_state->hw.adjusted_mode.hdisplay >= 3840 && + crtc_state->hw.adjusted_mode.vdisplay >= 2160) { + /* Get hblank early enable value required */ + val &= ~HBLANK_START_COUNT_MASK(pipe); + hblank_early_prog = calc_hblank_early_prog(encoder, crtc_state); + if (hblank_early_prog < 32) + val |= HBLANK_START_COUNT(pipe, HBLANK_START_COUNT_32); + else if (hblank_early_prog < 64) + val |= HBLANK_START_COUNT(pipe, HBLANK_START_COUNT_64); + else if (hblank_early_prog < 96) + val |= HBLANK_START_COUNT(pipe, HBLANK_START_COUNT_96); + else + val |= HBLANK_START_COUNT(pipe, HBLANK_START_COUNT_128); + + /* Get samples room value required */ + val &= ~NUMBER_SAMPLES_PER_LINE_MASK(pipe); + samples_room = calc_samples_room(crtc_state); + if (samples_room < 3) + val |= NUMBER_SAMPLES_PER_LINE(pipe, samples_room); + else /* Program 0 i.e "All Samples available in buffer" */ + val |= NUMBER_SAMPLES_PER_LINE(pipe, 0x0); + } + + intel_de_write(i915, AUD_CONFIG_BE, val); +} + +#undef ROUNDING_FACTOR + +static void hsw_audio_codec_enable(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct drm_connector *connector = conn_state->connector; + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + const u8 *eld = connector->eld; + u32 tmp; + int len, i; + + mutex_lock(&dev_priv->display.audio.mutex); + + /* Enable Audio WA for 4k DSC usecases */ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP)) + enable_audio_dsc_wa(encoder, crtc_state); + + /* Enable audio presence detect, invalidate ELD */ + tmp = intel_de_read(dev_priv, HSW_AUD_PIN_ELD_CP_VLD); + tmp |= AUDIO_OUTPUT_ENABLE(cpu_transcoder); + tmp &= ~AUDIO_ELD_VALID(cpu_transcoder); + intel_de_write(dev_priv, HSW_AUD_PIN_ELD_CP_VLD, tmp); + + /* + * FIXME: We're supposed to wait for vblank here, but we have vblanks + * disabled during the mode set. The proper fix would be to push the + * rest of the setup into a vblank work item, queued here, but the + * infrastructure is not there yet. + */ + + /* Reset ELD write address */ + tmp = intel_de_read(dev_priv, HSW_AUD_DIP_ELD_CTRL(cpu_transcoder)); + tmp &= ~IBX_ELD_ADDRESS_MASK; + intel_de_write(dev_priv, HSW_AUD_DIP_ELD_CTRL(cpu_transcoder), tmp); + + /* Up to 84 bytes of hw ELD buffer */ + len = min(drm_eld_size(eld), 84); + for (i = 0; i < len / 4; i++) + intel_de_write(dev_priv, HSW_AUD_EDID_DATA(cpu_transcoder), + *((const u32 *)eld + i)); + + /* ELD valid */ + tmp = intel_de_read(dev_priv, HSW_AUD_PIN_ELD_CP_VLD); + tmp |= AUDIO_ELD_VALID(cpu_transcoder); + intel_de_write(dev_priv, HSW_AUD_PIN_ELD_CP_VLD, tmp); + + /* Enable timestamps */ + hsw_audio_config_update(encoder, crtc_state); + + mutex_unlock(&dev_priv->display.audio.mutex); +} + +static void ilk_audio_codec_disable(struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->uapi.crtc); + enum pipe pipe = crtc->pipe; + enum port port = encoder->port; + u32 tmp, eldv; + i915_reg_t aud_config, aud_cntrl_st2; + + if (drm_WARN_ON(&dev_priv->drm, port == PORT_A)) + return; + + if (HAS_PCH_IBX(dev_priv)) { + aud_config = IBX_AUD_CFG(pipe); + aud_cntrl_st2 = IBX_AUD_CNTL_ST2; + } else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { + aud_config = VLV_AUD_CFG(pipe); + aud_cntrl_st2 = VLV_AUD_CNTL_ST2; + } else { + aud_config = CPT_AUD_CFG(pipe); + aud_cntrl_st2 = CPT_AUD_CNTRL_ST2; + } + + /* Disable timestamps */ + tmp = intel_de_read(dev_priv, aud_config); + tmp &= ~AUD_CONFIG_N_VALUE_INDEX; + tmp |= AUD_CONFIG_N_PROG_ENABLE; + tmp &= ~AUD_CONFIG_UPPER_N_MASK; + tmp &= ~AUD_CONFIG_LOWER_N_MASK; + if (intel_crtc_has_dp_encoder(old_crtc_state)) + tmp |= AUD_CONFIG_N_VALUE_INDEX; + intel_de_write(dev_priv, aud_config, tmp); + + eldv = IBX_ELD_VALID(port); + + /* Invalidate ELD */ + tmp = intel_de_read(dev_priv, aud_cntrl_st2); + tmp &= ~eldv; + intel_de_write(dev_priv, aud_cntrl_st2, tmp); +} + +static void ilk_audio_codec_enable(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_connector *connector = conn_state->connector; + enum pipe pipe = crtc->pipe; + enum port port = encoder->port; + const u8 *eld = connector->eld; + u32 tmp, eldv; + int len, i; + i915_reg_t hdmiw_hdmiedid, aud_config, aud_cntl_st, aud_cntrl_st2; + + if (drm_WARN_ON(&dev_priv->drm, port == PORT_A)) + return; + + /* + * FIXME: We're supposed to wait for vblank here, but we have vblanks + * disabled during the mode set. The proper fix would be to push the + * rest of the setup into a vblank work item, queued here, but the + * infrastructure is not there yet. + */ + + if (HAS_PCH_IBX(dev_priv)) { + hdmiw_hdmiedid = IBX_HDMIW_HDMIEDID(pipe); + aud_config = IBX_AUD_CFG(pipe); + aud_cntl_st = IBX_AUD_CNTL_ST(pipe); + aud_cntrl_st2 = IBX_AUD_CNTL_ST2; + } else if (IS_VALLEYVIEW(dev_priv) || + IS_CHERRYVIEW(dev_priv)) { + hdmiw_hdmiedid = VLV_HDMIW_HDMIEDID(pipe); + aud_config = VLV_AUD_CFG(pipe); + aud_cntl_st = VLV_AUD_CNTL_ST(pipe); + aud_cntrl_st2 = VLV_AUD_CNTL_ST2; + } else { + hdmiw_hdmiedid = CPT_HDMIW_HDMIEDID(pipe); + aud_config = CPT_AUD_CFG(pipe); + aud_cntl_st = CPT_AUD_CNTL_ST(pipe); + aud_cntrl_st2 = CPT_AUD_CNTRL_ST2; + } + + eldv = IBX_ELD_VALID(port); + + /* Invalidate ELD */ + tmp = intel_de_read(dev_priv, aud_cntrl_st2); + tmp &= ~eldv; + intel_de_write(dev_priv, aud_cntrl_st2, tmp); + + /* Reset ELD write address */ + tmp = intel_de_read(dev_priv, aud_cntl_st); + tmp &= ~IBX_ELD_ADDRESS_MASK; + intel_de_write(dev_priv, aud_cntl_st, tmp); + + /* Up to 84 bytes of hw ELD buffer */ + len = min(drm_eld_size(eld), 84); + for (i = 0; i < len / 4; i++) + intel_de_write(dev_priv, hdmiw_hdmiedid, + *((const u32 *)eld + i)); + + /* ELD valid */ + tmp = intel_de_read(dev_priv, aud_cntrl_st2); + tmp |= eldv; + intel_de_write(dev_priv, aud_cntrl_st2, tmp); + + /* Enable timestamps */ + tmp = intel_de_read(dev_priv, aud_config); + tmp &= ~AUD_CONFIG_N_VALUE_INDEX; + tmp &= ~AUD_CONFIG_N_PROG_ENABLE; + tmp &= ~AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK; + if (intel_crtc_has_dp_encoder(crtc_state)) + tmp |= AUD_CONFIG_N_VALUE_INDEX; + else + tmp |= audio_config_hdmi_pixel_clock(crtc_state); + intel_de_write(dev_priv, aud_config, tmp); +} + +/** + * intel_audio_codec_enable - Enable the audio codec for HD audio + * @encoder: encoder on which to enable audio + * @crtc_state: pointer to the current crtc state. + * @conn_state: pointer to the current connector state. + * + * The enable sequences may only be performed after enabling the transcoder and + * port, and after completed link training. + */ +void intel_audio_codec_enable(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct i915_audio_component *acomp = dev_priv->display.audio.component; + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_connector *connector = conn_state->connector; + const struct drm_display_mode *adjusted_mode = + &crtc_state->hw.adjusted_mode; + enum port port = encoder->port; + enum pipe pipe = crtc->pipe; + + if (!crtc_state->has_audio) + return; + + drm_dbg_kms(&dev_priv->drm, "[CONNECTOR:%d:%s][ENCODER:%d:%s] Enable audio codec on pipe %c, %u bytes ELD\n", + connector->base.id, connector->name, + encoder->base.base.id, encoder->base.name, + pipe_name(pipe), drm_eld_size(connector->eld)); + + /* FIXME precompute the ELD in .compute_config() */ + if (!connector->eld[0]) + drm_dbg_kms(&dev_priv->drm, + "Bogus ELD on [CONNECTOR:%d:%s]\n", + connector->base.id, connector->name); + + connector->eld[6] = drm_av_sync_delay(connector, adjusted_mode) / 2; + + if (dev_priv->display.funcs.audio) + dev_priv->display.funcs.audio->audio_codec_enable(encoder, + crtc_state, + conn_state); + + mutex_lock(&dev_priv->display.audio.mutex); + encoder->audio_connector = connector; + + /* referred in audio callbacks */ + dev_priv->display.audio.encoder_map[pipe] = encoder; + mutex_unlock(&dev_priv->display.audio.mutex); + + if (acomp && acomp->base.audio_ops && + acomp->base.audio_ops->pin_eld_notify) { + /* audio drivers expect pipe = -1 to indicate Non-MST cases */ + if (!intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST)) + pipe = -1; + acomp->base.audio_ops->pin_eld_notify(acomp->base.audio_ops->audio_ptr, + (int) port, (int) pipe); + } + + intel_lpe_audio_notify(dev_priv, pipe, port, connector->eld, + crtc_state->port_clock, + intel_crtc_has_dp_encoder(crtc_state)); +} + +/** + * intel_audio_codec_disable - Disable the audio codec for HD audio + * @encoder: encoder on which to disable audio + * @old_crtc_state: pointer to the old crtc state. + * @old_conn_state: pointer to the old connector state. + * + * The disable sequences must be performed before disabling the transcoder or + * port. + */ +void intel_audio_codec_disable(struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct i915_audio_component *acomp = dev_priv->display.audio.component; + struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->uapi.crtc); + struct drm_connector *connector = old_conn_state->connector; + enum port port = encoder->port; + enum pipe pipe = crtc->pipe; + + if (!old_crtc_state->has_audio) + return; + + drm_dbg_kms(&dev_priv->drm, "[CONNECTOR:%d:%s][ENCODER:%d:%s] Disable audio codec on pipe %c\n", + connector->base.id, connector->name, + encoder->base.base.id, encoder->base.name, pipe_name(pipe)); + + if (dev_priv->display.funcs.audio) + dev_priv->display.funcs.audio->audio_codec_disable(encoder, + old_crtc_state, + old_conn_state); + + mutex_lock(&dev_priv->display.audio.mutex); + encoder->audio_connector = NULL; + dev_priv->display.audio.encoder_map[pipe] = NULL; + mutex_unlock(&dev_priv->display.audio.mutex); + + if (acomp && acomp->base.audio_ops && + acomp->base.audio_ops->pin_eld_notify) { + /* audio drivers expect pipe = -1 to indicate Non-MST cases */ + if (!intel_crtc_has_type(old_crtc_state, INTEL_OUTPUT_DP_MST)) + pipe = -1; + acomp->base.audio_ops->pin_eld_notify(acomp->base.audio_ops->audio_ptr, + (int) port, (int) pipe); + } + + intel_lpe_audio_notify(dev_priv, pipe, port, NULL, 0, false); +} + +static const struct intel_audio_funcs g4x_audio_funcs = { + .audio_codec_enable = g4x_audio_codec_enable, + .audio_codec_disable = g4x_audio_codec_disable, +}; + +static const struct intel_audio_funcs ilk_audio_funcs = { + .audio_codec_enable = ilk_audio_codec_enable, + .audio_codec_disable = ilk_audio_codec_disable, +}; + +static const struct intel_audio_funcs hsw_audio_funcs = { + .audio_codec_enable = hsw_audio_codec_enable, + .audio_codec_disable = hsw_audio_codec_disable, +}; + +/** + * intel_audio_hooks_init - Set up chip specific audio hooks + * @dev_priv: device private + */ +void intel_audio_hooks_init(struct drm_i915_private *dev_priv) +{ + if (IS_G4X(dev_priv)) { + dev_priv->display.funcs.audio = &g4x_audio_funcs; + } else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { + dev_priv->display.funcs.audio = &ilk_audio_funcs; + } else if (IS_HASWELL(dev_priv) || DISPLAY_VER(dev_priv) >= 8) { + dev_priv->display.funcs.audio = &hsw_audio_funcs; + } else if (HAS_PCH_SPLIT(dev_priv)) { + dev_priv->display.funcs.audio = &ilk_audio_funcs; + } +} + +struct aud_ts_cdclk_m_n { + u8 m; + u16 n; +}; + +void intel_audio_cdclk_change_pre(struct drm_i915_private *i915) +{ + if (DISPLAY_VER(i915) >= 13) + intel_de_rmw(i915, AUD_TS_CDCLK_M, AUD_TS_CDCLK_M_EN, 0); +} + +static void get_aud_ts_cdclk_m_n(int refclk, int cdclk, struct aud_ts_cdclk_m_n *aud_ts) +{ + if (refclk == 24000) + aud_ts->m = 12; + else + aud_ts->m = 15; + + aud_ts->n = cdclk * aud_ts->m / 24000; +} + +void intel_audio_cdclk_change_post(struct drm_i915_private *i915) +{ + struct aud_ts_cdclk_m_n aud_ts; + + if (DISPLAY_VER(i915) >= 13) { + get_aud_ts_cdclk_m_n(i915->display.cdclk.hw.ref, i915->display.cdclk.hw.cdclk, &aud_ts); + + intel_de_write(i915, AUD_TS_CDCLK_N, aud_ts.n); + intel_de_write(i915, AUD_TS_CDCLK_M, aud_ts.m | AUD_TS_CDCLK_M_EN); + drm_dbg_kms(&i915->drm, "aud_ts_cdclk set to M=%u, N=%u\n", aud_ts.m, aud_ts.n); + } +} + +static int glk_force_audio_cdclk_commit(struct intel_atomic_state *state, + struct intel_crtc *crtc, + bool enable) +{ + struct intel_cdclk_state *cdclk_state; + int ret; + + /* need to hold at least one crtc lock for the global state */ + ret = drm_modeset_lock(&crtc->base.mutex, state->base.acquire_ctx); + if (ret) + return ret; + + cdclk_state = intel_atomic_get_cdclk_state(state); + if (IS_ERR(cdclk_state)) + return PTR_ERR(cdclk_state); + + cdclk_state->force_min_cdclk = enable ? 2 * 96000 : 0; + + return drm_atomic_commit(&state->base); +} + +static void glk_force_audio_cdclk(struct drm_i915_private *dev_priv, + bool enable) +{ + struct drm_modeset_acquire_ctx ctx; + struct drm_atomic_state *state; + struct intel_crtc *crtc; + int ret; + + crtc = intel_first_crtc(dev_priv); + if (!crtc) + return; + + drm_modeset_acquire_init(&ctx, 0); + state = drm_atomic_state_alloc(&dev_priv->drm); + if (drm_WARN_ON(&dev_priv->drm, !state)) + return; + + state->acquire_ctx = &ctx; + +retry: + ret = glk_force_audio_cdclk_commit(to_intel_atomic_state(state), crtc, + enable); + if (ret == -EDEADLK) { + drm_atomic_state_clear(state); + drm_modeset_backoff(&ctx); + goto retry; + } + + drm_WARN_ON(&dev_priv->drm, ret); + + drm_atomic_state_put(state); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +static unsigned long i915_audio_component_get_power(struct device *kdev) +{ + struct drm_i915_private *dev_priv = kdev_to_i915(kdev); + intel_wakeref_t ret; + + /* Catch potential impedance mismatches before they occur! */ + BUILD_BUG_ON(sizeof(intel_wakeref_t) > sizeof(unsigned long)); + + ret = intel_display_power_get(dev_priv, POWER_DOMAIN_AUDIO_PLAYBACK); + + if (dev_priv->display.audio.power_refcount++ == 0) { + if (DISPLAY_VER(dev_priv) >= 9) { + intel_de_write(dev_priv, AUD_FREQ_CNTRL, + dev_priv->display.audio.freq_cntrl); + drm_dbg_kms(&dev_priv->drm, + "restored AUD_FREQ_CNTRL to 0x%x\n", + dev_priv->display.audio.freq_cntrl); + } + + /* Force CDCLK to 2*BCLK as long as we need audio powered. */ + if (IS_GEMINILAKE(dev_priv)) + glk_force_audio_cdclk(dev_priv, true); + + if (DISPLAY_VER(dev_priv) >= 10) + intel_de_write(dev_priv, AUD_PIN_BUF_CTL, + (intel_de_read(dev_priv, AUD_PIN_BUF_CTL) | AUD_PIN_BUF_ENABLE)); + } + + return ret; +} + +static void i915_audio_component_put_power(struct device *kdev, + unsigned long cookie) +{ + struct drm_i915_private *dev_priv = kdev_to_i915(kdev); + + /* Stop forcing CDCLK to 2*BCLK if no need for audio to be powered. */ + if (--dev_priv->display.audio.power_refcount == 0) + if (IS_GEMINILAKE(dev_priv)) + glk_force_audio_cdclk(dev_priv, false); + + intel_display_power_put(dev_priv, POWER_DOMAIN_AUDIO_PLAYBACK, cookie); +} + +static void i915_audio_component_codec_wake_override(struct device *kdev, + bool enable) +{ + struct drm_i915_private *dev_priv = kdev_to_i915(kdev); + unsigned long cookie; + u32 tmp; + + if (DISPLAY_VER(dev_priv) < 9) + return; + + cookie = i915_audio_component_get_power(kdev); + + /* + * Enable/disable generating the codec wake signal, overriding the + * internal logic to generate the codec wake to controller. + */ + tmp = intel_de_read(dev_priv, HSW_AUD_CHICKENBIT); + tmp &= ~SKL_AUD_CODEC_WAKE_SIGNAL; + intel_de_write(dev_priv, HSW_AUD_CHICKENBIT, tmp); + usleep_range(1000, 1500); + + if (enable) { + tmp = intel_de_read(dev_priv, HSW_AUD_CHICKENBIT); + tmp |= SKL_AUD_CODEC_WAKE_SIGNAL; + intel_de_write(dev_priv, HSW_AUD_CHICKENBIT, tmp); + usleep_range(1000, 1500); + } + + i915_audio_component_put_power(kdev, cookie); +} + +/* Get CDCLK in kHz */ +static int i915_audio_component_get_cdclk_freq(struct device *kdev) +{ + struct drm_i915_private *dev_priv = kdev_to_i915(kdev); + + if (drm_WARN_ON_ONCE(&dev_priv->drm, !HAS_DDI(dev_priv))) + return -ENODEV; + + return dev_priv->display.cdclk.hw.cdclk; +} + +/* + * get the intel_encoder according to the parameter port and pipe + * intel_encoder is saved by the index of pipe + * MST & (pipe >= 0): return the audio.encoder_map[pipe], + * when port is matched + * MST & (pipe < 0): this is invalid + * Non-MST & (pipe >= 0): only pipe = 0 (the first device entry) + * will get the right intel_encoder with port matched + * Non-MST & (pipe < 0): get the right intel_encoder with port matched + */ +static struct intel_encoder *get_saved_enc(struct drm_i915_private *dev_priv, + int port, int pipe) +{ + struct intel_encoder *encoder; + + /* MST */ + if (pipe >= 0) { + if (drm_WARN_ON(&dev_priv->drm, + pipe >= ARRAY_SIZE(dev_priv->display.audio.encoder_map))) + return NULL; + + encoder = dev_priv->display.audio.encoder_map[pipe]; + /* + * when bootup, audio driver may not know it is + * MST or not. So it will poll all the port & pipe + * combinations + */ + if (encoder != NULL && encoder->port == port && + encoder->type == INTEL_OUTPUT_DP_MST) + return encoder; + } + + /* Non-MST */ + if (pipe > 0) + return NULL; + + for_each_pipe(dev_priv, pipe) { + encoder = dev_priv->display.audio.encoder_map[pipe]; + if (encoder == NULL) + continue; + + if (encoder->type == INTEL_OUTPUT_DP_MST) + continue; + + if (port == encoder->port) + return encoder; + } + + return NULL; +} + +static int i915_audio_component_sync_audio_rate(struct device *kdev, int port, + int pipe, int rate) +{ + struct drm_i915_private *dev_priv = kdev_to_i915(kdev); + struct i915_audio_component *acomp = dev_priv->display.audio.component; + struct intel_encoder *encoder; + struct intel_crtc *crtc; + unsigned long cookie; + int err = 0; + + if (!HAS_DDI(dev_priv)) + return 0; + + cookie = i915_audio_component_get_power(kdev); + mutex_lock(&dev_priv->display.audio.mutex); + + /* 1. get the pipe */ + encoder = get_saved_enc(dev_priv, port, pipe); + if (!encoder || !encoder->base.crtc) { + drm_dbg_kms(&dev_priv->drm, "Not valid for port %c\n", + port_name(port)); + err = -ENODEV; + goto unlock; + } + + crtc = to_intel_crtc(encoder->base.crtc); + + /* port must be valid now, otherwise the pipe will be invalid */ + acomp->aud_sample_rate[port] = rate; + + hsw_audio_config_update(encoder, crtc->config); + + unlock: + mutex_unlock(&dev_priv->display.audio.mutex); + i915_audio_component_put_power(kdev, cookie); + return err; +} + +static int i915_audio_component_get_eld(struct device *kdev, int port, + int pipe, bool *enabled, + unsigned char *buf, int max_bytes) +{ + struct drm_i915_private *dev_priv = kdev_to_i915(kdev); + struct intel_encoder *intel_encoder; + const u8 *eld; + int ret = -EINVAL; + + mutex_lock(&dev_priv->display.audio.mutex); + + intel_encoder = get_saved_enc(dev_priv, port, pipe); + if (!intel_encoder) { + drm_dbg_kms(&dev_priv->drm, "Not valid for port %c\n", + port_name(port)); + mutex_unlock(&dev_priv->display.audio.mutex); + return ret; + } + + ret = 0; + *enabled = intel_encoder->audio_connector != NULL; + if (*enabled) { + eld = intel_encoder->audio_connector->eld; + ret = drm_eld_size(eld); + memcpy(buf, eld, min(max_bytes, ret)); + } + + mutex_unlock(&dev_priv->display.audio.mutex); + return ret; +} + +static const struct drm_audio_component_ops i915_audio_component_ops = { + .owner = THIS_MODULE, + .get_power = i915_audio_component_get_power, + .put_power = i915_audio_component_put_power, + .codec_wake_override = i915_audio_component_codec_wake_override, + .get_cdclk_freq = i915_audio_component_get_cdclk_freq, + .sync_audio_rate = i915_audio_component_sync_audio_rate, + .get_eld = i915_audio_component_get_eld, +}; + +static int i915_audio_component_bind(struct device *i915_kdev, + struct device *hda_kdev, void *data) +{ + struct i915_audio_component *acomp = data; + struct drm_i915_private *dev_priv = kdev_to_i915(i915_kdev); + int i; + + if (drm_WARN_ON(&dev_priv->drm, acomp->base.ops || acomp->base.dev)) + return -EEXIST; + + if (drm_WARN_ON(&dev_priv->drm, + !device_link_add(hda_kdev, i915_kdev, + DL_FLAG_STATELESS))) + return -ENOMEM; + + drm_modeset_lock_all(&dev_priv->drm); + acomp->base.ops = &i915_audio_component_ops; + acomp->base.dev = i915_kdev; + BUILD_BUG_ON(MAX_PORTS != I915_MAX_PORTS); + for (i = 0; i < ARRAY_SIZE(acomp->aud_sample_rate); i++) + acomp->aud_sample_rate[i] = 0; + dev_priv->display.audio.component = acomp; + drm_modeset_unlock_all(&dev_priv->drm); + + return 0; +} + +static void i915_audio_component_unbind(struct device *i915_kdev, + struct device *hda_kdev, void *data) +{ + struct i915_audio_component *acomp = data; + struct drm_i915_private *dev_priv = kdev_to_i915(i915_kdev); + + drm_modeset_lock_all(&dev_priv->drm); + acomp->base.ops = NULL; + acomp->base.dev = NULL; + dev_priv->display.audio.component = NULL; + drm_modeset_unlock_all(&dev_priv->drm); + + device_link_remove(hda_kdev, i915_kdev); + + if (dev_priv->display.audio.power_refcount) + drm_err(&dev_priv->drm, "audio power refcount %d after unbind\n", + dev_priv->display.audio.power_refcount); +} + +static const struct component_ops i915_audio_component_bind_ops = { + .bind = i915_audio_component_bind, + .unbind = i915_audio_component_unbind, +}; + +#define AUD_FREQ_TMODE_SHIFT 14 +#define AUD_FREQ_4T 0 +#define AUD_FREQ_8T (2 << AUD_FREQ_TMODE_SHIFT) +#define AUD_FREQ_PULLCLKS(x) (((x) & 0x3) << 11) +#define AUD_FREQ_BCLK_96M BIT(4) + +#define AUD_FREQ_GEN12 (AUD_FREQ_8T | AUD_FREQ_PULLCLKS(0) | AUD_FREQ_BCLK_96M) +#define AUD_FREQ_TGL_BROKEN (AUD_FREQ_8T | AUD_FREQ_PULLCLKS(2) | AUD_FREQ_BCLK_96M) + +/** + * i915_audio_component_init - initialize and register the audio component + * @dev_priv: i915 device instance + * + * This will register with the component framework a child component which + * will bind dynamically to the snd_hda_intel driver's corresponding master + * component when the latter is registered. During binding the child + * initializes an instance of struct i915_audio_component which it receives + * from the master. The master can then start to use the interface defined by + * this struct. Each side can break the binding at any point by deregistering + * its own component after which each side's component unbind callback is + * called. + * + * We ignore any error during registration and continue with reduced + * functionality (i.e. without HDMI audio). + */ +static void i915_audio_component_init(struct drm_i915_private *dev_priv) +{ + u32 aud_freq, aud_freq_init; + int ret; + + ret = component_add_typed(dev_priv->drm.dev, + &i915_audio_component_bind_ops, + I915_COMPONENT_AUDIO); + if (ret < 0) { + drm_err(&dev_priv->drm, + "failed to add audio component (%d)\n", ret); + /* continue with reduced functionality */ + return; + } + + if (DISPLAY_VER(dev_priv) >= 9) { + aud_freq_init = intel_de_read(dev_priv, AUD_FREQ_CNTRL); + + if (DISPLAY_VER(dev_priv) >= 12) + aud_freq = AUD_FREQ_GEN12; + else + aud_freq = aud_freq_init; + + /* use BIOS provided value for TGL and RKL unless it is a known bad value */ + if ((IS_TIGERLAKE(dev_priv) || IS_ROCKETLAKE(dev_priv)) && + aud_freq_init != AUD_FREQ_TGL_BROKEN) + aud_freq = aud_freq_init; + + drm_dbg_kms(&dev_priv->drm, "use AUD_FREQ_CNTRL of 0x%x (init value 0x%x)\n", + aud_freq, aud_freq_init); + + dev_priv->display.audio.freq_cntrl = aud_freq; + } + + /* init with current cdclk */ + intel_audio_cdclk_change_post(dev_priv); + + dev_priv->display.audio.component_registered = true; +} + +/** + * i915_audio_component_cleanup - deregister the audio component + * @dev_priv: i915 device instance + * + * Deregisters the audio component, breaking any existing binding to the + * corresponding snd_hda_intel driver's master component. + */ +static void i915_audio_component_cleanup(struct drm_i915_private *dev_priv) +{ + if (!dev_priv->display.audio.component_registered) + return; + + component_del(dev_priv->drm.dev, &i915_audio_component_bind_ops); + dev_priv->display.audio.component_registered = false; +} + +/** + * intel_audio_init() - Initialize the audio driver either using + * component framework or using lpe audio bridge + * @dev_priv: the i915 drm device private data + * + */ +void intel_audio_init(struct drm_i915_private *dev_priv) +{ + if (intel_lpe_audio_init(dev_priv) < 0) + i915_audio_component_init(dev_priv); +} + +/** + * intel_audio_deinit() - deinitialize the audio driver + * @dev_priv: the i915 drm device private data + * + */ +void intel_audio_deinit(struct drm_i915_private *dev_priv) +{ + if (dev_priv->display.audio.lpe.platdev != NULL) + intel_lpe_audio_teardown(dev_priv); + else + i915_audio_component_cleanup(dev_priv); +} diff --git a/drivers/gpu/drm/i915/display/intel_audio.h b/drivers/gpu/drm/i915/display/intel_audio.h new file mode 100644 index 000000000..63b22131d --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_audio.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_AUDIO_H__ +#define __INTEL_AUDIO_H__ + +struct drm_connector_state; +struct drm_i915_private; +struct intel_crtc_state; +struct intel_encoder; + +void intel_audio_hooks_init(struct drm_i915_private *dev_priv); +void intel_audio_codec_enable(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state); +void intel_audio_codec_disable(struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state); +void intel_audio_cdclk_change_pre(struct drm_i915_private *dev_priv); +void intel_audio_cdclk_change_post(struct drm_i915_private *dev_priv); +void intel_audio_init(struct drm_i915_private *dev_priv); +void intel_audio_deinit(struct drm_i915_private *dev_priv); + +#endif /* __INTEL_AUDIO_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_audio_regs.h b/drivers/gpu/drm/i915/display/intel_audio_regs.h new file mode 100644 index 000000000..d1e5844e3 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_audio_regs.h @@ -0,0 +1,160 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2022 Intel Corporation + */ + +#ifndef __INTEL_AUDIO_REGS_H__ +#define __INTEL_AUDIO_REGS_H__ + +#include "i915_reg_defs.h" + +#define G4X_AUD_VID_DID _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x62020) +#define INTEL_AUDIO_DEVCL 0x808629FB +#define INTEL_AUDIO_DEVBLC 0x80862801 +#define INTEL_AUDIO_DEVCTG 0x80862802 + +#define G4X_AUD_CNTL_ST _MMIO(0x620B4) +#define G4X_ELDV_DEVCL_DEVBLC (1 << 13) +#define G4X_ELDV_DEVCTG (1 << 14) +#define G4X_ELD_ADDR_MASK (0xf << 5) +#define G4X_ELD_ACK (1 << 4) +#define G4X_HDMIW_HDMIEDID _MMIO(0x6210C) + +#define _IBX_HDMIW_HDMIEDID_A 0xE2050 +#define _IBX_HDMIW_HDMIEDID_B 0xE2150 +#define IBX_HDMIW_HDMIEDID(pipe) _MMIO_PIPE(pipe, _IBX_HDMIW_HDMIEDID_A, \ + _IBX_HDMIW_HDMIEDID_B) +#define _IBX_AUD_CNTL_ST_A 0xE20B4 +#define _IBX_AUD_CNTL_ST_B 0xE21B4 +#define IBX_AUD_CNTL_ST(pipe) _MMIO_PIPE(pipe, _IBX_AUD_CNTL_ST_A, \ + _IBX_AUD_CNTL_ST_B) +#define IBX_ELD_BUFFER_SIZE_MASK (0x1f << 10) +#define IBX_ELD_ADDRESS_MASK (0x1f << 5) +#define IBX_ELD_ACK (1 << 4) +#define IBX_AUD_CNTL_ST2 _MMIO(0xE20C0) +#define IBX_CP_READY(port) ((1 << 1) << (((port) - 1) * 4)) +#define IBX_ELD_VALID(port) ((1 << 0) << (((port) - 1) * 4)) + +#define _CPT_HDMIW_HDMIEDID_A 0xE5050 +#define _CPT_HDMIW_HDMIEDID_B 0xE5150 +#define CPT_HDMIW_HDMIEDID(pipe) _MMIO_PIPE(pipe, _CPT_HDMIW_HDMIEDID_A, _CPT_HDMIW_HDMIEDID_B) +#define _CPT_AUD_CNTL_ST_A 0xE50B4 +#define _CPT_AUD_CNTL_ST_B 0xE51B4 +#define CPT_AUD_CNTL_ST(pipe) _MMIO_PIPE(pipe, _CPT_AUD_CNTL_ST_A, _CPT_AUD_CNTL_ST_B) +#define CPT_AUD_CNTRL_ST2 _MMIO(0xE50C0) + +#define _VLV_HDMIW_HDMIEDID_A (VLV_DISPLAY_BASE + 0x62050) +#define _VLV_HDMIW_HDMIEDID_B (VLV_DISPLAY_BASE + 0x62150) +#define VLV_HDMIW_HDMIEDID(pipe) _MMIO_PIPE(pipe, _VLV_HDMIW_HDMIEDID_A, _VLV_HDMIW_HDMIEDID_B) +#define _VLV_AUD_CNTL_ST_A (VLV_DISPLAY_BASE + 0x620B4) +#define _VLV_AUD_CNTL_ST_B (VLV_DISPLAY_BASE + 0x621B4) +#define VLV_AUD_CNTL_ST(pipe) _MMIO_PIPE(pipe, _VLV_AUD_CNTL_ST_A, _VLV_AUD_CNTL_ST_B) +#define VLV_AUD_CNTL_ST2 _MMIO(VLV_DISPLAY_BASE + 0x620C0) + +#define _IBX_AUD_CONFIG_A 0xe2000 +#define _IBX_AUD_CONFIG_B 0xe2100 +#define IBX_AUD_CFG(pipe) _MMIO_PIPE(pipe, _IBX_AUD_CONFIG_A, _IBX_AUD_CONFIG_B) +#define _CPT_AUD_CONFIG_A 0xe5000 +#define _CPT_AUD_CONFIG_B 0xe5100 +#define CPT_AUD_CFG(pipe) _MMIO_PIPE(pipe, _CPT_AUD_CONFIG_A, _CPT_AUD_CONFIG_B) +#define _VLV_AUD_CONFIG_A (VLV_DISPLAY_BASE + 0x62000) +#define _VLV_AUD_CONFIG_B (VLV_DISPLAY_BASE + 0x62100) +#define VLV_AUD_CFG(pipe) _MMIO_PIPE(pipe, _VLV_AUD_CONFIG_A, _VLV_AUD_CONFIG_B) + +#define AUD_CONFIG_N_VALUE_INDEX (1 << 29) +#define AUD_CONFIG_N_PROG_ENABLE (1 << 28) +#define AUD_CONFIG_UPPER_N_SHIFT 20 +#define AUD_CONFIG_UPPER_N_MASK (0xff << 20) +#define AUD_CONFIG_LOWER_N_SHIFT 4 +#define AUD_CONFIG_LOWER_N_MASK (0xfff << 4) +#define AUD_CONFIG_N_MASK (AUD_CONFIG_UPPER_N_MASK | AUD_CONFIG_LOWER_N_MASK) +#define AUD_CONFIG_N(n) \ + (((((n) >> 12) & 0xff) << AUD_CONFIG_UPPER_N_SHIFT) | \ + (((n) & 0xfff) << AUD_CONFIG_LOWER_N_SHIFT)) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_SHIFT 16 +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK (0xf << 16) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_25175 (0 << 16) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_25200 (1 << 16) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_27000 (2 << 16) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_27027 (3 << 16) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_54000 (4 << 16) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_54054 (5 << 16) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_74176 (6 << 16) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_74250 (7 << 16) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_148352 (8 << 16) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_148500 (9 << 16) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_296703 (10 << 16) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_297000 (11 << 16) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_593407 (12 << 16) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_594000 (13 << 16) +#define AUD_CONFIG_DISABLE_NCTS (1 << 3) + +#define _HSW_AUD_CONFIG_A 0x65000 +#define _HSW_AUD_CONFIG_B 0x65100 +#define HSW_AUD_CFG(trans) _MMIO_TRANS(trans, _HSW_AUD_CONFIG_A, _HSW_AUD_CONFIG_B) + +#define _HSW_AUD_MISC_CTRL_A 0x65010 +#define _HSW_AUD_MISC_CTRL_B 0x65110 +#define HSW_AUD_MISC_CTRL(trans) _MMIO_TRANS(trans, _HSW_AUD_MISC_CTRL_A, _HSW_AUD_MISC_CTRL_B) + +#define _HSW_AUD_M_CTS_ENABLE_A 0x65028 +#define _HSW_AUD_M_CTS_ENABLE_B 0x65128 +#define HSW_AUD_M_CTS_ENABLE(trans) _MMIO_TRANS(trans, _HSW_AUD_M_CTS_ENABLE_A, _HSW_AUD_M_CTS_ENABLE_B) +#define AUD_M_CTS_M_VALUE_INDEX (1 << 21) +#define AUD_M_CTS_M_PROG_ENABLE (1 << 20) +#define AUD_CONFIG_M_MASK 0xfffff + +#define _HSW_AUD_DIP_ELD_CTRL_ST_A 0x650b4 +#define _HSW_AUD_DIP_ELD_CTRL_ST_B 0x651b4 +#define HSW_AUD_DIP_ELD_CTRL(trans) _MMIO_TRANS(trans, _HSW_AUD_DIP_ELD_CTRL_ST_A, _HSW_AUD_DIP_ELD_CTRL_ST_B) + +/* Audio Digital Converter */ +#define _HSW_AUD_DIG_CNVT_1 0x65080 +#define _HSW_AUD_DIG_CNVT_2 0x65180 +#define AUD_DIG_CNVT(trans) _MMIO_TRANS(trans, _HSW_AUD_DIG_CNVT_1, _HSW_AUD_DIG_CNVT_2) +#define DIP_PORT_SEL_MASK 0x3 + +#define _HSW_AUD_EDID_DATA_A 0x65050 +#define _HSW_AUD_EDID_DATA_B 0x65150 +#define HSW_AUD_EDID_DATA(trans) _MMIO_TRANS(trans, _HSW_AUD_EDID_DATA_A, _HSW_AUD_EDID_DATA_B) + +#define HSW_AUD_PIPE_CONV_CFG _MMIO(0x6507c) +#define HSW_AUD_PIN_ELD_CP_VLD _MMIO(0x650c0) +#define AUDIO_INACTIVE(trans) ((1 << 3) << ((trans) * 4)) +#define AUDIO_OUTPUT_ENABLE(trans) ((1 << 2) << ((trans) * 4)) +#define AUDIO_CP_READY(trans) ((1 << 1) << ((trans) * 4)) +#define AUDIO_ELD_VALID(trans) ((1 << 0) << ((trans) * 4)) + +#define _AUD_TCA_DP_2DOT0_CTRL 0x650bc +#define _AUD_TCB_DP_2DOT0_CTRL 0x651bc +#define AUD_DP_2DOT0_CTRL(trans) _MMIO_TRANS(trans, _AUD_TCA_DP_2DOT0_CTRL, _AUD_TCB_DP_2DOT0_CTRL) +#define AUD_ENABLE_SDP_SPLIT REG_BIT(31) + +#define HSW_AUD_CHICKENBIT _MMIO(0x65f10) +#define SKL_AUD_CODEC_WAKE_SIGNAL (1 << 15) + +#define AUD_FREQ_CNTRL _MMIO(0x65900) +#define AUD_PIN_BUF_CTL _MMIO(0x48414) +#define AUD_PIN_BUF_ENABLE REG_BIT(31) + +#define AUD_TS_CDCLK_M _MMIO(0x65ea0) +#define AUD_TS_CDCLK_M_EN REG_BIT(31) +#define AUD_TS_CDCLK_N _MMIO(0x65ea4) + +/* Display Audio Config Reg */ +#define AUD_CONFIG_BE _MMIO(0x65ef0) +#define HBLANK_EARLY_ENABLE_ICL(pipe) (0x1 << (20 - (pipe))) +#define HBLANK_EARLY_ENABLE_TGL(pipe) (0x1 << (24 + (pipe))) +#define HBLANK_START_COUNT_MASK(pipe) (0x7 << (3 + ((pipe) * 6))) +#define HBLANK_START_COUNT(pipe, val) (((val) & 0x7) << (3 + ((pipe)) * 6)) +#define NUMBER_SAMPLES_PER_LINE_MASK(pipe) (0x3 << ((pipe) * 6)) +#define NUMBER_SAMPLES_PER_LINE(pipe, val) (((val) & 0x3) << ((pipe) * 6)) + +#define HBLANK_START_COUNT_8 0 +#define HBLANK_START_COUNT_16 1 +#define HBLANK_START_COUNT_32 2 +#define HBLANK_START_COUNT_64 3 +#define HBLANK_START_COUNT_96 4 +#define HBLANK_START_COUNT_128 5 + +#endif /* __INTEL_AUDIO_REGS_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_backlight.c b/drivers/gpu/drm/i915/display/intel_backlight.c new file mode 100644 index 000000000..beba39a38 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_backlight.c @@ -0,0 +1,1794 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2021 Intel Corporation + */ + +#include +#include +#include +#include + +#include + +#include "intel_backlight.h" +#include "intel_backlight_regs.h" +#include "intel_connector.h" +#include "intel_de.h" +#include "intel_display_types.h" +#include "intel_dp_aux_backlight.h" +#include "intel_dsi_dcs_backlight.h" +#include "intel_panel.h" +#include "intel_pci_config.h" +#include "intel_pps.h" +#include "intel_quirks.h" + +/** + * scale - scale values from one range to another + * @source_val: value in range [@source_min..@source_max] + * @source_min: minimum legal value for @source_val + * @source_max: maximum legal value for @source_val + * @target_min: corresponding target value for @source_min + * @target_max: corresponding target value for @source_max + * + * Return @source_val in range [@source_min..@source_max] scaled to range + * [@target_min..@target_max]. + */ +static u32 scale(u32 source_val, + u32 source_min, u32 source_max, + u32 target_min, u32 target_max) +{ + u64 target_val; + + WARN_ON(source_min > source_max); + WARN_ON(target_min > target_max); + + /* defensive */ + source_val = clamp(source_val, source_min, source_max); + + /* avoid overflows */ + target_val = mul_u32_u32(source_val - source_min, + target_max - target_min); + target_val = DIV_ROUND_CLOSEST_ULL(target_val, source_max - source_min); + target_val += target_min; + + return target_val; +} + +/* + * Scale user_level in range [0..user_max] to [0..hw_max], clamping the result + * to [hw_min..hw_max]. + */ +static u32 clamp_user_to_hw(struct intel_connector *connector, + u32 user_level, u32 user_max) +{ + struct intel_panel *panel = &connector->panel; + u32 hw_level; + + hw_level = scale(user_level, 0, user_max, 0, panel->backlight.max); + hw_level = clamp(hw_level, panel->backlight.min, panel->backlight.max); + + return hw_level; +} + +/* Scale hw_level in range [hw_min..hw_max] to [0..user_max]. */ +static u32 scale_hw_to_user(struct intel_connector *connector, + u32 hw_level, u32 user_max) +{ + struct intel_panel *panel = &connector->panel; + + return scale(hw_level, panel->backlight.min, panel->backlight.max, + 0, user_max); +} + +u32 intel_backlight_invert_pwm_level(struct intel_connector *connector, u32 val) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + + drm_WARN_ON(&dev_priv->drm, panel->backlight.pwm_level_max == 0); + + if (dev_priv->params.invert_brightness < 0) + return val; + + if (dev_priv->params.invert_brightness > 0 || + intel_has_quirk(dev_priv, QUIRK_INVERT_BRIGHTNESS)) { + return panel->backlight.pwm_level_max - val + panel->backlight.pwm_level_min; + } + + return val; +} + +void intel_backlight_set_pwm_level(const struct drm_connector_state *conn_state, u32 val) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct drm_i915_private *i915 = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + + drm_dbg_kms(&i915->drm, "set backlight PWM = %d\n", val); + panel->backlight.pwm_funcs->set(conn_state, val); +} + +u32 intel_backlight_level_to_pwm(struct intel_connector *connector, u32 val) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + + drm_WARN_ON_ONCE(&dev_priv->drm, + panel->backlight.max == 0 || panel->backlight.pwm_level_max == 0); + + val = scale(val, panel->backlight.min, panel->backlight.max, + panel->backlight.pwm_level_min, panel->backlight.pwm_level_max); + + return intel_backlight_invert_pwm_level(connector, val); +} + +u32 intel_backlight_level_from_pwm(struct intel_connector *connector, u32 val) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + + drm_WARN_ON_ONCE(&dev_priv->drm, + panel->backlight.max == 0 || panel->backlight.pwm_level_max == 0); + + if (dev_priv->params.invert_brightness > 0 || + (dev_priv->params.invert_brightness == 0 && intel_has_quirk(dev_priv, QUIRK_INVERT_BRIGHTNESS))) + val = panel->backlight.pwm_level_max - (val - panel->backlight.pwm_level_min); + + return scale(val, panel->backlight.pwm_level_min, panel->backlight.pwm_level_max, + panel->backlight.min, panel->backlight.max); +} + +static u32 lpt_get_backlight(struct intel_connector *connector, enum pipe unused) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + + return intel_de_read(dev_priv, BLC_PWM_PCH_CTL2) & BACKLIGHT_DUTY_CYCLE_MASK; +} + +static u32 pch_get_backlight(struct intel_connector *connector, enum pipe unused) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + + return intel_de_read(dev_priv, BLC_PWM_CPU_CTL) & BACKLIGHT_DUTY_CYCLE_MASK; +} + +static u32 i9xx_get_backlight(struct intel_connector *connector, enum pipe unused) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + u32 val; + + val = intel_de_read(dev_priv, BLC_PWM_CTL) & BACKLIGHT_DUTY_CYCLE_MASK; + if (DISPLAY_VER(dev_priv) < 4) + val >>= 1; + + if (panel->backlight.combination_mode) { + u8 lbpc; + + pci_read_config_byte(to_pci_dev(dev_priv->drm.dev), LBPC, &lbpc); + val *= lbpc; + } + + return val; +} + +static u32 vlv_get_backlight(struct intel_connector *connector, enum pipe pipe) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + + if (drm_WARN_ON(&dev_priv->drm, pipe != PIPE_A && pipe != PIPE_B)) + return 0; + + return intel_de_read(dev_priv, VLV_BLC_PWM_CTL(pipe)) & BACKLIGHT_DUTY_CYCLE_MASK; +} + +static u32 bxt_get_backlight(struct intel_connector *connector, enum pipe unused) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + + return intel_de_read(dev_priv, + BXT_BLC_PWM_DUTY(panel->backlight.controller)); +} + +static u32 ext_pwm_get_backlight(struct intel_connector *connector, enum pipe unused) +{ + struct intel_panel *panel = &connector->panel; + struct pwm_state state; + + pwm_get_state(panel->backlight.pwm, &state); + return pwm_get_relative_duty_cycle(&state, 100); +} + +static void lpt_set_backlight(const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + + u32 val = intel_de_read(dev_priv, BLC_PWM_PCH_CTL2) & ~BACKLIGHT_DUTY_CYCLE_MASK; + intel_de_write(dev_priv, BLC_PWM_PCH_CTL2, val | level); +} + +static void pch_set_backlight(const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + u32 tmp; + + tmp = intel_de_read(dev_priv, BLC_PWM_CPU_CTL) & ~BACKLIGHT_DUTY_CYCLE_MASK; + intel_de_write(dev_priv, BLC_PWM_CPU_CTL, tmp | level); +} + +static void i9xx_set_backlight(const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + u32 tmp, mask; + + drm_WARN_ON(&dev_priv->drm, panel->backlight.pwm_level_max == 0); + + if (panel->backlight.combination_mode) { + u8 lbpc; + + lbpc = level * 0xfe / panel->backlight.pwm_level_max + 1; + level /= lbpc; + pci_write_config_byte(to_pci_dev(dev_priv->drm.dev), LBPC, lbpc); + } + + if (DISPLAY_VER(dev_priv) == 4) { + mask = BACKLIGHT_DUTY_CYCLE_MASK; + } else { + level <<= 1; + mask = BACKLIGHT_DUTY_CYCLE_MASK_PNV; + } + + tmp = intel_de_read(dev_priv, BLC_PWM_CTL) & ~mask; + intel_de_write(dev_priv, BLC_PWM_CTL, tmp | level); +} + +static void vlv_set_backlight(const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + enum pipe pipe = to_intel_crtc(conn_state->crtc)->pipe; + u32 tmp; + + tmp = intel_de_read(dev_priv, VLV_BLC_PWM_CTL(pipe)) & ~BACKLIGHT_DUTY_CYCLE_MASK; + intel_de_write(dev_priv, VLV_BLC_PWM_CTL(pipe), tmp | level); +} + +static void bxt_set_backlight(const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + + intel_de_write(dev_priv, + BXT_BLC_PWM_DUTY(panel->backlight.controller), level); +} + +static void ext_pwm_set_backlight(const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_panel *panel = &to_intel_connector(conn_state->connector)->panel; + + pwm_set_relative_duty_cycle(&panel->backlight.pwm_state, level, 100); + pwm_apply_state(panel->backlight.pwm, &panel->backlight.pwm_state); +} + +static void +intel_panel_actually_set_backlight(const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct drm_i915_private *i915 = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + + drm_dbg_kms(&i915->drm, "set backlight level = %d\n", level); + + panel->backlight.funcs->set(conn_state, level); +} + +/* set backlight brightness to level in range [0..max], assuming hw min is + * respected. + */ +void intel_backlight_set_acpi(const struct drm_connector_state *conn_state, + u32 user_level, u32 user_max) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + u32 hw_level; + + /* + * Lack of crtc may occur during driver init because + * connection_mutex isn't held across the entire backlight + * setup + modeset readout, and the BIOS can issue the + * requests at any time. + */ + if (!panel->backlight.present || !conn_state->crtc) + return; + + mutex_lock(&dev_priv->display.backlight.lock); + + drm_WARN_ON(&dev_priv->drm, panel->backlight.max == 0); + + hw_level = clamp_user_to_hw(connector, user_level, user_max); + panel->backlight.level = hw_level; + + if (panel->backlight.device) + panel->backlight.device->props.brightness = + scale_hw_to_user(connector, + panel->backlight.level, + panel->backlight.device->props.max_brightness); + + if (panel->backlight.enabled) + intel_panel_actually_set_backlight(conn_state, hw_level); + + mutex_unlock(&dev_priv->display.backlight.lock); +} + +static void lpt_disable_backlight(const struct drm_connector_state *old_conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(old_conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + u32 tmp; + + intel_backlight_set_pwm_level(old_conn_state, level); + + /* + * Although we don't support or enable CPU PWM with LPT/SPT based + * systems, it may have been enabled prior to loading the + * driver. Disable to avoid warnings on LCPLL disable. + * + * This needs rework if we need to add support for CPU PWM on PCH split + * platforms. + */ + tmp = intel_de_read(dev_priv, BLC_PWM_CPU_CTL2); + if (tmp & BLM_PWM_ENABLE) { + drm_dbg_kms(&dev_priv->drm, + "cpu backlight was enabled, disabling\n"); + intel_de_write(dev_priv, BLC_PWM_CPU_CTL2, + tmp & ~BLM_PWM_ENABLE); + } + + tmp = intel_de_read(dev_priv, BLC_PWM_PCH_CTL1); + intel_de_write(dev_priv, BLC_PWM_PCH_CTL1, tmp & ~BLM_PCH_PWM_ENABLE); +} + +static void pch_disable_backlight(const struct drm_connector_state *old_conn_state, u32 val) +{ + struct intel_connector *connector = to_intel_connector(old_conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + u32 tmp; + + intel_backlight_set_pwm_level(old_conn_state, val); + + tmp = intel_de_read(dev_priv, BLC_PWM_CPU_CTL2); + intel_de_write(dev_priv, BLC_PWM_CPU_CTL2, tmp & ~BLM_PWM_ENABLE); + + tmp = intel_de_read(dev_priv, BLC_PWM_PCH_CTL1); + intel_de_write(dev_priv, BLC_PWM_PCH_CTL1, tmp & ~BLM_PCH_PWM_ENABLE); +} + +static void i9xx_disable_backlight(const struct drm_connector_state *old_conn_state, u32 val) +{ + intel_backlight_set_pwm_level(old_conn_state, val); +} + +static void i965_disable_backlight(const struct drm_connector_state *old_conn_state, u32 val) +{ + struct drm_i915_private *dev_priv = to_i915(old_conn_state->connector->dev); + u32 tmp; + + intel_backlight_set_pwm_level(old_conn_state, val); + + tmp = intel_de_read(dev_priv, BLC_PWM_CTL2); + intel_de_write(dev_priv, BLC_PWM_CTL2, tmp & ~BLM_PWM_ENABLE); +} + +static void vlv_disable_backlight(const struct drm_connector_state *old_conn_state, u32 val) +{ + struct intel_connector *connector = to_intel_connector(old_conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + enum pipe pipe = to_intel_crtc(old_conn_state->crtc)->pipe; + u32 tmp; + + intel_backlight_set_pwm_level(old_conn_state, val); + + tmp = intel_de_read(dev_priv, VLV_BLC_PWM_CTL2(pipe)); + intel_de_write(dev_priv, VLV_BLC_PWM_CTL2(pipe), + tmp & ~BLM_PWM_ENABLE); +} + +static void bxt_disable_backlight(const struct drm_connector_state *old_conn_state, u32 val) +{ + struct intel_connector *connector = to_intel_connector(old_conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + u32 tmp; + + intel_backlight_set_pwm_level(old_conn_state, val); + + tmp = intel_de_read(dev_priv, + BXT_BLC_PWM_CTL(panel->backlight.controller)); + intel_de_write(dev_priv, BXT_BLC_PWM_CTL(panel->backlight.controller), + tmp & ~BXT_BLC_PWM_ENABLE); + + if (panel->backlight.controller == 1) { + val = intel_de_read(dev_priv, UTIL_PIN_CTL); + val &= ~UTIL_PIN_ENABLE; + intel_de_write(dev_priv, UTIL_PIN_CTL, val); + } +} + +static void cnp_disable_backlight(const struct drm_connector_state *old_conn_state, u32 val) +{ + struct intel_connector *connector = to_intel_connector(old_conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + u32 tmp; + + intel_backlight_set_pwm_level(old_conn_state, val); + + tmp = intel_de_read(dev_priv, + BXT_BLC_PWM_CTL(panel->backlight.controller)); + intel_de_write(dev_priv, BXT_BLC_PWM_CTL(panel->backlight.controller), + tmp & ~BXT_BLC_PWM_ENABLE); +} + +static void ext_pwm_disable_backlight(const struct drm_connector_state *old_conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(old_conn_state->connector); + struct intel_panel *panel = &connector->panel; + + intel_backlight_set_pwm_level(old_conn_state, level); + + panel->backlight.pwm_state.enabled = false; + pwm_apply_state(panel->backlight.pwm, &panel->backlight.pwm_state); +} + +void intel_backlight_disable(const struct drm_connector_state *old_conn_state) +{ + struct intel_connector *connector = to_intel_connector(old_conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + + if (!panel->backlight.present) + return; + + /* + * Do not disable backlight on the vga_switcheroo path. When switching + * away from i915, the other client may depend on i915 to handle the + * backlight. This will leave the backlight on unnecessarily when + * another client is not activated. + */ + if (dev_priv->drm.switch_power_state == DRM_SWITCH_POWER_CHANGING) { + drm_dbg_kms(&dev_priv->drm, + "Skipping backlight disable on vga switch\n"); + return; + } + + mutex_lock(&dev_priv->display.backlight.lock); + + if (panel->backlight.device) + panel->backlight.device->props.power = FB_BLANK_POWERDOWN; + panel->backlight.enabled = false; + panel->backlight.funcs->disable(old_conn_state, 0); + + mutex_unlock(&dev_priv->display.backlight.lock); +} + +static void lpt_enable_backlight(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + u32 pch_ctl1, pch_ctl2, schicken; + + pch_ctl1 = intel_de_read(dev_priv, BLC_PWM_PCH_CTL1); + if (pch_ctl1 & BLM_PCH_PWM_ENABLE) { + drm_dbg_kms(&dev_priv->drm, "pch backlight already enabled\n"); + pch_ctl1 &= ~BLM_PCH_PWM_ENABLE; + intel_de_write(dev_priv, BLC_PWM_PCH_CTL1, pch_ctl1); + } + + if (HAS_PCH_LPT(dev_priv)) { + schicken = intel_de_read(dev_priv, SOUTH_CHICKEN2); + if (panel->backlight.alternate_pwm_increment) + schicken |= LPT_PWM_GRANULARITY; + else + schicken &= ~LPT_PWM_GRANULARITY; + intel_de_write(dev_priv, SOUTH_CHICKEN2, schicken); + } else { + schicken = intel_de_read(dev_priv, SOUTH_CHICKEN1); + if (panel->backlight.alternate_pwm_increment) + schicken |= SPT_PWM_GRANULARITY; + else + schicken &= ~SPT_PWM_GRANULARITY; + intel_de_write(dev_priv, SOUTH_CHICKEN1, schicken); + } + + pch_ctl2 = panel->backlight.pwm_level_max << 16; + intel_de_write(dev_priv, BLC_PWM_PCH_CTL2, pch_ctl2); + + pch_ctl1 = 0; + if (panel->backlight.active_low_pwm) + pch_ctl1 |= BLM_PCH_POLARITY; + + /* After LPT, override is the default. */ + if (HAS_PCH_LPT(dev_priv)) + pch_ctl1 |= BLM_PCH_OVERRIDE_ENABLE; + + intel_de_write(dev_priv, BLC_PWM_PCH_CTL1, pch_ctl1); + intel_de_posting_read(dev_priv, BLC_PWM_PCH_CTL1); + intel_de_write(dev_priv, BLC_PWM_PCH_CTL1, + pch_ctl1 | BLM_PCH_PWM_ENABLE); + + /* This won't stick until the above enable. */ + intel_backlight_set_pwm_level(conn_state, level); +} + +static void pch_enable_backlight(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + u32 cpu_ctl2, pch_ctl1, pch_ctl2; + + cpu_ctl2 = intel_de_read(dev_priv, BLC_PWM_CPU_CTL2); + if (cpu_ctl2 & BLM_PWM_ENABLE) { + drm_dbg_kms(&dev_priv->drm, "cpu backlight already enabled\n"); + cpu_ctl2 &= ~BLM_PWM_ENABLE; + intel_de_write(dev_priv, BLC_PWM_CPU_CTL2, cpu_ctl2); + } + + pch_ctl1 = intel_de_read(dev_priv, BLC_PWM_PCH_CTL1); + if (pch_ctl1 & BLM_PCH_PWM_ENABLE) { + drm_dbg_kms(&dev_priv->drm, "pch backlight already enabled\n"); + pch_ctl1 &= ~BLM_PCH_PWM_ENABLE; + intel_de_write(dev_priv, BLC_PWM_PCH_CTL1, pch_ctl1); + } + + if (cpu_transcoder == TRANSCODER_EDP) + cpu_ctl2 = BLM_TRANSCODER_EDP; + else + cpu_ctl2 = BLM_PIPE(cpu_transcoder); + intel_de_write(dev_priv, BLC_PWM_CPU_CTL2, cpu_ctl2); + intel_de_posting_read(dev_priv, BLC_PWM_CPU_CTL2); + intel_de_write(dev_priv, BLC_PWM_CPU_CTL2, cpu_ctl2 | BLM_PWM_ENABLE); + + /* This won't stick until the above enable. */ + intel_backlight_set_pwm_level(conn_state, level); + + pch_ctl2 = panel->backlight.pwm_level_max << 16; + intel_de_write(dev_priv, BLC_PWM_PCH_CTL2, pch_ctl2); + + pch_ctl1 = 0; + if (panel->backlight.active_low_pwm) + pch_ctl1 |= BLM_PCH_POLARITY; + + intel_de_write(dev_priv, BLC_PWM_PCH_CTL1, pch_ctl1); + intel_de_posting_read(dev_priv, BLC_PWM_PCH_CTL1); + intel_de_write(dev_priv, BLC_PWM_PCH_CTL1, + pch_ctl1 | BLM_PCH_PWM_ENABLE); +} + +static void i9xx_enable_backlight(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + u32 ctl, freq; + + ctl = intel_de_read(dev_priv, BLC_PWM_CTL); + if (ctl & BACKLIGHT_DUTY_CYCLE_MASK_PNV) { + drm_dbg_kms(&dev_priv->drm, "backlight already enabled\n"); + intel_de_write(dev_priv, BLC_PWM_CTL, 0); + } + + freq = panel->backlight.pwm_level_max; + if (panel->backlight.combination_mode) + freq /= 0xff; + + ctl = freq << 17; + if (panel->backlight.combination_mode) + ctl |= BLM_LEGACY_MODE; + if (IS_PINEVIEW(dev_priv) && panel->backlight.active_low_pwm) + ctl |= BLM_POLARITY_PNV; + + intel_de_write(dev_priv, BLC_PWM_CTL, ctl); + intel_de_posting_read(dev_priv, BLC_PWM_CTL); + + /* XXX: combine this into above write? */ + intel_backlight_set_pwm_level(conn_state, level); + + /* + * Needed to enable backlight on some 855gm models. BLC_HIST_CTL is + * 855gm only, but checking for gen2 is safe, as 855gm is the only gen2 + * that has backlight. + */ + if (DISPLAY_VER(dev_priv) == 2) + intel_de_write(dev_priv, BLC_HIST_CTL, BLM_HISTOGRAM_ENABLE); +} + +static void i965_enable_backlight(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + enum pipe pipe = to_intel_crtc(conn_state->crtc)->pipe; + u32 ctl, ctl2, freq; + + ctl2 = intel_de_read(dev_priv, BLC_PWM_CTL2); + if (ctl2 & BLM_PWM_ENABLE) { + drm_dbg_kms(&dev_priv->drm, "backlight already enabled\n"); + ctl2 &= ~BLM_PWM_ENABLE; + intel_de_write(dev_priv, BLC_PWM_CTL2, ctl2); + } + + freq = panel->backlight.pwm_level_max; + if (panel->backlight.combination_mode) + freq /= 0xff; + + ctl = freq << 16; + intel_de_write(dev_priv, BLC_PWM_CTL, ctl); + + ctl2 = BLM_PIPE(pipe); + if (panel->backlight.combination_mode) + ctl2 |= BLM_COMBINATION_MODE; + if (panel->backlight.active_low_pwm) + ctl2 |= BLM_POLARITY_I965; + intel_de_write(dev_priv, BLC_PWM_CTL2, ctl2); + intel_de_posting_read(dev_priv, BLC_PWM_CTL2); + intel_de_write(dev_priv, BLC_PWM_CTL2, ctl2 | BLM_PWM_ENABLE); + + intel_backlight_set_pwm_level(conn_state, level); +} + +static void vlv_enable_backlight(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + enum pipe pipe = to_intel_crtc(crtc_state->uapi.crtc)->pipe; + u32 ctl, ctl2; + + ctl2 = intel_de_read(dev_priv, VLV_BLC_PWM_CTL2(pipe)); + if (ctl2 & BLM_PWM_ENABLE) { + drm_dbg_kms(&dev_priv->drm, "backlight already enabled\n"); + ctl2 &= ~BLM_PWM_ENABLE; + intel_de_write(dev_priv, VLV_BLC_PWM_CTL2(pipe), ctl2); + } + + ctl = panel->backlight.pwm_level_max << 16; + intel_de_write(dev_priv, VLV_BLC_PWM_CTL(pipe), ctl); + + /* XXX: combine this into above write? */ + intel_backlight_set_pwm_level(conn_state, level); + + ctl2 = 0; + if (panel->backlight.active_low_pwm) + ctl2 |= BLM_POLARITY_I965; + intel_de_write(dev_priv, VLV_BLC_PWM_CTL2(pipe), ctl2); + intel_de_posting_read(dev_priv, VLV_BLC_PWM_CTL2(pipe)); + intel_de_write(dev_priv, VLV_BLC_PWM_CTL2(pipe), + ctl2 | BLM_PWM_ENABLE); +} + +static void bxt_enable_backlight(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + enum pipe pipe = to_intel_crtc(crtc_state->uapi.crtc)->pipe; + u32 pwm_ctl, val; + + /* Controller 1 uses the utility pin. */ + if (panel->backlight.controller == 1) { + val = intel_de_read(dev_priv, UTIL_PIN_CTL); + if (val & UTIL_PIN_ENABLE) { + drm_dbg_kms(&dev_priv->drm, + "util pin already enabled\n"); + val &= ~UTIL_PIN_ENABLE; + intel_de_write(dev_priv, UTIL_PIN_CTL, val); + } + + val = 0; + if (panel->backlight.util_pin_active_low) + val |= UTIL_PIN_POLARITY; + intel_de_write(dev_priv, UTIL_PIN_CTL, + val | UTIL_PIN_PIPE(pipe) | UTIL_PIN_MODE_PWM | UTIL_PIN_ENABLE); + } + + pwm_ctl = intel_de_read(dev_priv, + BXT_BLC_PWM_CTL(panel->backlight.controller)); + if (pwm_ctl & BXT_BLC_PWM_ENABLE) { + drm_dbg_kms(&dev_priv->drm, "backlight already enabled\n"); + pwm_ctl &= ~BXT_BLC_PWM_ENABLE; + intel_de_write(dev_priv, + BXT_BLC_PWM_CTL(panel->backlight.controller), + pwm_ctl); + } + + intel_de_write(dev_priv, + BXT_BLC_PWM_FREQ(panel->backlight.controller), + panel->backlight.pwm_level_max); + + intel_backlight_set_pwm_level(conn_state, level); + + pwm_ctl = 0; + if (panel->backlight.active_low_pwm) + pwm_ctl |= BXT_BLC_PWM_POLARITY; + + intel_de_write(dev_priv, BXT_BLC_PWM_CTL(panel->backlight.controller), + pwm_ctl); + intel_de_posting_read(dev_priv, + BXT_BLC_PWM_CTL(panel->backlight.controller)); + intel_de_write(dev_priv, BXT_BLC_PWM_CTL(panel->backlight.controller), + pwm_ctl | BXT_BLC_PWM_ENABLE); +} + +static void cnp_enable_backlight(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + u32 pwm_ctl; + + pwm_ctl = intel_de_read(dev_priv, + BXT_BLC_PWM_CTL(panel->backlight.controller)); + if (pwm_ctl & BXT_BLC_PWM_ENABLE) { + drm_dbg_kms(&dev_priv->drm, "backlight already enabled\n"); + pwm_ctl &= ~BXT_BLC_PWM_ENABLE; + intel_de_write(dev_priv, + BXT_BLC_PWM_CTL(panel->backlight.controller), + pwm_ctl); + } + + intel_de_write(dev_priv, + BXT_BLC_PWM_FREQ(panel->backlight.controller), + panel->backlight.pwm_level_max); + + intel_backlight_set_pwm_level(conn_state, level); + + pwm_ctl = 0; + if (panel->backlight.active_low_pwm) + pwm_ctl |= BXT_BLC_PWM_POLARITY; + + intel_de_write(dev_priv, BXT_BLC_PWM_CTL(panel->backlight.controller), + pwm_ctl); + intel_de_posting_read(dev_priv, + BXT_BLC_PWM_CTL(panel->backlight.controller)); + intel_de_write(dev_priv, BXT_BLC_PWM_CTL(panel->backlight.controller), + pwm_ctl | BXT_BLC_PWM_ENABLE); +} + +static void ext_pwm_enable_backlight(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct intel_panel *panel = &connector->panel; + + pwm_set_relative_duty_cycle(&panel->backlight.pwm_state, level, 100); + panel->backlight.pwm_state.enabled = true; + pwm_apply_state(panel->backlight.pwm, &panel->backlight.pwm_state); +} + +static void __intel_backlight_enable(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct intel_panel *panel = &connector->panel; + + WARN_ON(panel->backlight.max == 0); + + if (panel->backlight.level <= panel->backlight.min) { + panel->backlight.level = panel->backlight.max; + if (panel->backlight.device) + panel->backlight.device->props.brightness = + scale_hw_to_user(connector, + panel->backlight.level, + panel->backlight.device->props.max_brightness); + } + + panel->backlight.funcs->enable(crtc_state, conn_state, panel->backlight.level); + panel->backlight.enabled = true; + if (panel->backlight.device) + panel->backlight.device->props.power = FB_BLANK_UNBLANK; +} + +void intel_backlight_enable(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + enum pipe pipe = to_intel_crtc(crtc_state->uapi.crtc)->pipe; + + if (!panel->backlight.present) + return; + + drm_dbg_kms(&dev_priv->drm, "pipe %c\n", pipe_name(pipe)); + + mutex_lock(&dev_priv->display.backlight.lock); + + __intel_backlight_enable(crtc_state, conn_state); + + mutex_unlock(&dev_priv->display.backlight.lock); +} + +#if IS_ENABLED(CONFIG_BACKLIGHT_CLASS_DEVICE) +static u32 intel_panel_get_backlight(struct intel_connector *connector) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + u32 val = 0; + + mutex_lock(&dev_priv->display.backlight.lock); + + if (panel->backlight.enabled) + val = panel->backlight.funcs->get(connector, intel_connector_get_pipe(connector)); + + mutex_unlock(&dev_priv->display.backlight.lock); + + drm_dbg_kms(&dev_priv->drm, "get backlight PWM = %d\n", val); + return val; +} + +/* Scale user_level in range [0..user_max] to [hw_min..hw_max]. */ +static u32 scale_user_to_hw(struct intel_connector *connector, + u32 user_level, u32 user_max) +{ + struct intel_panel *panel = &connector->panel; + + return scale(user_level, 0, user_max, + panel->backlight.min, panel->backlight.max); +} + +/* set backlight brightness to level in range [0..max], scaling wrt hw min */ +static void intel_panel_set_backlight(const struct drm_connector_state *conn_state, + u32 user_level, u32 user_max) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + u32 hw_level; + + if (!panel->backlight.present) + return; + + mutex_lock(&dev_priv->display.backlight.lock); + + drm_WARN_ON(&dev_priv->drm, panel->backlight.max == 0); + + hw_level = scale_user_to_hw(connector, user_level, user_max); + panel->backlight.level = hw_level; + + if (panel->backlight.enabled) + intel_panel_actually_set_backlight(conn_state, hw_level); + + mutex_unlock(&dev_priv->display.backlight.lock); +} + +static int intel_backlight_device_update_status(struct backlight_device *bd) +{ + struct intel_connector *connector = bl_get_data(bd); + struct intel_panel *panel = &connector->panel; + struct drm_device *dev = connector->base.dev; + + drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); + DRM_DEBUG_KMS("updating intel_backlight, brightness=%d/%d\n", + bd->props.brightness, bd->props.max_brightness); + intel_panel_set_backlight(connector->base.state, bd->props.brightness, + bd->props.max_brightness); + + /* + * Allow flipping bl_power as a sub-state of enabled. Sadly the + * backlight class device does not make it easy to differentiate + * between callbacks for brightness and bl_power, so our backlight_power + * callback needs to take this into account. + */ + if (panel->backlight.enabled) { + if (panel->backlight.power) { + bool enable = bd->props.power == FB_BLANK_UNBLANK && + bd->props.brightness != 0; + panel->backlight.power(connector, enable); + } + } else { + bd->props.power = FB_BLANK_POWERDOWN; + } + + drm_modeset_unlock(&dev->mode_config.connection_mutex); + return 0; +} + +static int intel_backlight_device_get_brightness(struct backlight_device *bd) +{ + struct intel_connector *connector = bl_get_data(bd); + struct drm_device *dev = connector->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + intel_wakeref_t wakeref; + int ret = 0; + + with_intel_runtime_pm(&dev_priv->runtime_pm, wakeref) { + u32 hw_level; + + drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); + + hw_level = intel_panel_get_backlight(connector); + ret = scale_hw_to_user(connector, + hw_level, bd->props.max_brightness); + + drm_modeset_unlock(&dev->mode_config.connection_mutex); + } + + return ret; +} + +static const struct backlight_ops intel_backlight_device_ops = { + .update_status = intel_backlight_device_update_status, + .get_brightness = intel_backlight_device_get_brightness, +}; + +int intel_backlight_device_register(struct intel_connector *connector) +{ + struct drm_i915_private *i915 = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + struct backlight_properties props; + struct backlight_device *bd; + const char *name; + int ret = 0; + + if (WARN_ON(panel->backlight.device)) + return -ENODEV; + + if (!panel->backlight.present) + return 0; + + WARN_ON(panel->backlight.max == 0); + + if (!acpi_video_backlight_use_native()) { + drm_info(&i915->drm, "Skipping intel_backlight registration\n"); + return 0; + } + + memset(&props, 0, sizeof(props)); + props.type = BACKLIGHT_RAW; + + /* + * Note: Everything should work even if the backlight device max + * presented to the userspace is arbitrarily chosen. + */ + props.max_brightness = panel->backlight.max; + props.brightness = scale_hw_to_user(connector, + panel->backlight.level, + props.max_brightness); + + if (panel->backlight.enabled) + props.power = FB_BLANK_UNBLANK; + else + props.power = FB_BLANK_POWERDOWN; + + name = kstrdup("intel_backlight", GFP_KERNEL); + if (!name) + return -ENOMEM; + + bd = backlight_device_get_by_name(name); + if (bd) { + put_device(&bd->dev); + /* + * Using the same name independent of the drm device or connector + * prevents registration of multiple backlight devices in the + * driver. However, we need to use the default name for backward + * compatibility. Use unique names for subsequent backlight devices as a + * fallback when the default name already exists. + */ + kfree(name); + name = kasprintf(GFP_KERNEL, "card%d-%s-backlight", + i915->drm.primary->index, connector->base.name); + if (!name) + return -ENOMEM; + } + bd = backlight_device_register(name, connector->base.kdev, connector, + &intel_backlight_device_ops, &props); + + if (IS_ERR(bd)) { + drm_err(&i915->drm, + "[CONNECTOR:%d:%s] backlight device %s register failed: %ld\n", + connector->base.base.id, connector->base.name, name, PTR_ERR(bd)); + ret = PTR_ERR(bd); + goto out; + } + + panel->backlight.device = bd; + + drm_dbg_kms(&i915->drm, + "[CONNECTOR:%d:%s] backlight device %s registered\n", + connector->base.base.id, connector->base.name, name); + +out: + kfree(name); + + return ret; +} + +void intel_backlight_device_unregister(struct intel_connector *connector) +{ + struct intel_panel *panel = &connector->panel; + + if (panel->backlight.device) { + backlight_device_unregister(panel->backlight.device); + panel->backlight.device = NULL; + } +} +#endif /* CONFIG_BACKLIGHT_CLASS_DEVICE */ + +/* + * CNP: PWM clock frequency is 19.2 MHz or 24 MHz. + * PWM increment = 1 + */ +static u32 cnp_hz_to_pwm(struct intel_connector *connector, u32 pwm_freq_hz) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + + return DIV_ROUND_CLOSEST(KHz(RUNTIME_INFO(dev_priv)->rawclk_freq), + pwm_freq_hz); +} + +/* + * BXT: PWM clock frequency = 19.2 MHz. + */ +static u32 bxt_hz_to_pwm(struct intel_connector *connector, u32 pwm_freq_hz) +{ + return DIV_ROUND_CLOSEST(KHz(19200), pwm_freq_hz); +} + +/* + * SPT: This value represents the period of the PWM stream in clock periods + * multiplied by 16 (default increment) or 128 (alternate increment selected in + * SCHICKEN_1 bit 0). PWM clock is 24 MHz. + */ +static u32 spt_hz_to_pwm(struct intel_connector *connector, u32 pwm_freq_hz) +{ + struct intel_panel *panel = &connector->panel; + u32 mul; + + if (panel->backlight.alternate_pwm_increment) + mul = 128; + else + mul = 16; + + return DIV_ROUND_CLOSEST(MHz(24), pwm_freq_hz * mul); +} + +/* + * LPT: This value represents the period of the PWM stream in clock periods + * multiplied by 128 (default increment) or 16 (alternate increment, selected in + * LPT SOUTH_CHICKEN2 register bit 5). + */ +static u32 lpt_hz_to_pwm(struct intel_connector *connector, u32 pwm_freq_hz) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + u32 mul, clock; + + if (panel->backlight.alternate_pwm_increment) + mul = 16; + else + mul = 128; + + if (HAS_PCH_LPT_H(dev_priv)) + clock = MHz(135); /* LPT:H */ + else + clock = MHz(24); /* LPT:LP */ + + return DIV_ROUND_CLOSEST(clock, pwm_freq_hz * mul); +} + +/* + * ILK/SNB/IVB: This value represents the period of the PWM stream in PCH + * display raw clocks multiplied by 128. + */ +static u32 pch_hz_to_pwm(struct intel_connector *connector, u32 pwm_freq_hz) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + + return DIV_ROUND_CLOSEST(KHz(RUNTIME_INFO(dev_priv)->rawclk_freq), + pwm_freq_hz * 128); +} + +/* + * Gen2: This field determines the number of time base events (display core + * clock frequency/32) in total for a complete cycle of modulated backlight + * control. + * + * Gen3: A time base event equals the display core clock ([DevPNV] HRAW clock) + * divided by 32. + */ +static u32 i9xx_hz_to_pwm(struct intel_connector *connector, u32 pwm_freq_hz) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + int clock; + + if (IS_PINEVIEW(dev_priv)) + clock = KHz(RUNTIME_INFO(dev_priv)->rawclk_freq); + else + clock = KHz(dev_priv->display.cdclk.hw.cdclk); + + return DIV_ROUND_CLOSEST(clock, pwm_freq_hz * 32); +} + +/* + * Gen4: This value represents the period of the PWM stream in display core + * clocks ([DevCTG] HRAW clocks) multiplied by 128. + * + */ +static u32 i965_hz_to_pwm(struct intel_connector *connector, u32 pwm_freq_hz) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + int clock; + + if (IS_G4X(dev_priv)) + clock = KHz(RUNTIME_INFO(dev_priv)->rawclk_freq); + else + clock = KHz(dev_priv->display.cdclk.hw.cdclk); + + return DIV_ROUND_CLOSEST(clock, pwm_freq_hz * 128); +} + +/* + * VLV: This value represents the period of the PWM stream in display core + * clocks ([DevCTG] 200MHz HRAW clocks) multiplied by 128 or 25MHz S0IX clocks + * multiplied by 16. CHV uses a 19.2MHz S0IX clock. + */ +static u32 vlv_hz_to_pwm(struct intel_connector *connector, u32 pwm_freq_hz) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + int mul, clock; + + if ((intel_de_read(dev_priv, CBR1_VLV) & CBR_PWM_CLOCK_MUX_SELECT) == 0) { + if (IS_CHERRYVIEW(dev_priv)) + clock = KHz(19200); + else + clock = MHz(25); + mul = 16; + } else { + clock = KHz(RUNTIME_INFO(dev_priv)->rawclk_freq); + mul = 128; + } + + return DIV_ROUND_CLOSEST(clock, pwm_freq_hz * mul); +} + +static u16 get_vbt_pwm_freq(struct intel_connector *connector) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + u16 pwm_freq_hz = connector->panel.vbt.backlight.pwm_freq_hz; + + if (pwm_freq_hz) { + drm_dbg_kms(&dev_priv->drm, + "VBT defined backlight frequency %u Hz\n", + pwm_freq_hz); + } else { + pwm_freq_hz = 200; + drm_dbg_kms(&dev_priv->drm, + "default backlight frequency %u Hz\n", + pwm_freq_hz); + } + + return pwm_freq_hz; +} + +static u32 get_backlight_max_vbt(struct intel_connector *connector) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + u16 pwm_freq_hz = get_vbt_pwm_freq(connector); + u32 pwm; + + if (!panel->backlight.pwm_funcs->hz_to_pwm) { + drm_dbg_kms(&dev_priv->drm, + "backlight frequency conversion not supported\n"); + return 0; + } + + pwm = panel->backlight.pwm_funcs->hz_to_pwm(connector, pwm_freq_hz); + if (!pwm) { + drm_dbg_kms(&dev_priv->drm, + "backlight frequency conversion failed\n"); + return 0; + } + + return pwm; +} + +/* + * Note: The setup hooks can't assume pipe is set! + */ +static u32 get_backlight_min_vbt(struct intel_connector *connector) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + int min; + + drm_WARN_ON(&dev_priv->drm, panel->backlight.pwm_level_max == 0); + + /* + * XXX: If the vbt value is 255, it makes min equal to max, which leads + * to problems. There are such machines out there. Either our + * interpretation is wrong or the vbt has bogus data. Or both. Safeguard + * against this by letting the minimum be at most (arbitrarily chosen) + * 25% of the max. + */ + min = clamp_t(int, connector->panel.vbt.backlight.min_brightness, 0, 64); + if (min != connector->panel.vbt.backlight.min_brightness) { + drm_dbg_kms(&dev_priv->drm, + "clamping VBT min backlight %d/255 to %d/255\n", + connector->panel.vbt.backlight.min_brightness, min); + } + + /* vbt value is a coefficient in range [0..255] */ + return scale(min, 0, 255, 0, panel->backlight.pwm_level_max); +} + +static int lpt_setup_backlight(struct intel_connector *connector, enum pipe unused) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + u32 cpu_ctl2, pch_ctl1, pch_ctl2, val; + bool alt, cpu_mode; + + if (HAS_PCH_LPT(dev_priv)) + alt = intel_de_read(dev_priv, SOUTH_CHICKEN2) & LPT_PWM_GRANULARITY; + else + alt = intel_de_read(dev_priv, SOUTH_CHICKEN1) & SPT_PWM_GRANULARITY; + panel->backlight.alternate_pwm_increment = alt; + + pch_ctl1 = intel_de_read(dev_priv, BLC_PWM_PCH_CTL1); + panel->backlight.active_low_pwm = pch_ctl1 & BLM_PCH_POLARITY; + + pch_ctl2 = intel_de_read(dev_priv, BLC_PWM_PCH_CTL2); + panel->backlight.pwm_level_max = pch_ctl2 >> 16; + + cpu_ctl2 = intel_de_read(dev_priv, BLC_PWM_CPU_CTL2); + + if (!panel->backlight.pwm_level_max) + panel->backlight.pwm_level_max = get_backlight_max_vbt(connector); + + if (!panel->backlight.pwm_level_max) + return -ENODEV; + + panel->backlight.pwm_level_min = get_backlight_min_vbt(connector); + + panel->backlight.pwm_enabled = pch_ctl1 & BLM_PCH_PWM_ENABLE; + + cpu_mode = panel->backlight.pwm_enabled && HAS_PCH_LPT(dev_priv) && + !(pch_ctl1 & BLM_PCH_OVERRIDE_ENABLE) && + (cpu_ctl2 & BLM_PWM_ENABLE); + + if (cpu_mode) { + val = pch_get_backlight(connector, unused); + + drm_dbg_kms(&dev_priv->drm, + "CPU backlight register was enabled, switching to PCH override\n"); + + /* Write converted CPU PWM value to PCH override register */ + lpt_set_backlight(connector->base.state, val); + intel_de_write(dev_priv, BLC_PWM_PCH_CTL1, + pch_ctl1 | BLM_PCH_OVERRIDE_ENABLE); + + intel_de_write(dev_priv, BLC_PWM_CPU_CTL2, + cpu_ctl2 & ~BLM_PWM_ENABLE); + } + + return 0; +} + +static int pch_setup_backlight(struct intel_connector *connector, enum pipe unused) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + u32 cpu_ctl2, pch_ctl1, pch_ctl2; + + pch_ctl1 = intel_de_read(dev_priv, BLC_PWM_PCH_CTL1); + panel->backlight.active_low_pwm = pch_ctl1 & BLM_PCH_POLARITY; + + pch_ctl2 = intel_de_read(dev_priv, BLC_PWM_PCH_CTL2); + panel->backlight.pwm_level_max = pch_ctl2 >> 16; + + if (!panel->backlight.pwm_level_max) + panel->backlight.pwm_level_max = get_backlight_max_vbt(connector); + + if (!panel->backlight.pwm_level_max) + return -ENODEV; + + panel->backlight.pwm_level_min = get_backlight_min_vbt(connector); + + cpu_ctl2 = intel_de_read(dev_priv, BLC_PWM_CPU_CTL2); + panel->backlight.pwm_enabled = (cpu_ctl2 & BLM_PWM_ENABLE) && + (pch_ctl1 & BLM_PCH_PWM_ENABLE); + + return 0; +} + +static int i9xx_setup_backlight(struct intel_connector *connector, enum pipe unused) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + u32 ctl, val; + + ctl = intel_de_read(dev_priv, BLC_PWM_CTL); + + if (DISPLAY_VER(dev_priv) == 2 || IS_I915GM(dev_priv) || IS_I945GM(dev_priv)) + panel->backlight.combination_mode = ctl & BLM_LEGACY_MODE; + + if (IS_PINEVIEW(dev_priv)) + panel->backlight.active_low_pwm = ctl & BLM_POLARITY_PNV; + + panel->backlight.pwm_level_max = ctl >> 17; + + if (!panel->backlight.pwm_level_max) { + panel->backlight.pwm_level_max = get_backlight_max_vbt(connector); + panel->backlight.pwm_level_max >>= 1; + } + + if (!panel->backlight.pwm_level_max) + return -ENODEV; + + if (panel->backlight.combination_mode) + panel->backlight.pwm_level_max *= 0xff; + + panel->backlight.pwm_level_min = get_backlight_min_vbt(connector); + + val = i9xx_get_backlight(connector, unused); + val = intel_backlight_invert_pwm_level(connector, val); + val = clamp(val, panel->backlight.pwm_level_min, panel->backlight.pwm_level_max); + + panel->backlight.pwm_enabled = val != 0; + + return 0; +} + +static int i965_setup_backlight(struct intel_connector *connector, enum pipe unused) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + u32 ctl, ctl2; + + ctl2 = intel_de_read(dev_priv, BLC_PWM_CTL2); + panel->backlight.combination_mode = ctl2 & BLM_COMBINATION_MODE; + panel->backlight.active_low_pwm = ctl2 & BLM_POLARITY_I965; + + ctl = intel_de_read(dev_priv, BLC_PWM_CTL); + panel->backlight.pwm_level_max = ctl >> 16; + + if (!panel->backlight.pwm_level_max) + panel->backlight.pwm_level_max = get_backlight_max_vbt(connector); + + if (!panel->backlight.pwm_level_max) + return -ENODEV; + + if (panel->backlight.combination_mode) + panel->backlight.pwm_level_max *= 0xff; + + panel->backlight.pwm_level_min = get_backlight_min_vbt(connector); + + panel->backlight.pwm_enabled = ctl2 & BLM_PWM_ENABLE; + + return 0; +} + +static int vlv_setup_backlight(struct intel_connector *connector, enum pipe pipe) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + u32 ctl, ctl2; + + if (drm_WARN_ON(&dev_priv->drm, pipe != PIPE_A && pipe != PIPE_B)) + return -ENODEV; + + ctl2 = intel_de_read(dev_priv, VLV_BLC_PWM_CTL2(pipe)); + panel->backlight.active_low_pwm = ctl2 & BLM_POLARITY_I965; + + ctl = intel_de_read(dev_priv, VLV_BLC_PWM_CTL(pipe)); + panel->backlight.pwm_level_max = ctl >> 16; + + if (!panel->backlight.pwm_level_max) + panel->backlight.pwm_level_max = get_backlight_max_vbt(connector); + + if (!panel->backlight.pwm_level_max) + return -ENODEV; + + panel->backlight.pwm_level_min = get_backlight_min_vbt(connector); + + panel->backlight.pwm_enabled = ctl2 & BLM_PWM_ENABLE; + + return 0; +} + +static int +bxt_setup_backlight(struct intel_connector *connector, enum pipe unused) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + u32 pwm_ctl, val; + + panel->backlight.controller = connector->panel.vbt.backlight.controller; + + pwm_ctl = intel_de_read(dev_priv, + BXT_BLC_PWM_CTL(panel->backlight.controller)); + + /* Controller 1 uses the utility pin. */ + if (panel->backlight.controller == 1) { + val = intel_de_read(dev_priv, UTIL_PIN_CTL); + panel->backlight.util_pin_active_low = + val & UTIL_PIN_POLARITY; + } + + panel->backlight.active_low_pwm = pwm_ctl & BXT_BLC_PWM_POLARITY; + panel->backlight.pwm_level_max = + intel_de_read(dev_priv, BXT_BLC_PWM_FREQ(panel->backlight.controller)); + + if (!panel->backlight.pwm_level_max) + panel->backlight.pwm_level_max = get_backlight_max_vbt(connector); + + if (!panel->backlight.pwm_level_max) + return -ENODEV; + + panel->backlight.pwm_level_min = get_backlight_min_vbt(connector); + + panel->backlight.pwm_enabled = pwm_ctl & BXT_BLC_PWM_ENABLE; + + return 0; +} + +static int +cnp_setup_backlight(struct intel_connector *connector, enum pipe unused) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + u32 pwm_ctl; + + /* + * CNP has the BXT implementation of backlight, but with only one + * controller. TODO: ICP has multiple controllers but we only use + * controller 0 for now. + */ + panel->backlight.controller = 0; + + pwm_ctl = intel_de_read(dev_priv, + BXT_BLC_PWM_CTL(panel->backlight.controller)); + + panel->backlight.active_low_pwm = pwm_ctl & BXT_BLC_PWM_POLARITY; + panel->backlight.pwm_level_max = + intel_de_read(dev_priv, BXT_BLC_PWM_FREQ(panel->backlight.controller)); + + if (!panel->backlight.pwm_level_max) + panel->backlight.pwm_level_max = get_backlight_max_vbt(connector); + + if (!panel->backlight.pwm_level_max) + return -ENODEV; + + panel->backlight.pwm_level_min = get_backlight_min_vbt(connector); + + panel->backlight.pwm_enabled = pwm_ctl & BXT_BLC_PWM_ENABLE; + + return 0; +} + +static int ext_pwm_setup_backlight(struct intel_connector *connector, + enum pipe pipe) +{ + struct drm_device *dev = connector->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + struct intel_panel *panel = &connector->panel; + const char *desc; + u32 level; + + /* Get the right PWM chip for DSI backlight according to VBT */ + if (connector->panel.vbt.dsi.config->pwm_blc == PPS_BLC_PMIC) { + panel->backlight.pwm = pwm_get(dev->dev, "pwm_pmic_backlight"); + desc = "PMIC"; + } else { + panel->backlight.pwm = pwm_get(dev->dev, "pwm_soc_backlight"); + desc = "SoC"; + } + + if (IS_ERR(panel->backlight.pwm)) { + drm_err(&dev_priv->drm, "Failed to get the %s PWM chip\n", + desc); + panel->backlight.pwm = NULL; + return -ENODEV; + } + + panel->backlight.pwm_level_max = 100; /* 100% */ + panel->backlight.pwm_level_min = get_backlight_min_vbt(connector); + + if (pwm_is_enabled(panel->backlight.pwm)) { + /* PWM is already enabled, use existing settings */ + pwm_get_state(panel->backlight.pwm, &panel->backlight.pwm_state); + + level = pwm_get_relative_duty_cycle(&panel->backlight.pwm_state, + 100); + level = intel_backlight_invert_pwm_level(connector, level); + panel->backlight.pwm_enabled = true; + + drm_dbg_kms(&dev_priv->drm, "PWM already enabled at freq %ld, VBT freq %d, level %d\n", + NSEC_PER_SEC / (unsigned long)panel->backlight.pwm_state.period, + get_vbt_pwm_freq(connector), level); + } else { + /* Set period from VBT frequency, leave other settings at 0. */ + panel->backlight.pwm_state.period = + NSEC_PER_SEC / get_vbt_pwm_freq(connector); + } + + drm_info(&dev_priv->drm, "Using %s PWM for LCD backlight control\n", + desc); + return 0; +} + +static void intel_pwm_set_backlight(const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct intel_panel *panel = &connector->panel; + + panel->backlight.pwm_funcs->set(conn_state, + intel_backlight_invert_pwm_level(connector, level)); +} + +static u32 intel_pwm_get_backlight(struct intel_connector *connector, enum pipe pipe) +{ + struct intel_panel *panel = &connector->panel; + + return intel_backlight_invert_pwm_level(connector, + panel->backlight.pwm_funcs->get(connector, pipe)); +} + +static void intel_pwm_enable_backlight(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct intel_panel *panel = &connector->panel; + + panel->backlight.pwm_funcs->enable(crtc_state, conn_state, + intel_backlight_invert_pwm_level(connector, level)); +} + +static void intel_pwm_disable_backlight(const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct intel_panel *panel = &connector->panel; + + panel->backlight.pwm_funcs->disable(conn_state, + intel_backlight_invert_pwm_level(connector, level)); +} + +static int intel_pwm_setup_backlight(struct intel_connector *connector, enum pipe pipe) +{ + struct intel_panel *panel = &connector->panel; + int ret = panel->backlight.pwm_funcs->setup(connector, pipe); + + if (ret < 0) + return ret; + + panel->backlight.min = panel->backlight.pwm_level_min; + panel->backlight.max = panel->backlight.pwm_level_max; + panel->backlight.level = intel_pwm_get_backlight(connector, pipe); + panel->backlight.enabled = panel->backlight.pwm_enabled; + + return 0; +} + +void intel_backlight_update(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + + if (!panel->backlight.present) + return; + + mutex_lock(&dev_priv->display.backlight.lock); + if (!panel->backlight.enabled) + __intel_backlight_enable(crtc_state, conn_state); + + mutex_unlock(&dev_priv->display.backlight.lock); +} + +int intel_backlight_setup(struct intel_connector *connector, enum pipe pipe) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + int ret; + + if (!connector->panel.vbt.backlight.present) { + if (intel_has_quirk(dev_priv, QUIRK_BACKLIGHT_PRESENT)) { + drm_dbg_kms(&dev_priv->drm, + "no backlight present per VBT, but present per quirk\n"); + } else { + drm_dbg_kms(&dev_priv->drm, + "no backlight present per VBT\n"); + return 0; + } + } + + /* ensure intel_panel has been initialized first */ + if (drm_WARN_ON(&dev_priv->drm, !panel->backlight.funcs)) + return -ENODEV; + + /* set level and max in panel struct */ + mutex_lock(&dev_priv->display.backlight.lock); + ret = panel->backlight.funcs->setup(connector, pipe); + mutex_unlock(&dev_priv->display.backlight.lock); + + if (ret) { + drm_dbg_kms(&dev_priv->drm, + "failed to setup backlight for connector %s\n", + connector->base.name); + return ret; + } + + panel->backlight.present = true; + + drm_dbg_kms(&dev_priv->drm, + "Connector %s backlight initialized, %s, brightness %u/%u\n", + connector->base.name, + str_enabled_disabled(panel->backlight.enabled), + panel->backlight.level, panel->backlight.max); + + return 0; +} + +void intel_backlight_destroy(struct intel_panel *panel) +{ + /* dispose of the pwm */ + if (panel->backlight.pwm) + pwm_put(panel->backlight.pwm); + + panel->backlight.present = false; +} + +static const struct intel_panel_bl_funcs bxt_pwm_funcs = { + .setup = bxt_setup_backlight, + .enable = bxt_enable_backlight, + .disable = bxt_disable_backlight, + .set = bxt_set_backlight, + .get = bxt_get_backlight, + .hz_to_pwm = bxt_hz_to_pwm, +}; + +static const struct intel_panel_bl_funcs cnp_pwm_funcs = { + .setup = cnp_setup_backlight, + .enable = cnp_enable_backlight, + .disable = cnp_disable_backlight, + .set = bxt_set_backlight, + .get = bxt_get_backlight, + .hz_to_pwm = cnp_hz_to_pwm, +}; + +static const struct intel_panel_bl_funcs lpt_pwm_funcs = { + .setup = lpt_setup_backlight, + .enable = lpt_enable_backlight, + .disable = lpt_disable_backlight, + .set = lpt_set_backlight, + .get = lpt_get_backlight, + .hz_to_pwm = lpt_hz_to_pwm, +}; + +static const struct intel_panel_bl_funcs spt_pwm_funcs = { + .setup = lpt_setup_backlight, + .enable = lpt_enable_backlight, + .disable = lpt_disable_backlight, + .set = lpt_set_backlight, + .get = lpt_get_backlight, + .hz_to_pwm = spt_hz_to_pwm, +}; + +static const struct intel_panel_bl_funcs pch_pwm_funcs = { + .setup = pch_setup_backlight, + .enable = pch_enable_backlight, + .disable = pch_disable_backlight, + .set = pch_set_backlight, + .get = pch_get_backlight, + .hz_to_pwm = pch_hz_to_pwm, +}; + +static const struct intel_panel_bl_funcs ext_pwm_funcs = { + .setup = ext_pwm_setup_backlight, + .enable = ext_pwm_enable_backlight, + .disable = ext_pwm_disable_backlight, + .set = ext_pwm_set_backlight, + .get = ext_pwm_get_backlight, +}; + +static const struct intel_panel_bl_funcs vlv_pwm_funcs = { + .setup = vlv_setup_backlight, + .enable = vlv_enable_backlight, + .disable = vlv_disable_backlight, + .set = vlv_set_backlight, + .get = vlv_get_backlight, + .hz_to_pwm = vlv_hz_to_pwm, +}; + +static const struct intel_panel_bl_funcs i965_pwm_funcs = { + .setup = i965_setup_backlight, + .enable = i965_enable_backlight, + .disable = i965_disable_backlight, + .set = i9xx_set_backlight, + .get = i9xx_get_backlight, + .hz_to_pwm = i965_hz_to_pwm, +}; + +static const struct intel_panel_bl_funcs i9xx_pwm_funcs = { + .setup = i9xx_setup_backlight, + .enable = i9xx_enable_backlight, + .disable = i9xx_disable_backlight, + .set = i9xx_set_backlight, + .get = i9xx_get_backlight, + .hz_to_pwm = i9xx_hz_to_pwm, +}; + +static const struct intel_panel_bl_funcs pwm_bl_funcs = { + .setup = intel_pwm_setup_backlight, + .enable = intel_pwm_enable_backlight, + .disable = intel_pwm_disable_backlight, + .set = intel_pwm_set_backlight, + .get = intel_pwm_get_backlight, +}; + +/* Set up chip specific backlight functions */ +void intel_backlight_init_funcs(struct intel_panel *panel) +{ + struct intel_connector *connector = + container_of(panel, struct intel_connector, panel); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + + if (connector->base.connector_type == DRM_MODE_CONNECTOR_DSI && + intel_dsi_dcs_init_backlight_funcs(connector) == 0) + return; + + if (IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv)) { + panel->backlight.pwm_funcs = &bxt_pwm_funcs; + } else if (INTEL_PCH_TYPE(dev_priv) >= PCH_CNP) { + panel->backlight.pwm_funcs = &cnp_pwm_funcs; + } else if (INTEL_PCH_TYPE(dev_priv) >= PCH_LPT) { + if (HAS_PCH_LPT(dev_priv)) + panel->backlight.pwm_funcs = &lpt_pwm_funcs; + else + panel->backlight.pwm_funcs = &spt_pwm_funcs; + } else if (HAS_PCH_SPLIT(dev_priv)) { + panel->backlight.pwm_funcs = &pch_pwm_funcs; + } else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { + if (connector->base.connector_type == DRM_MODE_CONNECTOR_DSI) { + panel->backlight.pwm_funcs = &ext_pwm_funcs; + } else { + panel->backlight.pwm_funcs = &vlv_pwm_funcs; + } + } else if (DISPLAY_VER(dev_priv) == 4) { + panel->backlight.pwm_funcs = &i965_pwm_funcs; + } else { + panel->backlight.pwm_funcs = &i9xx_pwm_funcs; + } + + if (connector->base.connector_type == DRM_MODE_CONNECTOR_eDP) { + if (intel_dp_aux_init_backlight_funcs(connector) == 0) + return; + + if (!intel_has_quirk(dev_priv, QUIRK_NO_PPS_BACKLIGHT_POWER_HOOK)) + connector->panel.backlight.power = intel_pps_backlight_power; + } + + /* We're using a standard PWM backlight interface */ + panel->backlight.funcs = &pwm_bl_funcs; +} diff --git a/drivers/gpu/drm/i915/display/intel_backlight.h b/drivers/gpu/drm/i915/display/intel_backlight.h new file mode 100644 index 000000000..339643f63 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_backlight.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2021 Intel Corporation + */ + +#ifndef __INTEL_BACKLIGHT_H__ +#define __INTEL_BACKLIGHT_H__ + +#include + +struct drm_connector_state; +struct intel_atomic_state; +struct intel_connector; +struct intel_crtc_state; +struct intel_encoder; +struct intel_panel; +enum pipe; + +void intel_backlight_init_funcs(struct intel_panel *panel); +int intel_backlight_setup(struct intel_connector *connector, enum pipe pipe); +void intel_backlight_destroy(struct intel_panel *panel); + +void intel_backlight_enable(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state); +void intel_backlight_update(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state); +void intel_backlight_disable(const struct drm_connector_state *old_conn_state); + +void intel_backlight_set_acpi(const struct drm_connector_state *conn_state, + u32 level, u32 max); +void intel_backlight_set_pwm_level(const struct drm_connector_state *conn_state, + u32 level); +u32 intel_backlight_invert_pwm_level(struct intel_connector *connector, u32 level); +u32 intel_backlight_level_to_pwm(struct intel_connector *connector, u32 level); +u32 intel_backlight_level_from_pwm(struct intel_connector *connector, u32 val); + +#if IS_ENABLED(CONFIG_BACKLIGHT_CLASS_DEVICE) +int intel_backlight_device_register(struct intel_connector *connector); +void intel_backlight_device_unregister(struct intel_connector *connector); +#else /* CONFIG_BACKLIGHT_CLASS_DEVICE */ +static inline int intel_backlight_device_register(struct intel_connector *connector) +{ + return 0; +} +static inline void intel_backlight_device_unregister(struct intel_connector *connector) +{ +} +#endif /* CONFIG_BACKLIGHT_CLASS_DEVICE */ + +#endif /* __INTEL_BACKLIGHT_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_backlight_regs.h b/drivers/gpu/drm/i915/display/intel_backlight_regs.h new file mode 100644 index 000000000..50c1210f6 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_backlight_regs.h @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2022 Intel Corporation + */ + +#ifndef __INTEL_BACKLIGHT_REGS_H__ +#define __INTEL_BACKLIGHT_REGS_H__ + +#include "i915_reg_defs.h" + +#define _VLV_BLC_PWM_CTL2_A (DISPLAY_MMIO_BASE(dev_priv) + 0x61250) +#define _VLV_BLC_PWM_CTL2_B (DISPLAY_MMIO_BASE(dev_priv) + 0x61350) +#define VLV_BLC_PWM_CTL2(pipe) _MMIO_PIPE(pipe, _VLV_BLC_PWM_CTL2_A, \ + _VLV_BLC_PWM_CTL2_B) + +#define _VLV_BLC_PWM_CTL_A (DISPLAY_MMIO_BASE(dev_priv) + 0x61254) +#define _VLV_BLC_PWM_CTL_B (DISPLAY_MMIO_BASE(dev_priv) + 0x61354) +#define VLV_BLC_PWM_CTL(pipe) _MMIO_PIPE(pipe, _VLV_BLC_PWM_CTL_A, \ + _VLV_BLC_PWM_CTL_B) + +#define _VLV_BLC_HIST_CTL_A (DISPLAY_MMIO_BASE(dev_priv) + 0x61260) +#define _VLV_BLC_HIST_CTL_B (DISPLAY_MMIO_BASE(dev_priv) + 0x61360) +#define VLV_BLC_HIST_CTL(pipe) _MMIO_PIPE(pipe, _VLV_BLC_HIST_CTL_A, \ + _VLV_BLC_HIST_CTL_B) + +/* Backlight control */ +#define BLC_PWM_CTL2 _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x61250) /* 965+ only */ +#define BLM_PWM_ENABLE (1 << 31) +#define BLM_COMBINATION_MODE (1 << 30) /* gen4 only */ +#define BLM_PIPE_SELECT (1 << 29) +#define BLM_PIPE_SELECT_IVB (3 << 29) +#define BLM_PIPE_A (0 << 29) +#define BLM_PIPE_B (1 << 29) +#define BLM_PIPE_C (2 << 29) /* ivb + */ +#define BLM_TRANSCODER_A BLM_PIPE_A /* hsw */ +#define BLM_TRANSCODER_B BLM_PIPE_B +#define BLM_TRANSCODER_C BLM_PIPE_C +#define BLM_TRANSCODER_EDP (3 << 29) +#define BLM_PIPE(pipe) ((pipe) << 29) +#define BLM_POLARITY_I965 (1 << 28) /* gen4 only */ +#define BLM_PHASE_IN_INTERUPT_STATUS (1 << 26) +#define BLM_PHASE_IN_ENABLE (1 << 25) +#define BLM_PHASE_IN_INTERUPT_ENABL (1 << 24) +#define BLM_PHASE_IN_TIME_BASE_SHIFT (16) +#define BLM_PHASE_IN_TIME_BASE_MASK (0xff << 16) +#define BLM_PHASE_IN_COUNT_SHIFT (8) +#define BLM_PHASE_IN_COUNT_MASK (0xff << 8) +#define BLM_PHASE_IN_INCR_SHIFT (0) +#define BLM_PHASE_IN_INCR_MASK (0xff << 0) +#define BLC_PWM_CTL _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x61254) +/* + * This is the most significant 15 bits of the number of backlight cycles in a + * complete cycle of the modulated backlight control. + * + * The actual value is this field multiplied by two. + */ +#define BACKLIGHT_MODULATION_FREQ_SHIFT (17) +#define BACKLIGHT_MODULATION_FREQ_MASK (0x7fff << 17) +#define BLM_LEGACY_MODE (1 << 16) /* gen2 only */ +/* + * This is the number of cycles out of the backlight modulation cycle for which + * the backlight is on. + * + * This field must be no greater than the number of cycles in the complete + * backlight modulation cycle. + */ +#define BACKLIGHT_DUTY_CYCLE_SHIFT (0) +#define BACKLIGHT_DUTY_CYCLE_MASK (0xffff) +#define BACKLIGHT_DUTY_CYCLE_MASK_PNV (0xfffe) +#define BLM_POLARITY_PNV (1 << 0) /* pnv only */ + +#define BLC_HIST_CTL _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x61260) +#define BLM_HISTOGRAM_ENABLE (1 << 31) + +/* New registers for PCH-split platforms. Safe where new bits show up, the + * register layout machtes with gen4 BLC_PWM_CTL[12]. */ +#define BLC_PWM_CPU_CTL2 _MMIO(0x48250) +#define BLC_PWM_CPU_CTL _MMIO(0x48254) + +#define HSW_BLC_PWM2_CTL _MMIO(0x48350) + +/* PCH CTL1 is totally different, all but the below bits are reserved. CTL2 is + * like the normal CTL from gen4 and earlier. Hooray for confusing naming. */ +#define BLC_PWM_PCH_CTL1 _MMIO(0xc8250) +#define BLM_PCH_PWM_ENABLE (1 << 31) +#define BLM_PCH_OVERRIDE_ENABLE (1 << 30) +#define BLM_PCH_POLARITY (1 << 29) +#define BLC_PWM_PCH_CTL2 _MMIO(0xc8254) + +/* BXT backlight register definition. */ +#define _BXT_BLC_PWM_CTL1 0xC8250 +#define BXT_BLC_PWM_ENABLE (1 << 31) +#define BXT_BLC_PWM_POLARITY (1 << 29) +#define _BXT_BLC_PWM_FREQ1 0xC8254 +#define _BXT_BLC_PWM_DUTY1 0xC8258 + +#define _BXT_BLC_PWM_CTL2 0xC8350 +#define _BXT_BLC_PWM_FREQ2 0xC8354 +#define _BXT_BLC_PWM_DUTY2 0xC8358 + +#define BXT_BLC_PWM_CTL(controller) _MMIO_PIPE(controller, \ + _BXT_BLC_PWM_CTL1, _BXT_BLC_PWM_CTL2) +#define BXT_BLC_PWM_FREQ(controller) _MMIO_PIPE(controller, \ + _BXT_BLC_PWM_FREQ1, _BXT_BLC_PWM_FREQ2) +#define BXT_BLC_PWM_DUTY(controller) _MMIO_PIPE(controller, \ + _BXT_BLC_PWM_DUTY1, _BXT_BLC_PWM_DUTY2) + +/* Utility pin */ +#define UTIL_PIN_CTL _MMIO(0x48400) +#define UTIL_PIN_ENABLE (1 << 31) +#define UTIL_PIN_PIPE_MASK (3 << 29) +#define UTIL_PIN_PIPE(x) ((x) << 29) +#define UTIL_PIN_MODE_MASK (0xf << 24) +#define UTIL_PIN_MODE_DATA (0 << 24) +#define UTIL_PIN_MODE_PWM (1 << 24) +#define UTIL_PIN_MODE_VBLANK (4 << 24) +#define UTIL_PIN_MODE_VSYNC (5 << 24) +#define UTIL_PIN_MODE_EYE_LEVEL (8 << 24) +#define UTIL_PIN_OUTPUT_DATA (1 << 23) +#define UTIL_PIN_POLARITY (1 << 22) +#define UTIL_PIN_DIRECTION_INPUT (1 << 19) +#define UTIL_PIN_INPUT_DATA (1 << 16) + +#endif /* __INTEL_BACKLIGHT_REGS_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_bios.c b/drivers/gpu/drm/i915/display/intel_bios.c new file mode 100644 index 000000000..a70b70617 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_bios.c @@ -0,0 +1,3790 @@ +/* + * Copyright © 2006 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Eric Anholt + * + */ + +#include +#include +#include + +#include "display/intel_display.h" +#include "display/intel_display_types.h" +#include "display/intel_gmbus.h" + +#include "i915_drv.h" +#include "i915_reg.h" + +#define _INTEL_BIOS_PRIVATE +#include "intel_vbt_defs.h" + +/** + * DOC: Video BIOS Table (VBT) + * + * The Video BIOS Table, or VBT, provides platform and board specific + * configuration information to the driver that is not discoverable or available + * through other means. The configuration is mostly related to display + * hardware. The VBT is available via the ACPI OpRegion or, on older systems, in + * the PCI ROM. + * + * The VBT consists of a VBT Header (defined as &struct vbt_header), a BDB + * Header (&struct bdb_header), and a number of BIOS Data Blocks (BDB) that + * contain the actual configuration information. The VBT Header, and thus the + * VBT, begins with "$VBT" signature. The VBT Header contains the offset of the + * BDB Header. The data blocks are concatenated after the BDB Header. The data + * blocks have a 1-byte Block ID, 2-byte Block Size, and Block Size bytes of + * data. (Block 53, the MIPI Sequence Block is an exception.) + * + * The driver parses the VBT during load. The relevant information is stored in + * driver private data for ease of use, and the actual VBT is not read after + * that. + */ + +/* Wrapper for VBT child device config */ +struct intel_bios_encoder_data { + struct drm_i915_private *i915; + + struct child_device_config child; + struct dsc_compression_parameters_entry *dsc; + struct list_head node; +}; + +#define SLAVE_ADDR1 0x70 +#define SLAVE_ADDR2 0x72 + +/* Get BDB block size given a pointer to Block ID. */ +static u32 _get_blocksize(const u8 *block_base) +{ + /* The MIPI Sequence Block v3+ has a separate size field. */ + if (*block_base == BDB_MIPI_SEQUENCE && *(block_base + 3) >= 3) + return *((const u32 *)(block_base + 4)); + else + return *((const u16 *)(block_base + 1)); +} + +/* Get BDB block size give a pointer to data after Block ID and Block Size. */ +static u32 get_blocksize(const void *block_data) +{ + return _get_blocksize(block_data - 3); +} + +static const void * +find_raw_section(const void *_bdb, enum bdb_block_id section_id) +{ + const struct bdb_header *bdb = _bdb; + const u8 *base = _bdb; + int index = 0; + u32 total, current_size; + enum bdb_block_id current_id; + + /* skip to first section */ + index += bdb->header_size; + total = bdb->bdb_size; + + /* walk the sections looking for section_id */ + while (index + 3 < total) { + current_id = *(base + index); + current_size = _get_blocksize(base + index); + index += 3; + + if (index + current_size > total) + return NULL; + + if (current_id == section_id) + return base + index; + + index += current_size; + } + + return NULL; +} + +/* + * Offset from the start of BDB to the start of the + * block data (just past the block header). + */ +static u32 raw_block_offset(const void *bdb, enum bdb_block_id section_id) +{ + const void *block; + + block = find_raw_section(bdb, section_id); + if (!block) + return 0; + + return block - bdb; +} + +struct bdb_block_entry { + struct list_head node; + enum bdb_block_id section_id; + u8 data[]; +}; + +static const void * +find_section(struct drm_i915_private *i915, + enum bdb_block_id section_id) +{ + struct bdb_block_entry *entry; + + list_for_each_entry(entry, &i915->display.vbt.bdb_blocks, node) { + if (entry->section_id == section_id) + return entry->data + 3; + } + + return NULL; +} + +static const struct { + enum bdb_block_id section_id; + size_t min_size; +} bdb_blocks[] = { + { .section_id = BDB_GENERAL_FEATURES, + .min_size = sizeof(struct bdb_general_features), }, + { .section_id = BDB_GENERAL_DEFINITIONS, + .min_size = sizeof(struct bdb_general_definitions), }, + { .section_id = BDB_PSR, + .min_size = sizeof(struct bdb_psr), }, + { .section_id = BDB_DRIVER_FEATURES, + .min_size = sizeof(struct bdb_driver_features), }, + { .section_id = BDB_SDVO_LVDS_OPTIONS, + .min_size = sizeof(struct bdb_sdvo_lvds_options), }, + { .section_id = BDB_SDVO_PANEL_DTDS, + .min_size = sizeof(struct bdb_sdvo_panel_dtds), }, + { .section_id = BDB_EDP, + .min_size = sizeof(struct bdb_edp), }, + { .section_id = BDB_LVDS_OPTIONS, + .min_size = sizeof(struct bdb_lvds_options), }, + /* + * BDB_LVDS_LFP_DATA depends on BDB_LVDS_LFP_DATA_PTRS, + * so keep the two ordered. + */ + { .section_id = BDB_LVDS_LFP_DATA_PTRS, + .min_size = sizeof(struct bdb_lvds_lfp_data_ptrs), }, + { .section_id = BDB_LVDS_LFP_DATA, + .min_size = 0, /* special case */ }, + { .section_id = BDB_LVDS_BACKLIGHT, + .min_size = sizeof(struct bdb_lfp_backlight_data), }, + { .section_id = BDB_LFP_POWER, + .min_size = sizeof(struct bdb_lfp_power), }, + { .section_id = BDB_MIPI_CONFIG, + .min_size = sizeof(struct bdb_mipi_config), }, + { .section_id = BDB_MIPI_SEQUENCE, + .min_size = sizeof(struct bdb_mipi_sequence) }, + { .section_id = BDB_COMPRESSION_PARAMETERS, + .min_size = sizeof(struct bdb_compression_parameters), }, + { .section_id = BDB_GENERIC_DTD, + .min_size = sizeof(struct bdb_generic_dtd), }, +}; + +static size_t lfp_data_min_size(struct drm_i915_private *i915) +{ + const struct bdb_lvds_lfp_data_ptrs *ptrs; + size_t size; + + ptrs = find_section(i915, BDB_LVDS_LFP_DATA_PTRS); + if (!ptrs) + return 0; + + size = sizeof(struct bdb_lvds_lfp_data); + if (ptrs->panel_name.table_size) + size = max(size, ptrs->panel_name.offset + + sizeof(struct bdb_lvds_lfp_data_tail)); + + return size; +} + +static bool validate_lfp_data_ptrs(const void *bdb, + const struct bdb_lvds_lfp_data_ptrs *ptrs) +{ + int fp_timing_size, dvo_timing_size, panel_pnp_id_size, panel_name_size; + int data_block_size, lfp_data_size; + const void *data_block; + int i; + + data_block = find_raw_section(bdb, BDB_LVDS_LFP_DATA); + if (!data_block) + return false; + + data_block_size = get_blocksize(data_block); + if (data_block_size == 0) + return false; + + /* always 3 indicating the presence of fp_timing+dvo_timing+panel_pnp_id */ + if (ptrs->lvds_entries != 3) + return false; + + fp_timing_size = ptrs->ptr[0].fp_timing.table_size; + dvo_timing_size = ptrs->ptr[0].dvo_timing.table_size; + panel_pnp_id_size = ptrs->ptr[0].panel_pnp_id.table_size; + panel_name_size = ptrs->panel_name.table_size; + + /* fp_timing has variable size */ + if (fp_timing_size < 32 || + dvo_timing_size != sizeof(struct lvds_dvo_timing) || + panel_pnp_id_size != sizeof(struct lvds_pnp_id)) + return false; + + /* panel_name is not present in old VBTs */ + if (panel_name_size != 0 && + panel_name_size != sizeof(struct lvds_lfp_panel_name)) + return false; + + lfp_data_size = ptrs->ptr[1].fp_timing.offset - ptrs->ptr[0].fp_timing.offset; + if (16 * lfp_data_size > data_block_size) + return false; + + /* make sure the table entries have uniform size */ + for (i = 1; i < 16; i++) { + if (ptrs->ptr[i].fp_timing.table_size != fp_timing_size || + ptrs->ptr[i].dvo_timing.table_size != dvo_timing_size || + ptrs->ptr[i].panel_pnp_id.table_size != panel_pnp_id_size) + return false; + + if (ptrs->ptr[i].fp_timing.offset - ptrs->ptr[i-1].fp_timing.offset != lfp_data_size || + ptrs->ptr[i].dvo_timing.offset - ptrs->ptr[i-1].dvo_timing.offset != lfp_data_size || + ptrs->ptr[i].panel_pnp_id.offset - ptrs->ptr[i-1].panel_pnp_id.offset != lfp_data_size) + return false; + } + + /* + * Except for vlv/chv machines all real VBTs seem to have 6 + * unaccounted bytes in the fp_timing table. And it doesn't + * appear to be a really intentional hole as the fp_timing + * 0xffff terminator is always within those 6 missing bytes. + */ + if (fp_timing_size + 6 + dvo_timing_size + panel_pnp_id_size == lfp_data_size) + fp_timing_size += 6; + + if (fp_timing_size + dvo_timing_size + panel_pnp_id_size != lfp_data_size) + return false; + + if (ptrs->ptr[0].fp_timing.offset + fp_timing_size != ptrs->ptr[0].dvo_timing.offset || + ptrs->ptr[0].dvo_timing.offset + dvo_timing_size != ptrs->ptr[0].panel_pnp_id.offset || + ptrs->ptr[0].panel_pnp_id.offset + panel_pnp_id_size != lfp_data_size) + return false; + + /* make sure the tables fit inside the data block */ + for (i = 0; i < 16; i++) { + if (ptrs->ptr[i].fp_timing.offset + fp_timing_size > data_block_size || + ptrs->ptr[i].dvo_timing.offset + dvo_timing_size > data_block_size || + ptrs->ptr[i].panel_pnp_id.offset + panel_pnp_id_size > data_block_size) + return false; + } + + if (ptrs->panel_name.offset + 16 * panel_name_size > data_block_size) + return false; + + /* make sure fp_timing terminators are present at expected locations */ + for (i = 0; i < 16; i++) { + const u16 *t = data_block + ptrs->ptr[i].fp_timing.offset + + fp_timing_size - 2; + + if (*t != 0xffff) + return false; + } + + return true; +} + +/* make the data table offsets relative to the data block */ +static bool fixup_lfp_data_ptrs(const void *bdb, void *ptrs_block) +{ + struct bdb_lvds_lfp_data_ptrs *ptrs = ptrs_block; + u32 offset; + int i; + + offset = raw_block_offset(bdb, BDB_LVDS_LFP_DATA); + + for (i = 0; i < 16; i++) { + if (ptrs->ptr[i].fp_timing.offset < offset || + ptrs->ptr[i].dvo_timing.offset < offset || + ptrs->ptr[i].panel_pnp_id.offset < offset) + return false; + + ptrs->ptr[i].fp_timing.offset -= offset; + ptrs->ptr[i].dvo_timing.offset -= offset; + ptrs->ptr[i].panel_pnp_id.offset -= offset; + } + + if (ptrs->panel_name.table_size) { + if (ptrs->panel_name.offset < offset) + return false; + + ptrs->panel_name.offset -= offset; + } + + return validate_lfp_data_ptrs(bdb, ptrs); +} + +static int make_lfp_data_ptr(struct lvds_lfp_data_ptr_table *table, + int table_size, int total_size) +{ + if (total_size < table_size) + return total_size; + + table->table_size = table_size; + table->offset = total_size - table_size; + + return total_size - table_size; +} + +static void next_lfp_data_ptr(struct lvds_lfp_data_ptr_table *next, + const struct lvds_lfp_data_ptr_table *prev, + int size) +{ + next->table_size = prev->table_size; + next->offset = prev->offset + size; +} + +static void *generate_lfp_data_ptrs(struct drm_i915_private *i915, + const void *bdb) +{ + int i, size, table_size, block_size, offset, fp_timing_size; + struct bdb_lvds_lfp_data_ptrs *ptrs; + const void *block; + void *ptrs_block; + + /* + * The hardcoded fp_timing_size is only valid for + * modernish VBTs. All older VBTs definitely should + * include block 41 and thus we don't need to + * generate one. + */ + if (i915->display.vbt.version < 155) + return NULL; + + fp_timing_size = 38; + + block = find_raw_section(bdb, BDB_LVDS_LFP_DATA); + if (!block) + return NULL; + + drm_dbg_kms(&i915->drm, "Generating LFP data table pointers\n"); + + block_size = get_blocksize(block); + + size = fp_timing_size + sizeof(struct lvds_dvo_timing) + + sizeof(struct lvds_pnp_id); + if (size * 16 > block_size) + return NULL; + + ptrs_block = kzalloc(sizeof(*ptrs) + 3, GFP_KERNEL); + if (!ptrs_block) + return NULL; + + *(u8 *)(ptrs_block + 0) = BDB_LVDS_LFP_DATA_PTRS; + *(u16 *)(ptrs_block + 1) = sizeof(*ptrs); + ptrs = ptrs_block + 3; + + table_size = sizeof(struct lvds_pnp_id); + size = make_lfp_data_ptr(&ptrs->ptr[0].panel_pnp_id, table_size, size); + + table_size = sizeof(struct lvds_dvo_timing); + size = make_lfp_data_ptr(&ptrs->ptr[0].dvo_timing, table_size, size); + + table_size = fp_timing_size; + size = make_lfp_data_ptr(&ptrs->ptr[0].fp_timing, table_size, size); + + if (ptrs->ptr[0].fp_timing.table_size) + ptrs->lvds_entries++; + if (ptrs->ptr[0].dvo_timing.table_size) + ptrs->lvds_entries++; + if (ptrs->ptr[0].panel_pnp_id.table_size) + ptrs->lvds_entries++; + + if (size != 0 || ptrs->lvds_entries != 3) { + kfree(ptrs_block); + return NULL; + } + + size = fp_timing_size + sizeof(struct lvds_dvo_timing) + + sizeof(struct lvds_pnp_id); + for (i = 1; i < 16; i++) { + next_lfp_data_ptr(&ptrs->ptr[i].fp_timing, &ptrs->ptr[i-1].fp_timing, size); + next_lfp_data_ptr(&ptrs->ptr[i].dvo_timing, &ptrs->ptr[i-1].dvo_timing, size); + next_lfp_data_ptr(&ptrs->ptr[i].panel_pnp_id, &ptrs->ptr[i-1].panel_pnp_id, size); + } + + table_size = sizeof(struct lvds_lfp_panel_name); + + if (16 * (size + table_size) <= block_size) { + ptrs->panel_name.table_size = table_size; + ptrs->panel_name.offset = size * 16; + } + + offset = block - bdb; + + for (i = 0; i < 16; i++) { + ptrs->ptr[i].fp_timing.offset += offset; + ptrs->ptr[i].dvo_timing.offset += offset; + ptrs->ptr[i].panel_pnp_id.offset += offset; + } + + if (ptrs->panel_name.table_size) + ptrs->panel_name.offset += offset; + + return ptrs_block; +} + +static void +init_bdb_block(struct drm_i915_private *i915, + const void *bdb, enum bdb_block_id section_id, + size_t min_size) +{ + struct bdb_block_entry *entry; + void *temp_block = NULL; + const void *block; + size_t block_size; + + block = find_raw_section(bdb, section_id); + + /* Modern VBTs lack the LFP data table pointers block, make one up */ + if (!block && section_id == BDB_LVDS_LFP_DATA_PTRS) { + temp_block = generate_lfp_data_ptrs(i915, bdb); + if (temp_block) + block = temp_block + 3; + } + if (!block) + return; + + drm_WARN(&i915->drm, min_size == 0, + "Block %d min_size is zero\n", section_id); + + block_size = get_blocksize(block); + + /* + * Version number and new block size are considered + * part of the header for MIPI sequenece block v3+. + */ + if (section_id == BDB_MIPI_SEQUENCE && *(const u8 *)block >= 3) + block_size += 5; + + entry = kzalloc(struct_size(entry, data, max(min_size, block_size) + 3), + GFP_KERNEL); + if (!entry) { + kfree(temp_block); + return; + } + + entry->section_id = section_id; + memcpy(entry->data, block - 3, block_size + 3); + + kfree(temp_block); + + drm_dbg_kms(&i915->drm, "Found BDB block %d (size %zu, min size %zu)\n", + section_id, block_size, min_size); + + if (section_id == BDB_LVDS_LFP_DATA_PTRS && + !fixup_lfp_data_ptrs(bdb, entry->data + 3)) { + drm_err(&i915->drm, "VBT has malformed LFP data table pointers\n"); + kfree(entry); + return; + } + + list_add_tail(&entry->node, &i915->display.vbt.bdb_blocks); +} + +static void init_bdb_blocks(struct drm_i915_private *i915, + const void *bdb) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bdb_blocks); i++) { + enum bdb_block_id section_id = bdb_blocks[i].section_id; + size_t min_size = bdb_blocks[i].min_size; + + if (section_id == BDB_LVDS_LFP_DATA) + min_size = lfp_data_min_size(i915); + + init_bdb_block(i915, bdb, section_id, min_size); + } +} + +static void +fill_detail_timing_data(struct drm_display_mode *panel_fixed_mode, + const struct lvds_dvo_timing *dvo_timing) +{ + panel_fixed_mode->hdisplay = (dvo_timing->hactive_hi << 8) | + dvo_timing->hactive_lo; + panel_fixed_mode->hsync_start = panel_fixed_mode->hdisplay + + ((dvo_timing->hsync_off_hi << 8) | dvo_timing->hsync_off_lo); + panel_fixed_mode->hsync_end = panel_fixed_mode->hsync_start + + ((dvo_timing->hsync_pulse_width_hi << 8) | + dvo_timing->hsync_pulse_width_lo); + panel_fixed_mode->htotal = panel_fixed_mode->hdisplay + + ((dvo_timing->hblank_hi << 8) | dvo_timing->hblank_lo); + + panel_fixed_mode->vdisplay = (dvo_timing->vactive_hi << 8) | + dvo_timing->vactive_lo; + panel_fixed_mode->vsync_start = panel_fixed_mode->vdisplay + + ((dvo_timing->vsync_off_hi << 4) | dvo_timing->vsync_off_lo); + panel_fixed_mode->vsync_end = panel_fixed_mode->vsync_start + + ((dvo_timing->vsync_pulse_width_hi << 4) | + dvo_timing->vsync_pulse_width_lo); + panel_fixed_mode->vtotal = panel_fixed_mode->vdisplay + + ((dvo_timing->vblank_hi << 8) | dvo_timing->vblank_lo); + panel_fixed_mode->clock = dvo_timing->clock * 10; + panel_fixed_mode->type = DRM_MODE_TYPE_PREFERRED; + + if (dvo_timing->hsync_positive) + panel_fixed_mode->flags |= DRM_MODE_FLAG_PHSYNC; + else + panel_fixed_mode->flags |= DRM_MODE_FLAG_NHSYNC; + + if (dvo_timing->vsync_positive) + panel_fixed_mode->flags |= DRM_MODE_FLAG_PVSYNC; + else + panel_fixed_mode->flags |= DRM_MODE_FLAG_NVSYNC; + + panel_fixed_mode->width_mm = (dvo_timing->himage_hi << 8) | + dvo_timing->himage_lo; + panel_fixed_mode->height_mm = (dvo_timing->vimage_hi << 8) | + dvo_timing->vimage_lo; + + /* Some VBTs have bogus h/vtotal values */ + if (panel_fixed_mode->hsync_end > panel_fixed_mode->htotal) + panel_fixed_mode->htotal = panel_fixed_mode->hsync_end + 1; + if (panel_fixed_mode->vsync_end > panel_fixed_mode->vtotal) + panel_fixed_mode->vtotal = panel_fixed_mode->vsync_end + 1; + + drm_mode_set_name(panel_fixed_mode); +} + +static const struct lvds_dvo_timing * +get_lvds_dvo_timing(const struct bdb_lvds_lfp_data *data, + const struct bdb_lvds_lfp_data_ptrs *ptrs, + int index) +{ + return (const void *)data + ptrs->ptr[index].dvo_timing.offset; +} + +static const struct lvds_fp_timing * +get_lvds_fp_timing(const struct bdb_lvds_lfp_data *data, + const struct bdb_lvds_lfp_data_ptrs *ptrs, + int index) +{ + return (const void *)data + ptrs->ptr[index].fp_timing.offset; +} + +static const struct lvds_pnp_id * +get_lvds_pnp_id(const struct bdb_lvds_lfp_data *data, + const struct bdb_lvds_lfp_data_ptrs *ptrs, + int index) +{ + return (const void *)data + ptrs->ptr[index].panel_pnp_id.offset; +} + +static const struct bdb_lvds_lfp_data_tail * +get_lfp_data_tail(const struct bdb_lvds_lfp_data *data, + const struct bdb_lvds_lfp_data_ptrs *ptrs) +{ + if (ptrs->panel_name.table_size) + return (const void *)data + ptrs->panel_name.offset; + else + return NULL; +} + +static void dump_pnp_id(struct drm_i915_private *i915, + const struct lvds_pnp_id *pnp_id, + const char *name) +{ + u16 mfg_name = be16_to_cpu((__force __be16)pnp_id->mfg_name); + char vend[4]; + + drm_dbg_kms(&i915->drm, "%s PNPID mfg: %s (0x%x), prod: %u, serial: %u, week: %d, year: %d\n", + name, drm_edid_decode_mfg_id(mfg_name, vend), + pnp_id->mfg_name, pnp_id->product_code, pnp_id->serial, + pnp_id->mfg_week, pnp_id->mfg_year + 1990); +} + +static int opregion_get_panel_type(struct drm_i915_private *i915, + const struct intel_bios_encoder_data *devdata, + const struct edid *edid, bool use_fallback) +{ + return intel_opregion_get_panel_type(i915); +} + +static int vbt_get_panel_type(struct drm_i915_private *i915, + const struct intel_bios_encoder_data *devdata, + const struct edid *edid, bool use_fallback) +{ + const struct bdb_lvds_options *lvds_options; + + lvds_options = find_section(i915, BDB_LVDS_OPTIONS); + if (!lvds_options) + return -1; + + if (lvds_options->panel_type > 0xf && + lvds_options->panel_type != 0xff) { + drm_dbg_kms(&i915->drm, "Invalid VBT panel type 0x%x\n", + lvds_options->panel_type); + return -1; + } + + if (devdata && devdata->child.handle == DEVICE_HANDLE_LFP2) + return lvds_options->panel_type2; + + drm_WARN_ON(&i915->drm, devdata && devdata->child.handle != DEVICE_HANDLE_LFP1); + + return lvds_options->panel_type; +} + +static int pnpid_get_panel_type(struct drm_i915_private *i915, + const struct intel_bios_encoder_data *devdata, + const struct edid *edid, bool use_fallback) +{ + const struct bdb_lvds_lfp_data *data; + const struct bdb_lvds_lfp_data_ptrs *ptrs; + const struct lvds_pnp_id *edid_id; + struct lvds_pnp_id edid_id_nodate; + int i, best = -1; + + if (!edid) + return -1; + + edid_id = (const void *)&edid->mfg_id[0]; + + edid_id_nodate = *edid_id; + edid_id_nodate.mfg_week = 0; + edid_id_nodate.mfg_year = 0; + + dump_pnp_id(i915, edid_id, "EDID"); + + ptrs = find_section(i915, BDB_LVDS_LFP_DATA_PTRS); + if (!ptrs) + return -1; + + data = find_section(i915, BDB_LVDS_LFP_DATA); + if (!data) + return -1; + + for (i = 0; i < 16; i++) { + const struct lvds_pnp_id *vbt_id = + get_lvds_pnp_id(data, ptrs, i); + + /* full match? */ + if (!memcmp(vbt_id, edid_id, sizeof(*vbt_id))) + return i; + + /* + * Accept a match w/o date if no full match is found, + * and the VBT entry does not specify a date. + */ + if (best < 0 && + !memcmp(vbt_id, &edid_id_nodate, sizeof(*vbt_id))) + best = i; + } + + return best; +} + +static int fallback_get_panel_type(struct drm_i915_private *i915, + const struct intel_bios_encoder_data *devdata, + const struct edid *edid, bool use_fallback) +{ + return use_fallback ? 0 : -1; +} + +enum panel_type { + PANEL_TYPE_OPREGION, + PANEL_TYPE_VBT, + PANEL_TYPE_PNPID, + PANEL_TYPE_FALLBACK, +}; + +static int get_panel_type(struct drm_i915_private *i915, + const struct intel_bios_encoder_data *devdata, + const struct edid *edid, bool use_fallback) +{ + struct { + const char *name; + int (*get_panel_type)(struct drm_i915_private *i915, + const struct intel_bios_encoder_data *devdata, + const struct edid *edid, bool use_fallback); + int panel_type; + } panel_types[] = { + [PANEL_TYPE_OPREGION] = { + .name = "OpRegion", + .get_panel_type = opregion_get_panel_type, + }, + [PANEL_TYPE_VBT] = { + .name = "VBT", + .get_panel_type = vbt_get_panel_type, + }, + [PANEL_TYPE_PNPID] = { + .name = "PNPID", + .get_panel_type = pnpid_get_panel_type, + }, + [PANEL_TYPE_FALLBACK] = { + .name = "fallback", + .get_panel_type = fallback_get_panel_type, + }, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(panel_types); i++) { + panel_types[i].panel_type = panel_types[i].get_panel_type(i915, devdata, + edid, use_fallback); + + drm_WARN_ON(&i915->drm, panel_types[i].panel_type > 0xf && + panel_types[i].panel_type != 0xff); + + if (panel_types[i].panel_type >= 0) + drm_dbg_kms(&i915->drm, "Panel type (%s): %d\n", + panel_types[i].name, panel_types[i].panel_type); + } + + if (panel_types[PANEL_TYPE_OPREGION].panel_type >= 0) + i = PANEL_TYPE_OPREGION; + else if (panel_types[PANEL_TYPE_VBT].panel_type == 0xff && + panel_types[PANEL_TYPE_PNPID].panel_type >= 0) + i = PANEL_TYPE_PNPID; + else if (panel_types[PANEL_TYPE_VBT].panel_type != 0xff && + panel_types[PANEL_TYPE_VBT].panel_type >= 0) + i = PANEL_TYPE_VBT; + else + i = PANEL_TYPE_FALLBACK; + + drm_dbg_kms(&i915->drm, "Selected panel type (%s): %d\n", + panel_types[i].name, panel_types[i].panel_type); + + return panel_types[i].panel_type; +} + +static unsigned int panel_bits(unsigned int value, int panel_type, int num_bits) +{ + return (value >> (panel_type * num_bits)) & (BIT(num_bits) - 1); +} + +static bool panel_bool(unsigned int value, int panel_type) +{ + return panel_bits(value, panel_type, 1); +} + +/* Parse general panel options */ +static void +parse_panel_options(struct drm_i915_private *i915, + struct intel_panel *panel) +{ + const struct bdb_lvds_options *lvds_options; + int panel_type = panel->vbt.panel_type; + int drrs_mode; + + lvds_options = find_section(i915, BDB_LVDS_OPTIONS); + if (!lvds_options) + return; + + panel->vbt.lvds_dither = lvds_options->pixel_dither; + + /* + * Empirical evidence indicates the block size can be + * either 4,14,16,24+ bytes. For older VBTs no clear + * relationship between the block size vs. BDB version. + */ + if (get_blocksize(lvds_options) < 16) + return; + + drrs_mode = panel_bits(lvds_options->dps_panel_type_bits, + panel_type, 2); + /* + * VBT has static DRRS = 0 and seamless DRRS = 2. + * The below piece of code is required to adjust vbt.drrs_type + * to match the enum drrs_support_type. + */ + switch (drrs_mode) { + case 0: + panel->vbt.drrs_type = DRRS_TYPE_STATIC; + drm_dbg_kms(&i915->drm, "DRRS supported mode is static\n"); + break; + case 2: + panel->vbt.drrs_type = DRRS_TYPE_SEAMLESS; + drm_dbg_kms(&i915->drm, + "DRRS supported mode is seamless\n"); + break; + default: + panel->vbt.drrs_type = DRRS_TYPE_NONE; + drm_dbg_kms(&i915->drm, + "DRRS not supported (VBT input)\n"); + break; + } +} + +static void +parse_lfp_panel_dtd(struct drm_i915_private *i915, + struct intel_panel *panel, + const struct bdb_lvds_lfp_data *lvds_lfp_data, + const struct bdb_lvds_lfp_data_ptrs *lvds_lfp_data_ptrs) +{ + const struct lvds_dvo_timing *panel_dvo_timing; + const struct lvds_fp_timing *fp_timing; + struct drm_display_mode *panel_fixed_mode; + int panel_type = panel->vbt.panel_type; + + panel_dvo_timing = get_lvds_dvo_timing(lvds_lfp_data, + lvds_lfp_data_ptrs, + panel_type); + + panel_fixed_mode = kzalloc(sizeof(*panel_fixed_mode), GFP_KERNEL); + if (!panel_fixed_mode) + return; + + fill_detail_timing_data(panel_fixed_mode, panel_dvo_timing); + + panel->vbt.lfp_lvds_vbt_mode = panel_fixed_mode; + + drm_dbg_kms(&i915->drm, + "Found panel mode in BIOS VBT legacy lfp table: " DRM_MODE_FMT "\n", + DRM_MODE_ARG(panel_fixed_mode)); + + fp_timing = get_lvds_fp_timing(lvds_lfp_data, + lvds_lfp_data_ptrs, + panel_type); + + /* check the resolution, just to be sure */ + if (fp_timing->x_res == panel_fixed_mode->hdisplay && + fp_timing->y_res == panel_fixed_mode->vdisplay) { + panel->vbt.bios_lvds_val = fp_timing->lvds_reg_val; + drm_dbg_kms(&i915->drm, + "VBT initial LVDS value %x\n", + panel->vbt.bios_lvds_val); + } +} + +static void +parse_lfp_data(struct drm_i915_private *i915, + struct intel_panel *panel) +{ + const struct bdb_lvds_lfp_data *data; + const struct bdb_lvds_lfp_data_tail *tail; + const struct bdb_lvds_lfp_data_ptrs *ptrs; + const struct lvds_pnp_id *pnp_id; + int panel_type = panel->vbt.panel_type; + + ptrs = find_section(i915, BDB_LVDS_LFP_DATA_PTRS); + if (!ptrs) + return; + + data = find_section(i915, BDB_LVDS_LFP_DATA); + if (!data) + return; + + if (!panel->vbt.lfp_lvds_vbt_mode) + parse_lfp_panel_dtd(i915, panel, data, ptrs); + + pnp_id = get_lvds_pnp_id(data, ptrs, panel_type); + dump_pnp_id(i915, pnp_id, "Panel"); + + tail = get_lfp_data_tail(data, ptrs); + if (!tail) + return; + + drm_dbg_kms(&i915->drm, "Panel name: %.*s\n", + (int)sizeof(tail->panel_name[0].name), + tail->panel_name[panel_type].name); + + if (i915->display.vbt.version >= 188) { + panel->vbt.seamless_drrs_min_refresh_rate = + tail->seamless_drrs_min_refresh_rate[panel_type]; + drm_dbg_kms(&i915->drm, + "Seamless DRRS min refresh rate: %d Hz\n", + panel->vbt.seamless_drrs_min_refresh_rate); + } +} + +static void +parse_generic_dtd(struct drm_i915_private *i915, + struct intel_panel *panel) +{ + const struct bdb_generic_dtd *generic_dtd; + const struct generic_dtd_entry *dtd; + struct drm_display_mode *panel_fixed_mode; + int num_dtd; + + /* + * Older VBTs provided DTD information for internal displays through + * the "LFP panel tables" block (42). As of VBT revision 229 the + * DTD information should be provided via a newer "generic DTD" + * block (58). Just to be safe, we'll try the new generic DTD block + * first on VBT >= 229, but still fall back to trying the old LFP + * block if that fails. + */ + if (i915->display.vbt.version < 229) + return; + + generic_dtd = find_section(i915, BDB_GENERIC_DTD); + if (!generic_dtd) + return; + + if (generic_dtd->gdtd_size < sizeof(struct generic_dtd_entry)) { + drm_err(&i915->drm, "GDTD size %u is too small.\n", + generic_dtd->gdtd_size); + return; + } else if (generic_dtd->gdtd_size != + sizeof(struct generic_dtd_entry)) { + drm_err(&i915->drm, "Unexpected GDTD size %u\n", + generic_dtd->gdtd_size); + /* DTD has unknown fields, but keep going */ + } + + num_dtd = (get_blocksize(generic_dtd) - + sizeof(struct bdb_generic_dtd)) / generic_dtd->gdtd_size; + if (panel->vbt.panel_type >= num_dtd) { + drm_err(&i915->drm, + "Panel type %d not found in table of %d DTD's\n", + panel->vbt.panel_type, num_dtd); + return; + } + + dtd = &generic_dtd->dtd[panel->vbt.panel_type]; + + panel_fixed_mode = kzalloc(sizeof(*panel_fixed_mode), GFP_KERNEL); + if (!panel_fixed_mode) + return; + + panel_fixed_mode->hdisplay = dtd->hactive; + panel_fixed_mode->hsync_start = + panel_fixed_mode->hdisplay + dtd->hfront_porch; + panel_fixed_mode->hsync_end = + panel_fixed_mode->hsync_start + dtd->hsync; + panel_fixed_mode->htotal = + panel_fixed_mode->hdisplay + dtd->hblank; + + panel_fixed_mode->vdisplay = dtd->vactive; + panel_fixed_mode->vsync_start = + panel_fixed_mode->vdisplay + dtd->vfront_porch; + panel_fixed_mode->vsync_end = + panel_fixed_mode->vsync_start + dtd->vsync; + panel_fixed_mode->vtotal = + panel_fixed_mode->vdisplay + dtd->vblank; + + panel_fixed_mode->clock = dtd->pixel_clock; + panel_fixed_mode->width_mm = dtd->width_mm; + panel_fixed_mode->height_mm = dtd->height_mm; + + panel_fixed_mode->type = DRM_MODE_TYPE_PREFERRED; + drm_mode_set_name(panel_fixed_mode); + + if (dtd->hsync_positive_polarity) + panel_fixed_mode->flags |= DRM_MODE_FLAG_PHSYNC; + else + panel_fixed_mode->flags |= DRM_MODE_FLAG_NHSYNC; + + if (dtd->vsync_positive_polarity) + panel_fixed_mode->flags |= DRM_MODE_FLAG_PVSYNC; + else + panel_fixed_mode->flags |= DRM_MODE_FLAG_NVSYNC; + + drm_dbg_kms(&i915->drm, + "Found panel mode in BIOS VBT generic dtd table: " DRM_MODE_FMT "\n", + DRM_MODE_ARG(panel_fixed_mode)); + + panel->vbt.lfp_lvds_vbt_mode = panel_fixed_mode; +} + +static void +parse_lfp_backlight(struct drm_i915_private *i915, + struct intel_panel *panel) +{ + const struct bdb_lfp_backlight_data *backlight_data; + const struct lfp_backlight_data_entry *entry; + int panel_type = panel->vbt.panel_type; + u16 level; + + backlight_data = find_section(i915, BDB_LVDS_BACKLIGHT); + if (!backlight_data) + return; + + if (backlight_data->entry_size != sizeof(backlight_data->data[0])) { + drm_dbg_kms(&i915->drm, + "Unsupported backlight data entry size %u\n", + backlight_data->entry_size); + return; + } + + entry = &backlight_data->data[panel_type]; + + panel->vbt.backlight.present = entry->type == BDB_BACKLIGHT_TYPE_PWM; + if (!panel->vbt.backlight.present) { + drm_dbg_kms(&i915->drm, + "PWM backlight not present in VBT (type %u)\n", + entry->type); + return; + } + + panel->vbt.backlight.type = INTEL_BACKLIGHT_DISPLAY_DDI; + if (i915->display.vbt.version >= 191) { + size_t exp_size; + + if (i915->display.vbt.version >= 236) + exp_size = sizeof(struct bdb_lfp_backlight_data); + else if (i915->display.vbt.version >= 234) + exp_size = EXP_BDB_LFP_BL_DATA_SIZE_REV_234; + else + exp_size = EXP_BDB_LFP_BL_DATA_SIZE_REV_191; + + if (get_blocksize(backlight_data) >= exp_size) { + const struct lfp_backlight_control_method *method; + + method = &backlight_data->backlight_control[panel_type]; + panel->vbt.backlight.type = method->type; + panel->vbt.backlight.controller = method->controller; + } + } + + panel->vbt.backlight.pwm_freq_hz = entry->pwm_freq_hz; + panel->vbt.backlight.active_low_pwm = entry->active_low_pwm; + + if (i915->display.vbt.version >= 234) { + u16 min_level; + bool scale; + + level = backlight_data->brightness_level[panel_type].level; + min_level = backlight_data->brightness_min_level[panel_type].level; + + if (i915->display.vbt.version >= 236) + scale = backlight_data->brightness_precision_bits[panel_type] == 16; + else + scale = level > 255; + + if (scale) + min_level = min_level / 255; + + if (min_level > 255) { + drm_warn(&i915->drm, "Brightness min level > 255\n"); + level = 255; + } + panel->vbt.backlight.min_brightness = min_level; + + panel->vbt.backlight.brightness_precision_bits = + backlight_data->brightness_precision_bits[panel_type]; + } else { + level = backlight_data->level[panel_type]; + panel->vbt.backlight.min_brightness = entry->min_brightness; + } + + drm_dbg_kms(&i915->drm, + "VBT backlight PWM modulation frequency %u Hz, " + "active %s, min brightness %u, level %u, controller %u\n", + panel->vbt.backlight.pwm_freq_hz, + panel->vbt.backlight.active_low_pwm ? "low" : "high", + panel->vbt.backlight.min_brightness, + level, + panel->vbt.backlight.controller); +} + +/* Try to find sdvo panel data */ +static void +parse_sdvo_panel_data(struct drm_i915_private *i915, + struct intel_panel *panel) +{ + const struct bdb_sdvo_panel_dtds *dtds; + struct drm_display_mode *panel_fixed_mode; + int index; + + index = i915->params.vbt_sdvo_panel_type; + if (index == -2) { + drm_dbg_kms(&i915->drm, + "Ignore SDVO panel mode from BIOS VBT tables.\n"); + return; + } + + if (index == -1) { + const struct bdb_sdvo_lvds_options *sdvo_lvds_options; + + sdvo_lvds_options = find_section(i915, BDB_SDVO_LVDS_OPTIONS); + if (!sdvo_lvds_options) + return; + + index = sdvo_lvds_options->panel_type; + } + + dtds = find_section(i915, BDB_SDVO_PANEL_DTDS); + if (!dtds) + return; + + panel_fixed_mode = kzalloc(sizeof(*panel_fixed_mode), GFP_KERNEL); + if (!panel_fixed_mode) + return; + + fill_detail_timing_data(panel_fixed_mode, &dtds->dtds[index]); + + panel->vbt.sdvo_lvds_vbt_mode = panel_fixed_mode; + + drm_dbg_kms(&i915->drm, + "Found SDVO panel mode in BIOS VBT tables: " DRM_MODE_FMT "\n", + DRM_MODE_ARG(panel_fixed_mode)); +} + +static int intel_bios_ssc_frequency(struct drm_i915_private *i915, + bool alternate) +{ + switch (DISPLAY_VER(i915)) { + case 2: + return alternate ? 66667 : 48000; + case 3: + case 4: + return alternate ? 100000 : 96000; + default: + return alternate ? 100000 : 120000; + } +} + +static void +parse_general_features(struct drm_i915_private *i915) +{ + const struct bdb_general_features *general; + + general = find_section(i915, BDB_GENERAL_FEATURES); + if (!general) + return; + + i915->display.vbt.int_tv_support = general->int_tv_support; + /* int_crt_support can't be trusted on earlier platforms */ + if (i915->display.vbt.version >= 155 && + (HAS_DDI(i915) || IS_VALLEYVIEW(i915))) + i915->display.vbt.int_crt_support = general->int_crt_support; + i915->display.vbt.lvds_use_ssc = general->enable_ssc; + i915->display.vbt.lvds_ssc_freq = + intel_bios_ssc_frequency(i915, general->ssc_freq); + i915->display.vbt.display_clock_mode = general->display_clock_mode; + i915->display.vbt.fdi_rx_polarity_inverted = general->fdi_rx_polarity_inverted; + if (i915->display.vbt.version >= 181) { + i915->display.vbt.orientation = general->rotate_180 ? + DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP : + DRM_MODE_PANEL_ORIENTATION_NORMAL; + } else { + i915->display.vbt.orientation = DRM_MODE_PANEL_ORIENTATION_UNKNOWN; + } + + if (i915->display.vbt.version >= 249 && general->afc_startup_config) { + i915->display.vbt.override_afc_startup = true; + i915->display.vbt.override_afc_startup_val = general->afc_startup_config == 0x1 ? 0x0 : 0x7; + } + + drm_dbg_kms(&i915->drm, + "BDB_GENERAL_FEATURES int_tv_support %d int_crt_support %d lvds_use_ssc %d lvds_ssc_freq %d display_clock_mode %d fdi_rx_polarity_inverted %d\n", + i915->display.vbt.int_tv_support, + i915->display.vbt.int_crt_support, + i915->display.vbt.lvds_use_ssc, + i915->display.vbt.lvds_ssc_freq, + i915->display.vbt.display_clock_mode, + i915->display.vbt.fdi_rx_polarity_inverted); +} + +static const struct child_device_config * +child_device_ptr(const struct bdb_general_definitions *defs, int i) +{ + return (const void *) &defs->devices[i * defs->child_dev_size]; +} + +static void +parse_sdvo_device_mapping(struct drm_i915_private *i915) +{ + struct sdvo_device_mapping *mapping; + const struct intel_bios_encoder_data *devdata; + const struct child_device_config *child; + int count = 0; + + /* + * Only parse SDVO mappings on gens that could have SDVO. This isn't + * accurate and doesn't have to be, as long as it's not too strict. + */ + if (!IS_DISPLAY_VER(i915, 3, 7)) { + drm_dbg_kms(&i915->drm, "Skipping SDVO device mapping\n"); + return; + } + + list_for_each_entry(devdata, &i915->display.vbt.display_devices, node) { + child = &devdata->child; + + if (child->slave_addr != SLAVE_ADDR1 && + child->slave_addr != SLAVE_ADDR2) { + /* + * If the slave address is neither 0x70 nor 0x72, + * it is not a SDVO device. Skip it. + */ + continue; + } + if (child->dvo_port != DEVICE_PORT_DVOB && + child->dvo_port != DEVICE_PORT_DVOC) { + /* skip the incorrect SDVO port */ + drm_dbg_kms(&i915->drm, + "Incorrect SDVO port. Skip it\n"); + continue; + } + drm_dbg_kms(&i915->drm, + "the SDVO device with slave addr %2x is found on" + " %s port\n", + child->slave_addr, + (child->dvo_port == DEVICE_PORT_DVOB) ? + "SDVOB" : "SDVOC"); + mapping = &i915->display.vbt.sdvo_mappings[child->dvo_port - 1]; + if (!mapping->initialized) { + mapping->dvo_port = child->dvo_port; + mapping->slave_addr = child->slave_addr; + mapping->dvo_wiring = child->dvo_wiring; + mapping->ddc_pin = child->ddc_pin; + mapping->i2c_pin = child->i2c_pin; + mapping->initialized = 1; + drm_dbg_kms(&i915->drm, + "SDVO device: dvo=%x, addr=%x, wiring=%d, ddc_pin=%d, i2c_pin=%d\n", + mapping->dvo_port, mapping->slave_addr, + mapping->dvo_wiring, mapping->ddc_pin, + mapping->i2c_pin); + } else { + drm_dbg_kms(&i915->drm, + "Maybe one SDVO port is shared by " + "two SDVO device.\n"); + } + if (child->slave2_addr) { + /* Maybe this is a SDVO device with multiple inputs */ + /* And the mapping info is not added */ + drm_dbg_kms(&i915->drm, + "there exists the slave2_addr. Maybe this" + " is a SDVO device with multiple inputs.\n"); + } + count++; + } + + if (!count) { + /* No SDVO device info is found */ + drm_dbg_kms(&i915->drm, + "No SDVO device info is found in VBT\n"); + } +} + +static void +parse_driver_features(struct drm_i915_private *i915) +{ + const struct bdb_driver_features *driver; + + driver = find_section(i915, BDB_DRIVER_FEATURES); + if (!driver) + return; + + if (DISPLAY_VER(i915) >= 5) { + /* + * Note that we consider BDB_DRIVER_FEATURE_INT_SDVO_LVDS + * to mean "eDP". The VBT spec doesn't agree with that + * interpretation, but real world VBTs seem to. + */ + if (driver->lvds_config != BDB_DRIVER_FEATURE_INT_LVDS) + i915->display.vbt.int_lvds_support = 0; + } else { + /* + * FIXME it's not clear which BDB version has the LVDS config + * bits defined. Revision history in the VBT spec says: + * "0.92 | Add two definitions for VBT value of LVDS Active + * Config (00b and 11b values defined) | 06/13/2005" + * but does not the specify the BDB version. + * + * So far version 134 (on i945gm) is the oldest VBT observed + * in the wild with the bits correctly populated. Version + * 108 (on i85x) does not have the bits correctly populated. + */ + if (i915->display.vbt.version >= 134 && + driver->lvds_config != BDB_DRIVER_FEATURE_INT_LVDS && + driver->lvds_config != BDB_DRIVER_FEATURE_INT_SDVO_LVDS) + i915->display.vbt.int_lvds_support = 0; + } +} + +static void +parse_panel_driver_features(struct drm_i915_private *i915, + struct intel_panel *panel) +{ + const struct bdb_driver_features *driver; + + driver = find_section(i915, BDB_DRIVER_FEATURES); + if (!driver) + return; + + if (i915->display.vbt.version < 228) { + drm_dbg_kms(&i915->drm, "DRRS State Enabled:%d\n", + driver->drrs_enabled); + /* + * If DRRS is not supported, drrs_type has to be set to 0. + * This is because, VBT is configured in such a way that + * static DRRS is 0 and DRRS not supported is represented by + * driver->drrs_enabled=false + */ + if (!driver->drrs_enabled && panel->vbt.drrs_type != DRRS_TYPE_NONE) { + /* + * FIXME Should DMRRS perhaps be treated as seamless + * but without the automatic downclocking? + */ + if (driver->dmrrs_enabled) + panel->vbt.drrs_type = DRRS_TYPE_STATIC; + else + panel->vbt.drrs_type = DRRS_TYPE_NONE; + } + + panel->vbt.psr.enable = driver->psr_enabled; + } +} + +static void +parse_power_conservation_features(struct drm_i915_private *i915, + struct intel_panel *panel) +{ + const struct bdb_lfp_power *power; + u8 panel_type = panel->vbt.panel_type; + + panel->vbt.vrr = true; /* matches Windows behaviour */ + + if (i915->display.vbt.version < 228) + return; + + power = find_section(i915, BDB_LFP_POWER); + if (!power) + return; + + panel->vbt.psr.enable = panel_bool(power->psr, panel_type); + + /* + * If DRRS is not supported, drrs_type has to be set to 0. + * This is because, VBT is configured in such a way that + * static DRRS is 0 and DRRS not supported is represented by + * power->drrs & BIT(panel_type)=false + */ + if (!panel_bool(power->drrs, panel_type) && panel->vbt.drrs_type != DRRS_TYPE_NONE) { + /* + * FIXME Should DMRRS perhaps be treated as seamless + * but without the automatic downclocking? + */ + if (panel_bool(power->dmrrs, panel_type)) + panel->vbt.drrs_type = DRRS_TYPE_STATIC; + else + panel->vbt.drrs_type = DRRS_TYPE_NONE; + } + + if (i915->display.vbt.version >= 232) + panel->vbt.edp.hobl = panel_bool(power->hobl, panel_type); + + if (i915->display.vbt.version >= 233) + panel->vbt.vrr = panel_bool(power->vrr_feature_enabled, + panel_type); +} + +static void +parse_edp(struct drm_i915_private *i915, + struct intel_panel *panel) +{ + const struct bdb_edp *edp; + const struct edp_power_seq *edp_pps; + const struct edp_fast_link_params *edp_link_params; + int panel_type = panel->vbt.panel_type; + + edp = find_section(i915, BDB_EDP); + if (!edp) + return; + + switch (panel_bits(edp->color_depth, panel_type, 2)) { + case EDP_18BPP: + panel->vbt.edp.bpp = 18; + break; + case EDP_24BPP: + panel->vbt.edp.bpp = 24; + break; + case EDP_30BPP: + panel->vbt.edp.bpp = 30; + break; + } + + /* Get the eDP sequencing and link info */ + edp_pps = &edp->power_seqs[panel_type]; + edp_link_params = &edp->fast_link_params[panel_type]; + + panel->vbt.edp.pps = *edp_pps; + + if (i915->display.vbt.version >= 224) { + panel->vbt.edp.rate = + edp->edp_fast_link_training_rate[panel_type] * 20; + } else { + switch (edp_link_params->rate) { + case EDP_RATE_1_62: + panel->vbt.edp.rate = 162000; + break; + case EDP_RATE_2_7: + panel->vbt.edp.rate = 270000; + break; + case EDP_RATE_5_4: + panel->vbt.edp.rate = 540000; + break; + default: + drm_dbg_kms(&i915->drm, + "VBT has unknown eDP link rate value %u\n", + edp_link_params->rate); + break; + } + } + + switch (edp_link_params->lanes) { + case EDP_LANE_1: + panel->vbt.edp.lanes = 1; + break; + case EDP_LANE_2: + panel->vbt.edp.lanes = 2; + break; + case EDP_LANE_4: + panel->vbt.edp.lanes = 4; + break; + default: + drm_dbg_kms(&i915->drm, + "VBT has unknown eDP lane count value %u\n", + edp_link_params->lanes); + break; + } + + switch (edp_link_params->preemphasis) { + case EDP_PREEMPHASIS_NONE: + panel->vbt.edp.preemphasis = DP_TRAIN_PRE_EMPH_LEVEL_0; + break; + case EDP_PREEMPHASIS_3_5dB: + panel->vbt.edp.preemphasis = DP_TRAIN_PRE_EMPH_LEVEL_1; + break; + case EDP_PREEMPHASIS_6dB: + panel->vbt.edp.preemphasis = DP_TRAIN_PRE_EMPH_LEVEL_2; + break; + case EDP_PREEMPHASIS_9_5dB: + panel->vbt.edp.preemphasis = DP_TRAIN_PRE_EMPH_LEVEL_3; + break; + default: + drm_dbg_kms(&i915->drm, + "VBT has unknown eDP pre-emphasis value %u\n", + edp_link_params->preemphasis); + break; + } + + switch (edp_link_params->vswing) { + case EDP_VSWING_0_4V: + panel->vbt.edp.vswing = DP_TRAIN_VOLTAGE_SWING_LEVEL_0; + break; + case EDP_VSWING_0_6V: + panel->vbt.edp.vswing = DP_TRAIN_VOLTAGE_SWING_LEVEL_1; + break; + case EDP_VSWING_0_8V: + panel->vbt.edp.vswing = DP_TRAIN_VOLTAGE_SWING_LEVEL_2; + break; + case EDP_VSWING_1_2V: + panel->vbt.edp.vswing = DP_TRAIN_VOLTAGE_SWING_LEVEL_3; + break; + default: + drm_dbg_kms(&i915->drm, + "VBT has unknown eDP voltage swing value %u\n", + edp_link_params->vswing); + break; + } + + if (i915->display.vbt.version >= 173) { + u8 vswing; + + /* Don't read from VBT if module parameter has valid value*/ + if (i915->params.edp_vswing) { + panel->vbt.edp.low_vswing = + i915->params.edp_vswing == 1; + } else { + vswing = (edp->edp_vswing_preemph >> (panel_type * 4)) & 0xF; + panel->vbt.edp.low_vswing = vswing == 0; + } + } + + panel->vbt.edp.drrs_msa_timing_delay = + panel_bits(edp->sdrrs_msa_timing_delay, panel_type, 2); + + if (i915->display.vbt.version >= 244) + panel->vbt.edp.max_link_rate = + edp->edp_max_port_link_rate[panel_type] * 20; +} + +static void +parse_psr(struct drm_i915_private *i915, + struct intel_panel *panel) +{ + const struct bdb_psr *psr; + const struct psr_table *psr_table; + int panel_type = panel->vbt.panel_type; + + psr = find_section(i915, BDB_PSR); + if (!psr) { + drm_dbg_kms(&i915->drm, "No PSR BDB found.\n"); + return; + } + + psr_table = &psr->psr_table[panel_type]; + + panel->vbt.psr.full_link = psr_table->full_link; + panel->vbt.psr.require_aux_wakeup = psr_table->require_aux_to_wakeup; + + /* Allowed VBT values goes from 0 to 15 */ + panel->vbt.psr.idle_frames = psr_table->idle_frames < 0 ? 0 : + psr_table->idle_frames > 15 ? 15 : psr_table->idle_frames; + + /* + * New psr options 0=500us, 1=100us, 2=2500us, 3=0us + * Old decimal value is wake up time in multiples of 100 us. + */ + if (i915->display.vbt.version >= 205 && + (DISPLAY_VER(i915) >= 9 && !IS_BROXTON(i915))) { + switch (psr_table->tp1_wakeup_time) { + case 0: + panel->vbt.psr.tp1_wakeup_time_us = 500; + break; + case 1: + panel->vbt.psr.tp1_wakeup_time_us = 100; + break; + case 3: + panel->vbt.psr.tp1_wakeup_time_us = 0; + break; + default: + drm_dbg_kms(&i915->drm, + "VBT tp1 wakeup time value %d is outside range[0-3], defaulting to max value 2500us\n", + psr_table->tp1_wakeup_time); + fallthrough; + case 2: + panel->vbt.psr.tp1_wakeup_time_us = 2500; + break; + } + + switch (psr_table->tp2_tp3_wakeup_time) { + case 0: + panel->vbt.psr.tp2_tp3_wakeup_time_us = 500; + break; + case 1: + panel->vbt.psr.tp2_tp3_wakeup_time_us = 100; + break; + case 3: + panel->vbt.psr.tp2_tp3_wakeup_time_us = 0; + break; + default: + drm_dbg_kms(&i915->drm, + "VBT tp2_tp3 wakeup time value %d is outside range[0-3], defaulting to max value 2500us\n", + psr_table->tp2_tp3_wakeup_time); + fallthrough; + case 2: + panel->vbt.psr.tp2_tp3_wakeup_time_us = 2500; + break; + } + } else { + panel->vbt.psr.tp1_wakeup_time_us = psr_table->tp1_wakeup_time * 100; + panel->vbt.psr.tp2_tp3_wakeup_time_us = psr_table->tp2_tp3_wakeup_time * 100; + } + + if (i915->display.vbt.version >= 226) { + u32 wakeup_time = psr->psr2_tp2_tp3_wakeup_time; + + wakeup_time = panel_bits(wakeup_time, panel_type, 2); + switch (wakeup_time) { + case 0: + wakeup_time = 500; + break; + case 1: + wakeup_time = 100; + break; + case 3: + wakeup_time = 50; + break; + default: + case 2: + wakeup_time = 2500; + break; + } + panel->vbt.psr.psr2_tp2_tp3_wakeup_time_us = wakeup_time; + } else { + /* Reusing PSR1 wakeup time for PSR2 in older VBTs */ + panel->vbt.psr.psr2_tp2_tp3_wakeup_time_us = panel->vbt.psr.tp2_tp3_wakeup_time_us; + } +} + +static void parse_dsi_backlight_ports(struct drm_i915_private *i915, + struct intel_panel *panel, + enum port port) +{ + enum port port_bc = DISPLAY_VER(i915) >= 11 ? PORT_B : PORT_C; + + if (!panel->vbt.dsi.config->dual_link || i915->display.vbt.version < 197) { + panel->vbt.dsi.bl_ports = BIT(port); + if (panel->vbt.dsi.config->cabc_supported) + panel->vbt.dsi.cabc_ports = BIT(port); + + return; + } + + switch (panel->vbt.dsi.config->dl_dcs_backlight_ports) { + case DL_DCS_PORT_A: + panel->vbt.dsi.bl_ports = BIT(PORT_A); + break; + case DL_DCS_PORT_C: + panel->vbt.dsi.bl_ports = BIT(port_bc); + break; + default: + case DL_DCS_PORT_A_AND_C: + panel->vbt.dsi.bl_ports = BIT(PORT_A) | BIT(port_bc); + break; + } + + if (!panel->vbt.dsi.config->cabc_supported) + return; + + switch (panel->vbt.dsi.config->dl_dcs_cabc_ports) { + case DL_DCS_PORT_A: + panel->vbt.dsi.cabc_ports = BIT(PORT_A); + break; + case DL_DCS_PORT_C: + panel->vbt.dsi.cabc_ports = BIT(port_bc); + break; + default: + case DL_DCS_PORT_A_AND_C: + panel->vbt.dsi.cabc_ports = + BIT(PORT_A) | BIT(port_bc); + break; + } +} + +static void +parse_mipi_config(struct drm_i915_private *i915, + struct intel_panel *panel) +{ + const struct bdb_mipi_config *start; + const struct mipi_config *config; + const struct mipi_pps_data *pps; + int panel_type = panel->vbt.panel_type; + enum port port; + + /* parse MIPI blocks only if LFP type is MIPI */ + if (!intel_bios_is_dsi_present(i915, &port)) + return; + + /* Initialize this to undefined indicating no generic MIPI support */ + panel->vbt.dsi.panel_id = MIPI_DSI_UNDEFINED_PANEL_ID; + + /* Block #40 is already parsed and panel_fixed_mode is + * stored in i915->lfp_lvds_vbt_mode + * resuse this when needed + */ + + /* Parse #52 for panel index used from panel_type already + * parsed + */ + start = find_section(i915, BDB_MIPI_CONFIG); + if (!start) { + drm_dbg_kms(&i915->drm, "No MIPI config BDB found"); + return; + } + + drm_dbg(&i915->drm, "Found MIPI Config block, panel index = %d\n", + panel_type); + + /* + * get hold of the correct configuration block and pps data as per + * the panel_type as index + */ + config = &start->config[panel_type]; + pps = &start->pps[panel_type]; + + /* store as of now full data. Trim when we realise all is not needed */ + panel->vbt.dsi.config = kmemdup(config, sizeof(struct mipi_config), GFP_KERNEL); + if (!panel->vbt.dsi.config) + return; + + panel->vbt.dsi.pps = kmemdup(pps, sizeof(struct mipi_pps_data), GFP_KERNEL); + if (!panel->vbt.dsi.pps) { + kfree(panel->vbt.dsi.config); + return; + } + + parse_dsi_backlight_ports(i915, panel, port); + + /* FIXME is the 90 vs. 270 correct? */ + switch (config->rotation) { + case ENABLE_ROTATION_0: + /* + * Most (all?) VBTs claim 0 degrees despite having + * an upside down panel, thus we do not trust this. + */ + panel->vbt.dsi.orientation = + DRM_MODE_PANEL_ORIENTATION_UNKNOWN; + break; + case ENABLE_ROTATION_90: + panel->vbt.dsi.orientation = + DRM_MODE_PANEL_ORIENTATION_RIGHT_UP; + break; + case ENABLE_ROTATION_180: + panel->vbt.dsi.orientation = + DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP; + break; + case ENABLE_ROTATION_270: + panel->vbt.dsi.orientation = + DRM_MODE_PANEL_ORIENTATION_LEFT_UP; + break; + } + + /* We have mandatory mipi config blocks. Initialize as generic panel */ + panel->vbt.dsi.panel_id = MIPI_DSI_GENERIC_PANEL_ID; +} + +/* Find the sequence block and size for the given panel. */ +static const u8 * +find_panel_sequence_block(const struct bdb_mipi_sequence *sequence, + u16 panel_id, u32 *seq_size) +{ + u32 total = get_blocksize(sequence); + const u8 *data = &sequence->data[0]; + u8 current_id; + u32 current_size; + int header_size = sequence->version >= 3 ? 5 : 3; + int index = 0; + int i; + + /* skip new block size */ + if (sequence->version >= 3) + data += 4; + + for (i = 0; i < MAX_MIPI_CONFIGURATIONS && index < total; i++) { + if (index + header_size > total) { + DRM_ERROR("Invalid sequence block (header)\n"); + return NULL; + } + + current_id = *(data + index); + if (sequence->version >= 3) + current_size = *((const u32 *)(data + index + 1)); + else + current_size = *((const u16 *)(data + index + 1)); + + index += header_size; + + if (index + current_size > total) { + DRM_ERROR("Invalid sequence block\n"); + return NULL; + } + + if (current_id == panel_id) { + *seq_size = current_size; + return data + index; + } + + index += current_size; + } + + DRM_ERROR("Sequence block detected but no valid configuration\n"); + + return NULL; +} + +static int goto_next_sequence(const u8 *data, int index, int total) +{ + u16 len; + + /* Skip Sequence Byte. */ + for (index = index + 1; index < total; index += len) { + u8 operation_byte = *(data + index); + index++; + + switch (operation_byte) { + case MIPI_SEQ_ELEM_END: + return index; + case MIPI_SEQ_ELEM_SEND_PKT: + if (index + 4 > total) + return 0; + + len = *((const u16 *)(data + index + 2)) + 4; + break; + case MIPI_SEQ_ELEM_DELAY: + len = 4; + break; + case MIPI_SEQ_ELEM_GPIO: + len = 2; + break; + case MIPI_SEQ_ELEM_I2C: + if (index + 7 > total) + return 0; + len = *(data + index + 6) + 7; + break; + default: + DRM_ERROR("Unknown operation byte\n"); + return 0; + } + } + + return 0; +} + +static int goto_next_sequence_v3(const u8 *data, int index, int total) +{ + int seq_end; + u16 len; + u32 size_of_sequence; + + /* + * Could skip sequence based on Size of Sequence alone, but also do some + * checking on the structure. + */ + if (total < 5) { + DRM_ERROR("Too small sequence size\n"); + return 0; + } + + /* Skip Sequence Byte. */ + index++; + + /* + * Size of Sequence. Excludes the Sequence Byte and the size itself, + * includes MIPI_SEQ_ELEM_END byte, excludes the final MIPI_SEQ_END + * byte. + */ + size_of_sequence = *((const u32 *)(data + index)); + index += 4; + + seq_end = index + size_of_sequence; + if (seq_end > total) { + DRM_ERROR("Invalid sequence size\n"); + return 0; + } + + for (; index < total; index += len) { + u8 operation_byte = *(data + index); + index++; + + if (operation_byte == MIPI_SEQ_ELEM_END) { + if (index != seq_end) { + DRM_ERROR("Invalid element structure\n"); + return 0; + } + return index; + } + + len = *(data + index); + index++; + + /* + * FIXME: Would be nice to check elements like for v1/v2 in + * goto_next_sequence() above. + */ + switch (operation_byte) { + case MIPI_SEQ_ELEM_SEND_PKT: + case MIPI_SEQ_ELEM_DELAY: + case MIPI_SEQ_ELEM_GPIO: + case MIPI_SEQ_ELEM_I2C: + case MIPI_SEQ_ELEM_SPI: + case MIPI_SEQ_ELEM_PMIC: + break; + default: + DRM_ERROR("Unknown operation byte %u\n", + operation_byte); + break; + } + } + + return 0; +} + +/* + * Get len of pre-fixed deassert fragment from a v1 init OTP sequence, + * skip all delay + gpio operands and stop at the first DSI packet op. + */ +static int get_init_otp_deassert_fragment_len(struct drm_i915_private *i915, + struct intel_panel *panel) +{ + const u8 *data = panel->vbt.dsi.sequence[MIPI_SEQ_INIT_OTP]; + int index, len; + + if (drm_WARN_ON(&i915->drm, + !data || panel->vbt.dsi.seq_version != 1)) + return 0; + + /* index = 1 to skip sequence byte */ + for (index = 1; data[index] != MIPI_SEQ_ELEM_END; index += len) { + switch (data[index]) { + case MIPI_SEQ_ELEM_SEND_PKT: + return index == 1 ? 0 : index; + case MIPI_SEQ_ELEM_DELAY: + len = 5; /* 1 byte for operand + uint32 */ + break; + case MIPI_SEQ_ELEM_GPIO: + len = 3; /* 1 byte for op, 1 for gpio_nr, 1 for value */ + break; + default: + return 0; + } + } + + return 0; +} + +/* + * Some v1 VBT MIPI sequences do the deassert in the init OTP sequence. + * The deassert must be done before calling intel_dsi_device_ready, so for + * these devices we split the init OTP sequence into a deassert sequence and + * the actual init OTP part. + */ +static void fixup_mipi_sequences(struct drm_i915_private *i915, + struct intel_panel *panel) +{ + u8 *init_otp; + int len; + + /* Limit this to VLV for now. */ + if (!IS_VALLEYVIEW(i915)) + return; + + /* Limit this to v1 vid-mode sequences */ + if (panel->vbt.dsi.config->is_cmd_mode || + panel->vbt.dsi.seq_version != 1) + return; + + /* Only do this if there are otp and assert seqs and no deassert seq */ + if (!panel->vbt.dsi.sequence[MIPI_SEQ_INIT_OTP] || + !panel->vbt.dsi.sequence[MIPI_SEQ_ASSERT_RESET] || + panel->vbt.dsi.sequence[MIPI_SEQ_DEASSERT_RESET]) + return; + + /* The deassert-sequence ends at the first DSI packet */ + len = get_init_otp_deassert_fragment_len(i915, panel); + if (!len) + return; + + drm_dbg_kms(&i915->drm, + "Using init OTP fragment to deassert reset\n"); + + /* Copy the fragment, update seq byte and terminate it */ + init_otp = (u8 *)panel->vbt.dsi.sequence[MIPI_SEQ_INIT_OTP]; + panel->vbt.dsi.deassert_seq = kmemdup(init_otp, len + 1, GFP_KERNEL); + if (!panel->vbt.dsi.deassert_seq) + return; + panel->vbt.dsi.deassert_seq[0] = MIPI_SEQ_DEASSERT_RESET; + panel->vbt.dsi.deassert_seq[len] = MIPI_SEQ_ELEM_END; + /* Use the copy for deassert */ + panel->vbt.dsi.sequence[MIPI_SEQ_DEASSERT_RESET] = + panel->vbt.dsi.deassert_seq; + /* Replace the last byte of the fragment with init OTP seq byte */ + init_otp[len - 1] = MIPI_SEQ_INIT_OTP; + /* And make MIPI_MIPI_SEQ_INIT_OTP point to it */ + panel->vbt.dsi.sequence[MIPI_SEQ_INIT_OTP] = init_otp + len - 1; +} + +static void +parse_mipi_sequence(struct drm_i915_private *i915, + struct intel_panel *panel) +{ + int panel_type = panel->vbt.panel_type; + const struct bdb_mipi_sequence *sequence; + const u8 *seq_data; + u32 seq_size; + u8 *data; + int index = 0; + + /* Only our generic panel driver uses the sequence block. */ + if (panel->vbt.dsi.panel_id != MIPI_DSI_GENERIC_PANEL_ID) + return; + + sequence = find_section(i915, BDB_MIPI_SEQUENCE); + if (!sequence) { + drm_dbg_kms(&i915->drm, + "No MIPI Sequence found, parsing complete\n"); + return; + } + + /* Fail gracefully for forward incompatible sequence block. */ + if (sequence->version >= 4) { + drm_err(&i915->drm, + "Unable to parse MIPI Sequence Block v%u\n", + sequence->version); + return; + } + + drm_dbg(&i915->drm, "Found MIPI sequence block v%u\n", + sequence->version); + + seq_data = find_panel_sequence_block(sequence, panel_type, &seq_size); + if (!seq_data) + return; + + data = kmemdup(seq_data, seq_size, GFP_KERNEL); + if (!data) + return; + + /* Parse the sequences, store pointers to each sequence. */ + for (;;) { + u8 seq_id = *(data + index); + if (seq_id == MIPI_SEQ_END) + break; + + if (seq_id >= MIPI_SEQ_MAX) { + drm_err(&i915->drm, "Unknown sequence %u\n", + seq_id); + goto err; + } + + /* Log about presence of sequences we won't run. */ + if (seq_id == MIPI_SEQ_TEAR_ON || seq_id == MIPI_SEQ_TEAR_OFF) + drm_dbg_kms(&i915->drm, + "Unsupported sequence %u\n", seq_id); + + panel->vbt.dsi.sequence[seq_id] = data + index; + + if (sequence->version >= 3) + index = goto_next_sequence_v3(data, index, seq_size); + else + index = goto_next_sequence(data, index, seq_size); + if (!index) { + drm_err(&i915->drm, "Invalid sequence %u\n", + seq_id); + goto err; + } + } + + panel->vbt.dsi.data = data; + panel->vbt.dsi.size = seq_size; + panel->vbt.dsi.seq_version = sequence->version; + + fixup_mipi_sequences(i915, panel); + + drm_dbg(&i915->drm, "MIPI related VBT parsing complete\n"); + return; + +err: + kfree(data); + memset(panel->vbt.dsi.sequence, 0, sizeof(panel->vbt.dsi.sequence)); +} + +static void +parse_compression_parameters(struct drm_i915_private *i915) +{ + const struct bdb_compression_parameters *params; + struct intel_bios_encoder_data *devdata; + const struct child_device_config *child; + u16 block_size; + int index; + + if (i915->display.vbt.version < 198) + return; + + params = find_section(i915, BDB_COMPRESSION_PARAMETERS); + if (params) { + /* Sanity checks */ + if (params->entry_size != sizeof(params->data[0])) { + drm_dbg_kms(&i915->drm, + "VBT: unsupported compression param entry size\n"); + return; + } + + block_size = get_blocksize(params); + if (block_size < sizeof(*params)) { + drm_dbg_kms(&i915->drm, + "VBT: expected 16 compression param entries\n"); + return; + } + } + + list_for_each_entry(devdata, &i915->display.vbt.display_devices, node) { + child = &devdata->child; + + if (!child->compression_enable) + continue; + + if (!params) { + drm_dbg_kms(&i915->drm, + "VBT: compression params not available\n"); + continue; + } + + if (child->compression_method_cps) { + drm_dbg_kms(&i915->drm, + "VBT: CPS compression not supported\n"); + continue; + } + + index = child->compression_structure_index; + + devdata->dsc = kmemdup(¶ms->data[index], + sizeof(*devdata->dsc), GFP_KERNEL); + } +} + +static u8 translate_iboost(u8 val) +{ + static const u8 mapping[] = { 1, 3, 7 }; /* See VBT spec */ + + if (val >= ARRAY_SIZE(mapping)) { + DRM_DEBUG_KMS("Unsupported I_boost value found in VBT (%d), display may not work properly\n", val); + return 0; + } + return mapping[val]; +} + +static const u8 cnp_ddc_pin_map[] = { + [0] = 0, /* N/A */ + [DDC_BUS_DDI_B] = GMBUS_PIN_1_BXT, + [DDC_BUS_DDI_C] = GMBUS_PIN_2_BXT, + [DDC_BUS_DDI_D] = GMBUS_PIN_4_CNP, /* sic */ + [DDC_BUS_DDI_F] = GMBUS_PIN_3_BXT, /* sic */ +}; + +static const u8 icp_ddc_pin_map[] = { + [ICL_DDC_BUS_DDI_A] = GMBUS_PIN_1_BXT, + [ICL_DDC_BUS_DDI_B] = GMBUS_PIN_2_BXT, + [TGL_DDC_BUS_DDI_C] = GMBUS_PIN_3_BXT, + [ICL_DDC_BUS_PORT_1] = GMBUS_PIN_9_TC1_ICP, + [ICL_DDC_BUS_PORT_2] = GMBUS_PIN_10_TC2_ICP, + [ICL_DDC_BUS_PORT_3] = GMBUS_PIN_11_TC3_ICP, + [ICL_DDC_BUS_PORT_4] = GMBUS_PIN_12_TC4_ICP, + [TGL_DDC_BUS_PORT_5] = GMBUS_PIN_13_TC5_TGP, + [TGL_DDC_BUS_PORT_6] = GMBUS_PIN_14_TC6_TGP, +}; + +static const u8 rkl_pch_tgp_ddc_pin_map[] = { + [ICL_DDC_BUS_DDI_A] = GMBUS_PIN_1_BXT, + [ICL_DDC_BUS_DDI_B] = GMBUS_PIN_2_BXT, + [RKL_DDC_BUS_DDI_D] = GMBUS_PIN_9_TC1_ICP, + [RKL_DDC_BUS_DDI_E] = GMBUS_PIN_10_TC2_ICP, +}; + +static const u8 adls_ddc_pin_map[] = { + [ICL_DDC_BUS_DDI_A] = GMBUS_PIN_1_BXT, + [ADLS_DDC_BUS_PORT_TC1] = GMBUS_PIN_9_TC1_ICP, + [ADLS_DDC_BUS_PORT_TC2] = GMBUS_PIN_10_TC2_ICP, + [ADLS_DDC_BUS_PORT_TC3] = GMBUS_PIN_11_TC3_ICP, + [ADLS_DDC_BUS_PORT_TC4] = GMBUS_PIN_12_TC4_ICP, +}; + +static const u8 gen9bc_tgp_ddc_pin_map[] = { + [DDC_BUS_DDI_B] = GMBUS_PIN_2_BXT, + [DDC_BUS_DDI_C] = GMBUS_PIN_9_TC1_ICP, + [DDC_BUS_DDI_D] = GMBUS_PIN_10_TC2_ICP, +}; + +static const u8 adlp_ddc_pin_map[] = { + [ICL_DDC_BUS_DDI_A] = GMBUS_PIN_1_BXT, + [ICL_DDC_BUS_DDI_B] = GMBUS_PIN_2_BXT, + [ADLP_DDC_BUS_PORT_TC1] = GMBUS_PIN_9_TC1_ICP, + [ADLP_DDC_BUS_PORT_TC2] = GMBUS_PIN_10_TC2_ICP, + [ADLP_DDC_BUS_PORT_TC3] = GMBUS_PIN_11_TC3_ICP, + [ADLP_DDC_BUS_PORT_TC4] = GMBUS_PIN_12_TC4_ICP, +}; + +static u8 map_ddc_pin(struct drm_i915_private *i915, u8 vbt_pin) +{ + const u8 *ddc_pin_map; + int n_entries; + + if (IS_ALDERLAKE_P(i915)) { + ddc_pin_map = adlp_ddc_pin_map; + n_entries = ARRAY_SIZE(adlp_ddc_pin_map); + } else if (IS_ALDERLAKE_S(i915)) { + ddc_pin_map = adls_ddc_pin_map; + n_entries = ARRAY_SIZE(adls_ddc_pin_map); + } else if (INTEL_PCH_TYPE(i915) >= PCH_DG1) { + return vbt_pin; + } else if (IS_ROCKETLAKE(i915) && INTEL_PCH_TYPE(i915) == PCH_TGP) { + ddc_pin_map = rkl_pch_tgp_ddc_pin_map; + n_entries = ARRAY_SIZE(rkl_pch_tgp_ddc_pin_map); + } else if (HAS_PCH_TGP(i915) && DISPLAY_VER(i915) == 9) { + ddc_pin_map = gen9bc_tgp_ddc_pin_map; + n_entries = ARRAY_SIZE(gen9bc_tgp_ddc_pin_map); + } else if (INTEL_PCH_TYPE(i915) >= PCH_ICP) { + ddc_pin_map = icp_ddc_pin_map; + n_entries = ARRAY_SIZE(icp_ddc_pin_map); + } else if (HAS_PCH_CNP(i915)) { + ddc_pin_map = cnp_ddc_pin_map; + n_entries = ARRAY_SIZE(cnp_ddc_pin_map); + } else { + /* Assuming direct map */ + return vbt_pin; + } + + if (vbt_pin < n_entries && ddc_pin_map[vbt_pin] != 0) + return ddc_pin_map[vbt_pin]; + + drm_dbg_kms(&i915->drm, + "Ignoring alternate pin: VBT claims DDC pin %d, which is not valid for this platform\n", + vbt_pin); + return 0; +} + +static enum port get_port_by_ddc_pin(struct drm_i915_private *i915, u8 ddc_pin) +{ + const struct intel_bios_encoder_data *devdata; + enum port port; + + if (!ddc_pin) + return PORT_NONE; + + for_each_port(port) { + devdata = i915->display.vbt.ports[port]; + + if (devdata && ddc_pin == devdata->child.ddc_pin) + return port; + } + + return PORT_NONE; +} + +static void sanitize_ddc_pin(struct intel_bios_encoder_data *devdata, + enum port port) +{ + struct drm_i915_private *i915 = devdata->i915; + struct child_device_config *child; + u8 mapped_ddc_pin; + enum port p; + + if (!devdata->child.ddc_pin) + return; + + mapped_ddc_pin = map_ddc_pin(i915, devdata->child.ddc_pin); + if (!intel_gmbus_is_valid_pin(i915, mapped_ddc_pin)) { + drm_dbg_kms(&i915->drm, + "Port %c has invalid DDC pin %d, " + "sticking to defaults\n", + port_name(port), mapped_ddc_pin); + devdata->child.ddc_pin = 0; + return; + } + + p = get_port_by_ddc_pin(i915, devdata->child.ddc_pin); + if (p == PORT_NONE) + return; + + drm_dbg_kms(&i915->drm, + "port %c trying to use the same DDC pin (0x%x) as port %c, " + "disabling port %c DVI/HDMI support\n", + port_name(port), mapped_ddc_pin, + port_name(p), port_name(p)); + + /* + * If we have multiple ports supposedly sharing the pin, then dvi/hdmi + * couldn't exist on the shared port. Otherwise they share the same ddc + * pin and system couldn't communicate with them separately. + * + * Give inverse child device order the priority, last one wins. Yes, + * there are real machines (eg. Asrock B250M-HDV) where VBT has both + * port A and port E with the same AUX ch and we must pick port E :( + */ + child = &i915->display.vbt.ports[p]->child; + + child->device_type &= ~DEVICE_TYPE_TMDS_DVI_SIGNALING; + child->device_type |= DEVICE_TYPE_NOT_HDMI_OUTPUT; + + child->ddc_pin = 0; +} + +static enum port get_port_by_aux_ch(struct drm_i915_private *i915, u8 aux_ch) +{ + const struct intel_bios_encoder_data *devdata; + enum port port; + + if (!aux_ch) + return PORT_NONE; + + for_each_port(port) { + devdata = i915->display.vbt.ports[port]; + + if (devdata && aux_ch == devdata->child.aux_channel) + return port; + } + + return PORT_NONE; +} + +static void sanitize_aux_ch(struct intel_bios_encoder_data *devdata, + enum port port) +{ + struct drm_i915_private *i915 = devdata->i915; + struct child_device_config *child; + enum port p; + + p = get_port_by_aux_ch(i915, devdata->child.aux_channel); + if (p == PORT_NONE) + return; + + drm_dbg_kms(&i915->drm, + "port %c trying to use the same AUX CH (0x%x) as port %c, " + "disabling port %c DP support\n", + port_name(port), devdata->child.aux_channel, + port_name(p), port_name(p)); + + /* + * If we have multiple ports supposedly sharing the aux channel, then DP + * couldn't exist on the shared port. Otherwise they share the same aux + * channel and system couldn't communicate with them separately. + * + * Give inverse child device order the priority, last one wins. Yes, + * there are real machines (eg. Asrock B250M-HDV) where VBT has both + * port A and port E with the same AUX ch and we must pick port E :( + */ + child = &i915->display.vbt.ports[p]->child; + + child->device_type &= ~DEVICE_TYPE_DISPLAYPORT_OUTPUT; + child->aux_channel = 0; +} + +static u8 dvo_port_type(u8 dvo_port) +{ + switch (dvo_port) { + case DVO_PORT_HDMIA: + case DVO_PORT_HDMIB: + case DVO_PORT_HDMIC: + case DVO_PORT_HDMID: + case DVO_PORT_HDMIE: + case DVO_PORT_HDMIF: + case DVO_PORT_HDMIG: + case DVO_PORT_HDMIH: + case DVO_PORT_HDMII: + return DVO_PORT_HDMIA; + case DVO_PORT_DPA: + case DVO_PORT_DPB: + case DVO_PORT_DPC: + case DVO_PORT_DPD: + case DVO_PORT_DPE: + case DVO_PORT_DPF: + case DVO_PORT_DPG: + case DVO_PORT_DPH: + case DVO_PORT_DPI: + return DVO_PORT_DPA; + case DVO_PORT_MIPIA: + case DVO_PORT_MIPIB: + case DVO_PORT_MIPIC: + case DVO_PORT_MIPID: + return DVO_PORT_MIPIA; + default: + return dvo_port; + } +} + +static enum port __dvo_port_to_port(int n_ports, int n_dvo, + const int port_mapping[][3], u8 dvo_port) +{ + enum port port; + int i; + + for (port = PORT_A; port < n_ports; port++) { + for (i = 0; i < n_dvo; i++) { + if (port_mapping[port][i] == -1) + break; + + if (dvo_port == port_mapping[port][i]) + return port; + } + } + + return PORT_NONE; +} + +static enum port dvo_port_to_port(struct drm_i915_private *i915, + u8 dvo_port) +{ + /* + * Each DDI port can have more than one value on the "DVO Port" field, + * so look for all the possible values for each port. + */ + static const int port_mapping[][3] = { + [PORT_A] = { DVO_PORT_HDMIA, DVO_PORT_DPA, -1 }, + [PORT_B] = { DVO_PORT_HDMIB, DVO_PORT_DPB, -1 }, + [PORT_C] = { DVO_PORT_HDMIC, DVO_PORT_DPC, -1 }, + [PORT_D] = { DVO_PORT_HDMID, DVO_PORT_DPD, -1 }, + [PORT_E] = { DVO_PORT_HDMIE, DVO_PORT_DPE, DVO_PORT_CRT }, + [PORT_F] = { DVO_PORT_HDMIF, DVO_PORT_DPF, -1 }, + [PORT_G] = { DVO_PORT_HDMIG, DVO_PORT_DPG, -1 }, + [PORT_H] = { DVO_PORT_HDMIH, DVO_PORT_DPH, -1 }, + [PORT_I] = { DVO_PORT_HDMII, DVO_PORT_DPI, -1 }, + }; + /* + * RKL VBT uses PHY based mapping. Combo PHYs A,B,C,D + * map to DDI A,B,TC1,TC2 respectively. + */ + static const int rkl_port_mapping[][3] = { + [PORT_A] = { DVO_PORT_HDMIA, DVO_PORT_DPA, -1 }, + [PORT_B] = { DVO_PORT_HDMIB, DVO_PORT_DPB, -1 }, + [PORT_C] = { -1 }, + [PORT_TC1] = { DVO_PORT_HDMIC, DVO_PORT_DPC, -1 }, + [PORT_TC2] = { DVO_PORT_HDMID, DVO_PORT_DPD, -1 }, + }; + /* + * Alderlake S ports used in the driver are PORT_A, PORT_D, PORT_E, + * PORT_F and PORT_G, we need to map that to correct VBT sections. + */ + static const int adls_port_mapping[][3] = { + [PORT_A] = { DVO_PORT_HDMIA, DVO_PORT_DPA, -1 }, + [PORT_B] = { -1 }, + [PORT_C] = { -1 }, + [PORT_TC1] = { DVO_PORT_HDMIB, DVO_PORT_DPB, -1 }, + [PORT_TC2] = { DVO_PORT_HDMIC, DVO_PORT_DPC, -1 }, + [PORT_TC3] = { DVO_PORT_HDMID, DVO_PORT_DPD, -1 }, + [PORT_TC4] = { DVO_PORT_HDMIE, DVO_PORT_DPE, -1 }, + }; + static const int xelpd_port_mapping[][3] = { + [PORT_A] = { DVO_PORT_HDMIA, DVO_PORT_DPA, -1 }, + [PORT_B] = { DVO_PORT_HDMIB, DVO_PORT_DPB, -1 }, + [PORT_C] = { DVO_PORT_HDMIC, DVO_PORT_DPC, -1 }, + [PORT_D_XELPD] = { DVO_PORT_HDMID, DVO_PORT_DPD, -1 }, + [PORT_E_XELPD] = { DVO_PORT_HDMIE, DVO_PORT_DPE, -1 }, + [PORT_TC1] = { DVO_PORT_HDMIF, DVO_PORT_DPF, -1 }, + [PORT_TC2] = { DVO_PORT_HDMIG, DVO_PORT_DPG, -1 }, + [PORT_TC3] = { DVO_PORT_HDMIH, DVO_PORT_DPH, -1 }, + [PORT_TC4] = { DVO_PORT_HDMII, DVO_PORT_DPI, -1 }, + }; + + if (DISPLAY_VER(i915) >= 13) + return __dvo_port_to_port(ARRAY_SIZE(xelpd_port_mapping), + ARRAY_SIZE(xelpd_port_mapping[0]), + xelpd_port_mapping, + dvo_port); + else if (IS_ALDERLAKE_S(i915)) + return __dvo_port_to_port(ARRAY_SIZE(adls_port_mapping), + ARRAY_SIZE(adls_port_mapping[0]), + adls_port_mapping, + dvo_port); + else if (IS_DG1(i915) || IS_ROCKETLAKE(i915)) + return __dvo_port_to_port(ARRAY_SIZE(rkl_port_mapping), + ARRAY_SIZE(rkl_port_mapping[0]), + rkl_port_mapping, + dvo_port); + else + return __dvo_port_to_port(ARRAY_SIZE(port_mapping), + ARRAY_SIZE(port_mapping[0]), + port_mapping, + dvo_port); +} + +static enum port +dsi_dvo_port_to_port(struct drm_i915_private *i915, u8 dvo_port) +{ + switch (dvo_port) { + case DVO_PORT_MIPIA: + return PORT_A; + case DVO_PORT_MIPIC: + if (DISPLAY_VER(i915) >= 11) + return PORT_B; + else + return PORT_C; + default: + return PORT_NONE; + } +} + +static int parse_bdb_230_dp_max_link_rate(const int vbt_max_link_rate) +{ + switch (vbt_max_link_rate) { + default: + case BDB_230_VBT_DP_MAX_LINK_RATE_DEF: + return 0; + case BDB_230_VBT_DP_MAX_LINK_RATE_UHBR20: + return 2000000; + case BDB_230_VBT_DP_MAX_LINK_RATE_UHBR13P5: + return 1350000; + case BDB_230_VBT_DP_MAX_LINK_RATE_UHBR10: + return 1000000; + case BDB_230_VBT_DP_MAX_LINK_RATE_HBR3: + return 810000; + case BDB_230_VBT_DP_MAX_LINK_RATE_HBR2: + return 540000; + case BDB_230_VBT_DP_MAX_LINK_RATE_HBR: + return 270000; + case BDB_230_VBT_DP_MAX_LINK_RATE_LBR: + return 162000; + } +} + +static int parse_bdb_216_dp_max_link_rate(const int vbt_max_link_rate) +{ + switch (vbt_max_link_rate) { + default: + case BDB_216_VBT_DP_MAX_LINK_RATE_HBR3: + return 810000; + case BDB_216_VBT_DP_MAX_LINK_RATE_HBR2: + return 540000; + case BDB_216_VBT_DP_MAX_LINK_RATE_HBR: + return 270000; + case BDB_216_VBT_DP_MAX_LINK_RATE_LBR: + return 162000; + } +} + +static int _intel_bios_dp_max_link_rate(const struct intel_bios_encoder_data *devdata) +{ + if (!devdata || devdata->i915->display.vbt.version < 216) + return 0; + + if (devdata->i915->display.vbt.version >= 230) + return parse_bdb_230_dp_max_link_rate(devdata->child.dp_max_link_rate); + else + return parse_bdb_216_dp_max_link_rate(devdata->child.dp_max_link_rate); +} + +static int _intel_bios_dp_max_lane_count(const struct intel_bios_encoder_data *devdata) +{ + if (!devdata || devdata->i915->display.vbt.version < 244) + return 0; + + return devdata->child.dp_max_lane_count + 1; +} + +static void sanitize_device_type(struct intel_bios_encoder_data *devdata, + enum port port) +{ + struct drm_i915_private *i915 = devdata->i915; + bool is_hdmi; + + if (port != PORT_A || DISPLAY_VER(i915) >= 12) + return; + + if (!intel_bios_encoder_supports_dvi(devdata)) + return; + + is_hdmi = intel_bios_encoder_supports_hdmi(devdata); + + drm_dbg_kms(&i915->drm, "VBT claims port A supports DVI%s, ignoring\n", + is_hdmi ? "/HDMI" : ""); + + devdata->child.device_type &= ~DEVICE_TYPE_TMDS_DVI_SIGNALING; + devdata->child.device_type |= DEVICE_TYPE_NOT_HDMI_OUTPUT; +} + +static bool +intel_bios_encoder_supports_crt(const struct intel_bios_encoder_data *devdata) +{ + return devdata->child.device_type & DEVICE_TYPE_ANALOG_OUTPUT; +} + +bool +intel_bios_encoder_supports_dvi(const struct intel_bios_encoder_data *devdata) +{ + return devdata->child.device_type & DEVICE_TYPE_TMDS_DVI_SIGNALING; +} + +bool +intel_bios_encoder_supports_hdmi(const struct intel_bios_encoder_data *devdata) +{ + return intel_bios_encoder_supports_dvi(devdata) && + (devdata->child.device_type & DEVICE_TYPE_NOT_HDMI_OUTPUT) == 0; +} + +bool +intel_bios_encoder_supports_dp(const struct intel_bios_encoder_data *devdata) +{ + return devdata->child.device_type & DEVICE_TYPE_DISPLAYPORT_OUTPUT; +} + +static bool +intel_bios_encoder_supports_edp(const struct intel_bios_encoder_data *devdata) +{ + return intel_bios_encoder_supports_dp(devdata) && + devdata->child.device_type & DEVICE_TYPE_INTERNAL_CONNECTOR; +} + +static bool +intel_bios_encoder_supports_dsi(const struct intel_bios_encoder_data *devdata) +{ + return devdata->child.device_type & DEVICE_TYPE_MIPI_OUTPUT; +} + +static int _intel_bios_hdmi_level_shift(const struct intel_bios_encoder_data *devdata) +{ + if (!devdata || devdata->i915->display.vbt.version < 158) + return -1; + + return devdata->child.hdmi_level_shifter_value; +} + +static int _intel_bios_max_tmds_clock(const struct intel_bios_encoder_data *devdata) +{ + if (!devdata || devdata->i915->display.vbt.version < 204) + return 0; + + switch (devdata->child.hdmi_max_data_rate) { + default: + MISSING_CASE(devdata->child.hdmi_max_data_rate); + fallthrough; + case HDMI_MAX_DATA_RATE_PLATFORM: + return 0; + case HDMI_MAX_DATA_RATE_594: + return 594000; + case HDMI_MAX_DATA_RATE_340: + return 340000; + case HDMI_MAX_DATA_RATE_300: + return 300000; + case HDMI_MAX_DATA_RATE_297: + return 297000; + case HDMI_MAX_DATA_RATE_165: + return 165000; + } +} + +static bool is_port_valid(struct drm_i915_private *i915, enum port port) +{ + /* + * On some ICL SKUs port F is not present, but broken VBTs mark + * the port as present. Only try to initialize port F for the + * SKUs that may actually have it. + */ + if (port == PORT_F && IS_ICELAKE(i915)) + return IS_ICL_WITH_PORT_F(i915); + + return true; +} + +static void print_ddi_port(const struct intel_bios_encoder_data *devdata, + enum port port) +{ + struct drm_i915_private *i915 = devdata->i915; + const struct child_device_config *child = &devdata->child; + bool is_dvi, is_hdmi, is_dp, is_edp, is_dsi, is_crt, supports_typec_usb, supports_tbt; + int dp_boost_level, dp_max_link_rate, hdmi_boost_level, hdmi_level_shift, max_tmds_clock; + + is_dvi = intel_bios_encoder_supports_dvi(devdata); + is_dp = intel_bios_encoder_supports_dp(devdata); + is_crt = intel_bios_encoder_supports_crt(devdata); + is_hdmi = intel_bios_encoder_supports_hdmi(devdata); + is_edp = intel_bios_encoder_supports_edp(devdata); + is_dsi = intel_bios_encoder_supports_dsi(devdata); + + supports_typec_usb = intel_bios_encoder_supports_typec_usb(devdata); + supports_tbt = intel_bios_encoder_supports_tbt(devdata); + + drm_dbg_kms(&i915->drm, + "Port %c VBT info: CRT:%d DVI:%d HDMI:%d DP:%d eDP:%d DSI:%d LSPCON:%d USB-Type-C:%d TBT:%d DSC:%d\n", + port_name(port), is_crt, is_dvi, is_hdmi, is_dp, is_edp, is_dsi, + HAS_LSPCON(i915) && child->lspcon, + supports_typec_usb, supports_tbt, + devdata->dsc != NULL); + + hdmi_level_shift = _intel_bios_hdmi_level_shift(devdata); + if (hdmi_level_shift >= 0) { + drm_dbg_kms(&i915->drm, + "Port %c VBT HDMI level shift: %d\n", + port_name(port), hdmi_level_shift); + } + + max_tmds_clock = _intel_bios_max_tmds_clock(devdata); + if (max_tmds_clock) + drm_dbg_kms(&i915->drm, + "Port %c VBT HDMI max TMDS clock: %d kHz\n", + port_name(port), max_tmds_clock); + + /* I_boost config for SKL and above */ + dp_boost_level = intel_bios_encoder_dp_boost_level(devdata); + if (dp_boost_level) + drm_dbg_kms(&i915->drm, + "Port %c VBT (e)DP boost level: %d\n", + port_name(port), dp_boost_level); + + hdmi_boost_level = intel_bios_encoder_hdmi_boost_level(devdata); + if (hdmi_boost_level) + drm_dbg_kms(&i915->drm, + "Port %c VBT HDMI boost level: %d\n", + port_name(port), hdmi_boost_level); + + dp_max_link_rate = _intel_bios_dp_max_link_rate(devdata); + if (dp_max_link_rate) + drm_dbg_kms(&i915->drm, + "Port %c VBT DP max link rate: %d\n", + port_name(port), dp_max_link_rate); +} + +static void parse_ddi_port(struct intel_bios_encoder_data *devdata) +{ + struct drm_i915_private *i915 = devdata->i915; + const struct child_device_config *child = &devdata->child; + enum port port; + + port = dvo_port_to_port(i915, child->dvo_port); + if (port == PORT_NONE && DISPLAY_VER(i915) >= 11) + port = dsi_dvo_port_to_port(i915, child->dvo_port); + if (port == PORT_NONE) + return; + + if (!is_port_valid(i915, port)) { + drm_dbg_kms(&i915->drm, + "VBT reports port %c as supported, but that can't be true: skipping\n", + port_name(port)); + return; + } + + if (i915->display.vbt.ports[port]) { + drm_dbg_kms(&i915->drm, + "More than one child device for port %c in VBT, using the first.\n", + port_name(port)); + return; + } + + sanitize_device_type(devdata, port); + + if (intel_bios_encoder_supports_dvi(devdata)) + sanitize_ddc_pin(devdata, port); + + if (intel_bios_encoder_supports_dp(devdata)) + sanitize_aux_ch(devdata, port); + + i915->display.vbt.ports[port] = devdata; +} + +static bool has_ddi_port_info(struct drm_i915_private *i915) +{ + return DISPLAY_VER(i915) >= 5 || IS_G4X(i915); +} + +static void parse_ddi_ports(struct drm_i915_private *i915) +{ + struct intel_bios_encoder_data *devdata; + enum port port; + + if (!has_ddi_port_info(i915)) + return; + + list_for_each_entry(devdata, &i915->display.vbt.display_devices, node) + parse_ddi_port(devdata); + + for_each_port(port) { + if (i915->display.vbt.ports[port]) + print_ddi_port(i915->display.vbt.ports[port], port); + } +} + +static void +parse_general_definitions(struct drm_i915_private *i915) +{ + const struct bdb_general_definitions *defs; + struct intel_bios_encoder_data *devdata; + const struct child_device_config *child; + int i, child_device_num; + u8 expected_size; + u16 block_size; + int bus_pin; + + defs = find_section(i915, BDB_GENERAL_DEFINITIONS); + if (!defs) { + drm_dbg_kms(&i915->drm, + "No general definition block is found, no devices defined.\n"); + return; + } + + block_size = get_blocksize(defs); + if (block_size < sizeof(*defs)) { + drm_dbg_kms(&i915->drm, + "General definitions block too small (%u)\n", + block_size); + return; + } + + bus_pin = defs->crt_ddc_gmbus_pin; + drm_dbg_kms(&i915->drm, "crt_ddc_bus_pin: %d\n", bus_pin); + if (intel_gmbus_is_valid_pin(i915, bus_pin)) + i915->display.vbt.crt_ddc_pin = bus_pin; + + if (i915->display.vbt.version < 106) { + expected_size = 22; + } else if (i915->display.vbt.version < 111) { + expected_size = 27; + } else if (i915->display.vbt.version < 195) { + expected_size = LEGACY_CHILD_DEVICE_CONFIG_SIZE; + } else if (i915->display.vbt.version == 195) { + expected_size = 37; + } else if (i915->display.vbt.version <= 215) { + expected_size = 38; + } else if (i915->display.vbt.version <= 237) { + expected_size = 39; + } else { + expected_size = sizeof(*child); + BUILD_BUG_ON(sizeof(*child) < 39); + drm_dbg(&i915->drm, + "Expected child device config size for VBT version %u not known; assuming %u\n", + i915->display.vbt.version, expected_size); + } + + /* Flag an error for unexpected size, but continue anyway. */ + if (defs->child_dev_size != expected_size) + drm_err(&i915->drm, + "Unexpected child device config size %u (expected %u for VBT version %u)\n", + defs->child_dev_size, expected_size, i915->display.vbt.version); + + /* The legacy sized child device config is the minimum we need. */ + if (defs->child_dev_size < LEGACY_CHILD_DEVICE_CONFIG_SIZE) { + drm_dbg_kms(&i915->drm, + "Child device config size %u is too small.\n", + defs->child_dev_size); + return; + } + + /* get the number of child device */ + child_device_num = (block_size - sizeof(*defs)) / defs->child_dev_size; + + for (i = 0; i < child_device_num; i++) { + child = child_device_ptr(defs, i); + if (!child->device_type) + continue; + + drm_dbg_kms(&i915->drm, + "Found VBT child device with type 0x%x\n", + child->device_type); + + devdata = kzalloc(sizeof(*devdata), GFP_KERNEL); + if (!devdata) + break; + + devdata->i915 = i915; + + /* + * Copy as much as we know (sizeof) and is available + * (child_dev_size) of the child device config. Accessing the + * data must depend on VBT version. + */ + memcpy(&devdata->child, child, + min_t(size_t, defs->child_dev_size, sizeof(*child))); + + list_add_tail(&devdata->node, &i915->display.vbt.display_devices); + } + + if (list_empty(&i915->display.vbt.display_devices)) + drm_dbg_kms(&i915->drm, + "no child dev is parsed from VBT\n"); +} + +/* Common defaults which may be overridden by VBT. */ +static void +init_vbt_defaults(struct drm_i915_private *i915) +{ + i915->display.vbt.crt_ddc_pin = GMBUS_PIN_VGADDC; + + /* general features */ + i915->display.vbt.int_tv_support = 1; + i915->display.vbt.int_crt_support = 1; + + /* driver features */ + i915->display.vbt.int_lvds_support = 1; + + /* Default to using SSC */ + i915->display.vbt.lvds_use_ssc = 1; + /* + * Core/SandyBridge/IvyBridge use alternative (120MHz) reference + * clock for LVDS. + */ + i915->display.vbt.lvds_ssc_freq = intel_bios_ssc_frequency(i915, + !HAS_PCH_SPLIT(i915)); + drm_dbg_kms(&i915->drm, "Set default to SSC at %d kHz\n", + i915->display.vbt.lvds_ssc_freq); +} + +/* Common defaults which may be overridden by VBT. */ +static void +init_vbt_panel_defaults(struct intel_panel *panel) +{ + /* Default to having backlight */ + panel->vbt.backlight.present = true; + + /* LFP panel data */ + panel->vbt.lvds_dither = true; +} + +/* Defaults to initialize only if there is no VBT. */ +static void +init_vbt_missing_defaults(struct drm_i915_private *i915) +{ + enum port port; + int ports = BIT(PORT_A) | BIT(PORT_B) | BIT(PORT_C) | + BIT(PORT_D) | BIT(PORT_E) | BIT(PORT_F); + + if (!HAS_DDI(i915) && !IS_CHERRYVIEW(i915)) + return; + + for_each_port_masked(port, ports) { + struct intel_bios_encoder_data *devdata; + struct child_device_config *child; + enum phy phy = intel_port_to_phy(i915, port); + + /* + * VBT has the TypeC mode (native,TBT/USB) and we don't want + * to detect it. + */ + if (intel_phy_is_tc(i915, phy)) + continue; + + /* Create fake child device config */ + devdata = kzalloc(sizeof(*devdata), GFP_KERNEL); + if (!devdata) + break; + + devdata->i915 = i915; + child = &devdata->child; + + if (port == PORT_F) + child->dvo_port = DVO_PORT_HDMIF; + else if (port == PORT_E) + child->dvo_port = DVO_PORT_HDMIE; + else + child->dvo_port = DVO_PORT_HDMIA + port; + + if (port != PORT_A && port != PORT_E) + child->device_type |= DEVICE_TYPE_TMDS_DVI_SIGNALING; + + if (port != PORT_E) + child->device_type |= DEVICE_TYPE_DISPLAYPORT_OUTPUT; + + if (port == PORT_A) + child->device_type |= DEVICE_TYPE_INTERNAL_CONNECTOR; + + list_add_tail(&devdata->node, &i915->display.vbt.display_devices); + + drm_dbg_kms(&i915->drm, + "Generating default VBT child device with type 0x04%x on port %c\n", + child->device_type, port_name(port)); + } + + /* Bypass some minimum baseline VBT version checks */ + i915->display.vbt.version = 155; +} + +static const struct bdb_header *get_bdb_header(const struct vbt_header *vbt) +{ + const void *_vbt = vbt; + + return _vbt + vbt->bdb_offset; +} + +/** + * intel_bios_is_valid_vbt - does the given buffer contain a valid VBT + * @buf: pointer to a buffer to validate + * @size: size of the buffer + * + * Returns true on valid VBT. + */ +bool intel_bios_is_valid_vbt(const void *buf, size_t size) +{ + const struct vbt_header *vbt = buf; + const struct bdb_header *bdb; + + if (!vbt) + return false; + + if (sizeof(struct vbt_header) > size) { + DRM_DEBUG_DRIVER("VBT header incomplete\n"); + return false; + } + + if (memcmp(vbt->signature, "$VBT", 4)) { + DRM_DEBUG_DRIVER("VBT invalid signature\n"); + return false; + } + + if (vbt->vbt_size > size) { + DRM_DEBUG_DRIVER("VBT incomplete (vbt_size overflows)\n"); + return false; + } + + size = vbt->vbt_size; + + if (range_overflows_t(size_t, + vbt->bdb_offset, + sizeof(struct bdb_header), + size)) { + DRM_DEBUG_DRIVER("BDB header incomplete\n"); + return false; + } + + bdb = get_bdb_header(vbt); + if (range_overflows_t(size_t, vbt->bdb_offset, bdb->bdb_size, size)) { + DRM_DEBUG_DRIVER("BDB incomplete\n"); + return false; + } + + return vbt; +} + +static struct vbt_header *spi_oprom_get_vbt(struct drm_i915_private *i915) +{ + u32 count, data, found, store = 0; + u32 static_region, oprom_offset; + u32 oprom_size = 0x200000; + u16 vbt_size; + u32 *vbt; + + static_region = intel_uncore_read(&i915->uncore, SPI_STATIC_REGIONS); + static_region &= OPTIONROM_SPI_REGIONID_MASK; + intel_uncore_write(&i915->uncore, PRIMARY_SPI_REGIONID, static_region); + + oprom_offset = intel_uncore_read(&i915->uncore, OROM_OFFSET); + oprom_offset &= OROM_OFFSET_MASK; + + for (count = 0; count < oprom_size; count += 4) { + intel_uncore_write(&i915->uncore, PRIMARY_SPI_ADDRESS, oprom_offset + count); + data = intel_uncore_read(&i915->uncore, PRIMARY_SPI_TRIGGER); + + if (data == *((const u32 *)"$VBT")) { + found = oprom_offset + count; + break; + } + } + + if (count >= oprom_size) + goto err_not_found; + + /* Get VBT size and allocate space for the VBT */ + intel_uncore_write(&i915->uncore, PRIMARY_SPI_ADDRESS, found + + offsetof(struct vbt_header, vbt_size)); + vbt_size = intel_uncore_read(&i915->uncore, PRIMARY_SPI_TRIGGER); + vbt_size &= 0xffff; + + vbt = kzalloc(round_up(vbt_size, 4), GFP_KERNEL); + if (!vbt) + goto err_not_found; + + for (count = 0; count < vbt_size; count += 4) { + intel_uncore_write(&i915->uncore, PRIMARY_SPI_ADDRESS, found + count); + data = intel_uncore_read(&i915->uncore, PRIMARY_SPI_TRIGGER); + *(vbt + store++) = data; + } + + if (!intel_bios_is_valid_vbt(vbt, vbt_size)) + goto err_free_vbt; + + drm_dbg_kms(&i915->drm, "Found valid VBT in SPI flash\n"); + + return (struct vbt_header *)vbt; + +err_free_vbt: + kfree(vbt); +err_not_found: + return NULL; +} + +static struct vbt_header *oprom_get_vbt(struct drm_i915_private *i915) +{ + struct pci_dev *pdev = to_pci_dev(i915->drm.dev); + void __iomem *p = NULL, *oprom; + struct vbt_header *vbt; + u16 vbt_size; + size_t i, size; + + oprom = pci_map_rom(pdev, &size); + if (!oprom) + return NULL; + + /* Scour memory looking for the VBT signature. */ + for (i = 0; i + 4 < size; i += 4) { + if (ioread32(oprom + i) != *((const u32 *)"$VBT")) + continue; + + p = oprom + i; + size -= i; + break; + } + + if (!p) + goto err_unmap_oprom; + + if (sizeof(struct vbt_header) > size) { + drm_dbg(&i915->drm, "VBT header incomplete\n"); + goto err_unmap_oprom; + } + + vbt_size = ioread16(p + offsetof(struct vbt_header, vbt_size)); + if (vbt_size > size) { + drm_dbg(&i915->drm, + "VBT incomplete (vbt_size overflows)\n"); + goto err_unmap_oprom; + } + + /* The rest will be validated by intel_bios_is_valid_vbt() */ + vbt = kmalloc(vbt_size, GFP_KERNEL); + if (!vbt) + goto err_unmap_oprom; + + memcpy_fromio(vbt, p, vbt_size); + + if (!intel_bios_is_valid_vbt(vbt, vbt_size)) + goto err_free_vbt; + + pci_unmap_rom(pdev, oprom); + + drm_dbg_kms(&i915->drm, "Found valid VBT in PCI ROM\n"); + + return vbt; + +err_free_vbt: + kfree(vbt); +err_unmap_oprom: + pci_unmap_rom(pdev, oprom); + + return NULL; +} + +/** + * intel_bios_init - find VBT and initialize settings from the BIOS + * @i915: i915 device instance + * + * Parse and initialize settings from the Video BIOS Tables (VBT). If the VBT + * was not found in ACPI OpRegion, try to find it in PCI ROM first. Also + * initialize some defaults if the VBT is not present at all. + */ +void intel_bios_init(struct drm_i915_private *i915) +{ + const struct vbt_header *vbt = i915->display.opregion.vbt; + struct vbt_header *oprom_vbt = NULL; + const struct bdb_header *bdb; + + INIT_LIST_HEAD(&i915->display.vbt.display_devices); + INIT_LIST_HEAD(&i915->display.vbt.bdb_blocks); + + if (!HAS_DISPLAY(i915)) { + drm_dbg_kms(&i915->drm, + "Skipping VBT init due to disabled display.\n"); + return; + } + + init_vbt_defaults(i915); + + /* + * If the OpRegion does not have VBT, look in SPI flash through MMIO or + * PCI mapping + */ + if (!vbt && IS_DGFX(i915)) { + oprom_vbt = spi_oprom_get_vbt(i915); + vbt = oprom_vbt; + } + + if (!vbt) { + oprom_vbt = oprom_get_vbt(i915); + vbt = oprom_vbt; + } + + if (!vbt) + goto out; + + bdb = get_bdb_header(vbt); + i915->display.vbt.version = bdb->version; + + drm_dbg_kms(&i915->drm, + "VBT signature \"%.*s\", BDB version %d\n", + (int)sizeof(vbt->signature), vbt->signature, i915->display.vbt.version); + + init_bdb_blocks(i915, bdb); + + /* Grab useful general definitions */ + parse_general_features(i915); + parse_general_definitions(i915); + parse_driver_features(i915); + + /* Depends on child device list */ + parse_compression_parameters(i915); + +out: + if (!vbt) { + drm_info(&i915->drm, + "Failed to find VBIOS tables (VBT)\n"); + init_vbt_missing_defaults(i915); + } + + /* Further processing on pre-parsed or generated child device data */ + parse_sdvo_device_mapping(i915); + parse_ddi_ports(i915); + + kfree(oprom_vbt); +} + +static void intel_bios_init_panel(struct drm_i915_private *i915, + struct intel_panel *panel, + const struct intel_bios_encoder_data *devdata, + const struct edid *edid, + bool use_fallback) +{ + /* already have it? */ + if (panel->vbt.panel_type >= 0) { + drm_WARN_ON(&i915->drm, !use_fallback); + return; + } + + panel->vbt.panel_type = get_panel_type(i915, devdata, + edid, use_fallback); + if (panel->vbt.panel_type < 0) { + drm_WARN_ON(&i915->drm, use_fallback); + return; + } + + init_vbt_panel_defaults(panel); + + parse_panel_options(i915, panel); + parse_generic_dtd(i915, panel); + parse_lfp_data(i915, panel); + parse_lfp_backlight(i915, panel); + parse_sdvo_panel_data(i915, panel); + parse_panel_driver_features(i915, panel); + parse_power_conservation_features(i915, panel); + parse_edp(i915, panel); + parse_psr(i915, panel); + parse_mipi_config(i915, panel); + parse_mipi_sequence(i915, panel); +} + +void intel_bios_init_panel_early(struct drm_i915_private *i915, + struct intel_panel *panel, + const struct intel_bios_encoder_data *devdata) +{ + intel_bios_init_panel(i915, panel, devdata, NULL, false); +} + +void intel_bios_init_panel_late(struct drm_i915_private *i915, + struct intel_panel *panel, + const struct intel_bios_encoder_data *devdata, + const struct edid *edid) +{ + intel_bios_init_panel(i915, panel, devdata, edid, true); +} + +/** + * intel_bios_driver_remove - Free any resources allocated by intel_bios_init() + * @i915: i915 device instance + */ +void intel_bios_driver_remove(struct drm_i915_private *i915) +{ + struct intel_bios_encoder_data *devdata, *nd; + struct bdb_block_entry *entry, *ne; + + list_for_each_entry_safe(devdata, nd, &i915->display.vbt.display_devices, node) { + list_del(&devdata->node); + kfree(devdata->dsc); + kfree(devdata); + } + + list_for_each_entry_safe(entry, ne, &i915->display.vbt.bdb_blocks, node) { + list_del(&entry->node); + kfree(entry); + } +} + +void intel_bios_fini_panel(struct intel_panel *panel) +{ + kfree(panel->vbt.sdvo_lvds_vbt_mode); + panel->vbt.sdvo_lvds_vbt_mode = NULL; + kfree(panel->vbt.lfp_lvds_vbt_mode); + panel->vbt.lfp_lvds_vbt_mode = NULL; + kfree(panel->vbt.dsi.data); + panel->vbt.dsi.data = NULL; + kfree(panel->vbt.dsi.pps); + panel->vbt.dsi.pps = NULL; + kfree(panel->vbt.dsi.config); + panel->vbt.dsi.config = NULL; + kfree(panel->vbt.dsi.deassert_seq); + panel->vbt.dsi.deassert_seq = NULL; +} + +/** + * intel_bios_is_tv_present - is integrated TV present in VBT + * @i915: i915 device instance + * + * Return true if TV is present. If no child devices were parsed from VBT, + * assume TV is present. + */ +bool intel_bios_is_tv_present(struct drm_i915_private *i915) +{ + const struct intel_bios_encoder_data *devdata; + const struct child_device_config *child; + + if (!i915->display.vbt.int_tv_support) + return false; + + if (list_empty(&i915->display.vbt.display_devices)) + return true; + + list_for_each_entry(devdata, &i915->display.vbt.display_devices, node) { + child = &devdata->child; + + /* + * If the device type is not TV, continue. + */ + switch (child->device_type) { + case DEVICE_TYPE_INT_TV: + case DEVICE_TYPE_TV: + case DEVICE_TYPE_TV_SVIDEO_COMPOSITE: + break; + default: + continue; + } + /* Only when the addin_offset is non-zero, it is regarded + * as present. + */ + if (child->addin_offset) + return true; + } + + return false; +} + +/** + * intel_bios_is_lvds_present - is LVDS present in VBT + * @i915: i915 device instance + * @i2c_pin: i2c pin for LVDS if present + * + * Return true if LVDS is present. If no child devices were parsed from VBT, + * assume LVDS is present. + */ +bool intel_bios_is_lvds_present(struct drm_i915_private *i915, u8 *i2c_pin) +{ + const struct intel_bios_encoder_data *devdata; + const struct child_device_config *child; + + if (list_empty(&i915->display.vbt.display_devices)) + return true; + + list_for_each_entry(devdata, &i915->display.vbt.display_devices, node) { + child = &devdata->child; + + /* If the device type is not LFP, continue. + * We have to check both the new identifiers as well as the + * old for compatibility with some BIOSes. + */ + if (child->device_type != DEVICE_TYPE_INT_LFP && + child->device_type != DEVICE_TYPE_LFP) + continue; + + if (intel_gmbus_is_valid_pin(i915, child->i2c_pin)) + *i2c_pin = child->i2c_pin; + + /* However, we cannot trust the BIOS writers to populate + * the VBT correctly. Since LVDS requires additional + * information from AIM blocks, a non-zero addin offset is + * a good indicator that the LVDS is actually present. + */ + if (child->addin_offset) + return true; + + /* But even then some BIOS writers perform some black magic + * and instantiate the device without reference to any + * additional data. Trust that if the VBT was written into + * the OpRegion then they have validated the LVDS's existence. + */ + if (i915->display.opregion.vbt) + return true; + } + + return false; +} + +/** + * intel_bios_is_port_present - is the specified digital port present + * @i915: i915 device instance + * @port: port to check + * + * Return true if the device in %port is present. + */ +bool intel_bios_is_port_present(struct drm_i915_private *i915, enum port port) +{ + if (WARN_ON(!has_ddi_port_info(i915))) + return true; + + return i915->display.vbt.ports[port]; +} + +/** + * intel_bios_is_port_edp - is the device in given port eDP + * @i915: i915 device instance + * @port: port to check + * + * Return true if the device in %port is eDP. + */ +bool intel_bios_is_port_edp(struct drm_i915_private *i915, enum port port) +{ + const struct intel_bios_encoder_data *devdata = + intel_bios_encoder_data_lookup(i915, port); + + return devdata && intel_bios_encoder_supports_edp(devdata); +} + +static bool intel_bios_encoder_supports_dp_dual_mode(const struct intel_bios_encoder_data *devdata) +{ + const struct child_device_config *child = &devdata->child; + + if (!intel_bios_encoder_supports_dp(devdata) || + !intel_bios_encoder_supports_hdmi(devdata)) + return false; + + if (dvo_port_type(child->dvo_port) == DVO_PORT_DPA) + return true; + + /* Only accept a HDMI dvo_port as DP++ if it has an AUX channel */ + if (dvo_port_type(child->dvo_port) == DVO_PORT_HDMIA && + child->aux_channel != 0) + return true; + + return false; +} + +bool intel_bios_is_port_dp_dual_mode(struct drm_i915_private *i915, + enum port port) +{ + const struct intel_bios_encoder_data *devdata = + intel_bios_encoder_data_lookup(i915, port); + + return devdata && intel_bios_encoder_supports_dp_dual_mode(devdata); +} + +/** + * intel_bios_is_dsi_present - is DSI present in VBT + * @i915: i915 device instance + * @port: port for DSI if present + * + * Return true if DSI is present, and return the port in %port. + */ +bool intel_bios_is_dsi_present(struct drm_i915_private *i915, + enum port *port) +{ + const struct intel_bios_encoder_data *devdata; + const struct child_device_config *child; + u8 dvo_port; + + list_for_each_entry(devdata, &i915->display.vbt.display_devices, node) { + child = &devdata->child; + + if (!(child->device_type & DEVICE_TYPE_MIPI_OUTPUT)) + continue; + + dvo_port = child->dvo_port; + + if (dsi_dvo_port_to_port(i915, dvo_port) == PORT_NONE) { + drm_dbg_kms(&i915->drm, + "VBT has unsupported DSI port %c\n", + port_name(dvo_port - DVO_PORT_MIPIA)); + continue; + } + + if (port) + *port = dsi_dvo_port_to_port(i915, dvo_port); + return true; + } + + return false; +} + +static void fill_dsc(struct intel_crtc_state *crtc_state, + struct dsc_compression_parameters_entry *dsc, + int dsc_max_bpc) +{ + struct drm_dsc_config *vdsc_cfg = &crtc_state->dsc.config; + int bpc = 8; + + vdsc_cfg->dsc_version_major = dsc->version_major; + vdsc_cfg->dsc_version_minor = dsc->version_minor; + + if (dsc->support_12bpc && dsc_max_bpc >= 12) + bpc = 12; + else if (dsc->support_10bpc && dsc_max_bpc >= 10) + bpc = 10; + else if (dsc->support_8bpc && dsc_max_bpc >= 8) + bpc = 8; + else + DRM_DEBUG_KMS("VBT: Unsupported BPC %d for DCS\n", + dsc_max_bpc); + + crtc_state->pipe_bpp = bpc * 3; + + crtc_state->dsc.compressed_bpp = min(crtc_state->pipe_bpp, + VBT_DSC_MAX_BPP(dsc->max_bpp)); + + /* + * FIXME: This is ugly, and slice count should take DSC engine + * throughput etc. into account. + * + * Also, per spec DSI supports 1, 2, 3 or 4 horizontal slices. + */ + if (dsc->slices_per_line & BIT(2)) { + crtc_state->dsc.slice_count = 4; + } else if (dsc->slices_per_line & BIT(1)) { + crtc_state->dsc.slice_count = 2; + } else { + /* FIXME */ + if (!(dsc->slices_per_line & BIT(0))) + DRM_DEBUG_KMS("VBT: Unsupported DSC slice count for DSI\n"); + + crtc_state->dsc.slice_count = 1; + } + + if (crtc_state->hw.adjusted_mode.crtc_hdisplay % + crtc_state->dsc.slice_count != 0) + DRM_DEBUG_KMS("VBT: DSC hdisplay %d not divisible by slice count %d\n", + crtc_state->hw.adjusted_mode.crtc_hdisplay, + crtc_state->dsc.slice_count); + + /* + * The VBT rc_buffer_block_size and rc_buffer_size definitions + * correspond to DP 1.4 DPCD offsets 0x62 and 0x63. + */ + vdsc_cfg->rc_model_size = drm_dsc_dp_rc_buffer_size(dsc->rc_buffer_block_size, + dsc->rc_buffer_size); + + /* FIXME: DSI spec says bpc + 1 for this one */ + vdsc_cfg->line_buf_depth = VBT_DSC_LINE_BUFFER_DEPTH(dsc->line_buffer_depth); + + vdsc_cfg->block_pred_enable = dsc->block_prediction_enable; + + vdsc_cfg->slice_height = dsc->slice_height; +} + +/* FIXME: initially DSI specific */ +bool intel_bios_get_dsc_params(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state, + int dsc_max_bpc) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + const struct intel_bios_encoder_data *devdata; + const struct child_device_config *child; + + list_for_each_entry(devdata, &i915->display.vbt.display_devices, node) { + child = &devdata->child; + + if (!(child->device_type & DEVICE_TYPE_MIPI_OUTPUT)) + continue; + + if (dsi_dvo_port_to_port(i915, child->dvo_port) == encoder->port) { + if (!devdata->dsc) + return false; + + if (crtc_state) + fill_dsc(crtc_state, devdata->dsc, dsc_max_bpc); + + return true; + } + } + + return false; +} + +/** + * intel_bios_is_port_hpd_inverted - is HPD inverted for %port + * @i915: i915 device instance + * @port: port to check + * + * Return true if HPD should be inverted for %port. + */ +bool +intel_bios_is_port_hpd_inverted(const struct drm_i915_private *i915, + enum port port) +{ + const struct intel_bios_encoder_data *devdata = i915->display.vbt.ports[port]; + + if (drm_WARN_ON_ONCE(&i915->drm, + !IS_GEMINILAKE(i915) && !IS_BROXTON(i915))) + return false; + + return devdata && devdata->child.hpd_invert; +} + +/** + * intel_bios_is_lspcon_present - if LSPCON is attached on %port + * @i915: i915 device instance + * @port: port to check + * + * Return true if LSPCON is present on this port + */ +bool +intel_bios_is_lspcon_present(const struct drm_i915_private *i915, + enum port port) +{ + const struct intel_bios_encoder_data *devdata = i915->display.vbt.ports[port]; + + return HAS_LSPCON(i915) && devdata && devdata->child.lspcon; +} + +/** + * intel_bios_is_lane_reversal_needed - if lane reversal needed on port + * @i915: i915 device instance + * @port: port to check + * + * Return true if port requires lane reversal + */ +bool +intel_bios_is_lane_reversal_needed(const struct drm_i915_private *i915, + enum port port) +{ + const struct intel_bios_encoder_data *devdata = i915->display.vbt.ports[port]; + + return devdata && devdata->child.lane_reversal; +} + +enum aux_ch intel_bios_port_aux_ch(struct drm_i915_private *i915, + enum port port) +{ + const struct intel_bios_encoder_data *devdata = i915->display.vbt.ports[port]; + enum aux_ch aux_ch; + + if (!devdata || !devdata->child.aux_channel) { + aux_ch = (enum aux_ch)port; + + drm_dbg_kms(&i915->drm, + "using AUX %c for port %c (platform default)\n", + aux_ch_name(aux_ch), port_name(port)); + return aux_ch; + } + + /* + * RKL/DG1 VBT uses PHY based mapping. Combo PHYs A,B,C,D + * map to DDI A,B,TC1,TC2 respectively. + * + * ADL-S VBT uses PHY based mapping. Combo PHYs A,B,C,D,E + * map to DDI A,TC1,TC2,TC3,TC4 respectively. + */ + switch (devdata->child.aux_channel) { + case DP_AUX_A: + aux_ch = AUX_CH_A; + break; + case DP_AUX_B: + if (IS_ALDERLAKE_S(i915)) + aux_ch = AUX_CH_USBC1; + else + aux_ch = AUX_CH_B; + break; + case DP_AUX_C: + if (IS_ALDERLAKE_S(i915)) + aux_ch = AUX_CH_USBC2; + else if (IS_DG1(i915) || IS_ROCKETLAKE(i915)) + aux_ch = AUX_CH_USBC1; + else + aux_ch = AUX_CH_C; + break; + case DP_AUX_D: + if (DISPLAY_VER(i915) >= 13) + aux_ch = AUX_CH_D_XELPD; + else if (IS_ALDERLAKE_S(i915)) + aux_ch = AUX_CH_USBC3; + else if (IS_DG1(i915) || IS_ROCKETLAKE(i915)) + aux_ch = AUX_CH_USBC2; + else + aux_ch = AUX_CH_D; + break; + case DP_AUX_E: + if (DISPLAY_VER(i915) >= 13) + aux_ch = AUX_CH_E_XELPD; + else if (IS_ALDERLAKE_S(i915)) + aux_ch = AUX_CH_USBC4; + else + aux_ch = AUX_CH_E; + break; + case DP_AUX_F: + if (DISPLAY_VER(i915) >= 13) + aux_ch = AUX_CH_USBC1; + else + aux_ch = AUX_CH_F; + break; + case DP_AUX_G: + if (DISPLAY_VER(i915) >= 13) + aux_ch = AUX_CH_USBC2; + else + aux_ch = AUX_CH_G; + break; + case DP_AUX_H: + if (DISPLAY_VER(i915) >= 13) + aux_ch = AUX_CH_USBC3; + else + aux_ch = AUX_CH_H; + break; + case DP_AUX_I: + if (DISPLAY_VER(i915) >= 13) + aux_ch = AUX_CH_USBC4; + else + aux_ch = AUX_CH_I; + break; + default: + MISSING_CASE(devdata->child.aux_channel); + aux_ch = AUX_CH_A; + break; + } + + drm_dbg_kms(&i915->drm, "using AUX %c for port %c (VBT)\n", + aux_ch_name(aux_ch), port_name(port)); + + return aux_ch; +} + +int intel_bios_max_tmds_clock(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + const struct intel_bios_encoder_data *devdata = i915->display.vbt.ports[encoder->port]; + + return _intel_bios_max_tmds_clock(devdata); +} + +/* This is an index in the HDMI/DVI DDI buffer translation table, or -1 */ +int intel_bios_hdmi_level_shift(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + const struct intel_bios_encoder_data *devdata = i915->display.vbt.ports[encoder->port]; + + return _intel_bios_hdmi_level_shift(devdata); +} + +int intel_bios_encoder_dp_boost_level(const struct intel_bios_encoder_data *devdata) +{ + if (!devdata || devdata->i915->display.vbt.version < 196 || !devdata->child.iboost) + return 0; + + return translate_iboost(devdata->child.dp_iboost_level); +} + +int intel_bios_encoder_hdmi_boost_level(const struct intel_bios_encoder_data *devdata) +{ + if (!devdata || devdata->i915->display.vbt.version < 196 || !devdata->child.iboost) + return 0; + + return translate_iboost(devdata->child.hdmi_iboost_level); +} + +int intel_bios_dp_max_link_rate(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + const struct intel_bios_encoder_data *devdata = i915->display.vbt.ports[encoder->port]; + + return _intel_bios_dp_max_link_rate(devdata); +} + +int intel_bios_dp_max_lane_count(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + const struct intel_bios_encoder_data *devdata = i915->display.vbt.ports[encoder->port]; + + return _intel_bios_dp_max_lane_count(devdata); +} + +int intel_bios_alternate_ddc_pin(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + const struct intel_bios_encoder_data *devdata = i915->display.vbt.ports[encoder->port]; + + if (!devdata || !devdata->child.ddc_pin) + return 0; + + return map_ddc_pin(i915, devdata->child.ddc_pin); +} + +bool intel_bios_encoder_supports_typec_usb(const struct intel_bios_encoder_data *devdata) +{ + return devdata->i915->display.vbt.version >= 195 && devdata->child.dp_usb_type_c; +} + +bool intel_bios_encoder_supports_tbt(const struct intel_bios_encoder_data *devdata) +{ + return devdata->i915->display.vbt.version >= 209 && devdata->child.tbt; +} + +const struct intel_bios_encoder_data * +intel_bios_encoder_data_lookup(struct drm_i915_private *i915, enum port port) +{ + return i915->display.vbt.ports[port]; +} diff --git a/drivers/gpu/drm/i915/display/intel_bios.h b/drivers/gpu/drm/i915/display/intel_bios.h new file mode 100644 index 000000000..ff1fdd2e0 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_bios.h @@ -0,0 +1,280 @@ +/* + * Copyright © 2016-2019 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * Please use intel_vbt_defs.h for VBT private data, to hide and abstract away + * the VBT from the rest of the driver. Add the parsed, clean data to struct + * intel_vbt_data within struct drm_i915_private. + */ + +#ifndef _INTEL_BIOS_H_ +#define _INTEL_BIOS_H_ + +#include + +struct drm_i915_private; +struct edid; +struct intel_bios_encoder_data; +struct intel_crtc_state; +struct intel_encoder; +struct intel_panel; +enum port; + +enum intel_backlight_type { + INTEL_BACKLIGHT_PMIC, + INTEL_BACKLIGHT_LPSS, + INTEL_BACKLIGHT_DISPLAY_DDI, + INTEL_BACKLIGHT_DSI_DCS, + INTEL_BACKLIGHT_PANEL_DRIVER_INTERFACE, + INTEL_BACKLIGHT_VESA_EDP_AUX_INTERFACE, +}; + +struct edp_power_seq { + u16 t1_t3; + u16 t8; + u16 t9; + u16 t10; + u16 t11_t12; +} __packed; + +/* + * MIPI Sequence Block definitions + * + * Note the VBT spec has AssertReset / DeassertReset swapped from their + * usual naming, we use the proper names here to avoid confusion when + * reading the code. + */ +enum mipi_seq { + MIPI_SEQ_END = 0, + MIPI_SEQ_DEASSERT_RESET, /* Spec says MipiAssertResetPin */ + MIPI_SEQ_INIT_OTP, + MIPI_SEQ_DISPLAY_ON, + MIPI_SEQ_DISPLAY_OFF, + MIPI_SEQ_ASSERT_RESET, /* Spec says MipiDeassertResetPin */ + MIPI_SEQ_BACKLIGHT_ON, /* sequence block v2+ */ + MIPI_SEQ_BACKLIGHT_OFF, /* sequence block v2+ */ + MIPI_SEQ_TEAR_ON, /* sequence block v2+ */ + MIPI_SEQ_TEAR_OFF, /* sequence block v3+ */ + MIPI_SEQ_POWER_ON, /* sequence block v3+ */ + MIPI_SEQ_POWER_OFF, /* sequence block v3+ */ + MIPI_SEQ_MAX +}; + +enum mipi_seq_element { + MIPI_SEQ_ELEM_END = 0, + MIPI_SEQ_ELEM_SEND_PKT, + MIPI_SEQ_ELEM_DELAY, + MIPI_SEQ_ELEM_GPIO, + MIPI_SEQ_ELEM_I2C, /* sequence block v2+ */ + MIPI_SEQ_ELEM_SPI, /* sequence block v3+ */ + MIPI_SEQ_ELEM_PMIC, /* sequence block v3+ */ + MIPI_SEQ_ELEM_MAX +}; + +#define MIPI_DSI_UNDEFINED_PANEL_ID 0 +#define MIPI_DSI_GENERIC_PANEL_ID 1 + +struct mipi_config { + u16 panel_id; + + /* General Params */ + u32 enable_dithering:1; + u32 rsvd1:1; + u32 is_bridge:1; + + u32 panel_arch_type:2; + u32 is_cmd_mode:1; + +#define NON_BURST_SYNC_PULSE 0x1 +#define NON_BURST_SYNC_EVENTS 0x2 +#define BURST_MODE 0x3 + u32 video_transfer_mode:2; + + u32 cabc_supported:1; +#define PPS_BLC_PMIC 0 +#define PPS_BLC_SOC 1 + u32 pwm_blc:1; + + /* Bit 13:10 */ +#define PIXEL_FORMAT_RGB565 0x1 +#define PIXEL_FORMAT_RGB666 0x2 +#define PIXEL_FORMAT_RGB666_LOOSELY_PACKED 0x3 +#define PIXEL_FORMAT_RGB888 0x4 + u32 videomode_color_format:4; + + /* Bit 15:14 */ +#define ENABLE_ROTATION_0 0x0 +#define ENABLE_ROTATION_90 0x1 +#define ENABLE_ROTATION_180 0x2 +#define ENABLE_ROTATION_270 0x3 + u32 rotation:2; + u32 bta_enabled:1; + u32 rsvd2:15; + + /* 2 byte Port Description */ +#define DUAL_LINK_NOT_SUPPORTED 0 +#define DUAL_LINK_FRONT_BACK 1 +#define DUAL_LINK_PIXEL_ALT 2 + u16 dual_link:2; + u16 lane_cnt:2; + u16 pixel_overlap:3; + u16 rgb_flip:1; +#define DL_DCS_PORT_A 0x00 +#define DL_DCS_PORT_C 0x01 +#define DL_DCS_PORT_A_AND_C 0x02 + u16 dl_dcs_cabc_ports:2; + u16 dl_dcs_backlight_ports:2; + u16 rsvd3:4; + + u16 rsvd4; + + u8 rsvd5; + u32 target_burst_mode_freq; + u32 dsi_ddr_clk; + u32 bridge_ref_clk; + +#define BYTE_CLK_SEL_20MHZ 0 +#define BYTE_CLK_SEL_10MHZ 1 +#define BYTE_CLK_SEL_5MHZ 2 + u8 byte_clk_sel:2; + + u8 rsvd6:6; + + /* DPHY Flags */ + u16 dphy_param_valid:1; + u16 eot_pkt_disabled:1; + u16 enable_clk_stop:1; + u16 rsvd7:13; + + u32 hs_tx_timeout; + u32 lp_rx_timeout; + u32 turn_around_timeout; + u32 device_reset_timer; + u32 master_init_timer; + u32 dbi_bw_timer; + u32 lp_byte_clk_val; + + /* 4 byte Dphy Params */ + u32 prepare_cnt:6; + u32 rsvd8:2; + u32 clk_zero_cnt:8; + u32 trail_cnt:5; + u32 rsvd9:3; + u32 exit_zero_cnt:6; + u32 rsvd10:2; + + u32 clk_lane_switch_cnt; + u32 hl_switch_cnt; + + u32 rsvd11[6]; + + /* timings based on dphy spec */ + u8 tclk_miss; + u8 tclk_post; + u8 rsvd12; + u8 tclk_pre; + u8 tclk_prepare; + u8 tclk_settle; + u8 tclk_term_enable; + u8 tclk_trail; + u16 tclk_prepare_clkzero; + u8 rsvd13; + u8 td_term_enable; + u8 teot; + u8 ths_exit; + u8 ths_prepare; + u16 ths_prepare_hszero; + u8 rsvd14; + u8 ths_settle; + u8 ths_skip; + u8 ths_trail; + u8 tinit; + u8 tlpx; + u8 rsvd15[3]; + + /* GPIOs */ + u8 panel_enable; + u8 bl_enable; + u8 pwm_enable; + u8 reset_r_n; + u8 pwr_down_r; + u8 stdby_r_n; + +} __packed; + +/* all delays have a unit of 100us */ +struct mipi_pps_data { + u16 panel_on_delay; + u16 bl_enable_delay; + u16 bl_disable_delay; + u16 panel_off_delay; + u16 panel_power_cycle_delay; +} __packed; + +void intel_bios_init(struct drm_i915_private *dev_priv); +void intel_bios_init_panel_early(struct drm_i915_private *dev_priv, + struct intel_panel *panel, + const struct intel_bios_encoder_data *devdata); +void intel_bios_init_panel_late(struct drm_i915_private *dev_priv, + struct intel_panel *panel, + const struct intel_bios_encoder_data *devdata, + const struct edid *edid); +void intel_bios_fini_panel(struct intel_panel *panel); +void intel_bios_driver_remove(struct drm_i915_private *dev_priv); +bool intel_bios_is_valid_vbt(const void *buf, size_t size); +bool intel_bios_is_tv_present(struct drm_i915_private *dev_priv); +bool intel_bios_is_lvds_present(struct drm_i915_private *dev_priv, u8 *i2c_pin); +bool intel_bios_is_port_present(struct drm_i915_private *dev_priv, enum port port); +bool intel_bios_is_port_edp(struct drm_i915_private *dev_priv, enum port port); +bool intel_bios_is_port_dp_dual_mode(struct drm_i915_private *dev_priv, enum port port); +bool intel_bios_is_dsi_present(struct drm_i915_private *dev_priv, enum port *port); +bool intel_bios_is_port_hpd_inverted(const struct drm_i915_private *i915, + enum port port); +bool intel_bios_is_lspcon_present(const struct drm_i915_private *i915, + enum port port); +bool intel_bios_is_lane_reversal_needed(const struct drm_i915_private *i915, + enum port port); +enum aux_ch intel_bios_port_aux_ch(struct drm_i915_private *dev_priv, enum port port); +bool intel_bios_get_dsc_params(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state, + int dsc_max_bpc); +int intel_bios_max_tmds_clock(struct intel_encoder *encoder); +int intel_bios_hdmi_level_shift(struct intel_encoder *encoder); +int intel_bios_dp_max_link_rate(struct intel_encoder *encoder); +int intel_bios_dp_max_lane_count(struct intel_encoder *encoder); +int intel_bios_alternate_ddc_pin(struct intel_encoder *encoder); +bool intel_bios_port_supports_typec_usb(struct drm_i915_private *i915, enum port port); +bool intel_bios_port_supports_tbt(struct drm_i915_private *i915, enum port port); + +const struct intel_bios_encoder_data * +intel_bios_encoder_data_lookup(struct drm_i915_private *i915, enum port port); + +bool intel_bios_encoder_supports_dvi(const struct intel_bios_encoder_data *devdata); +bool intel_bios_encoder_supports_hdmi(const struct intel_bios_encoder_data *devdata); +bool intel_bios_encoder_supports_dp(const struct intel_bios_encoder_data *devdata); +bool intel_bios_encoder_supports_typec_usb(const struct intel_bios_encoder_data *devdata); +bool intel_bios_encoder_supports_tbt(const struct intel_bios_encoder_data *devdata); +int intel_bios_encoder_dp_boost_level(const struct intel_bios_encoder_data *devdata); +int intel_bios_encoder_hdmi_boost_level(const struct intel_bios_encoder_data *devdata); + +#endif /* _INTEL_BIOS_H_ */ diff --git a/drivers/gpu/drm/i915/display/intel_bw.c b/drivers/gpu/drm/i915/display/intel_bw.c new file mode 100644 index 000000000..4ace026b2 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_bw.c @@ -0,0 +1,1203 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2019 Intel Corporation + */ + +#include + +#include "i915_drv.h" +#include "i915_reg.h" +#include "i915_utils.h" +#include "intel_atomic.h" +#include "intel_bw.h" +#include "intel_cdclk.h" +#include "intel_display_core.h" +#include "intel_display_types.h" +#include "skl_watermark.h" +#include "intel_mchbar_regs.h" +#include "intel_pcode.h" + +/* Parameters for Qclk Geyserville (QGV) */ +struct intel_qgv_point { + u16 dclk, t_rp, t_rdpre, t_rc, t_ras, t_rcd; +}; + +struct intel_psf_gv_point { + u8 clk; /* clock in multiples of 16.6666 MHz */ +}; + +struct intel_qgv_info { + struct intel_qgv_point points[I915_NUM_QGV_POINTS]; + struct intel_psf_gv_point psf_points[I915_NUM_PSF_GV_POINTS]; + u8 num_points; + u8 num_psf_points; + u8 t_bl; + u8 max_numchannels; + u8 channel_width; + u8 deinterleave; +}; + +static int dg1_mchbar_read_qgv_point_info(struct drm_i915_private *dev_priv, + struct intel_qgv_point *sp, + int point) +{ + u32 dclk_ratio, dclk_reference; + u32 val; + + val = intel_uncore_read(&dev_priv->uncore, SA_PERF_STATUS_0_0_0_MCHBAR_PC); + dclk_ratio = REG_FIELD_GET(DG1_QCLK_RATIO_MASK, val); + if (val & DG1_QCLK_REFERENCE) + dclk_reference = 6; /* 6 * 16.666 MHz = 100 MHz */ + else + dclk_reference = 8; /* 8 * 16.666 MHz = 133 MHz */ + sp->dclk = DIV_ROUND_UP((16667 * dclk_ratio * dclk_reference) + 500, 1000); + + val = intel_uncore_read(&dev_priv->uncore, SKL_MC_BIOS_DATA_0_0_0_MCHBAR_PCU); + if (val & DG1_GEAR_TYPE) + sp->dclk *= 2; + + if (sp->dclk == 0) + return -EINVAL; + + val = intel_uncore_read(&dev_priv->uncore, MCHBAR_CH0_CR_TC_PRE_0_0_0_MCHBAR); + sp->t_rp = REG_FIELD_GET(DG1_DRAM_T_RP_MASK, val); + sp->t_rdpre = REG_FIELD_GET(DG1_DRAM_T_RDPRE_MASK, val); + + val = intel_uncore_read(&dev_priv->uncore, MCHBAR_CH0_CR_TC_PRE_0_0_0_MCHBAR_HIGH); + sp->t_rcd = REG_FIELD_GET(DG1_DRAM_T_RCD_MASK, val); + sp->t_ras = REG_FIELD_GET(DG1_DRAM_T_RAS_MASK, val); + + sp->t_rc = sp->t_rp + sp->t_ras; + + return 0; +} + +static int icl_pcode_read_qgv_point_info(struct drm_i915_private *dev_priv, + struct intel_qgv_point *sp, + int point) +{ + u32 val = 0, val2 = 0; + u16 dclk; + int ret; + + ret = snb_pcode_read(&dev_priv->uncore, ICL_PCODE_MEM_SUBSYSYSTEM_INFO | + ICL_PCODE_MEM_SS_READ_QGV_POINT_INFO(point), + &val, &val2); + if (ret) + return ret; + + dclk = val & 0xffff; + sp->dclk = DIV_ROUND_UP((16667 * dclk) + (DISPLAY_VER(dev_priv) > 11 ? 500 : 0), 1000); + sp->t_rp = (val & 0xff0000) >> 16; + sp->t_rcd = (val & 0xff000000) >> 24; + + sp->t_rdpre = val2 & 0xff; + sp->t_ras = (val2 & 0xff00) >> 8; + + sp->t_rc = sp->t_rp + sp->t_ras; + + return 0; +} + +static int adls_pcode_read_psf_gv_point_info(struct drm_i915_private *dev_priv, + struct intel_psf_gv_point *points) +{ + u32 val = 0; + int ret; + int i; + + ret = snb_pcode_read(&dev_priv->uncore, ICL_PCODE_MEM_SUBSYSYSTEM_INFO | + ADL_PCODE_MEM_SS_READ_PSF_GV_INFO, &val, NULL); + if (ret) + return ret; + + for (i = 0; i < I915_NUM_PSF_GV_POINTS; i++) { + points[i].clk = val & 0xff; + val >>= 8; + } + + return 0; +} + +int icl_pcode_restrict_qgv_points(struct drm_i915_private *dev_priv, + u32 points_mask) +{ + int ret; + + /* bspec says to keep retrying for at least 1 ms */ + ret = skl_pcode_request(&dev_priv->uncore, ICL_PCODE_SAGV_DE_MEM_SS_CONFIG, + points_mask, + ICL_PCODE_REP_QGV_MASK | ADLS_PCODE_REP_PSF_MASK, + ICL_PCODE_REP_QGV_SAFE | ADLS_PCODE_REP_PSF_SAFE, + 1); + + if (ret < 0) { + drm_err(&dev_priv->drm, "Failed to disable qgv points (%d) points: 0x%x\n", ret, points_mask); + return ret; + } + + return 0; +} + +static int mtl_read_qgv_point_info(struct drm_i915_private *dev_priv, + struct intel_qgv_point *sp, int point) +{ + u32 val, val2; + u16 dclk; + + val = intel_uncore_read(&dev_priv->uncore, + MTL_MEM_SS_INFO_QGV_POINT_LOW(point)); + val2 = intel_uncore_read(&dev_priv->uncore, + MTL_MEM_SS_INFO_QGV_POINT_HIGH(point)); + dclk = REG_FIELD_GET(MTL_DCLK_MASK, val); + sp->dclk = DIV_ROUND_UP((16667 * dclk), 1000); + sp->t_rp = REG_FIELD_GET(MTL_TRP_MASK, val); + sp->t_rcd = REG_FIELD_GET(MTL_TRCD_MASK, val); + + sp->t_rdpre = REG_FIELD_GET(MTL_TRDPRE_MASK, val2); + sp->t_ras = REG_FIELD_GET(MTL_TRAS_MASK, val2); + + sp->t_rc = sp->t_rp + sp->t_ras; + + return 0; +} + +static int +intel_read_qgv_point_info(struct drm_i915_private *dev_priv, + struct intel_qgv_point *sp, + int point) +{ + if (DISPLAY_VER(dev_priv) >= 14) + return mtl_read_qgv_point_info(dev_priv, sp, point); + else if (IS_DG1(dev_priv)) + return dg1_mchbar_read_qgv_point_info(dev_priv, sp, point); + else + return icl_pcode_read_qgv_point_info(dev_priv, sp, point); +} + +static int icl_get_qgv_points(struct drm_i915_private *dev_priv, + struct intel_qgv_info *qi, + bool is_y_tile) +{ + const struct dram_info *dram_info = &dev_priv->dram_info; + int i, ret; + + qi->num_points = dram_info->num_qgv_points; + qi->num_psf_points = dram_info->num_psf_gv_points; + + if (DISPLAY_VER(dev_priv) >= 14) { + switch (dram_info->type) { + case INTEL_DRAM_DDR4: + qi->t_bl = 4; + qi->max_numchannels = 2; + qi->channel_width = 64; + qi->deinterleave = 2; + break; + case INTEL_DRAM_DDR5: + qi->t_bl = 8; + qi->max_numchannels = 4; + qi->channel_width = 32; + qi->deinterleave = 2; + break; + case INTEL_DRAM_LPDDR4: + case INTEL_DRAM_LPDDR5: + qi->t_bl = 16; + qi->max_numchannels = 8; + qi->channel_width = 16; + qi->deinterleave = 4; + break; + default: + MISSING_CASE(dram_info->type); + return -EINVAL; + } + } else if (DISPLAY_VER(dev_priv) >= 12) { + switch (dram_info->type) { + case INTEL_DRAM_DDR4: + qi->t_bl = is_y_tile ? 8 : 4; + qi->max_numchannels = 2; + qi->channel_width = 64; + qi->deinterleave = is_y_tile ? 1 : 2; + break; + case INTEL_DRAM_DDR5: + qi->t_bl = is_y_tile ? 16 : 8; + qi->max_numchannels = 4; + qi->channel_width = 32; + qi->deinterleave = is_y_tile ? 1 : 2; + break; + case INTEL_DRAM_LPDDR4: + if (IS_ROCKETLAKE(dev_priv)) { + qi->t_bl = 8; + qi->max_numchannels = 4; + qi->channel_width = 32; + qi->deinterleave = 2; + break; + } + fallthrough; + case INTEL_DRAM_LPDDR5: + qi->t_bl = 16; + qi->max_numchannels = 8; + qi->channel_width = 16; + qi->deinterleave = is_y_tile ? 2 : 4; + break; + default: + qi->t_bl = 16; + qi->max_numchannels = 1; + break; + } + } else if (DISPLAY_VER(dev_priv) == 11) { + qi->t_bl = dev_priv->dram_info.type == INTEL_DRAM_DDR4 ? 4 : 8; + qi->max_numchannels = 1; + } + + if (drm_WARN_ON(&dev_priv->drm, + qi->num_points > ARRAY_SIZE(qi->points))) + qi->num_points = ARRAY_SIZE(qi->points); + + for (i = 0; i < qi->num_points; i++) { + struct intel_qgv_point *sp = &qi->points[i]; + + ret = intel_read_qgv_point_info(dev_priv, sp, i); + if (ret) + return ret; + + drm_dbg_kms(&dev_priv->drm, + "QGV %d: DCLK=%d tRP=%d tRDPRE=%d tRAS=%d tRCD=%d tRC=%d\n", + i, sp->dclk, sp->t_rp, sp->t_rdpre, sp->t_ras, + sp->t_rcd, sp->t_rc); + } + + if (qi->num_psf_points > 0) { + ret = adls_pcode_read_psf_gv_point_info(dev_priv, qi->psf_points); + if (ret) { + drm_err(&dev_priv->drm, "Failed to read PSF point data; PSF points will not be considered in bandwidth calculations.\n"); + qi->num_psf_points = 0; + } + + for (i = 0; i < qi->num_psf_points; i++) + drm_dbg_kms(&dev_priv->drm, + "PSF GV %d: CLK=%d \n", + i, qi->psf_points[i].clk); + } + + return 0; +} + +static int adl_calc_psf_bw(int clk) +{ + /* + * clk is multiples of 16.666MHz (100/6) + * According to BSpec PSF GV bandwidth is + * calculated as BW = 64 * clk * 16.666Mhz + */ + return DIV_ROUND_CLOSEST(64 * clk * 100, 6); +} + +static int icl_sagv_max_dclk(const struct intel_qgv_info *qi) +{ + u16 dclk = 0; + int i; + + for (i = 0; i < qi->num_points; i++) + dclk = max(dclk, qi->points[i].dclk); + + return dclk; +} + +struct intel_sa_info { + u16 displayrtids; + u8 deburst, deprogbwlimit, derating; +}; + +static const struct intel_sa_info icl_sa_info = { + .deburst = 8, + .deprogbwlimit = 25, /* GB/s */ + .displayrtids = 128, + .derating = 10, +}; + +static const struct intel_sa_info tgl_sa_info = { + .deburst = 16, + .deprogbwlimit = 34, /* GB/s */ + .displayrtids = 256, + .derating = 10, +}; + +static const struct intel_sa_info rkl_sa_info = { + .deburst = 8, + .deprogbwlimit = 20, /* GB/s */ + .displayrtids = 128, + .derating = 10, +}; + +static const struct intel_sa_info adls_sa_info = { + .deburst = 16, + .deprogbwlimit = 38, /* GB/s */ + .displayrtids = 256, + .derating = 10, +}; + +static const struct intel_sa_info adlp_sa_info = { + .deburst = 16, + .deprogbwlimit = 38, /* GB/s */ + .displayrtids = 256, + .derating = 20, +}; + +static const struct intel_sa_info mtl_sa_info = { + .deburst = 32, + .deprogbwlimit = 38, /* GB/s */ + .displayrtids = 256, + .derating = 20, +}; + +static int icl_get_bw_info(struct drm_i915_private *dev_priv, const struct intel_sa_info *sa) +{ + struct intel_qgv_info qi = {}; + bool is_y_tile = true; /* assume y tile may be used */ + int num_channels = max_t(u8, 1, dev_priv->dram_info.num_channels); + int ipqdepth, ipqdepthpch = 16; + int dclk_max; + int maxdebw; + int num_groups = ARRAY_SIZE(dev_priv->display.bw.max); + int i, ret; + + ret = icl_get_qgv_points(dev_priv, &qi, is_y_tile); + if (ret) { + drm_dbg_kms(&dev_priv->drm, + "Failed to get memory subsystem information, ignoring bandwidth limits"); + return ret; + } + + dclk_max = icl_sagv_max_dclk(&qi); + maxdebw = min(sa->deprogbwlimit * 1000, dclk_max * 16 * 6 / 10); + ipqdepth = min(ipqdepthpch, sa->displayrtids / num_channels); + qi.deinterleave = DIV_ROUND_UP(num_channels, is_y_tile ? 4 : 2); + + for (i = 0; i < num_groups; i++) { + struct intel_bw_info *bi = &dev_priv->display.bw.max[i]; + int clpchgroup; + int j; + + clpchgroup = (sa->deburst * qi.deinterleave / num_channels) << i; + bi->num_planes = (ipqdepth - clpchgroup) / clpchgroup + 1; + + bi->num_qgv_points = qi.num_points; + bi->num_psf_gv_points = qi.num_psf_points; + + for (j = 0; j < qi.num_points; j++) { + const struct intel_qgv_point *sp = &qi.points[j]; + int ct, bw; + + /* + * Max row cycle time + * + * FIXME what is the logic behind the + * assumed burst length? + */ + ct = max_t(int, sp->t_rc, sp->t_rp + sp->t_rcd + + (clpchgroup - 1) * qi.t_bl + sp->t_rdpre); + bw = DIV_ROUND_UP(sp->dclk * clpchgroup * 32 * num_channels, ct); + + bi->deratedbw[j] = min(maxdebw, + bw * (100 - sa->derating) / 100); + + drm_dbg_kms(&dev_priv->drm, + "BW%d / QGV %d: num_planes=%d deratedbw=%u\n", + i, j, bi->num_planes, bi->deratedbw[j]); + } + } + /* + * In case if SAGV is disabled in BIOS, we always get 1 + * SAGV point, but we can't send PCode commands to restrict it + * as it will fail and pointless anyway. + */ + if (qi.num_points == 1) + dev_priv->display.sagv.status = I915_SAGV_NOT_CONTROLLED; + else + dev_priv->display.sagv.status = I915_SAGV_ENABLED; + + return 0; +} + +static int tgl_get_bw_info(struct drm_i915_private *dev_priv, const struct intel_sa_info *sa) +{ + struct intel_qgv_info qi = {}; + const struct dram_info *dram_info = &dev_priv->dram_info; + bool is_y_tile = true; /* assume y tile may be used */ + int num_channels = max_t(u8, 1, dev_priv->dram_info.num_channels); + int ipqdepth, ipqdepthpch = 16; + int dclk_max; + int maxdebw, peakbw; + int clperchgroup; + int num_groups = ARRAY_SIZE(dev_priv->display.bw.max); + int i, ret; + + ret = icl_get_qgv_points(dev_priv, &qi, is_y_tile); + if (ret) { + drm_dbg_kms(&dev_priv->drm, + "Failed to get memory subsystem information, ignoring bandwidth limits"); + return ret; + } + + if (dram_info->type == INTEL_DRAM_LPDDR4 || dram_info->type == INTEL_DRAM_LPDDR5) + num_channels *= 2; + + qi.deinterleave = qi.deinterleave ? : DIV_ROUND_UP(num_channels, is_y_tile ? 4 : 2); + + if (num_channels < qi.max_numchannels && DISPLAY_VER(dev_priv) >= 12) + qi.deinterleave = max(DIV_ROUND_UP(qi.deinterleave, 2), 1); + + if (DISPLAY_VER(dev_priv) > 11 && num_channels > qi.max_numchannels) + drm_warn(&dev_priv->drm, "Number of channels exceeds max number of channels."); + if (qi.max_numchannels != 0) + num_channels = min_t(u8, num_channels, qi.max_numchannels); + + dclk_max = icl_sagv_max_dclk(&qi); + + peakbw = num_channels * DIV_ROUND_UP(qi.channel_width, 8) * dclk_max; + maxdebw = min(sa->deprogbwlimit * 1000, peakbw * 6 / 10); /* 60% */ + + ipqdepth = min(ipqdepthpch, sa->displayrtids / num_channels); + /* + * clperchgroup = 4kpagespermempage * clperchperblock, + * clperchperblock = 8 / num_channels * interleave + */ + clperchgroup = 4 * DIV_ROUND_UP(8, num_channels) * qi.deinterleave; + + for (i = 0; i < num_groups; i++) { + struct intel_bw_info *bi = &dev_priv->display.bw.max[i]; + struct intel_bw_info *bi_next; + int clpchgroup; + int j; + + clpchgroup = (sa->deburst * qi.deinterleave / num_channels) << i; + + if (i < num_groups - 1) { + bi_next = &dev_priv->display.bw.max[i + 1]; + + if (clpchgroup < clperchgroup) + bi_next->num_planes = (ipqdepth - clpchgroup) / + clpchgroup + 1; + else + bi_next->num_planes = 0; + } + + bi->num_qgv_points = qi.num_points; + bi->num_psf_gv_points = qi.num_psf_points; + + for (j = 0; j < qi.num_points; j++) { + const struct intel_qgv_point *sp = &qi.points[j]; + int ct, bw; + + /* + * Max row cycle time + * + * FIXME what is the logic behind the + * assumed burst length? + */ + ct = max_t(int, sp->t_rc, sp->t_rp + sp->t_rcd + + (clpchgroup - 1) * qi.t_bl + sp->t_rdpre); + bw = DIV_ROUND_UP(sp->dclk * clpchgroup * 32 * num_channels, ct); + + bi->deratedbw[j] = min(maxdebw, + bw * (100 - sa->derating) / 100); + + drm_dbg_kms(&dev_priv->drm, + "BW%d / QGV %d: num_planes=%d deratedbw=%u\n", + i, j, bi->num_planes, bi->deratedbw[j]); + } + + for (j = 0; j < qi.num_psf_points; j++) { + const struct intel_psf_gv_point *sp = &qi.psf_points[j]; + + bi->psf_bw[j] = adl_calc_psf_bw(sp->clk); + + drm_dbg_kms(&dev_priv->drm, + "BW%d / PSF GV %d: num_planes=%d bw=%u\n", + i, j, bi->num_planes, bi->psf_bw[j]); + } + } + + /* + * In case if SAGV is disabled in BIOS, we always get 1 + * SAGV point, but we can't send PCode commands to restrict it + * as it will fail and pointless anyway. + */ + if (qi.num_points == 1) + dev_priv->display.sagv.status = I915_SAGV_NOT_CONTROLLED; + else + dev_priv->display.sagv.status = I915_SAGV_ENABLED; + + return 0; +} + +static void dg2_get_bw_info(struct drm_i915_private *i915) +{ + unsigned int deratedbw = IS_DG2_G11(i915) ? 38000 : 50000; + int num_groups = ARRAY_SIZE(i915->display.bw.max); + int i; + + /* + * DG2 doesn't have SAGV or QGV points, just a constant max bandwidth + * that doesn't depend on the number of planes enabled. So fill all the + * plane group with constant bw information for uniformity with other + * platforms. DG2-G10 platforms have a constant 50 GB/s bandwidth, + * whereas DG2-G11 platforms have 38 GB/s. + */ + for (i = 0; i < num_groups; i++) { + struct intel_bw_info *bi = &i915->display.bw.max[i]; + + bi->num_planes = 1; + /* Need only one dummy QGV point per group */ + bi->num_qgv_points = 1; + bi->deratedbw[0] = deratedbw; + } + + i915->display.sagv.status = I915_SAGV_NOT_CONTROLLED; +} + +static unsigned int icl_max_bw(struct drm_i915_private *dev_priv, + int num_planes, int qgv_point) +{ + int i; + + /* + * Let's return max bw for 0 planes + */ + num_planes = max(1, num_planes); + + for (i = 0; i < ARRAY_SIZE(dev_priv->display.bw.max); i++) { + const struct intel_bw_info *bi = + &dev_priv->display.bw.max[i]; + + /* + * Pcode will not expose all QGV points when + * SAGV is forced to off/min/med/max. + */ + if (qgv_point >= bi->num_qgv_points) + return UINT_MAX; + + if (num_planes >= bi->num_planes) + return bi->deratedbw[qgv_point]; + } + + return 0; +} + +static unsigned int tgl_max_bw(struct drm_i915_private *dev_priv, + int num_planes, int qgv_point) +{ + int i; + + /* + * Let's return max bw for 0 planes + */ + num_planes = max(1, num_planes); + + for (i = ARRAY_SIZE(dev_priv->display.bw.max) - 1; i >= 0; i--) { + const struct intel_bw_info *bi = + &dev_priv->display.bw.max[i]; + + /* + * Pcode will not expose all QGV points when + * SAGV is forced to off/min/med/max. + */ + if (qgv_point >= bi->num_qgv_points) + return UINT_MAX; + + if (num_planes <= bi->num_planes) + return bi->deratedbw[qgv_point]; + } + + return dev_priv->display.bw.max[0].deratedbw[qgv_point]; +} + +static unsigned int adl_psf_bw(struct drm_i915_private *dev_priv, + int psf_gv_point) +{ + const struct intel_bw_info *bi = + &dev_priv->display.bw.max[0]; + + return bi->psf_bw[psf_gv_point]; +} + +void intel_bw_init_hw(struct drm_i915_private *dev_priv) +{ + if (!HAS_DISPLAY(dev_priv)) + return; + + if (DISPLAY_VER(dev_priv) >= 14) + tgl_get_bw_info(dev_priv, &mtl_sa_info); + else if (IS_DG2(dev_priv)) + dg2_get_bw_info(dev_priv); + else if (IS_ALDERLAKE_P(dev_priv)) + tgl_get_bw_info(dev_priv, &adlp_sa_info); + else if (IS_ALDERLAKE_S(dev_priv)) + tgl_get_bw_info(dev_priv, &adls_sa_info); + else if (IS_ROCKETLAKE(dev_priv)) + tgl_get_bw_info(dev_priv, &rkl_sa_info); + else if (DISPLAY_VER(dev_priv) == 12) + tgl_get_bw_info(dev_priv, &tgl_sa_info); + else if (DISPLAY_VER(dev_priv) == 11) + icl_get_bw_info(dev_priv, &icl_sa_info); +} + +static unsigned int intel_bw_crtc_num_active_planes(const struct intel_crtc_state *crtc_state) +{ + /* + * We assume cursors are small enough + * to not not cause bandwidth problems. + */ + return hweight8(crtc_state->active_planes & ~BIT(PLANE_CURSOR)); +} + +static unsigned int intel_bw_crtc_data_rate(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + unsigned int data_rate = 0; + enum plane_id plane_id; + + for_each_plane_id_on_crtc(crtc, plane_id) { + /* + * We assume cursors are small enough + * to not not cause bandwidth problems. + */ + if (plane_id == PLANE_CURSOR) + continue; + + data_rate += crtc_state->data_rate[plane_id]; + + if (DISPLAY_VER(i915) < 11) + data_rate += crtc_state->data_rate_y[plane_id]; + } + + return data_rate; +} + +/* "Maximum Pipe Read Bandwidth" */ +static int intel_bw_crtc_min_cdclk(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + + if (DISPLAY_VER(i915) < 12) + return 0; + + return DIV_ROUND_UP_ULL(mul_u32_u32(intel_bw_crtc_data_rate(crtc_state), 10), 512); +} + +void intel_bw_crtc_update(struct intel_bw_state *bw_state, + const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + + bw_state->data_rate[crtc->pipe] = + intel_bw_crtc_data_rate(crtc_state); + bw_state->num_active_planes[crtc->pipe] = + intel_bw_crtc_num_active_planes(crtc_state); + + drm_dbg_kms(&i915->drm, "pipe %c data rate %u num active planes %u\n", + pipe_name(crtc->pipe), + bw_state->data_rate[crtc->pipe], + bw_state->num_active_planes[crtc->pipe]); +} + +static unsigned int intel_bw_num_active_planes(struct drm_i915_private *dev_priv, + const struct intel_bw_state *bw_state) +{ + unsigned int num_active_planes = 0; + enum pipe pipe; + + for_each_pipe(dev_priv, pipe) + num_active_planes += bw_state->num_active_planes[pipe]; + + return num_active_planes; +} + +static unsigned int intel_bw_data_rate(struct drm_i915_private *dev_priv, + const struct intel_bw_state *bw_state) +{ + unsigned int data_rate = 0; + enum pipe pipe; + + for_each_pipe(dev_priv, pipe) + data_rate += bw_state->data_rate[pipe]; + + if (DISPLAY_VER(dev_priv) >= 13 && i915_vtd_active(dev_priv)) + data_rate = DIV_ROUND_UP(data_rate * 105, 100); + + return data_rate; +} + +struct intel_bw_state * +intel_atomic_get_old_bw_state(struct intel_atomic_state *state) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_global_state *bw_state; + + bw_state = intel_atomic_get_old_global_obj_state(state, &dev_priv->display.bw.obj); + + return to_intel_bw_state(bw_state); +} + +struct intel_bw_state * +intel_atomic_get_new_bw_state(struct intel_atomic_state *state) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_global_state *bw_state; + + bw_state = intel_atomic_get_new_global_obj_state(state, &dev_priv->display.bw.obj); + + return to_intel_bw_state(bw_state); +} + +struct intel_bw_state * +intel_atomic_get_bw_state(struct intel_atomic_state *state) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_global_state *bw_state; + + bw_state = intel_atomic_get_global_obj_state(state, &dev_priv->display.bw.obj); + if (IS_ERR(bw_state)) + return ERR_CAST(bw_state); + + return to_intel_bw_state(bw_state); +} + +static bool intel_bw_state_changed(struct drm_i915_private *i915, + const struct intel_bw_state *old_bw_state, + const struct intel_bw_state *new_bw_state) +{ + enum pipe pipe; + + for_each_pipe(i915, pipe) { + const struct intel_dbuf_bw *old_crtc_bw = + &old_bw_state->dbuf_bw[pipe]; + const struct intel_dbuf_bw *new_crtc_bw = + &new_bw_state->dbuf_bw[pipe]; + enum dbuf_slice slice; + + for_each_dbuf_slice(i915, slice) { + if (old_crtc_bw->max_bw[slice] != new_crtc_bw->max_bw[slice] || + old_crtc_bw->active_planes[slice] != new_crtc_bw->active_planes[slice]) + return true; + } + + if (old_bw_state->min_cdclk[pipe] != new_bw_state->min_cdclk[pipe]) + return true; + } + + return false; +} + +static void skl_plane_calc_dbuf_bw(struct intel_bw_state *bw_state, + struct intel_crtc *crtc, + enum plane_id plane_id, + const struct skl_ddb_entry *ddb, + unsigned int data_rate) +{ + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + struct intel_dbuf_bw *crtc_bw = &bw_state->dbuf_bw[crtc->pipe]; + unsigned int dbuf_mask = skl_ddb_dbuf_slice_mask(i915, ddb); + enum dbuf_slice slice; + + /* + * The arbiter can only really guarantee an + * equal share of the total bw to each plane. + */ + for_each_dbuf_slice_in_mask(i915, slice, dbuf_mask) { + crtc_bw->max_bw[slice] = max(crtc_bw->max_bw[slice], data_rate); + crtc_bw->active_planes[slice] |= BIT(plane_id); + } +} + +static void skl_crtc_calc_dbuf_bw(struct intel_bw_state *bw_state, + const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + struct intel_dbuf_bw *crtc_bw = &bw_state->dbuf_bw[crtc->pipe]; + enum plane_id plane_id; + + memset(crtc_bw, 0, sizeof(*crtc_bw)); + + if (!crtc_state->hw.active) + return; + + for_each_plane_id_on_crtc(crtc, plane_id) { + /* + * We assume cursors are small enough + * to not cause bandwidth problems. + */ + if (plane_id == PLANE_CURSOR) + continue; + + skl_plane_calc_dbuf_bw(bw_state, crtc, plane_id, + &crtc_state->wm.skl.plane_ddb[plane_id], + crtc_state->data_rate[plane_id]); + + if (DISPLAY_VER(i915) < 11) + skl_plane_calc_dbuf_bw(bw_state, crtc, plane_id, + &crtc_state->wm.skl.plane_ddb_y[plane_id], + crtc_state->data_rate[plane_id]); + } +} + +/* "Maximum Data Buffer Bandwidth" */ +static int +intel_bw_dbuf_min_cdclk(struct drm_i915_private *i915, + const struct intel_bw_state *bw_state) +{ + unsigned int total_max_bw = 0; + enum dbuf_slice slice; + + for_each_dbuf_slice(i915, slice) { + int num_active_planes = 0; + unsigned int max_bw = 0; + enum pipe pipe; + + /* + * The arbiter can only really guarantee an + * equal share of the total bw to each plane. + */ + for_each_pipe(i915, pipe) { + const struct intel_dbuf_bw *crtc_bw = &bw_state->dbuf_bw[pipe]; + + max_bw = max(crtc_bw->max_bw[slice], max_bw); + num_active_planes += hweight8(crtc_bw->active_planes[slice]); + } + max_bw *= num_active_planes; + + total_max_bw = max(total_max_bw, max_bw); + } + + return DIV_ROUND_UP(total_max_bw, 64); +} + +int intel_bw_min_cdclk(struct drm_i915_private *i915, + const struct intel_bw_state *bw_state) +{ + enum pipe pipe; + int min_cdclk; + + min_cdclk = intel_bw_dbuf_min_cdclk(i915, bw_state); + + for_each_pipe(i915, pipe) + min_cdclk = max(bw_state->min_cdclk[pipe], min_cdclk); + + return min_cdclk; +} + +int intel_bw_calc_min_cdclk(struct intel_atomic_state *state, + bool *need_cdclk_calc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_bw_state *new_bw_state = NULL; + const struct intel_bw_state *old_bw_state = NULL; + const struct intel_cdclk_state *cdclk_state; + const struct intel_crtc_state *crtc_state; + int old_min_cdclk, new_min_cdclk; + struct intel_crtc *crtc; + int i; + + if (DISPLAY_VER(dev_priv) < 9) + return 0; + + for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) { + new_bw_state = intel_atomic_get_bw_state(state); + if (IS_ERR(new_bw_state)) + return PTR_ERR(new_bw_state); + + old_bw_state = intel_atomic_get_old_bw_state(state); + + skl_crtc_calc_dbuf_bw(new_bw_state, crtc_state); + + new_bw_state->min_cdclk[crtc->pipe] = + intel_bw_crtc_min_cdclk(crtc_state); + } + + if (!old_bw_state) + return 0; + + if (intel_bw_state_changed(dev_priv, old_bw_state, new_bw_state)) { + int ret = intel_atomic_lock_global_state(&new_bw_state->base); + if (ret) + return ret; + } + + old_min_cdclk = intel_bw_min_cdclk(dev_priv, old_bw_state); + new_min_cdclk = intel_bw_min_cdclk(dev_priv, new_bw_state); + + /* + * No need to check against the cdclk state if + * the min cdclk doesn't increase. + * + * Ie. we only ever increase the cdclk due to bandwidth + * requirements. This can reduce back and forth + * display blinking due to constant cdclk changes. + */ + if (new_min_cdclk <= old_min_cdclk) + return 0; + + cdclk_state = intel_atomic_get_cdclk_state(state); + if (IS_ERR(cdclk_state)) + return PTR_ERR(cdclk_state); + + /* + * No need to recalculate the cdclk state if + * the min cdclk doesn't increase. + * + * Ie. we only ever increase the cdclk due to bandwidth + * requirements. This can reduce back and forth + * display blinking due to constant cdclk changes. + */ + if (new_min_cdclk <= cdclk_state->bw_min_cdclk) + return 0; + + drm_dbg_kms(&dev_priv->drm, + "new bandwidth min cdclk (%d kHz) > old min cdclk (%d kHz)\n", + new_min_cdclk, cdclk_state->bw_min_cdclk); + *need_cdclk_calc = true; + + return 0; +} + +static u16 icl_qgv_points_mask(struct drm_i915_private *i915) +{ + unsigned int num_psf_gv_points = i915->display.bw.max[0].num_psf_gv_points; + unsigned int num_qgv_points = i915->display.bw.max[0].num_qgv_points; + u16 qgv_points = 0, psf_points = 0; + + /* + * We can _not_ use the whole ADLS_QGV_PT_MASK here, as PCode rejects + * it with failure if we try masking any unadvertised points. + * So need to operate only with those returned from PCode. + */ + if (num_qgv_points > 0) + qgv_points = GENMASK(num_qgv_points - 1, 0); + + if (num_psf_gv_points > 0) + psf_points = GENMASK(num_psf_gv_points - 1, 0); + + return ICL_PCODE_REQ_QGV_PT(qgv_points) | ADLS_PCODE_REQ_PSF_PT(psf_points); +} + +static int intel_bw_check_data_rate(struct intel_atomic_state *state, bool *changed) +{ + struct drm_i915_private *i915 = to_i915(state->base.dev); + const struct intel_crtc_state *new_crtc_state, *old_crtc_state; + struct intel_crtc *crtc; + int i; + + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, + new_crtc_state, i) { + unsigned int old_data_rate = + intel_bw_crtc_data_rate(old_crtc_state); + unsigned int new_data_rate = + intel_bw_crtc_data_rate(new_crtc_state); + unsigned int old_active_planes = + intel_bw_crtc_num_active_planes(old_crtc_state); + unsigned int new_active_planes = + intel_bw_crtc_num_active_planes(new_crtc_state); + struct intel_bw_state *new_bw_state; + + /* + * Avoid locking the bw state when + * nothing significant has changed. + */ + if (old_data_rate == new_data_rate && + old_active_planes == new_active_planes) + continue; + + new_bw_state = intel_atomic_get_bw_state(state); + if (IS_ERR(new_bw_state)) + return PTR_ERR(new_bw_state); + + new_bw_state->data_rate[crtc->pipe] = new_data_rate; + new_bw_state->num_active_planes[crtc->pipe] = new_active_planes; + + *changed = true; + + drm_dbg_kms(&i915->drm, + "[CRTC:%d:%s] data rate %u num active planes %u\n", + crtc->base.base.id, crtc->base.name, + new_bw_state->data_rate[crtc->pipe], + new_bw_state->num_active_planes[crtc->pipe]); + } + + return 0; +} + +int intel_bw_atomic_check(struct intel_atomic_state *state) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + const struct intel_bw_state *old_bw_state; + struct intel_bw_state *new_bw_state; + unsigned int data_rate; + unsigned int num_active_planes; + int i, ret; + u16 qgv_points = 0, psf_points = 0; + unsigned int max_bw_point = 0, max_bw = 0; + unsigned int num_qgv_points = dev_priv->display.bw.max[0].num_qgv_points; + unsigned int num_psf_gv_points = dev_priv->display.bw.max[0].num_psf_gv_points; + bool changed = false; + + /* FIXME earlier gens need some checks too */ + if (DISPLAY_VER(dev_priv) < 11) + return 0; + + ret = intel_bw_check_data_rate(state, &changed); + if (ret) + return ret; + + old_bw_state = intel_atomic_get_old_bw_state(state); + new_bw_state = intel_atomic_get_new_bw_state(state); + + if (new_bw_state && + intel_can_enable_sagv(dev_priv, old_bw_state) != + intel_can_enable_sagv(dev_priv, new_bw_state)) + changed = true; + + /* + * If none of our inputs (data rates, number of active + * planes, SAGV yes/no) changed then nothing to do here. + */ + if (!changed) + return 0; + + ret = intel_atomic_lock_global_state(&new_bw_state->base); + if (ret) + return ret; + + data_rate = intel_bw_data_rate(dev_priv, new_bw_state); + data_rate = DIV_ROUND_UP(data_rate, 1000); + + num_active_planes = intel_bw_num_active_planes(dev_priv, new_bw_state); + + for (i = 0; i < num_qgv_points; i++) { + unsigned int max_data_rate; + + if (DISPLAY_VER(dev_priv) > 11) + max_data_rate = tgl_max_bw(dev_priv, num_active_planes, i); + else + max_data_rate = icl_max_bw(dev_priv, num_active_planes, i); + /* + * We need to know which qgv point gives us + * maximum bandwidth in order to disable SAGV + * if we find that we exceed SAGV block time + * with watermarks. By that moment we already + * have those, as it is calculated earlier in + * intel_atomic_check, + */ + if (max_data_rate > max_bw) { + max_bw_point = i; + max_bw = max_data_rate; + } + if (max_data_rate >= data_rate) + qgv_points |= BIT(i); + + drm_dbg_kms(&dev_priv->drm, "QGV point %d: max bw %d required %d\n", + i, max_data_rate, data_rate); + } + + for (i = 0; i < num_psf_gv_points; i++) { + unsigned int max_data_rate = adl_psf_bw(dev_priv, i); + + if (max_data_rate >= data_rate) + psf_points |= BIT(i); + + drm_dbg_kms(&dev_priv->drm, "PSF GV point %d: max bw %d" + " required %d\n", + i, max_data_rate, data_rate); + } + + /* + * BSpec states that we always should have at least one allowed point + * left, so if we couldn't - simply reject the configuration for obvious + * reasons. + */ + if (qgv_points == 0) { + drm_dbg_kms(&dev_priv->drm, "No QGV points provide sufficient memory" + " bandwidth %d for display configuration(%d active planes).\n", + data_rate, num_active_planes); + return -EINVAL; + } + + if (num_psf_gv_points > 0 && psf_points == 0) { + drm_dbg_kms(&dev_priv->drm, "No PSF GV points provide sufficient memory" + " bandwidth %d for display configuration(%d active planes).\n", + data_rate, num_active_planes); + return -EINVAL; + } + + /* + * Leave only single point with highest bandwidth, if + * we can't enable SAGV due to the increased memory latency it may + * cause. + */ + if (!intel_can_enable_sagv(dev_priv, new_bw_state)) { + qgv_points = BIT(max_bw_point); + drm_dbg_kms(&dev_priv->drm, "No SAGV, using single QGV point %d\n", + max_bw_point); + } + + /* + * We store the ones which need to be masked as that is what PCode + * actually accepts as a parameter. + */ + new_bw_state->qgv_points_mask = + ~(ICL_PCODE_REQ_QGV_PT(qgv_points) | + ADLS_PCODE_REQ_PSF_PT(psf_points)) & + icl_qgv_points_mask(dev_priv); + + /* + * If the actual mask had changed we need to make sure that + * the commits are serialized(in case this is a nomodeset, nonblocking) + */ + if (new_bw_state->qgv_points_mask != old_bw_state->qgv_points_mask) { + ret = intel_atomic_serialize_global_state(&new_bw_state->base); + if (ret) + return ret; + } + + return 0; +} + +static struct intel_global_state * +intel_bw_duplicate_state(struct intel_global_obj *obj) +{ + struct intel_bw_state *state; + + state = kmemdup(obj->state, sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + + return &state->base; +} + +static void intel_bw_destroy_state(struct intel_global_obj *obj, + struct intel_global_state *state) +{ + kfree(state); +} + +static const struct intel_global_state_funcs intel_bw_funcs = { + .atomic_duplicate_state = intel_bw_duplicate_state, + .atomic_destroy_state = intel_bw_destroy_state, +}; + +int intel_bw_init(struct drm_i915_private *dev_priv) +{ + struct intel_bw_state *state; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + intel_atomic_global_obj_init(dev_priv, &dev_priv->display.bw.obj, + &state->base, &intel_bw_funcs); + + return 0; +} diff --git a/drivers/gpu/drm/i915/display/intel_bw.h b/drivers/gpu/drm/i915/display/intel_bw.h new file mode 100644 index 000000000..cb7ee3a24 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_bw.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_BW_H__ +#define __INTEL_BW_H__ + +#include + +#include "intel_display.h" +#include "intel_display_power.h" +#include "intel_global_state.h" + +struct drm_i915_private; +struct intel_atomic_state; +struct intel_crtc_state; + +struct intel_dbuf_bw { + unsigned int max_bw[I915_MAX_DBUF_SLICES]; + u8 active_planes[I915_MAX_DBUF_SLICES]; +}; + +struct intel_bw_state { + struct intel_global_state base; + struct intel_dbuf_bw dbuf_bw[I915_MAX_PIPES]; + + /* + * Contains a bit mask, used to determine, whether correspondent + * pipe allows SAGV or not. + */ + u8 pipe_sagv_reject; + + /* bitmask of active pipes */ + u8 active_pipes; + + /* + * Current QGV points mask, which restricts + * some particular SAGV states, not to confuse + * with pipe_sagv_mask. + */ + u16 qgv_points_mask; + + int min_cdclk[I915_MAX_PIPES]; + unsigned int data_rate[I915_MAX_PIPES]; + u8 num_active_planes[I915_MAX_PIPES]; +}; + +#define to_intel_bw_state(x) container_of((x), struct intel_bw_state, base) + +struct intel_bw_state * +intel_atomic_get_old_bw_state(struct intel_atomic_state *state); + +struct intel_bw_state * +intel_atomic_get_new_bw_state(struct intel_atomic_state *state); + +struct intel_bw_state * +intel_atomic_get_bw_state(struct intel_atomic_state *state); + +void intel_bw_init_hw(struct drm_i915_private *dev_priv); +int intel_bw_init(struct drm_i915_private *dev_priv); +int intel_bw_atomic_check(struct intel_atomic_state *state); +void intel_bw_crtc_update(struct intel_bw_state *bw_state, + const struct intel_crtc_state *crtc_state); +int icl_pcode_restrict_qgv_points(struct drm_i915_private *dev_priv, + u32 points_mask); +int intel_bw_calc_min_cdclk(struct intel_atomic_state *state, + bool *need_cdclk_calc); +int intel_bw_min_cdclk(struct drm_i915_private *i915, + const struct intel_bw_state *bw_state); + +#endif /* __INTEL_BW_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_cdclk.c b/drivers/gpu/drm/i915/display/intel_cdclk.c new file mode 100644 index 000000000..25dcdde5f --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_cdclk.c @@ -0,0 +1,3283 @@ +/* + * Copyright © 2006-2017 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include + +#include "hsw_ips.h" +#include "intel_atomic.h" +#include "intel_atomic_plane.h" +#include "intel_audio.h" +#include "intel_bw.h" +#include "intel_cdclk.h" +#include "intel_crtc.h" +#include "intel_de.h" +#include "intel_display_types.h" +#include "intel_mchbar_regs.h" +#include "intel_pci_config.h" +#include "intel_pcode.h" +#include "intel_psr.h" +#include "vlv_sideband.h" + +/** + * DOC: CDCLK / RAWCLK + * + * The display engine uses several different clocks to do its work. There + * are two main clocks involved that aren't directly related to the actual + * pixel clock or any symbol/bit clock of the actual output port. These + * are the core display clock (CDCLK) and RAWCLK. + * + * CDCLK clocks most of the display pipe logic, and thus its frequency + * must be high enough to support the rate at which pixels are flowing + * through the pipes. Downscaling must also be accounted as that increases + * the effective pixel rate. + * + * On several platforms the CDCLK frequency can be changed dynamically + * to minimize power consumption for a given display configuration. + * Typically changes to the CDCLK frequency require all the display pipes + * to be shut down while the frequency is being changed. + * + * On SKL+ the DMC will toggle the CDCLK off/on during DC5/6 entry/exit. + * DMC will not change the active CDCLK frequency however, so that part + * will still be performed by the driver directly. + * + * RAWCLK is a fixed frequency clock, often used by various auxiliary + * blocks such as AUX CH or backlight PWM. Hence the only thing we + * really need to know about RAWCLK is its frequency so that various + * dividers can be programmed correctly. + */ + +struct intel_cdclk_funcs { + void (*get_cdclk)(struct drm_i915_private *i915, + struct intel_cdclk_config *cdclk_config); + void (*set_cdclk)(struct drm_i915_private *i915, + const struct intel_cdclk_config *cdclk_config, + enum pipe pipe); + int (*modeset_calc_cdclk)(struct intel_cdclk_state *state); + u8 (*calc_voltage_level)(int cdclk); +}; + +void intel_cdclk_get_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + dev_priv->display.funcs.cdclk->get_cdclk(dev_priv, cdclk_config); +} + +static void intel_cdclk_set_cdclk(struct drm_i915_private *dev_priv, + const struct intel_cdclk_config *cdclk_config, + enum pipe pipe) +{ + dev_priv->display.funcs.cdclk->set_cdclk(dev_priv, cdclk_config, pipe); +} + +static int intel_cdclk_modeset_calc_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_state *cdclk_config) +{ + return dev_priv->display.funcs.cdclk->modeset_calc_cdclk(cdclk_config); +} + +static u8 intel_cdclk_calc_voltage_level(struct drm_i915_private *dev_priv, + int cdclk) +{ + return dev_priv->display.funcs.cdclk->calc_voltage_level(cdclk); +} + +static void fixed_133mhz_get_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + cdclk_config->cdclk = 133333; +} + +static void fixed_200mhz_get_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + cdclk_config->cdclk = 200000; +} + +static void fixed_266mhz_get_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + cdclk_config->cdclk = 266667; +} + +static void fixed_333mhz_get_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + cdclk_config->cdclk = 333333; +} + +static void fixed_400mhz_get_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + cdclk_config->cdclk = 400000; +} + +static void fixed_450mhz_get_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + cdclk_config->cdclk = 450000; +} + +static void i85x_get_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + struct pci_dev *pdev = to_pci_dev(dev_priv->drm.dev); + u16 hpllcc = 0; + + /* + * 852GM/852GMV only supports 133 MHz and the HPLLCC + * encoding is different :( + * FIXME is this the right way to detect 852GM/852GMV? + */ + if (pdev->revision == 0x1) { + cdclk_config->cdclk = 133333; + return; + } + + pci_bus_read_config_word(pdev->bus, + PCI_DEVFN(0, 3), HPLLCC, &hpllcc); + + /* Assume that the hardware is in the high speed state. This + * should be the default. + */ + switch (hpllcc & GC_CLOCK_CONTROL_MASK) { + case GC_CLOCK_133_200: + case GC_CLOCK_133_200_2: + case GC_CLOCK_100_200: + cdclk_config->cdclk = 200000; + break; + case GC_CLOCK_166_250: + cdclk_config->cdclk = 250000; + break; + case GC_CLOCK_100_133: + cdclk_config->cdclk = 133333; + break; + case GC_CLOCK_133_266: + case GC_CLOCK_133_266_2: + case GC_CLOCK_166_266: + cdclk_config->cdclk = 266667; + break; + } +} + +static void i915gm_get_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + struct pci_dev *pdev = to_pci_dev(dev_priv->drm.dev); + u16 gcfgc = 0; + + pci_read_config_word(pdev, GCFGC, &gcfgc); + + if (gcfgc & GC_LOW_FREQUENCY_ENABLE) { + cdclk_config->cdclk = 133333; + return; + } + + switch (gcfgc & GC_DISPLAY_CLOCK_MASK) { + case GC_DISPLAY_CLOCK_333_320_MHZ: + cdclk_config->cdclk = 333333; + break; + default: + case GC_DISPLAY_CLOCK_190_200_MHZ: + cdclk_config->cdclk = 190000; + break; + } +} + +static void i945gm_get_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + struct pci_dev *pdev = to_pci_dev(dev_priv->drm.dev); + u16 gcfgc = 0; + + pci_read_config_word(pdev, GCFGC, &gcfgc); + + if (gcfgc & GC_LOW_FREQUENCY_ENABLE) { + cdclk_config->cdclk = 133333; + return; + } + + switch (gcfgc & GC_DISPLAY_CLOCK_MASK) { + case GC_DISPLAY_CLOCK_333_320_MHZ: + cdclk_config->cdclk = 320000; + break; + default: + case GC_DISPLAY_CLOCK_190_200_MHZ: + cdclk_config->cdclk = 200000; + break; + } +} + +static unsigned int intel_hpll_vco(struct drm_i915_private *dev_priv) +{ + static const unsigned int blb_vco[8] = { + [0] = 3200000, + [1] = 4000000, + [2] = 5333333, + [3] = 4800000, + [4] = 6400000, + }; + static const unsigned int pnv_vco[8] = { + [0] = 3200000, + [1] = 4000000, + [2] = 5333333, + [3] = 4800000, + [4] = 2666667, + }; + static const unsigned int cl_vco[8] = { + [0] = 3200000, + [1] = 4000000, + [2] = 5333333, + [3] = 6400000, + [4] = 3333333, + [5] = 3566667, + [6] = 4266667, + }; + static const unsigned int elk_vco[8] = { + [0] = 3200000, + [1] = 4000000, + [2] = 5333333, + [3] = 4800000, + }; + static const unsigned int ctg_vco[8] = { + [0] = 3200000, + [1] = 4000000, + [2] = 5333333, + [3] = 6400000, + [4] = 2666667, + [5] = 4266667, + }; + const unsigned int *vco_table; + unsigned int vco; + u8 tmp = 0; + + /* FIXME other chipsets? */ + if (IS_GM45(dev_priv)) + vco_table = ctg_vco; + else if (IS_G45(dev_priv)) + vco_table = elk_vco; + else if (IS_I965GM(dev_priv)) + vco_table = cl_vco; + else if (IS_PINEVIEW(dev_priv)) + vco_table = pnv_vco; + else if (IS_G33(dev_priv)) + vco_table = blb_vco; + else + return 0; + + tmp = intel_de_read(dev_priv, + IS_PINEVIEW(dev_priv) || IS_MOBILE(dev_priv) ? HPLLVCO_MOBILE : HPLLVCO); + + vco = vco_table[tmp & 0x7]; + if (vco == 0) + drm_err(&dev_priv->drm, "Bad HPLL VCO (HPLLVCO=0x%02x)\n", + tmp); + else + drm_dbg_kms(&dev_priv->drm, "HPLL VCO %u kHz\n", vco); + + return vco; +} + +static void g33_get_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + struct pci_dev *pdev = to_pci_dev(dev_priv->drm.dev); + static const u8 div_3200[] = { 12, 10, 8, 7, 5, 16 }; + static const u8 div_4000[] = { 14, 12, 10, 8, 6, 20 }; + static const u8 div_4800[] = { 20, 14, 12, 10, 8, 24 }; + static const u8 div_5333[] = { 20, 16, 12, 12, 8, 28 }; + const u8 *div_table; + unsigned int cdclk_sel; + u16 tmp = 0; + + cdclk_config->vco = intel_hpll_vco(dev_priv); + + pci_read_config_word(pdev, GCFGC, &tmp); + + cdclk_sel = (tmp >> 4) & 0x7; + + if (cdclk_sel >= ARRAY_SIZE(div_3200)) + goto fail; + + switch (cdclk_config->vco) { + case 3200000: + div_table = div_3200; + break; + case 4000000: + div_table = div_4000; + break; + case 4800000: + div_table = div_4800; + break; + case 5333333: + div_table = div_5333; + break; + default: + goto fail; + } + + cdclk_config->cdclk = DIV_ROUND_CLOSEST(cdclk_config->vco, + div_table[cdclk_sel]); + return; + +fail: + drm_err(&dev_priv->drm, + "Unable to determine CDCLK. HPLL VCO=%u kHz, CFGC=0x%08x\n", + cdclk_config->vco, tmp); + cdclk_config->cdclk = 190476; +} + +static void pnv_get_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + struct pci_dev *pdev = to_pci_dev(dev_priv->drm.dev); + u16 gcfgc = 0; + + pci_read_config_word(pdev, GCFGC, &gcfgc); + + switch (gcfgc & GC_DISPLAY_CLOCK_MASK) { + case GC_DISPLAY_CLOCK_267_MHZ_PNV: + cdclk_config->cdclk = 266667; + break; + case GC_DISPLAY_CLOCK_333_MHZ_PNV: + cdclk_config->cdclk = 333333; + break; + case GC_DISPLAY_CLOCK_444_MHZ_PNV: + cdclk_config->cdclk = 444444; + break; + case GC_DISPLAY_CLOCK_200_MHZ_PNV: + cdclk_config->cdclk = 200000; + break; + default: + drm_err(&dev_priv->drm, + "Unknown pnv display core clock 0x%04x\n", gcfgc); + fallthrough; + case GC_DISPLAY_CLOCK_133_MHZ_PNV: + cdclk_config->cdclk = 133333; + break; + case GC_DISPLAY_CLOCK_167_MHZ_PNV: + cdclk_config->cdclk = 166667; + break; + } +} + +static void i965gm_get_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + struct pci_dev *pdev = to_pci_dev(dev_priv->drm.dev); + static const u8 div_3200[] = { 16, 10, 8 }; + static const u8 div_4000[] = { 20, 12, 10 }; + static const u8 div_5333[] = { 24, 16, 14 }; + const u8 *div_table; + unsigned int cdclk_sel; + u16 tmp = 0; + + cdclk_config->vco = intel_hpll_vco(dev_priv); + + pci_read_config_word(pdev, GCFGC, &tmp); + + cdclk_sel = ((tmp >> 8) & 0x1f) - 1; + + if (cdclk_sel >= ARRAY_SIZE(div_3200)) + goto fail; + + switch (cdclk_config->vco) { + case 3200000: + div_table = div_3200; + break; + case 4000000: + div_table = div_4000; + break; + case 5333333: + div_table = div_5333; + break; + default: + goto fail; + } + + cdclk_config->cdclk = DIV_ROUND_CLOSEST(cdclk_config->vco, + div_table[cdclk_sel]); + return; + +fail: + drm_err(&dev_priv->drm, + "Unable to determine CDCLK. HPLL VCO=%u kHz, CFGC=0x%04x\n", + cdclk_config->vco, tmp); + cdclk_config->cdclk = 200000; +} + +static void gm45_get_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + struct pci_dev *pdev = to_pci_dev(dev_priv->drm.dev); + unsigned int cdclk_sel; + u16 tmp = 0; + + cdclk_config->vco = intel_hpll_vco(dev_priv); + + pci_read_config_word(pdev, GCFGC, &tmp); + + cdclk_sel = (tmp >> 12) & 0x1; + + switch (cdclk_config->vco) { + case 2666667: + case 4000000: + case 5333333: + cdclk_config->cdclk = cdclk_sel ? 333333 : 222222; + break; + case 3200000: + cdclk_config->cdclk = cdclk_sel ? 320000 : 228571; + break; + default: + drm_err(&dev_priv->drm, + "Unable to determine CDCLK. HPLL VCO=%u, CFGC=0x%04x\n", + cdclk_config->vco, tmp); + cdclk_config->cdclk = 222222; + break; + } +} + +static void hsw_get_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + u32 lcpll = intel_de_read(dev_priv, LCPLL_CTL); + u32 freq = lcpll & LCPLL_CLK_FREQ_MASK; + + if (lcpll & LCPLL_CD_SOURCE_FCLK) + cdclk_config->cdclk = 800000; + else if (intel_de_read(dev_priv, FUSE_STRAP) & HSW_CDCLK_LIMIT) + cdclk_config->cdclk = 450000; + else if (freq == LCPLL_CLK_FREQ_450) + cdclk_config->cdclk = 450000; + else if (IS_HSW_ULT(dev_priv)) + cdclk_config->cdclk = 337500; + else + cdclk_config->cdclk = 540000; +} + +static int vlv_calc_cdclk(struct drm_i915_private *dev_priv, int min_cdclk) +{ + int freq_320 = (dev_priv->hpll_freq << 1) % 320000 != 0 ? + 333333 : 320000; + + /* + * We seem to get an unstable or solid color picture at 200MHz. + * Not sure what's wrong. For now use 200MHz only when all pipes + * are off. + */ + if (IS_VALLEYVIEW(dev_priv) && min_cdclk > freq_320) + return 400000; + else if (min_cdclk > 266667) + return freq_320; + else if (min_cdclk > 0) + return 266667; + else + return 200000; +} + +static u8 vlv_calc_voltage_level(struct drm_i915_private *dev_priv, int cdclk) +{ + if (IS_VALLEYVIEW(dev_priv)) { + if (cdclk >= 320000) /* jump to highest voltage for 400MHz too */ + return 2; + else if (cdclk >= 266667) + return 1; + else + return 0; + } else { + /* + * Specs are full of misinformation, but testing on actual + * hardware has shown that we just need to write the desired + * CCK divider into the Punit register. + */ + return DIV_ROUND_CLOSEST(dev_priv->hpll_freq << 1, cdclk) - 1; + } +} + +static void vlv_get_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + u32 val; + + vlv_iosf_sb_get(dev_priv, + BIT(VLV_IOSF_SB_CCK) | BIT(VLV_IOSF_SB_PUNIT)); + + cdclk_config->vco = vlv_get_hpll_vco(dev_priv); + cdclk_config->cdclk = vlv_get_cck_clock(dev_priv, "cdclk", + CCK_DISPLAY_CLOCK_CONTROL, + cdclk_config->vco); + + val = vlv_punit_read(dev_priv, PUNIT_REG_DSPSSPM); + + vlv_iosf_sb_put(dev_priv, + BIT(VLV_IOSF_SB_CCK) | BIT(VLV_IOSF_SB_PUNIT)); + + if (IS_VALLEYVIEW(dev_priv)) + cdclk_config->voltage_level = (val & DSPFREQGUAR_MASK) >> + DSPFREQGUAR_SHIFT; + else + cdclk_config->voltage_level = (val & DSPFREQGUAR_MASK_CHV) >> + DSPFREQGUAR_SHIFT_CHV; +} + +static void vlv_program_pfi_credits(struct drm_i915_private *dev_priv) +{ + unsigned int credits, default_credits; + + if (IS_CHERRYVIEW(dev_priv)) + default_credits = PFI_CREDIT(12); + else + default_credits = PFI_CREDIT(8); + + if (dev_priv->display.cdclk.hw.cdclk >= dev_priv->czclk_freq) { + /* CHV suggested value is 31 or 63 */ + if (IS_CHERRYVIEW(dev_priv)) + credits = PFI_CREDIT_63; + else + credits = PFI_CREDIT(15); + } else { + credits = default_credits; + } + + /* + * WA - write default credits before re-programming + * FIXME: should we also set the resend bit here? + */ + intel_de_write(dev_priv, GCI_CONTROL, + VGA_FAST_MODE_DISABLE | default_credits); + + intel_de_write(dev_priv, GCI_CONTROL, + VGA_FAST_MODE_DISABLE | credits | PFI_CREDIT_RESEND); + + /* + * FIXME is this guaranteed to clear + * immediately or should we poll for it? + */ + drm_WARN_ON(&dev_priv->drm, + intel_de_read(dev_priv, GCI_CONTROL) & PFI_CREDIT_RESEND); +} + +static void vlv_set_cdclk(struct drm_i915_private *dev_priv, + const struct intel_cdclk_config *cdclk_config, + enum pipe pipe) +{ + int cdclk = cdclk_config->cdclk; + u32 val, cmd = cdclk_config->voltage_level; + intel_wakeref_t wakeref; + + switch (cdclk) { + case 400000: + case 333333: + case 320000: + case 266667: + case 200000: + break; + default: + MISSING_CASE(cdclk); + return; + } + + /* There are cases where we can end up here with power domains + * off and a CDCLK frequency other than the minimum, like when + * issuing a modeset without actually changing any display after + * a system suspend. So grab the display core domain, which covers + * the HW blocks needed for the following programming. + */ + wakeref = intel_display_power_get(dev_priv, POWER_DOMAIN_DISPLAY_CORE); + + vlv_iosf_sb_get(dev_priv, + BIT(VLV_IOSF_SB_CCK) | + BIT(VLV_IOSF_SB_BUNIT) | + BIT(VLV_IOSF_SB_PUNIT)); + + val = vlv_punit_read(dev_priv, PUNIT_REG_DSPSSPM); + val &= ~DSPFREQGUAR_MASK; + val |= (cmd << DSPFREQGUAR_SHIFT); + vlv_punit_write(dev_priv, PUNIT_REG_DSPSSPM, val); + if (wait_for((vlv_punit_read(dev_priv, PUNIT_REG_DSPSSPM) & + DSPFREQSTAT_MASK) == (cmd << DSPFREQSTAT_SHIFT), + 50)) { + drm_err(&dev_priv->drm, + "timed out waiting for CDclk change\n"); + } + + if (cdclk == 400000) { + u32 divider; + + divider = DIV_ROUND_CLOSEST(dev_priv->hpll_freq << 1, + cdclk) - 1; + + /* adjust cdclk divider */ + val = vlv_cck_read(dev_priv, CCK_DISPLAY_CLOCK_CONTROL); + val &= ~CCK_FREQUENCY_VALUES; + val |= divider; + vlv_cck_write(dev_priv, CCK_DISPLAY_CLOCK_CONTROL, val); + + if (wait_for((vlv_cck_read(dev_priv, CCK_DISPLAY_CLOCK_CONTROL) & + CCK_FREQUENCY_STATUS) == (divider << CCK_FREQUENCY_STATUS_SHIFT), + 50)) + drm_err(&dev_priv->drm, + "timed out waiting for CDclk change\n"); + } + + /* adjust self-refresh exit latency value */ + val = vlv_bunit_read(dev_priv, BUNIT_REG_BISOC); + val &= ~0x7f; + + /* + * For high bandwidth configs, we set a higher latency in the bunit + * so that the core display fetch happens in time to avoid underruns. + */ + if (cdclk == 400000) + val |= 4500 / 250; /* 4.5 usec */ + else + val |= 3000 / 250; /* 3.0 usec */ + vlv_bunit_write(dev_priv, BUNIT_REG_BISOC, val); + + vlv_iosf_sb_put(dev_priv, + BIT(VLV_IOSF_SB_CCK) | + BIT(VLV_IOSF_SB_BUNIT) | + BIT(VLV_IOSF_SB_PUNIT)); + + intel_update_cdclk(dev_priv); + + vlv_program_pfi_credits(dev_priv); + + intel_display_power_put(dev_priv, POWER_DOMAIN_DISPLAY_CORE, wakeref); +} + +static void chv_set_cdclk(struct drm_i915_private *dev_priv, + const struct intel_cdclk_config *cdclk_config, + enum pipe pipe) +{ + int cdclk = cdclk_config->cdclk; + u32 val, cmd = cdclk_config->voltage_level; + intel_wakeref_t wakeref; + + switch (cdclk) { + case 333333: + case 320000: + case 266667: + case 200000: + break; + default: + MISSING_CASE(cdclk); + return; + } + + /* There are cases where we can end up here with power domains + * off and a CDCLK frequency other than the minimum, like when + * issuing a modeset without actually changing any display after + * a system suspend. So grab the display core domain, which covers + * the HW blocks needed for the following programming. + */ + wakeref = intel_display_power_get(dev_priv, POWER_DOMAIN_DISPLAY_CORE); + + vlv_punit_get(dev_priv); + val = vlv_punit_read(dev_priv, PUNIT_REG_DSPSSPM); + val &= ~DSPFREQGUAR_MASK_CHV; + val |= (cmd << DSPFREQGUAR_SHIFT_CHV); + vlv_punit_write(dev_priv, PUNIT_REG_DSPSSPM, val); + if (wait_for((vlv_punit_read(dev_priv, PUNIT_REG_DSPSSPM) & + DSPFREQSTAT_MASK_CHV) == (cmd << DSPFREQSTAT_SHIFT_CHV), + 50)) { + drm_err(&dev_priv->drm, + "timed out waiting for CDclk change\n"); + } + + vlv_punit_put(dev_priv); + + intel_update_cdclk(dev_priv); + + vlv_program_pfi_credits(dev_priv); + + intel_display_power_put(dev_priv, POWER_DOMAIN_DISPLAY_CORE, wakeref); +} + +static int bdw_calc_cdclk(int min_cdclk) +{ + if (min_cdclk > 540000) + return 675000; + else if (min_cdclk > 450000) + return 540000; + else if (min_cdclk > 337500) + return 450000; + else + return 337500; +} + +static u8 bdw_calc_voltage_level(int cdclk) +{ + switch (cdclk) { + default: + case 337500: + return 2; + case 450000: + return 0; + case 540000: + return 1; + case 675000: + return 3; + } +} + +static void bdw_get_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + u32 lcpll = intel_de_read(dev_priv, LCPLL_CTL); + u32 freq = lcpll & LCPLL_CLK_FREQ_MASK; + + if (lcpll & LCPLL_CD_SOURCE_FCLK) + cdclk_config->cdclk = 800000; + else if (intel_de_read(dev_priv, FUSE_STRAP) & HSW_CDCLK_LIMIT) + cdclk_config->cdclk = 450000; + else if (freq == LCPLL_CLK_FREQ_450) + cdclk_config->cdclk = 450000; + else if (freq == LCPLL_CLK_FREQ_54O_BDW) + cdclk_config->cdclk = 540000; + else if (freq == LCPLL_CLK_FREQ_337_5_BDW) + cdclk_config->cdclk = 337500; + else + cdclk_config->cdclk = 675000; + + /* + * Can't read this out :( Let's assume it's + * at least what the CDCLK frequency requires. + */ + cdclk_config->voltage_level = + bdw_calc_voltage_level(cdclk_config->cdclk); +} + +static u32 bdw_cdclk_freq_sel(int cdclk) +{ + switch (cdclk) { + default: + MISSING_CASE(cdclk); + fallthrough; + case 337500: + return LCPLL_CLK_FREQ_337_5_BDW; + case 450000: + return LCPLL_CLK_FREQ_450; + case 540000: + return LCPLL_CLK_FREQ_54O_BDW; + case 675000: + return LCPLL_CLK_FREQ_675_BDW; + } +} + +static void bdw_set_cdclk(struct drm_i915_private *dev_priv, + const struct intel_cdclk_config *cdclk_config, + enum pipe pipe) +{ + int cdclk = cdclk_config->cdclk; + int ret; + + if (drm_WARN(&dev_priv->drm, + (intel_de_read(dev_priv, LCPLL_CTL) & + (LCPLL_PLL_DISABLE | LCPLL_PLL_LOCK | + LCPLL_CD_CLOCK_DISABLE | LCPLL_ROOT_CD_CLOCK_DISABLE | + LCPLL_CD2X_CLOCK_DISABLE | LCPLL_POWER_DOWN_ALLOW | + LCPLL_CD_SOURCE_FCLK)) != LCPLL_PLL_LOCK, + "trying to change cdclk frequency with cdclk not enabled\n")) + return; + + ret = snb_pcode_write(&dev_priv->uncore, BDW_PCODE_DISPLAY_FREQ_CHANGE_REQ, 0x0); + if (ret) { + drm_err(&dev_priv->drm, + "failed to inform pcode about cdclk change\n"); + return; + } + + intel_de_rmw(dev_priv, LCPLL_CTL, + 0, LCPLL_CD_SOURCE_FCLK); + + /* + * According to the spec, it should be enough to poll for this 1 us. + * However, extensive testing shows that this can take longer. + */ + if (wait_for_us(intel_de_read(dev_priv, LCPLL_CTL) & + LCPLL_CD_SOURCE_FCLK_DONE, 100)) + drm_err(&dev_priv->drm, "Switching to FCLK failed\n"); + + intel_de_rmw(dev_priv, LCPLL_CTL, + LCPLL_CLK_FREQ_MASK, bdw_cdclk_freq_sel(cdclk)); + + intel_de_rmw(dev_priv, LCPLL_CTL, + LCPLL_CD_SOURCE_FCLK, 0); + + if (wait_for_us((intel_de_read(dev_priv, LCPLL_CTL) & + LCPLL_CD_SOURCE_FCLK_DONE) == 0, 1)) + drm_err(&dev_priv->drm, "Switching back to LCPLL failed\n"); + + snb_pcode_write(&dev_priv->uncore, HSW_PCODE_DE_WRITE_FREQ_REQ, + cdclk_config->voltage_level); + + intel_de_write(dev_priv, CDCLK_FREQ, + DIV_ROUND_CLOSEST(cdclk, 1000) - 1); + + intel_update_cdclk(dev_priv); +} + +static int skl_calc_cdclk(int min_cdclk, int vco) +{ + if (vco == 8640000) { + if (min_cdclk > 540000) + return 617143; + else if (min_cdclk > 432000) + return 540000; + else if (min_cdclk > 308571) + return 432000; + else + return 308571; + } else { + if (min_cdclk > 540000) + return 675000; + else if (min_cdclk > 450000) + return 540000; + else if (min_cdclk > 337500) + return 450000; + else + return 337500; + } +} + +static u8 skl_calc_voltage_level(int cdclk) +{ + if (cdclk > 540000) + return 3; + else if (cdclk > 450000) + return 2; + else if (cdclk > 337500) + return 1; + else + return 0; +} + +static void skl_dpll0_update(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + u32 val; + + cdclk_config->ref = 24000; + cdclk_config->vco = 0; + + val = intel_de_read(dev_priv, LCPLL1_CTL); + if ((val & LCPLL_PLL_ENABLE) == 0) + return; + + if (drm_WARN_ON(&dev_priv->drm, (val & LCPLL_PLL_LOCK) == 0)) + return; + + val = intel_de_read(dev_priv, DPLL_CTRL1); + + if (drm_WARN_ON(&dev_priv->drm, + (val & (DPLL_CTRL1_HDMI_MODE(SKL_DPLL0) | + DPLL_CTRL1_SSC(SKL_DPLL0) | + DPLL_CTRL1_OVERRIDE(SKL_DPLL0))) != + DPLL_CTRL1_OVERRIDE(SKL_DPLL0))) + return; + + switch (val & DPLL_CTRL1_LINK_RATE_MASK(SKL_DPLL0)) { + case DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_810, SKL_DPLL0): + case DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1350, SKL_DPLL0): + case DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1620, SKL_DPLL0): + case DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_2700, SKL_DPLL0): + cdclk_config->vco = 8100000; + break; + case DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1080, SKL_DPLL0): + case DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_2160, SKL_DPLL0): + cdclk_config->vco = 8640000; + break; + default: + MISSING_CASE(val & DPLL_CTRL1_LINK_RATE_MASK(SKL_DPLL0)); + break; + } +} + +static void skl_get_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + u32 cdctl; + + skl_dpll0_update(dev_priv, cdclk_config); + + cdclk_config->cdclk = cdclk_config->bypass = cdclk_config->ref; + + if (cdclk_config->vco == 0) + goto out; + + cdctl = intel_de_read(dev_priv, CDCLK_CTL); + + if (cdclk_config->vco == 8640000) { + switch (cdctl & CDCLK_FREQ_SEL_MASK) { + case CDCLK_FREQ_450_432: + cdclk_config->cdclk = 432000; + break; + case CDCLK_FREQ_337_308: + cdclk_config->cdclk = 308571; + break; + case CDCLK_FREQ_540: + cdclk_config->cdclk = 540000; + break; + case CDCLK_FREQ_675_617: + cdclk_config->cdclk = 617143; + break; + default: + MISSING_CASE(cdctl & CDCLK_FREQ_SEL_MASK); + break; + } + } else { + switch (cdctl & CDCLK_FREQ_SEL_MASK) { + case CDCLK_FREQ_450_432: + cdclk_config->cdclk = 450000; + break; + case CDCLK_FREQ_337_308: + cdclk_config->cdclk = 337500; + break; + case CDCLK_FREQ_540: + cdclk_config->cdclk = 540000; + break; + case CDCLK_FREQ_675_617: + cdclk_config->cdclk = 675000; + break; + default: + MISSING_CASE(cdctl & CDCLK_FREQ_SEL_MASK); + break; + } + } + + out: + /* + * Can't read this out :( Let's assume it's + * at least what the CDCLK frequency requires. + */ + cdclk_config->voltage_level = + skl_calc_voltage_level(cdclk_config->cdclk); +} + +/* convert from kHz to .1 fixpoint MHz with -1MHz offset */ +static int skl_cdclk_decimal(int cdclk) +{ + return DIV_ROUND_CLOSEST(cdclk - 1000, 500); +} + +static void skl_set_preferred_cdclk_vco(struct drm_i915_private *dev_priv, + int vco) +{ + bool changed = dev_priv->skl_preferred_vco_freq != vco; + + dev_priv->skl_preferred_vco_freq = vco; + + if (changed) + intel_update_max_cdclk(dev_priv); +} + +static u32 skl_dpll0_link_rate(struct drm_i915_private *dev_priv, int vco) +{ + drm_WARN_ON(&dev_priv->drm, vco != 8100000 && vco != 8640000); + + /* + * We always enable DPLL0 with the lowest link rate possible, but still + * taking into account the VCO required to operate the eDP panel at the + * desired frequency. The usual DP link rates operate with a VCO of + * 8100 while the eDP 1.4 alternate link rates need a VCO of 8640. + * The modeset code is responsible for the selection of the exact link + * rate later on, with the constraint of choosing a frequency that + * works with vco. + */ + if (vco == 8640000) + return DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1080, SKL_DPLL0); + else + return DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_810, SKL_DPLL0); +} + +static void skl_dpll0_enable(struct drm_i915_private *dev_priv, int vco) +{ + intel_de_rmw(dev_priv, DPLL_CTRL1, + DPLL_CTRL1_HDMI_MODE(SKL_DPLL0) | + DPLL_CTRL1_SSC(SKL_DPLL0) | + DPLL_CTRL1_LINK_RATE_MASK(SKL_DPLL0), + DPLL_CTRL1_OVERRIDE(SKL_DPLL0) | + skl_dpll0_link_rate(dev_priv, vco)); + intel_de_posting_read(dev_priv, DPLL_CTRL1); + + intel_de_rmw(dev_priv, LCPLL1_CTL, + 0, LCPLL_PLL_ENABLE); + + if (intel_de_wait_for_set(dev_priv, LCPLL1_CTL, LCPLL_PLL_LOCK, 5)) + drm_err(&dev_priv->drm, "DPLL0 not locked\n"); + + dev_priv->display.cdclk.hw.vco = vco; + + /* We'll want to keep using the current vco from now on. */ + skl_set_preferred_cdclk_vco(dev_priv, vco); +} + +static void skl_dpll0_disable(struct drm_i915_private *dev_priv) +{ + intel_de_rmw(dev_priv, LCPLL1_CTL, + LCPLL_PLL_ENABLE, 0); + + if (intel_de_wait_for_clear(dev_priv, LCPLL1_CTL, LCPLL_PLL_LOCK, 1)) + drm_err(&dev_priv->drm, "Couldn't disable DPLL0\n"); + + dev_priv->display.cdclk.hw.vco = 0; +} + +static u32 skl_cdclk_freq_sel(struct drm_i915_private *dev_priv, + int cdclk, int vco) +{ + switch (cdclk) { + default: + drm_WARN_ON(&dev_priv->drm, + cdclk != dev_priv->display.cdclk.hw.bypass); + drm_WARN_ON(&dev_priv->drm, vco != 0); + fallthrough; + case 308571: + case 337500: + return CDCLK_FREQ_337_308; + case 450000: + case 432000: + return CDCLK_FREQ_450_432; + case 540000: + return CDCLK_FREQ_540; + case 617143: + case 675000: + return CDCLK_FREQ_675_617; + } +} + +static void skl_set_cdclk(struct drm_i915_private *dev_priv, + const struct intel_cdclk_config *cdclk_config, + enum pipe pipe) +{ + int cdclk = cdclk_config->cdclk; + int vco = cdclk_config->vco; + u32 freq_select, cdclk_ctl; + int ret; + + /* + * Based on WA#1183 CDCLK rates 308 and 617MHz CDCLK rates are + * unsupported on SKL. In theory this should never happen since only + * the eDP1.4 2.16 and 4.32Gbps rates require it, but eDP1.4 is not + * supported on SKL either, see the above WA. WARN whenever trying to + * use the corresponding VCO freq as that always leads to using the + * minimum 308MHz CDCLK. + */ + drm_WARN_ON_ONCE(&dev_priv->drm, + IS_SKYLAKE(dev_priv) && vco == 8640000); + + ret = skl_pcode_request(&dev_priv->uncore, SKL_PCODE_CDCLK_CONTROL, + SKL_CDCLK_PREPARE_FOR_CHANGE, + SKL_CDCLK_READY_FOR_CHANGE, + SKL_CDCLK_READY_FOR_CHANGE, 3); + if (ret) { + drm_err(&dev_priv->drm, + "Failed to inform PCU about cdclk change (%d)\n", ret); + return; + } + + freq_select = skl_cdclk_freq_sel(dev_priv, cdclk, vco); + + if (dev_priv->display.cdclk.hw.vco != 0 && + dev_priv->display.cdclk.hw.vco != vco) + skl_dpll0_disable(dev_priv); + + cdclk_ctl = intel_de_read(dev_priv, CDCLK_CTL); + + if (dev_priv->display.cdclk.hw.vco != vco) { + /* Wa Display #1183: skl,kbl,cfl */ + cdclk_ctl &= ~(CDCLK_FREQ_SEL_MASK | CDCLK_FREQ_DECIMAL_MASK); + cdclk_ctl |= freq_select | skl_cdclk_decimal(cdclk); + intel_de_write(dev_priv, CDCLK_CTL, cdclk_ctl); + } + + /* Wa Display #1183: skl,kbl,cfl */ + cdclk_ctl |= CDCLK_DIVMUX_CD_OVERRIDE; + intel_de_write(dev_priv, CDCLK_CTL, cdclk_ctl); + intel_de_posting_read(dev_priv, CDCLK_CTL); + + if (dev_priv->display.cdclk.hw.vco != vco) + skl_dpll0_enable(dev_priv, vco); + + /* Wa Display #1183: skl,kbl,cfl */ + cdclk_ctl &= ~(CDCLK_FREQ_SEL_MASK | CDCLK_FREQ_DECIMAL_MASK); + intel_de_write(dev_priv, CDCLK_CTL, cdclk_ctl); + + cdclk_ctl |= freq_select | skl_cdclk_decimal(cdclk); + intel_de_write(dev_priv, CDCLK_CTL, cdclk_ctl); + + /* Wa Display #1183: skl,kbl,cfl */ + cdclk_ctl &= ~CDCLK_DIVMUX_CD_OVERRIDE; + intel_de_write(dev_priv, CDCLK_CTL, cdclk_ctl); + intel_de_posting_read(dev_priv, CDCLK_CTL); + + /* inform PCU of the change */ + snb_pcode_write(&dev_priv->uncore, SKL_PCODE_CDCLK_CONTROL, + cdclk_config->voltage_level); + + intel_update_cdclk(dev_priv); +} + +static void skl_sanitize_cdclk(struct drm_i915_private *dev_priv) +{ + u32 cdctl, expected; + + /* + * check if the pre-os initialized the display + * There is SWF18 scratchpad register defined which is set by the + * pre-os which can be used by the OS drivers to check the status + */ + if ((intel_de_read(dev_priv, SWF_ILK(0x18)) & 0x00FFFFFF) == 0) + goto sanitize; + + intel_update_cdclk(dev_priv); + intel_cdclk_dump_config(dev_priv, &dev_priv->display.cdclk.hw, "Current CDCLK"); + + /* Is PLL enabled and locked ? */ + if (dev_priv->display.cdclk.hw.vco == 0 || + dev_priv->display.cdclk.hw.cdclk == dev_priv->display.cdclk.hw.bypass) + goto sanitize; + + /* DPLL okay; verify the cdclock + * + * Noticed in some instances that the freq selection is correct but + * decimal part is programmed wrong from BIOS where pre-os does not + * enable display. Verify the same as well. + */ + cdctl = intel_de_read(dev_priv, CDCLK_CTL); + expected = (cdctl & CDCLK_FREQ_SEL_MASK) | + skl_cdclk_decimal(dev_priv->display.cdclk.hw.cdclk); + if (cdctl == expected) + /* All well; nothing to sanitize */ + return; + +sanitize: + drm_dbg_kms(&dev_priv->drm, "Sanitizing cdclk programmed by pre-os\n"); + + /* force cdclk programming */ + dev_priv->display.cdclk.hw.cdclk = 0; + /* force full PLL disable + enable */ + dev_priv->display.cdclk.hw.vco = -1; +} + +static void skl_cdclk_init_hw(struct drm_i915_private *dev_priv) +{ + struct intel_cdclk_config cdclk_config; + + skl_sanitize_cdclk(dev_priv); + + if (dev_priv->display.cdclk.hw.cdclk != 0 && + dev_priv->display.cdclk.hw.vco != 0) { + /* + * Use the current vco as our initial + * guess as to what the preferred vco is. + */ + if (dev_priv->skl_preferred_vco_freq == 0) + skl_set_preferred_cdclk_vco(dev_priv, + dev_priv->display.cdclk.hw.vco); + return; + } + + cdclk_config = dev_priv->display.cdclk.hw; + + cdclk_config.vco = dev_priv->skl_preferred_vco_freq; + if (cdclk_config.vco == 0) + cdclk_config.vco = 8100000; + cdclk_config.cdclk = skl_calc_cdclk(0, cdclk_config.vco); + cdclk_config.voltage_level = skl_calc_voltage_level(cdclk_config.cdclk); + + skl_set_cdclk(dev_priv, &cdclk_config, INVALID_PIPE); +} + +static void skl_cdclk_uninit_hw(struct drm_i915_private *dev_priv) +{ + struct intel_cdclk_config cdclk_config = dev_priv->display.cdclk.hw; + + cdclk_config.cdclk = cdclk_config.bypass; + cdclk_config.vco = 0; + cdclk_config.voltage_level = skl_calc_voltage_level(cdclk_config.cdclk); + + skl_set_cdclk(dev_priv, &cdclk_config, INVALID_PIPE); +} + +static bool has_cdclk_squasher(struct drm_i915_private *i915) +{ + return IS_DG2(i915); +} + +struct intel_cdclk_vals { + u32 cdclk; + u16 refclk; + u16 waveform; + u8 divider; /* CD2X divider * 2 */ + u8 ratio; +}; + +static const struct intel_cdclk_vals bxt_cdclk_table[] = { + { .refclk = 19200, .cdclk = 144000, .divider = 8, .ratio = 60 }, + { .refclk = 19200, .cdclk = 288000, .divider = 4, .ratio = 60 }, + { .refclk = 19200, .cdclk = 384000, .divider = 3, .ratio = 60 }, + { .refclk = 19200, .cdclk = 576000, .divider = 2, .ratio = 60 }, + { .refclk = 19200, .cdclk = 624000, .divider = 2, .ratio = 65 }, + {} +}; + +static const struct intel_cdclk_vals glk_cdclk_table[] = { + { .refclk = 19200, .cdclk = 79200, .divider = 8, .ratio = 33 }, + { .refclk = 19200, .cdclk = 158400, .divider = 4, .ratio = 33 }, + { .refclk = 19200, .cdclk = 316800, .divider = 2, .ratio = 33 }, + {} +}; + +static const struct intel_cdclk_vals icl_cdclk_table[] = { + { .refclk = 19200, .cdclk = 172800, .divider = 2, .ratio = 18 }, + { .refclk = 19200, .cdclk = 192000, .divider = 2, .ratio = 20 }, + { .refclk = 19200, .cdclk = 307200, .divider = 2, .ratio = 32 }, + { .refclk = 19200, .cdclk = 326400, .divider = 4, .ratio = 68 }, + { .refclk = 19200, .cdclk = 556800, .divider = 2, .ratio = 58 }, + { .refclk = 19200, .cdclk = 652800, .divider = 2, .ratio = 68 }, + + { .refclk = 24000, .cdclk = 180000, .divider = 2, .ratio = 15 }, + { .refclk = 24000, .cdclk = 192000, .divider = 2, .ratio = 16 }, + { .refclk = 24000, .cdclk = 312000, .divider = 2, .ratio = 26 }, + { .refclk = 24000, .cdclk = 324000, .divider = 4, .ratio = 54 }, + { .refclk = 24000, .cdclk = 552000, .divider = 2, .ratio = 46 }, + { .refclk = 24000, .cdclk = 648000, .divider = 2, .ratio = 54 }, + + { .refclk = 38400, .cdclk = 172800, .divider = 2, .ratio = 9 }, + { .refclk = 38400, .cdclk = 192000, .divider = 2, .ratio = 10 }, + { .refclk = 38400, .cdclk = 307200, .divider = 2, .ratio = 16 }, + { .refclk = 38400, .cdclk = 326400, .divider = 4, .ratio = 34 }, + { .refclk = 38400, .cdclk = 556800, .divider = 2, .ratio = 29 }, + { .refclk = 38400, .cdclk = 652800, .divider = 2, .ratio = 34 }, + {} +}; + +static const struct intel_cdclk_vals rkl_cdclk_table[] = { + { .refclk = 19200, .cdclk = 172800, .divider = 4, .ratio = 36 }, + { .refclk = 19200, .cdclk = 192000, .divider = 4, .ratio = 40 }, + { .refclk = 19200, .cdclk = 307200, .divider = 4, .ratio = 64 }, + { .refclk = 19200, .cdclk = 326400, .divider = 8, .ratio = 136 }, + { .refclk = 19200, .cdclk = 556800, .divider = 4, .ratio = 116 }, + { .refclk = 19200, .cdclk = 652800, .divider = 4, .ratio = 136 }, + + { .refclk = 24000, .cdclk = 180000, .divider = 4, .ratio = 30 }, + { .refclk = 24000, .cdclk = 192000, .divider = 4, .ratio = 32 }, + { .refclk = 24000, .cdclk = 312000, .divider = 4, .ratio = 52 }, + { .refclk = 24000, .cdclk = 324000, .divider = 8, .ratio = 108 }, + { .refclk = 24000, .cdclk = 552000, .divider = 4, .ratio = 92 }, + { .refclk = 24000, .cdclk = 648000, .divider = 4, .ratio = 108 }, + + { .refclk = 38400, .cdclk = 172800, .divider = 4, .ratio = 18 }, + { .refclk = 38400, .cdclk = 192000, .divider = 4, .ratio = 20 }, + { .refclk = 38400, .cdclk = 307200, .divider = 4, .ratio = 32 }, + { .refclk = 38400, .cdclk = 326400, .divider = 8, .ratio = 68 }, + { .refclk = 38400, .cdclk = 556800, .divider = 4, .ratio = 58 }, + { .refclk = 38400, .cdclk = 652800, .divider = 4, .ratio = 68 }, + {} +}; + +static const struct intel_cdclk_vals adlp_a_step_cdclk_table[] = { + { .refclk = 19200, .cdclk = 307200, .divider = 2, .ratio = 32 }, + { .refclk = 19200, .cdclk = 556800, .divider = 2, .ratio = 58 }, + { .refclk = 19200, .cdclk = 652800, .divider = 2, .ratio = 68 }, + + { .refclk = 24000, .cdclk = 312000, .divider = 2, .ratio = 26 }, + { .refclk = 24000, .cdclk = 552000, .divider = 2, .ratio = 46 }, + { .refclk = 24400, .cdclk = 648000, .divider = 2, .ratio = 54 }, + + { .refclk = 38400, .cdclk = 307200, .divider = 2, .ratio = 16 }, + { .refclk = 38400, .cdclk = 556800, .divider = 2, .ratio = 29 }, + { .refclk = 38400, .cdclk = 652800, .divider = 2, .ratio = 34 }, + {} +}; + +static const struct intel_cdclk_vals adlp_cdclk_table[] = { + { .refclk = 19200, .cdclk = 172800, .divider = 3, .ratio = 27 }, + { .refclk = 19200, .cdclk = 192000, .divider = 2, .ratio = 20 }, + { .refclk = 19200, .cdclk = 307200, .divider = 2, .ratio = 32 }, + { .refclk = 19200, .cdclk = 556800, .divider = 2, .ratio = 58 }, + { .refclk = 19200, .cdclk = 652800, .divider = 2, .ratio = 68 }, + + { .refclk = 24000, .cdclk = 176000, .divider = 3, .ratio = 22 }, + { .refclk = 24000, .cdclk = 192000, .divider = 2, .ratio = 16 }, + { .refclk = 24000, .cdclk = 312000, .divider = 2, .ratio = 26 }, + { .refclk = 24000, .cdclk = 552000, .divider = 2, .ratio = 46 }, + { .refclk = 24000, .cdclk = 648000, .divider = 2, .ratio = 54 }, + + { .refclk = 38400, .cdclk = 179200, .divider = 3, .ratio = 14 }, + { .refclk = 38400, .cdclk = 192000, .divider = 2, .ratio = 10 }, + { .refclk = 38400, .cdclk = 307200, .divider = 2, .ratio = 16 }, + { .refclk = 38400, .cdclk = 556800, .divider = 2, .ratio = 29 }, + { .refclk = 38400, .cdclk = 652800, .divider = 2, .ratio = 34 }, + {} +}; + +static const struct intel_cdclk_vals dg2_cdclk_table[] = { + { .refclk = 38400, .cdclk = 163200, .divider = 2, .ratio = 34, .waveform = 0x8888 }, + { .refclk = 38400, .cdclk = 204000, .divider = 2, .ratio = 34, .waveform = 0x9248 }, + { .refclk = 38400, .cdclk = 244800, .divider = 2, .ratio = 34, .waveform = 0xa4a4 }, + { .refclk = 38400, .cdclk = 285600, .divider = 2, .ratio = 34, .waveform = 0xa54a }, + { .refclk = 38400, .cdclk = 326400, .divider = 2, .ratio = 34, .waveform = 0xaaaa }, + { .refclk = 38400, .cdclk = 367200, .divider = 2, .ratio = 34, .waveform = 0xad5a }, + { .refclk = 38400, .cdclk = 408000, .divider = 2, .ratio = 34, .waveform = 0xb6b6 }, + { .refclk = 38400, .cdclk = 448800, .divider = 2, .ratio = 34, .waveform = 0xdbb6 }, + { .refclk = 38400, .cdclk = 489600, .divider = 2, .ratio = 34, .waveform = 0xeeee }, + { .refclk = 38400, .cdclk = 530400, .divider = 2, .ratio = 34, .waveform = 0xf7de }, + { .refclk = 38400, .cdclk = 571200, .divider = 2, .ratio = 34, .waveform = 0xfefe }, + { .refclk = 38400, .cdclk = 612000, .divider = 2, .ratio = 34, .waveform = 0xfffe }, + { .refclk = 38400, .cdclk = 652800, .divider = 2, .ratio = 34, .waveform = 0xffff }, + {} +}; + +static int bxt_calc_cdclk(struct drm_i915_private *dev_priv, int min_cdclk) +{ + const struct intel_cdclk_vals *table = dev_priv->display.cdclk.table; + int i; + + for (i = 0; table[i].refclk; i++) + if (table[i].refclk == dev_priv->display.cdclk.hw.ref && + table[i].cdclk >= min_cdclk) + return table[i].cdclk; + + drm_WARN(&dev_priv->drm, 1, + "Cannot satisfy minimum cdclk %d with refclk %u\n", + min_cdclk, dev_priv->display.cdclk.hw.ref); + return 0; +} + +static int bxt_calc_cdclk_pll_vco(struct drm_i915_private *dev_priv, int cdclk) +{ + const struct intel_cdclk_vals *table = dev_priv->display.cdclk.table; + int i; + + if (cdclk == dev_priv->display.cdclk.hw.bypass) + return 0; + + for (i = 0; table[i].refclk; i++) + if (table[i].refclk == dev_priv->display.cdclk.hw.ref && + table[i].cdclk == cdclk) + return dev_priv->display.cdclk.hw.ref * table[i].ratio; + + drm_WARN(&dev_priv->drm, 1, "cdclk %d not valid for refclk %u\n", + cdclk, dev_priv->display.cdclk.hw.ref); + return 0; +} + +static u8 bxt_calc_voltage_level(int cdclk) +{ + return DIV_ROUND_UP(cdclk, 25000); +} + +static u8 icl_calc_voltage_level(int cdclk) +{ + if (cdclk > 556800) + return 2; + else if (cdclk > 312000) + return 1; + else + return 0; +} + +static u8 ehl_calc_voltage_level(int cdclk) +{ + if (cdclk > 326400) + return 3; + else if (cdclk > 312000) + return 2; + else if (cdclk > 180000) + return 1; + else + return 0; +} + +static u8 tgl_calc_voltage_level(int cdclk) +{ + if (cdclk > 556800) + return 3; + else if (cdclk > 326400) + return 2; + else if (cdclk > 312000) + return 1; + else + return 0; +} + +static void icl_readout_refclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + u32 dssm = intel_de_read(dev_priv, SKL_DSSM) & ICL_DSSM_CDCLK_PLL_REFCLK_MASK; + + switch (dssm) { + default: + MISSING_CASE(dssm); + fallthrough; + case ICL_DSSM_CDCLK_PLL_REFCLK_24MHz: + cdclk_config->ref = 24000; + break; + case ICL_DSSM_CDCLK_PLL_REFCLK_19_2MHz: + cdclk_config->ref = 19200; + break; + case ICL_DSSM_CDCLK_PLL_REFCLK_38_4MHz: + cdclk_config->ref = 38400; + break; + } +} + +static void bxt_de_pll_readout(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + u32 val, ratio; + + if (IS_DG2(dev_priv)) + cdclk_config->ref = 38400; + else if (DISPLAY_VER(dev_priv) >= 11) + icl_readout_refclk(dev_priv, cdclk_config); + else + cdclk_config->ref = 19200; + + val = intel_de_read(dev_priv, BXT_DE_PLL_ENABLE); + if ((val & BXT_DE_PLL_PLL_ENABLE) == 0 || + (val & BXT_DE_PLL_LOCK) == 0) { + /* + * CDCLK PLL is disabled, the VCO/ratio doesn't matter, but + * setting it to zero is a way to signal that. + */ + cdclk_config->vco = 0; + return; + } + + /* + * DISPLAY_VER >= 11 have the ratio directly in the PLL enable register, + * gen9lp had it in a separate PLL control register. + */ + if (DISPLAY_VER(dev_priv) >= 11) + ratio = val & ICL_CDCLK_PLL_RATIO_MASK; + else + ratio = intel_de_read(dev_priv, BXT_DE_PLL_CTL) & BXT_DE_PLL_RATIO_MASK; + + cdclk_config->vco = ratio * cdclk_config->ref; +} + +static void bxt_get_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config) +{ + u32 squash_ctl = 0; + u32 divider; + int div; + + bxt_de_pll_readout(dev_priv, cdclk_config); + + if (DISPLAY_VER(dev_priv) >= 12) + cdclk_config->bypass = cdclk_config->ref / 2; + else if (DISPLAY_VER(dev_priv) >= 11) + cdclk_config->bypass = 50000; + else + cdclk_config->bypass = cdclk_config->ref; + + if (cdclk_config->vco == 0) { + cdclk_config->cdclk = cdclk_config->bypass; + goto out; + } + + divider = intel_de_read(dev_priv, CDCLK_CTL) & BXT_CDCLK_CD2X_DIV_SEL_MASK; + + switch (divider) { + case BXT_CDCLK_CD2X_DIV_SEL_1: + div = 2; + break; + case BXT_CDCLK_CD2X_DIV_SEL_1_5: + div = 3; + break; + case BXT_CDCLK_CD2X_DIV_SEL_2: + div = 4; + break; + case BXT_CDCLK_CD2X_DIV_SEL_4: + div = 8; + break; + default: + MISSING_CASE(divider); + return; + } + + if (has_cdclk_squasher(dev_priv)) + squash_ctl = intel_de_read(dev_priv, CDCLK_SQUASH_CTL); + + if (squash_ctl & CDCLK_SQUASH_ENABLE) { + u16 waveform; + int size; + + size = REG_FIELD_GET(CDCLK_SQUASH_WINDOW_SIZE_MASK, squash_ctl) + 1; + waveform = REG_FIELD_GET(CDCLK_SQUASH_WAVEFORM_MASK, squash_ctl) >> (16 - size); + + cdclk_config->cdclk = DIV_ROUND_CLOSEST(hweight16(waveform) * + cdclk_config->vco, size * div); + } else { + cdclk_config->cdclk = DIV_ROUND_CLOSEST(cdclk_config->vco, div); + } + + out: + /* + * Can't read this out :( Let's assume it's + * at least what the CDCLK frequency requires. + */ + cdclk_config->voltage_level = + intel_cdclk_calc_voltage_level(dev_priv, cdclk_config->cdclk); +} + +static void bxt_de_pll_disable(struct drm_i915_private *dev_priv) +{ + intel_de_write(dev_priv, BXT_DE_PLL_ENABLE, 0); + + /* Timeout 200us */ + if (intel_de_wait_for_clear(dev_priv, + BXT_DE_PLL_ENABLE, BXT_DE_PLL_LOCK, 1)) + drm_err(&dev_priv->drm, "timeout waiting for DE PLL unlock\n"); + + dev_priv->display.cdclk.hw.vco = 0; +} + +static void bxt_de_pll_enable(struct drm_i915_private *dev_priv, int vco) +{ + int ratio = DIV_ROUND_CLOSEST(vco, dev_priv->display.cdclk.hw.ref); + + intel_de_rmw(dev_priv, BXT_DE_PLL_CTL, + BXT_DE_PLL_RATIO_MASK, BXT_DE_PLL_RATIO(ratio)); + + intel_de_write(dev_priv, BXT_DE_PLL_ENABLE, BXT_DE_PLL_PLL_ENABLE); + + /* Timeout 200us */ + if (intel_de_wait_for_set(dev_priv, + BXT_DE_PLL_ENABLE, BXT_DE_PLL_LOCK, 1)) + drm_err(&dev_priv->drm, "timeout waiting for DE PLL lock\n"); + + dev_priv->display.cdclk.hw.vco = vco; +} + +static void icl_cdclk_pll_disable(struct drm_i915_private *dev_priv) +{ + intel_de_rmw(dev_priv, BXT_DE_PLL_ENABLE, + BXT_DE_PLL_PLL_ENABLE, 0); + + /* Timeout 200us */ + if (intel_de_wait_for_clear(dev_priv, BXT_DE_PLL_ENABLE, BXT_DE_PLL_LOCK, 1)) + drm_err(&dev_priv->drm, "timeout waiting for CDCLK PLL unlock\n"); + + dev_priv->display.cdclk.hw.vco = 0; +} + +static void icl_cdclk_pll_enable(struct drm_i915_private *dev_priv, int vco) +{ + int ratio = DIV_ROUND_CLOSEST(vco, dev_priv->display.cdclk.hw.ref); + u32 val; + + val = ICL_CDCLK_PLL_RATIO(ratio); + intel_de_write(dev_priv, BXT_DE_PLL_ENABLE, val); + + val |= BXT_DE_PLL_PLL_ENABLE; + intel_de_write(dev_priv, BXT_DE_PLL_ENABLE, val); + + /* Timeout 200us */ + if (intel_de_wait_for_set(dev_priv, BXT_DE_PLL_ENABLE, BXT_DE_PLL_LOCK, 1)) + drm_err(&dev_priv->drm, "timeout waiting for CDCLK PLL lock\n"); + + dev_priv->display.cdclk.hw.vco = vco; +} + +static void adlp_cdclk_pll_crawl(struct drm_i915_private *dev_priv, int vco) +{ + int ratio = DIV_ROUND_CLOSEST(vco, dev_priv->display.cdclk.hw.ref); + u32 val; + + /* Write PLL ratio without disabling */ + val = ICL_CDCLK_PLL_RATIO(ratio) | BXT_DE_PLL_PLL_ENABLE; + intel_de_write(dev_priv, BXT_DE_PLL_ENABLE, val); + + /* Submit freq change request */ + val |= BXT_DE_PLL_FREQ_REQ; + intel_de_write(dev_priv, BXT_DE_PLL_ENABLE, val); + + /* Timeout 200us */ + if (intel_de_wait_for_set(dev_priv, BXT_DE_PLL_ENABLE, + BXT_DE_PLL_LOCK | BXT_DE_PLL_FREQ_REQ_ACK, 1)) + drm_err(&dev_priv->drm, "timeout waiting for FREQ change request ack\n"); + + val &= ~BXT_DE_PLL_FREQ_REQ; + intel_de_write(dev_priv, BXT_DE_PLL_ENABLE, val); + + dev_priv->display.cdclk.hw.vco = vco; +} + +static u32 bxt_cdclk_cd2x_pipe(struct drm_i915_private *dev_priv, enum pipe pipe) +{ + if (DISPLAY_VER(dev_priv) >= 12) { + if (pipe == INVALID_PIPE) + return TGL_CDCLK_CD2X_PIPE_NONE; + else + return TGL_CDCLK_CD2X_PIPE(pipe); + } else if (DISPLAY_VER(dev_priv) >= 11) { + if (pipe == INVALID_PIPE) + return ICL_CDCLK_CD2X_PIPE_NONE; + else + return ICL_CDCLK_CD2X_PIPE(pipe); + } else { + if (pipe == INVALID_PIPE) + return BXT_CDCLK_CD2X_PIPE_NONE; + else + return BXT_CDCLK_CD2X_PIPE(pipe); + } +} + +static u32 bxt_cdclk_cd2x_div_sel(struct drm_i915_private *dev_priv, + int cdclk, int vco) +{ + /* cdclk = vco / 2 / div{1,1.5,2,4} */ + switch (DIV_ROUND_CLOSEST(vco, cdclk)) { + default: + drm_WARN_ON(&dev_priv->drm, + cdclk != dev_priv->display.cdclk.hw.bypass); + drm_WARN_ON(&dev_priv->drm, vco != 0); + fallthrough; + case 2: + return BXT_CDCLK_CD2X_DIV_SEL_1; + case 3: + return BXT_CDCLK_CD2X_DIV_SEL_1_5; + case 4: + return BXT_CDCLK_CD2X_DIV_SEL_2; + case 8: + return BXT_CDCLK_CD2X_DIV_SEL_4; + } +} + +static u32 cdclk_squash_waveform(struct drm_i915_private *dev_priv, + int cdclk) +{ + const struct intel_cdclk_vals *table = dev_priv->display.cdclk.table; + int i; + + if (cdclk == dev_priv->display.cdclk.hw.bypass) + return 0; + + for (i = 0; table[i].refclk; i++) + if (table[i].refclk == dev_priv->display.cdclk.hw.ref && + table[i].cdclk == cdclk) + return table[i].waveform; + + drm_WARN(&dev_priv->drm, 1, "cdclk %d not valid for refclk %u\n", + cdclk, dev_priv->display.cdclk.hw.ref); + + return 0xffff; +} + +static void bxt_set_cdclk(struct drm_i915_private *dev_priv, + const struct intel_cdclk_config *cdclk_config, + enum pipe pipe) +{ + int cdclk = cdclk_config->cdclk; + int vco = cdclk_config->vco; + u32 val; + u16 waveform; + int clock; + int ret; + + /* Inform power controller of upcoming frequency change. */ + if (DISPLAY_VER(dev_priv) >= 11) + ret = skl_pcode_request(&dev_priv->uncore, SKL_PCODE_CDCLK_CONTROL, + SKL_CDCLK_PREPARE_FOR_CHANGE, + SKL_CDCLK_READY_FOR_CHANGE, + SKL_CDCLK_READY_FOR_CHANGE, 3); + else + /* + * BSpec requires us to wait up to 150usec, but that leads to + * timeouts; the 2ms used here is based on experiment. + */ + ret = snb_pcode_write_timeout(&dev_priv->uncore, + HSW_PCODE_DE_WRITE_FREQ_REQ, + 0x80000000, 150, 2); + if (ret) { + drm_err(&dev_priv->drm, + "Failed to inform PCU about cdclk change (err %d, freq %d)\n", + ret, cdclk); + return; + } + + if (HAS_CDCLK_CRAWL(dev_priv) && dev_priv->display.cdclk.hw.vco > 0 && vco > 0) { + if (dev_priv->display.cdclk.hw.vco != vco) + adlp_cdclk_pll_crawl(dev_priv, vco); + } else if (DISPLAY_VER(dev_priv) >= 11) { + if (dev_priv->display.cdclk.hw.vco != 0 && + dev_priv->display.cdclk.hw.vco != vco) + icl_cdclk_pll_disable(dev_priv); + + if (dev_priv->display.cdclk.hw.vco != vco) + icl_cdclk_pll_enable(dev_priv, vco); + } else { + if (dev_priv->display.cdclk.hw.vco != 0 && + dev_priv->display.cdclk.hw.vco != vco) + bxt_de_pll_disable(dev_priv); + + if (dev_priv->display.cdclk.hw.vco != vco) + bxt_de_pll_enable(dev_priv, vco); + } + + waveform = cdclk_squash_waveform(dev_priv, cdclk); + + if (waveform) + clock = vco / 2; + else + clock = cdclk; + + if (has_cdclk_squasher(dev_priv)) { + u32 squash_ctl = 0; + + if (waveform) + squash_ctl = CDCLK_SQUASH_ENABLE | + CDCLK_SQUASH_WINDOW_SIZE(0xf) | waveform; + + intel_de_write(dev_priv, CDCLK_SQUASH_CTL, squash_ctl); + } + + val = bxt_cdclk_cd2x_div_sel(dev_priv, clock, vco) | + bxt_cdclk_cd2x_pipe(dev_priv, pipe) | + skl_cdclk_decimal(cdclk); + + /* + * Disable SSA Precharge when CD clock frequency < 500 MHz, + * enable otherwise. + */ + if ((IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv)) && + cdclk >= 500000) + val |= BXT_CDCLK_SSA_PRECHARGE_ENABLE; + intel_de_write(dev_priv, CDCLK_CTL, val); + + if (pipe != INVALID_PIPE) + intel_crtc_wait_for_next_vblank(intel_crtc_for_pipe(dev_priv, pipe)); + + if (DISPLAY_VER(dev_priv) >= 11) { + ret = snb_pcode_write(&dev_priv->uncore, SKL_PCODE_CDCLK_CONTROL, + cdclk_config->voltage_level); + } else { + /* + * The timeout isn't specified, the 2ms used here is based on + * experiment. + * FIXME: Waiting for the request completion could be delayed + * until the next PCODE request based on BSpec. + */ + ret = snb_pcode_write_timeout(&dev_priv->uncore, + HSW_PCODE_DE_WRITE_FREQ_REQ, + cdclk_config->voltage_level, + 150, 2); + } + + if (ret) { + drm_err(&dev_priv->drm, + "PCode CDCLK freq set failed, (err %d, freq %d)\n", + ret, cdclk); + return; + } + + intel_update_cdclk(dev_priv); + + if (DISPLAY_VER(dev_priv) >= 11) + /* + * Can't read out the voltage level :( + * Let's just assume everything is as expected. + */ + dev_priv->display.cdclk.hw.voltage_level = cdclk_config->voltage_level; +} + +static void bxt_sanitize_cdclk(struct drm_i915_private *dev_priv) +{ + u32 cdctl, expected; + int cdclk, clock, vco; + + intel_update_cdclk(dev_priv); + intel_cdclk_dump_config(dev_priv, &dev_priv->display.cdclk.hw, "Current CDCLK"); + + if (dev_priv->display.cdclk.hw.vco == 0 || + dev_priv->display.cdclk.hw.cdclk == dev_priv->display.cdclk.hw.bypass) + goto sanitize; + + /* DPLL okay; verify the cdclock + * + * Some BIOS versions leave an incorrect decimal frequency value and + * set reserved MBZ bits in CDCLK_CTL at least during exiting from S4, + * so sanitize this register. + */ + cdctl = intel_de_read(dev_priv, CDCLK_CTL); + /* + * Let's ignore the pipe field, since BIOS could have configured the + * dividers both synching to an active pipe, or asynchronously + * (PIPE_NONE). + */ + cdctl &= ~bxt_cdclk_cd2x_pipe(dev_priv, INVALID_PIPE); + + /* Make sure this is a legal cdclk value for the platform */ + cdclk = bxt_calc_cdclk(dev_priv, dev_priv->display.cdclk.hw.cdclk); + if (cdclk != dev_priv->display.cdclk.hw.cdclk) + goto sanitize; + + /* Make sure the VCO is correct for the cdclk */ + vco = bxt_calc_cdclk_pll_vco(dev_priv, cdclk); + if (vco != dev_priv->display.cdclk.hw.vco) + goto sanitize; + + expected = skl_cdclk_decimal(cdclk); + + /* Figure out what CD2X divider we should be using for this cdclk */ + if (has_cdclk_squasher(dev_priv)) + clock = dev_priv->display.cdclk.hw.vco / 2; + else + clock = dev_priv->display.cdclk.hw.cdclk; + + expected |= bxt_cdclk_cd2x_div_sel(dev_priv, clock, + dev_priv->display.cdclk.hw.vco); + + /* + * Disable SSA Precharge when CD clock frequency < 500 MHz, + * enable otherwise. + */ + if ((IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv)) && + dev_priv->display.cdclk.hw.cdclk >= 500000) + expected |= BXT_CDCLK_SSA_PRECHARGE_ENABLE; + + if (cdctl == expected) + /* All well; nothing to sanitize */ + return; + +sanitize: + drm_dbg_kms(&dev_priv->drm, "Sanitizing cdclk programmed by pre-os\n"); + + /* force cdclk programming */ + dev_priv->display.cdclk.hw.cdclk = 0; + + /* force full PLL disable + enable */ + dev_priv->display.cdclk.hw.vco = -1; +} + +static void bxt_cdclk_init_hw(struct drm_i915_private *dev_priv) +{ + struct intel_cdclk_config cdclk_config; + + bxt_sanitize_cdclk(dev_priv); + + if (dev_priv->display.cdclk.hw.cdclk != 0 && + dev_priv->display.cdclk.hw.vco != 0) + return; + + cdclk_config = dev_priv->display.cdclk.hw; + + /* + * FIXME: + * - The initial CDCLK needs to be read from VBT. + * Need to make this change after VBT has changes for BXT. + */ + cdclk_config.cdclk = bxt_calc_cdclk(dev_priv, 0); + cdclk_config.vco = bxt_calc_cdclk_pll_vco(dev_priv, cdclk_config.cdclk); + cdclk_config.voltage_level = + intel_cdclk_calc_voltage_level(dev_priv, cdclk_config.cdclk); + + bxt_set_cdclk(dev_priv, &cdclk_config, INVALID_PIPE); +} + +static void bxt_cdclk_uninit_hw(struct drm_i915_private *dev_priv) +{ + struct intel_cdclk_config cdclk_config = dev_priv->display.cdclk.hw; + + cdclk_config.cdclk = cdclk_config.bypass; + cdclk_config.vco = 0; + cdclk_config.voltage_level = + intel_cdclk_calc_voltage_level(dev_priv, cdclk_config.cdclk); + + bxt_set_cdclk(dev_priv, &cdclk_config, INVALID_PIPE); +} + +/** + * intel_cdclk_init_hw - Initialize CDCLK hardware + * @i915: i915 device + * + * Initialize CDCLK. This consists mainly of initializing dev_priv->display.cdclk.hw and + * sanitizing the state of the hardware if needed. This is generally done only + * during the display core initialization sequence, after which the DMC will + * take care of turning CDCLK off/on as needed. + */ +void intel_cdclk_init_hw(struct drm_i915_private *i915) +{ + if (DISPLAY_VER(i915) >= 10 || IS_BROXTON(i915)) + bxt_cdclk_init_hw(i915); + else if (DISPLAY_VER(i915) == 9) + skl_cdclk_init_hw(i915); +} + +/** + * intel_cdclk_uninit_hw - Uninitialize CDCLK hardware + * @i915: i915 device + * + * Uninitialize CDCLK. This is done only during the display core + * uninitialization sequence. + */ +void intel_cdclk_uninit_hw(struct drm_i915_private *i915) +{ + if (DISPLAY_VER(i915) >= 10 || IS_BROXTON(i915)) + bxt_cdclk_uninit_hw(i915); + else if (DISPLAY_VER(i915) == 9) + skl_cdclk_uninit_hw(i915); +} + +static bool intel_cdclk_can_crawl(struct drm_i915_private *dev_priv, + const struct intel_cdclk_config *a, + const struct intel_cdclk_config *b) +{ + int a_div, b_div; + + if (!HAS_CDCLK_CRAWL(dev_priv)) + return false; + + /* + * The vco and cd2x divider will change independently + * from each, so we disallow cd2x change when crawling. + */ + a_div = DIV_ROUND_CLOSEST(a->vco, a->cdclk); + b_div = DIV_ROUND_CLOSEST(b->vco, b->cdclk); + + return a->vco != 0 && b->vco != 0 && + a->vco != b->vco && + a_div == b_div && + a->ref == b->ref; +} + +static bool intel_cdclk_can_squash(struct drm_i915_private *dev_priv, + const struct intel_cdclk_config *a, + const struct intel_cdclk_config *b) +{ + /* + * FIXME should store a bit more state in intel_cdclk_config + * to differentiate squasher vs. cd2x divider properly. For + * the moment all platforms with squasher use a fixed cd2x + * divider. + */ + if (!has_cdclk_squasher(dev_priv)) + return false; + + return a->cdclk != b->cdclk && + a->vco != 0 && + a->vco == b->vco && + a->ref == b->ref; +} + +/** + * intel_cdclk_needs_modeset - Determine if changong between the CDCLK + * configurations requires a modeset on all pipes + * @a: first CDCLK configuration + * @b: second CDCLK configuration + * + * Returns: + * True if changing between the two CDCLK configurations + * requires all pipes to be off, false if not. + */ +bool intel_cdclk_needs_modeset(const struct intel_cdclk_config *a, + const struct intel_cdclk_config *b) +{ + return a->cdclk != b->cdclk || + a->vco != b->vco || + a->ref != b->ref; +} + +/** + * intel_cdclk_can_cd2x_update - Determine if changing between the two CDCLK + * configurations requires only a cd2x divider update + * @dev_priv: i915 device + * @a: first CDCLK configuration + * @b: second CDCLK configuration + * + * Returns: + * True if changing between the two CDCLK configurations + * can be done with just a cd2x divider update, false if not. + */ +static bool intel_cdclk_can_cd2x_update(struct drm_i915_private *dev_priv, + const struct intel_cdclk_config *a, + const struct intel_cdclk_config *b) +{ + /* Older hw doesn't have the capability */ + if (DISPLAY_VER(dev_priv) < 10 && !IS_BROXTON(dev_priv)) + return false; + + /* + * FIXME should store a bit more state in intel_cdclk_config + * to differentiate squasher vs. cd2x divider properly. For + * the moment all platforms with squasher use a fixed cd2x + * divider. + */ + if (has_cdclk_squasher(dev_priv)) + return false; + + return a->cdclk != b->cdclk && + a->vco != 0 && + a->vco == b->vco && + a->ref == b->ref; +} + +/** + * intel_cdclk_changed - Determine if two CDCLK configurations are different + * @a: first CDCLK configuration + * @b: second CDCLK configuration + * + * Returns: + * True if the CDCLK configurations don't match, false if they do. + */ +static bool intel_cdclk_changed(const struct intel_cdclk_config *a, + const struct intel_cdclk_config *b) +{ + return intel_cdclk_needs_modeset(a, b) || + a->voltage_level != b->voltage_level; +} + +void intel_cdclk_dump_config(struct drm_i915_private *i915, + const struct intel_cdclk_config *cdclk_config, + const char *context) +{ + drm_dbg_kms(&i915->drm, "%s %d kHz, VCO %d kHz, ref %d kHz, bypass %d kHz, voltage level %d\n", + context, cdclk_config->cdclk, cdclk_config->vco, + cdclk_config->ref, cdclk_config->bypass, + cdclk_config->voltage_level); +} + +/** + * intel_set_cdclk - Push the CDCLK configuration to the hardware + * @dev_priv: i915 device + * @cdclk_config: new CDCLK configuration + * @pipe: pipe with which to synchronize the update + * + * Program the hardware based on the passed in CDCLK state, + * if necessary. + */ +static void intel_set_cdclk(struct drm_i915_private *dev_priv, + const struct intel_cdclk_config *cdclk_config, + enum pipe pipe) +{ + struct intel_encoder *encoder; + + if (!intel_cdclk_changed(&dev_priv->display.cdclk.hw, cdclk_config)) + return; + + if (drm_WARN_ON_ONCE(&dev_priv->drm, !dev_priv->display.funcs.cdclk->set_cdclk)) + return; + + intel_cdclk_dump_config(dev_priv, cdclk_config, "Changing CDCLK to"); + + for_each_intel_encoder_with_psr(&dev_priv->drm, encoder) { + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + + intel_psr_pause(intel_dp); + } + + intel_audio_cdclk_change_pre(dev_priv); + + /* + * Lock aux/gmbus while we change cdclk in case those + * functions use cdclk. Not all platforms/ports do, + * but we'll lock them all for simplicity. + */ + mutex_lock(&dev_priv->display.gmbus.mutex); + for_each_intel_dp(&dev_priv->drm, encoder) { + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + + mutex_lock_nest_lock(&intel_dp->aux.hw_mutex, + &dev_priv->display.gmbus.mutex); + } + + intel_cdclk_set_cdclk(dev_priv, cdclk_config, pipe); + + for_each_intel_dp(&dev_priv->drm, encoder) { + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + + mutex_unlock(&intel_dp->aux.hw_mutex); + } + mutex_unlock(&dev_priv->display.gmbus.mutex); + + for_each_intel_encoder_with_psr(&dev_priv->drm, encoder) { + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + + intel_psr_resume(intel_dp); + } + + intel_audio_cdclk_change_post(dev_priv); + + if (drm_WARN(&dev_priv->drm, + intel_cdclk_changed(&dev_priv->display.cdclk.hw, cdclk_config), + "cdclk state doesn't match!\n")) { + intel_cdclk_dump_config(dev_priv, &dev_priv->display.cdclk.hw, "[hw state]"); + intel_cdclk_dump_config(dev_priv, cdclk_config, "[sw state]"); + } +} + +/** + * intel_set_cdclk_pre_plane_update - Push the CDCLK state to the hardware + * @state: intel atomic state + * + * Program the hardware before updating the HW plane state based on the + * new CDCLK state, if necessary. + */ +void +intel_set_cdclk_pre_plane_update(struct intel_atomic_state *state) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + const struct intel_cdclk_state *old_cdclk_state = + intel_atomic_get_old_cdclk_state(state); + const struct intel_cdclk_state *new_cdclk_state = + intel_atomic_get_new_cdclk_state(state); + enum pipe pipe = new_cdclk_state->pipe; + + if (!intel_cdclk_changed(&old_cdclk_state->actual, + &new_cdclk_state->actual)) + return; + + if (pipe == INVALID_PIPE || + old_cdclk_state->actual.cdclk <= new_cdclk_state->actual.cdclk) { + drm_WARN_ON(&dev_priv->drm, !new_cdclk_state->base.changed); + + intel_set_cdclk(dev_priv, &new_cdclk_state->actual, pipe); + } +} + +/** + * intel_set_cdclk_post_plane_update - Push the CDCLK state to the hardware + * @state: intel atomic state + * + * Program the hardware after updating the HW plane state based on the + * new CDCLK state, if necessary. + */ +void +intel_set_cdclk_post_plane_update(struct intel_atomic_state *state) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + const struct intel_cdclk_state *old_cdclk_state = + intel_atomic_get_old_cdclk_state(state); + const struct intel_cdclk_state *new_cdclk_state = + intel_atomic_get_new_cdclk_state(state); + enum pipe pipe = new_cdclk_state->pipe; + + if (!intel_cdclk_changed(&old_cdclk_state->actual, + &new_cdclk_state->actual)) + return; + + if (pipe != INVALID_PIPE && + old_cdclk_state->actual.cdclk > new_cdclk_state->actual.cdclk) { + drm_WARN_ON(&dev_priv->drm, !new_cdclk_state->base.changed); + + intel_set_cdclk(dev_priv, &new_cdclk_state->actual, pipe); + } +} + +static int intel_pixel_rate_to_cdclk(const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + int pixel_rate = crtc_state->pixel_rate; + + if (DISPLAY_VER(dev_priv) >= 10) + return DIV_ROUND_UP(pixel_rate, 2); + else if (DISPLAY_VER(dev_priv) == 9 || + IS_BROADWELL(dev_priv) || IS_HASWELL(dev_priv)) + return pixel_rate; + else if (IS_CHERRYVIEW(dev_priv)) + return DIV_ROUND_UP(pixel_rate * 100, 95); + else if (crtc_state->double_wide) + return DIV_ROUND_UP(pixel_rate * 100, 90 * 2); + else + return DIV_ROUND_UP(pixel_rate * 100, 90); +} + +static int intel_planes_min_cdclk(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_plane *plane; + int min_cdclk = 0; + + for_each_intel_plane_on_crtc(&dev_priv->drm, crtc, plane) + min_cdclk = max(crtc_state->min_cdclk[plane->id], min_cdclk); + + return min_cdclk; +} + +int intel_crtc_compute_min_cdclk(const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = + to_i915(crtc_state->uapi.crtc->dev); + int min_cdclk; + + if (!crtc_state->hw.enable) + return 0; + + min_cdclk = intel_pixel_rate_to_cdclk(crtc_state); + + /* pixel rate mustn't exceed 95% of cdclk with IPS on BDW */ + if (IS_BROADWELL(dev_priv) && hsw_crtc_state_ips_capable(crtc_state)) + min_cdclk = DIV_ROUND_UP(min_cdclk * 100, 95); + + /* BSpec says "Do not use DisplayPort with CDCLK less than 432 MHz, + * audio enabled, port width x4, and link rate HBR2 (5.4 GHz), or else + * there may be audio corruption or screen corruption." This cdclk + * restriction for GLK is 316.8 MHz. + */ + if (intel_crtc_has_dp_encoder(crtc_state) && + crtc_state->has_audio && + crtc_state->port_clock >= 540000 && + crtc_state->lane_count == 4) { + if (DISPLAY_VER(dev_priv) == 10) { + /* Display WA #1145: glk */ + min_cdclk = max(316800, min_cdclk); + } else if (DISPLAY_VER(dev_priv) == 9 || IS_BROADWELL(dev_priv)) { + /* Display WA #1144: skl,bxt */ + min_cdclk = max(432000, min_cdclk); + } + } + + /* + * According to BSpec, "The CD clock frequency must be at least twice + * the frequency of the Azalia BCLK." and BCLK is 96 MHz by default. + */ + if (crtc_state->has_audio && DISPLAY_VER(dev_priv) >= 9) + min_cdclk = max(2 * 96000, min_cdclk); + + /* + * "For DP audio configuration, cdclk frequency shall be set to + * meet the following requirements: + * DP Link Frequency(MHz) | Cdclk frequency(MHz) + * 270 | 320 or higher + * 162 | 200 or higher" + */ + if ((IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) && + intel_crtc_has_dp_encoder(crtc_state) && crtc_state->has_audio) + min_cdclk = max(crtc_state->port_clock, min_cdclk); + + /* + * On Valleyview some DSI panels lose (v|h)sync when the clock is lower + * than 320000KHz. + */ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DSI) && + IS_VALLEYVIEW(dev_priv)) + min_cdclk = max(320000, min_cdclk); + + /* + * On Geminilake once the CDCLK gets as low as 79200 + * picture gets unstable, despite that values are + * correct for DSI PLL and DE PLL. + */ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DSI) && + IS_GEMINILAKE(dev_priv)) + min_cdclk = max(158400, min_cdclk); + + /* Account for additional needs from the planes */ + min_cdclk = max(intel_planes_min_cdclk(crtc_state), min_cdclk); + + /* + * When we decide to use only one VDSC engine, since + * each VDSC operates with 1 ppc throughput, pixel clock + * cannot be higher than the VDSC clock (cdclk) + */ + if (crtc_state->dsc.compression_enable && !crtc_state->dsc.dsc_split) + min_cdclk = max(min_cdclk, (int)crtc_state->pixel_rate); + + /* + * HACK. Currently for TGL/DG2 platforms we calculate + * min_cdclk initially based on pixel_rate divided + * by 2, accounting for also plane requirements, + * however in some cases the lowest possible CDCLK + * doesn't work and causing the underruns. + * Explicitly stating here that this seems to be currently + * rather a Hack, than final solution. + */ + if (IS_TIGERLAKE(dev_priv) || IS_DG2(dev_priv)) { + /* + * Clamp to max_cdclk_freq in case pixel rate is higher, + * in order not to break an 8K, but still leave W/A at place. + */ + min_cdclk = max_t(int, min_cdclk, + min_t(int, crtc_state->pixel_rate, + dev_priv->display.cdclk.max_cdclk_freq)); + } + + return min_cdclk; +} + +static int intel_compute_min_cdclk(struct intel_cdclk_state *cdclk_state) +{ + struct intel_atomic_state *state = cdclk_state->base.state; + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + const struct intel_bw_state *bw_state; + struct intel_crtc *crtc; + struct intel_crtc_state *crtc_state; + int min_cdclk, i; + enum pipe pipe; + + for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) { + int ret; + + min_cdclk = intel_crtc_compute_min_cdclk(crtc_state); + if (min_cdclk < 0) + return min_cdclk; + + if (cdclk_state->min_cdclk[crtc->pipe] == min_cdclk) + continue; + + cdclk_state->min_cdclk[crtc->pipe] = min_cdclk; + + ret = intel_atomic_lock_global_state(&cdclk_state->base); + if (ret) + return ret; + } + + bw_state = intel_atomic_get_new_bw_state(state); + if (bw_state) { + min_cdclk = intel_bw_min_cdclk(dev_priv, bw_state); + + if (cdclk_state->bw_min_cdclk != min_cdclk) { + int ret; + + cdclk_state->bw_min_cdclk = min_cdclk; + + ret = intel_atomic_lock_global_state(&cdclk_state->base); + if (ret) + return ret; + } + } + + min_cdclk = max(cdclk_state->force_min_cdclk, + cdclk_state->bw_min_cdclk); + for_each_pipe(dev_priv, pipe) + min_cdclk = max(cdclk_state->min_cdclk[pipe], min_cdclk); + + /* + * Avoid glk_force_audio_cdclk() causing excessive screen + * blinking when multiple pipes are active by making sure + * CDCLK frequency is always high enough for audio. With a + * single active pipe we can always change CDCLK frequency + * by changing the cd2x divider (see glk_cdclk_table[]) and + * thus a full modeset won't be needed then. + */ + if (IS_GEMINILAKE(dev_priv) && cdclk_state->active_pipes && + !is_power_of_2(cdclk_state->active_pipes)) + min_cdclk = max(2 * 96000, min_cdclk); + + if (min_cdclk > dev_priv->display.cdclk.max_cdclk_freq) { + drm_dbg_kms(&dev_priv->drm, + "required cdclk (%d kHz) exceeds max (%d kHz)\n", + min_cdclk, dev_priv->display.cdclk.max_cdclk_freq); + return -EINVAL; + } + + return min_cdclk; +} + +/* + * Account for port clock min voltage level requirements. + * This only really does something on DISPLA_VER >= 11 but can be + * called on earlier platforms as well. + * + * Note that this functions assumes that 0 is + * the lowest voltage value, and higher values + * correspond to increasingly higher voltages. + * + * Should that relationship no longer hold on + * future platforms this code will need to be + * adjusted. + */ +static int bxt_compute_min_voltage_level(struct intel_cdclk_state *cdclk_state) +{ + struct intel_atomic_state *state = cdclk_state->base.state; + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_crtc *crtc; + struct intel_crtc_state *crtc_state; + u8 min_voltage_level; + int i; + enum pipe pipe; + + for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) { + int ret; + + if (crtc_state->hw.enable) + min_voltage_level = crtc_state->min_voltage_level; + else + min_voltage_level = 0; + + if (cdclk_state->min_voltage_level[crtc->pipe] == min_voltage_level) + continue; + + cdclk_state->min_voltage_level[crtc->pipe] = min_voltage_level; + + ret = intel_atomic_lock_global_state(&cdclk_state->base); + if (ret) + return ret; + } + + min_voltage_level = 0; + for_each_pipe(dev_priv, pipe) + min_voltage_level = max(cdclk_state->min_voltage_level[pipe], + min_voltage_level); + + return min_voltage_level; +} + +static int vlv_modeset_calc_cdclk(struct intel_cdclk_state *cdclk_state) +{ + struct intel_atomic_state *state = cdclk_state->base.state; + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + int min_cdclk, cdclk; + + min_cdclk = intel_compute_min_cdclk(cdclk_state); + if (min_cdclk < 0) + return min_cdclk; + + cdclk = vlv_calc_cdclk(dev_priv, min_cdclk); + + cdclk_state->logical.cdclk = cdclk; + cdclk_state->logical.voltage_level = + vlv_calc_voltage_level(dev_priv, cdclk); + + if (!cdclk_state->active_pipes) { + cdclk = vlv_calc_cdclk(dev_priv, cdclk_state->force_min_cdclk); + + cdclk_state->actual.cdclk = cdclk; + cdclk_state->actual.voltage_level = + vlv_calc_voltage_level(dev_priv, cdclk); + } else { + cdclk_state->actual = cdclk_state->logical; + } + + return 0; +} + +static int bdw_modeset_calc_cdclk(struct intel_cdclk_state *cdclk_state) +{ + int min_cdclk, cdclk; + + min_cdclk = intel_compute_min_cdclk(cdclk_state); + if (min_cdclk < 0) + return min_cdclk; + + /* + * FIXME should also account for plane ratio + * once 64bpp pixel formats are supported. + */ + cdclk = bdw_calc_cdclk(min_cdclk); + + cdclk_state->logical.cdclk = cdclk; + cdclk_state->logical.voltage_level = + bdw_calc_voltage_level(cdclk); + + if (!cdclk_state->active_pipes) { + cdclk = bdw_calc_cdclk(cdclk_state->force_min_cdclk); + + cdclk_state->actual.cdclk = cdclk; + cdclk_state->actual.voltage_level = + bdw_calc_voltage_level(cdclk); + } else { + cdclk_state->actual = cdclk_state->logical; + } + + return 0; +} + +static int skl_dpll0_vco(struct intel_cdclk_state *cdclk_state) +{ + struct intel_atomic_state *state = cdclk_state->base.state; + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_crtc *crtc; + struct intel_crtc_state *crtc_state; + int vco, i; + + vco = cdclk_state->logical.vco; + if (!vco) + vco = dev_priv->skl_preferred_vco_freq; + + for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) { + if (!crtc_state->hw.enable) + continue; + + if (!intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP)) + continue; + + /* + * DPLL0 VCO may need to be adjusted to get the correct + * clock for eDP. This will affect cdclk as well. + */ + switch (crtc_state->port_clock / 2) { + case 108000: + case 216000: + vco = 8640000; + break; + default: + vco = 8100000; + break; + } + } + + return vco; +} + +static int skl_modeset_calc_cdclk(struct intel_cdclk_state *cdclk_state) +{ + int min_cdclk, cdclk, vco; + + min_cdclk = intel_compute_min_cdclk(cdclk_state); + if (min_cdclk < 0) + return min_cdclk; + + vco = skl_dpll0_vco(cdclk_state); + + /* + * FIXME should also account for plane ratio + * once 64bpp pixel formats are supported. + */ + cdclk = skl_calc_cdclk(min_cdclk, vco); + + cdclk_state->logical.vco = vco; + cdclk_state->logical.cdclk = cdclk; + cdclk_state->logical.voltage_level = + skl_calc_voltage_level(cdclk); + + if (!cdclk_state->active_pipes) { + cdclk = skl_calc_cdclk(cdclk_state->force_min_cdclk, vco); + + cdclk_state->actual.vco = vco; + cdclk_state->actual.cdclk = cdclk; + cdclk_state->actual.voltage_level = + skl_calc_voltage_level(cdclk); + } else { + cdclk_state->actual = cdclk_state->logical; + } + + return 0; +} + +static int bxt_modeset_calc_cdclk(struct intel_cdclk_state *cdclk_state) +{ + struct intel_atomic_state *state = cdclk_state->base.state; + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + int min_cdclk, min_voltage_level, cdclk, vco; + + min_cdclk = intel_compute_min_cdclk(cdclk_state); + if (min_cdclk < 0) + return min_cdclk; + + min_voltage_level = bxt_compute_min_voltage_level(cdclk_state); + if (min_voltage_level < 0) + return min_voltage_level; + + cdclk = bxt_calc_cdclk(dev_priv, min_cdclk); + vco = bxt_calc_cdclk_pll_vco(dev_priv, cdclk); + + cdclk_state->logical.vco = vco; + cdclk_state->logical.cdclk = cdclk; + cdclk_state->logical.voltage_level = + max_t(int, min_voltage_level, + intel_cdclk_calc_voltage_level(dev_priv, cdclk)); + + if (!cdclk_state->active_pipes) { + cdclk = bxt_calc_cdclk(dev_priv, cdclk_state->force_min_cdclk); + vco = bxt_calc_cdclk_pll_vco(dev_priv, cdclk); + + cdclk_state->actual.vco = vco; + cdclk_state->actual.cdclk = cdclk; + cdclk_state->actual.voltage_level = + intel_cdclk_calc_voltage_level(dev_priv, cdclk); + } else { + cdclk_state->actual = cdclk_state->logical; + } + + return 0; +} + +static int fixed_modeset_calc_cdclk(struct intel_cdclk_state *cdclk_state) +{ + int min_cdclk; + + /* + * We can't change the cdclk frequency, but we still want to + * check that the required minimum frequency doesn't exceed + * the actual cdclk frequency. + */ + min_cdclk = intel_compute_min_cdclk(cdclk_state); + if (min_cdclk < 0) + return min_cdclk; + + return 0; +} + +static struct intel_global_state *intel_cdclk_duplicate_state(struct intel_global_obj *obj) +{ + struct intel_cdclk_state *cdclk_state; + + cdclk_state = kmemdup(obj->state, sizeof(*cdclk_state), GFP_KERNEL); + if (!cdclk_state) + return NULL; + + cdclk_state->pipe = INVALID_PIPE; + + return &cdclk_state->base; +} + +static void intel_cdclk_destroy_state(struct intel_global_obj *obj, + struct intel_global_state *state) +{ + kfree(state); +} + +static const struct intel_global_state_funcs intel_cdclk_funcs = { + .atomic_duplicate_state = intel_cdclk_duplicate_state, + .atomic_destroy_state = intel_cdclk_destroy_state, +}; + +struct intel_cdclk_state * +intel_atomic_get_cdclk_state(struct intel_atomic_state *state) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_global_state *cdclk_state; + + cdclk_state = intel_atomic_get_global_obj_state(state, &dev_priv->display.cdclk.obj); + if (IS_ERR(cdclk_state)) + return ERR_CAST(cdclk_state); + + return to_intel_cdclk_state(cdclk_state); +} + +int intel_cdclk_atomic_check(struct intel_atomic_state *state, + bool *need_cdclk_calc) +{ + const struct intel_cdclk_state *old_cdclk_state; + const struct intel_cdclk_state *new_cdclk_state; + struct intel_plane_state *plane_state; + struct intel_plane *plane; + int ret; + int i; + + /* + * active_planes bitmask has been updated, and potentially affected + * planes are part of the state. We can now compute the minimum cdclk + * for each plane. + */ + for_each_new_intel_plane_in_state(state, plane, plane_state, i) { + ret = intel_plane_calc_min_cdclk(state, plane, need_cdclk_calc); + if (ret) + return ret; + } + + ret = intel_bw_calc_min_cdclk(state, need_cdclk_calc); + if (ret) + return ret; + + old_cdclk_state = intel_atomic_get_old_cdclk_state(state); + new_cdclk_state = intel_atomic_get_new_cdclk_state(state); + + if (new_cdclk_state && + old_cdclk_state->force_min_cdclk != new_cdclk_state->force_min_cdclk) + *need_cdclk_calc = true; + + return 0; +} + +int intel_cdclk_init(struct drm_i915_private *dev_priv) +{ + struct intel_cdclk_state *cdclk_state; + + cdclk_state = kzalloc(sizeof(*cdclk_state), GFP_KERNEL); + if (!cdclk_state) + return -ENOMEM; + + intel_atomic_global_obj_init(dev_priv, &dev_priv->display.cdclk.obj, + &cdclk_state->base, &intel_cdclk_funcs); + + return 0; +} + +int intel_modeset_calc_cdclk(struct intel_atomic_state *state) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + const struct intel_cdclk_state *old_cdclk_state; + struct intel_cdclk_state *new_cdclk_state; + enum pipe pipe = INVALID_PIPE; + int ret; + + new_cdclk_state = intel_atomic_get_cdclk_state(state); + if (IS_ERR(new_cdclk_state)) + return PTR_ERR(new_cdclk_state); + + old_cdclk_state = intel_atomic_get_old_cdclk_state(state); + + new_cdclk_state->active_pipes = + intel_calc_active_pipes(state, old_cdclk_state->active_pipes); + + ret = intel_cdclk_modeset_calc_cdclk(dev_priv, new_cdclk_state); + if (ret) + return ret; + + if (intel_cdclk_changed(&old_cdclk_state->actual, + &new_cdclk_state->actual)) { + /* + * Also serialize commits across all crtcs + * if the actual hw needs to be poked. + */ + ret = intel_atomic_serialize_global_state(&new_cdclk_state->base); + if (ret) + return ret; + } else if (old_cdclk_state->active_pipes != new_cdclk_state->active_pipes || + old_cdclk_state->force_min_cdclk != new_cdclk_state->force_min_cdclk || + intel_cdclk_changed(&old_cdclk_state->logical, + &new_cdclk_state->logical)) { + ret = intel_atomic_lock_global_state(&new_cdclk_state->base); + if (ret) + return ret; + } else { + return 0; + } + + if (is_power_of_2(new_cdclk_state->active_pipes) && + intel_cdclk_can_cd2x_update(dev_priv, + &old_cdclk_state->actual, + &new_cdclk_state->actual)) { + struct intel_crtc *crtc; + struct intel_crtc_state *crtc_state; + + pipe = ilog2(new_cdclk_state->active_pipes); + crtc = intel_crtc_for_pipe(dev_priv, pipe); + + crtc_state = intel_atomic_get_crtc_state(&state->base, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + if (drm_atomic_crtc_needs_modeset(&crtc_state->uapi)) + pipe = INVALID_PIPE; + } + + if (intel_cdclk_can_squash(dev_priv, + &old_cdclk_state->actual, + &new_cdclk_state->actual)) { + drm_dbg_kms(&dev_priv->drm, + "Can change cdclk via squasher\n"); + } else if (intel_cdclk_can_crawl(dev_priv, + &old_cdclk_state->actual, + &new_cdclk_state->actual)) { + drm_dbg_kms(&dev_priv->drm, + "Can change cdclk via crawl\n"); + } else if (pipe != INVALID_PIPE) { + new_cdclk_state->pipe = pipe; + + drm_dbg_kms(&dev_priv->drm, + "Can change cdclk cd2x divider with pipe %c active\n", + pipe_name(pipe)); + } else if (intel_cdclk_needs_modeset(&old_cdclk_state->actual, + &new_cdclk_state->actual)) { + /* All pipes must be switched off while we change the cdclk. */ + ret = intel_modeset_all_pipes(state); + if (ret) + return ret; + + drm_dbg_kms(&dev_priv->drm, + "Modeset required for cdclk change\n"); + } + + drm_dbg_kms(&dev_priv->drm, + "New cdclk calculated to be logical %u kHz, actual %u kHz\n", + new_cdclk_state->logical.cdclk, + new_cdclk_state->actual.cdclk); + drm_dbg_kms(&dev_priv->drm, + "New voltage level calculated to be logical %u, actual %u\n", + new_cdclk_state->logical.voltage_level, + new_cdclk_state->actual.voltage_level); + + return 0; +} + +static int intel_compute_max_dotclk(struct drm_i915_private *dev_priv) +{ + int max_cdclk_freq = dev_priv->display.cdclk.max_cdclk_freq; + + if (DISPLAY_VER(dev_priv) >= 10) + return 2 * max_cdclk_freq; + else if (DISPLAY_VER(dev_priv) == 9 || + IS_BROADWELL(dev_priv) || IS_HASWELL(dev_priv)) + return max_cdclk_freq; + else if (IS_CHERRYVIEW(dev_priv)) + return max_cdclk_freq*95/100; + else if (DISPLAY_VER(dev_priv) < 4) + return 2*max_cdclk_freq*90/100; + else + return max_cdclk_freq*90/100; +} + +/** + * intel_update_max_cdclk - Determine the maximum support CDCLK frequency + * @dev_priv: i915 device + * + * Determine the maximum CDCLK frequency the platform supports, and also + * derive the maximum dot clock frequency the maximum CDCLK frequency + * allows. + */ +void intel_update_max_cdclk(struct drm_i915_private *dev_priv) +{ + if (IS_JSL_EHL(dev_priv)) { + if (dev_priv->display.cdclk.hw.ref == 24000) + dev_priv->display.cdclk.max_cdclk_freq = 552000; + else + dev_priv->display.cdclk.max_cdclk_freq = 556800; + } else if (DISPLAY_VER(dev_priv) >= 11) { + if (dev_priv->display.cdclk.hw.ref == 24000) + dev_priv->display.cdclk.max_cdclk_freq = 648000; + else + dev_priv->display.cdclk.max_cdclk_freq = 652800; + } else if (IS_GEMINILAKE(dev_priv)) { + dev_priv->display.cdclk.max_cdclk_freq = 316800; + } else if (IS_BROXTON(dev_priv)) { + dev_priv->display.cdclk.max_cdclk_freq = 624000; + } else if (DISPLAY_VER(dev_priv) == 9) { + u32 limit = intel_de_read(dev_priv, SKL_DFSM) & SKL_DFSM_CDCLK_LIMIT_MASK; + int max_cdclk, vco; + + vco = dev_priv->skl_preferred_vco_freq; + drm_WARN_ON(&dev_priv->drm, vco != 8100000 && vco != 8640000); + + /* + * Use the lower (vco 8640) cdclk values as a + * first guess. skl_calc_cdclk() will correct it + * if the preferred vco is 8100 instead. + */ + if (limit == SKL_DFSM_CDCLK_LIMIT_675) + max_cdclk = 617143; + else if (limit == SKL_DFSM_CDCLK_LIMIT_540) + max_cdclk = 540000; + else if (limit == SKL_DFSM_CDCLK_LIMIT_450) + max_cdclk = 432000; + else + max_cdclk = 308571; + + dev_priv->display.cdclk.max_cdclk_freq = skl_calc_cdclk(max_cdclk, vco); + } else if (IS_BROADWELL(dev_priv)) { + /* + * FIXME with extra cooling we can allow + * 540 MHz for ULX and 675 Mhz for ULT. + * How can we know if extra cooling is + * available? PCI ID, VTB, something else? + */ + if (intel_de_read(dev_priv, FUSE_STRAP) & HSW_CDCLK_LIMIT) + dev_priv->display.cdclk.max_cdclk_freq = 450000; + else if (IS_BDW_ULX(dev_priv)) + dev_priv->display.cdclk.max_cdclk_freq = 450000; + else if (IS_BDW_ULT(dev_priv)) + dev_priv->display.cdclk.max_cdclk_freq = 540000; + else + dev_priv->display.cdclk.max_cdclk_freq = 675000; + } else if (IS_CHERRYVIEW(dev_priv)) { + dev_priv->display.cdclk.max_cdclk_freq = 320000; + } else if (IS_VALLEYVIEW(dev_priv)) { + dev_priv->display.cdclk.max_cdclk_freq = 400000; + } else { + /* otherwise assume cdclk is fixed */ + dev_priv->display.cdclk.max_cdclk_freq = dev_priv->display.cdclk.hw.cdclk; + } + + dev_priv->max_dotclk_freq = intel_compute_max_dotclk(dev_priv); + + drm_dbg(&dev_priv->drm, "Max CD clock rate: %d kHz\n", + dev_priv->display.cdclk.max_cdclk_freq); + + drm_dbg(&dev_priv->drm, "Max dotclock rate: %d kHz\n", + dev_priv->max_dotclk_freq); +} + +/** + * intel_update_cdclk - Determine the current CDCLK frequency + * @dev_priv: i915 device + * + * Determine the current CDCLK frequency. + */ +void intel_update_cdclk(struct drm_i915_private *dev_priv) +{ + intel_cdclk_get_cdclk(dev_priv, &dev_priv->display.cdclk.hw); + + /* + * 9:0 CMBUS [sic] CDCLK frequency (cdfreq): + * Programmng [sic] note: bit[9:2] should be programmed to the number + * of cdclk that generates 4MHz reference clock freq which is used to + * generate GMBus clock. This will vary with the cdclk freq. + */ + if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) + intel_de_write(dev_priv, GMBUSFREQ_VLV, + DIV_ROUND_UP(dev_priv->display.cdclk.hw.cdclk, 1000)); +} + +static int dg1_rawclk(struct drm_i915_private *dev_priv) +{ + /* + * DG1 always uses a 38.4 MHz rawclk. The bspec tells us + * "Program Numerator=2, Denominator=4, Divider=37 decimal." + */ + intel_de_write(dev_priv, PCH_RAWCLK_FREQ, + CNP_RAWCLK_DEN(4) | CNP_RAWCLK_DIV(37) | ICP_RAWCLK_NUM(2)); + + return 38400; +} + +static int cnp_rawclk(struct drm_i915_private *dev_priv) +{ + u32 rawclk; + int divider, fraction; + + if (intel_de_read(dev_priv, SFUSE_STRAP) & SFUSE_STRAP_RAW_FREQUENCY) { + /* 24 MHz */ + divider = 24000; + fraction = 0; + } else { + /* 19.2 MHz */ + divider = 19000; + fraction = 200; + } + + rawclk = CNP_RAWCLK_DIV(divider / 1000); + if (fraction) { + int numerator = 1; + + rawclk |= CNP_RAWCLK_DEN(DIV_ROUND_CLOSEST(numerator * 1000, + fraction) - 1); + if (INTEL_PCH_TYPE(dev_priv) >= PCH_ICP) + rawclk |= ICP_RAWCLK_NUM(numerator); + } + + intel_de_write(dev_priv, PCH_RAWCLK_FREQ, rawclk); + return divider + fraction; +} + +static int pch_rawclk(struct drm_i915_private *dev_priv) +{ + return (intel_de_read(dev_priv, PCH_RAWCLK_FREQ) & RAWCLK_FREQ_MASK) * 1000; +} + +static int vlv_hrawclk(struct drm_i915_private *dev_priv) +{ + /* RAWCLK_FREQ_VLV register updated from power well code */ + return vlv_get_cck_clock_hpll(dev_priv, "hrawclk", + CCK_DISPLAY_REF_CLOCK_CONTROL); +} + +static int i9xx_hrawclk(struct drm_i915_private *dev_priv) +{ + u32 clkcfg; + + /* + * hrawclock is 1/4 the FSB frequency + * + * Note that this only reads the state of the FSB + * straps, not the actual FSB frequency. Some BIOSen + * let you configure each independently. Ideally we'd + * read out the actual FSB frequency but sadly we + * don't know which registers have that information, + * and all the relevant docs have gone to bit heaven :( + */ + clkcfg = intel_de_read(dev_priv, CLKCFG) & CLKCFG_FSB_MASK; + + if (IS_MOBILE(dev_priv)) { + switch (clkcfg) { + case CLKCFG_FSB_400: + return 100000; + case CLKCFG_FSB_533: + return 133333; + case CLKCFG_FSB_667: + return 166667; + case CLKCFG_FSB_800: + return 200000; + case CLKCFG_FSB_1067: + return 266667; + case CLKCFG_FSB_1333: + return 333333; + default: + MISSING_CASE(clkcfg); + return 133333; + } + } else { + switch (clkcfg) { + case CLKCFG_FSB_400_ALT: + return 100000; + case CLKCFG_FSB_533: + return 133333; + case CLKCFG_FSB_667: + return 166667; + case CLKCFG_FSB_800: + return 200000; + case CLKCFG_FSB_1067_ALT: + return 266667; + case CLKCFG_FSB_1333_ALT: + return 333333; + case CLKCFG_FSB_1600_ALT: + return 400000; + default: + return 133333; + } + } +} + +/** + * intel_read_rawclk - Determine the current RAWCLK frequency + * @dev_priv: i915 device + * + * Determine the current RAWCLK frequency. RAWCLK is a fixed + * frequency clock so this needs to done only once. + */ +u32 intel_read_rawclk(struct drm_i915_private *dev_priv) +{ + u32 freq; + + if (INTEL_PCH_TYPE(dev_priv) >= PCH_DG1) + freq = dg1_rawclk(dev_priv); + else if (INTEL_PCH_TYPE(dev_priv) >= PCH_MTP) + /* + * MTL always uses a 38.4 MHz rawclk. The bspec tells us + * "RAWCLK_FREQ defaults to the values for 38.4 and does + * not need to be programmed." + */ + freq = 38400; + else if (INTEL_PCH_TYPE(dev_priv) >= PCH_CNP) + freq = cnp_rawclk(dev_priv); + else if (HAS_PCH_SPLIT(dev_priv)) + freq = pch_rawclk(dev_priv); + else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) + freq = vlv_hrawclk(dev_priv); + else if (DISPLAY_VER(dev_priv) >= 3) + freq = i9xx_hrawclk(dev_priv); + else + /* no rawclk on other platforms, or no need to know it */ + return 0; + + return freq; +} + +static const struct intel_cdclk_funcs tgl_cdclk_funcs = { + .get_cdclk = bxt_get_cdclk, + .set_cdclk = bxt_set_cdclk, + .modeset_calc_cdclk = bxt_modeset_calc_cdclk, + .calc_voltage_level = tgl_calc_voltage_level, +}; + +static const struct intel_cdclk_funcs ehl_cdclk_funcs = { + .get_cdclk = bxt_get_cdclk, + .set_cdclk = bxt_set_cdclk, + .modeset_calc_cdclk = bxt_modeset_calc_cdclk, + .calc_voltage_level = ehl_calc_voltage_level, +}; + +static const struct intel_cdclk_funcs icl_cdclk_funcs = { + .get_cdclk = bxt_get_cdclk, + .set_cdclk = bxt_set_cdclk, + .modeset_calc_cdclk = bxt_modeset_calc_cdclk, + .calc_voltage_level = icl_calc_voltage_level, +}; + +static const struct intel_cdclk_funcs bxt_cdclk_funcs = { + .get_cdclk = bxt_get_cdclk, + .set_cdclk = bxt_set_cdclk, + .modeset_calc_cdclk = bxt_modeset_calc_cdclk, + .calc_voltage_level = bxt_calc_voltage_level, +}; + +static const struct intel_cdclk_funcs skl_cdclk_funcs = { + .get_cdclk = skl_get_cdclk, + .set_cdclk = skl_set_cdclk, + .modeset_calc_cdclk = skl_modeset_calc_cdclk, +}; + +static const struct intel_cdclk_funcs bdw_cdclk_funcs = { + .get_cdclk = bdw_get_cdclk, + .set_cdclk = bdw_set_cdclk, + .modeset_calc_cdclk = bdw_modeset_calc_cdclk, +}; + +static const struct intel_cdclk_funcs chv_cdclk_funcs = { + .get_cdclk = vlv_get_cdclk, + .set_cdclk = chv_set_cdclk, + .modeset_calc_cdclk = vlv_modeset_calc_cdclk, +}; + +static const struct intel_cdclk_funcs vlv_cdclk_funcs = { + .get_cdclk = vlv_get_cdclk, + .set_cdclk = vlv_set_cdclk, + .modeset_calc_cdclk = vlv_modeset_calc_cdclk, +}; + +static const struct intel_cdclk_funcs hsw_cdclk_funcs = { + .get_cdclk = hsw_get_cdclk, + .modeset_calc_cdclk = fixed_modeset_calc_cdclk, +}; + +/* SNB, IVB, 965G, 945G */ +static const struct intel_cdclk_funcs fixed_400mhz_cdclk_funcs = { + .get_cdclk = fixed_400mhz_get_cdclk, + .modeset_calc_cdclk = fixed_modeset_calc_cdclk, +}; + +static const struct intel_cdclk_funcs ilk_cdclk_funcs = { + .get_cdclk = fixed_450mhz_get_cdclk, + .modeset_calc_cdclk = fixed_modeset_calc_cdclk, +}; + +static const struct intel_cdclk_funcs gm45_cdclk_funcs = { + .get_cdclk = gm45_get_cdclk, + .modeset_calc_cdclk = fixed_modeset_calc_cdclk, +}; + +/* G45 uses G33 */ + +static const struct intel_cdclk_funcs i965gm_cdclk_funcs = { + .get_cdclk = i965gm_get_cdclk, + .modeset_calc_cdclk = fixed_modeset_calc_cdclk, +}; + +/* i965G uses fixed 400 */ + +static const struct intel_cdclk_funcs pnv_cdclk_funcs = { + .get_cdclk = pnv_get_cdclk, + .modeset_calc_cdclk = fixed_modeset_calc_cdclk, +}; + +static const struct intel_cdclk_funcs g33_cdclk_funcs = { + .get_cdclk = g33_get_cdclk, + .modeset_calc_cdclk = fixed_modeset_calc_cdclk, +}; + +static const struct intel_cdclk_funcs i945gm_cdclk_funcs = { + .get_cdclk = i945gm_get_cdclk, + .modeset_calc_cdclk = fixed_modeset_calc_cdclk, +}; + +/* i945G uses fixed 400 */ + +static const struct intel_cdclk_funcs i915gm_cdclk_funcs = { + .get_cdclk = i915gm_get_cdclk, + .modeset_calc_cdclk = fixed_modeset_calc_cdclk, +}; + +static const struct intel_cdclk_funcs i915g_cdclk_funcs = { + .get_cdclk = fixed_333mhz_get_cdclk, + .modeset_calc_cdclk = fixed_modeset_calc_cdclk, +}; + +static const struct intel_cdclk_funcs i865g_cdclk_funcs = { + .get_cdclk = fixed_266mhz_get_cdclk, + .modeset_calc_cdclk = fixed_modeset_calc_cdclk, +}; + +static const struct intel_cdclk_funcs i85x_cdclk_funcs = { + .get_cdclk = i85x_get_cdclk, + .modeset_calc_cdclk = fixed_modeset_calc_cdclk, +}; + +static const struct intel_cdclk_funcs i845g_cdclk_funcs = { + .get_cdclk = fixed_200mhz_get_cdclk, + .modeset_calc_cdclk = fixed_modeset_calc_cdclk, +}; + +static const struct intel_cdclk_funcs i830_cdclk_funcs = { + .get_cdclk = fixed_133mhz_get_cdclk, + .modeset_calc_cdclk = fixed_modeset_calc_cdclk, +}; + +/** + * intel_init_cdclk_hooks - Initialize CDCLK related modesetting hooks + * @dev_priv: i915 device + */ +void intel_init_cdclk_hooks(struct drm_i915_private *dev_priv) +{ + if (IS_DG2(dev_priv)) { + dev_priv->display.funcs.cdclk = &tgl_cdclk_funcs; + dev_priv->display.cdclk.table = dg2_cdclk_table; + } else if (IS_ALDERLAKE_P(dev_priv)) { + dev_priv->display.funcs.cdclk = &tgl_cdclk_funcs; + /* Wa_22011320316:adl-p[a0] */ + if (IS_ADLP_DISPLAY_STEP(dev_priv, STEP_A0, STEP_B0)) + dev_priv->display.cdclk.table = adlp_a_step_cdclk_table; + else + dev_priv->display.cdclk.table = adlp_cdclk_table; + } else if (IS_ROCKETLAKE(dev_priv)) { + dev_priv->display.funcs.cdclk = &tgl_cdclk_funcs; + dev_priv->display.cdclk.table = rkl_cdclk_table; + } else if (DISPLAY_VER(dev_priv) >= 12) { + dev_priv->display.funcs.cdclk = &tgl_cdclk_funcs; + dev_priv->display.cdclk.table = icl_cdclk_table; + } else if (IS_JSL_EHL(dev_priv)) { + dev_priv->display.funcs.cdclk = &ehl_cdclk_funcs; + dev_priv->display.cdclk.table = icl_cdclk_table; + } else if (DISPLAY_VER(dev_priv) >= 11) { + dev_priv->display.funcs.cdclk = &icl_cdclk_funcs; + dev_priv->display.cdclk.table = icl_cdclk_table; + } else if (IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv)) { + dev_priv->display.funcs.cdclk = &bxt_cdclk_funcs; + if (IS_GEMINILAKE(dev_priv)) + dev_priv->display.cdclk.table = glk_cdclk_table; + else + dev_priv->display.cdclk.table = bxt_cdclk_table; + } else if (DISPLAY_VER(dev_priv) == 9) { + dev_priv->display.funcs.cdclk = &skl_cdclk_funcs; + } else if (IS_BROADWELL(dev_priv)) { + dev_priv->display.funcs.cdclk = &bdw_cdclk_funcs; + } else if (IS_HASWELL(dev_priv)) { + dev_priv->display.funcs.cdclk = &hsw_cdclk_funcs; + } else if (IS_CHERRYVIEW(dev_priv)) { + dev_priv->display.funcs.cdclk = &chv_cdclk_funcs; + } else if (IS_VALLEYVIEW(dev_priv)) { + dev_priv->display.funcs.cdclk = &vlv_cdclk_funcs; + } else if (IS_SANDYBRIDGE(dev_priv) || IS_IVYBRIDGE(dev_priv)) { + dev_priv->display.funcs.cdclk = &fixed_400mhz_cdclk_funcs; + } else if (IS_IRONLAKE(dev_priv)) { + dev_priv->display.funcs.cdclk = &ilk_cdclk_funcs; + } else if (IS_GM45(dev_priv)) { + dev_priv->display.funcs.cdclk = &gm45_cdclk_funcs; + } else if (IS_G45(dev_priv)) { + dev_priv->display.funcs.cdclk = &g33_cdclk_funcs; + } else if (IS_I965GM(dev_priv)) { + dev_priv->display.funcs.cdclk = &i965gm_cdclk_funcs; + } else if (IS_I965G(dev_priv)) { + dev_priv->display.funcs.cdclk = &fixed_400mhz_cdclk_funcs; + } else if (IS_PINEVIEW(dev_priv)) { + dev_priv->display.funcs.cdclk = &pnv_cdclk_funcs; + } else if (IS_G33(dev_priv)) { + dev_priv->display.funcs.cdclk = &g33_cdclk_funcs; + } else if (IS_I945GM(dev_priv)) { + dev_priv->display.funcs.cdclk = &i945gm_cdclk_funcs; + } else if (IS_I945G(dev_priv)) { + dev_priv->display.funcs.cdclk = &fixed_400mhz_cdclk_funcs; + } else if (IS_I915GM(dev_priv)) { + dev_priv->display.funcs.cdclk = &i915gm_cdclk_funcs; + } else if (IS_I915G(dev_priv)) { + dev_priv->display.funcs.cdclk = &i915g_cdclk_funcs; + } else if (IS_I865G(dev_priv)) { + dev_priv->display.funcs.cdclk = &i865g_cdclk_funcs; + } else if (IS_I85X(dev_priv)) { + dev_priv->display.funcs.cdclk = &i85x_cdclk_funcs; + } else if (IS_I845G(dev_priv)) { + dev_priv->display.funcs.cdclk = &i845g_cdclk_funcs; + } else if (IS_I830(dev_priv)) { + dev_priv->display.funcs.cdclk = &i830_cdclk_funcs; + } + + if (drm_WARN(&dev_priv->drm, !dev_priv->display.funcs.cdclk, + "Unknown platform. Assuming i830\n")) + dev_priv->display.funcs.cdclk = &i830_cdclk_funcs; +} diff --git a/drivers/gpu/drm/i915/display/intel_cdclk.h b/drivers/gpu/drm/i915/display/intel_cdclk.h new file mode 100644 index 000000000..c674879a8 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_cdclk.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_CDCLK_H__ +#define __INTEL_CDCLK_H__ + +#include + +#include "intel_display.h" +#include "intel_global_state.h" + +struct drm_i915_private; +struct intel_atomic_state; +struct intel_crtc_state; + +struct intel_cdclk_config { + unsigned int cdclk, vco, ref, bypass; + u8 voltage_level; +}; + +struct intel_cdclk_state { + struct intel_global_state base; + + /* + * Logical configuration of cdclk (used for all scaling, + * watermark, etc. calculations and checks). This is + * computed as if all enabled crtcs were active. + */ + struct intel_cdclk_config logical; + + /* + * Actual configuration of cdclk, can be different from the + * logical configuration only when all crtc's are DPMS off. + */ + struct intel_cdclk_config actual; + + /* minimum acceptable cdclk to satisfy bandwidth requirements */ + int bw_min_cdclk; + /* minimum acceptable cdclk for each pipe */ + int min_cdclk[I915_MAX_PIPES]; + /* minimum acceptable voltage level for each pipe */ + u8 min_voltage_level[I915_MAX_PIPES]; + + /* pipe to which cd2x update is synchronized */ + enum pipe pipe; + + /* forced minimum cdclk for glk+ audio w/a */ + int force_min_cdclk; + + /* bitmask of active pipes */ + u8 active_pipes; +}; + +int intel_crtc_compute_min_cdclk(const struct intel_crtc_state *crtc_state); +void intel_cdclk_init_hw(struct drm_i915_private *i915); +void intel_cdclk_uninit_hw(struct drm_i915_private *i915); +void intel_init_cdclk_hooks(struct drm_i915_private *dev_priv); +void intel_update_max_cdclk(struct drm_i915_private *dev_priv); +void intel_update_cdclk(struct drm_i915_private *dev_priv); +u32 intel_read_rawclk(struct drm_i915_private *dev_priv); +bool intel_cdclk_needs_modeset(const struct intel_cdclk_config *a, + const struct intel_cdclk_config *b); +void intel_set_cdclk_pre_plane_update(struct intel_atomic_state *state); +void intel_set_cdclk_post_plane_update(struct intel_atomic_state *state); +void intel_cdclk_dump_config(struct drm_i915_private *i915, + const struct intel_cdclk_config *cdclk_config, + const char *context); +int intel_modeset_calc_cdclk(struct intel_atomic_state *state); +void intel_cdclk_get_cdclk(struct drm_i915_private *dev_priv, + struct intel_cdclk_config *cdclk_config); +int intel_cdclk_atomic_check(struct intel_atomic_state *state, + bool *need_cdclk_calc); +struct intel_cdclk_state * +intel_atomic_get_cdclk_state(struct intel_atomic_state *state); + +#define to_intel_cdclk_state(x) container_of((x), struct intel_cdclk_state, base) +#define intel_atomic_get_old_cdclk_state(state) \ + to_intel_cdclk_state(intel_atomic_get_old_global_obj_state(state, &to_i915(state->base.dev)->display.cdclk.obj)) +#define intel_atomic_get_new_cdclk_state(state) \ + to_intel_cdclk_state(intel_atomic_get_new_global_obj_state(state, &to_i915(state->base.dev)->display.cdclk.obj)) + +int intel_cdclk_init(struct drm_i915_private *dev_priv); + +#endif /* __INTEL_CDCLK_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_color.c b/drivers/gpu/drm/i915/display/intel_color.c new file mode 100644 index 000000000..78211e583 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_color.c @@ -0,0 +1,2295 @@ +/* + * Copyright © 2016 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#include "intel_color.h" +#include "intel_de.h" +#include "intel_display_types.h" +#include "intel_dpll.h" +#include "intel_dsb.h" +#include "vlv_dsi_pll.h" + +struct intel_color_funcs { + int (*color_check)(struct intel_crtc_state *crtc_state); + /* + * Program non-arming double buffered color management registers + * before vblank evasion. The registers should then latch after + * the arming register is written (by color_commit_arm()) during + * the next vblank start, alongside any other double buffered + * registers involved with the same commit. This hook is optional. + */ + void (*color_commit_noarm)(const struct intel_crtc_state *crtc_state); + /* + * Program arming double buffered color management registers + * during vblank evasion. The registers (and whatever other registers + * they arm that were written by color_commit_noarm) should then latch + * during the next vblank start, alongside any other double buffered + * registers involved with the same commit. + */ + void (*color_commit_arm)(const struct intel_crtc_state *crtc_state); + /* + * Load LUTs (and other single buffered color management + * registers). Will (hopefully) be called during the vblank + * following the latching of any double buffered registers + * involved with the same commit. + */ + void (*load_luts)(const struct intel_crtc_state *crtc_state); + void (*read_luts)(struct intel_crtc_state *crtc_state); +}; + +#define CTM_COEFF_SIGN (1ULL << 63) + +#define CTM_COEFF_1_0 (1ULL << 32) +#define CTM_COEFF_2_0 (CTM_COEFF_1_0 << 1) +#define CTM_COEFF_4_0 (CTM_COEFF_2_0 << 1) +#define CTM_COEFF_8_0 (CTM_COEFF_4_0 << 1) +#define CTM_COEFF_0_5 (CTM_COEFF_1_0 >> 1) +#define CTM_COEFF_0_25 (CTM_COEFF_0_5 >> 1) +#define CTM_COEFF_0_125 (CTM_COEFF_0_25 >> 1) + +#define CTM_COEFF_LIMITED_RANGE ((235ULL - 16ULL) * CTM_COEFF_1_0 / 255) + +#define CTM_COEFF_NEGATIVE(coeff) (((coeff) & CTM_COEFF_SIGN) != 0) +#define CTM_COEFF_ABS(coeff) ((coeff) & (CTM_COEFF_SIGN - 1)) + +#define LEGACY_LUT_LENGTH 256 + +/* + * ILK+ csc matrix: + * + * |R/Cr| | c0 c1 c2 | ( |R/Cr| |preoff0| ) |postoff0| + * |G/Y | = | c3 c4 c5 | x ( |G/Y | + |preoff1| ) + |postoff1| + * |B/Cb| | c6 c7 c8 | ( |B/Cb| |preoff2| ) |postoff2| + * + * ILK/SNB don't have explicit post offsets, and instead + * CSC_MODE_YUV_TO_RGB and CSC_BLACK_SCREEN_OFFSET are used: + * CSC_MODE_YUV_TO_RGB=0 + CSC_BLACK_SCREEN_OFFSET=0 -> 1/2, 0, 1/2 + * CSC_MODE_YUV_TO_RGB=0 + CSC_BLACK_SCREEN_OFFSET=1 -> 1/2, 1/16, 1/2 + * CSC_MODE_YUV_TO_RGB=1 + CSC_BLACK_SCREEN_OFFSET=0 -> 0, 0, 0 + * CSC_MODE_YUV_TO_RGB=1 + CSC_BLACK_SCREEN_OFFSET=1 -> 1/16, 1/16, 1/16 + */ + +/* + * Extract the CSC coefficient from a CTM coefficient (in U32.32 fixed point + * format). This macro takes the coefficient we want transformed and the + * number of fractional bits. + * + * We only have a 9 bits precision window which slides depending on the value + * of the CTM coefficient and we write the value from bit 3. We also round the + * value. + */ +#define ILK_CSC_COEFF_FP(coeff, fbits) \ + (clamp_val(((coeff) >> (32 - (fbits) - 3)) + 4, 0, 0xfff) & 0xff8) + +#define ILK_CSC_COEFF_LIMITED_RANGE 0x0dc0 +#define ILK_CSC_COEFF_1_0 0x7800 + +#define ILK_CSC_POSTOFF_LIMITED_RANGE (16 * (1 << 12) / 255) + +/* Nop pre/post offsets */ +static const u16 ilk_csc_off_zero[3] = {}; + +/* Identity matrix */ +static const u16 ilk_csc_coeff_identity[9] = { + ILK_CSC_COEFF_1_0, 0, 0, + 0, ILK_CSC_COEFF_1_0, 0, + 0, 0, ILK_CSC_COEFF_1_0, +}; + +/* Limited range RGB post offsets */ +static const u16 ilk_csc_postoff_limited_range[3] = { + ILK_CSC_POSTOFF_LIMITED_RANGE, + ILK_CSC_POSTOFF_LIMITED_RANGE, + ILK_CSC_POSTOFF_LIMITED_RANGE, +}; + +/* Full range RGB -> limited range RGB matrix */ +static const u16 ilk_csc_coeff_limited_range[9] = { + ILK_CSC_COEFF_LIMITED_RANGE, 0, 0, + 0, ILK_CSC_COEFF_LIMITED_RANGE, 0, + 0, 0, ILK_CSC_COEFF_LIMITED_RANGE, +}; + +/* BT.709 full range RGB -> limited range YCbCr matrix */ +static const u16 ilk_csc_coeff_rgb_to_ycbcr[9] = { + 0x1e08, 0x9cc0, 0xb528, + 0x2ba8, 0x09d8, 0x37e8, + 0xbce8, 0x9ad8, 0x1e08, +}; + +/* Limited range YCbCr post offsets */ +static const u16 ilk_csc_postoff_rgb_to_ycbcr[3] = { + 0x0800, 0x0100, 0x0800, +}; + +static bool lut_is_legacy(const struct drm_property_blob *lut) +{ + return drm_color_lut_size(lut) == LEGACY_LUT_LENGTH; +} + +static bool crtc_state_is_legacy_gamma(const struct intel_crtc_state *crtc_state) +{ + return !crtc_state->hw.degamma_lut && + !crtc_state->hw.ctm && + crtc_state->hw.gamma_lut && + lut_is_legacy(crtc_state->hw.gamma_lut); +} + +/* + * When using limited range, multiply the matrix given by userspace by + * the matrix that we would use for the limited range. + */ +static u64 *ctm_mult_by_limited(u64 *result, const u64 *input) +{ + int i; + + for (i = 0; i < 9; i++) { + u64 user_coeff = input[i]; + u32 limited_coeff = CTM_COEFF_LIMITED_RANGE; + u32 abs_coeff = clamp_val(CTM_COEFF_ABS(user_coeff), 0, + CTM_COEFF_4_0 - 1) >> 2; + + /* + * By scaling every co-efficient with limited range (16-235) + * vs full range (0-255) the final o/p will be scaled down to + * fit in the limited range supported by the panel. + */ + result[i] = mul_u32_u32(limited_coeff, abs_coeff) >> 30; + result[i] |= user_coeff & CTM_COEFF_SIGN; + } + + return result; +} + +static void ilk_update_pipe_csc(struct intel_crtc *crtc, + const u16 preoff[3], + const u16 coeff[9], + const u16 postoff[3]) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + + intel_de_write_fw(dev_priv, PIPE_CSC_PREOFF_HI(pipe), preoff[0]); + intel_de_write_fw(dev_priv, PIPE_CSC_PREOFF_ME(pipe), preoff[1]); + intel_de_write_fw(dev_priv, PIPE_CSC_PREOFF_LO(pipe), preoff[2]); + + intel_de_write_fw(dev_priv, PIPE_CSC_COEFF_RY_GY(pipe), + coeff[0] << 16 | coeff[1]); + intel_de_write_fw(dev_priv, PIPE_CSC_COEFF_BY(pipe), coeff[2] << 16); + + intel_de_write_fw(dev_priv, PIPE_CSC_COEFF_RU_GU(pipe), + coeff[3] << 16 | coeff[4]); + intel_de_write_fw(dev_priv, PIPE_CSC_COEFF_BU(pipe), coeff[5] << 16); + + intel_de_write_fw(dev_priv, PIPE_CSC_COEFF_RV_GV(pipe), + coeff[6] << 16 | coeff[7]); + intel_de_write_fw(dev_priv, PIPE_CSC_COEFF_BV(pipe), coeff[8] << 16); + + if (DISPLAY_VER(dev_priv) >= 7) { + intel_de_write_fw(dev_priv, PIPE_CSC_POSTOFF_HI(pipe), + postoff[0]); + intel_de_write_fw(dev_priv, PIPE_CSC_POSTOFF_ME(pipe), + postoff[1]); + intel_de_write_fw(dev_priv, PIPE_CSC_POSTOFF_LO(pipe), + postoff[2]); + } +} + +static void icl_update_output_csc(struct intel_crtc *crtc, + const u16 preoff[3], + const u16 coeff[9], + const u16 postoff[3]) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + + intel_de_write_fw(dev_priv, PIPE_CSC_OUTPUT_PREOFF_HI(pipe), preoff[0]); + intel_de_write_fw(dev_priv, PIPE_CSC_OUTPUT_PREOFF_ME(pipe), preoff[1]); + intel_de_write_fw(dev_priv, PIPE_CSC_OUTPUT_PREOFF_LO(pipe), preoff[2]); + + intel_de_write_fw(dev_priv, PIPE_CSC_OUTPUT_COEFF_RY_GY(pipe), + coeff[0] << 16 | coeff[1]); + intel_de_write_fw(dev_priv, PIPE_CSC_OUTPUT_COEFF_BY(pipe), + coeff[2] << 16); + + intel_de_write_fw(dev_priv, PIPE_CSC_OUTPUT_COEFF_RU_GU(pipe), + coeff[3] << 16 | coeff[4]); + intel_de_write_fw(dev_priv, PIPE_CSC_OUTPUT_COEFF_BU(pipe), + coeff[5] << 16); + + intel_de_write_fw(dev_priv, PIPE_CSC_OUTPUT_COEFF_RV_GV(pipe), + coeff[6] << 16 | coeff[7]); + intel_de_write_fw(dev_priv, PIPE_CSC_OUTPUT_COEFF_BV(pipe), + coeff[8] << 16); + + intel_de_write_fw(dev_priv, PIPE_CSC_OUTPUT_POSTOFF_HI(pipe), postoff[0]); + intel_de_write_fw(dev_priv, PIPE_CSC_OUTPUT_POSTOFF_ME(pipe), postoff[1]); + intel_de_write_fw(dev_priv, PIPE_CSC_OUTPUT_POSTOFF_LO(pipe), postoff[2]); +} + +static bool ilk_csc_limited_range(const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + + /* + * FIXME if there's a gamma LUT after the CSC, we should + * do the range compression using the gamma LUT instead. + */ + return crtc_state->limited_color_range && + (IS_HASWELL(dev_priv) || IS_BROADWELL(dev_priv) || + IS_DISPLAY_VER(dev_priv, 9, 10)); +} + +static void ilk_csc_convert_ctm(const struct intel_crtc_state *crtc_state, + u16 coeffs[9]) +{ + const struct drm_color_ctm *ctm = crtc_state->hw.ctm->data; + const u64 *input; + u64 temp[9]; + int i; + + if (ilk_csc_limited_range(crtc_state)) + input = ctm_mult_by_limited(temp, ctm->matrix); + else + input = ctm->matrix; + + /* + * Convert fixed point S31.32 input to format supported by the + * hardware. + */ + for (i = 0; i < 9; i++) { + u64 abs_coeff = ((1ULL << 63) - 1) & input[i]; + + /* + * Clamp input value to min/max supported by + * hardware. + */ + abs_coeff = clamp_val(abs_coeff, 0, CTM_COEFF_4_0 - 1); + + coeffs[i] = 0; + + /* sign bit */ + if (CTM_COEFF_NEGATIVE(input[i])) + coeffs[i] |= 1 << 15; + + if (abs_coeff < CTM_COEFF_0_125) + coeffs[i] |= (3 << 12) | + ILK_CSC_COEFF_FP(abs_coeff, 12); + else if (abs_coeff < CTM_COEFF_0_25) + coeffs[i] |= (2 << 12) | + ILK_CSC_COEFF_FP(abs_coeff, 11); + else if (abs_coeff < CTM_COEFF_0_5) + coeffs[i] |= (1 << 12) | + ILK_CSC_COEFF_FP(abs_coeff, 10); + else if (abs_coeff < CTM_COEFF_1_0) + coeffs[i] |= ILK_CSC_COEFF_FP(abs_coeff, 9); + else if (abs_coeff < CTM_COEFF_2_0) + coeffs[i] |= (7 << 12) | + ILK_CSC_COEFF_FP(abs_coeff, 8); + else + coeffs[i] |= (6 << 12) | + ILK_CSC_COEFF_FP(abs_coeff, 7); + } +} + +static void ilk_load_csc_matrix(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + bool limited_color_range = ilk_csc_limited_range(crtc_state); + + if (crtc_state->hw.ctm) { + u16 coeff[9]; + + ilk_csc_convert_ctm(crtc_state, coeff); + ilk_update_pipe_csc(crtc, ilk_csc_off_zero, coeff, + limited_color_range ? + ilk_csc_postoff_limited_range : + ilk_csc_off_zero); + } else if (crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB) { + ilk_update_pipe_csc(crtc, ilk_csc_off_zero, + ilk_csc_coeff_rgb_to_ycbcr, + ilk_csc_postoff_rgb_to_ycbcr); + } else if (limited_color_range) { + ilk_update_pipe_csc(crtc, ilk_csc_off_zero, + ilk_csc_coeff_limited_range, + ilk_csc_postoff_limited_range); + } else if (crtc_state->csc_enable) { + /* + * On GLK both pipe CSC and degamma LUT are controlled + * by csc_enable. Hence for the cases where the degama + * LUT is needed but CSC is not we need to load an + * identity matrix. + */ + drm_WARN_ON(&dev_priv->drm, !IS_GEMINILAKE(dev_priv)); + + ilk_update_pipe_csc(crtc, ilk_csc_off_zero, + ilk_csc_coeff_identity, + ilk_csc_off_zero); + } +} + +static void icl_load_csc_matrix(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + if (crtc_state->hw.ctm) { + u16 coeff[9]; + + ilk_csc_convert_ctm(crtc_state, coeff); + ilk_update_pipe_csc(crtc, ilk_csc_off_zero, + coeff, ilk_csc_off_zero); + } + + if (crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB) { + icl_update_output_csc(crtc, ilk_csc_off_zero, + ilk_csc_coeff_rgb_to_ycbcr, + ilk_csc_postoff_rgb_to_ycbcr); + } else if (crtc_state->limited_color_range) { + icl_update_output_csc(crtc, ilk_csc_off_zero, + ilk_csc_coeff_limited_range, + ilk_csc_postoff_limited_range); + } +} + +static void chv_load_cgm_csc(struct intel_crtc *crtc, + const struct drm_property_blob *blob) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + const struct drm_color_ctm *ctm = blob->data; + enum pipe pipe = crtc->pipe; + u16 coeffs[9]; + int i; + + for (i = 0; i < ARRAY_SIZE(coeffs); i++) { + u64 abs_coeff = ((1ULL << 63) - 1) & ctm->matrix[i]; + + /* Round coefficient. */ + abs_coeff += 1 << (32 - 13); + /* Clamp to hardware limits. */ + abs_coeff = clamp_val(abs_coeff, 0, CTM_COEFF_8_0 - 1); + + coeffs[i] = 0; + + /* Write coefficients in S3.12 format. */ + if (ctm->matrix[i] & (1ULL << 63)) + coeffs[i] |= 1 << 15; + + coeffs[i] |= ((abs_coeff >> 32) & 7) << 12; + coeffs[i] |= (abs_coeff >> 20) & 0xfff; + } + + intel_de_write_fw(dev_priv, CGM_PIPE_CSC_COEFF01(pipe), + coeffs[1] << 16 | coeffs[0]); + intel_de_write_fw(dev_priv, CGM_PIPE_CSC_COEFF23(pipe), + coeffs[3] << 16 | coeffs[2]); + intel_de_write_fw(dev_priv, CGM_PIPE_CSC_COEFF45(pipe), + coeffs[5] << 16 | coeffs[4]); + intel_de_write_fw(dev_priv, CGM_PIPE_CSC_COEFF67(pipe), + coeffs[7] << 16 | coeffs[6]); + intel_de_write_fw(dev_priv, CGM_PIPE_CSC_COEFF8(pipe), + coeffs[8]); +} + +/* convert hw value with given bit_precision to lut property val */ +static u32 intel_color_lut_pack(u32 val, int bit_precision) +{ + u32 max = 0xffff >> (16 - bit_precision); + + val = clamp_val(val, 0, max); + + if (bit_precision < 16) + val <<= 16 - bit_precision; + + return val; +} + +static u32 i9xx_lut_8(const struct drm_color_lut *color) +{ + return drm_color_lut_extract(color->red, 8) << 16 | + drm_color_lut_extract(color->green, 8) << 8 | + drm_color_lut_extract(color->blue, 8); +} + +static void i9xx_lut_8_pack(struct drm_color_lut *entry, u32 val) +{ + entry->red = intel_color_lut_pack(REG_FIELD_GET(LGC_PALETTE_RED_MASK, val), 8); + entry->green = intel_color_lut_pack(REG_FIELD_GET(LGC_PALETTE_GREEN_MASK, val), 8); + entry->blue = intel_color_lut_pack(REG_FIELD_GET(LGC_PALETTE_BLUE_MASK, val), 8); +} + +/* i965+ "10.6" bit interpolated format "even DW" (low 8 bits) */ +static u32 i965_lut_10p6_ldw(const struct drm_color_lut *color) +{ + return (color->red & 0xff) << 16 | + (color->green & 0xff) << 8 | + (color->blue & 0xff); +} + +/* i965+ "10.6" interpolated format "odd DW" (high 8 bits) */ +static u32 i965_lut_10p6_udw(const struct drm_color_lut *color) +{ + return (color->red >> 8) << 16 | + (color->green >> 8) << 8 | + (color->blue >> 8); +} + +static void i965_lut_10p6_pack(struct drm_color_lut *entry, u32 ldw, u32 udw) +{ + entry->red = REG_FIELD_GET(PALETTE_RED_MASK, udw) << 8 | + REG_FIELD_GET(PALETTE_RED_MASK, ldw); + entry->green = REG_FIELD_GET(PALETTE_GREEN_MASK, udw) << 8 | + REG_FIELD_GET(PALETTE_GREEN_MASK, ldw); + entry->blue = REG_FIELD_GET(PALETTE_BLUE_MASK, udw) << 8 | + REG_FIELD_GET(PALETTE_BLUE_MASK, ldw); +} + +static u16 i965_lut_11p6_max_pack(u32 val) +{ + /* PIPEGCMAX is 11.6, clamp to 10.6 */ + return clamp_val(val, 0, 0xffff); +} + +static u32 ilk_lut_10(const struct drm_color_lut *color) +{ + return drm_color_lut_extract(color->red, 10) << 20 | + drm_color_lut_extract(color->green, 10) << 10 | + drm_color_lut_extract(color->blue, 10); +} + +static void ilk_lut_10_pack(struct drm_color_lut *entry, u32 val) +{ + entry->red = intel_color_lut_pack(REG_FIELD_GET(PREC_PALETTE_RED_MASK, val), 10); + entry->green = intel_color_lut_pack(REG_FIELD_GET(PREC_PALETTE_GREEN_MASK, val), 10); + entry->blue = intel_color_lut_pack(REG_FIELD_GET(PREC_PALETTE_BLUE_MASK, val), 10); +} + +static void icl_lut_multi_seg_pack(struct drm_color_lut *entry, u32 ldw, u32 udw) +{ + entry->red = REG_FIELD_GET(PAL_PREC_MULTI_SEG_RED_UDW_MASK, udw) << 6 | + REG_FIELD_GET(PAL_PREC_MULTI_SEG_RED_LDW_MASK, ldw); + entry->green = REG_FIELD_GET(PAL_PREC_MULTI_SEG_GREEN_UDW_MASK, udw) << 6 | + REG_FIELD_GET(PAL_PREC_MULTI_SEG_GREEN_LDW_MASK, ldw); + entry->blue = REG_FIELD_GET(PAL_PREC_MULTI_SEG_BLUE_UDW_MASK, udw) << 6 | + REG_FIELD_GET(PAL_PREC_MULTI_SEG_BLUE_LDW_MASK, ldw); +} + +static void icl_color_commit_noarm(const struct intel_crtc_state *crtc_state) +{ + icl_load_csc_matrix(crtc_state); +} + +static void skl_color_commit_noarm(const struct intel_crtc_state *crtc_state) +{ + /* + * Possibly related to display WA #1184, SKL CSC loses the latched + * CSC coeff/offset register values if the CSC registers are disarmed + * between DC5 exit and PSR exit. This will cause the plane(s) to + * output all black (until CSC_MODE is rearmed and properly latched). + * Once PSR exit (and proper register latching) has occurred the + * danger is over. Thus when PSR is enabled the CSC coeff/offset + * register programming will be peformed from skl_color_commit_arm() + * which is called after PSR exit. + */ + if (!crtc_state->has_psr) + ilk_load_csc_matrix(crtc_state); +} + +static void ilk_color_commit_noarm(const struct intel_crtc_state *crtc_state) +{ + ilk_load_csc_matrix(crtc_state); +} + +static void i9xx_color_commit_arm(const struct intel_crtc_state *crtc_state) +{ + /* update PIPECONF GAMMA_MODE */ + i9xx_set_pipeconf(crtc_state); +} + +static void ilk_color_commit_arm(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + + /* update PIPECONF GAMMA_MODE */ + ilk_set_pipeconf(crtc_state); + + intel_de_write_fw(dev_priv, PIPE_CSC_MODE(crtc->pipe), + crtc_state->csc_mode); +} + +static void hsw_color_commit_arm(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + + intel_de_write(dev_priv, GAMMA_MODE(crtc->pipe), + crtc_state->gamma_mode); + + intel_de_write_fw(dev_priv, PIPE_CSC_MODE(crtc->pipe), + crtc_state->csc_mode); +} + +static void skl_color_commit_arm(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + u32 val = 0; + + if (crtc_state->has_psr) + ilk_load_csc_matrix(crtc_state); + + /* + * We don't (yet) allow userspace to control the pipe background color, + * so force it to black, but apply pipe gamma and CSC appropriately + * so that its handling will match how we program our planes. + */ + if (crtc_state->gamma_enable) + val |= SKL_BOTTOM_COLOR_GAMMA_ENABLE; + if (crtc_state->csc_enable) + val |= SKL_BOTTOM_COLOR_CSC_ENABLE; + intel_de_write(dev_priv, SKL_BOTTOM_COLOR(pipe), val); + + intel_de_write(dev_priv, GAMMA_MODE(crtc->pipe), + crtc_state->gamma_mode); + + intel_de_write_fw(dev_priv, PIPE_CSC_MODE(crtc->pipe), + crtc_state->csc_mode); +} + +static void i9xx_load_lut_8(struct intel_crtc *crtc, + const struct drm_property_blob *blob) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + const struct drm_color_lut *lut; + enum pipe pipe = crtc->pipe; + int i; + + if (!blob) + return; + + lut = blob->data; + + for (i = 0; i < 256; i++) + intel_de_write_fw(dev_priv, PALETTE(pipe, i), + i9xx_lut_8(&lut[i])); +} + +static void i9xx_load_luts(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + const struct drm_property_blob *gamma_lut = crtc_state->hw.gamma_lut; + + assert_pll_enabled(dev_priv, crtc->pipe); + + i9xx_load_lut_8(crtc, gamma_lut); +} + +static void i965_load_lut_10p6(struct intel_crtc *crtc, + const struct drm_property_blob *blob) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + const struct drm_color_lut *lut = blob->data; + int i, lut_size = drm_color_lut_size(blob); + enum pipe pipe = crtc->pipe; + + for (i = 0; i < lut_size - 1; i++) { + intel_de_write_fw(dev_priv, PALETTE(pipe, 2 * i + 0), + i965_lut_10p6_ldw(&lut[i])); + intel_de_write_fw(dev_priv, PALETTE(pipe, 2 * i + 1), + i965_lut_10p6_udw(&lut[i])); + } + + intel_de_write_fw(dev_priv, PIPEGCMAX(pipe, 0), lut[i].red); + intel_de_write_fw(dev_priv, PIPEGCMAX(pipe, 1), lut[i].green); + intel_de_write_fw(dev_priv, PIPEGCMAX(pipe, 2), lut[i].blue); +} + +static void i965_load_luts(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + const struct drm_property_blob *gamma_lut = crtc_state->hw.gamma_lut; + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DSI)) + assert_dsi_pll_enabled(dev_priv); + else + assert_pll_enabled(dev_priv, crtc->pipe); + + if (crtc_state->gamma_mode == GAMMA_MODE_MODE_8BIT) + i9xx_load_lut_8(crtc, gamma_lut); + else + i965_load_lut_10p6(crtc, gamma_lut); +} + +static void ilk_load_lut_8(struct intel_crtc *crtc, + const struct drm_property_blob *blob) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + const struct drm_color_lut *lut; + enum pipe pipe = crtc->pipe; + int i; + + if (!blob) + return; + + lut = blob->data; + + for (i = 0; i < 256; i++) + intel_de_write_fw(dev_priv, LGC_PALETTE(pipe, i), + i9xx_lut_8(&lut[i])); +} + +static void ilk_load_lut_10(struct intel_crtc *crtc, + const struct drm_property_blob *blob) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + const struct drm_color_lut *lut = blob->data; + int i, lut_size = drm_color_lut_size(blob); + enum pipe pipe = crtc->pipe; + + for (i = 0; i < lut_size; i++) + intel_de_write_fw(dev_priv, PREC_PALETTE(pipe, i), + ilk_lut_10(&lut[i])); +} + +static void ilk_load_luts(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + const struct drm_property_blob *gamma_lut = crtc_state->hw.gamma_lut; + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + ilk_load_lut_8(crtc, gamma_lut); + break; + case GAMMA_MODE_MODE_10BIT: + ilk_load_lut_10(crtc, gamma_lut); + break; + default: + MISSING_CASE(crtc_state->gamma_mode); + break; + } +} + +static int ivb_lut_10_size(u32 prec_index) +{ + if (prec_index & PAL_PREC_SPLIT_MODE) + return 512; + else + return 1024; +} + +/* + * IVB/HSW Bspec / PAL_PREC_INDEX: + * "Restriction : Index auto increment mode is not + * supported and must not be enabled." + */ +static void ivb_load_lut_10(struct intel_crtc *crtc, + const struct drm_property_blob *blob, + u32 prec_index) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + int hw_lut_size = ivb_lut_10_size(prec_index); + const struct drm_color_lut *lut = blob->data; + int i, lut_size = drm_color_lut_size(blob); + enum pipe pipe = crtc->pipe; + + for (i = 0; i < hw_lut_size; i++) { + /* We discard half the user entries in split gamma mode */ + const struct drm_color_lut *entry = + &lut[i * (lut_size - 1) / (hw_lut_size - 1)]; + + intel_de_write_fw(dev_priv, PREC_PAL_INDEX(pipe), prec_index++); + intel_de_write_fw(dev_priv, PREC_PAL_DATA(pipe), + ilk_lut_10(entry)); + } + + /* + * Reset the index, otherwise it prevents the legacy palette to be + * written properly. + */ + intel_de_write_fw(dev_priv, PREC_PAL_INDEX(pipe), 0); +} + +/* On BDW+ the index auto increment mode actually works */ +static void bdw_load_lut_10(struct intel_crtc *crtc, + const struct drm_property_blob *blob, + u32 prec_index) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + int hw_lut_size = ivb_lut_10_size(prec_index); + const struct drm_color_lut *lut = blob->data; + int i, lut_size = drm_color_lut_size(blob); + enum pipe pipe = crtc->pipe; + + intel_de_write_fw(dev_priv, PREC_PAL_INDEX(pipe), + prec_index | PAL_PREC_AUTO_INCREMENT); + + for (i = 0; i < hw_lut_size; i++) { + /* We discard half the user entries in split gamma mode */ + const struct drm_color_lut *entry = + &lut[i * (lut_size - 1) / (hw_lut_size - 1)]; + + intel_de_write_fw(dev_priv, PREC_PAL_DATA(pipe), + ilk_lut_10(entry)); + } + + /* + * Reset the index, otherwise it prevents the legacy palette to be + * written properly. + */ + intel_de_write_fw(dev_priv, PREC_PAL_INDEX(pipe), 0); +} + +static void ivb_load_lut_ext_max(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + + /* Program the max register to clamp values > 1.0. */ + intel_dsb_reg_write(crtc_state, PREC_PAL_EXT_GC_MAX(pipe, 0), 1 << 16); + intel_dsb_reg_write(crtc_state, PREC_PAL_EXT_GC_MAX(pipe, 1), 1 << 16); + intel_dsb_reg_write(crtc_state, PREC_PAL_EXT_GC_MAX(pipe, 2), 1 << 16); + + /* + * Program the gc max 2 register to clamp values > 1.0. + * ToDo: Extend the ABI to be able to program values + * from 3.0 to 7.0 + */ + if (DISPLAY_VER(dev_priv) >= 10) { + intel_dsb_reg_write(crtc_state, PREC_PAL_EXT2_GC_MAX(pipe, 0), + 1 << 16); + intel_dsb_reg_write(crtc_state, PREC_PAL_EXT2_GC_MAX(pipe, 1), + 1 << 16); + intel_dsb_reg_write(crtc_state, PREC_PAL_EXT2_GC_MAX(pipe, 2), + 1 << 16); + } +} + +static void ivb_load_luts(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + const struct drm_property_blob *gamma_lut = crtc_state->hw.gamma_lut; + const struct drm_property_blob *degamma_lut = crtc_state->hw.degamma_lut; + const struct drm_property_blob *blob = gamma_lut ?: degamma_lut; + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + ilk_load_lut_8(crtc, blob); + break; + case GAMMA_MODE_MODE_SPLIT: + ivb_load_lut_10(crtc, degamma_lut, PAL_PREC_SPLIT_MODE | + PAL_PREC_INDEX_VALUE(0)); + ivb_load_lut_ext_max(crtc_state); + ivb_load_lut_10(crtc, gamma_lut, PAL_PREC_SPLIT_MODE | + PAL_PREC_INDEX_VALUE(512)); + break; + case GAMMA_MODE_MODE_10BIT: + ivb_load_lut_10(crtc, blob, + PAL_PREC_INDEX_VALUE(0)); + ivb_load_lut_ext_max(crtc_state); + break; + default: + MISSING_CASE(crtc_state->gamma_mode); + break; + } +} + +static void bdw_load_luts(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + const struct drm_property_blob *gamma_lut = crtc_state->hw.gamma_lut; + const struct drm_property_blob *degamma_lut = crtc_state->hw.degamma_lut; + const struct drm_property_blob *blob = gamma_lut ?: degamma_lut; + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + ilk_load_lut_8(crtc, blob); + break; + case GAMMA_MODE_MODE_SPLIT: + bdw_load_lut_10(crtc, degamma_lut, PAL_PREC_SPLIT_MODE | + PAL_PREC_INDEX_VALUE(0)); + ivb_load_lut_ext_max(crtc_state); + bdw_load_lut_10(crtc, gamma_lut, PAL_PREC_SPLIT_MODE | + PAL_PREC_INDEX_VALUE(512)); + break; + case GAMMA_MODE_MODE_10BIT: + + bdw_load_lut_10(crtc, blob, + PAL_PREC_INDEX_VALUE(0)); + ivb_load_lut_ext_max(crtc_state); + break; + default: + MISSING_CASE(crtc_state->gamma_mode); + break; + } +} + +static int glk_degamma_lut_size(struct drm_i915_private *i915) +{ + if (DISPLAY_VER(i915) >= 13) + return 131; + else + return 35; +} + +static void glk_load_degamma_lut(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + int i, lut_size = INTEL_INFO(dev_priv)->display.color.degamma_lut_size; + const struct drm_color_lut *lut = crtc_state->hw.degamma_lut->data; + + /* + * When setting the auto-increment bit, the hardware seems to + * ignore the index bits, so we need to reset it to index 0 + * separately. + */ + intel_de_write_fw(dev_priv, PRE_CSC_GAMC_INDEX(pipe), 0); + intel_de_write_fw(dev_priv, PRE_CSC_GAMC_INDEX(pipe), + PRE_CSC_GAMC_AUTO_INCREMENT); + + for (i = 0; i < lut_size; i++) { + /* + * First lut_size entries represent range from 0 to 1.0 + * 3 additional lut entries will represent extended range + * inputs 3.0 and 7.0 respectively, currently clamped + * at 1.0. Since the precision is 16bit, the user + * value can be directly filled to register. + * The pipe degamma table in GLK+ onwards doesn't + * support different values per channel, so this just + * programs green value which will be equal to Red and + * Blue into the lut registers. + * ToDo: Extend to max 7.0. Enable 32 bit input value + * as compared to just 16 to achieve this. + */ + intel_de_write_fw(dev_priv, PRE_CSC_GAMC_DATA(pipe), + lut[i].green); + } + + /* Clamp values > 1.0. */ + while (i++ < glk_degamma_lut_size(dev_priv)) + intel_de_write_fw(dev_priv, PRE_CSC_GAMC_DATA(pipe), 1 << 16); + + intel_de_write_fw(dev_priv, PRE_CSC_GAMC_INDEX(pipe), 0); +} + +static void glk_load_degamma_lut_linear(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + int i, lut_size = INTEL_INFO(dev_priv)->display.color.degamma_lut_size; + + /* + * When setting the auto-increment bit, the hardware seems to + * ignore the index bits, so we need to reset it to index 0 + * separately. + */ + intel_de_write_fw(dev_priv, PRE_CSC_GAMC_INDEX(pipe), 0); + intel_de_write_fw(dev_priv, PRE_CSC_GAMC_INDEX(pipe), + PRE_CSC_GAMC_AUTO_INCREMENT); + + for (i = 0; i < lut_size; i++) { + u32 v = (i << 16) / (lut_size - 1); + + intel_de_write_fw(dev_priv, PRE_CSC_GAMC_DATA(pipe), v); + } + + /* Clamp values > 1.0. */ + while (i++ < 35) + intel_de_write_fw(dev_priv, PRE_CSC_GAMC_DATA(pipe), 1 << 16); + + intel_de_write_fw(dev_priv, PRE_CSC_GAMC_INDEX(pipe), 0); +} + +static void glk_load_luts(const struct intel_crtc_state *crtc_state) +{ + const struct drm_property_blob *gamma_lut = crtc_state->hw.gamma_lut; + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + /* + * On GLK+ both pipe CSC and degamma LUT are controlled + * by csc_enable. Hence for the cases where the CSC is + * needed but degamma LUT is not we need to load a + * linear degamma LUT. In fact we'll just always load + * the degama LUT so that we don't have to reload + * it every time the pipe CSC is being enabled. + */ + if (crtc_state->hw.degamma_lut) + glk_load_degamma_lut(crtc_state); + else + glk_load_degamma_lut_linear(crtc_state); + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + ilk_load_lut_8(crtc, gamma_lut); + break; + case GAMMA_MODE_MODE_10BIT: + bdw_load_lut_10(crtc, gamma_lut, PAL_PREC_INDEX_VALUE(0)); + ivb_load_lut_ext_max(crtc_state); + break; + default: + MISSING_CASE(crtc_state->gamma_mode); + break; + } +} + +/* ilk+ "12.4" interpolated format (high 10 bits) */ +static u32 ilk_lut_12p4_udw(const struct drm_color_lut *color) +{ + return (color->red >> 6) << 20 | (color->green >> 6) << 10 | + (color->blue >> 6); +} + +/* ilk+ "12.4" interpolated format (low 6 bits) */ +static u32 ilk_lut_12p4_ldw(const struct drm_color_lut *color) +{ + return (color->red & 0x3f) << 24 | (color->green & 0x3f) << 14 | + (color->blue & 0x3f) << 4; +} + +static void +icl_load_gcmax(const struct intel_crtc_state *crtc_state, + const struct drm_color_lut *color) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + enum pipe pipe = crtc->pipe; + + /* FIXME LUT entries are 16 bit only, so we can prog 0xFFFF max */ + intel_dsb_reg_write(crtc_state, PREC_PAL_GC_MAX(pipe, 0), color->red); + intel_dsb_reg_write(crtc_state, PREC_PAL_GC_MAX(pipe, 1), color->green); + intel_dsb_reg_write(crtc_state, PREC_PAL_GC_MAX(pipe, 2), color->blue); +} + +static void +icl_program_gamma_superfine_segment(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + const struct drm_property_blob *blob = crtc_state->hw.gamma_lut; + const struct drm_color_lut *lut = blob->data; + enum pipe pipe = crtc->pipe; + int i; + + /* + * Program Super Fine segment (let's call it seg1)... + * + * Super Fine segment's step is 1/(8 * 128 * 256) and it has + * 9 entries, corresponding to values 0, 1/(8 * 128 * 256), + * 2/(8 * 128 * 256) ... 8/(8 * 128 * 256). + */ + intel_dsb_reg_write(crtc_state, PREC_PAL_MULTI_SEG_INDEX(pipe), + PAL_PREC_AUTO_INCREMENT); + + for (i = 0; i < 9; i++) { + const struct drm_color_lut *entry = &lut[i]; + + intel_dsb_indexed_reg_write(crtc_state, PREC_PAL_MULTI_SEG_DATA(pipe), + ilk_lut_12p4_ldw(entry)); + intel_dsb_indexed_reg_write(crtc_state, PREC_PAL_MULTI_SEG_DATA(pipe), + ilk_lut_12p4_udw(entry)); + } +} + +static void +icl_program_gamma_multi_segment(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + const struct drm_property_blob *blob = crtc_state->hw.gamma_lut; + const struct drm_color_lut *lut = blob->data; + const struct drm_color_lut *entry; + enum pipe pipe = crtc->pipe; + int i; + + /* + * Program Fine segment (let's call it seg2)... + * + * Fine segment's step is 1/(128 * 256) i.e. 1/(128 * 256), 2/(128 * 256) + * ... 256/(128 * 256). So in order to program fine segment of LUT we + * need to pick every 8th entry in the LUT, and program 256 indexes. + * + * PAL_PREC_INDEX[0] and PAL_PREC_INDEX[1] map to seg2[1], + * seg2[0] being unused by the hardware. + */ + intel_dsb_reg_write(crtc_state, PREC_PAL_INDEX(pipe), + PAL_PREC_AUTO_INCREMENT); + for (i = 1; i < 257; i++) { + entry = &lut[i * 8]; + intel_dsb_indexed_reg_write(crtc_state, PREC_PAL_DATA(pipe), + ilk_lut_12p4_ldw(entry)); + intel_dsb_indexed_reg_write(crtc_state, PREC_PAL_DATA(pipe), + ilk_lut_12p4_udw(entry)); + } + + /* + * Program Coarse segment (let's call it seg3)... + * + * Coarse segment starts from index 0 and it's step is 1/256 ie 0, + * 1/256, 2/256 ... 256/256. As per the description of each entry in LUT + * above, we need to pick every (8 * 128)th entry in LUT, and + * program 256 of those. + * + * Spec is not very clear about if entries seg3[0] and seg3[1] are + * being used or not, but we still need to program these to advance + * the index. + */ + for (i = 0; i < 256; i++) { + entry = &lut[i * 8 * 128]; + intel_dsb_indexed_reg_write(crtc_state, PREC_PAL_DATA(pipe), + ilk_lut_12p4_ldw(entry)); + intel_dsb_indexed_reg_write(crtc_state, PREC_PAL_DATA(pipe), + ilk_lut_12p4_udw(entry)); + } + + /* The last entry in the LUT is to be programmed in GCMAX */ + entry = &lut[256 * 8 * 128]; + icl_load_gcmax(crtc_state, entry); + ivb_load_lut_ext_max(crtc_state); +} + +static void icl_load_luts(const struct intel_crtc_state *crtc_state) +{ + const struct drm_property_blob *gamma_lut = crtc_state->hw.gamma_lut; + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + if (crtc_state->hw.degamma_lut) + glk_load_degamma_lut(crtc_state); + + switch (crtc_state->gamma_mode & GAMMA_MODE_MODE_MASK) { + case GAMMA_MODE_MODE_8BIT: + ilk_load_lut_8(crtc, gamma_lut); + break; + case GAMMA_MODE_MODE_12BIT_MULTI_SEGMENTED: + icl_program_gamma_superfine_segment(crtc_state); + icl_program_gamma_multi_segment(crtc_state); + break; + case GAMMA_MODE_MODE_10BIT: + bdw_load_lut_10(crtc, gamma_lut, PAL_PREC_INDEX_VALUE(0)); + ivb_load_lut_ext_max(crtc_state); + break; + default: + MISSING_CASE(crtc_state->gamma_mode); + break; + } + + intel_dsb_commit(crtc_state); +} + +static u32 chv_cgm_degamma_ldw(const struct drm_color_lut *color) +{ + return drm_color_lut_extract(color->green, 14) << 16 | + drm_color_lut_extract(color->blue, 14); +} + +static u32 chv_cgm_degamma_udw(const struct drm_color_lut *color) +{ + return drm_color_lut_extract(color->red, 14); +} + +static void chv_load_cgm_degamma(struct intel_crtc *crtc, + const struct drm_property_blob *blob) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + const struct drm_color_lut *lut = blob->data; + int i, lut_size = drm_color_lut_size(blob); + enum pipe pipe = crtc->pipe; + + for (i = 0; i < lut_size; i++) { + intel_de_write_fw(dev_priv, CGM_PIPE_DEGAMMA(pipe, i, 0), + chv_cgm_degamma_ldw(&lut[i])); + intel_de_write_fw(dev_priv, CGM_PIPE_DEGAMMA(pipe, i, 1), + chv_cgm_degamma_udw(&lut[i])); + } +} + +static u32 chv_cgm_gamma_ldw(const struct drm_color_lut *color) +{ + return drm_color_lut_extract(color->green, 10) << 16 | + drm_color_lut_extract(color->blue, 10); +} + +static u32 chv_cgm_gamma_udw(const struct drm_color_lut *color) +{ + return drm_color_lut_extract(color->red, 10); +} + +static void chv_cgm_gamma_pack(struct drm_color_lut *entry, u32 ldw, u32 udw) +{ + entry->green = intel_color_lut_pack(REG_FIELD_GET(CGM_PIPE_GAMMA_GREEN_MASK, ldw), 10); + entry->blue = intel_color_lut_pack(REG_FIELD_GET(CGM_PIPE_GAMMA_BLUE_MASK, ldw), 10); + entry->red = intel_color_lut_pack(REG_FIELD_GET(CGM_PIPE_GAMMA_RED_MASK, udw), 10); +} + +static void chv_load_cgm_gamma(struct intel_crtc *crtc, + const struct drm_property_blob *blob) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + const struct drm_color_lut *lut = blob->data; + int i, lut_size = drm_color_lut_size(blob); + enum pipe pipe = crtc->pipe; + + for (i = 0; i < lut_size; i++) { + intel_de_write_fw(dev_priv, CGM_PIPE_GAMMA(pipe, i, 0), + chv_cgm_gamma_ldw(&lut[i])); + intel_de_write_fw(dev_priv, CGM_PIPE_GAMMA(pipe, i, 1), + chv_cgm_gamma_udw(&lut[i])); + } +} + +static void chv_load_luts(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + const struct drm_property_blob *degamma_lut = crtc_state->hw.degamma_lut; + const struct drm_property_blob *gamma_lut = crtc_state->hw.gamma_lut; + const struct drm_property_blob *ctm = crtc_state->hw.ctm; + + if (crtc_state->cgm_mode & CGM_PIPE_MODE_CSC) + chv_load_cgm_csc(crtc, ctm); + + if (crtc_state->cgm_mode & CGM_PIPE_MODE_DEGAMMA) + chv_load_cgm_degamma(crtc, degamma_lut); + + if (crtc_state->cgm_mode & CGM_PIPE_MODE_GAMMA) + chv_load_cgm_gamma(crtc, gamma_lut); + else + i965_load_luts(crtc_state); + + intel_de_write_fw(dev_priv, CGM_PIPE_MODE(crtc->pipe), + crtc_state->cgm_mode); +} + +void intel_color_load_luts(const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + + dev_priv->display.funcs.color->load_luts(crtc_state); +} + +void intel_color_commit_noarm(const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + + if (dev_priv->display.funcs.color->color_commit_noarm) + dev_priv->display.funcs.color->color_commit_noarm(crtc_state); +} + +void intel_color_commit_arm(const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + + dev_priv->display.funcs.color->color_commit_arm(crtc_state); +} + +static bool intel_can_preload_luts(const struct intel_crtc_state *new_crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(new_crtc_state->uapi.crtc); + struct intel_atomic_state *state = + to_intel_atomic_state(new_crtc_state->uapi.state); + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + + return !old_crtc_state->hw.gamma_lut && + !old_crtc_state->hw.degamma_lut; +} + +static bool chv_can_preload_luts(const struct intel_crtc_state *new_crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(new_crtc_state->uapi.crtc); + struct intel_atomic_state *state = + to_intel_atomic_state(new_crtc_state->uapi.state); + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + + /* + * CGM_PIPE_MODE is itself single buffered. We'd have to + * somehow split it out from chv_load_luts() if we wanted + * the ability to preload the CGM LUTs/CSC without tearing. + */ + if (old_crtc_state->cgm_mode || new_crtc_state->cgm_mode) + return false; + + return !old_crtc_state->hw.gamma_lut; +} + +static bool glk_can_preload_luts(const struct intel_crtc_state *new_crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(new_crtc_state->uapi.crtc); + struct intel_atomic_state *state = + to_intel_atomic_state(new_crtc_state->uapi.state); + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + + /* + * The hardware degamma is active whenever the pipe + * CSC is active. Thus even if the old state has no + * software degamma we need to avoid clobbering the + * linear hardware degamma mid scanout. + */ + return !old_crtc_state->csc_enable && + !old_crtc_state->hw.gamma_lut; +} + +int intel_color_check(struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + + return dev_priv->display.funcs.color->color_check(crtc_state); +} + +void intel_color_get_config(struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + + if (dev_priv->display.funcs.color->read_luts) + dev_priv->display.funcs.color->read_luts(crtc_state); +} + +static bool need_plane_update(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(plane->base.dev); + + /* + * On pre-SKL the pipe gamma enable and pipe csc enable for + * the pipe bottom color are configured via the primary plane. + * We have to reconfigure that even if the plane is inactive. + */ + return crtc_state->active_planes & BIT(plane->id) || + (DISPLAY_VER(dev_priv) < 9 && + plane->id == PLANE_PRIMARY); +} + +static int +intel_color_add_affected_planes(struct intel_crtc_state *new_crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(new_crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_atomic_state *state = + to_intel_atomic_state(new_crtc_state->uapi.state); + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + struct intel_plane *plane; + + if (!new_crtc_state->hw.active || + drm_atomic_crtc_needs_modeset(&new_crtc_state->uapi)) + return 0; + + if (new_crtc_state->gamma_enable == old_crtc_state->gamma_enable && + new_crtc_state->csc_enable == old_crtc_state->csc_enable) + return 0; + + for_each_intel_plane_on_crtc(&dev_priv->drm, crtc, plane) { + struct intel_plane_state *plane_state; + + if (!need_plane_update(plane, new_crtc_state)) + continue; + + plane_state = intel_atomic_get_plane_state(state, plane); + if (IS_ERR(plane_state)) + return PTR_ERR(plane_state); + + new_crtc_state->update_planes |= BIT(plane->id); + } + + return 0; +} + +static int check_lut_size(const struct drm_property_blob *lut, int expected) +{ + int len; + + if (!lut) + return 0; + + len = drm_color_lut_size(lut); + if (len != expected) { + DRM_DEBUG_KMS("Invalid LUT size; got %d, expected %d\n", + len, expected); + return -EINVAL; + } + + return 0; +} + +static int check_luts(const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + const struct drm_property_blob *gamma_lut = crtc_state->hw.gamma_lut; + const struct drm_property_blob *degamma_lut = crtc_state->hw.degamma_lut; + int gamma_length, degamma_length; + u32 gamma_tests, degamma_tests; + + /* Always allow legacy gamma LUT with no further checking. */ + if (crtc_state_is_legacy_gamma(crtc_state)) + return 0; + + /* C8 relies on its palette being stored in the legacy LUT */ + if (crtc_state->c8_planes) { + drm_dbg_kms(&dev_priv->drm, + "C8 pixelformat requires the legacy LUT\n"); + return -EINVAL; + } + + degamma_length = INTEL_INFO(dev_priv)->display.color.degamma_lut_size; + gamma_length = INTEL_INFO(dev_priv)->display.color.gamma_lut_size; + degamma_tests = INTEL_INFO(dev_priv)->display.color.degamma_lut_tests; + gamma_tests = INTEL_INFO(dev_priv)->display.color.gamma_lut_tests; + + if (check_lut_size(degamma_lut, degamma_length) || + check_lut_size(gamma_lut, gamma_length)) + return -EINVAL; + + if (drm_color_lut_check(degamma_lut, degamma_tests) || + drm_color_lut_check(gamma_lut, gamma_tests)) + return -EINVAL; + + return 0; +} + +static u32 i9xx_gamma_mode(struct intel_crtc_state *crtc_state) +{ + if (!crtc_state->gamma_enable || + crtc_state_is_legacy_gamma(crtc_state)) + return GAMMA_MODE_MODE_8BIT; + else + return GAMMA_MODE_MODE_10BIT; /* i965+ only */ +} + +static int i9xx_color_check(struct intel_crtc_state *crtc_state) +{ + int ret; + + ret = check_luts(crtc_state); + if (ret) + return ret; + + crtc_state->gamma_enable = + crtc_state->hw.gamma_lut && + !crtc_state->c8_planes; + + crtc_state->gamma_mode = i9xx_gamma_mode(crtc_state); + + ret = intel_color_add_affected_planes(crtc_state); + if (ret) + return ret; + + crtc_state->preload_luts = intel_can_preload_luts(crtc_state); + + return 0; +} + +static u32 chv_cgm_mode(const struct intel_crtc_state *crtc_state) +{ + u32 cgm_mode = 0; + + if (crtc_state_is_legacy_gamma(crtc_state)) + return 0; + + if (crtc_state->hw.degamma_lut) + cgm_mode |= CGM_PIPE_MODE_DEGAMMA; + if (crtc_state->hw.ctm) + cgm_mode |= CGM_PIPE_MODE_CSC; + if (crtc_state->hw.gamma_lut) + cgm_mode |= CGM_PIPE_MODE_GAMMA; + + return cgm_mode; +} + +/* + * CHV color pipeline: + * u0.10 -> CGM degamma -> u0.14 -> CGM csc -> u0.14 -> CGM gamma -> + * u0.10 -> WGC csc -> u0.10 -> pipe gamma -> u0.10 + * + * We always bypass the WGC csc and use the CGM csc + * instead since it has degamma and better precision. + */ +static int chv_color_check(struct intel_crtc_state *crtc_state) +{ + int ret; + + ret = check_luts(crtc_state); + if (ret) + return ret; + + /* + * Pipe gamma will be used only for the legacy LUT. + * Otherwise we bypass it and use the CGM gamma instead. + */ + crtc_state->gamma_enable = + crtc_state_is_legacy_gamma(crtc_state) && + !crtc_state->c8_planes; + + crtc_state->gamma_mode = GAMMA_MODE_MODE_8BIT; + + crtc_state->cgm_mode = chv_cgm_mode(crtc_state); + + ret = intel_color_add_affected_planes(crtc_state); + if (ret) + return ret; + + crtc_state->preload_luts = chv_can_preload_luts(crtc_state); + + return 0; +} + +static u32 ilk_gamma_mode(const struct intel_crtc_state *crtc_state) +{ + if (!crtc_state->gamma_enable || + crtc_state_is_legacy_gamma(crtc_state)) + return GAMMA_MODE_MODE_8BIT; + else + return GAMMA_MODE_MODE_10BIT; +} + +static u32 ilk_csc_mode(const struct intel_crtc_state *crtc_state) +{ + /* + * CSC comes after the LUT in RGB->YCbCr mode. + * RGB->YCbCr needs the limited range offsets added to + * the output. RGB limited range output is handled by + * the hw automagically elsewhere. + */ + if (crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB) + return CSC_BLACK_SCREEN_OFFSET; + + return CSC_MODE_YUV_TO_RGB | + CSC_POSITION_BEFORE_GAMMA; +} + +static int ilk_color_check(struct intel_crtc_state *crtc_state) +{ + int ret; + + ret = check_luts(crtc_state); + if (ret) + return ret; + + crtc_state->gamma_enable = + crtc_state->hw.gamma_lut && + !crtc_state->c8_planes; + + /* + * We don't expose the ctm on ilk/snb currently, also RGB + * limited range output is handled by the hw automagically. + */ + crtc_state->csc_enable = + crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB; + + crtc_state->gamma_mode = ilk_gamma_mode(crtc_state); + + crtc_state->csc_mode = ilk_csc_mode(crtc_state); + + ret = intel_color_add_affected_planes(crtc_state); + if (ret) + return ret; + + crtc_state->preload_luts = intel_can_preload_luts(crtc_state); + + return 0; +} + +static u32 ivb_gamma_mode(const struct intel_crtc_state *crtc_state) +{ + if (!crtc_state->gamma_enable || + crtc_state_is_legacy_gamma(crtc_state)) + return GAMMA_MODE_MODE_8BIT; + else if (crtc_state->hw.gamma_lut && + crtc_state->hw.degamma_lut) + return GAMMA_MODE_MODE_SPLIT; + else + return GAMMA_MODE_MODE_10BIT; +} + +static u32 ivb_csc_mode(const struct intel_crtc_state *crtc_state) +{ + bool limited_color_range = ilk_csc_limited_range(crtc_state); + + /* + * CSC comes after the LUT in degamma, RGB->YCbCr, + * and RGB full->limited range mode. + */ + if (crtc_state->hw.degamma_lut || + crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB || + limited_color_range) + return 0; + + return CSC_POSITION_BEFORE_GAMMA; +} + +static int ivb_color_check(struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + bool limited_color_range = ilk_csc_limited_range(crtc_state); + int ret; + + ret = check_luts(crtc_state); + if (ret) + return ret; + + if (crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB && + crtc_state->hw.ctm) { + drm_dbg_kms(&dev_priv->drm, + "YCBCR and CTM together are not possible\n"); + return -EINVAL; + } + + crtc_state->gamma_enable = + (crtc_state->hw.gamma_lut || + crtc_state->hw.degamma_lut) && + !crtc_state->c8_planes; + + crtc_state->csc_enable = + crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB || + crtc_state->hw.ctm || limited_color_range; + + crtc_state->gamma_mode = ivb_gamma_mode(crtc_state); + + crtc_state->csc_mode = ivb_csc_mode(crtc_state); + + ret = intel_color_add_affected_planes(crtc_state); + if (ret) + return ret; + + crtc_state->preload_luts = intel_can_preload_luts(crtc_state); + + return 0; +} + +static u32 glk_gamma_mode(const struct intel_crtc_state *crtc_state) +{ + if (!crtc_state->gamma_enable || + crtc_state_is_legacy_gamma(crtc_state)) + return GAMMA_MODE_MODE_8BIT; + else + return GAMMA_MODE_MODE_10BIT; +} + +static int glk_color_check(struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + int ret; + + ret = check_luts(crtc_state); + if (ret) + return ret; + + if (crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB && + crtc_state->hw.ctm) { + drm_dbg_kms(&dev_priv->drm, + "YCBCR and CTM together are not possible\n"); + return -EINVAL; + } + + crtc_state->gamma_enable = + crtc_state->hw.gamma_lut && + !crtc_state->c8_planes; + + /* On GLK+ degamma LUT is controlled by csc_enable */ + crtc_state->csc_enable = + crtc_state->hw.degamma_lut || + crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB || + crtc_state->hw.ctm || crtc_state->limited_color_range; + + crtc_state->gamma_mode = glk_gamma_mode(crtc_state); + + crtc_state->csc_mode = 0; + + ret = intel_color_add_affected_planes(crtc_state); + if (ret) + return ret; + + crtc_state->preload_luts = glk_can_preload_luts(crtc_state); + + return 0; +} + +static u32 icl_gamma_mode(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + u32 gamma_mode = 0; + + if (crtc_state->hw.degamma_lut) + gamma_mode |= PRE_CSC_GAMMA_ENABLE; + + if (crtc_state->hw.gamma_lut && + !crtc_state->c8_planes) + gamma_mode |= POST_CSC_GAMMA_ENABLE; + + if (!crtc_state->hw.gamma_lut || + crtc_state_is_legacy_gamma(crtc_state)) + gamma_mode |= GAMMA_MODE_MODE_8BIT; + /* + * Enable 10bit gamma for D13 + * ToDo: Extend to Logarithmic Gamma once the new UAPI + * is accepted and implemented by a userspace consumer + */ + else if (DISPLAY_VER(i915) >= 13) + gamma_mode |= GAMMA_MODE_MODE_10BIT; + else + gamma_mode |= GAMMA_MODE_MODE_12BIT_MULTI_SEGMENTED; + + return gamma_mode; +} + +static u32 icl_csc_mode(const struct intel_crtc_state *crtc_state) +{ + u32 csc_mode = 0; + + if (crtc_state->hw.ctm) + csc_mode |= ICL_CSC_ENABLE; + + if (crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB || + crtc_state->limited_color_range) + csc_mode |= ICL_OUTPUT_CSC_ENABLE; + + return csc_mode; +} + +static int icl_color_check(struct intel_crtc_state *crtc_state) +{ + int ret; + + ret = check_luts(crtc_state); + if (ret) + return ret; + + crtc_state->gamma_mode = icl_gamma_mode(crtc_state); + + crtc_state->csc_mode = icl_csc_mode(crtc_state); + + crtc_state->preload_luts = intel_can_preload_luts(crtc_state); + + return 0; +} + +static int i9xx_gamma_precision(const struct intel_crtc_state *crtc_state) +{ + if (!crtc_state->gamma_enable) + return 0; + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + return 8; + case GAMMA_MODE_MODE_10BIT: + return 16; + default: + MISSING_CASE(crtc_state->gamma_mode); + return 0; + } +} + +static int ilk_gamma_precision(const struct intel_crtc_state *crtc_state) +{ + if (!crtc_state->gamma_enable) + return 0; + + if ((crtc_state->csc_mode & CSC_POSITION_BEFORE_GAMMA) == 0) + return 0; + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + return 8; + case GAMMA_MODE_MODE_10BIT: + return 10; + default: + MISSING_CASE(crtc_state->gamma_mode); + return 0; + } +} + +static int chv_gamma_precision(const struct intel_crtc_state *crtc_state) +{ + if (crtc_state->cgm_mode & CGM_PIPE_MODE_GAMMA) + return 10; + else + return i9xx_gamma_precision(crtc_state); +} + +static int glk_gamma_precision(const struct intel_crtc_state *crtc_state) +{ + if (!crtc_state->gamma_enable) + return 0; + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + return 8; + case GAMMA_MODE_MODE_10BIT: + return 10; + default: + MISSING_CASE(crtc_state->gamma_mode); + return 0; + } +} + +static int icl_gamma_precision(const struct intel_crtc_state *crtc_state) +{ + if ((crtc_state->gamma_mode & POST_CSC_GAMMA_ENABLE) == 0) + return 0; + + switch (crtc_state->gamma_mode & GAMMA_MODE_MODE_MASK) { + case GAMMA_MODE_MODE_8BIT: + return 8; + case GAMMA_MODE_MODE_10BIT: + return 10; + case GAMMA_MODE_MODE_12BIT_MULTI_SEGMENTED: + return 16; + default: + MISSING_CASE(crtc_state->gamma_mode); + return 0; + } +} + +int intel_color_get_gamma_bit_precision(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + + if (HAS_GMCH(dev_priv)) { + if (IS_CHERRYVIEW(dev_priv)) + return chv_gamma_precision(crtc_state); + else + return i9xx_gamma_precision(crtc_state); + } else { + if (DISPLAY_VER(dev_priv) >= 11) + return icl_gamma_precision(crtc_state); + else if (DISPLAY_VER(dev_priv) == 10) + return glk_gamma_precision(crtc_state); + else if (IS_IRONLAKE(dev_priv)) + return ilk_gamma_precision(crtc_state); + } + + return 0; +} + +static bool err_check(struct drm_color_lut *lut1, + struct drm_color_lut *lut2, u32 err) +{ + return ((abs((long)lut2->red - lut1->red)) <= err) && + ((abs((long)lut2->blue - lut1->blue)) <= err) && + ((abs((long)lut2->green - lut1->green)) <= err); +} + +static bool intel_color_lut_entries_equal(struct drm_color_lut *lut1, + struct drm_color_lut *lut2, + int lut_size, u32 err) +{ + int i; + + for (i = 0; i < lut_size; i++) { + if (!err_check(&lut1[i], &lut2[i], err)) + return false; + } + + return true; +} + +bool intel_color_lut_equal(struct drm_property_blob *blob1, + struct drm_property_blob *blob2, + u32 gamma_mode, u32 bit_precision) +{ + struct drm_color_lut *lut1, *lut2; + int lut_size1, lut_size2; + u32 err; + + if (!blob1 != !blob2) + return false; + + if (!blob1) + return true; + + lut_size1 = drm_color_lut_size(blob1); + lut_size2 = drm_color_lut_size(blob2); + + /* check sw and hw lut size */ + if (lut_size1 != lut_size2) + return false; + + lut1 = blob1->data; + lut2 = blob2->data; + + err = 0xffff >> bit_precision; + + /* check sw and hw lut entry to be equal */ + switch (gamma_mode & GAMMA_MODE_MODE_MASK) { + case GAMMA_MODE_MODE_8BIT: + case GAMMA_MODE_MODE_10BIT: + if (!intel_color_lut_entries_equal(lut1, lut2, + lut_size2, err)) + return false; + break; + case GAMMA_MODE_MODE_12BIT_MULTI_SEGMENTED: + if (!intel_color_lut_entries_equal(lut1, lut2, + 9, err)) + return false; + break; + default: + MISSING_CASE(gamma_mode); + return false; + } + + return true; +} + +static struct drm_property_blob *i9xx_read_lut_8(struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + struct drm_property_blob *blob; + struct drm_color_lut *lut; + int i; + + blob = drm_property_create_blob(&dev_priv->drm, + sizeof(struct drm_color_lut) * LEGACY_LUT_LENGTH, + NULL); + if (IS_ERR(blob)) + return NULL; + + lut = blob->data; + + for (i = 0; i < LEGACY_LUT_LENGTH; i++) { + u32 val = intel_de_read_fw(dev_priv, PALETTE(pipe, i)); + + i9xx_lut_8_pack(&lut[i], val); + } + + return blob; +} + +static void i9xx_read_luts(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + if (!crtc_state->gamma_enable) + return; + + crtc_state->hw.gamma_lut = i9xx_read_lut_8(crtc); +} + +static struct drm_property_blob *i965_read_lut_10p6(struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + int i, lut_size = INTEL_INFO(dev_priv)->display.color.gamma_lut_size; + enum pipe pipe = crtc->pipe; + struct drm_property_blob *blob; + struct drm_color_lut *lut; + + blob = drm_property_create_blob(&dev_priv->drm, + sizeof(struct drm_color_lut) * lut_size, + NULL); + if (IS_ERR(blob)) + return NULL; + + lut = blob->data; + + for (i = 0; i < lut_size - 1; i++) { + u32 ldw = intel_de_read_fw(dev_priv, PALETTE(pipe, 2 * i + 0)); + u32 udw = intel_de_read_fw(dev_priv, PALETTE(pipe, 2 * i + 1)); + + i965_lut_10p6_pack(&lut[i], ldw, udw); + } + + lut[i].red = i965_lut_11p6_max_pack(intel_de_read_fw(dev_priv, PIPEGCMAX(pipe, 0))); + lut[i].green = i965_lut_11p6_max_pack(intel_de_read_fw(dev_priv, PIPEGCMAX(pipe, 1))); + lut[i].blue = i965_lut_11p6_max_pack(intel_de_read_fw(dev_priv, PIPEGCMAX(pipe, 2))); + + return blob; +} + +static void i965_read_luts(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + if (!crtc_state->gamma_enable) + return; + + if (crtc_state->gamma_mode == GAMMA_MODE_MODE_8BIT) + crtc_state->hw.gamma_lut = i9xx_read_lut_8(crtc); + else + crtc_state->hw.gamma_lut = i965_read_lut_10p6(crtc); +} + +static struct drm_property_blob *chv_read_cgm_gamma(struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + int i, lut_size = INTEL_INFO(dev_priv)->display.color.gamma_lut_size; + enum pipe pipe = crtc->pipe; + struct drm_property_blob *blob; + struct drm_color_lut *lut; + + blob = drm_property_create_blob(&dev_priv->drm, + sizeof(struct drm_color_lut) * lut_size, + NULL); + if (IS_ERR(blob)) + return NULL; + + lut = blob->data; + + for (i = 0; i < lut_size; i++) { + u32 ldw = intel_de_read_fw(dev_priv, CGM_PIPE_GAMMA(pipe, i, 0)); + u32 udw = intel_de_read_fw(dev_priv, CGM_PIPE_GAMMA(pipe, i, 1)); + + chv_cgm_gamma_pack(&lut[i], ldw, udw); + } + + return blob; +} + +static void chv_read_luts(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + if (crtc_state->cgm_mode & CGM_PIPE_MODE_GAMMA) + crtc_state->hw.gamma_lut = chv_read_cgm_gamma(crtc); + else + i965_read_luts(crtc_state); +} + +static struct drm_property_blob *ilk_read_lut_8(struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + struct drm_property_blob *blob; + struct drm_color_lut *lut; + int i; + + blob = drm_property_create_blob(&dev_priv->drm, + sizeof(struct drm_color_lut) * LEGACY_LUT_LENGTH, + NULL); + if (IS_ERR(blob)) + return NULL; + + lut = blob->data; + + for (i = 0; i < LEGACY_LUT_LENGTH; i++) { + u32 val = intel_de_read_fw(dev_priv, LGC_PALETTE(pipe, i)); + + i9xx_lut_8_pack(&lut[i], val); + } + + return blob; +} + +static struct drm_property_blob *ilk_read_lut_10(struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + int i, lut_size = INTEL_INFO(dev_priv)->display.color.gamma_lut_size; + enum pipe pipe = crtc->pipe; + struct drm_property_blob *blob; + struct drm_color_lut *lut; + + blob = drm_property_create_blob(&dev_priv->drm, + sizeof(struct drm_color_lut) * lut_size, + NULL); + if (IS_ERR(blob)) + return NULL; + + lut = blob->data; + + for (i = 0; i < lut_size; i++) { + u32 val = intel_de_read_fw(dev_priv, PREC_PALETTE(pipe, i)); + + ilk_lut_10_pack(&lut[i], val); + } + + return blob; +} + +static void ilk_read_luts(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + if (!crtc_state->gamma_enable) + return; + + if ((crtc_state->csc_mode & CSC_POSITION_BEFORE_GAMMA) == 0) + return; + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + crtc_state->hw.gamma_lut = ilk_read_lut_8(crtc); + break; + case GAMMA_MODE_MODE_10BIT: + crtc_state->hw.gamma_lut = ilk_read_lut_10(crtc); + break; + default: + MISSING_CASE(crtc_state->gamma_mode); + break; + } +} + +/* On BDW+ the index auto increment mode actually works */ +static struct drm_property_blob *bdw_read_lut_10(struct intel_crtc *crtc, + u32 prec_index) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + int i, hw_lut_size = ivb_lut_10_size(prec_index); + int lut_size = INTEL_INFO(dev_priv)->display.color.gamma_lut_size; + enum pipe pipe = crtc->pipe; + struct drm_property_blob *blob; + struct drm_color_lut *lut; + + drm_WARN_ON(&dev_priv->drm, lut_size != hw_lut_size); + + blob = drm_property_create_blob(&dev_priv->drm, + sizeof(struct drm_color_lut) * lut_size, + NULL); + if (IS_ERR(blob)) + return NULL; + + lut = blob->data; + + intel_de_write_fw(dev_priv, PREC_PAL_INDEX(pipe), + prec_index | PAL_PREC_AUTO_INCREMENT); + + for (i = 0; i < lut_size; i++) { + u32 val = intel_de_read_fw(dev_priv, PREC_PAL_DATA(pipe)); + + ilk_lut_10_pack(&lut[i], val); + } + + intel_de_write_fw(dev_priv, PREC_PAL_INDEX(pipe), 0); + + return blob; +} + +static void glk_read_luts(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + if (!crtc_state->gamma_enable) + return; + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + crtc_state->hw.gamma_lut = ilk_read_lut_8(crtc); + break; + case GAMMA_MODE_MODE_10BIT: + crtc_state->hw.gamma_lut = bdw_read_lut_10(crtc, PAL_PREC_INDEX_VALUE(0)); + break; + default: + MISSING_CASE(crtc_state->gamma_mode); + break; + } +} + +static void icl_color_commit_arm(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + + /* + * We don't (yet) allow userspace to control the pipe background color, + * so force it to black. + */ + intel_de_write(i915, SKL_BOTTOM_COLOR(pipe), 0); + + intel_de_write(i915, GAMMA_MODE(crtc->pipe), + crtc_state->gamma_mode); + + intel_de_write_fw(i915, PIPE_CSC_MODE(crtc->pipe), + crtc_state->csc_mode); +} + +static struct drm_property_blob * +icl_read_lut_multi_segment(struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + int i, lut_size = INTEL_INFO(dev_priv)->display.color.gamma_lut_size; + enum pipe pipe = crtc->pipe; + struct drm_property_blob *blob; + struct drm_color_lut *lut; + + blob = drm_property_create_blob(&dev_priv->drm, + sizeof(struct drm_color_lut) * lut_size, + NULL); + if (IS_ERR(blob)) + return NULL; + + lut = blob->data; + + intel_de_write_fw(dev_priv, PREC_PAL_MULTI_SEG_INDEX(pipe), + PAL_PREC_AUTO_INCREMENT); + + for (i = 0; i < 9; i++) { + u32 ldw = intel_de_read_fw(dev_priv, PREC_PAL_MULTI_SEG_DATA(pipe)); + u32 udw = intel_de_read_fw(dev_priv, PREC_PAL_MULTI_SEG_DATA(pipe)); + + icl_lut_multi_seg_pack(&lut[i], ldw, udw); + } + + intel_de_write_fw(dev_priv, PREC_PAL_MULTI_SEG_INDEX(pipe), 0); + + /* + * FIXME readouts from PAL_PREC_DATA register aren't giving + * correct values in the case of fine and coarse segments. + * Restricting readouts only for super fine segment as of now. + */ + + return blob; +} + +static void icl_read_luts(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + if ((crtc_state->gamma_mode & POST_CSC_GAMMA_ENABLE) == 0) + return; + + switch (crtc_state->gamma_mode & GAMMA_MODE_MODE_MASK) { + case GAMMA_MODE_MODE_8BIT: + crtc_state->hw.gamma_lut = ilk_read_lut_8(crtc); + break; + case GAMMA_MODE_MODE_10BIT: + crtc_state->hw.gamma_lut = bdw_read_lut_10(crtc, PAL_PREC_INDEX_VALUE(0)); + break; + case GAMMA_MODE_MODE_12BIT_MULTI_SEGMENTED: + crtc_state->hw.gamma_lut = icl_read_lut_multi_segment(crtc); + break; + default: + MISSING_CASE(crtc_state->gamma_mode); + break; + } +} + +static const struct intel_color_funcs chv_color_funcs = { + .color_check = chv_color_check, + .color_commit_arm = i9xx_color_commit_arm, + .load_luts = chv_load_luts, + .read_luts = chv_read_luts, +}; + +static const struct intel_color_funcs i965_color_funcs = { + .color_check = i9xx_color_check, + .color_commit_arm = i9xx_color_commit_arm, + .load_luts = i965_load_luts, + .read_luts = i965_read_luts, +}; + +static const struct intel_color_funcs i9xx_color_funcs = { + .color_check = i9xx_color_check, + .color_commit_arm = i9xx_color_commit_arm, + .load_luts = i9xx_load_luts, + .read_luts = i9xx_read_luts, +}; + +static const struct intel_color_funcs icl_color_funcs = { + .color_check = icl_color_check, + .color_commit_noarm = icl_color_commit_noarm, + .color_commit_arm = icl_color_commit_arm, + .load_luts = icl_load_luts, + .read_luts = icl_read_luts, +}; + +static const struct intel_color_funcs glk_color_funcs = { + .color_check = glk_color_check, + .color_commit_noarm = skl_color_commit_noarm, + .color_commit_arm = skl_color_commit_arm, + .load_luts = glk_load_luts, + .read_luts = glk_read_luts, +}; + +static const struct intel_color_funcs skl_color_funcs = { + .color_check = ivb_color_check, + .color_commit_noarm = skl_color_commit_noarm, + .color_commit_arm = skl_color_commit_arm, + .load_luts = bdw_load_luts, + .read_luts = NULL, +}; + +static const struct intel_color_funcs bdw_color_funcs = { + .color_check = ivb_color_check, + .color_commit_noarm = ilk_color_commit_noarm, + .color_commit_arm = hsw_color_commit_arm, + .load_luts = bdw_load_luts, + .read_luts = NULL, +}; + +static const struct intel_color_funcs hsw_color_funcs = { + .color_check = ivb_color_check, + .color_commit_noarm = ilk_color_commit_noarm, + .color_commit_arm = hsw_color_commit_arm, + .load_luts = ivb_load_luts, + .read_luts = NULL, +}; + +static const struct intel_color_funcs ivb_color_funcs = { + .color_check = ivb_color_check, + .color_commit_noarm = ilk_color_commit_noarm, + .color_commit_arm = ilk_color_commit_arm, + .load_luts = ivb_load_luts, + .read_luts = NULL, +}; + +static const struct intel_color_funcs ilk_color_funcs = { + .color_check = ilk_color_check, + .color_commit_noarm = ilk_color_commit_noarm, + .color_commit_arm = ilk_color_commit_arm, + .load_luts = ilk_load_luts, + .read_luts = ilk_read_luts, +}; + +void intel_color_init(struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + bool has_ctm = INTEL_INFO(dev_priv)->display.color.degamma_lut_size != 0; + + drm_mode_crtc_set_gamma_size(&crtc->base, 256); + + if (HAS_GMCH(dev_priv)) { + if (IS_CHERRYVIEW(dev_priv)) { + dev_priv->display.funcs.color = &chv_color_funcs; + } else if (DISPLAY_VER(dev_priv) >= 4) { + dev_priv->display.funcs.color = &i965_color_funcs; + } else { + dev_priv->display.funcs.color = &i9xx_color_funcs; + } + } else { + if (DISPLAY_VER(dev_priv) >= 11) + dev_priv->display.funcs.color = &icl_color_funcs; + else if (DISPLAY_VER(dev_priv) == 10) + dev_priv->display.funcs.color = &glk_color_funcs; + else if (DISPLAY_VER(dev_priv) == 9) + dev_priv->display.funcs.color = &skl_color_funcs; + else if (DISPLAY_VER(dev_priv) == 8) + dev_priv->display.funcs.color = &bdw_color_funcs; + else if (DISPLAY_VER(dev_priv) == 7) { + if (IS_HASWELL(dev_priv)) + dev_priv->display.funcs.color = &hsw_color_funcs; + else + dev_priv->display.funcs.color = &ivb_color_funcs; + } else + dev_priv->display.funcs.color = &ilk_color_funcs; + } + + drm_crtc_enable_color_mgmt(&crtc->base, + INTEL_INFO(dev_priv)->display.color.degamma_lut_size, + has_ctm, + INTEL_INFO(dev_priv)->display.color.gamma_lut_size); +} diff --git a/drivers/gpu/drm/i915/display/intel_color.h b/drivers/gpu/drm/i915/display/intel_color.h new file mode 100644 index 000000000..fd873425e --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_color.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_COLOR_H__ +#define __INTEL_COLOR_H__ + +#include + +struct intel_crtc_state; +struct intel_crtc; +struct drm_property_blob; + +void intel_color_init(struct intel_crtc *crtc); +int intel_color_check(struct intel_crtc_state *crtc_state); +void intel_color_commit_noarm(const struct intel_crtc_state *crtc_state); +void intel_color_commit_arm(const struct intel_crtc_state *crtc_state); +void intel_color_load_luts(const struct intel_crtc_state *crtc_state); +void intel_color_get_config(struct intel_crtc_state *crtc_state); +int intel_color_get_gamma_bit_precision(const struct intel_crtc_state *crtc_state); +bool intel_color_lut_equal(struct drm_property_blob *blob1, + struct drm_property_blob *blob2, + u32 gamma_mode, u32 bit_precision); + +#endif /* __INTEL_COLOR_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_combo_phy.c b/drivers/gpu/drm/i915/display/intel_combo_phy.c new file mode 100644 index 000000000..64890f39c --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_combo_phy.c @@ -0,0 +1,439 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2018 Intel Corporation + */ + +#include "intel_combo_phy.h" +#include "intel_combo_phy_regs.h" +#include "intel_de.h" +#include "intel_display_types.h" + +#define for_each_combo_phy(__dev_priv, __phy) \ + for ((__phy) = PHY_A; (__phy) < I915_MAX_PHYS; (__phy)++) \ + for_each_if(intel_phy_is_combo(__dev_priv, __phy)) + +#define for_each_combo_phy_reverse(__dev_priv, __phy) \ + for ((__phy) = I915_MAX_PHYS; (__phy)-- > PHY_A;) \ + for_each_if(intel_phy_is_combo(__dev_priv, __phy)) + +enum { + PROCMON_0_85V_DOT_0, + PROCMON_0_95V_DOT_0, + PROCMON_0_95V_DOT_1, + PROCMON_1_05V_DOT_0, + PROCMON_1_05V_DOT_1, +}; + +static const struct icl_procmon { + const char *name; + u32 dw1, dw9, dw10; +} icl_procmon_values[] = { + [PROCMON_0_85V_DOT_0] = { + .name = "0.85V dot0 (low-voltage)", + .dw1 = 0x00000000, .dw9 = 0x62AB67BB, .dw10 = 0x51914F96, + }, + [PROCMON_0_95V_DOT_0] = { + .name = "0.95V dot0", + .dw1 = 0x00000000, .dw9 = 0x86E172C7, .dw10 = 0x77CA5EAB, + }, + [PROCMON_0_95V_DOT_1] = { + .name = "0.95V dot1", + .dw1 = 0x00000000, .dw9 = 0x93F87FE1, .dw10 = 0x8AE871C5, + }, + [PROCMON_1_05V_DOT_0] = { + .name = "1.05V dot0", + .dw1 = 0x00000000, .dw9 = 0x98FA82DD, .dw10 = 0x89E46DC1, + }, + [PROCMON_1_05V_DOT_1] = { + .name = "1.05V dot1", + .dw1 = 0x00440000, .dw9 = 0x9A00AB25, .dw10 = 0x8AE38FF1, + }, +}; + +static const struct icl_procmon * +icl_get_procmon_ref_values(struct drm_i915_private *dev_priv, enum phy phy) +{ + const struct icl_procmon *procmon; + u32 val; + + val = intel_de_read(dev_priv, ICL_PORT_COMP_DW3(phy)); + switch (val & (PROCESS_INFO_MASK | VOLTAGE_INFO_MASK)) { + default: + MISSING_CASE(val); + fallthrough; + case VOLTAGE_INFO_0_85V | PROCESS_INFO_DOT_0: + procmon = &icl_procmon_values[PROCMON_0_85V_DOT_0]; + break; + case VOLTAGE_INFO_0_95V | PROCESS_INFO_DOT_0: + procmon = &icl_procmon_values[PROCMON_0_95V_DOT_0]; + break; + case VOLTAGE_INFO_0_95V | PROCESS_INFO_DOT_1: + procmon = &icl_procmon_values[PROCMON_0_95V_DOT_1]; + break; + case VOLTAGE_INFO_1_05V | PROCESS_INFO_DOT_0: + procmon = &icl_procmon_values[PROCMON_1_05V_DOT_0]; + break; + case VOLTAGE_INFO_1_05V | PROCESS_INFO_DOT_1: + procmon = &icl_procmon_values[PROCMON_1_05V_DOT_1]; + break; + } + + return procmon; +} + +static void icl_set_procmon_ref_values(struct drm_i915_private *dev_priv, + enum phy phy) +{ + const struct icl_procmon *procmon; + u32 val; + + procmon = icl_get_procmon_ref_values(dev_priv, phy); + + val = intel_de_read(dev_priv, ICL_PORT_COMP_DW1(phy)); + val &= ~((0xff << 16) | 0xff); + val |= procmon->dw1; + intel_de_write(dev_priv, ICL_PORT_COMP_DW1(phy), val); + + intel_de_write(dev_priv, ICL_PORT_COMP_DW9(phy), procmon->dw9); + intel_de_write(dev_priv, ICL_PORT_COMP_DW10(phy), procmon->dw10); +} + +static bool check_phy_reg(struct drm_i915_private *dev_priv, + enum phy phy, i915_reg_t reg, u32 mask, + u32 expected_val) +{ + u32 val = intel_de_read(dev_priv, reg); + + if ((val & mask) != expected_val) { + drm_dbg(&dev_priv->drm, + "Combo PHY %c reg %08x state mismatch: " + "current %08x mask %08x expected %08x\n", + phy_name(phy), + reg.reg, val, mask, expected_val); + return false; + } + + return true; +} + +static bool icl_verify_procmon_ref_values(struct drm_i915_private *dev_priv, + enum phy phy) +{ + const struct icl_procmon *procmon; + bool ret; + + procmon = icl_get_procmon_ref_values(dev_priv, phy); + + drm_dbg_kms(&dev_priv->drm, + "Combo PHY %c Voltage/Process Info : %s\n", + phy_name(phy), procmon->name); + + ret = check_phy_reg(dev_priv, phy, ICL_PORT_COMP_DW1(phy), + (0xff << 16) | 0xff, procmon->dw1); + ret &= check_phy_reg(dev_priv, phy, ICL_PORT_COMP_DW9(phy), + -1U, procmon->dw9); + ret &= check_phy_reg(dev_priv, phy, ICL_PORT_COMP_DW10(phy), + -1U, procmon->dw10); + + return ret; +} + +static bool has_phy_misc(struct drm_i915_private *i915, enum phy phy) +{ + /* + * Some platforms only expect PHY_MISC to be programmed for PHY-A and + * PHY-B and may not even have instances of the register for the + * other combo PHY's. + * + * ADL-S technically has three instances of PHY_MISC, but only requires + * that we program it for PHY A. + */ + + if (IS_ALDERLAKE_S(i915)) + return phy == PHY_A; + else if (IS_JSL_EHL(i915) || + IS_ROCKETLAKE(i915) || + IS_DG1(i915)) + return phy < PHY_C; + + return true; +} + +static bool icl_combo_phy_enabled(struct drm_i915_private *dev_priv, + enum phy phy) +{ + /* The PHY C added by EHL has no PHY_MISC register */ + if (!has_phy_misc(dev_priv, phy)) + return intel_de_read(dev_priv, ICL_PORT_COMP_DW0(phy)) & COMP_INIT; + else + return !(intel_de_read(dev_priv, ICL_PHY_MISC(phy)) & + ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN) && + (intel_de_read(dev_priv, ICL_PORT_COMP_DW0(phy)) & COMP_INIT); +} + +static bool ehl_vbt_ddi_d_present(struct drm_i915_private *i915) +{ + bool ddi_a_present = intel_bios_is_port_present(i915, PORT_A); + bool ddi_d_present = intel_bios_is_port_present(i915, PORT_D); + bool dsi_present = intel_bios_is_dsi_present(i915, NULL); + + /* + * VBT's 'dvo port' field for child devices references the DDI, not + * the PHY. So if combo PHY A is wired up to drive an external + * display, we should see a child device present on PORT_D and + * nothing on PORT_A and no DSI. + */ + if (ddi_d_present && !ddi_a_present && !dsi_present) + return true; + + /* + * If we encounter a VBT that claims to have an external display on + * DDI-D _and_ an internal display on DDI-A/DSI leave an error message + * in the log and let the internal display win. + */ + if (ddi_d_present) + drm_err(&i915->drm, + "VBT claims to have both internal and external displays on PHY A. Configuring for internal.\n"); + + return false; +} + +static bool phy_is_master(struct drm_i915_private *dev_priv, enum phy phy) +{ + /* + * Certain PHYs are connected to compensation resistors and act + * as masters to other PHYs. + * + * ICL,TGL: + * A(master) -> B(slave), C(slave) + * RKL,DG1: + * A(master) -> B(slave) + * C(master) -> D(slave) + * ADL-S: + * A(master) -> B(slave), C(slave) + * D(master) -> E(slave) + * + * We must set the IREFGEN bit for any PHY acting as a master + * to another PHY. + */ + if (phy == PHY_A) + return true; + else if (IS_ALDERLAKE_S(dev_priv)) + return phy == PHY_D; + else if (IS_DG1(dev_priv) || IS_ROCKETLAKE(dev_priv)) + return phy == PHY_C; + + return false; +} + +static bool icl_combo_phy_verify_state(struct drm_i915_private *dev_priv, + enum phy phy) +{ + bool ret = true; + u32 expected_val = 0; + + if (!icl_combo_phy_enabled(dev_priv, phy)) + return false; + + if (DISPLAY_VER(dev_priv) >= 12) { + ret &= check_phy_reg(dev_priv, phy, ICL_PORT_TX_DW8_LN(0, phy), + ICL_PORT_TX_DW8_ODCC_CLK_SEL | + ICL_PORT_TX_DW8_ODCC_CLK_DIV_SEL_MASK, + ICL_PORT_TX_DW8_ODCC_CLK_SEL | + ICL_PORT_TX_DW8_ODCC_CLK_DIV_SEL_DIV2); + + ret &= check_phy_reg(dev_priv, phy, ICL_PORT_PCS_DW1_LN(0, phy), + DCC_MODE_SELECT_MASK, + DCC_MODE_SELECT_CONTINUOSLY); + } + + ret &= icl_verify_procmon_ref_values(dev_priv, phy); + + if (phy_is_master(dev_priv, phy)) { + ret &= check_phy_reg(dev_priv, phy, ICL_PORT_COMP_DW8(phy), + IREFGEN, IREFGEN); + + if (IS_JSL_EHL(dev_priv)) { + if (ehl_vbt_ddi_d_present(dev_priv)) + expected_val = ICL_PHY_MISC_MUX_DDID; + + ret &= check_phy_reg(dev_priv, phy, ICL_PHY_MISC(phy), + ICL_PHY_MISC_MUX_DDID, + expected_val); + } + } + + ret &= check_phy_reg(dev_priv, phy, ICL_PORT_CL_DW5(phy), + CL_POWER_DOWN_ENABLE, CL_POWER_DOWN_ENABLE); + + return ret; +} + +void intel_combo_phy_power_up_lanes(struct drm_i915_private *dev_priv, + enum phy phy, bool is_dsi, + int lane_count, bool lane_reversal) +{ + u8 lane_mask; + u32 val; + + if (is_dsi) { + drm_WARN_ON(&dev_priv->drm, lane_reversal); + + switch (lane_count) { + case 1: + lane_mask = PWR_DOWN_LN_3_1_0; + break; + case 2: + lane_mask = PWR_DOWN_LN_3_1; + break; + case 3: + lane_mask = PWR_DOWN_LN_3; + break; + default: + MISSING_CASE(lane_count); + fallthrough; + case 4: + lane_mask = PWR_UP_ALL_LANES; + break; + } + } else { + switch (lane_count) { + case 1: + lane_mask = lane_reversal ? PWR_DOWN_LN_2_1_0 : + PWR_DOWN_LN_3_2_1; + break; + case 2: + lane_mask = lane_reversal ? PWR_DOWN_LN_1_0 : + PWR_DOWN_LN_3_2; + break; + default: + MISSING_CASE(lane_count); + fallthrough; + case 4: + lane_mask = PWR_UP_ALL_LANES; + break; + } + } + + val = intel_de_read(dev_priv, ICL_PORT_CL_DW10(phy)); + val &= ~PWR_DOWN_LN_MASK; + val |= lane_mask; + intel_de_write(dev_priv, ICL_PORT_CL_DW10(phy), val); +} + +static void icl_combo_phys_init(struct drm_i915_private *dev_priv) +{ + enum phy phy; + + for_each_combo_phy(dev_priv, phy) { + u32 val; + + if (icl_combo_phy_verify_state(dev_priv, phy)) { + drm_dbg(&dev_priv->drm, + "Combo PHY %c already enabled, won't reprogram it.\n", + phy_name(phy)); + continue; + } + + if (!has_phy_misc(dev_priv, phy)) + goto skip_phy_misc; + + /* + * EHL's combo PHY A can be hooked up to either an external + * display (via DDI-D) or an internal display (via DDI-A or + * the DSI DPHY). This is a motherboard design decision that + * can't be changed on the fly, so initialize the PHY's mux + * based on whether our VBT indicates the presence of any + * "internal" child devices. + */ + val = intel_de_read(dev_priv, ICL_PHY_MISC(phy)); + if (IS_JSL_EHL(dev_priv) && phy == PHY_A) { + val &= ~ICL_PHY_MISC_MUX_DDID; + + if (ehl_vbt_ddi_d_present(dev_priv)) + val |= ICL_PHY_MISC_MUX_DDID; + } + + val &= ~ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN; + intel_de_write(dev_priv, ICL_PHY_MISC(phy), val); + +skip_phy_misc: + if (DISPLAY_VER(dev_priv) >= 12) { + val = intel_de_read(dev_priv, ICL_PORT_TX_DW8_LN(0, phy)); + val &= ~ICL_PORT_TX_DW8_ODCC_CLK_DIV_SEL_MASK; + val |= ICL_PORT_TX_DW8_ODCC_CLK_SEL; + val |= ICL_PORT_TX_DW8_ODCC_CLK_DIV_SEL_DIV2; + intel_de_write(dev_priv, ICL_PORT_TX_DW8_GRP(phy), val); + + val = intel_de_read(dev_priv, ICL_PORT_PCS_DW1_LN(0, phy)); + val &= ~DCC_MODE_SELECT_MASK; + val |= DCC_MODE_SELECT_CONTINUOSLY; + intel_de_write(dev_priv, ICL_PORT_PCS_DW1_GRP(phy), val); + } + + icl_set_procmon_ref_values(dev_priv, phy); + + if (phy_is_master(dev_priv, phy)) { + val = intel_de_read(dev_priv, ICL_PORT_COMP_DW8(phy)); + val |= IREFGEN; + intel_de_write(dev_priv, ICL_PORT_COMP_DW8(phy), val); + } + + val = intel_de_read(dev_priv, ICL_PORT_COMP_DW0(phy)); + val |= COMP_INIT; + intel_de_write(dev_priv, ICL_PORT_COMP_DW0(phy), val); + + val = intel_de_read(dev_priv, ICL_PORT_CL_DW5(phy)); + val |= CL_POWER_DOWN_ENABLE; + intel_de_write(dev_priv, ICL_PORT_CL_DW5(phy), val); + } +} + +static void icl_combo_phys_uninit(struct drm_i915_private *dev_priv) +{ + enum phy phy; + + for_each_combo_phy_reverse(dev_priv, phy) { + u32 val; + + if (phy == PHY_A && + !icl_combo_phy_verify_state(dev_priv, phy)) { + if (IS_TIGERLAKE(dev_priv) || IS_DG1(dev_priv)) { + /* + * A known problem with old ifwi: + * https://gitlab.freedesktop.org/drm/intel/-/issues/2411 + * Suppress the warning for CI. Remove ASAP! + */ + drm_dbg_kms(&dev_priv->drm, + "Combo PHY %c HW state changed unexpectedly\n", + phy_name(phy)); + } else { + drm_warn(&dev_priv->drm, + "Combo PHY %c HW state changed unexpectedly\n", + phy_name(phy)); + } + } + + if (!has_phy_misc(dev_priv, phy)) + goto skip_phy_misc; + + val = intel_de_read(dev_priv, ICL_PHY_MISC(phy)); + val |= ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN; + intel_de_write(dev_priv, ICL_PHY_MISC(phy), val); + +skip_phy_misc: + val = intel_de_read(dev_priv, ICL_PORT_COMP_DW0(phy)); + val &= ~COMP_INIT; + intel_de_write(dev_priv, ICL_PORT_COMP_DW0(phy), val); + } +} + +void intel_combo_phy_init(struct drm_i915_private *i915) +{ + icl_combo_phys_init(i915); +} + +void intel_combo_phy_uninit(struct drm_i915_private *i915) +{ + icl_combo_phys_uninit(i915); +} diff --git a/drivers/gpu/drm/i915/display/intel_combo_phy.h b/drivers/gpu/drm/i915/display/intel_combo_phy.h new file mode 100644 index 000000000..660886f86 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_combo_phy.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_COMBO_PHY_H__ +#define __INTEL_COMBO_PHY_H__ + +#include + +struct drm_i915_private; +enum phy; + +void intel_combo_phy_init(struct drm_i915_private *dev_priv); +void intel_combo_phy_uninit(struct drm_i915_private *dev_priv); +void intel_combo_phy_power_up_lanes(struct drm_i915_private *dev_priv, + enum phy phy, bool is_dsi, + int lane_count, bool lane_reversal); + +#endif /* __INTEL_COMBO_PHY_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_combo_phy_regs.h b/drivers/gpu/drm/i915/display/intel_combo_phy_regs.h new file mode 100644 index 000000000..2ed65193c --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_combo_phy_regs.h @@ -0,0 +1,162 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2022 Intel Corporation + */ + +#ifndef __INTEL_COMBO_PHY_REGS__ +#define __INTEL_COMBO_PHY_REGS__ + +#include "i915_reg_defs.h" + +#define _ICL_COMBOPHY_A 0x162000 +#define _ICL_COMBOPHY_B 0x6C000 +#define _EHL_COMBOPHY_C 0x160000 +#define _RKL_COMBOPHY_D 0x161000 +#define _ADL_COMBOPHY_E 0x16B000 + +#define _ICL_COMBOPHY(phy) _PICK(phy, _ICL_COMBOPHY_A, \ + _ICL_COMBOPHY_B, \ + _EHL_COMBOPHY_C, \ + _RKL_COMBOPHY_D, \ + _ADL_COMBOPHY_E) + +/* ICL Port CL_DW registers */ +#define _ICL_PORT_CL_DW(dw, phy) (_ICL_COMBOPHY(phy) + \ + 4 * (dw)) + +#define ICL_PORT_CL_DW5(phy) _MMIO(_ICL_PORT_CL_DW(5, phy)) +#define CL_POWER_DOWN_ENABLE (1 << 4) +#define SUS_CLOCK_CONFIG (3 << 0) + +#define ICL_PORT_CL_DW10(phy) _MMIO(_ICL_PORT_CL_DW(10, phy)) +#define PG_SEQ_DELAY_OVERRIDE_MASK (3 << 25) +#define PG_SEQ_DELAY_OVERRIDE_SHIFT 25 +#define PG_SEQ_DELAY_OVERRIDE_ENABLE (1 << 24) +#define PWR_UP_ALL_LANES (0x0 << 4) +#define PWR_DOWN_LN_3_2_1 (0xe << 4) +#define PWR_DOWN_LN_3_2 (0xc << 4) +#define PWR_DOWN_LN_3 (0x8 << 4) +#define PWR_DOWN_LN_2_1_0 (0x7 << 4) +#define PWR_DOWN_LN_1_0 (0x3 << 4) +#define PWR_DOWN_LN_3_1 (0xa << 4) +#define PWR_DOWN_LN_3_1_0 (0xb << 4) +#define PWR_DOWN_LN_MASK (0xf << 4) +#define PWR_DOWN_LN_SHIFT 4 +#define EDP4K2K_MODE_OVRD_EN (1 << 3) +#define EDP4K2K_MODE_OVRD_OPTIMIZED (1 << 2) + +#define ICL_PORT_CL_DW12(phy) _MMIO(_ICL_PORT_CL_DW(12, phy)) +#define ICL_LANE_ENABLE_AUX (1 << 0) + +/* ICL Port COMP_DW registers */ +#define _ICL_PORT_COMP 0x100 +#define _ICL_PORT_COMP_DW(dw, phy) (_ICL_COMBOPHY(phy) + \ + _ICL_PORT_COMP + 4 * (dw)) + +#define ICL_PORT_COMP_DW0(phy) _MMIO(_ICL_PORT_COMP_DW(0, phy)) +#define COMP_INIT (1 << 31) + +#define ICL_PORT_COMP_DW1(phy) _MMIO(_ICL_PORT_COMP_DW(1, phy)) + +#define ICL_PORT_COMP_DW3(phy) _MMIO(_ICL_PORT_COMP_DW(3, phy)) +#define PROCESS_INFO_DOT_0 (0 << 26) +#define PROCESS_INFO_DOT_1 (1 << 26) +#define PROCESS_INFO_DOT_4 (2 << 26) +#define PROCESS_INFO_MASK (7 << 26) +#define PROCESS_INFO_SHIFT 26 +#define VOLTAGE_INFO_0_85V (0 << 24) +#define VOLTAGE_INFO_0_95V (1 << 24) +#define VOLTAGE_INFO_1_05V (2 << 24) +#define VOLTAGE_INFO_MASK (3 << 24) +#define VOLTAGE_INFO_SHIFT 24 + +#define ICL_PORT_COMP_DW8(phy) _MMIO(_ICL_PORT_COMP_DW(8, phy)) +#define IREFGEN (1 << 24) + +#define ICL_PORT_COMP_DW9(phy) _MMIO(_ICL_PORT_COMP_DW(9, phy)) + +#define ICL_PORT_COMP_DW10(phy) _MMIO(_ICL_PORT_COMP_DW(10, phy)) + +/* ICL Port PCS registers */ +#define _ICL_PORT_PCS_AUX 0x300 +#define _ICL_PORT_PCS_GRP 0x600 +#define _ICL_PORT_PCS_LN(ln) (0x800 + (ln) * 0x100) +#define _ICL_PORT_PCS_DW_AUX(dw, phy) (_ICL_COMBOPHY(phy) + \ + _ICL_PORT_PCS_AUX + 4 * (dw)) +#define _ICL_PORT_PCS_DW_GRP(dw, phy) (_ICL_COMBOPHY(phy) + \ + _ICL_PORT_PCS_GRP + 4 * (dw)) +#define _ICL_PORT_PCS_DW_LN(dw, ln, phy) (_ICL_COMBOPHY(phy) + \ + _ICL_PORT_PCS_LN(ln) + 4 * (dw)) +#define ICL_PORT_PCS_DW1_AUX(phy) _MMIO(_ICL_PORT_PCS_DW_AUX(1, phy)) +#define ICL_PORT_PCS_DW1_GRP(phy) _MMIO(_ICL_PORT_PCS_DW_GRP(1, phy)) +#define ICL_PORT_PCS_DW1_LN(ln, phy) _MMIO(_ICL_PORT_PCS_DW_LN(1, ln, phy)) +#define DCC_MODE_SELECT_MASK (0x3 << 20) +#define DCC_MODE_SELECT_CONTINUOSLY (0x3 << 20) +#define COMMON_KEEPER_EN (1 << 26) +#define LATENCY_OPTIM_MASK (0x3 << 2) +#define LATENCY_OPTIM_VAL(x) ((x) << 2) + +/* ICL Port TX registers */ +#define _ICL_PORT_TX_AUX 0x380 +#define _ICL_PORT_TX_GRP 0x680 +#define _ICL_PORT_TX_LN(ln) (0x880 + (ln) * 0x100) + +#define _ICL_PORT_TX_DW_AUX(dw, phy) (_ICL_COMBOPHY(phy) + \ + _ICL_PORT_TX_AUX + 4 * (dw)) +#define _ICL_PORT_TX_DW_GRP(dw, phy) (_ICL_COMBOPHY(phy) + \ + _ICL_PORT_TX_GRP + 4 * (dw)) +#define _ICL_PORT_TX_DW_LN(dw, ln, phy) (_ICL_COMBOPHY(phy) + \ + _ICL_PORT_TX_LN(ln) + 4 * (dw)) + +#define ICL_PORT_TX_DW2_AUX(phy) _MMIO(_ICL_PORT_TX_DW_AUX(2, phy)) +#define ICL_PORT_TX_DW2_GRP(phy) _MMIO(_ICL_PORT_TX_DW_GRP(2, phy)) +#define ICL_PORT_TX_DW2_LN(ln, phy) _MMIO(_ICL_PORT_TX_DW_LN(2, ln, phy)) +#define SWING_SEL_UPPER(x) (((x) >> 3) << 15) +#define SWING_SEL_UPPER_MASK (1 << 15) +#define SWING_SEL_LOWER(x) (((x) & 0x7) << 11) +#define SWING_SEL_LOWER_MASK (0x7 << 11) +#define FRC_LATENCY_OPTIM_MASK (0x7 << 8) +#define FRC_LATENCY_OPTIM_VAL(x) ((x) << 8) +#define RCOMP_SCALAR(x) ((x) << 0) +#define RCOMP_SCALAR_MASK (0xFF << 0) + +#define ICL_PORT_TX_DW4_AUX(phy) _MMIO(_ICL_PORT_TX_DW_AUX(4, phy)) +#define ICL_PORT_TX_DW4_GRP(phy) _MMIO(_ICL_PORT_TX_DW_GRP(4, phy)) +#define ICL_PORT_TX_DW4_LN(ln, phy) _MMIO(_ICL_PORT_TX_DW_LN(4, ln, phy)) +#define LOADGEN_SELECT (1 << 31) +#define POST_CURSOR_1(x) ((x) << 12) +#define POST_CURSOR_1_MASK (0x3F << 12) +#define POST_CURSOR_2(x) ((x) << 6) +#define POST_CURSOR_2_MASK (0x3F << 6) +#define CURSOR_COEFF(x) ((x) << 0) +#define CURSOR_COEFF_MASK (0x3F << 0) + +#define ICL_PORT_TX_DW5_AUX(phy) _MMIO(_ICL_PORT_TX_DW_AUX(5, phy)) +#define ICL_PORT_TX_DW5_GRP(phy) _MMIO(_ICL_PORT_TX_DW_GRP(5, phy)) +#define ICL_PORT_TX_DW5_LN(ln, phy) _MMIO(_ICL_PORT_TX_DW_LN(5, ln, phy)) +#define TX_TRAINING_EN (1 << 31) +#define TAP2_DISABLE (1 << 30) +#define TAP3_DISABLE (1 << 29) +#define SCALING_MODE_SEL(x) ((x) << 18) +#define SCALING_MODE_SEL_MASK (0x7 << 18) +#define RTERM_SELECT(x) ((x) << 3) +#define RTERM_SELECT_MASK (0x7 << 3) + +#define ICL_PORT_TX_DW7_AUX(phy) _MMIO(_ICL_PORT_TX_DW_AUX(7, phy)) +#define ICL_PORT_TX_DW7_GRP(phy) _MMIO(_ICL_PORT_TX_DW_GRP(7, phy)) +#define ICL_PORT_TX_DW7_LN(ln, phy) _MMIO(_ICL_PORT_TX_DW_LN(7, ln, phy)) +#define N_SCALAR(x) ((x) << 24) +#define N_SCALAR_MASK (0x7F << 24) + +#define ICL_PORT_TX_DW8_AUX(phy) _MMIO(_ICL_PORT_TX_DW_AUX(8, phy)) +#define ICL_PORT_TX_DW8_GRP(phy) _MMIO(_ICL_PORT_TX_DW_GRP(8, phy)) +#define ICL_PORT_TX_DW8_LN(ln, phy) _MMIO(_ICL_PORT_TX_DW_LN(8, ln, phy)) +#define ICL_PORT_TX_DW8_ODCC_CLK_SEL REG_BIT(31) +#define ICL_PORT_TX_DW8_ODCC_CLK_DIV_SEL_MASK REG_GENMASK(30, 29) +#define ICL_PORT_TX_DW8_ODCC_CLK_DIV_SEL_DIV2 REG_FIELD_PREP(ICL_PORT_TX_DW8_ODCC_CLK_DIV_SEL_MASK, 0x1) + +#define _ICL_DPHY_CHKN_REG 0x194 +#define ICL_DPHY_CHKN(port) _MMIO(_ICL_COMBOPHY(port) + _ICL_DPHY_CHKN_REG) +#define ICL_DPHY_CHKN_AFE_OVER_PPI_STRAP REG_BIT(7) + +#endif /* __INTEL_COMBO_PHY_REGS__ */ diff --git a/drivers/gpu/drm/i915/display/intel_connector.c b/drivers/gpu/drm/i915/display/intel_connector.c new file mode 100644 index 000000000..8bb296f3d --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_connector.c @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2007 Dave Airlie + * Copyright (c) 2007, 2010 Intel Corporation + * Jesse Barnes + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include + +#include +#include + +#include "i915_drv.h" +#include "intel_backlight.h" +#include "intel_connector.h" +#include "intel_display_debugfs.h" +#include "intel_display_types.h" +#include "intel_hdcp.h" +#include "intel_panel.h" + +int intel_connector_init(struct intel_connector *connector) +{ + struct intel_digital_connector_state *conn_state; + + /* + * Allocate enough memory to hold intel_digital_connector_state, + * This might be a few bytes too many, but for connectors that don't + * need it we'll free the state and allocate a smaller one on the first + * successful commit anyway. + */ + conn_state = kzalloc(sizeof(*conn_state), GFP_KERNEL); + if (!conn_state) + return -ENOMEM; + + __drm_atomic_helper_connector_reset(&connector->base, + &conn_state->base); + + intel_panel_init_alloc(connector); + + return 0; +} + +struct intel_connector *intel_connector_alloc(void) +{ + struct intel_connector *connector; + + connector = kzalloc(sizeof(*connector), GFP_KERNEL); + if (!connector) + return NULL; + + if (intel_connector_init(connector) < 0) { + kfree(connector); + return NULL; + } + + return connector; +} + +/* + * Free the bits allocated by intel_connector_alloc. + * This should only be used after intel_connector_alloc has returned + * successfully, and before drm_connector_init returns successfully. + * Otherwise the destroy callbacks for the connector and the state should + * take care of proper cleanup/free (see intel_connector_destroy). + */ +void intel_connector_free(struct intel_connector *connector) +{ + kfree(to_intel_digital_connector_state(connector->base.state)); + kfree(connector); +} + +/* + * Connector type independent destroy hook for drm_connector_funcs. + */ +void intel_connector_destroy(struct drm_connector *connector) +{ + struct intel_connector *intel_connector = to_intel_connector(connector); + + kfree(intel_connector->detect_edid); + + intel_hdcp_cleanup(intel_connector); + + if (!IS_ERR_OR_NULL(intel_connector->edid)) + kfree(intel_connector->edid); + + intel_panel_fini(intel_connector); + + drm_connector_cleanup(connector); + + if (intel_connector->port) + drm_dp_mst_put_port_malloc(intel_connector->port); + + kfree(connector); +} + +int intel_connector_register(struct drm_connector *connector) +{ + struct intel_connector *intel_connector = to_intel_connector(connector); + int ret; + + ret = intel_backlight_device_register(intel_connector); + if (ret) + goto err; + + if (i915_inject_probe_failure(to_i915(connector->dev))) { + ret = -EFAULT; + goto err_backlight; + } + + intel_connector_debugfs_add(intel_connector); + + return 0; + +err_backlight: + intel_backlight_device_unregister(intel_connector); +err: + return ret; +} + +void intel_connector_unregister(struct drm_connector *connector) +{ + struct intel_connector *intel_connector = to_intel_connector(connector); + + intel_backlight_device_unregister(intel_connector); +} + +void intel_connector_attach_encoder(struct intel_connector *connector, + struct intel_encoder *encoder) +{ + connector->encoder = encoder; + drm_connector_attach_encoder(&connector->base, &encoder->base); +} + +/* + * Simple connector->get_hw_state implementation for encoders that support only + * one connector and no cloning and hence the encoder state determines the state + * of the connector. + */ +bool intel_connector_get_hw_state(struct intel_connector *connector) +{ + enum pipe pipe = 0; + struct intel_encoder *encoder = intel_attached_encoder(connector); + + return encoder->get_hw_state(encoder, &pipe); +} + +enum pipe intel_connector_get_pipe(struct intel_connector *connector) +{ + struct drm_device *dev = connector->base.dev; + + drm_WARN_ON(dev, + !drm_modeset_is_locked(&dev->mode_config.connection_mutex)); + + if (!connector->base.state->crtc) + return INVALID_PIPE; + + return to_intel_crtc(connector->base.state->crtc)->pipe; +} + +/** + * intel_connector_update_modes - update connector from edid + * @connector: DRM connector device to use + * @edid: previously read EDID information + */ +int intel_connector_update_modes(struct drm_connector *connector, + struct edid *edid) +{ + int ret; + + drm_connector_update_edid_property(connector, edid); + ret = drm_add_edid_modes(connector, edid); + + return ret; +} + +/** + * intel_ddc_get_modes - get modelist from monitor + * @connector: DRM connector device to use + * @adapter: i2c adapter + * + * Fetch the EDID information from @connector using the DDC bus. + */ +int intel_ddc_get_modes(struct drm_connector *connector, + struct i2c_adapter *adapter) +{ + struct edid *edid; + int ret; + + edid = drm_get_edid(connector, adapter); + if (!edid) + return 0; + + ret = intel_connector_update_modes(connector, edid); + kfree(edid); + + return ret; +} + +static const struct drm_prop_enum_list force_audio_names[] = { + { HDMI_AUDIO_OFF_DVI, "force-dvi" }, + { HDMI_AUDIO_OFF, "off" }, + { HDMI_AUDIO_AUTO, "auto" }, + { HDMI_AUDIO_ON, "on" }, +}; + +void +intel_attach_force_audio_property(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct drm_i915_private *dev_priv = to_i915(dev); + struct drm_property *prop; + + prop = dev_priv->display.properties.force_audio; + if (prop == NULL) { + prop = drm_property_create_enum(dev, 0, + "audio", + force_audio_names, + ARRAY_SIZE(force_audio_names)); + if (prop == NULL) + return; + + dev_priv->display.properties.force_audio = prop; + } + drm_object_attach_property(&connector->base, prop, 0); +} + +static const struct drm_prop_enum_list broadcast_rgb_names[] = { + { INTEL_BROADCAST_RGB_AUTO, "Automatic" }, + { INTEL_BROADCAST_RGB_FULL, "Full" }, + { INTEL_BROADCAST_RGB_LIMITED, "Limited 16:235" }, +}; + +void +intel_attach_broadcast_rgb_property(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct drm_i915_private *dev_priv = to_i915(dev); + struct drm_property *prop; + + prop = dev_priv->display.properties.broadcast_rgb; + if (prop == NULL) { + prop = drm_property_create_enum(dev, DRM_MODE_PROP_ENUM, + "Broadcast RGB", + broadcast_rgb_names, + ARRAY_SIZE(broadcast_rgb_names)); + if (prop == NULL) + return; + + dev_priv->display.properties.broadcast_rgb = prop; + } + + drm_object_attach_property(&connector->base, prop, 0); +} + +void +intel_attach_aspect_ratio_property(struct drm_connector *connector) +{ + if (!drm_mode_create_aspect_ratio_property(connector->dev)) + drm_object_attach_property(&connector->base, + connector->dev->mode_config.aspect_ratio_property, + DRM_MODE_PICTURE_ASPECT_NONE); +} + +void +intel_attach_hdmi_colorspace_property(struct drm_connector *connector) +{ + if (!drm_mode_create_hdmi_colorspace_property(connector)) + drm_connector_attach_colorspace_property(connector); +} + +void +intel_attach_dp_colorspace_property(struct drm_connector *connector) +{ + if (!drm_mode_create_dp_colorspace_property(connector)) + drm_connector_attach_colorspace_property(connector); +} diff --git a/drivers/gpu/drm/i915/display/intel_connector.h b/drivers/gpu/drm/i915/display/intel_connector.h new file mode 100644 index 000000000..661a37a3c --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_connector.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_CONNECTOR_H__ +#define __INTEL_CONNECTOR_H__ + +#include "intel_display.h" + +struct drm_connector; +struct edid; +struct i2c_adapter; +struct intel_connector; +struct intel_encoder; + +int intel_connector_init(struct intel_connector *connector); +struct intel_connector *intel_connector_alloc(void); +void intel_connector_free(struct intel_connector *connector); +void intel_connector_destroy(struct drm_connector *connector); +int intel_connector_register(struct drm_connector *connector); +void intel_connector_unregister(struct drm_connector *connector); +void intel_connector_attach_encoder(struct intel_connector *connector, + struct intel_encoder *encoder); +bool intel_connector_get_hw_state(struct intel_connector *connector); +enum pipe intel_connector_get_pipe(struct intel_connector *connector); +int intel_connector_update_modes(struct drm_connector *connector, + struct edid *edid); +int intel_ddc_get_modes(struct drm_connector *c, struct i2c_adapter *adapter); +void intel_attach_force_audio_property(struct drm_connector *connector); +void intel_attach_broadcast_rgb_property(struct drm_connector *connector); +void intel_attach_aspect_ratio_property(struct drm_connector *connector); +void intel_attach_hdmi_colorspace_property(struct drm_connector *connector); +void intel_attach_dp_colorspace_property(struct drm_connector *connector); + +#endif /* __INTEL_CONNECTOR_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_crt.c b/drivers/gpu/drm/i915/display/intel_crt.c new file mode 100644 index 000000000..e60b2cf84 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_crt.c @@ -0,0 +1,1124 @@ +/* + * Copyright © 2006-2007 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Eric Anholt + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "i915_drv.h" +#include "intel_connector.h" +#include "intel_crt.h" +#include "intel_crtc.h" +#include "intel_ddi.h" +#include "intel_ddi_buf_trans.h" +#include "intel_de.h" +#include "intel_display_types.h" +#include "intel_fdi.h" +#include "intel_fifo_underrun.h" +#include "intel_gmbus.h" +#include "intel_hotplug.h" +#include "intel_pch_display.h" +#include "intel_pch_refclk.h" + +/* Here's the desired hotplug mode */ +#define ADPA_HOTPLUG_BITS (ADPA_CRT_HOTPLUG_PERIOD_128 | \ + ADPA_CRT_HOTPLUG_WARMUP_10MS | \ + ADPA_CRT_HOTPLUG_SAMPLE_4S | \ + ADPA_CRT_HOTPLUG_VOLTAGE_50 | \ + ADPA_CRT_HOTPLUG_VOLREF_325MV | \ + ADPA_CRT_HOTPLUG_ENABLE) + +struct intel_crt { + struct intel_encoder base; + /* DPMS state is stored in the connector, which we need in the + * encoder's enable/disable callbacks */ + struct intel_connector *connector; + bool force_hotplug_required; + i915_reg_t adpa_reg; +}; + +static struct intel_crt *intel_encoder_to_crt(struct intel_encoder *encoder) +{ + return container_of(encoder, struct intel_crt, base); +} + +static struct intel_crt *intel_attached_crt(struct intel_connector *connector) +{ + return intel_encoder_to_crt(intel_attached_encoder(connector)); +} + +bool intel_crt_port_enabled(struct drm_i915_private *dev_priv, + i915_reg_t adpa_reg, enum pipe *pipe) +{ + u32 val; + + val = intel_de_read(dev_priv, adpa_reg); + + /* asserts want to know the pipe even if the port is disabled */ + if (HAS_PCH_CPT(dev_priv)) + *pipe = (val & ADPA_PIPE_SEL_MASK_CPT) >> ADPA_PIPE_SEL_SHIFT_CPT; + else + *pipe = (val & ADPA_PIPE_SEL_MASK) >> ADPA_PIPE_SEL_SHIFT; + + return val & ADPA_DAC_ENABLE; +} + +static bool intel_crt_get_hw_state(struct intel_encoder *encoder, + enum pipe *pipe) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_crt *crt = intel_encoder_to_crt(encoder); + intel_wakeref_t wakeref; + bool ret; + + wakeref = intel_display_power_get_if_enabled(dev_priv, + encoder->power_domain); + if (!wakeref) + return false; + + ret = intel_crt_port_enabled(dev_priv, crt->adpa_reg, pipe); + + intel_display_power_put(dev_priv, encoder->power_domain, wakeref); + + return ret; +} + +static unsigned int intel_crt_get_flags(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_crt *crt = intel_encoder_to_crt(encoder); + u32 tmp, flags = 0; + + tmp = intel_de_read(dev_priv, crt->adpa_reg); + + if (tmp & ADPA_HSYNC_ACTIVE_HIGH) + flags |= DRM_MODE_FLAG_PHSYNC; + else + flags |= DRM_MODE_FLAG_NHSYNC; + + if (tmp & ADPA_VSYNC_ACTIVE_HIGH) + flags |= DRM_MODE_FLAG_PVSYNC; + else + flags |= DRM_MODE_FLAG_NVSYNC; + + return flags; +} + +static void intel_crt_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config) +{ + pipe_config->output_types |= BIT(INTEL_OUTPUT_ANALOG); + + pipe_config->hw.adjusted_mode.flags |= intel_crt_get_flags(encoder); + + pipe_config->hw.adjusted_mode.crtc_clock = pipe_config->port_clock; +} + +static void hsw_crt_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config) +{ + lpt_pch_get_config(pipe_config); + + hsw_ddi_get_config(encoder, pipe_config); + + pipe_config->hw.adjusted_mode.flags &= ~(DRM_MODE_FLAG_PHSYNC | + DRM_MODE_FLAG_NHSYNC | + DRM_MODE_FLAG_PVSYNC | + DRM_MODE_FLAG_NVSYNC); + pipe_config->hw.adjusted_mode.flags |= intel_crt_get_flags(encoder); +} + +/* Note: The caller is required to filter out dpms modes not supported by the + * platform. */ +static void intel_crt_set_dpms(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int mode) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_crt *crt = intel_encoder_to_crt(encoder); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + const struct drm_display_mode *adjusted_mode = &crtc_state->hw.adjusted_mode; + u32 adpa; + + if (DISPLAY_VER(dev_priv) >= 5) + adpa = ADPA_HOTPLUG_BITS; + else + adpa = 0; + + if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) + adpa |= ADPA_HSYNC_ACTIVE_HIGH; + if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) + adpa |= ADPA_VSYNC_ACTIVE_HIGH; + + /* For CPT allow 3 pipe config, for others just use A or B */ + if (HAS_PCH_LPT(dev_priv)) + ; /* Those bits don't exist here */ + else if (HAS_PCH_CPT(dev_priv)) + adpa |= ADPA_PIPE_SEL_CPT(crtc->pipe); + else + adpa |= ADPA_PIPE_SEL(crtc->pipe); + + if (!HAS_PCH_SPLIT(dev_priv)) + intel_de_write(dev_priv, BCLRPAT(crtc->pipe), 0); + + switch (mode) { + case DRM_MODE_DPMS_ON: + adpa |= ADPA_DAC_ENABLE; + break; + case DRM_MODE_DPMS_STANDBY: + adpa |= ADPA_DAC_ENABLE | ADPA_HSYNC_CNTL_DISABLE; + break; + case DRM_MODE_DPMS_SUSPEND: + adpa |= ADPA_DAC_ENABLE | ADPA_VSYNC_CNTL_DISABLE; + break; + case DRM_MODE_DPMS_OFF: + adpa |= ADPA_HSYNC_CNTL_DISABLE | ADPA_VSYNC_CNTL_DISABLE; + break; + } + + intel_de_write(dev_priv, crt->adpa_reg, adpa); +} + +static void intel_disable_crt(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + intel_crt_set_dpms(encoder, old_crtc_state, DRM_MODE_DPMS_OFF); +} + +static void pch_disable_crt(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ +} + +static void pch_post_disable_crt(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + intel_disable_crt(state, encoder, old_crtc_state, old_conn_state); +} + +static void hsw_disable_crt(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + + drm_WARN_ON(&dev_priv->drm, !old_crtc_state->has_pch_encoder); + + intel_set_pch_fifo_underrun_reporting(dev_priv, PIPE_A, false); +} + +static void hsw_post_disable_crt(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + + intel_crtc_vblank_off(old_crtc_state); + + intel_disable_transcoder(old_crtc_state); + + intel_ddi_disable_transcoder_func(old_crtc_state); + + ilk_pfit_disable(old_crtc_state); + + intel_ddi_disable_pipe_clock(old_crtc_state); + + pch_post_disable_crt(state, encoder, old_crtc_state, old_conn_state); + + lpt_pch_disable(state, crtc); + + hsw_fdi_disable(encoder); + + drm_WARN_ON(&dev_priv->drm, !old_crtc_state->has_pch_encoder); + + intel_set_pch_fifo_underrun_reporting(dev_priv, PIPE_A, true); +} + +static void hsw_pre_pll_enable_crt(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + + drm_WARN_ON(&dev_priv->drm, !crtc_state->has_pch_encoder); + + intel_set_pch_fifo_underrun_reporting(dev_priv, PIPE_A, false); +} + +static void hsw_pre_enable_crt(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + enum pipe pipe = crtc->pipe; + + drm_WARN_ON(&dev_priv->drm, !crtc_state->has_pch_encoder); + + intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, false); + + hsw_fdi_link_train(encoder, crtc_state); + + intel_ddi_enable_pipe_clock(encoder, crtc_state); +} + +static void hsw_enable_crt(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + enum pipe pipe = crtc->pipe; + + drm_WARN_ON(&dev_priv->drm, !crtc_state->has_pch_encoder); + + intel_ddi_enable_transcoder_func(encoder, crtc_state); + + intel_enable_transcoder(crtc_state); + + lpt_pch_enable(state, crtc); + + intel_crtc_vblank_on(crtc_state); + + intel_crt_set_dpms(encoder, crtc_state, DRM_MODE_DPMS_ON); + + intel_crtc_wait_for_next_vblank(crtc); + intel_crtc_wait_for_next_vblank(crtc); + intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, true); + intel_set_pch_fifo_underrun_reporting(dev_priv, PIPE_A, true); +} + +static void intel_enable_crt(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + intel_crt_set_dpms(encoder, crtc_state, DRM_MODE_DPMS_ON); +} + +static enum drm_mode_status +intel_crt_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct drm_device *dev = connector->dev; + struct drm_i915_private *dev_priv = to_i915(dev); + int max_dotclk = dev_priv->max_dotclk_freq; + enum drm_mode_status status; + int max_clock; + + status = intel_cpu_transcoder_mode_valid(dev_priv, mode); + if (status != MODE_OK) + return status; + + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) + return MODE_NO_DBLESCAN; + + if (mode->clock < 25000) + return MODE_CLOCK_LOW; + + if (HAS_PCH_LPT(dev_priv)) + max_clock = 180000; + else if (IS_VALLEYVIEW(dev_priv)) + /* + * 270 MHz due to current DPLL limits, + * DAC limit supposedly 355 MHz. + */ + max_clock = 270000; + else if (IS_DISPLAY_VER(dev_priv, 3, 4)) + max_clock = 400000; + else + max_clock = 350000; + if (mode->clock > max_clock) + return MODE_CLOCK_HIGH; + + if (mode->clock > max_dotclk) + return MODE_CLOCK_HIGH; + + /* The FDI receiver on LPT only supports 8bpc and only has 2 lanes. */ + if (HAS_PCH_LPT(dev_priv) && + ilk_get_lanes_required(mode->clock, 270000, 24) > 2) + return MODE_CLOCK_HIGH; + + /* HSW/BDW FDI limited to 4k */ + if (mode->hdisplay > 4096) + return MODE_H_ILLEGAL; + + return MODE_OK; +} + +static int intel_crt_compute_config(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config, + struct drm_connector_state *conn_state) +{ + struct drm_display_mode *adjusted_mode = + &pipe_config->hw.adjusted_mode; + + if (adjusted_mode->flags & DRM_MODE_FLAG_DBLSCAN) + return -EINVAL; + + pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB; + + return 0; +} + +static int pch_crt_compute_config(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config, + struct drm_connector_state *conn_state) +{ + struct drm_display_mode *adjusted_mode = + &pipe_config->hw.adjusted_mode; + + if (adjusted_mode->flags & DRM_MODE_FLAG_DBLSCAN) + return -EINVAL; + + pipe_config->has_pch_encoder = true; + pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB; + + return 0; +} + +static int hsw_crt_compute_config(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config, + struct drm_connector_state *conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct drm_display_mode *adjusted_mode = + &pipe_config->hw.adjusted_mode; + + if (adjusted_mode->flags & DRM_MODE_FLAG_DBLSCAN) + return -EINVAL; + + /* HSW/BDW FDI limited to 4k */ + if (adjusted_mode->crtc_hdisplay > 4096 || + adjusted_mode->crtc_hblank_start > 4096) + return -EINVAL; + + pipe_config->has_pch_encoder = true; + pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB; + + /* LPT FDI RX only supports 8bpc. */ + if (HAS_PCH_LPT(dev_priv)) { + if (pipe_config->bw_constrained && pipe_config->pipe_bpp < 24) { + drm_dbg_kms(&dev_priv->drm, + "LPT only supports 24bpp\n"); + return -EINVAL; + } + + pipe_config->pipe_bpp = 24; + } + + /* FDI must always be 2.7 GHz */ + pipe_config->port_clock = 135000 * 2; + + adjusted_mode->crtc_clock = lpt_iclkip(pipe_config); + + return 0; +} + +static bool ilk_crt_detect_hotplug(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct intel_crt *crt = intel_attached_crt(to_intel_connector(connector)); + struct drm_i915_private *dev_priv = to_i915(dev); + u32 adpa; + bool ret; + + /* The first time through, trigger an explicit detection cycle */ + if (crt->force_hotplug_required) { + bool turn_off_dac = HAS_PCH_SPLIT(dev_priv); + u32 save_adpa; + + crt->force_hotplug_required = false; + + save_adpa = adpa = intel_de_read(dev_priv, crt->adpa_reg); + drm_dbg_kms(&dev_priv->drm, + "trigger hotplug detect cycle: adpa=0x%x\n", adpa); + + adpa |= ADPA_CRT_HOTPLUG_FORCE_TRIGGER; + if (turn_off_dac) + adpa &= ~ADPA_DAC_ENABLE; + + intel_de_write(dev_priv, crt->adpa_reg, adpa); + + if (intel_de_wait_for_clear(dev_priv, + crt->adpa_reg, + ADPA_CRT_HOTPLUG_FORCE_TRIGGER, + 1000)) + drm_dbg_kms(&dev_priv->drm, + "timed out waiting for FORCE_TRIGGER"); + + if (turn_off_dac) { + intel_de_write(dev_priv, crt->adpa_reg, save_adpa); + intel_de_posting_read(dev_priv, crt->adpa_reg); + } + } + + /* Check the status to see if both blue and green are on now */ + adpa = intel_de_read(dev_priv, crt->adpa_reg); + if ((adpa & ADPA_CRT_HOTPLUG_MONITOR_MASK) != 0) + ret = true; + else + ret = false; + drm_dbg_kms(&dev_priv->drm, "ironlake hotplug adpa=0x%x, result %d\n", + adpa, ret); + + return ret; +} + +static bool valleyview_crt_detect_hotplug(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct intel_crt *crt = intel_attached_crt(to_intel_connector(connector)); + struct drm_i915_private *dev_priv = to_i915(dev); + bool reenable_hpd; + u32 adpa; + bool ret; + u32 save_adpa; + + /* + * Doing a force trigger causes a hpd interrupt to get sent, which can + * get us stuck in a loop if we're polling: + * - We enable power wells and reset the ADPA + * - output_poll_exec does force probe on VGA, triggering a hpd + * - HPD handler waits for poll to unlock dev->mode_config.mutex + * - output_poll_exec shuts off the ADPA, unlocks + * dev->mode_config.mutex + * - HPD handler runs, resets ADPA and brings us back to the start + * + * Just disable HPD interrupts here to prevent this + */ + reenable_hpd = intel_hpd_disable(dev_priv, crt->base.hpd_pin); + + save_adpa = adpa = intel_de_read(dev_priv, crt->adpa_reg); + drm_dbg_kms(&dev_priv->drm, + "trigger hotplug detect cycle: adpa=0x%x\n", adpa); + + adpa |= ADPA_CRT_HOTPLUG_FORCE_TRIGGER; + + intel_de_write(dev_priv, crt->adpa_reg, adpa); + + if (intel_de_wait_for_clear(dev_priv, crt->adpa_reg, + ADPA_CRT_HOTPLUG_FORCE_TRIGGER, 1000)) { + drm_dbg_kms(&dev_priv->drm, + "timed out waiting for FORCE_TRIGGER"); + intel_de_write(dev_priv, crt->adpa_reg, save_adpa); + } + + /* Check the status to see if both blue and green are on now */ + adpa = intel_de_read(dev_priv, crt->adpa_reg); + if ((adpa & ADPA_CRT_HOTPLUG_MONITOR_MASK) != 0) + ret = true; + else + ret = false; + + drm_dbg_kms(&dev_priv->drm, + "valleyview hotplug adpa=0x%x, result %d\n", adpa, ret); + + if (reenable_hpd) + intel_hpd_enable(dev_priv, crt->base.hpd_pin); + + return ret; +} + +static bool intel_crt_detect_hotplug(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct drm_i915_private *dev_priv = to_i915(dev); + u32 stat; + bool ret = false; + int i, tries = 0; + + if (HAS_PCH_SPLIT(dev_priv)) + return ilk_crt_detect_hotplug(connector); + + if (IS_VALLEYVIEW(dev_priv)) + return valleyview_crt_detect_hotplug(connector); + + /* + * On 4 series desktop, CRT detect sequence need to be done twice + * to get a reliable result. + */ + + if (IS_G45(dev_priv)) + tries = 2; + else + tries = 1; + + for (i = 0; i < tries ; i++) { + /* turn on the FORCE_DETECT */ + i915_hotplug_interrupt_update(dev_priv, + CRT_HOTPLUG_FORCE_DETECT, + CRT_HOTPLUG_FORCE_DETECT); + /* wait for FORCE_DETECT to go off */ + if (intel_de_wait_for_clear(dev_priv, PORT_HOTPLUG_EN, + CRT_HOTPLUG_FORCE_DETECT, 1000)) + drm_dbg_kms(&dev_priv->drm, + "timed out waiting for FORCE_DETECT to go off"); + } + + stat = intel_de_read(dev_priv, PORT_HOTPLUG_STAT); + if ((stat & CRT_HOTPLUG_MONITOR_MASK) != CRT_HOTPLUG_MONITOR_NONE) + ret = true; + + /* clear the interrupt we just generated, if any */ + intel_de_write(dev_priv, PORT_HOTPLUG_STAT, CRT_HOTPLUG_INT_STATUS); + + i915_hotplug_interrupt_update(dev_priv, CRT_HOTPLUG_FORCE_DETECT, 0); + + return ret; +} + +static struct edid *intel_crt_get_edid(struct drm_connector *connector, + struct i2c_adapter *i2c) +{ + struct edid *edid; + + edid = drm_get_edid(connector, i2c); + + if (!edid && !intel_gmbus_is_forced_bit(i2c)) { + drm_dbg_kms(connector->dev, + "CRT GMBUS EDID read failed, retry using GPIO bit-banging\n"); + intel_gmbus_force_bit(i2c, true); + edid = drm_get_edid(connector, i2c); + intel_gmbus_force_bit(i2c, false); + } + + return edid; +} + +/* local version of intel_ddc_get_modes() to use intel_crt_get_edid() */ +static int intel_crt_ddc_get_modes(struct drm_connector *connector, + struct i2c_adapter *adapter) +{ + struct edid *edid; + int ret; + + edid = intel_crt_get_edid(connector, adapter); + if (!edid) + return 0; + + ret = intel_connector_update_modes(connector, edid); + kfree(edid); + + return ret; +} + +static bool intel_crt_detect_ddc(struct drm_connector *connector) +{ + struct intel_crt *crt = intel_attached_crt(to_intel_connector(connector)); + struct drm_i915_private *dev_priv = to_i915(crt->base.base.dev); + struct edid *edid; + struct i2c_adapter *i2c; + bool ret = false; + + i2c = intel_gmbus_get_adapter(dev_priv, dev_priv->display.vbt.crt_ddc_pin); + edid = intel_crt_get_edid(connector, i2c); + + if (edid) { + bool is_digital = edid->input & DRM_EDID_INPUT_DIGITAL; + + /* + * This may be a DVI-I connector with a shared DDC + * link between analog and digital outputs, so we + * have to check the EDID input spec of the attached device. + */ + if (!is_digital) { + drm_dbg_kms(&dev_priv->drm, + "CRT detected via DDC:0x50 [EDID]\n"); + ret = true; + } else { + drm_dbg_kms(&dev_priv->drm, + "CRT not detected via DDC:0x50 [EDID reports a digital panel]\n"); + } + } else { + drm_dbg_kms(&dev_priv->drm, + "CRT not detected via DDC:0x50 [no valid EDID found]\n"); + } + + kfree(edid); + + return ret; +} + +static enum drm_connector_status +intel_crt_load_detect(struct intel_crt *crt, u32 pipe) +{ + struct drm_device *dev = crt->base.base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + struct intel_uncore *uncore = &dev_priv->uncore; + u32 save_bclrpat; + u32 save_vtotal; + u32 vtotal, vactive; + u32 vsample; + u32 vblank, vblank_start, vblank_end; + u32 dsl; + i915_reg_t bclrpat_reg, vtotal_reg, + vblank_reg, vsync_reg, pipeconf_reg, pipe_dsl_reg; + u8 st00; + enum drm_connector_status status; + + drm_dbg_kms(&dev_priv->drm, "starting load-detect on CRT\n"); + + bclrpat_reg = BCLRPAT(pipe); + vtotal_reg = VTOTAL(pipe); + vblank_reg = VBLANK(pipe); + vsync_reg = VSYNC(pipe); + pipeconf_reg = PIPECONF(pipe); + pipe_dsl_reg = PIPEDSL(pipe); + + save_bclrpat = intel_uncore_read(uncore, bclrpat_reg); + save_vtotal = intel_uncore_read(uncore, vtotal_reg); + vblank = intel_uncore_read(uncore, vblank_reg); + + vtotal = ((save_vtotal >> 16) & 0xfff) + 1; + vactive = (save_vtotal & 0x7ff) + 1; + + vblank_start = (vblank & 0xfff) + 1; + vblank_end = ((vblank >> 16) & 0xfff) + 1; + + /* Set the border color to purple. */ + intel_uncore_write(uncore, bclrpat_reg, 0x500050); + + if (DISPLAY_VER(dev_priv) != 2) { + u32 pipeconf = intel_uncore_read(uncore, pipeconf_reg); + intel_uncore_write(uncore, + pipeconf_reg, + pipeconf | PIPECONF_FORCE_BORDER); + intel_uncore_posting_read(uncore, pipeconf_reg); + /* Wait for next Vblank to substitue + * border color for Color info */ + intel_crtc_wait_for_next_vblank(intel_crtc_for_pipe(dev_priv, pipe)); + st00 = intel_uncore_read8(uncore, _VGA_MSR_WRITE); + status = ((st00 & (1 << 4)) != 0) ? + connector_status_connected : + connector_status_disconnected; + + intel_uncore_write(uncore, pipeconf_reg, pipeconf); + } else { + bool restore_vblank = false; + int count, detect; + + /* + * If there isn't any border, add some. + * Yes, this will flicker + */ + if (vblank_start <= vactive && vblank_end >= vtotal) { + u32 vsync = intel_de_read(dev_priv, vsync_reg); + u32 vsync_start = (vsync & 0xffff) + 1; + + vblank_start = vsync_start; + intel_uncore_write(uncore, + vblank_reg, + (vblank_start - 1) | + ((vblank_end - 1) << 16)); + restore_vblank = true; + } + /* sample in the vertical border, selecting the larger one */ + if (vblank_start - vactive >= vtotal - vblank_end) + vsample = (vblank_start + vactive) >> 1; + else + vsample = (vtotal + vblank_end) >> 1; + + /* + * Wait for the border to be displayed + */ + while (intel_uncore_read(uncore, pipe_dsl_reg) >= vactive) + ; + while ((dsl = intel_uncore_read(uncore, pipe_dsl_reg)) <= + vsample) + ; + /* + * Watch ST00 for an entire scanline + */ + detect = 0; + count = 0; + do { + count++; + /* Read the ST00 VGA status register */ + st00 = intel_uncore_read8(uncore, _VGA_MSR_WRITE); + if (st00 & (1 << 4)) + detect++; + } while ((intel_uncore_read(uncore, pipe_dsl_reg) == dsl)); + + /* restore vblank if necessary */ + if (restore_vblank) + intel_uncore_write(uncore, vblank_reg, vblank); + /* + * If more than 3/4 of the scanline detected a monitor, + * then it is assumed to be present. This works even on i830, + * where there isn't any way to force the border color across + * the screen + */ + status = detect * 4 > count * 3 ? + connector_status_connected : + connector_status_disconnected; + } + + /* Restore previous settings */ + intel_uncore_write(uncore, bclrpat_reg, save_bclrpat); + + return status; +} + +static int intel_spurious_crt_detect_dmi_callback(const struct dmi_system_id *id) +{ + DRM_DEBUG_DRIVER("Skipping CRT detection for %s\n", id->ident); + return 1; +} + +static const struct dmi_system_id intel_spurious_crt_detect[] = { + { + .callback = intel_spurious_crt_detect_dmi_callback, + .ident = "ACER ZGB", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ACER"), + DMI_MATCH(DMI_PRODUCT_NAME, "ZGB"), + }, + }, + { + .callback = intel_spurious_crt_detect_dmi_callback, + .ident = "Intel DZ77BH-55K", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "DZ77BH-55K"), + }, + }, + { } +}; + +static int +intel_crt_detect(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + bool force) +{ + struct drm_i915_private *dev_priv = to_i915(connector->dev); + struct intel_crt *crt = intel_attached_crt(to_intel_connector(connector)); + struct intel_encoder *intel_encoder = &crt->base; + intel_wakeref_t wakeref; + int status, ret; + struct intel_load_detect_pipe tmp; + + drm_dbg_kms(&dev_priv->drm, "[CONNECTOR:%d:%s] force=%d\n", + connector->base.id, connector->name, + force); + + if (!INTEL_DISPLAY_ENABLED(dev_priv)) + return connector_status_disconnected; + + if (dev_priv->params.load_detect_test) { + wakeref = intel_display_power_get(dev_priv, + intel_encoder->power_domain); + goto load_detect; + } + + /* Skip machines without VGA that falsely report hotplug events */ + if (dmi_check_system(intel_spurious_crt_detect)) + return connector_status_disconnected; + + wakeref = intel_display_power_get(dev_priv, + intel_encoder->power_domain); + + if (I915_HAS_HOTPLUG(dev_priv)) { + /* We can not rely on the HPD pin always being correctly wired + * up, for example many KVM do not pass it through, and so + * only trust an assertion that the monitor is connected. + */ + if (intel_crt_detect_hotplug(connector)) { + drm_dbg_kms(&dev_priv->drm, + "CRT detected via hotplug\n"); + status = connector_status_connected; + goto out; + } else + drm_dbg_kms(&dev_priv->drm, + "CRT not detected via hotplug\n"); + } + + if (intel_crt_detect_ddc(connector)) { + status = connector_status_connected; + goto out; + } + + /* Load detection is broken on HPD capable machines. Whoever wants a + * broken monitor (without edid) to work behind a broken kvm (that fails + * to have the right resistors for HP detection) needs to fix this up. + * For now just bail out. */ + if (I915_HAS_HOTPLUG(dev_priv)) { + status = connector_status_disconnected; + goto out; + } + +load_detect: + if (!force) { + status = connector->status; + goto out; + } + + /* for pre-945g platforms use load detect */ + ret = intel_get_load_detect_pipe(connector, &tmp, ctx); + if (ret > 0) { + if (intel_crt_detect_ddc(connector)) + status = connector_status_connected; + else if (DISPLAY_VER(dev_priv) < 4) + status = intel_crt_load_detect(crt, + to_intel_crtc(connector->state->crtc)->pipe); + else if (dev_priv->params.load_detect_test) + status = connector_status_disconnected; + else + status = connector_status_unknown; + intel_release_load_detect_pipe(connector, &tmp, ctx); + } else if (ret == 0) { + status = connector_status_unknown; + } else { + status = ret; + } + +out: + intel_display_power_put(dev_priv, intel_encoder->power_domain, wakeref); + + /* + * Make sure the refs for power wells enabled during detect are + * dropped to avoid a new detect cycle triggered by HPD polling. + */ + intel_display_power_flush_work(dev_priv); + + return status; +} + +static int intel_crt_get_modes(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct drm_i915_private *dev_priv = to_i915(dev); + struct intel_crt *crt = intel_attached_crt(to_intel_connector(connector)); + struct intel_encoder *intel_encoder = &crt->base; + intel_wakeref_t wakeref; + struct i2c_adapter *i2c; + int ret; + + wakeref = intel_display_power_get(dev_priv, + intel_encoder->power_domain); + + i2c = intel_gmbus_get_adapter(dev_priv, dev_priv->display.vbt.crt_ddc_pin); + ret = intel_crt_ddc_get_modes(connector, i2c); + if (ret || !IS_G4X(dev_priv)) + goto out; + + /* Try to probe digital port for output in DVI-I -> VGA mode. */ + i2c = intel_gmbus_get_adapter(dev_priv, GMBUS_PIN_DPB); + ret = intel_crt_ddc_get_modes(connector, i2c); + +out: + intel_display_power_put(dev_priv, intel_encoder->power_domain, wakeref); + + return ret; +} + +void intel_crt_reset(struct drm_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->dev); + struct intel_crt *crt = intel_encoder_to_crt(to_intel_encoder(encoder)); + + if (DISPLAY_VER(dev_priv) >= 5) { + u32 adpa; + + adpa = intel_de_read(dev_priv, crt->adpa_reg); + adpa &= ~ADPA_CRT_HOTPLUG_MASK; + adpa |= ADPA_HOTPLUG_BITS; + intel_de_write(dev_priv, crt->adpa_reg, adpa); + intel_de_posting_read(dev_priv, crt->adpa_reg); + + drm_dbg_kms(&dev_priv->drm, "crt adpa set to 0x%x\n", adpa); + crt->force_hotplug_required = true; + } + +} + +/* + * Routines for controlling stuff on the analog port + */ + +static const struct drm_connector_funcs intel_crt_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .late_register = intel_connector_register, + .early_unregister = intel_connector_unregister, + .destroy = intel_connector_destroy, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, +}; + +static const struct drm_connector_helper_funcs intel_crt_connector_helper_funcs = { + .detect_ctx = intel_crt_detect, + .mode_valid = intel_crt_mode_valid, + .get_modes = intel_crt_get_modes, +}; + +static const struct drm_encoder_funcs intel_crt_enc_funcs = { + .reset = intel_crt_reset, + .destroy = intel_encoder_destroy, +}; + +void intel_crt_init(struct drm_i915_private *dev_priv) +{ + struct drm_connector *connector; + struct intel_crt *crt; + struct intel_connector *intel_connector; + i915_reg_t adpa_reg; + u32 adpa; + + if (HAS_PCH_SPLIT(dev_priv)) + adpa_reg = PCH_ADPA; + else if (IS_VALLEYVIEW(dev_priv)) + adpa_reg = VLV_ADPA; + else + adpa_reg = ADPA; + + adpa = intel_de_read(dev_priv, adpa_reg); + if ((adpa & ADPA_DAC_ENABLE) == 0) { + /* + * On some machines (some IVB at least) CRT can be + * fused off, but there's no known fuse bit to + * indicate that. On these machine the ADPA register + * works normally, except the DAC enable bit won't + * take. So the only way to tell is attempt to enable + * it and see what happens. + */ + intel_de_write(dev_priv, adpa_reg, + adpa | ADPA_DAC_ENABLE | ADPA_HSYNC_CNTL_DISABLE | ADPA_VSYNC_CNTL_DISABLE); + if ((intel_de_read(dev_priv, adpa_reg) & ADPA_DAC_ENABLE) == 0) + return; + intel_de_write(dev_priv, adpa_reg, adpa); + } + + crt = kzalloc(sizeof(struct intel_crt), GFP_KERNEL); + if (!crt) + return; + + intel_connector = intel_connector_alloc(); + if (!intel_connector) { + kfree(crt); + return; + } + + connector = &intel_connector->base; + crt->connector = intel_connector; + drm_connector_init(&dev_priv->drm, &intel_connector->base, + &intel_crt_connector_funcs, DRM_MODE_CONNECTOR_VGA); + + drm_encoder_init(&dev_priv->drm, &crt->base.base, &intel_crt_enc_funcs, + DRM_MODE_ENCODER_DAC, "CRT"); + + intel_connector_attach_encoder(intel_connector, &crt->base); + + crt->base.type = INTEL_OUTPUT_ANALOG; + crt->base.cloneable = (1 << INTEL_OUTPUT_DVO) | (1 << INTEL_OUTPUT_HDMI); + if (IS_I830(dev_priv)) + crt->base.pipe_mask = BIT(PIPE_A); + else + crt->base.pipe_mask = ~0; + + if (DISPLAY_VER(dev_priv) == 2) + connector->interlace_allowed = 0; + else + connector->interlace_allowed = 1; + connector->doublescan_allowed = 0; + + crt->adpa_reg = adpa_reg; + + crt->base.power_domain = POWER_DOMAIN_PORT_CRT; + + if (I915_HAS_HOTPLUG(dev_priv) && + !dmi_check_system(intel_spurious_crt_detect)) { + crt->base.hpd_pin = HPD_CRT; + crt->base.hotplug = intel_encoder_hotplug; + intel_connector->polled = DRM_CONNECTOR_POLL_HPD; + } else { + intel_connector->polled = DRM_CONNECTOR_POLL_CONNECT; + } + + if (HAS_DDI(dev_priv)) { + crt->base.port = PORT_E; + crt->base.get_config = hsw_crt_get_config; + crt->base.get_hw_state = intel_ddi_get_hw_state; + crt->base.compute_config = hsw_crt_compute_config; + crt->base.pre_pll_enable = hsw_pre_pll_enable_crt; + crt->base.pre_enable = hsw_pre_enable_crt; + crt->base.enable = hsw_enable_crt; + crt->base.disable = hsw_disable_crt; + crt->base.post_disable = hsw_post_disable_crt; + crt->base.enable_clock = hsw_ddi_enable_clock; + crt->base.disable_clock = hsw_ddi_disable_clock; + crt->base.is_clock_enabled = hsw_ddi_is_clock_enabled; + + intel_ddi_buf_trans_init(&crt->base); + } else { + if (HAS_PCH_SPLIT(dev_priv)) { + crt->base.compute_config = pch_crt_compute_config; + crt->base.disable = pch_disable_crt; + crt->base.post_disable = pch_post_disable_crt; + } else { + crt->base.compute_config = intel_crt_compute_config; + crt->base.disable = intel_disable_crt; + } + crt->base.port = PORT_NONE; + crt->base.get_config = intel_crt_get_config; + crt->base.get_hw_state = intel_crt_get_hw_state; + crt->base.enable = intel_enable_crt; + } + intel_connector->get_hw_state = intel_connector_get_hw_state; + + drm_connector_helper_add(connector, &intel_crt_connector_helper_funcs); + + /* + * TODO: find a proper way to discover whether we need to set the the + * polarity and link reversal bits or not, instead of relying on the + * BIOS. + */ + if (HAS_PCH_LPT(dev_priv)) { + u32 fdi_config = FDI_RX_POLARITY_REVERSED_LPT | + FDI_RX_LINK_REVERSAL_OVERRIDE; + + dev_priv->display.fdi.rx_config = intel_de_read(dev_priv, + FDI_RX_CTL(PIPE_A)) & fdi_config; + } + + intel_crt_reset(&crt->base.base); +} diff --git a/drivers/gpu/drm/i915/display/intel_crt.h b/drivers/gpu/drm/i915/display/intel_crt.h new file mode 100644 index 000000000..c6071efd9 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_crt.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_CRT_H__ +#define __INTEL_CRT_H__ + +#include "i915_reg_defs.h" + +enum pipe; +struct drm_encoder; +struct drm_i915_private; + +bool intel_crt_port_enabled(struct drm_i915_private *dev_priv, + i915_reg_t adpa_reg, enum pipe *pipe); +void intel_crt_init(struct drm_i915_private *dev_priv); +void intel_crt_reset(struct drm_encoder *encoder); + +#endif /* __INTEL_CRT_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_crtc.c b/drivers/gpu/drm/i915/display/intel_crtc.c new file mode 100644 index 000000000..6792a9056 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_crtc.c @@ -0,0 +1,705 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2020 Intel Corporation + */ +#include +#include +#include + +#include +#include +#include +#include + +#include "i915_irq.h" +#include "i915_vgpu.h" +#include "i9xx_plane.h" +#include "icl_dsi.h" +#include "intel_atomic.h" +#include "intel_atomic_plane.h" +#include "intel_color.h" +#include "intel_crtc.h" +#include "intel_cursor.h" +#include "intel_display_debugfs.h" +#include "intel_display_trace.h" +#include "intel_display_types.h" +#include "intel_drrs.h" +#include "intel_dsi.h" +#include "intel_pipe_crc.h" +#include "intel_psr.h" +#include "intel_sprite.h" +#include "intel_vrr.h" +#include "skl_universal_plane.h" + +static void assert_vblank_disabled(struct drm_crtc *crtc) +{ + if (I915_STATE_WARN_ON(drm_crtc_vblank_get(crtc) == 0)) + drm_crtc_vblank_put(crtc); +} + +struct intel_crtc *intel_first_crtc(struct drm_i915_private *i915) +{ + return to_intel_crtc(drm_crtc_from_index(&i915->drm, 0)); +} + +struct intel_crtc *intel_crtc_for_pipe(struct drm_i915_private *i915, + enum pipe pipe) +{ + struct intel_crtc *crtc; + + for_each_intel_crtc(&i915->drm, crtc) { + if (crtc->pipe == pipe) + return crtc; + } + + return NULL; +} + +void intel_crtc_wait_for_next_vblank(struct intel_crtc *crtc) +{ + drm_crtc_wait_one_vblank(&crtc->base); +} + +void intel_wait_for_vblank_if_active(struct drm_i915_private *i915, + enum pipe pipe) +{ + struct intel_crtc *crtc = intel_crtc_for_pipe(i915, pipe); + + if (crtc->active) + intel_crtc_wait_for_next_vblank(crtc); +} + +u32 intel_crtc_get_vblank_counter(struct intel_crtc *crtc) +{ + struct drm_device *dev = crtc->base.dev; + struct drm_vblank_crtc *vblank = &dev->vblank[drm_crtc_index(&crtc->base)]; + + if (!crtc->active) + return 0; + + if (!vblank->max_vblank_count) + return (u32)drm_crtc_accurate_vblank_count(&crtc->base); + + return crtc->base.funcs->get_vblank_counter(&crtc->base); +} + +u32 intel_crtc_max_vblank_count(const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + + /* + * From Gen 11, In case of dsi cmd mode, frame counter wouldnt + * have updated at the beginning of TE, if we want to use + * the hw counter, then we would find it updated in only + * the next TE, hence switching to sw counter. + */ + if (crtc_state->mode_flags & (I915_MODE_FLAG_DSI_USE_TE0 | + I915_MODE_FLAG_DSI_USE_TE1)) + return 0; + + /* + * On i965gm the hardware frame counter reads + * zero when the TV encoder is enabled :( + */ + if (IS_I965GM(dev_priv) && + (crtc_state->output_types & BIT(INTEL_OUTPUT_TVOUT))) + return 0; + + if (DISPLAY_VER(dev_priv) >= 5 || IS_G4X(dev_priv)) + return 0xffffffff; /* full 32 bit counter */ + else if (DISPLAY_VER(dev_priv) >= 3) + return 0xffffff; /* only 24 bits of frame count */ + else + return 0; /* Gen2 doesn't have a hardware frame counter */ +} + +void intel_crtc_vblank_on(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + assert_vblank_disabled(&crtc->base); + drm_crtc_set_max_vblank_count(&crtc->base, + intel_crtc_max_vblank_count(crtc_state)); + drm_crtc_vblank_on(&crtc->base); + + /* + * Should really happen exactly when we enable the pipe + * but we want the frame counters in the trace, and that + * requires vblank support on some platforms/outputs. + */ + trace_intel_pipe_enable(crtc); +} + +void intel_crtc_vblank_off(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + /* + * Should really happen exactly when we disable the pipe + * but we want the frame counters in the trace, and that + * requires vblank support on some platforms/outputs. + */ + trace_intel_pipe_disable(crtc); + + drm_crtc_vblank_off(&crtc->base); + assert_vblank_disabled(&crtc->base); +} + +struct intel_crtc_state *intel_crtc_state_alloc(struct intel_crtc *crtc) +{ + struct intel_crtc_state *crtc_state; + + crtc_state = kmalloc(sizeof(*crtc_state), GFP_KERNEL); + + if (crtc_state) + intel_crtc_state_reset(crtc_state, crtc); + + return crtc_state; +} + +void intel_crtc_state_reset(struct intel_crtc_state *crtc_state, + struct intel_crtc *crtc) +{ + memset(crtc_state, 0, sizeof(*crtc_state)); + + __drm_atomic_helper_crtc_state_reset(&crtc_state->uapi, &crtc->base); + + crtc_state->cpu_transcoder = INVALID_TRANSCODER; + crtc_state->master_transcoder = INVALID_TRANSCODER; + crtc_state->hsw_workaround_pipe = INVALID_PIPE; + crtc_state->scaler_state.scaler_id = -1; + crtc_state->mst_master_transcoder = INVALID_TRANSCODER; +} + +static struct intel_crtc *intel_crtc_alloc(void) +{ + struct intel_crtc_state *crtc_state; + struct intel_crtc *crtc; + + crtc = kzalloc(sizeof(*crtc), GFP_KERNEL); + if (!crtc) + return ERR_PTR(-ENOMEM); + + crtc_state = intel_crtc_state_alloc(crtc); + if (!crtc_state) { + kfree(crtc); + return ERR_PTR(-ENOMEM); + } + + crtc->base.state = &crtc_state->uapi; + crtc->config = crtc_state; + + return crtc; +} + +static void intel_crtc_free(struct intel_crtc *crtc) +{ + intel_crtc_destroy_state(&crtc->base, crtc->base.state); + kfree(crtc); +} + +static void intel_crtc_destroy(struct drm_crtc *_crtc) +{ + struct intel_crtc *crtc = to_intel_crtc(_crtc); + + cpu_latency_qos_remove_request(&crtc->vblank_pm_qos); + + drm_crtc_cleanup(&crtc->base); + kfree(crtc); +} + +static int intel_crtc_late_register(struct drm_crtc *crtc) +{ + intel_crtc_debugfs_add(crtc); + return 0; +} + +#define INTEL_CRTC_FUNCS \ + .set_config = drm_atomic_helper_set_config, \ + .destroy = intel_crtc_destroy, \ + .page_flip = drm_atomic_helper_page_flip, \ + .atomic_duplicate_state = intel_crtc_duplicate_state, \ + .atomic_destroy_state = intel_crtc_destroy_state, \ + .set_crc_source = intel_crtc_set_crc_source, \ + .verify_crc_source = intel_crtc_verify_crc_source, \ + .get_crc_sources = intel_crtc_get_crc_sources, \ + .late_register = intel_crtc_late_register + +static const struct drm_crtc_funcs bdw_crtc_funcs = { + INTEL_CRTC_FUNCS, + + .get_vblank_counter = g4x_get_vblank_counter, + .enable_vblank = bdw_enable_vblank, + .disable_vblank = bdw_disable_vblank, + .get_vblank_timestamp = intel_crtc_get_vblank_timestamp, +}; + +static const struct drm_crtc_funcs ilk_crtc_funcs = { + INTEL_CRTC_FUNCS, + + .get_vblank_counter = g4x_get_vblank_counter, + .enable_vblank = ilk_enable_vblank, + .disable_vblank = ilk_disable_vblank, + .get_vblank_timestamp = intel_crtc_get_vblank_timestamp, +}; + +static const struct drm_crtc_funcs g4x_crtc_funcs = { + INTEL_CRTC_FUNCS, + + .get_vblank_counter = g4x_get_vblank_counter, + .enable_vblank = i965_enable_vblank, + .disable_vblank = i965_disable_vblank, + .get_vblank_timestamp = intel_crtc_get_vblank_timestamp, +}; + +static const struct drm_crtc_funcs i965_crtc_funcs = { + INTEL_CRTC_FUNCS, + + .get_vblank_counter = i915_get_vblank_counter, + .enable_vblank = i965_enable_vblank, + .disable_vblank = i965_disable_vblank, + .get_vblank_timestamp = intel_crtc_get_vblank_timestamp, +}; + +static const struct drm_crtc_funcs i915gm_crtc_funcs = { + INTEL_CRTC_FUNCS, + + .get_vblank_counter = i915_get_vblank_counter, + .enable_vblank = i915gm_enable_vblank, + .disable_vblank = i915gm_disable_vblank, + .get_vblank_timestamp = intel_crtc_get_vblank_timestamp, +}; + +static const struct drm_crtc_funcs i915_crtc_funcs = { + INTEL_CRTC_FUNCS, + + .get_vblank_counter = i915_get_vblank_counter, + .enable_vblank = i8xx_enable_vblank, + .disable_vblank = i8xx_disable_vblank, + .get_vblank_timestamp = intel_crtc_get_vblank_timestamp, +}; + +static const struct drm_crtc_funcs i8xx_crtc_funcs = { + INTEL_CRTC_FUNCS, + + /* no hw vblank counter */ + .enable_vblank = i8xx_enable_vblank, + .disable_vblank = i8xx_disable_vblank, + .get_vblank_timestamp = intel_crtc_get_vblank_timestamp, +}; + +int intel_crtc_init(struct drm_i915_private *dev_priv, enum pipe pipe) +{ + struct intel_plane *primary, *cursor; + const struct drm_crtc_funcs *funcs; + struct intel_crtc *crtc; + int sprite, ret; + + crtc = intel_crtc_alloc(); + if (IS_ERR(crtc)) + return PTR_ERR(crtc); + + crtc->pipe = pipe; + crtc->num_scalers = RUNTIME_INFO(dev_priv)->num_scalers[pipe]; + + if (DISPLAY_VER(dev_priv) >= 9) + primary = skl_universal_plane_create(dev_priv, pipe, + PLANE_PRIMARY); + else + primary = intel_primary_plane_create(dev_priv, pipe); + if (IS_ERR(primary)) { + ret = PTR_ERR(primary); + goto fail; + } + crtc->plane_ids_mask |= BIT(primary->id); + + for_each_sprite(dev_priv, pipe, sprite) { + struct intel_plane *plane; + + if (DISPLAY_VER(dev_priv) >= 9) + plane = skl_universal_plane_create(dev_priv, pipe, + PLANE_SPRITE0 + sprite); + else + plane = intel_sprite_plane_create(dev_priv, pipe, sprite); + if (IS_ERR(plane)) { + ret = PTR_ERR(plane); + goto fail; + } + crtc->plane_ids_mask |= BIT(plane->id); + } + + cursor = intel_cursor_plane_create(dev_priv, pipe); + if (IS_ERR(cursor)) { + ret = PTR_ERR(cursor); + goto fail; + } + crtc->plane_ids_mask |= BIT(cursor->id); + + if (HAS_GMCH(dev_priv)) { + if (IS_CHERRYVIEW(dev_priv) || + IS_VALLEYVIEW(dev_priv) || IS_G4X(dev_priv)) + funcs = &g4x_crtc_funcs; + else if (DISPLAY_VER(dev_priv) == 4) + funcs = &i965_crtc_funcs; + else if (IS_I945GM(dev_priv) || IS_I915GM(dev_priv)) + funcs = &i915gm_crtc_funcs; + else if (DISPLAY_VER(dev_priv) == 3) + funcs = &i915_crtc_funcs; + else + funcs = &i8xx_crtc_funcs; + } else { + if (DISPLAY_VER(dev_priv) >= 8) + funcs = &bdw_crtc_funcs; + else + funcs = &ilk_crtc_funcs; + } + + ret = drm_crtc_init_with_planes(&dev_priv->drm, &crtc->base, + &primary->base, &cursor->base, + funcs, "pipe %c", pipe_name(pipe)); + if (ret) + goto fail; + + if (DISPLAY_VER(dev_priv) >= 11) + drm_crtc_create_scaling_filter_property(&crtc->base, + BIT(DRM_SCALING_FILTER_DEFAULT) | + BIT(DRM_SCALING_FILTER_NEAREST_NEIGHBOR)); + + intel_color_init(crtc); + + intel_crtc_drrs_init(crtc); + intel_crtc_crc_init(crtc); + + cpu_latency_qos_add_request(&crtc->vblank_pm_qos, PM_QOS_DEFAULT_VALUE); + + drm_WARN_ON(&dev_priv->drm, drm_crtc_index(&crtc->base) != crtc->pipe); + + return 0; + +fail: + intel_crtc_free(crtc); + + return ret; +} + +static bool intel_crtc_needs_vblank_work(const struct intel_crtc_state *crtc_state) +{ + return crtc_state->hw.active && + !intel_crtc_needs_modeset(crtc_state) && + !crtc_state->preload_luts && + (crtc_state->uapi.color_mgmt_changed || + crtc_state->update_pipe); +} + +static void intel_crtc_vblank_work(struct kthread_work *base) +{ + struct drm_vblank_work *work = to_drm_vblank_work(base); + struct intel_crtc_state *crtc_state = + container_of(work, typeof(*crtc_state), vblank_work); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + trace_intel_crtc_vblank_work_start(crtc); + + intel_color_load_luts(crtc_state); + + if (crtc_state->uapi.event) { + spin_lock_irq(&crtc->base.dev->event_lock); + drm_crtc_send_vblank_event(&crtc->base, crtc_state->uapi.event); + crtc_state->uapi.event = NULL; + spin_unlock_irq(&crtc->base.dev->event_lock); + } + + trace_intel_crtc_vblank_work_end(crtc); +} + +static void intel_crtc_vblank_work_init(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + drm_vblank_work_init(&crtc_state->vblank_work, &crtc->base, + intel_crtc_vblank_work); + /* + * Interrupt latency is critical for getting the vblank + * work executed as early as possible during the vblank. + */ + cpu_latency_qos_update_request(&crtc->vblank_pm_qos, 0); +} + +void intel_wait_for_vblank_workers(struct intel_atomic_state *state) +{ + struct intel_crtc_state *crtc_state; + struct intel_crtc *crtc; + int i; + + for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) { + if (!intel_crtc_needs_vblank_work(crtc_state)) + continue; + + drm_vblank_work_flush(&crtc_state->vblank_work); + cpu_latency_qos_update_request(&crtc->vblank_pm_qos, + PM_QOS_DEFAULT_VALUE); + } +} + +int intel_usecs_to_scanlines(const struct drm_display_mode *adjusted_mode, + int usecs) +{ + /* paranoia */ + if (!adjusted_mode->crtc_htotal) + return 1; + + return DIV_ROUND_UP(usecs * adjusted_mode->crtc_clock, + 1000 * adjusted_mode->crtc_htotal); +} + +static int intel_mode_vblank_start(const struct drm_display_mode *mode) +{ + int vblank_start = mode->crtc_vblank_start; + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + vblank_start = DIV_ROUND_UP(vblank_start, 2); + + return vblank_start; +} + +/** + * intel_pipe_update_start() - start update of a set of display registers + * @new_crtc_state: the new crtc state + * + * Mark the start of an update to pipe registers that should be updated + * atomically regarding vblank. If the next vblank will happens within + * the next 100 us, this function waits until the vblank passes. + * + * After a successful call to this function, interrupts will be disabled + * until a subsequent call to intel_pipe_update_end(). That is done to + * avoid random delays. + */ +void intel_pipe_update_start(struct intel_crtc_state *new_crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(new_crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + const struct drm_display_mode *adjusted_mode = &new_crtc_state->hw.adjusted_mode; + long timeout = msecs_to_jiffies_timeout(1); + int scanline, min, max, vblank_start; + wait_queue_head_t *wq = drm_crtc_vblank_waitqueue(&crtc->base); + bool need_vlv_dsi_wa = (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) && + intel_crtc_has_type(new_crtc_state, INTEL_OUTPUT_DSI); + DEFINE_WAIT(wait); + + intel_psr_lock(new_crtc_state); + + if (new_crtc_state->do_async_flip) + return; + + if (intel_crtc_needs_vblank_work(new_crtc_state)) + intel_crtc_vblank_work_init(new_crtc_state); + + if (new_crtc_state->vrr.enable) { + if (intel_vrr_is_push_sent(new_crtc_state)) + vblank_start = intel_vrr_vmin_vblank_start(new_crtc_state); + else + vblank_start = intel_vrr_vmax_vblank_start(new_crtc_state); + } else { + vblank_start = intel_mode_vblank_start(adjusted_mode); + } + + /* FIXME needs to be calibrated sensibly */ + min = vblank_start - intel_usecs_to_scanlines(adjusted_mode, + VBLANK_EVASION_TIME_US); + max = vblank_start - 1; + + if (min <= 0 || max <= 0) + goto irq_disable; + + if (drm_WARN_ON(&dev_priv->drm, drm_crtc_vblank_get(&crtc->base))) + goto irq_disable; + + /* + * Wait for psr to idle out after enabling the VBL interrupts + * VBL interrupts will start the PSR exit and prevent a PSR + * re-entry as well. + */ + intel_psr_wait_for_idle_locked(new_crtc_state); + + local_irq_disable(); + + crtc->debug.min_vbl = min; + crtc->debug.max_vbl = max; + trace_intel_pipe_update_start(crtc); + + for (;;) { + /* + * prepare_to_wait() has a memory barrier, which guarantees + * other CPUs can see the task state update by the time we + * read the scanline. + */ + prepare_to_wait(wq, &wait, TASK_UNINTERRUPTIBLE); + + scanline = intel_get_crtc_scanline(crtc); + if (scanline < min || scanline > max) + break; + + if (!timeout) { + drm_err(&dev_priv->drm, + "Potential atomic update failure on pipe %c\n", + pipe_name(crtc->pipe)); + break; + } + + local_irq_enable(); + + timeout = schedule_timeout(timeout); + + local_irq_disable(); + } + + finish_wait(wq, &wait); + + drm_crtc_vblank_put(&crtc->base); + + /* + * On VLV/CHV DSI the scanline counter would appear to + * increment approx. 1/3 of a scanline before start of vblank. + * The registers still get latched at start of vblank however. + * This means we must not write any registers on the first + * line of vblank (since not the whole line is actually in + * vblank). And unfortunately we can't use the interrupt to + * wait here since it will fire too soon. We could use the + * frame start interrupt instead since it will fire after the + * critical scanline, but that would require more changes + * in the interrupt code. So for now we'll just do the nasty + * thing and poll for the bad scanline to pass us by. + * + * FIXME figure out if BXT+ DSI suffers from this as well + */ + while (need_vlv_dsi_wa && scanline == vblank_start) + scanline = intel_get_crtc_scanline(crtc); + + crtc->debug.scanline_start = scanline; + crtc->debug.start_vbl_time = ktime_get(); + crtc->debug.start_vbl_count = intel_crtc_get_vblank_counter(crtc); + + trace_intel_pipe_update_vblank_evaded(crtc); + return; + +irq_disable: + local_irq_disable(); +} + +#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_VBLANK_EVADE) +static void dbg_vblank_evade(struct intel_crtc *crtc, ktime_t end) +{ + u64 delta = ktime_to_ns(ktime_sub(end, crtc->debug.start_vbl_time)); + unsigned int h; + + h = ilog2(delta >> 9); + if (h >= ARRAY_SIZE(crtc->debug.vbl.times)) + h = ARRAY_SIZE(crtc->debug.vbl.times) - 1; + crtc->debug.vbl.times[h]++; + + crtc->debug.vbl.sum += delta; + if (!crtc->debug.vbl.min || delta < crtc->debug.vbl.min) + crtc->debug.vbl.min = delta; + if (delta > crtc->debug.vbl.max) + crtc->debug.vbl.max = delta; + + if (delta > 1000 * VBLANK_EVASION_TIME_US) { + drm_dbg_kms(crtc->base.dev, + "Atomic update on pipe (%c) took %lld us, max time under evasion is %u us\n", + pipe_name(crtc->pipe), + div_u64(delta, 1000), + VBLANK_EVASION_TIME_US); + crtc->debug.vbl.over++; + } +} +#else +static void dbg_vblank_evade(struct intel_crtc *crtc, ktime_t end) {} +#endif + +/** + * intel_pipe_update_end() - end update of a set of display registers + * @new_crtc_state: the new crtc state + * + * Mark the end of an update started with intel_pipe_update_start(). This + * re-enables interrupts and verifies the update was actually completed + * before a vblank. + */ +void intel_pipe_update_end(struct intel_crtc_state *new_crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(new_crtc_state->uapi.crtc); + enum pipe pipe = crtc->pipe; + int scanline_end = intel_get_crtc_scanline(crtc); + u32 end_vbl_count = intel_crtc_get_vblank_counter(crtc); + ktime_t end_vbl_time = ktime_get(); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + + intel_psr_unlock(new_crtc_state); + + if (new_crtc_state->do_async_flip) + return; + + trace_intel_pipe_update_end(crtc, end_vbl_count, scanline_end); + + /* + * Incase of mipi dsi command mode, we need to set frame update + * request for every commit. + */ + if (DISPLAY_VER(dev_priv) >= 11 && + intel_crtc_has_type(new_crtc_state, INTEL_OUTPUT_DSI)) + icl_dsi_frame_update(new_crtc_state); + + /* We're still in the vblank-evade critical section, this can't race. + * Would be slightly nice to just grab the vblank count and arm the + * event outside of the critical section - the spinlock might spin for a + * while ... */ + if (intel_crtc_needs_vblank_work(new_crtc_state)) { + drm_vblank_work_schedule(&new_crtc_state->vblank_work, + drm_crtc_accurate_vblank_count(&crtc->base) + 1, + false); + } else if (new_crtc_state->uapi.event) { + drm_WARN_ON(&dev_priv->drm, + drm_crtc_vblank_get(&crtc->base) != 0); + + spin_lock(&crtc->base.dev->event_lock); + drm_crtc_arm_vblank_event(&crtc->base, + new_crtc_state->uapi.event); + spin_unlock(&crtc->base.dev->event_lock); + + new_crtc_state->uapi.event = NULL; + } + + /* + * Send VRR Push to terminate Vblank. If we are already in vblank + * this has to be done _after_ sampling the frame counter, as + * otherwise the push would immediately terminate the vblank and + * the sampled frame counter would correspond to the next frame + * instead of the current frame. + * + * There is a tiny race here (iff vblank evasion failed us) where + * we might sample the frame counter just before vmax vblank start + * but the push would be sent just after it. That would cause the + * push to affect the next frame instead of the current frame, + * which would cause the next frame to terminate already at vmin + * vblank start instead of vmax vblank start. + */ + intel_vrr_send_push(new_crtc_state); + + local_irq_enable(); + + if (intel_vgpu_active(dev_priv)) + return; + + if (crtc->debug.start_vbl_count && + crtc->debug.start_vbl_count != end_vbl_count) { + drm_err(&dev_priv->drm, + "Atomic update failure on pipe %c (start=%u end=%u) time %lld us, min %d, max %d, scanline start %d, end %d\n", + pipe_name(pipe), crtc->debug.start_vbl_count, + end_vbl_count, + ktime_us_delta(end_vbl_time, + crtc->debug.start_vbl_time), + crtc->debug.min_vbl, crtc->debug.max_vbl, + crtc->debug.scanline_start, scanline_end); + } + + dbg_vblank_evade(crtc, end_vbl_time); +} diff --git a/drivers/gpu/drm/i915/display/intel_crtc.h b/drivers/gpu/drm/i915/display/intel_crtc.h new file mode 100644 index 000000000..73077137f --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_crtc.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2020 Intel Corporation + */ + +#ifndef _INTEL_CRTC_H_ +#define _INTEL_CRTC_H_ + +#include + +enum i9xx_plane_id; +enum pipe; +struct drm_display_mode; +struct drm_i915_private; +struct intel_atomic_state; +struct intel_crtc; +struct intel_crtc_state; + +int intel_usecs_to_scanlines(const struct drm_display_mode *adjusted_mode, + int usecs); +u32 intel_crtc_max_vblank_count(const struct intel_crtc_state *crtc_state); +int intel_crtc_init(struct drm_i915_private *dev_priv, enum pipe pipe); +struct intel_crtc_state *intel_crtc_state_alloc(struct intel_crtc *crtc); +void intel_crtc_state_reset(struct intel_crtc_state *crtc_state, + struct intel_crtc *crtc); +u32 intel_crtc_get_vblank_counter(struct intel_crtc *crtc); +void intel_crtc_vblank_on(const struct intel_crtc_state *crtc_state); +void intel_crtc_vblank_off(const struct intel_crtc_state *crtc_state); +void intel_pipe_update_start(struct intel_crtc_state *new_crtc_state); +void intel_pipe_update_end(struct intel_crtc_state *new_crtc_state); +void intel_wait_for_vblank_workers(struct intel_atomic_state *state); +struct intel_crtc *intel_first_crtc(struct drm_i915_private *i915); +struct intel_crtc *intel_crtc_for_pipe(struct drm_i915_private *i915, + enum pipe pipe); +void intel_wait_for_vblank_if_active(struct drm_i915_private *i915, + enum pipe pipe); +void intel_crtc_wait_for_next_vblank(struct intel_crtc *crtc); + +#endif diff --git a/drivers/gpu/drm/i915/display/intel_crtc_state_dump.c b/drivers/gpu/drm/i915/display/intel_crtc_state_dump.c new file mode 100644 index 000000000..e9212f69c --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_crtc_state_dump.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2022 Intel Corporation + */ + +#include "i915_drv.h" +#include "intel_crtc_state_dump.h" +#include "intel_display_types.h" +#include "intel_hdmi.h" +#include "intel_vrr.h" + +static void intel_dump_crtc_timings(struct drm_i915_private *i915, + const struct drm_display_mode *mode) +{ + drm_dbg_kms(&i915->drm, "crtc timings: %d %d %d %d %d %d %d %d %d, " + "type: 0x%x flags: 0x%x\n", + mode->crtc_clock, + mode->crtc_hdisplay, mode->crtc_hsync_start, + mode->crtc_hsync_end, mode->crtc_htotal, + mode->crtc_vdisplay, mode->crtc_vsync_start, + mode->crtc_vsync_end, mode->crtc_vtotal, + mode->type, mode->flags); +} + +static void +intel_dump_m_n_config(const struct intel_crtc_state *pipe_config, + const char *id, unsigned int lane_count, + const struct intel_link_m_n *m_n) +{ + struct drm_i915_private *i915 = to_i915(pipe_config->uapi.crtc->dev); + + drm_dbg_kms(&i915->drm, + "%s: lanes: %i; data_m: %u, data_n: %u, link_m: %u, link_n: %u, tu: %u\n", + id, lane_count, + m_n->data_m, m_n->data_n, + m_n->link_m, m_n->link_n, m_n->tu); +} + +static void +intel_dump_infoframe(struct drm_i915_private *i915, + const union hdmi_infoframe *frame) +{ + if (!drm_debug_enabled(DRM_UT_KMS)) + return; + + hdmi_infoframe_log(KERN_DEBUG, i915->drm.dev, frame); +} + +static void +intel_dump_dp_vsc_sdp(struct drm_i915_private *i915, + const struct drm_dp_vsc_sdp *vsc) +{ + if (!drm_debug_enabled(DRM_UT_KMS)) + return; + + drm_dp_vsc_sdp_log(KERN_DEBUG, i915->drm.dev, vsc); +} + +#define OUTPUT_TYPE(x) [INTEL_OUTPUT_ ## x] = #x + +static const char * const output_type_str[] = { + OUTPUT_TYPE(UNUSED), + OUTPUT_TYPE(ANALOG), + OUTPUT_TYPE(DVO), + OUTPUT_TYPE(SDVO), + OUTPUT_TYPE(LVDS), + OUTPUT_TYPE(TVOUT), + OUTPUT_TYPE(HDMI), + OUTPUT_TYPE(DP), + OUTPUT_TYPE(EDP), + OUTPUT_TYPE(DSI), + OUTPUT_TYPE(DDI), + OUTPUT_TYPE(DP_MST), +}; + +#undef OUTPUT_TYPE + +static void snprintf_output_types(char *buf, size_t len, + unsigned int output_types) +{ + char *str = buf; + int i; + + str[0] = '\0'; + + for (i = 0; i < ARRAY_SIZE(output_type_str); i++) { + int r; + + if ((output_types & BIT(i)) == 0) + continue; + + r = snprintf(str, len, "%s%s", + str != buf ? "," : "", output_type_str[i]); + if (r >= len) + break; + str += r; + len -= r; + + output_types &= ~BIT(i); + } + + WARN_ON_ONCE(output_types != 0); +} + +static const char * const output_format_str[] = { + [INTEL_OUTPUT_FORMAT_RGB] = "RGB", + [INTEL_OUTPUT_FORMAT_YCBCR420] = "YCBCR4:2:0", + [INTEL_OUTPUT_FORMAT_YCBCR444] = "YCBCR4:4:4", +}; + +static const char *output_formats(enum intel_output_format format) +{ + if (format >= ARRAY_SIZE(output_format_str)) + return "invalid"; + return output_format_str[format]; +} + +static void intel_dump_plane_state(const struct intel_plane_state *plane_state) +{ + struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane); + struct drm_i915_private *i915 = to_i915(plane->base.dev); + const struct drm_framebuffer *fb = plane_state->hw.fb; + + if (!fb) { + drm_dbg_kms(&i915->drm, + "[PLANE:%d:%s] fb: [NOFB], visible: %s\n", + plane->base.base.id, plane->base.name, + str_yes_no(plane_state->uapi.visible)); + return; + } + + drm_dbg_kms(&i915->drm, + "[PLANE:%d:%s] fb: [FB:%d] %ux%u format = %p4cc modifier = 0x%llx, visible: %s\n", + plane->base.base.id, plane->base.name, + fb->base.id, fb->width, fb->height, &fb->format->format, + fb->modifier, str_yes_no(plane_state->uapi.visible)); + drm_dbg_kms(&i915->drm, "\trotation: 0x%x, scaler: %d, scaling_filter: %d\n", + plane_state->hw.rotation, plane_state->scaler_id, plane_state->hw.scaling_filter); + if (plane_state->uapi.visible) + drm_dbg_kms(&i915->drm, + "\tsrc: " DRM_RECT_FP_FMT " dst: " DRM_RECT_FMT "\n", + DRM_RECT_FP_ARG(&plane_state->uapi.src), + DRM_RECT_ARG(&plane_state->uapi.dst)); +} + +void intel_crtc_state_dump(const struct intel_crtc_state *pipe_config, + struct intel_atomic_state *state, + const char *context) +{ + struct intel_crtc *crtc = to_intel_crtc(pipe_config->uapi.crtc); + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + const struct intel_plane_state *plane_state; + struct intel_plane *plane; + char buf[64]; + int i; + + drm_dbg_kms(&i915->drm, "[CRTC:%d:%s] enable: %s [%s]\n", + crtc->base.base.id, crtc->base.name, + str_yes_no(pipe_config->hw.enable), context); + + if (!pipe_config->hw.enable) + goto dump_planes; + + snprintf_output_types(buf, sizeof(buf), pipe_config->output_types); + drm_dbg_kms(&i915->drm, + "active: %s, output_types: %s (0x%x), output format: %s\n", + str_yes_no(pipe_config->hw.active), + buf, pipe_config->output_types, + output_formats(pipe_config->output_format)); + + drm_dbg_kms(&i915->drm, + "cpu_transcoder: %s, pipe bpp: %i, dithering: %i\n", + transcoder_name(pipe_config->cpu_transcoder), + pipe_config->pipe_bpp, pipe_config->dither); + + drm_dbg_kms(&i915->drm, "MST master transcoder: %s\n", + transcoder_name(pipe_config->mst_master_transcoder)); + + drm_dbg_kms(&i915->drm, + "port sync: master transcoder: %s, slave transcoder bitmask = 0x%x\n", + transcoder_name(pipe_config->master_transcoder), + pipe_config->sync_mode_slaves_mask); + + drm_dbg_kms(&i915->drm, "bigjoiner: %s, pipes: 0x%x\n", + intel_crtc_is_bigjoiner_slave(pipe_config) ? "slave" : + intel_crtc_is_bigjoiner_master(pipe_config) ? "master" : "no", + pipe_config->bigjoiner_pipes); + + drm_dbg_kms(&i915->drm, "splitter: %s, link count %d, overlap %d\n", + str_enabled_disabled(pipe_config->splitter.enable), + pipe_config->splitter.link_count, + pipe_config->splitter.pixel_overlap); + + if (pipe_config->has_pch_encoder) + intel_dump_m_n_config(pipe_config, "fdi", + pipe_config->fdi_lanes, + &pipe_config->fdi_m_n); + + if (intel_crtc_has_dp_encoder(pipe_config)) { + intel_dump_m_n_config(pipe_config, "dp m_n", + pipe_config->lane_count, + &pipe_config->dp_m_n); + intel_dump_m_n_config(pipe_config, "dp m2_n2", + pipe_config->lane_count, + &pipe_config->dp_m2_n2); + } + + drm_dbg_kms(&i915->drm, "framestart delay: %d, MSA timing delay: %d\n", + pipe_config->framestart_delay, pipe_config->msa_timing_delay); + + drm_dbg_kms(&i915->drm, + "audio: %i, infoframes: %i, infoframes enabled: 0x%x\n", + pipe_config->has_audio, pipe_config->has_infoframe, + pipe_config->infoframes.enable); + + if (pipe_config->infoframes.enable & + intel_hdmi_infoframe_enable(HDMI_PACKET_TYPE_GENERAL_CONTROL)) + drm_dbg_kms(&i915->drm, "GCP: 0x%x\n", + pipe_config->infoframes.gcp); + if (pipe_config->infoframes.enable & + intel_hdmi_infoframe_enable(HDMI_INFOFRAME_TYPE_AVI)) + intel_dump_infoframe(i915, &pipe_config->infoframes.avi); + if (pipe_config->infoframes.enable & + intel_hdmi_infoframe_enable(HDMI_INFOFRAME_TYPE_SPD)) + intel_dump_infoframe(i915, &pipe_config->infoframes.spd); + if (pipe_config->infoframes.enable & + intel_hdmi_infoframe_enable(HDMI_INFOFRAME_TYPE_VENDOR)) + intel_dump_infoframe(i915, &pipe_config->infoframes.hdmi); + if (pipe_config->infoframes.enable & + intel_hdmi_infoframe_enable(HDMI_INFOFRAME_TYPE_DRM)) + intel_dump_infoframe(i915, &pipe_config->infoframes.drm); + if (pipe_config->infoframes.enable & + intel_hdmi_infoframe_enable(HDMI_PACKET_TYPE_GAMUT_METADATA)) + intel_dump_infoframe(i915, &pipe_config->infoframes.drm); + if (pipe_config->infoframes.enable & + intel_hdmi_infoframe_enable(DP_SDP_VSC)) + intel_dump_dp_vsc_sdp(i915, &pipe_config->infoframes.vsc); + + drm_dbg_kms(&i915->drm, "vrr: %s, vmin: %d, vmax: %d, pipeline full: %d, guardband: %d flipline: %d, vmin vblank: %d, vmax vblank: %d\n", + str_yes_no(pipe_config->vrr.enable), + pipe_config->vrr.vmin, pipe_config->vrr.vmax, + pipe_config->vrr.pipeline_full, pipe_config->vrr.guardband, + pipe_config->vrr.flipline, + intel_vrr_vmin_vblank_start(pipe_config), + intel_vrr_vmax_vblank_start(pipe_config)); + + drm_dbg_kms(&i915->drm, "requested mode: " DRM_MODE_FMT "\n", + DRM_MODE_ARG(&pipe_config->hw.mode)); + drm_dbg_kms(&i915->drm, "adjusted mode: " DRM_MODE_FMT "\n", + DRM_MODE_ARG(&pipe_config->hw.adjusted_mode)); + intel_dump_crtc_timings(i915, &pipe_config->hw.adjusted_mode); + drm_dbg_kms(&i915->drm, "pipe mode: " DRM_MODE_FMT "\n", + DRM_MODE_ARG(&pipe_config->hw.pipe_mode)); + intel_dump_crtc_timings(i915, &pipe_config->hw.pipe_mode); + drm_dbg_kms(&i915->drm, + "port clock: %d, pipe src: " DRM_RECT_FMT ", pixel rate %d\n", + pipe_config->port_clock, DRM_RECT_ARG(&pipe_config->pipe_src), + pipe_config->pixel_rate); + + drm_dbg_kms(&i915->drm, "linetime: %d, ips linetime: %d\n", + pipe_config->linetime, pipe_config->ips_linetime); + + if (DISPLAY_VER(i915) >= 9) + drm_dbg_kms(&i915->drm, + "num_scalers: %d, scaler_users: 0x%x, scaler_id: %d, scaling_filter: %d\n", + crtc->num_scalers, + pipe_config->scaler_state.scaler_users, + pipe_config->scaler_state.scaler_id, + pipe_config->hw.scaling_filter); + + if (HAS_GMCH(i915)) + drm_dbg_kms(&i915->drm, + "gmch pfit: control: 0x%08x, ratios: 0x%08x, lvds border: 0x%08x\n", + pipe_config->gmch_pfit.control, + pipe_config->gmch_pfit.pgm_ratios, + pipe_config->gmch_pfit.lvds_border_bits); + else + drm_dbg_kms(&i915->drm, + "pch pfit: " DRM_RECT_FMT ", %s, force thru: %s\n", + DRM_RECT_ARG(&pipe_config->pch_pfit.dst), + str_enabled_disabled(pipe_config->pch_pfit.enabled), + str_yes_no(pipe_config->pch_pfit.force_thru)); + + drm_dbg_kms(&i915->drm, "ips: %i, double wide: %i, drrs: %i\n", + pipe_config->ips_enabled, pipe_config->double_wide, + pipe_config->has_drrs); + + intel_dpll_dump_hw_state(i915, &pipe_config->dpll_hw_state); + + if (IS_CHERRYVIEW(i915)) + drm_dbg_kms(&i915->drm, + "cgm_mode: 0x%x gamma_mode: 0x%x gamma_enable: %d csc_enable: %d\n", + pipe_config->cgm_mode, pipe_config->gamma_mode, + pipe_config->gamma_enable, pipe_config->csc_enable); + else + drm_dbg_kms(&i915->drm, + "csc_mode: 0x%x gamma_mode: 0x%x gamma_enable: %d csc_enable: %d\n", + pipe_config->csc_mode, pipe_config->gamma_mode, + pipe_config->gamma_enable, pipe_config->csc_enable); + + drm_dbg_kms(&i915->drm, "degamma lut: %d entries, gamma lut: %d entries\n", + pipe_config->hw.degamma_lut ? + drm_color_lut_size(pipe_config->hw.degamma_lut) : 0, + pipe_config->hw.gamma_lut ? + drm_color_lut_size(pipe_config->hw.gamma_lut) : 0); + +dump_planes: + if (!state) + return; + + for_each_new_intel_plane_in_state(state, plane, plane_state, i) { + if (plane->pipe == crtc->pipe) + intel_dump_plane_state(plane_state); + } +} diff --git a/drivers/gpu/drm/i915/display/intel_crtc_state_dump.h b/drivers/gpu/drm/i915/display/intel_crtc_state_dump.h new file mode 100644 index 000000000..9399c35b7 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_crtc_state_dump.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2022 Intel Corporation + */ + +#ifndef __INTEL_CRTC_STATE_DUMP_H__ +#define __INTEL_CRTC_STATE_DUMP_H__ + +struct intel_crtc_state; +struct intel_atomic_state; + +void intel_crtc_state_dump(const struct intel_crtc_state *crtc_state, + struct intel_atomic_state *state, + const char *context); + +#endif /* __INTEL_CRTC_STATE_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_cursor.c b/drivers/gpu/drm/i915/display/intel_cursor.c new file mode 100644 index 000000000..87899e89b --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_cursor.c @@ -0,0 +1,828 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2020 Intel Corporation + */ +#include + +#include +#include +#include +#include +#include + +#include "intel_atomic.h" +#include "intel_atomic_plane.h" +#include "intel_cursor.h" +#include "intel_de.h" +#include "intel_display_types.h" +#include "intel_display.h" +#include "intel_fb.h" +#include "intel_fb_pin.h" +#include "intel_frontbuffer.h" +#include "intel_psr.h" +#include "intel_sprite.h" +#include "skl_watermark.h" + +/* Cursor formats */ +static const u32 intel_cursor_formats[] = { + DRM_FORMAT_ARGB8888, +}; + +static u32 intel_cursor_base(const struct intel_plane_state *plane_state) +{ + struct drm_i915_private *dev_priv = + to_i915(plane_state->uapi.plane->dev); + const struct drm_framebuffer *fb = plane_state->hw.fb; + const struct drm_i915_gem_object *obj = intel_fb_obj(fb); + u32 base; + + if (INTEL_INFO(dev_priv)->display.cursor_needs_physical) + base = sg_dma_address(obj->mm.pages->sgl); + else + base = intel_plane_ggtt_offset(plane_state); + + return base + plane_state->view.color_plane[0].offset; +} + +static u32 intel_cursor_position(const struct intel_plane_state *plane_state) +{ + int x = plane_state->uapi.dst.x1; + int y = plane_state->uapi.dst.y1; + u32 pos = 0; + + if (x < 0) { + pos |= CURSOR_POS_X_SIGN; + x = -x; + } + pos |= CURSOR_POS_X(x); + + if (y < 0) { + pos |= CURSOR_POS_Y_SIGN; + y = -y; + } + pos |= CURSOR_POS_Y(y); + + return pos; +} + +static bool intel_cursor_size_ok(const struct intel_plane_state *plane_state) +{ + const struct drm_mode_config *config = + &plane_state->uapi.plane->dev->mode_config; + int width = drm_rect_width(&plane_state->uapi.dst); + int height = drm_rect_height(&plane_state->uapi.dst); + + return width > 0 && width <= config->cursor_width && + height > 0 && height <= config->cursor_height; +} + +static int intel_cursor_check_surface(struct intel_plane_state *plane_state) +{ + struct drm_i915_private *dev_priv = + to_i915(plane_state->uapi.plane->dev); + unsigned int rotation = plane_state->hw.rotation; + int src_x, src_y; + u32 offset; + int ret; + + ret = intel_plane_compute_gtt(plane_state); + if (ret) + return ret; + + if (!plane_state->uapi.visible) + return 0; + + src_x = plane_state->uapi.src.x1 >> 16; + src_y = plane_state->uapi.src.y1 >> 16; + + intel_add_fb_offsets(&src_x, &src_y, plane_state, 0); + offset = intel_plane_compute_aligned_offset(&src_x, &src_y, + plane_state, 0); + + if (src_x != 0 || src_y != 0) { + drm_dbg_kms(&dev_priv->drm, + "Arbitrary cursor panning not supported\n"); + return -EINVAL; + } + + /* + * Put the final coordinates back so that the src + * coordinate checks will see the right values. + */ + drm_rect_translate_to(&plane_state->uapi.src, + src_x << 16, src_y << 16); + + /* ILK+ do this automagically in hardware */ + if (HAS_GMCH(dev_priv) && rotation & DRM_MODE_ROTATE_180) { + const struct drm_framebuffer *fb = plane_state->hw.fb; + int src_w = drm_rect_width(&plane_state->uapi.src) >> 16; + int src_h = drm_rect_height(&plane_state->uapi.src) >> 16; + + offset += (src_h * src_w - 1) * fb->format->cpp[0]; + } + + plane_state->view.color_plane[0].offset = offset; + plane_state->view.color_plane[0].x = src_x; + plane_state->view.color_plane[0].y = src_y; + + return 0; +} + +static int intel_check_cursor(struct intel_crtc_state *crtc_state, + struct intel_plane_state *plane_state) +{ + const struct drm_framebuffer *fb = plane_state->hw.fb; + struct drm_i915_private *i915 = to_i915(plane_state->uapi.plane->dev); + const struct drm_rect src = plane_state->uapi.src; + const struct drm_rect dst = plane_state->uapi.dst; + int ret; + + if (fb && fb->modifier != DRM_FORMAT_MOD_LINEAR) { + drm_dbg_kms(&i915->drm, "cursor cannot be tiled\n"); + return -EINVAL; + } + + ret = intel_atomic_plane_check_clipping(plane_state, crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + true); + if (ret) + return ret; + + /* Use the unclipped src/dst rectangles, which we program to hw */ + plane_state->uapi.src = src; + plane_state->uapi.dst = dst; + + /* final plane coordinates will be relative to the plane's pipe */ + drm_rect_translate(&plane_state->uapi.dst, + -crtc_state->pipe_src.x1, + -crtc_state->pipe_src.y1); + + ret = intel_cursor_check_surface(plane_state); + if (ret) + return ret; + + if (!plane_state->uapi.visible) + return 0; + + ret = intel_plane_check_src_coordinates(plane_state); + if (ret) + return ret; + + return 0; +} + +static unsigned int +i845_cursor_max_stride(struct intel_plane *plane, + u32 pixel_format, u64 modifier, + unsigned int rotation) +{ + return 2048; +} + +static u32 i845_cursor_ctl_crtc(const struct intel_crtc_state *crtc_state) +{ + u32 cntl = 0; + + if (crtc_state->gamma_enable) + cntl |= CURSOR_PIPE_GAMMA_ENABLE; + + return cntl; +} + +static u32 i845_cursor_ctl(const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state) +{ + return CURSOR_ENABLE | + CURSOR_FORMAT_ARGB | + CURSOR_STRIDE(plane_state->view.color_plane[0].mapping_stride); +} + +static bool i845_cursor_size_ok(const struct intel_plane_state *plane_state) +{ + int width = drm_rect_width(&plane_state->uapi.dst); + + /* + * 845g/865g are only limited by the width of their cursors, + * the height is arbitrary up to the precision of the register. + */ + return intel_cursor_size_ok(plane_state) && IS_ALIGNED(width, 64); +} + +static int i845_check_cursor(struct intel_crtc_state *crtc_state, + struct intel_plane_state *plane_state) +{ + const struct drm_framebuffer *fb = plane_state->hw.fb; + struct drm_i915_private *i915 = to_i915(plane_state->uapi.plane->dev); + int ret; + + ret = intel_check_cursor(crtc_state, plane_state); + if (ret) + return ret; + + /* if we want to turn off the cursor ignore width and height */ + if (!fb) + return 0; + + /* Check for which cursor types we support */ + if (!i845_cursor_size_ok(plane_state)) { + drm_dbg_kms(&i915->drm, + "Cursor dimension %dx%d not supported\n", + drm_rect_width(&plane_state->uapi.dst), + drm_rect_height(&plane_state->uapi.dst)); + return -EINVAL; + } + + drm_WARN_ON(&i915->drm, plane_state->uapi.visible && + plane_state->view.color_plane[0].mapping_stride != fb->pitches[0]); + + switch (fb->pitches[0]) { + case 256: + case 512: + case 1024: + case 2048: + break; + default: + drm_dbg_kms(&i915->drm, "Invalid cursor stride (%u)\n", + fb->pitches[0]); + return -EINVAL; + } + + plane_state->ctl = i845_cursor_ctl(crtc_state, plane_state); + + return 0; +} + +/* TODO: split into noarm+arm pair */ +static void i845_cursor_update_arm(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state) +{ + struct drm_i915_private *dev_priv = to_i915(plane->base.dev); + u32 cntl = 0, base = 0, pos = 0, size = 0; + + if (plane_state && plane_state->uapi.visible) { + unsigned int width = drm_rect_width(&plane_state->uapi.dst); + unsigned int height = drm_rect_height(&plane_state->uapi.dst); + + cntl = plane_state->ctl | + i845_cursor_ctl_crtc(crtc_state); + + size = CURSOR_HEIGHT(height) | CURSOR_WIDTH(width); + + base = intel_cursor_base(plane_state); + pos = intel_cursor_position(plane_state); + } + + /* On these chipsets we can only modify the base/size/stride + * whilst the cursor is disabled. + */ + if (plane->cursor.base != base || + plane->cursor.size != size || + plane->cursor.cntl != cntl) { + intel_de_write_fw(dev_priv, CURCNTR(PIPE_A), 0); + intel_de_write_fw(dev_priv, CURBASE(PIPE_A), base); + intel_de_write_fw(dev_priv, CURSIZE(PIPE_A), size); + intel_de_write_fw(dev_priv, CURPOS(PIPE_A), pos); + intel_de_write_fw(dev_priv, CURCNTR(PIPE_A), cntl); + + plane->cursor.base = base; + plane->cursor.size = size; + plane->cursor.cntl = cntl; + } else { + intel_de_write_fw(dev_priv, CURPOS(PIPE_A), pos); + } +} + +static void i845_cursor_disable_arm(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state) +{ + i845_cursor_update_arm(plane, crtc_state, NULL); +} + +static bool i845_cursor_get_hw_state(struct intel_plane *plane, + enum pipe *pipe) +{ + struct drm_i915_private *dev_priv = to_i915(plane->base.dev); + enum intel_display_power_domain power_domain; + intel_wakeref_t wakeref; + bool ret; + + power_domain = POWER_DOMAIN_PIPE(PIPE_A); + wakeref = intel_display_power_get_if_enabled(dev_priv, power_domain); + if (!wakeref) + return false; + + ret = intel_de_read(dev_priv, CURCNTR(PIPE_A)) & CURSOR_ENABLE; + + *pipe = PIPE_A; + + intel_display_power_put(dev_priv, power_domain, wakeref); + + return ret; +} + +static unsigned int +i9xx_cursor_max_stride(struct intel_plane *plane, + u32 pixel_format, u64 modifier, + unsigned int rotation) +{ + return plane->base.dev->mode_config.cursor_width * 4; +} + +static u32 i9xx_cursor_ctl_crtc(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + u32 cntl = 0; + + if (DISPLAY_VER(dev_priv) >= 11) + return cntl; + + if (crtc_state->gamma_enable) + cntl = MCURSOR_PIPE_GAMMA_ENABLE; + + if (crtc_state->csc_enable) + cntl |= MCURSOR_PIPE_CSC_ENABLE; + + if (DISPLAY_VER(dev_priv) < 5 && !IS_G4X(dev_priv)) + cntl |= MCURSOR_PIPE_SEL(crtc->pipe); + + return cntl; +} + +static u32 i9xx_cursor_ctl(const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state) +{ + struct drm_i915_private *dev_priv = + to_i915(plane_state->uapi.plane->dev); + u32 cntl = 0; + + if (IS_SANDYBRIDGE(dev_priv) || IS_IVYBRIDGE(dev_priv)) + cntl |= MCURSOR_TRICKLE_FEED_DISABLE; + + switch (drm_rect_width(&plane_state->uapi.dst)) { + case 64: + cntl |= MCURSOR_MODE_64_ARGB_AX; + break; + case 128: + cntl |= MCURSOR_MODE_128_ARGB_AX; + break; + case 256: + cntl |= MCURSOR_MODE_256_ARGB_AX; + break; + default: + MISSING_CASE(drm_rect_width(&plane_state->uapi.dst)); + return 0; + } + + if (plane_state->hw.rotation & DRM_MODE_ROTATE_180) + cntl |= MCURSOR_ROTATE_180; + + /* Wa_22012358565:adl-p */ + if (DISPLAY_VER(dev_priv) == 13) + cntl |= MCURSOR_ARB_SLOTS(1); + + return cntl; +} + +static bool i9xx_cursor_size_ok(const struct intel_plane_state *plane_state) +{ + struct drm_i915_private *dev_priv = + to_i915(plane_state->uapi.plane->dev); + int width = drm_rect_width(&plane_state->uapi.dst); + int height = drm_rect_height(&plane_state->uapi.dst); + + if (!intel_cursor_size_ok(plane_state)) + return false; + + /* Cursor width is limited to a few power-of-two sizes */ + switch (width) { + case 256: + case 128: + case 64: + break; + default: + return false; + } + + /* + * IVB+ have CUR_FBC_CTL which allows an arbitrary cursor + * height from 8 lines up to the cursor width, when the + * cursor is not rotated. Everything else requires square + * cursors. + */ + if (HAS_CUR_FBC(dev_priv) && + plane_state->hw.rotation & DRM_MODE_ROTATE_0) { + if (height < 8 || height > width) + return false; + } else { + if (height != width) + return false; + } + + return true; +} + +static int i9xx_check_cursor(struct intel_crtc_state *crtc_state, + struct intel_plane_state *plane_state) +{ + struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane); + struct drm_i915_private *dev_priv = to_i915(plane->base.dev); + const struct drm_framebuffer *fb = plane_state->hw.fb; + enum pipe pipe = plane->pipe; + int ret; + + ret = intel_check_cursor(crtc_state, plane_state); + if (ret) + return ret; + + /* if we want to turn off the cursor ignore width and height */ + if (!fb) + return 0; + + /* Check for which cursor types we support */ + if (!i9xx_cursor_size_ok(plane_state)) { + drm_dbg(&dev_priv->drm, + "Cursor dimension %dx%d not supported\n", + drm_rect_width(&plane_state->uapi.dst), + drm_rect_height(&plane_state->uapi.dst)); + return -EINVAL; + } + + drm_WARN_ON(&dev_priv->drm, plane_state->uapi.visible && + plane_state->view.color_plane[0].mapping_stride != fb->pitches[0]); + + if (fb->pitches[0] != + drm_rect_width(&plane_state->uapi.dst) * fb->format->cpp[0]) { + drm_dbg_kms(&dev_priv->drm, + "Invalid cursor stride (%u) (cursor width %d)\n", + fb->pitches[0], + drm_rect_width(&plane_state->uapi.dst)); + return -EINVAL; + } + + /* + * There's something wrong with the cursor on CHV pipe C. + * If it straddles the left edge of the screen then + * moving it away from the edge or disabling it often + * results in a pipe underrun, and often that can lead to + * dead pipe (constant underrun reported, and it scans + * out just a solid color). To recover from that, the + * display power well must be turned off and on again. + * Refuse the put the cursor into that compromised position. + */ + if (IS_CHERRYVIEW(dev_priv) && pipe == PIPE_C && + plane_state->uapi.visible && plane_state->uapi.dst.x1 < 0) { + drm_dbg_kms(&dev_priv->drm, + "CHV cursor C not allowed to straddle the left screen edge\n"); + return -EINVAL; + } + + plane_state->ctl = i9xx_cursor_ctl(crtc_state, plane_state); + + return 0; +} + +/* TODO: split into noarm+arm pair */ +static void i9xx_cursor_update_arm(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state) +{ + struct drm_i915_private *dev_priv = to_i915(plane->base.dev); + enum pipe pipe = plane->pipe; + u32 cntl = 0, base = 0, pos = 0, fbc_ctl = 0; + + if (plane_state && plane_state->uapi.visible) { + int width = drm_rect_width(&plane_state->uapi.dst); + int height = drm_rect_height(&plane_state->uapi.dst); + + cntl = plane_state->ctl | + i9xx_cursor_ctl_crtc(crtc_state); + + if (width != height) + fbc_ctl = CUR_FBC_EN | CUR_FBC_HEIGHT(height - 1); + + base = intel_cursor_base(plane_state); + pos = intel_cursor_position(plane_state); + } + + /* + * On some platforms writing CURCNTR first will also + * cause CURPOS to be armed by the CURBASE write. + * Without the CURCNTR write the CURPOS write would + * arm itself. Thus we always update CURCNTR before + * CURPOS. + * + * On other platforms CURPOS always requires the + * CURBASE write to arm the update. Additonally + * a write to any of the cursor register will cancel + * an already armed cursor update. Thus leaving out + * the CURBASE write after CURPOS could lead to a + * cursor that doesn't appear to move, or even change + * shape. Thus we always write CURBASE. + * + * The other registers are armed by the CURBASE write + * except when the plane is getting enabled at which time + * the CURCNTR write arms the update. + */ + + if (DISPLAY_VER(dev_priv) >= 9) + skl_write_cursor_wm(plane, crtc_state); + + if (plane_state) + intel_psr2_program_plane_sel_fetch(plane, crtc_state, plane_state, 0); + else + intel_psr2_disable_plane_sel_fetch(plane, crtc_state); + + if (plane->cursor.base != base || + plane->cursor.size != fbc_ctl || + plane->cursor.cntl != cntl) { + if (HAS_CUR_FBC(dev_priv)) + intel_de_write_fw(dev_priv, CUR_FBC_CTL(pipe), + fbc_ctl); + intel_de_write_fw(dev_priv, CURCNTR(pipe), cntl); + intel_de_write_fw(dev_priv, CURPOS(pipe), pos); + intel_de_write_fw(dev_priv, CURBASE(pipe), base); + + plane->cursor.base = base; + plane->cursor.size = fbc_ctl; + plane->cursor.cntl = cntl; + } else { + intel_de_write_fw(dev_priv, CURPOS(pipe), pos); + intel_de_write_fw(dev_priv, CURBASE(pipe), base); + } +} + +static void i9xx_cursor_disable_arm(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state) +{ + i9xx_cursor_update_arm(plane, crtc_state, NULL); +} + +static bool i9xx_cursor_get_hw_state(struct intel_plane *plane, + enum pipe *pipe) +{ + struct drm_i915_private *dev_priv = to_i915(plane->base.dev); + enum intel_display_power_domain power_domain; + intel_wakeref_t wakeref; + bool ret; + u32 val; + + /* + * Not 100% correct for planes that can move between pipes, + * but that's only the case for gen2-3 which don't have any + * display power wells. + */ + power_domain = POWER_DOMAIN_PIPE(plane->pipe); + wakeref = intel_display_power_get_if_enabled(dev_priv, power_domain); + if (!wakeref) + return false; + + val = intel_de_read(dev_priv, CURCNTR(plane->pipe)); + + ret = val & MCURSOR_MODE_MASK; + + if (DISPLAY_VER(dev_priv) >= 5 || IS_G4X(dev_priv)) + *pipe = plane->pipe; + else + *pipe = REG_FIELD_GET(MCURSOR_PIPE_SEL_MASK, val); + + intel_display_power_put(dev_priv, power_domain, wakeref); + + return ret; +} + +static bool intel_cursor_format_mod_supported(struct drm_plane *_plane, + u32 format, u64 modifier) +{ + if (!intel_fb_plane_supports_modifier(to_intel_plane(_plane), modifier)) + return false; + + return format == DRM_FORMAT_ARGB8888; +} + +static int +intel_legacy_cursor_update(struct drm_plane *_plane, + struct drm_crtc *_crtc, + struct drm_framebuffer *fb, + int crtc_x, int crtc_y, + unsigned int crtc_w, unsigned int crtc_h, + u32 src_x, u32 src_y, + u32 src_w, u32 src_h, + struct drm_modeset_acquire_ctx *ctx) +{ + struct intel_plane *plane = to_intel_plane(_plane); + struct intel_crtc *crtc = to_intel_crtc(_crtc); + struct intel_plane_state *old_plane_state = + to_intel_plane_state(plane->base.state); + struct intel_plane_state *new_plane_state; + struct intel_crtc_state *crtc_state = + to_intel_crtc_state(crtc->base.state); + struct intel_crtc_state *new_crtc_state; + int ret; + + /* + * When crtc is inactive or there is a modeset pending, + * wait for it to complete in the slowpath. + * PSR2 selective fetch also requires the slow path as + * PSR2 plane and transcoder registers can only be updated during + * vblank. + * + * FIXME bigjoiner fastpath would be good + */ + if (!crtc_state->hw.active || intel_crtc_needs_modeset(crtc_state) || + crtc_state->update_pipe || crtc_state->bigjoiner_pipes) + goto slow; + + /* + * Don't do an async update if there is an outstanding commit modifying + * the plane. This prevents our async update's changes from getting + * overridden by a previous synchronous update's state. + */ + if (old_plane_state->uapi.commit && + !try_wait_for_completion(&old_plane_state->uapi.commit->hw_done)) + goto slow; + + /* + * If any parameters change that may affect watermarks, + * take the slowpath. Only changing fb or position should be + * in the fastpath. + */ + if (old_plane_state->uapi.crtc != &crtc->base || + old_plane_state->uapi.src_w != src_w || + old_plane_state->uapi.src_h != src_h || + old_plane_state->uapi.crtc_w != crtc_w || + old_plane_state->uapi.crtc_h != crtc_h || + !old_plane_state->uapi.fb != !fb) + goto slow; + + new_plane_state = to_intel_plane_state(intel_plane_duplicate_state(&plane->base)); + if (!new_plane_state) + return -ENOMEM; + + new_crtc_state = to_intel_crtc_state(intel_crtc_duplicate_state(&crtc->base)); + if (!new_crtc_state) { + ret = -ENOMEM; + goto out_free; + } + + drm_atomic_set_fb_for_plane(&new_plane_state->uapi, fb); + + new_plane_state->uapi.src_x = src_x; + new_plane_state->uapi.src_y = src_y; + new_plane_state->uapi.src_w = src_w; + new_plane_state->uapi.src_h = src_h; + new_plane_state->uapi.crtc_x = crtc_x; + new_plane_state->uapi.crtc_y = crtc_y; + new_plane_state->uapi.crtc_w = crtc_w; + new_plane_state->uapi.crtc_h = crtc_h; + + intel_plane_copy_uapi_to_hw_state(new_plane_state, new_plane_state, crtc); + + ret = intel_plane_atomic_check_with_state(crtc_state, new_crtc_state, + old_plane_state, new_plane_state); + if (ret) + goto out_free; + + ret = intel_plane_pin_fb(new_plane_state); + if (ret) + goto out_free; + + intel_frontbuffer_flush(to_intel_frontbuffer(new_plane_state->hw.fb), + ORIGIN_CURSOR_UPDATE); + intel_frontbuffer_track(to_intel_frontbuffer(old_plane_state->hw.fb), + to_intel_frontbuffer(new_plane_state->hw.fb), + plane->frontbuffer_bit); + + /* Swap plane state */ + plane->base.state = &new_plane_state->uapi; + + /* + * We cannot swap crtc_state as it may be in use by an atomic commit or + * page flip that's running simultaneously. If we swap crtc_state and + * destroy the old state, we will cause a use-after-free there. + * + * Only update active_planes, which is needed for our internal + * bookkeeping. Either value will do the right thing when updating + * planes atomically. If the cursor was part of the atomic update then + * we would have taken the slowpath. + */ + crtc_state->active_planes = new_crtc_state->active_planes; + + /* + * Technically we should do a vblank evasion here to make + * sure all the cursor registers update on the same frame. + * For now just make sure the register writes happen as + * quickly as possible to minimize the race window. + */ + local_irq_disable(); + + if (new_plane_state->uapi.visible) { + intel_plane_update_noarm(plane, crtc_state, new_plane_state); + intel_plane_update_arm(plane, crtc_state, new_plane_state); + } else { + intel_plane_disable_arm(plane, crtc_state); + } + + local_irq_enable(); + + intel_plane_unpin_fb(old_plane_state); + +out_free: + if (new_crtc_state) + intel_crtc_destroy_state(&crtc->base, &new_crtc_state->uapi); + if (ret) + intel_plane_destroy_state(&plane->base, &new_plane_state->uapi); + else + intel_plane_destroy_state(&plane->base, &old_plane_state->uapi); + return ret; + +slow: + return drm_atomic_helper_update_plane(&plane->base, &crtc->base, fb, + crtc_x, crtc_y, crtc_w, crtc_h, + src_x, src_y, src_w, src_h, ctx); +} + +static const struct drm_plane_funcs intel_cursor_plane_funcs = { + .update_plane = intel_legacy_cursor_update, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = intel_plane_destroy, + .atomic_duplicate_state = intel_plane_duplicate_state, + .atomic_destroy_state = intel_plane_destroy_state, + .format_mod_supported = intel_cursor_format_mod_supported, +}; + +struct intel_plane * +intel_cursor_plane_create(struct drm_i915_private *dev_priv, + enum pipe pipe) +{ + struct intel_plane *cursor; + int ret, zpos; + u64 *modifiers; + + cursor = intel_plane_alloc(); + if (IS_ERR(cursor)) + return cursor; + + cursor->pipe = pipe; + cursor->i9xx_plane = (enum i9xx_plane_id) pipe; + cursor->id = PLANE_CURSOR; + cursor->frontbuffer_bit = INTEL_FRONTBUFFER(pipe, cursor->id); + + if (IS_I845G(dev_priv) || IS_I865G(dev_priv)) { + cursor->max_stride = i845_cursor_max_stride; + cursor->update_arm = i845_cursor_update_arm; + cursor->disable_arm = i845_cursor_disable_arm; + cursor->get_hw_state = i845_cursor_get_hw_state; + cursor->check_plane = i845_check_cursor; + } else { + cursor->max_stride = i9xx_cursor_max_stride; + cursor->update_arm = i9xx_cursor_update_arm; + cursor->disable_arm = i9xx_cursor_disable_arm; + cursor->get_hw_state = i9xx_cursor_get_hw_state; + cursor->check_plane = i9xx_check_cursor; + } + + cursor->cursor.base = ~0; + cursor->cursor.cntl = ~0; + + if (IS_I845G(dev_priv) || IS_I865G(dev_priv) || HAS_CUR_FBC(dev_priv)) + cursor->cursor.size = ~0; + + modifiers = intel_fb_plane_get_modifiers(dev_priv, INTEL_PLANE_CAP_NONE); + + ret = drm_universal_plane_init(&dev_priv->drm, &cursor->base, + 0, &intel_cursor_plane_funcs, + intel_cursor_formats, + ARRAY_SIZE(intel_cursor_formats), + modifiers, + DRM_PLANE_TYPE_CURSOR, + "cursor %c", pipe_name(pipe)); + + kfree(modifiers); + + if (ret) + goto fail; + + if (DISPLAY_VER(dev_priv) >= 4) + drm_plane_create_rotation_property(&cursor->base, + DRM_MODE_ROTATE_0, + DRM_MODE_ROTATE_0 | + DRM_MODE_ROTATE_180); + + zpos = RUNTIME_INFO(dev_priv)->num_sprites[pipe] + 1; + drm_plane_create_zpos_immutable_property(&cursor->base, zpos); + + if (DISPLAY_VER(dev_priv) >= 12) + drm_plane_enable_fb_damage_clips(&cursor->base); + + intel_plane_helper_add(cursor); + + return cursor; + +fail: + intel_plane_free(cursor); + + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/i915/display/intel_cursor.h b/drivers/gpu/drm/i915/display/intel_cursor.h new file mode 100644 index 000000000..ce333bf4c --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_cursor.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2020 Intel Corporation + */ + +#ifndef _INTEL_CURSOR_H_ +#define _INTEL_CURSOR_H_ + +enum pipe; +struct drm_i915_private; +struct intel_plane; + +struct intel_plane * +intel_cursor_plane_create(struct drm_i915_private *dev_priv, + enum pipe pipe); + +#endif diff --git a/drivers/gpu/drm/i915/display/intel_ddi.c b/drivers/gpu/drm/i915/display/intel_ddi.c new file mode 100644 index 000000000..706e2d956 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_ddi.c @@ -0,0 +1,4525 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Authors: + * Eugeni Dodonov + * + */ + +#include + +#include +#include + +#include "i915_drv.h" +#include "intel_audio.h" +#include "intel_audio_regs.h" +#include "intel_backlight.h" +#include "intel_combo_phy.h" +#include "intel_combo_phy_regs.h" +#include "intel_connector.h" +#include "intel_crtc.h" +#include "intel_ddi.h" +#include "intel_ddi_buf_trans.h" +#include "intel_de.h" +#include "intel_display_power.h" +#include "intel_display_types.h" +#include "intel_dkl_phy.h" +#include "intel_dp.h" +#include "intel_dp_link_training.h" +#include "intel_dp_mst.h" +#include "intel_dpio_phy.h" +#include "intel_dsi.h" +#include "intel_fdi.h" +#include "intel_fifo_underrun.h" +#include "intel_gmbus.h" +#include "intel_hdcp.h" +#include "intel_hdmi.h" +#include "intel_hotplug.h" +#include "intel_lspcon.h" +#include "intel_pps.h" +#include "intel_psr.h" +#include "intel_quirks.h" +#include "intel_snps_phy.h" +#include "intel_sprite.h" +#include "intel_tc.h" +#include "intel_tc_phy_regs.h" +#include "intel_vdsc.h" +#include "intel_vrr.h" +#include "skl_scaler.h" +#include "skl_universal_plane.h" + +static const u8 index_to_dp_signal_levels[] = { + [0] = DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | DP_TRAIN_PRE_EMPH_LEVEL_0, + [1] = DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | DP_TRAIN_PRE_EMPH_LEVEL_1, + [2] = DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | DP_TRAIN_PRE_EMPH_LEVEL_2, + [3] = DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | DP_TRAIN_PRE_EMPH_LEVEL_3, + [4] = DP_TRAIN_VOLTAGE_SWING_LEVEL_1 | DP_TRAIN_PRE_EMPH_LEVEL_0, + [5] = DP_TRAIN_VOLTAGE_SWING_LEVEL_1 | DP_TRAIN_PRE_EMPH_LEVEL_1, + [6] = DP_TRAIN_VOLTAGE_SWING_LEVEL_1 | DP_TRAIN_PRE_EMPH_LEVEL_2, + [7] = DP_TRAIN_VOLTAGE_SWING_LEVEL_2 | DP_TRAIN_PRE_EMPH_LEVEL_0, + [8] = DP_TRAIN_VOLTAGE_SWING_LEVEL_2 | DP_TRAIN_PRE_EMPH_LEVEL_1, + [9] = DP_TRAIN_VOLTAGE_SWING_LEVEL_3 | DP_TRAIN_PRE_EMPH_LEVEL_0, +}; + +static int intel_ddi_hdmi_level(struct intel_encoder *encoder, + const struct intel_ddi_buf_trans *trans) +{ + int level; + + level = intel_bios_hdmi_level_shift(encoder); + if (level < 0) + level = trans->hdmi_default_entry; + + return level; +} + +static bool has_buf_trans_select(struct drm_i915_private *i915) +{ + return DISPLAY_VER(i915) < 10 && !IS_BROXTON(i915); +} + +static bool has_iboost(struct drm_i915_private *i915) +{ + return DISPLAY_VER(i915) == 9 && !IS_BROXTON(i915); +} + +/* + * Starting with Haswell, DDI port buffers must be programmed with correct + * values in advance. This function programs the correct values for + * DP/eDP/FDI use cases. + */ +void hsw_prepare_dp_ddi_buffers(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + u32 iboost_bit = 0; + int i, n_entries; + enum port port = encoder->port; + const struct intel_ddi_buf_trans *trans; + + trans = encoder->get_buf_trans(encoder, crtc_state, &n_entries); + if (drm_WARN_ON_ONCE(&dev_priv->drm, !trans)) + return; + + /* If we're boosting the current, set bit 31 of trans1 */ + if (has_iboost(dev_priv) && + intel_bios_encoder_dp_boost_level(encoder->devdata)) + iboost_bit = DDI_BUF_BALANCE_LEG_ENABLE; + + for (i = 0; i < n_entries; i++) { + intel_de_write(dev_priv, DDI_BUF_TRANS_LO(port, i), + trans->entries[i].hsw.trans1 | iboost_bit); + intel_de_write(dev_priv, DDI_BUF_TRANS_HI(port, i), + trans->entries[i].hsw.trans2); + } +} + +/* + * Starting with Haswell, DDI port buffers must be programmed with correct + * values in advance. This function programs the correct values for + * HDMI/DVI use cases. + */ +static void hsw_prepare_hdmi_ddi_buffers(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + int level = intel_ddi_level(encoder, crtc_state, 0); + u32 iboost_bit = 0; + int n_entries; + enum port port = encoder->port; + const struct intel_ddi_buf_trans *trans; + + trans = encoder->get_buf_trans(encoder, crtc_state, &n_entries); + if (drm_WARN_ON_ONCE(&dev_priv->drm, !trans)) + return; + + /* If we're boosting the current, set bit 31 of trans1 */ + if (has_iboost(dev_priv) && + intel_bios_encoder_hdmi_boost_level(encoder->devdata)) + iboost_bit = DDI_BUF_BALANCE_LEG_ENABLE; + + /* Entry 9 is for HDMI: */ + intel_de_write(dev_priv, DDI_BUF_TRANS_LO(port, 9), + trans->entries[level].hsw.trans1 | iboost_bit); + intel_de_write(dev_priv, DDI_BUF_TRANS_HI(port, 9), + trans->entries[level].hsw.trans2); +} + +void intel_wait_ddi_buf_idle(struct drm_i915_private *dev_priv, + enum port port) +{ + if (IS_BROXTON(dev_priv)) { + udelay(16); + return; + } + + if (wait_for_us((intel_de_read(dev_priv, DDI_BUF_CTL(port)) & + DDI_BUF_IS_IDLE), 8)) + drm_err(&dev_priv->drm, "Timeout waiting for DDI BUF %c to get idle\n", + port_name(port)); +} + +static void intel_wait_ddi_buf_active(struct drm_i915_private *dev_priv, + enum port port) +{ + int ret; + + /* Wait > 518 usecs for DDI_BUF_CTL to be non idle */ + if (DISPLAY_VER(dev_priv) < 10) { + usleep_range(518, 1000); + return; + } + + ret = _wait_for(!(intel_de_read(dev_priv, DDI_BUF_CTL(port)) & + DDI_BUF_IS_IDLE), IS_DG2(dev_priv) ? 1200 : 500, 10, 10); + + if (ret) + drm_err(&dev_priv->drm, "Timeout waiting for DDI BUF %c to get active\n", + port_name(port)); +} + +static u32 hsw_pll_to_ddi_pll_sel(const struct intel_shared_dpll *pll) +{ + switch (pll->info->id) { + case DPLL_ID_WRPLL1: + return PORT_CLK_SEL_WRPLL1; + case DPLL_ID_WRPLL2: + return PORT_CLK_SEL_WRPLL2; + case DPLL_ID_SPLL: + return PORT_CLK_SEL_SPLL; + case DPLL_ID_LCPLL_810: + return PORT_CLK_SEL_LCPLL_810; + case DPLL_ID_LCPLL_1350: + return PORT_CLK_SEL_LCPLL_1350; + case DPLL_ID_LCPLL_2700: + return PORT_CLK_SEL_LCPLL_2700; + default: + MISSING_CASE(pll->info->id); + return PORT_CLK_SEL_NONE; + } +} + +static u32 icl_pll_to_ddi_clk_sel(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + const struct intel_shared_dpll *pll = crtc_state->shared_dpll; + int clock = crtc_state->port_clock; + const enum intel_dpll_id id = pll->info->id; + + switch (id) { + default: + /* + * DPLL_ID_ICL_DPLL0 and DPLL_ID_ICL_DPLL1 should not be used + * here, so do warn if this get passed in + */ + MISSING_CASE(id); + return DDI_CLK_SEL_NONE; + case DPLL_ID_ICL_TBTPLL: + switch (clock) { + case 162000: + return DDI_CLK_SEL_TBT_162; + case 270000: + return DDI_CLK_SEL_TBT_270; + case 540000: + return DDI_CLK_SEL_TBT_540; + case 810000: + return DDI_CLK_SEL_TBT_810; + default: + MISSING_CASE(clock); + return DDI_CLK_SEL_NONE; + } + case DPLL_ID_ICL_MGPLL1: + case DPLL_ID_ICL_MGPLL2: + case DPLL_ID_ICL_MGPLL3: + case DPLL_ID_ICL_MGPLL4: + case DPLL_ID_TGL_MGPLL5: + case DPLL_ID_TGL_MGPLL6: + return DDI_CLK_SEL_MG; + } +} + +static u32 ddi_buf_phy_link_rate(int port_clock) +{ + switch (port_clock) { + case 162000: + return DDI_BUF_PHY_LINK_RATE(0); + case 216000: + return DDI_BUF_PHY_LINK_RATE(4); + case 243000: + return DDI_BUF_PHY_LINK_RATE(5); + case 270000: + return DDI_BUF_PHY_LINK_RATE(1); + case 324000: + return DDI_BUF_PHY_LINK_RATE(6); + case 432000: + return DDI_BUF_PHY_LINK_RATE(7); + case 540000: + return DDI_BUF_PHY_LINK_RATE(2); + case 810000: + return DDI_BUF_PHY_LINK_RATE(3); + default: + MISSING_CASE(port_clock); + return DDI_BUF_PHY_LINK_RATE(0); + } +} + +static void intel_ddi_init_dp_buf_reg(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + enum phy phy = intel_port_to_phy(i915, encoder->port); + + /* DDI_BUF_CTL_ENABLE will be set by intel_ddi_prepare_link_retrain() later */ + intel_dp->DP = dig_port->saved_port_bits | + DDI_PORT_WIDTH(crtc_state->lane_count) | + DDI_BUF_TRANS_SELECT(0); + + if (IS_ALDERLAKE_P(i915) && intel_phy_is_tc(i915, phy)) { + intel_dp->DP |= ddi_buf_phy_link_rate(crtc_state->port_clock); + if (!intel_tc_port_in_tbt_alt_mode(dig_port)) + intel_dp->DP |= DDI_BUF_CTL_TC_PHY_OWNERSHIP; + } +} + +static int icl_calc_tbt_pll_link(struct drm_i915_private *dev_priv, + enum port port) +{ + u32 val = intel_de_read(dev_priv, DDI_CLK_SEL(port)) & DDI_CLK_SEL_MASK; + + switch (val) { + case DDI_CLK_SEL_NONE: + return 0; + case DDI_CLK_SEL_TBT_162: + return 162000; + case DDI_CLK_SEL_TBT_270: + return 270000; + case DDI_CLK_SEL_TBT_540: + return 540000; + case DDI_CLK_SEL_TBT_810: + return 810000; + default: + MISSING_CASE(val); + return 0; + } +} + +static void ddi_dotclock_get(struct intel_crtc_state *pipe_config) +{ + /* CRT dotclock is determined via other means */ + if (pipe_config->has_pch_encoder) + return; + + pipe_config->hw.adjusted_mode.crtc_clock = + intel_crtc_dotclock(pipe_config); +} + +void intel_ddi_set_dp_msa(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + u32 temp; + + if (!intel_crtc_has_dp_encoder(crtc_state)) + return; + + drm_WARN_ON(&dev_priv->drm, transcoder_is_dsi(cpu_transcoder)); + + temp = DP_MSA_MISC_SYNC_CLOCK; + + switch (crtc_state->pipe_bpp) { + case 18: + temp |= DP_MSA_MISC_6_BPC; + break; + case 24: + temp |= DP_MSA_MISC_8_BPC; + break; + case 30: + temp |= DP_MSA_MISC_10_BPC; + break; + case 36: + temp |= DP_MSA_MISC_12_BPC; + break; + default: + MISSING_CASE(crtc_state->pipe_bpp); + break; + } + + /* nonsense combination */ + drm_WARN_ON(&dev_priv->drm, crtc_state->limited_color_range && + crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB); + + if (crtc_state->limited_color_range) + temp |= DP_MSA_MISC_COLOR_CEA_RGB; + + /* + * As per DP 1.2 spec section 2.3.4.3 while sending + * YCBCR 444 signals we should program MSA MISC1/0 fields with + * colorspace information. + */ + if (crtc_state->output_format == INTEL_OUTPUT_FORMAT_YCBCR444) + temp |= DP_MSA_MISC_COLOR_YCBCR_444_BT709; + + /* + * As per DP 1.4a spec section 2.2.4.3 [MSA Field for Indication + * of Color Encoding Format and Content Color Gamut] while sending + * YCBCR 420, HDR BT.2020 signals we should program MSA MISC1 fields + * which indicate VSC SDP for the Pixel Encoding/Colorimetry Format. + */ + if (intel_dp_needs_vsc_sdp(crtc_state, conn_state)) + temp |= DP_MSA_MISC_COLOR_VSC_SDP; + + intel_de_write(dev_priv, TRANS_MSA_MISC(cpu_transcoder), temp); +} + +static u32 bdw_trans_port_sync_master_select(enum transcoder master_transcoder) +{ + if (master_transcoder == TRANSCODER_EDP) + return 0; + else + return master_transcoder + 1; +} + +static void +intel_ddi_config_transcoder_dp2(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + u32 val = 0; + + if (intel_dp_is_uhbr(crtc_state)) + val = TRANS_DP2_128B132B_CHANNEL_CODING; + + intel_de_write(i915, TRANS_DP2_CTL(cpu_transcoder), val); +} + +/* + * Returns the TRANS_DDI_FUNC_CTL value based on CRTC state. + * + * Only intended to be used by intel_ddi_enable_transcoder_func() and + * intel_ddi_config_transcoder_func(). + */ +static u32 +intel_ddi_transcoder_func_reg_val_get(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + enum port port = encoder->port; + u32 temp; + + /* Enable TRANS_DDI_FUNC_CTL for the pipe to work in HDMI mode */ + temp = TRANS_DDI_FUNC_ENABLE; + if (DISPLAY_VER(dev_priv) >= 12) + temp |= TGL_TRANS_DDI_SELECT_PORT(port); + else + temp |= TRANS_DDI_SELECT_PORT(port); + + switch (crtc_state->pipe_bpp) { + default: + MISSING_CASE(crtc_state->pipe_bpp); + fallthrough; + case 18: + temp |= TRANS_DDI_BPC_6; + break; + case 24: + temp |= TRANS_DDI_BPC_8; + break; + case 30: + temp |= TRANS_DDI_BPC_10; + break; + case 36: + temp |= TRANS_DDI_BPC_12; + break; + } + + if (crtc_state->hw.adjusted_mode.flags & DRM_MODE_FLAG_PVSYNC) + temp |= TRANS_DDI_PVSYNC; + if (crtc_state->hw.adjusted_mode.flags & DRM_MODE_FLAG_PHSYNC) + temp |= TRANS_DDI_PHSYNC; + + if (cpu_transcoder == TRANSCODER_EDP) { + switch (pipe) { + default: + MISSING_CASE(pipe); + fallthrough; + case PIPE_A: + /* On Haswell, can only use the always-on power well for + * eDP when not using the panel fitter, and when not + * using motion blur mitigation (which we don't + * support). */ + if (crtc_state->pch_pfit.force_thru) + temp |= TRANS_DDI_EDP_INPUT_A_ONOFF; + else + temp |= TRANS_DDI_EDP_INPUT_A_ON; + break; + case PIPE_B: + temp |= TRANS_DDI_EDP_INPUT_B_ONOFF; + break; + case PIPE_C: + temp |= TRANS_DDI_EDP_INPUT_C_ONOFF; + break; + } + } + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) { + if (crtc_state->has_hdmi_sink) + temp |= TRANS_DDI_MODE_SELECT_HDMI; + else + temp |= TRANS_DDI_MODE_SELECT_DVI; + + if (crtc_state->hdmi_scrambling) + temp |= TRANS_DDI_HDMI_SCRAMBLING; + if (crtc_state->hdmi_high_tmds_clock_ratio) + temp |= TRANS_DDI_HIGH_TMDS_CHAR_RATE; + } else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_ANALOG)) { + temp |= TRANS_DDI_MODE_SELECT_FDI_OR_128B132B; + temp |= (crtc_state->fdi_lanes - 1) << 1; + } else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST)) { + if (intel_dp_is_uhbr(crtc_state)) + temp |= TRANS_DDI_MODE_SELECT_FDI_OR_128B132B; + else + temp |= TRANS_DDI_MODE_SELECT_DP_MST; + temp |= DDI_PORT_WIDTH(crtc_state->lane_count); + + if (DISPLAY_VER(dev_priv) >= 12) { + enum transcoder master; + + master = crtc_state->mst_master_transcoder; + drm_WARN_ON(&dev_priv->drm, + master == INVALID_TRANSCODER); + temp |= TRANS_DDI_MST_TRANSPORT_SELECT(master); + } + } else { + temp |= TRANS_DDI_MODE_SELECT_DP_SST; + temp |= DDI_PORT_WIDTH(crtc_state->lane_count); + } + + if (IS_DISPLAY_VER(dev_priv, 8, 10) && + crtc_state->master_transcoder != INVALID_TRANSCODER) { + u8 master_select = + bdw_trans_port_sync_master_select(crtc_state->master_transcoder); + + temp |= TRANS_DDI_PORT_SYNC_ENABLE | + TRANS_DDI_PORT_SYNC_MASTER_SELECT(master_select); + } + + return temp; +} + +void intel_ddi_enable_transcoder_func(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + + if (DISPLAY_VER(dev_priv) >= 11) { + enum transcoder master_transcoder = crtc_state->master_transcoder; + u32 ctl2 = 0; + + if (master_transcoder != INVALID_TRANSCODER) { + u8 master_select = + bdw_trans_port_sync_master_select(master_transcoder); + + ctl2 |= PORT_SYNC_MODE_ENABLE | + PORT_SYNC_MODE_MASTER_SELECT(master_select); + } + + intel_de_write(dev_priv, + TRANS_DDI_FUNC_CTL2(cpu_transcoder), ctl2); + } + + intel_de_write(dev_priv, TRANS_DDI_FUNC_CTL(cpu_transcoder), + intel_ddi_transcoder_func_reg_val_get(encoder, + crtc_state)); +} + +/* + * Same as intel_ddi_enable_transcoder_func(), but it does not set the enable + * bit. + */ +static void +intel_ddi_config_transcoder_func(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + u32 ctl; + + ctl = intel_ddi_transcoder_func_reg_val_get(encoder, crtc_state); + ctl &= ~TRANS_DDI_FUNC_ENABLE; + intel_de_write(dev_priv, TRANS_DDI_FUNC_CTL(cpu_transcoder), ctl); +} + +void intel_ddi_disable_transcoder_func(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + u32 ctl; + + if (DISPLAY_VER(dev_priv) >= 11) + intel_de_write(dev_priv, + TRANS_DDI_FUNC_CTL2(cpu_transcoder), 0); + + ctl = intel_de_read(dev_priv, TRANS_DDI_FUNC_CTL(cpu_transcoder)); + + drm_WARN_ON(crtc->base.dev, ctl & TRANS_DDI_HDCP_SIGNALLING); + + ctl &= ~TRANS_DDI_FUNC_ENABLE; + + if (IS_DISPLAY_VER(dev_priv, 8, 10)) + ctl &= ~(TRANS_DDI_PORT_SYNC_ENABLE | + TRANS_DDI_PORT_SYNC_MASTER_SELECT_MASK); + + if (DISPLAY_VER(dev_priv) >= 12) { + if (!intel_dp_mst_is_master_trans(crtc_state)) { + ctl &= ~(TGL_TRANS_DDI_PORT_MASK | + TRANS_DDI_MODE_SELECT_MASK); + } + } else { + ctl &= ~(TRANS_DDI_PORT_MASK | TRANS_DDI_MODE_SELECT_MASK); + } + + intel_de_write(dev_priv, TRANS_DDI_FUNC_CTL(cpu_transcoder), ctl); + + if (intel_has_quirk(dev_priv, QUIRK_INCREASE_DDI_DISABLED_TIME) && + intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) { + drm_dbg_kms(&dev_priv->drm, + "Quirk Increase DDI disabled time\n"); + /* Quirk time at 100ms for reliable operation */ + msleep(100); + } +} + +int intel_ddi_toggle_hdcp_bits(struct intel_encoder *intel_encoder, + enum transcoder cpu_transcoder, + bool enable, u32 hdcp_mask) +{ + struct drm_device *dev = intel_encoder->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + intel_wakeref_t wakeref; + int ret = 0; + u32 tmp; + + wakeref = intel_display_power_get_if_enabled(dev_priv, + intel_encoder->power_domain); + if (drm_WARN_ON(dev, !wakeref)) + return -ENXIO; + + tmp = intel_de_read(dev_priv, TRANS_DDI_FUNC_CTL(cpu_transcoder)); + if (enable) + tmp |= hdcp_mask; + else + tmp &= ~hdcp_mask; + intel_de_write(dev_priv, TRANS_DDI_FUNC_CTL(cpu_transcoder), tmp); + intel_display_power_put(dev_priv, intel_encoder->power_domain, wakeref); + return ret; +} + +bool intel_ddi_connector_get_hw_state(struct intel_connector *intel_connector) +{ + struct drm_device *dev = intel_connector->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + struct intel_encoder *encoder = intel_attached_encoder(intel_connector); + int type = intel_connector->base.connector_type; + enum port port = encoder->port; + enum transcoder cpu_transcoder; + intel_wakeref_t wakeref; + enum pipe pipe = 0; + u32 tmp; + bool ret; + + wakeref = intel_display_power_get_if_enabled(dev_priv, + encoder->power_domain); + if (!wakeref) + return false; + + if (!encoder->get_hw_state(encoder, &pipe)) { + ret = false; + goto out; + } + + if (HAS_TRANSCODER(dev_priv, TRANSCODER_EDP) && port == PORT_A) + cpu_transcoder = TRANSCODER_EDP; + else + cpu_transcoder = (enum transcoder) pipe; + + tmp = intel_de_read(dev_priv, TRANS_DDI_FUNC_CTL(cpu_transcoder)); + + switch (tmp & TRANS_DDI_MODE_SELECT_MASK) { + case TRANS_DDI_MODE_SELECT_HDMI: + case TRANS_DDI_MODE_SELECT_DVI: + ret = type == DRM_MODE_CONNECTOR_HDMIA; + break; + + case TRANS_DDI_MODE_SELECT_DP_SST: + ret = type == DRM_MODE_CONNECTOR_eDP || + type == DRM_MODE_CONNECTOR_DisplayPort; + break; + + case TRANS_DDI_MODE_SELECT_DP_MST: + /* if the transcoder is in MST state then + * connector isn't connected */ + ret = false; + break; + + case TRANS_DDI_MODE_SELECT_FDI_OR_128B132B: + if (HAS_DP20(dev_priv)) + /* 128b/132b */ + ret = false; + else + /* FDI */ + ret = type == DRM_MODE_CONNECTOR_VGA; + break; + + default: + ret = false; + break; + } + +out: + intel_display_power_put(dev_priv, encoder->power_domain, wakeref); + + return ret; +} + +static void intel_ddi_get_encoder_pipes(struct intel_encoder *encoder, + u8 *pipe_mask, bool *is_dp_mst) +{ + struct drm_device *dev = encoder->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + enum port port = encoder->port; + intel_wakeref_t wakeref; + enum pipe p; + u32 tmp; + u8 mst_pipe_mask; + + *pipe_mask = 0; + *is_dp_mst = false; + + wakeref = intel_display_power_get_if_enabled(dev_priv, + encoder->power_domain); + if (!wakeref) + return; + + tmp = intel_de_read(dev_priv, DDI_BUF_CTL(port)); + if (!(tmp & DDI_BUF_CTL_ENABLE)) + goto out; + + if (HAS_TRANSCODER(dev_priv, TRANSCODER_EDP) && port == PORT_A) { + tmp = intel_de_read(dev_priv, + TRANS_DDI_FUNC_CTL(TRANSCODER_EDP)); + + switch (tmp & TRANS_DDI_EDP_INPUT_MASK) { + default: + MISSING_CASE(tmp & TRANS_DDI_EDP_INPUT_MASK); + fallthrough; + case TRANS_DDI_EDP_INPUT_A_ON: + case TRANS_DDI_EDP_INPUT_A_ONOFF: + *pipe_mask = BIT(PIPE_A); + break; + case TRANS_DDI_EDP_INPUT_B_ONOFF: + *pipe_mask = BIT(PIPE_B); + break; + case TRANS_DDI_EDP_INPUT_C_ONOFF: + *pipe_mask = BIT(PIPE_C); + break; + } + + goto out; + } + + mst_pipe_mask = 0; + for_each_pipe(dev_priv, p) { + enum transcoder cpu_transcoder = (enum transcoder)p; + unsigned int port_mask, ddi_select; + intel_wakeref_t trans_wakeref; + + trans_wakeref = intel_display_power_get_if_enabled(dev_priv, + POWER_DOMAIN_TRANSCODER(cpu_transcoder)); + if (!trans_wakeref) + continue; + + if (DISPLAY_VER(dev_priv) >= 12) { + port_mask = TGL_TRANS_DDI_PORT_MASK; + ddi_select = TGL_TRANS_DDI_SELECT_PORT(port); + } else { + port_mask = TRANS_DDI_PORT_MASK; + ddi_select = TRANS_DDI_SELECT_PORT(port); + } + + tmp = intel_de_read(dev_priv, + TRANS_DDI_FUNC_CTL(cpu_transcoder)); + intel_display_power_put(dev_priv, POWER_DOMAIN_TRANSCODER(cpu_transcoder), + trans_wakeref); + + if ((tmp & port_mask) != ddi_select) + continue; + + if ((tmp & TRANS_DDI_MODE_SELECT_MASK) == TRANS_DDI_MODE_SELECT_DP_MST || + (HAS_DP20(dev_priv) && + (tmp & TRANS_DDI_MODE_SELECT_MASK) == TRANS_DDI_MODE_SELECT_FDI_OR_128B132B)) + mst_pipe_mask |= BIT(p); + + *pipe_mask |= BIT(p); + } + + if (!*pipe_mask) + drm_dbg_kms(&dev_priv->drm, + "No pipe for [ENCODER:%d:%s] found\n", + encoder->base.base.id, encoder->base.name); + + if (!mst_pipe_mask && hweight8(*pipe_mask) > 1) { + drm_dbg_kms(&dev_priv->drm, + "Multiple pipes for [ENCODER:%d:%s] (pipe_mask %02x)\n", + encoder->base.base.id, encoder->base.name, + *pipe_mask); + *pipe_mask = BIT(ffs(*pipe_mask) - 1); + } + + if (mst_pipe_mask && mst_pipe_mask != *pipe_mask) + drm_dbg_kms(&dev_priv->drm, + "Conflicting MST and non-MST state for [ENCODER:%d:%s] (pipe_mask %02x mst_pipe_mask %02x)\n", + encoder->base.base.id, encoder->base.name, + *pipe_mask, mst_pipe_mask); + else + *is_dp_mst = mst_pipe_mask; + +out: + if (*pipe_mask && (IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv))) { + tmp = intel_de_read(dev_priv, BXT_PHY_CTL(port)); + if ((tmp & (BXT_PHY_CMNLANE_POWERDOWN_ACK | + BXT_PHY_LANE_POWERDOWN_ACK | + BXT_PHY_LANE_ENABLED)) != BXT_PHY_LANE_ENABLED) + drm_err(&dev_priv->drm, + "[ENCODER:%d:%s] enabled but PHY powered down? (PHY_CTL %08x)\n", + encoder->base.base.id, encoder->base.name, tmp); + } + + intel_display_power_put(dev_priv, encoder->power_domain, wakeref); +} + +bool intel_ddi_get_hw_state(struct intel_encoder *encoder, + enum pipe *pipe) +{ + u8 pipe_mask; + bool is_mst; + + intel_ddi_get_encoder_pipes(encoder, &pipe_mask, &is_mst); + + if (is_mst || !pipe_mask) + return false; + + *pipe = ffs(pipe_mask) - 1; + + return true; +} + +static enum intel_display_power_domain +intel_ddi_main_link_aux_domain(struct intel_digital_port *dig_port) +{ + /* ICL+ HW requires corresponding AUX IOs to be powered up for PSR with + * DC states enabled at the same time, while for driver initiated AUX + * transfers we need the same AUX IOs to be powered but with DC states + * disabled. Accordingly use the AUX power domain here which leaves DC + * states enabled. + * However, for non-A AUX ports the corresponding non-EDP transcoders + * would have already enabled power well 2 and DC_OFF. This means we can + * acquire a wider POWER_DOMAIN_AUX_{B,C,D,F} reference instead of a + * specific AUX_IO reference without powering up any extra wells. + * Note that PSR is enabled only on Port A even though this function + * returns the correct domain for other ports too. + */ + return dig_port->aux_ch == AUX_CH_A ? POWER_DOMAIN_AUX_IO_A : + intel_aux_power_domain(dig_port); +} + +static void intel_ddi_get_power_domains(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_digital_port *dig_port; + enum phy phy = intel_port_to_phy(dev_priv, encoder->port); + + /* + * TODO: Add support for MST encoders. Atm, the following should never + * happen since fake-MST encoders don't set their get_power_domains() + * hook. + */ + if (drm_WARN_ON(&dev_priv->drm, + intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST))) + return; + + dig_port = enc_to_dig_port(encoder); + + if (!intel_tc_port_in_tbt_alt_mode(dig_port)) { + drm_WARN_ON(&dev_priv->drm, dig_port->ddi_io_wakeref); + dig_port->ddi_io_wakeref = intel_display_power_get(dev_priv, + dig_port->ddi_io_power_domain); + } + + /* + * AUX power is only needed for (e)DP mode, and for HDMI mode on TC + * ports. + */ + if (intel_crtc_has_dp_encoder(crtc_state) || + intel_phy_is_tc(dev_priv, phy)) { + drm_WARN_ON(&dev_priv->drm, dig_port->aux_wakeref); + dig_port->aux_wakeref = + intel_display_power_get(dev_priv, + intel_ddi_main_link_aux_domain(dig_port)); + } +} + +void intel_ddi_enable_pipe_clock(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + enum phy phy = intel_port_to_phy(dev_priv, encoder->port); + u32 val; + + if (cpu_transcoder != TRANSCODER_EDP) { + if (DISPLAY_VER(dev_priv) >= 13) + val = TGL_TRANS_CLK_SEL_PORT(phy); + else if (DISPLAY_VER(dev_priv) >= 12) + val = TGL_TRANS_CLK_SEL_PORT(encoder->port); + else + val = TRANS_CLK_SEL_PORT(encoder->port); + + intel_de_write(dev_priv, TRANS_CLK_SEL(cpu_transcoder), val); + } +} + +void intel_ddi_disable_pipe_clock(const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + + if (cpu_transcoder != TRANSCODER_EDP) { + if (DISPLAY_VER(dev_priv) >= 12) + intel_de_write(dev_priv, + TRANS_CLK_SEL(cpu_transcoder), + TGL_TRANS_CLK_SEL_DISABLED); + else + intel_de_write(dev_priv, + TRANS_CLK_SEL(cpu_transcoder), + TRANS_CLK_SEL_DISABLED); + } +} + +static void _skl_ddi_set_iboost(struct drm_i915_private *dev_priv, + enum port port, u8 iboost) +{ + u32 tmp; + + tmp = intel_de_read(dev_priv, DISPIO_CR_TX_BMU_CR0); + tmp &= ~(BALANCE_LEG_MASK(port) | BALANCE_LEG_DISABLE(port)); + if (iboost) + tmp |= iboost << BALANCE_LEG_SHIFT(port); + else + tmp |= BALANCE_LEG_DISABLE(port); + intel_de_write(dev_priv, DISPIO_CR_TX_BMU_CR0, tmp); +} + +static void skl_ddi_set_iboost(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int level) +{ + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + u8 iboost; + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + iboost = intel_bios_encoder_hdmi_boost_level(encoder->devdata); + else + iboost = intel_bios_encoder_dp_boost_level(encoder->devdata); + + if (iboost == 0) { + const struct intel_ddi_buf_trans *trans; + int n_entries; + + trans = encoder->get_buf_trans(encoder, crtc_state, &n_entries); + if (drm_WARN_ON_ONCE(&dev_priv->drm, !trans)) + return; + + iboost = trans->entries[level].hsw.i_boost; + } + + /* Make sure that the requested I_boost is valid */ + if (iboost && iboost != 0x1 && iboost != 0x3 && iboost != 0x7) { + drm_err(&dev_priv->drm, "Invalid I_boost value %u\n", iboost); + return; + } + + _skl_ddi_set_iboost(dev_priv, encoder->port, iboost); + + if (encoder->port == PORT_A && dig_port->max_lanes == 4) + _skl_ddi_set_iboost(dev_priv, PORT_E, iboost); +} + +static u8 intel_ddi_dp_voltage_max(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + int n_entries; + + encoder->get_buf_trans(encoder, crtc_state, &n_entries); + + if (drm_WARN_ON(&dev_priv->drm, n_entries < 1)) + n_entries = 1; + if (drm_WARN_ON(&dev_priv->drm, + n_entries > ARRAY_SIZE(index_to_dp_signal_levels))) + n_entries = ARRAY_SIZE(index_to_dp_signal_levels); + + return index_to_dp_signal_levels[n_entries - 1] & + DP_TRAIN_VOLTAGE_SWING_MASK; +} + +/* + * We assume that the full set of pre-emphasis values can be + * used on all DDI platforms. Should that change we need to + * rethink this code. + */ +static u8 intel_ddi_dp_preemph_max(struct intel_dp *intel_dp) +{ + return DP_TRAIN_PRE_EMPH_LEVEL_3; +} + +static u32 icl_combo_phy_loadgen_select(const struct intel_crtc_state *crtc_state, + int lane) +{ + if (crtc_state->port_clock > 600000) + return 0; + + if (crtc_state->lane_count == 4) + return lane >= 1 ? LOADGEN_SELECT : 0; + else + return lane == 1 || lane == 2 ? LOADGEN_SELECT : 0; +} + +static void icl_ddi_combo_vswing_program(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + const struct intel_ddi_buf_trans *trans; + enum phy phy = intel_port_to_phy(dev_priv, encoder->port); + int n_entries, ln; + u32 val; + + trans = encoder->get_buf_trans(encoder, crtc_state, &n_entries); + if (drm_WARN_ON_ONCE(&dev_priv->drm, !trans)) + return; + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP)) { + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + + val = EDP4K2K_MODE_OVRD_EN | EDP4K2K_MODE_OVRD_OPTIMIZED; + intel_dp->hobl_active = is_hobl_buf_trans(trans); + intel_de_rmw(dev_priv, ICL_PORT_CL_DW10(phy), val, + intel_dp->hobl_active ? val : 0); + } + + /* Set PORT_TX_DW5 */ + val = intel_de_read(dev_priv, ICL_PORT_TX_DW5_LN(0, phy)); + val &= ~(SCALING_MODE_SEL_MASK | RTERM_SELECT_MASK | + TAP2_DISABLE | TAP3_DISABLE); + val |= SCALING_MODE_SEL(0x2); + val |= RTERM_SELECT(0x6); + val |= TAP3_DISABLE; + intel_de_write(dev_priv, ICL_PORT_TX_DW5_GRP(phy), val); + + /* Program PORT_TX_DW2 */ + for (ln = 0; ln < 4; ln++) { + int level = intel_ddi_level(encoder, crtc_state, ln); + + intel_de_rmw(dev_priv, ICL_PORT_TX_DW2_LN(ln, phy), + SWING_SEL_UPPER_MASK | SWING_SEL_LOWER_MASK | RCOMP_SCALAR_MASK, + SWING_SEL_UPPER(trans->entries[level].icl.dw2_swing_sel) | + SWING_SEL_LOWER(trans->entries[level].icl.dw2_swing_sel) | + RCOMP_SCALAR(0x98)); + } + + /* Program PORT_TX_DW4 */ + /* We cannot write to GRP. It would overwrite individual loadgen. */ + for (ln = 0; ln < 4; ln++) { + int level = intel_ddi_level(encoder, crtc_state, ln); + + intel_de_rmw(dev_priv, ICL_PORT_TX_DW4_LN(ln, phy), + POST_CURSOR_1_MASK | POST_CURSOR_2_MASK | CURSOR_COEFF_MASK, + POST_CURSOR_1(trans->entries[level].icl.dw4_post_cursor_1) | + POST_CURSOR_2(trans->entries[level].icl.dw4_post_cursor_2) | + CURSOR_COEFF(trans->entries[level].icl.dw4_cursor_coeff)); + } + + /* Program PORT_TX_DW7 */ + for (ln = 0; ln < 4; ln++) { + int level = intel_ddi_level(encoder, crtc_state, ln); + + intel_de_rmw(dev_priv, ICL_PORT_TX_DW7_LN(ln, phy), + N_SCALAR_MASK, + N_SCALAR(trans->entries[level].icl.dw7_n_scalar)); + } +} + +static void icl_combo_phy_set_signal_levels(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + enum phy phy = intel_port_to_phy(dev_priv, encoder->port); + u32 val; + int ln; + + /* + * 1. If port type is eDP or DP, + * set PORT_PCS_DW1 cmnkeeper_enable to 1b, + * else clear to 0b. + */ + val = intel_de_read(dev_priv, ICL_PORT_PCS_DW1_LN(0, phy)); + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + val &= ~COMMON_KEEPER_EN; + else + val |= COMMON_KEEPER_EN; + intel_de_write(dev_priv, ICL_PORT_PCS_DW1_GRP(phy), val); + + /* 2. Program loadgen select */ + /* + * Program PORT_TX_DW4 depending on Bit rate and used lanes + * <= 6 GHz and 4 lanes (LN0=0, LN1=1, LN2=1, LN3=1) + * <= 6 GHz and 1,2 lanes (LN0=0, LN1=1, LN2=1, LN3=0) + * > 6 GHz (LN0=0, LN1=0, LN2=0, LN3=0) + */ + for (ln = 0; ln < 4; ln++) { + intel_de_rmw(dev_priv, ICL_PORT_TX_DW4_LN(ln, phy), + LOADGEN_SELECT, + icl_combo_phy_loadgen_select(crtc_state, ln)); + } + + /* 3. Set PORT_CL_DW5 SUS Clock Config to 11b */ + intel_de_rmw(dev_priv, ICL_PORT_CL_DW5(phy), + 0, SUS_CLOCK_CONFIG); + + /* 4. Clear training enable to change swing values */ + val = intel_de_read(dev_priv, ICL_PORT_TX_DW5_LN(0, phy)); + val &= ~TX_TRAINING_EN; + intel_de_write(dev_priv, ICL_PORT_TX_DW5_GRP(phy), val); + + /* 5. Program swing and de-emphasis */ + icl_ddi_combo_vswing_program(encoder, crtc_state); + + /* 6. Set training enable to trigger update */ + val = intel_de_read(dev_priv, ICL_PORT_TX_DW5_LN(0, phy)); + val |= TX_TRAINING_EN; + intel_de_write(dev_priv, ICL_PORT_TX_DW5_GRP(phy), val); +} + +static void icl_mg_phy_set_signal_levels(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + enum tc_port tc_port = intel_port_to_tc(dev_priv, encoder->port); + const struct intel_ddi_buf_trans *trans; + int n_entries, ln; + + if (intel_tc_port_in_tbt_alt_mode(enc_to_dig_port(encoder))) + return; + + trans = encoder->get_buf_trans(encoder, crtc_state, &n_entries); + if (drm_WARN_ON_ONCE(&dev_priv->drm, !trans)) + return; + + for (ln = 0; ln < 2; ln++) { + intel_de_rmw(dev_priv, MG_TX1_LINK_PARAMS(ln, tc_port), + CRI_USE_FS32, 0); + intel_de_rmw(dev_priv, MG_TX2_LINK_PARAMS(ln, tc_port), + CRI_USE_FS32, 0); + } + + /* Program MG_TX_SWINGCTRL with values from vswing table */ + for (ln = 0; ln < 2; ln++) { + int level; + + level = intel_ddi_level(encoder, crtc_state, 2*ln+0); + + intel_de_rmw(dev_priv, MG_TX1_SWINGCTRL(ln, tc_port), + CRI_TXDEEMPH_OVERRIDE_17_12_MASK, + CRI_TXDEEMPH_OVERRIDE_17_12(trans->entries[level].mg.cri_txdeemph_override_17_12)); + + level = intel_ddi_level(encoder, crtc_state, 2*ln+1); + + intel_de_rmw(dev_priv, MG_TX2_SWINGCTRL(ln, tc_port), + CRI_TXDEEMPH_OVERRIDE_17_12_MASK, + CRI_TXDEEMPH_OVERRIDE_17_12(trans->entries[level].mg.cri_txdeemph_override_17_12)); + } + + /* Program MG_TX_DRVCTRL with values from vswing table */ + for (ln = 0; ln < 2; ln++) { + int level; + + level = intel_ddi_level(encoder, crtc_state, 2*ln+0); + + intel_de_rmw(dev_priv, MG_TX1_DRVCTRL(ln, tc_port), + CRI_TXDEEMPH_OVERRIDE_11_6_MASK | + CRI_TXDEEMPH_OVERRIDE_5_0_MASK, + CRI_TXDEEMPH_OVERRIDE_11_6(trans->entries[level].mg.cri_txdeemph_override_11_6) | + CRI_TXDEEMPH_OVERRIDE_5_0(trans->entries[level].mg.cri_txdeemph_override_5_0) | + CRI_TXDEEMPH_OVERRIDE_EN); + + level = intel_ddi_level(encoder, crtc_state, 2*ln+1); + + intel_de_rmw(dev_priv, MG_TX2_DRVCTRL(ln, tc_port), + CRI_TXDEEMPH_OVERRIDE_11_6_MASK | + CRI_TXDEEMPH_OVERRIDE_5_0_MASK, + CRI_TXDEEMPH_OVERRIDE_11_6(trans->entries[level].mg.cri_txdeemph_override_11_6) | + CRI_TXDEEMPH_OVERRIDE_5_0(trans->entries[level].mg.cri_txdeemph_override_5_0) | + CRI_TXDEEMPH_OVERRIDE_EN); + + /* FIXME: Program CRI_LOADGEN_SEL after the spec is updated */ + } + + /* + * Program MG_CLKHUB with value from frequency table + * In case of Legacy mode on MG PHY, both TX1 and TX2 enabled so use the + * values from table for which TX1 and TX2 enabled. + */ + for (ln = 0; ln < 2; ln++) { + intel_de_rmw(dev_priv, MG_CLKHUB(ln, tc_port), + CFG_LOW_RATE_LKREN_EN, + crtc_state->port_clock < 300000 ? CFG_LOW_RATE_LKREN_EN : 0); + } + + /* Program the MG_TX_DCC based on the link frequency */ + for (ln = 0; ln < 2; ln++) { + intel_de_rmw(dev_priv, MG_TX1_DCC(ln, tc_port), + CFG_AMI_CK_DIV_OVERRIDE_VAL_MASK | + CFG_AMI_CK_DIV_OVERRIDE_EN, + crtc_state->port_clock > 500000 ? + CFG_AMI_CK_DIV_OVERRIDE_VAL(1) | + CFG_AMI_CK_DIV_OVERRIDE_EN : 0); + + intel_de_rmw(dev_priv, MG_TX2_DCC(ln, tc_port), + CFG_AMI_CK_DIV_OVERRIDE_VAL_MASK | + CFG_AMI_CK_DIV_OVERRIDE_EN, + crtc_state->port_clock > 500000 ? + CFG_AMI_CK_DIV_OVERRIDE_VAL(1) | + CFG_AMI_CK_DIV_OVERRIDE_EN : 0); + } + + /* Program MG_TX_PISO_READLOAD with values from vswing table */ + for (ln = 0; ln < 2; ln++) { + intel_de_rmw(dev_priv, MG_TX1_PISO_READLOAD(ln, tc_port), + 0, CRI_CALCINIT); + intel_de_rmw(dev_priv, MG_TX2_PISO_READLOAD(ln, tc_port), + 0, CRI_CALCINIT); + } +} + +static void tgl_dkl_phy_set_signal_levels(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + enum tc_port tc_port = intel_port_to_tc(dev_priv, encoder->port); + const struct intel_ddi_buf_trans *trans; + int n_entries, ln; + + if (intel_tc_port_in_tbt_alt_mode(enc_to_dig_port(encoder))) + return; + + trans = encoder->get_buf_trans(encoder, crtc_state, &n_entries); + if (drm_WARN_ON_ONCE(&dev_priv->drm, !trans)) + return; + + for (ln = 0; ln < 2; ln++) { + int level; + + intel_dkl_phy_write(dev_priv, DKL_TX_PMD_LANE_SUS(tc_port), ln, 0); + + level = intel_ddi_level(encoder, crtc_state, 2*ln+0); + + intel_dkl_phy_rmw(dev_priv, DKL_TX_DPCNTL0(tc_port), ln, + DKL_TX_PRESHOOT_COEFF_MASK | + DKL_TX_DE_EMPAHSIS_COEFF_MASK | + DKL_TX_VSWING_CONTROL_MASK, + DKL_TX_PRESHOOT_COEFF(trans->entries[level].dkl.preshoot) | + DKL_TX_DE_EMPHASIS_COEFF(trans->entries[level].dkl.de_emphasis) | + DKL_TX_VSWING_CONTROL(trans->entries[level].dkl.vswing)); + + level = intel_ddi_level(encoder, crtc_state, 2*ln+1); + + intel_dkl_phy_rmw(dev_priv, DKL_TX_DPCNTL1(tc_port), ln, + DKL_TX_PRESHOOT_COEFF_MASK | + DKL_TX_DE_EMPAHSIS_COEFF_MASK | + DKL_TX_VSWING_CONTROL_MASK, + DKL_TX_PRESHOOT_COEFF(trans->entries[level].dkl.preshoot) | + DKL_TX_DE_EMPHASIS_COEFF(trans->entries[level].dkl.de_emphasis) | + DKL_TX_VSWING_CONTROL(trans->entries[level].dkl.vswing)); + + intel_dkl_phy_rmw(dev_priv, DKL_TX_DPCNTL2(tc_port), ln, + DKL_TX_DP20BITMODE, 0); + + if (IS_ALDERLAKE_P(dev_priv)) { + u32 val; + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) { + if (ln == 0) { + val = DKL_TX_DPCNTL2_CFG_LOADGENSELECT_TX1(0); + val |= DKL_TX_DPCNTL2_CFG_LOADGENSELECT_TX2(2); + } else { + val = DKL_TX_DPCNTL2_CFG_LOADGENSELECT_TX1(3); + val |= DKL_TX_DPCNTL2_CFG_LOADGENSELECT_TX2(3); + } + } else { + val = DKL_TX_DPCNTL2_CFG_LOADGENSELECT_TX1(0); + val |= DKL_TX_DPCNTL2_CFG_LOADGENSELECT_TX2(0); + } + + intel_dkl_phy_rmw(dev_priv, DKL_TX_DPCNTL2(tc_port), ln, + DKL_TX_DPCNTL2_CFG_LOADGENSELECT_TX1_MASK | + DKL_TX_DPCNTL2_CFG_LOADGENSELECT_TX2_MASK, + val); + } + } +} + +static int translate_signal_level(struct intel_dp *intel_dp, + u8 signal_levels) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + int i; + + for (i = 0; i < ARRAY_SIZE(index_to_dp_signal_levels); i++) { + if (index_to_dp_signal_levels[i] == signal_levels) + return i; + } + + drm_WARN(&i915->drm, 1, + "Unsupported voltage swing/pre-emphasis level: 0x%x\n", + signal_levels); + + return 0; +} + +static int intel_ddi_dp_level(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + int lane) +{ + u8 train_set = intel_dp->train_set[lane]; + + if (intel_dp_is_uhbr(crtc_state)) { + return train_set & DP_TX_FFE_PRESET_VALUE_MASK; + } else { + u8 signal_levels = train_set & (DP_TRAIN_VOLTAGE_SWING_MASK | + DP_TRAIN_PRE_EMPHASIS_MASK); + + return translate_signal_level(intel_dp, signal_levels); + } +} + +int intel_ddi_level(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int lane) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + const struct intel_ddi_buf_trans *trans; + int level, n_entries; + + trans = encoder->get_buf_trans(encoder, crtc_state, &n_entries); + if (drm_WARN_ON_ONCE(&i915->drm, !trans)) + return 0; + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + level = intel_ddi_hdmi_level(encoder, trans); + else + level = intel_ddi_dp_level(enc_to_intel_dp(encoder), crtc_state, + lane); + + if (drm_WARN_ON_ONCE(&i915->drm, level >= n_entries)) + level = n_entries - 1; + + return level; +} + +static void +hsw_set_signal_levels(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + int level = intel_ddi_level(encoder, crtc_state, 0); + enum port port = encoder->port; + u32 signal_levels; + + if (has_iboost(dev_priv)) + skl_ddi_set_iboost(encoder, crtc_state, level); + + /* HDMI ignores the rest */ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return; + + signal_levels = DDI_BUF_TRANS_SELECT(level); + + drm_dbg_kms(&dev_priv->drm, "Using signal levels %08x\n", + signal_levels); + + intel_dp->DP &= ~DDI_BUF_EMP_MASK; + intel_dp->DP |= signal_levels; + + intel_de_write(dev_priv, DDI_BUF_CTL(port), intel_dp->DP); + intel_de_posting_read(dev_priv, DDI_BUF_CTL(port)); +} + +static void _icl_ddi_enable_clock(struct drm_i915_private *i915, i915_reg_t reg, + u32 clk_sel_mask, u32 clk_sel, u32 clk_off) +{ + mutex_lock(&i915->display.dpll.lock); + + intel_de_rmw(i915, reg, clk_sel_mask, clk_sel); + + /* + * "This step and the step before must be + * done with separate register writes." + */ + intel_de_rmw(i915, reg, clk_off, 0); + + mutex_unlock(&i915->display.dpll.lock); +} + +static void _icl_ddi_disable_clock(struct drm_i915_private *i915, i915_reg_t reg, + u32 clk_off) +{ + mutex_lock(&i915->display.dpll.lock); + + intel_de_rmw(i915, reg, 0, clk_off); + + mutex_unlock(&i915->display.dpll.lock); +} + +static bool _icl_ddi_is_clock_enabled(struct drm_i915_private *i915, i915_reg_t reg, + u32 clk_off) +{ + return !(intel_de_read(i915, reg) & clk_off); +} + +static struct intel_shared_dpll * +_icl_ddi_get_pll(struct drm_i915_private *i915, i915_reg_t reg, + u32 clk_sel_mask, u32 clk_sel_shift) +{ + enum intel_dpll_id id; + + id = (intel_de_read(i915, reg) & clk_sel_mask) >> clk_sel_shift; + + return intel_get_shared_dpll_by_id(i915, id); +} + +static void adls_ddi_enable_clock(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + const struct intel_shared_dpll *pll = crtc_state->shared_dpll; + enum phy phy = intel_port_to_phy(i915, encoder->port); + + if (drm_WARN_ON(&i915->drm, !pll)) + return; + + _icl_ddi_enable_clock(i915, ADLS_DPCLKA_CFGCR(phy), + ADLS_DPCLKA_CFGCR_DDI_CLK_SEL_MASK(phy), + pll->info->id << ADLS_DPCLKA_CFGCR_DDI_SHIFT(phy), + ICL_DPCLKA_CFGCR0_DDI_CLK_OFF(phy)); +} + +static void adls_ddi_disable_clock(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum phy phy = intel_port_to_phy(i915, encoder->port); + + _icl_ddi_disable_clock(i915, ADLS_DPCLKA_CFGCR(phy), + ICL_DPCLKA_CFGCR0_DDI_CLK_OFF(phy)); +} + +static bool adls_ddi_is_clock_enabled(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum phy phy = intel_port_to_phy(i915, encoder->port); + + return _icl_ddi_is_clock_enabled(i915, ADLS_DPCLKA_CFGCR(phy), + ICL_DPCLKA_CFGCR0_DDI_CLK_OFF(phy)); +} + +static struct intel_shared_dpll *adls_ddi_get_pll(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum phy phy = intel_port_to_phy(i915, encoder->port); + + return _icl_ddi_get_pll(i915, ADLS_DPCLKA_CFGCR(phy), + ADLS_DPCLKA_CFGCR_DDI_CLK_SEL_MASK(phy), + ADLS_DPCLKA_CFGCR_DDI_SHIFT(phy)); +} + +static void rkl_ddi_enable_clock(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + const struct intel_shared_dpll *pll = crtc_state->shared_dpll; + enum phy phy = intel_port_to_phy(i915, encoder->port); + + if (drm_WARN_ON(&i915->drm, !pll)) + return; + + _icl_ddi_enable_clock(i915, ICL_DPCLKA_CFGCR0, + RKL_DPCLKA_CFGCR0_DDI_CLK_SEL_MASK(phy), + RKL_DPCLKA_CFGCR0_DDI_CLK_SEL(pll->info->id, phy), + RKL_DPCLKA_CFGCR0_DDI_CLK_OFF(phy)); +} + +static void rkl_ddi_disable_clock(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum phy phy = intel_port_to_phy(i915, encoder->port); + + _icl_ddi_disable_clock(i915, ICL_DPCLKA_CFGCR0, + RKL_DPCLKA_CFGCR0_DDI_CLK_OFF(phy)); +} + +static bool rkl_ddi_is_clock_enabled(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum phy phy = intel_port_to_phy(i915, encoder->port); + + return _icl_ddi_is_clock_enabled(i915, ICL_DPCLKA_CFGCR0, + RKL_DPCLKA_CFGCR0_DDI_CLK_OFF(phy)); +} + +static struct intel_shared_dpll *rkl_ddi_get_pll(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum phy phy = intel_port_to_phy(i915, encoder->port); + + return _icl_ddi_get_pll(i915, ICL_DPCLKA_CFGCR0, + RKL_DPCLKA_CFGCR0_DDI_CLK_SEL_MASK(phy), + RKL_DPCLKA_CFGCR0_DDI_CLK_SEL_SHIFT(phy)); +} + +static void dg1_ddi_enable_clock(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + const struct intel_shared_dpll *pll = crtc_state->shared_dpll; + enum phy phy = intel_port_to_phy(i915, encoder->port); + + if (drm_WARN_ON(&i915->drm, !pll)) + return; + + /* + * If we fail this, something went very wrong: first 2 PLLs should be + * used by first 2 phys and last 2 PLLs by last phys + */ + if (drm_WARN_ON(&i915->drm, + (pll->info->id < DPLL_ID_DG1_DPLL2 && phy >= PHY_C) || + (pll->info->id >= DPLL_ID_DG1_DPLL2 && phy < PHY_C))) + return; + + _icl_ddi_enable_clock(i915, DG1_DPCLKA_CFGCR0(phy), + DG1_DPCLKA_CFGCR0_DDI_CLK_SEL_MASK(phy), + DG1_DPCLKA_CFGCR0_DDI_CLK_SEL(pll->info->id, phy), + DG1_DPCLKA_CFGCR0_DDI_CLK_OFF(phy)); +} + +static void dg1_ddi_disable_clock(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum phy phy = intel_port_to_phy(i915, encoder->port); + + _icl_ddi_disable_clock(i915, DG1_DPCLKA_CFGCR0(phy), + DG1_DPCLKA_CFGCR0_DDI_CLK_OFF(phy)); +} + +static bool dg1_ddi_is_clock_enabled(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum phy phy = intel_port_to_phy(i915, encoder->port); + + return _icl_ddi_is_clock_enabled(i915, DG1_DPCLKA_CFGCR0(phy), + DG1_DPCLKA_CFGCR0_DDI_CLK_OFF(phy)); +} + +static struct intel_shared_dpll *dg1_ddi_get_pll(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum phy phy = intel_port_to_phy(i915, encoder->port); + enum intel_dpll_id id; + u32 val; + + val = intel_de_read(i915, DG1_DPCLKA_CFGCR0(phy)); + val &= DG1_DPCLKA_CFGCR0_DDI_CLK_SEL_MASK(phy); + val >>= DG1_DPCLKA_CFGCR0_DDI_CLK_SEL_SHIFT(phy); + id = val; + + /* + * _DG1_DPCLKA0_CFGCR0 maps between DPLL 0 and 1 with one bit for phy A + * and B while _DG1_DPCLKA1_CFGCR0 maps between DPLL 2 and 3 with one + * bit for phy C and D. + */ + if (phy >= PHY_C) + id += DPLL_ID_DG1_DPLL2; + + return intel_get_shared_dpll_by_id(i915, id); +} + +static void icl_ddi_combo_enable_clock(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + const struct intel_shared_dpll *pll = crtc_state->shared_dpll; + enum phy phy = intel_port_to_phy(i915, encoder->port); + + if (drm_WARN_ON(&i915->drm, !pll)) + return; + + _icl_ddi_enable_clock(i915, ICL_DPCLKA_CFGCR0, + ICL_DPCLKA_CFGCR0_DDI_CLK_SEL_MASK(phy), + ICL_DPCLKA_CFGCR0_DDI_CLK_SEL(pll->info->id, phy), + ICL_DPCLKA_CFGCR0_DDI_CLK_OFF(phy)); +} + +static void icl_ddi_combo_disable_clock(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum phy phy = intel_port_to_phy(i915, encoder->port); + + _icl_ddi_disable_clock(i915, ICL_DPCLKA_CFGCR0, + ICL_DPCLKA_CFGCR0_DDI_CLK_OFF(phy)); +} + +static bool icl_ddi_combo_is_clock_enabled(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum phy phy = intel_port_to_phy(i915, encoder->port); + + return _icl_ddi_is_clock_enabled(i915, ICL_DPCLKA_CFGCR0, + ICL_DPCLKA_CFGCR0_DDI_CLK_OFF(phy)); +} + +struct intel_shared_dpll *icl_ddi_combo_get_pll(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum phy phy = intel_port_to_phy(i915, encoder->port); + + return _icl_ddi_get_pll(i915, ICL_DPCLKA_CFGCR0, + ICL_DPCLKA_CFGCR0_DDI_CLK_SEL_MASK(phy), + ICL_DPCLKA_CFGCR0_DDI_CLK_SEL_SHIFT(phy)); +} + +static void jsl_ddi_tc_enable_clock(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + const struct intel_shared_dpll *pll = crtc_state->shared_dpll; + enum port port = encoder->port; + + if (drm_WARN_ON(&i915->drm, !pll)) + return; + + /* + * "For DDIC and DDID, program DDI_CLK_SEL to map the MG clock to the port. + * MG does not exist, but the programming is required to ungate DDIC and DDID." + */ + intel_de_write(i915, DDI_CLK_SEL(port), DDI_CLK_SEL_MG); + + icl_ddi_combo_enable_clock(encoder, crtc_state); +} + +static void jsl_ddi_tc_disable_clock(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum port port = encoder->port; + + icl_ddi_combo_disable_clock(encoder); + + intel_de_write(i915, DDI_CLK_SEL(port), DDI_CLK_SEL_NONE); +} + +static bool jsl_ddi_tc_is_clock_enabled(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum port port = encoder->port; + u32 tmp; + + tmp = intel_de_read(i915, DDI_CLK_SEL(port)); + + if ((tmp & DDI_CLK_SEL_MASK) == DDI_CLK_SEL_NONE) + return false; + + return icl_ddi_combo_is_clock_enabled(encoder); +} + +static void icl_ddi_tc_enable_clock(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + const struct intel_shared_dpll *pll = crtc_state->shared_dpll; + enum tc_port tc_port = intel_port_to_tc(i915, encoder->port); + enum port port = encoder->port; + + if (drm_WARN_ON(&i915->drm, !pll)) + return; + + intel_de_write(i915, DDI_CLK_SEL(port), + icl_pll_to_ddi_clk_sel(encoder, crtc_state)); + + mutex_lock(&i915->display.dpll.lock); + + intel_de_rmw(i915, ICL_DPCLKA_CFGCR0, + ICL_DPCLKA_CFGCR0_TC_CLK_OFF(tc_port), 0); + + mutex_unlock(&i915->display.dpll.lock); +} + +static void icl_ddi_tc_disable_clock(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum tc_port tc_port = intel_port_to_tc(i915, encoder->port); + enum port port = encoder->port; + + mutex_lock(&i915->display.dpll.lock); + + intel_de_rmw(i915, ICL_DPCLKA_CFGCR0, + 0, ICL_DPCLKA_CFGCR0_TC_CLK_OFF(tc_port)); + + mutex_unlock(&i915->display.dpll.lock); + + intel_de_write(i915, DDI_CLK_SEL(port), DDI_CLK_SEL_NONE); +} + +static bool icl_ddi_tc_is_clock_enabled(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum tc_port tc_port = intel_port_to_tc(i915, encoder->port); + enum port port = encoder->port; + u32 tmp; + + tmp = intel_de_read(i915, DDI_CLK_SEL(port)); + + if ((tmp & DDI_CLK_SEL_MASK) == DDI_CLK_SEL_NONE) + return false; + + tmp = intel_de_read(i915, ICL_DPCLKA_CFGCR0); + + return !(tmp & ICL_DPCLKA_CFGCR0_TC_CLK_OFF(tc_port)); +} + +static struct intel_shared_dpll *icl_ddi_tc_get_pll(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum tc_port tc_port = intel_port_to_tc(i915, encoder->port); + enum port port = encoder->port; + enum intel_dpll_id id; + u32 tmp; + + tmp = intel_de_read(i915, DDI_CLK_SEL(port)); + + switch (tmp & DDI_CLK_SEL_MASK) { + case DDI_CLK_SEL_TBT_162: + case DDI_CLK_SEL_TBT_270: + case DDI_CLK_SEL_TBT_540: + case DDI_CLK_SEL_TBT_810: + id = DPLL_ID_ICL_TBTPLL; + break; + case DDI_CLK_SEL_MG: + id = icl_tc_port_to_pll_id(tc_port); + break; + default: + MISSING_CASE(tmp); + fallthrough; + case DDI_CLK_SEL_NONE: + return NULL; + } + + return intel_get_shared_dpll_by_id(i915, id); +} + +static struct intel_shared_dpll *bxt_ddi_get_pll(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum intel_dpll_id id; + + switch (encoder->port) { + case PORT_A: + id = DPLL_ID_SKL_DPLL0; + break; + case PORT_B: + id = DPLL_ID_SKL_DPLL1; + break; + case PORT_C: + id = DPLL_ID_SKL_DPLL2; + break; + default: + MISSING_CASE(encoder->port); + return NULL; + } + + return intel_get_shared_dpll_by_id(i915, id); +} + +static void skl_ddi_enable_clock(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + const struct intel_shared_dpll *pll = crtc_state->shared_dpll; + enum port port = encoder->port; + + if (drm_WARN_ON(&i915->drm, !pll)) + return; + + mutex_lock(&i915->display.dpll.lock); + + intel_de_rmw(i915, DPLL_CTRL2, + DPLL_CTRL2_DDI_CLK_OFF(port) | + DPLL_CTRL2_DDI_CLK_SEL_MASK(port), + DPLL_CTRL2_DDI_CLK_SEL(pll->info->id, port) | + DPLL_CTRL2_DDI_SEL_OVERRIDE(port)); + + mutex_unlock(&i915->display.dpll.lock); +} + +static void skl_ddi_disable_clock(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum port port = encoder->port; + + mutex_lock(&i915->display.dpll.lock); + + intel_de_rmw(i915, DPLL_CTRL2, + 0, DPLL_CTRL2_DDI_CLK_OFF(port)); + + mutex_unlock(&i915->display.dpll.lock); +} + +static bool skl_ddi_is_clock_enabled(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum port port = encoder->port; + + /* + * FIXME Not sure if the override affects both + * the PLL selection and the CLK_OFF bit. + */ + return !(intel_de_read(i915, DPLL_CTRL2) & DPLL_CTRL2_DDI_CLK_OFF(port)); +} + +static struct intel_shared_dpll *skl_ddi_get_pll(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum port port = encoder->port; + enum intel_dpll_id id; + u32 tmp; + + tmp = intel_de_read(i915, DPLL_CTRL2); + + /* + * FIXME Not sure if the override affects both + * the PLL selection and the CLK_OFF bit. + */ + if ((tmp & DPLL_CTRL2_DDI_SEL_OVERRIDE(port)) == 0) + return NULL; + + id = (tmp & DPLL_CTRL2_DDI_CLK_SEL_MASK(port)) >> + DPLL_CTRL2_DDI_CLK_SEL_SHIFT(port); + + return intel_get_shared_dpll_by_id(i915, id); +} + +void hsw_ddi_enable_clock(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + const struct intel_shared_dpll *pll = crtc_state->shared_dpll; + enum port port = encoder->port; + + if (drm_WARN_ON(&i915->drm, !pll)) + return; + + intel_de_write(i915, PORT_CLK_SEL(port), hsw_pll_to_ddi_pll_sel(pll)); +} + +void hsw_ddi_disable_clock(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum port port = encoder->port; + + intel_de_write(i915, PORT_CLK_SEL(port), PORT_CLK_SEL_NONE); +} + +bool hsw_ddi_is_clock_enabled(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum port port = encoder->port; + + return intel_de_read(i915, PORT_CLK_SEL(port)) != PORT_CLK_SEL_NONE; +} + +static struct intel_shared_dpll *hsw_ddi_get_pll(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum port port = encoder->port; + enum intel_dpll_id id; + u32 tmp; + + tmp = intel_de_read(i915, PORT_CLK_SEL(port)); + + switch (tmp & PORT_CLK_SEL_MASK) { + case PORT_CLK_SEL_WRPLL1: + id = DPLL_ID_WRPLL1; + break; + case PORT_CLK_SEL_WRPLL2: + id = DPLL_ID_WRPLL2; + break; + case PORT_CLK_SEL_SPLL: + id = DPLL_ID_SPLL; + break; + case PORT_CLK_SEL_LCPLL_810: + id = DPLL_ID_LCPLL_810; + break; + case PORT_CLK_SEL_LCPLL_1350: + id = DPLL_ID_LCPLL_1350; + break; + case PORT_CLK_SEL_LCPLL_2700: + id = DPLL_ID_LCPLL_2700; + break; + default: + MISSING_CASE(tmp); + fallthrough; + case PORT_CLK_SEL_NONE: + return NULL; + } + + return intel_get_shared_dpll_by_id(i915, id); +} + +void intel_ddi_enable_clock(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + if (encoder->enable_clock) + encoder->enable_clock(encoder, crtc_state); +} + +void intel_ddi_disable_clock(struct intel_encoder *encoder) +{ + if (encoder->disable_clock) + encoder->disable_clock(encoder); +} + +void intel_ddi_sanitize_encoder_pll_mapping(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + u32 port_mask; + bool ddi_clk_needed; + + /* + * In case of DP MST, we sanitize the primary encoder only, not the + * virtual ones. + */ + if (encoder->type == INTEL_OUTPUT_DP_MST) + return; + + if (!encoder->base.crtc && intel_encoder_is_dp(encoder)) { + u8 pipe_mask; + bool is_mst; + + intel_ddi_get_encoder_pipes(encoder, &pipe_mask, &is_mst); + /* + * In the unlikely case that BIOS enables DP in MST mode, just + * warn since our MST HW readout is incomplete. + */ + if (drm_WARN_ON(&i915->drm, is_mst)) + return; + } + + port_mask = BIT(encoder->port); + ddi_clk_needed = encoder->base.crtc; + + if (encoder->type == INTEL_OUTPUT_DSI) { + struct intel_encoder *other_encoder; + + port_mask = intel_dsi_encoder_ports(encoder); + /* + * Sanity check that we haven't incorrectly registered another + * encoder using any of the ports of this DSI encoder. + */ + for_each_intel_encoder(&i915->drm, other_encoder) { + if (other_encoder == encoder) + continue; + + if (drm_WARN_ON(&i915->drm, + port_mask & BIT(other_encoder->port))) + return; + } + /* + * For DSI we keep the ddi clocks gated + * except during enable/disable sequence. + */ + ddi_clk_needed = false; + } + + if (ddi_clk_needed || !encoder->is_clock_enabled || + !encoder->is_clock_enabled(encoder)) + return; + + drm_notice(&i915->drm, + "[ENCODER:%d:%s] is disabled/in DSI mode with an ungated DDI clock, gate it\n", + encoder->base.base.id, encoder->base.name); + + encoder->disable_clock(encoder); +} + +static void +icl_program_mg_dp_mode(struct intel_digital_port *dig_port, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev); + enum tc_port tc_port = intel_port_to_tc(dev_priv, dig_port->base.port); + enum phy phy = intel_port_to_phy(dev_priv, dig_port->base.port); + u32 ln0, ln1, pin_assignment; + u8 width; + + if (!intel_phy_is_tc(dev_priv, phy) || + intel_tc_port_in_tbt_alt_mode(dig_port)) + return; + + if (DISPLAY_VER(dev_priv) >= 12) { + ln0 = intel_dkl_phy_read(dev_priv, DKL_DP_MODE(tc_port), 0); + ln1 = intel_dkl_phy_read(dev_priv, DKL_DP_MODE(tc_port), 1); + } else { + ln0 = intel_de_read(dev_priv, MG_DP_MODE(0, tc_port)); + ln1 = intel_de_read(dev_priv, MG_DP_MODE(1, tc_port)); + } + + ln0 &= ~(MG_DP_MODE_CFG_DP_X1_MODE | MG_DP_MODE_CFG_DP_X2_MODE); + ln1 &= ~(MG_DP_MODE_CFG_DP_X1_MODE | MG_DP_MODE_CFG_DP_X2_MODE); + + /* DPPATC */ + pin_assignment = intel_tc_port_get_pin_assignment_mask(dig_port); + width = crtc_state->lane_count; + + switch (pin_assignment) { + case 0x0: + drm_WARN_ON(&dev_priv->drm, + !intel_tc_port_in_legacy_mode(dig_port)); + if (width == 1) { + ln1 |= MG_DP_MODE_CFG_DP_X1_MODE; + } else { + ln0 |= MG_DP_MODE_CFG_DP_X2_MODE; + ln1 |= MG_DP_MODE_CFG_DP_X2_MODE; + } + break; + case 0x1: + if (width == 4) { + ln0 |= MG_DP_MODE_CFG_DP_X2_MODE; + ln1 |= MG_DP_MODE_CFG_DP_X2_MODE; + } + break; + case 0x2: + if (width == 2) { + ln0 |= MG_DP_MODE_CFG_DP_X2_MODE; + ln1 |= MG_DP_MODE_CFG_DP_X2_MODE; + } + break; + case 0x3: + case 0x5: + if (width == 1) { + ln0 |= MG_DP_MODE_CFG_DP_X1_MODE; + ln1 |= MG_DP_MODE_CFG_DP_X1_MODE; + } else { + ln0 |= MG_DP_MODE_CFG_DP_X2_MODE; + ln1 |= MG_DP_MODE_CFG_DP_X2_MODE; + } + break; + case 0x4: + case 0x6: + if (width == 1) { + ln0 |= MG_DP_MODE_CFG_DP_X1_MODE; + ln1 |= MG_DP_MODE_CFG_DP_X1_MODE; + } else { + ln0 |= MG_DP_MODE_CFG_DP_X2_MODE; + ln1 |= MG_DP_MODE_CFG_DP_X2_MODE; + } + break; + default: + MISSING_CASE(pin_assignment); + } + + if (DISPLAY_VER(dev_priv) >= 12) { + intel_dkl_phy_write(dev_priv, DKL_DP_MODE(tc_port), 0, ln0); + intel_dkl_phy_write(dev_priv, DKL_DP_MODE(tc_port), 1, ln1); + } else { + intel_de_write(dev_priv, MG_DP_MODE(0, tc_port), ln0); + intel_de_write(dev_priv, MG_DP_MODE(1, tc_port), ln1); + } +} + +static enum transcoder +tgl_dp_tp_transcoder(const struct intel_crtc_state *crtc_state) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST)) + return crtc_state->mst_master_transcoder; + else + return crtc_state->cpu_transcoder; +} + +i915_reg_t dp_tp_ctl_reg(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + + if (DISPLAY_VER(dev_priv) >= 12) + return TGL_DP_TP_CTL(tgl_dp_tp_transcoder(crtc_state)); + else + return DP_TP_CTL(encoder->port); +} + +i915_reg_t dp_tp_status_reg(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + + if (DISPLAY_VER(dev_priv) >= 12) + return TGL_DP_TP_STATUS(tgl_dp_tp_transcoder(crtc_state)); + else + return DP_TP_STATUS(encoder->port); +} + +static void intel_dp_sink_set_msa_timing_par_ignore_state(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + bool enable) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + + if (!crtc_state->vrr.enable) + return; + + if (drm_dp_dpcd_writeb(&intel_dp->aux, DP_DOWNSPREAD_CTRL, + enable ? DP_MSA_TIMING_PAR_IGNORE_EN : 0) <= 0) + drm_dbg_kms(&i915->drm, + "Failed to %s MSA_TIMING_PAR_IGNORE in the sink\n", + str_enable_disable(enable)); +} + +static void intel_dp_sink_set_fec_ready(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + + if (!crtc_state->fec_enable) + return; + + if (drm_dp_dpcd_writeb(&intel_dp->aux, DP_FEC_CONFIGURATION, DP_FEC_READY) <= 0) + drm_dbg_kms(&i915->drm, + "Failed to set FEC_READY in the sink\n"); +} + +static void intel_ddi_enable_fec(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dp *intel_dp; + u32 val; + + if (!crtc_state->fec_enable) + return; + + intel_dp = enc_to_intel_dp(encoder); + val = intel_de_read(dev_priv, dp_tp_ctl_reg(encoder, crtc_state)); + val |= DP_TP_CTL_FEC_ENABLE; + intel_de_write(dev_priv, dp_tp_ctl_reg(encoder, crtc_state), val); +} + +static void intel_ddi_disable_fec_state(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dp *intel_dp; + u32 val; + + if (!crtc_state->fec_enable) + return; + + intel_dp = enc_to_intel_dp(encoder); + val = intel_de_read(dev_priv, dp_tp_ctl_reg(encoder, crtc_state)); + val &= ~DP_TP_CTL_FEC_ENABLE; + intel_de_write(dev_priv, dp_tp_ctl_reg(encoder, crtc_state), val); + intel_de_posting_read(dev_priv, dp_tp_ctl_reg(encoder, crtc_state)); +} + +static void intel_ddi_power_up_lanes(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + enum phy phy = intel_port_to_phy(i915, encoder->port); + + if (intel_phy_is_combo(i915, phy)) { + bool lane_reversal = + dig_port->saved_port_bits & DDI_BUF_PORT_REVERSAL; + + intel_combo_phy_power_up_lanes(i915, phy, false, + crtc_state->lane_count, + lane_reversal); + } +} + +/* Splitter enable for eDP MSO is limited to certain pipes. */ +static u8 intel_ddi_splitter_pipe_mask(struct drm_i915_private *i915) +{ + if (IS_ALDERLAKE_P(i915)) + return BIT(PIPE_A) | BIT(PIPE_B); + else + return BIT(PIPE_A); +} + +static void intel_ddi_mso_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config) +{ + struct intel_crtc *crtc = to_intel_crtc(pipe_config->uapi.crtc); + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + u32 dss1; + + if (!HAS_MSO(i915)) + return; + + dss1 = intel_de_read(i915, ICL_PIPE_DSS_CTL1(pipe)); + + pipe_config->splitter.enable = dss1 & SPLITTER_ENABLE; + if (!pipe_config->splitter.enable) + return; + + if (drm_WARN_ON(&i915->drm, !(intel_ddi_splitter_pipe_mask(i915) & BIT(pipe)))) { + pipe_config->splitter.enable = false; + return; + } + + switch (dss1 & SPLITTER_CONFIGURATION_MASK) { + default: + drm_WARN(&i915->drm, true, + "Invalid splitter configuration, dss1=0x%08x\n", dss1); + fallthrough; + case SPLITTER_CONFIGURATION_2_SEGMENT: + pipe_config->splitter.link_count = 2; + break; + case SPLITTER_CONFIGURATION_4_SEGMENT: + pipe_config->splitter.link_count = 4; + break; + } + + pipe_config->splitter.pixel_overlap = REG_FIELD_GET(OVERLAP_PIXELS_MASK, dss1); +} + +static void intel_ddi_mso_configure(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + u32 dss1 = 0; + + if (!HAS_MSO(i915)) + return; + + if (crtc_state->splitter.enable) { + dss1 |= SPLITTER_ENABLE; + dss1 |= OVERLAP_PIXELS(crtc_state->splitter.pixel_overlap); + if (crtc_state->splitter.link_count == 2) + dss1 |= SPLITTER_CONFIGURATION_2_SEGMENT; + else + dss1 |= SPLITTER_CONFIGURATION_4_SEGMENT; + } + + intel_de_rmw(i915, ICL_PIPE_DSS_CTL1(pipe), + SPLITTER_ENABLE | SPLITTER_CONFIGURATION_MASK | + OVERLAP_PIXELS_MASK, dss1); +} + +static void tgl_ddi_pre_enable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + bool is_mst = intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST); + + intel_dp_set_link_params(intel_dp, + crtc_state->port_clock, + crtc_state->lane_count); + + /* + * We only configure what the register value will be here. Actual + * enabling happens during link training farther down. + */ + intel_ddi_init_dp_buf_reg(encoder, crtc_state); + + /* + * 1. Enable Power Wells + * + * This was handled at the beginning of intel_atomic_commit_tail(), + * before we called down into this function. + */ + + /* 2. Enable Panel Power if PPS is required */ + intel_pps_on(intel_dp); + + /* + * 3. For non-TBT Type-C ports, set FIA lane count + * (DFLEXDPSP.DPX4TXLATC) + * + * This was done before tgl_ddi_pre_enable_dp by + * hsw_crtc_enable()->intel_encoders_pre_pll_enable(). + */ + + /* + * 4. Enable the port PLL. + * + * The PLL enabling itself was already done before this function by + * hsw_crtc_enable()->intel_enable_shared_dpll(). We need only + * configure the PLL to port mapping here. + */ + intel_ddi_enable_clock(encoder, crtc_state); + + /* 5. If IO power is controlled through PWR_WELL_CTL, Enable IO Power */ + if (!intel_tc_port_in_tbt_alt_mode(dig_port)) { + drm_WARN_ON(&dev_priv->drm, dig_port->ddi_io_wakeref); + dig_port->ddi_io_wakeref = intel_display_power_get(dev_priv, + dig_port->ddi_io_power_domain); + } + + /* 6. Program DP_MODE */ + icl_program_mg_dp_mode(dig_port, crtc_state); + + /* + * 7. The rest of the below are substeps under the bspec's "Enable and + * Train Display Port" step. Note that steps that are specific to + * MST will be handled by intel_mst_pre_enable_dp() before/after it + * calls into this function. Also intel_mst_pre_enable_dp() only calls + * us when active_mst_links==0, so any steps designated for "single + * stream or multi-stream master transcoder" can just be performed + * unconditionally here. + */ + + /* + * 7.a Configure Transcoder Clock Select to direct the Port clock to the + * Transcoder. + */ + intel_ddi_enable_pipe_clock(encoder, crtc_state); + + if (HAS_DP20(dev_priv)) + intel_ddi_config_transcoder_dp2(encoder, crtc_state); + + /* + * 7.b Configure TRANS_DDI_FUNC_CTL DDI Select, DDI Mode Select & MST + * Transport Select + */ + intel_ddi_config_transcoder_func(encoder, crtc_state); + + /* + * 7.c Configure & enable DP_TP_CTL with link training pattern 1 + * selected + * + * This will be handled by the intel_dp_start_link_train() farther + * down this function. + */ + + /* 7.e Configure voltage swing and related IO settings */ + encoder->set_signal_levels(encoder, crtc_state); + + /* + * 7.f Combo PHY: Configure PORT_CL_DW10 Static Power Down to power up + * the used lanes of the DDI. + */ + intel_ddi_power_up_lanes(encoder, crtc_state); + + /* + * 7.g Program CoG/MSO configuration bits in DSS_CTL1 if selected. + */ + intel_ddi_mso_configure(crtc_state); + + if (!is_mst) + intel_dp_set_power(intel_dp, DP_SET_POWER_D0); + + intel_dp_configure_protocol_converter(intel_dp, crtc_state); + intel_dp_sink_set_decompression_state(intel_dp, crtc_state, true); + /* + * DDI FEC: "anticipates enabling FEC encoding sets the FEC_READY bit + * in the FEC_CONFIGURATION register to 1 before initiating link + * training + */ + intel_dp_sink_set_fec_ready(intel_dp, crtc_state); + + intel_dp_check_frl_training(intel_dp); + intel_dp_pcon_dsc_configure(intel_dp, crtc_state); + + /* + * 7.i Follow DisplayPort specification training sequence (see notes for + * failure handling) + * 7.j If DisplayPort multi-stream - Set DP_TP_CTL link training to Idle + * Pattern, wait for 5 idle patterns (DP_TP_STATUS Min_Idles_Sent) + * (timeout after 800 us) + */ + intel_dp_start_link_train(intel_dp, crtc_state); + + /* 7.k Set DP_TP_CTL link training to Normal */ + if (!is_trans_port_sync_mode(crtc_state)) + intel_dp_stop_link_train(intel_dp, crtc_state); + + /* 7.l Configure and enable FEC if needed */ + intel_ddi_enable_fec(encoder, crtc_state); + + intel_dsc_dp_pps_write(encoder, crtc_state); +} + +static void hsw_ddi_pre_enable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + enum port port = encoder->port; + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + bool is_mst = intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST); + + if (DISPLAY_VER(dev_priv) < 11) + drm_WARN_ON(&dev_priv->drm, + is_mst && (port == PORT_A || port == PORT_E)); + else + drm_WARN_ON(&dev_priv->drm, is_mst && port == PORT_A); + + intel_dp_set_link_params(intel_dp, + crtc_state->port_clock, + crtc_state->lane_count); + + /* + * We only configure what the register value will be here. Actual + * enabling happens during link training farther down. + */ + intel_ddi_init_dp_buf_reg(encoder, crtc_state); + + intel_pps_on(intel_dp); + + intel_ddi_enable_clock(encoder, crtc_state); + + if (!intel_tc_port_in_tbt_alt_mode(dig_port)) { + drm_WARN_ON(&dev_priv->drm, dig_port->ddi_io_wakeref); + dig_port->ddi_io_wakeref = intel_display_power_get(dev_priv, + dig_port->ddi_io_power_domain); + } + + icl_program_mg_dp_mode(dig_port, crtc_state); + + if (has_buf_trans_select(dev_priv)) + hsw_prepare_dp_ddi_buffers(encoder, crtc_state); + + encoder->set_signal_levels(encoder, crtc_state); + + intel_ddi_power_up_lanes(encoder, crtc_state); + + if (!is_mst) + intel_dp_set_power(intel_dp, DP_SET_POWER_D0); + intel_dp_configure_protocol_converter(intel_dp, crtc_state); + intel_dp_sink_set_decompression_state(intel_dp, crtc_state, + true); + intel_dp_sink_set_fec_ready(intel_dp, crtc_state); + intel_dp_start_link_train(intel_dp, crtc_state); + if ((port != PORT_A || DISPLAY_VER(dev_priv) >= 9) && + !is_trans_port_sync_mode(crtc_state)) + intel_dp_stop_link_train(intel_dp, crtc_state); + + intel_ddi_enable_fec(encoder, crtc_state); + + if (!is_mst) + intel_ddi_enable_pipe_clock(encoder, crtc_state); + + intel_dsc_dp_pps_write(encoder, crtc_state); +} + +static void intel_ddi_pre_enable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + + if (DISPLAY_VER(dev_priv) >= 12) + tgl_ddi_pre_enable_dp(state, encoder, crtc_state, conn_state); + else + hsw_ddi_pre_enable_dp(state, encoder, crtc_state, conn_state); + + /* MST will call a setting of MSA after an allocating of Virtual Channel + * from MST encoder pre_enable callback. + */ + if (!intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST)) + intel_ddi_set_dp_msa(crtc_state, conn_state); +} + +static void intel_ddi_pre_enable_hdmi(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + struct intel_hdmi *intel_hdmi = &dig_port->hdmi; + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + + intel_dp_dual_mode_set_tmds_output(intel_hdmi, true); + intel_ddi_enable_clock(encoder, crtc_state); + + drm_WARN_ON(&dev_priv->drm, dig_port->ddi_io_wakeref); + dig_port->ddi_io_wakeref = intel_display_power_get(dev_priv, + dig_port->ddi_io_power_domain); + + icl_program_mg_dp_mode(dig_port, crtc_state); + + intel_ddi_enable_pipe_clock(encoder, crtc_state); + + dig_port->set_infoframes(encoder, + crtc_state->has_infoframe, + crtc_state, conn_state); +} + +static void intel_ddi_pre_enable(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + + /* + * When called from DP MST code: + * - conn_state will be NULL + * - encoder will be the main encoder (ie. mst->primary) + * - the main connector associated with this port + * won't be active or linked to a crtc + * - crtc_state will be the state of the first stream to + * be activated on this port, and it may not be the same + * stream that will be deactivated last, but each stream + * should have a state that is identical when it comes to + * the DP link parameteres + */ + + drm_WARN_ON(&dev_priv->drm, crtc_state->has_pch_encoder); + + intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, true); + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) { + intel_ddi_pre_enable_hdmi(state, encoder, crtc_state, + conn_state); + } else { + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + + intel_ddi_pre_enable_dp(state, encoder, crtc_state, + conn_state); + + /* FIXME precompute everything properly */ + /* FIXME how do we turn infoframes off again? */ + if (dig_port->lspcon.active && dig_port->dp.has_hdmi_sink) + dig_port->set_infoframes(encoder, + crtc_state->has_infoframe, + crtc_state, conn_state); + } +} + +static void intel_disable_ddi_buf(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + enum port port = encoder->port; + bool wait = false; + u32 val; + + val = intel_de_read(dev_priv, DDI_BUF_CTL(port)); + if (val & DDI_BUF_CTL_ENABLE) { + val &= ~DDI_BUF_CTL_ENABLE; + intel_de_write(dev_priv, DDI_BUF_CTL(port), val); + wait = true; + } + + if (intel_crtc_has_dp_encoder(crtc_state)) { + val = intel_de_read(dev_priv, dp_tp_ctl_reg(encoder, crtc_state)); + val &= ~(DP_TP_CTL_ENABLE | DP_TP_CTL_LINK_TRAIN_MASK); + val |= DP_TP_CTL_LINK_TRAIN_PAT1; + intel_de_write(dev_priv, dp_tp_ctl_reg(encoder, crtc_state), val); + } + + /* Disable FEC in DP Sink */ + intel_ddi_disable_fec_state(encoder, crtc_state); + + if (wait) + intel_wait_ddi_buf_idle(dev_priv, port); +} + +static void intel_ddi_post_disable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + struct intel_dp *intel_dp = &dig_port->dp; + bool is_mst = intel_crtc_has_type(old_crtc_state, + INTEL_OUTPUT_DP_MST); + + if (!is_mst) + intel_dp_set_infoframes(encoder, false, + old_crtc_state, old_conn_state); + + /* + * Power down sink before disabling the port, otherwise we end + * up getting interrupts from the sink on detecting link loss. + */ + intel_dp_set_power(intel_dp, DP_SET_POWER_D3); + + if (DISPLAY_VER(dev_priv) >= 12) { + if (is_mst) { + enum transcoder cpu_transcoder = old_crtc_state->cpu_transcoder; + u32 val; + + val = intel_de_read(dev_priv, + TRANS_DDI_FUNC_CTL(cpu_transcoder)); + val &= ~(TGL_TRANS_DDI_PORT_MASK | + TRANS_DDI_MODE_SELECT_MASK); + intel_de_write(dev_priv, + TRANS_DDI_FUNC_CTL(cpu_transcoder), + val); + } + } else { + if (!is_mst) + intel_ddi_disable_pipe_clock(old_crtc_state); + } + + intel_disable_ddi_buf(encoder, old_crtc_state); + + /* + * From TGL spec: "If single stream or multi-stream master transcoder: + * Configure Transcoder Clock select to direct no clock to the + * transcoder" + */ + if (DISPLAY_VER(dev_priv) >= 12) + intel_ddi_disable_pipe_clock(old_crtc_state); + + intel_pps_vdd_on(intel_dp); + intel_pps_off(intel_dp); + + if (!intel_tc_port_in_tbt_alt_mode(dig_port)) + intel_display_power_put(dev_priv, + dig_port->ddi_io_power_domain, + fetch_and_zero(&dig_port->ddi_io_wakeref)); + + intel_ddi_disable_clock(encoder); +} + +static void intel_ddi_post_disable_hdmi(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + struct intel_hdmi *intel_hdmi = &dig_port->hdmi; + + dig_port->set_infoframes(encoder, false, + old_crtc_state, old_conn_state); + + if (DISPLAY_VER(dev_priv) < 12) + intel_ddi_disable_pipe_clock(old_crtc_state); + + intel_disable_ddi_buf(encoder, old_crtc_state); + + if (DISPLAY_VER(dev_priv) >= 12) + intel_ddi_disable_pipe_clock(old_crtc_state); + + intel_display_power_put(dev_priv, + dig_port->ddi_io_power_domain, + fetch_and_zero(&dig_port->ddi_io_wakeref)); + + intel_ddi_disable_clock(encoder); + + intel_dp_dual_mode_set_tmds_output(intel_hdmi, false); +} + +static void intel_ddi_post_disable(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + enum phy phy = intel_port_to_phy(dev_priv, encoder->port); + bool is_tc_port = intel_phy_is_tc(dev_priv, phy); + struct intel_crtc *slave_crtc; + + if (!intel_crtc_has_type(old_crtc_state, INTEL_OUTPUT_DP_MST)) { + intel_crtc_vblank_off(old_crtc_state); + + intel_disable_transcoder(old_crtc_state); + + intel_vrr_disable(old_crtc_state); + + intel_ddi_disable_transcoder_func(old_crtc_state); + + intel_dsc_disable(old_crtc_state); + + if (DISPLAY_VER(dev_priv) >= 9) + skl_scaler_disable(old_crtc_state); + else + ilk_pfit_disable(old_crtc_state); + } + + for_each_intel_crtc_in_pipe_mask(&dev_priv->drm, slave_crtc, + intel_crtc_bigjoiner_slave_pipes(old_crtc_state)) { + const struct intel_crtc_state *old_slave_crtc_state = + intel_atomic_get_old_crtc_state(state, slave_crtc); + + intel_crtc_vblank_off(old_slave_crtc_state); + + intel_dsc_disable(old_slave_crtc_state); + skl_scaler_disable(old_slave_crtc_state); + } + + /* + * When called from DP MST code: + * - old_conn_state will be NULL + * - encoder will be the main encoder (ie. mst->primary) + * - the main connector associated with this port + * won't be active or linked to a crtc + * - old_crtc_state will be the state of the last stream to + * be deactivated on this port, and it may not be the same + * stream that was activated last, but each stream + * should have a state that is identical when it comes to + * the DP link parameteres + */ + + if (intel_crtc_has_type(old_crtc_state, INTEL_OUTPUT_HDMI)) + intel_ddi_post_disable_hdmi(state, encoder, old_crtc_state, + old_conn_state); + else + intel_ddi_post_disable_dp(state, encoder, old_crtc_state, + old_conn_state); + + if (intel_crtc_has_dp_encoder(old_crtc_state) || is_tc_port) + intel_display_power_put(dev_priv, + intel_ddi_main_link_aux_domain(dig_port), + fetch_and_zero(&dig_port->aux_wakeref)); + + if (is_tc_port) + intel_tc_port_put_link(dig_port); +} + +static void trans_port_sync_stop_link_train(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + const struct drm_connector_state *conn_state; + struct drm_connector *conn; + int i; + + if (!crtc_state->sync_mode_slaves_mask) + return; + + for_each_new_connector_in_state(&state->base, conn, conn_state, i) { + struct intel_encoder *slave_encoder = + to_intel_encoder(conn_state->best_encoder); + struct intel_crtc *slave_crtc = to_intel_crtc(conn_state->crtc); + const struct intel_crtc_state *slave_crtc_state; + + if (!slave_crtc) + continue; + + slave_crtc_state = + intel_atomic_get_new_crtc_state(state, slave_crtc); + + if (slave_crtc_state->master_transcoder != + crtc_state->cpu_transcoder) + continue; + + intel_dp_stop_link_train(enc_to_intel_dp(slave_encoder), + slave_crtc_state); + } + + usleep_range(200, 400); + + intel_dp_stop_link_train(enc_to_intel_dp(encoder), + crtc_state); +} + +static void intel_enable_ddi_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + enum port port = encoder->port; + + if (port == PORT_A && DISPLAY_VER(dev_priv) < 9) + intel_dp_stop_link_train(intel_dp, crtc_state); + + drm_connector_update_privacy_screen(conn_state); + intel_edp_backlight_on(crtc_state, conn_state); + + if (!dig_port->lspcon.active || dig_port->dp.has_hdmi_sink) + intel_dp_set_infoframes(encoder, true, crtc_state, conn_state); + + intel_audio_codec_enable(encoder, crtc_state, conn_state); + + trans_port_sync_stop_link_train(state, encoder, crtc_state); +} + +static i915_reg_t +gen9_chicken_trans_reg_by_port(struct drm_i915_private *dev_priv, + enum port port) +{ + static const enum transcoder trans[] = { + [PORT_A] = TRANSCODER_EDP, + [PORT_B] = TRANSCODER_A, + [PORT_C] = TRANSCODER_B, + [PORT_D] = TRANSCODER_C, + [PORT_E] = TRANSCODER_A, + }; + + drm_WARN_ON(&dev_priv->drm, DISPLAY_VER(dev_priv) < 9); + + if (drm_WARN_ON(&dev_priv->drm, port < PORT_A || port > PORT_E)) + port = PORT_A; + + return CHICKEN_TRANS(trans[port]); +} + +static void intel_enable_ddi_hdmi(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + struct drm_connector *connector = conn_state->connector; + enum port port = encoder->port; + enum phy phy = intel_port_to_phy(dev_priv, port); + u32 buf_ctl; + + if (!intel_hdmi_handle_sink_scrambling(encoder, connector, + crtc_state->hdmi_high_tmds_clock_ratio, + crtc_state->hdmi_scrambling)) + drm_dbg_kms(&dev_priv->drm, + "[CONNECTOR:%d:%s] Failed to configure sink scrambling/TMDS bit clock ratio\n", + connector->base.id, connector->name); + + if (has_buf_trans_select(dev_priv)) + hsw_prepare_hdmi_ddi_buffers(encoder, crtc_state); + + encoder->set_signal_levels(encoder, crtc_state); + + /* Display WA #1143: skl,kbl,cfl */ + if (DISPLAY_VER(dev_priv) == 9 && !IS_BROXTON(dev_priv)) { + /* + * For some reason these chicken bits have been + * stuffed into a transcoder register, event though + * the bits affect a specific DDI port rather than + * a specific transcoder. + */ + i915_reg_t reg = gen9_chicken_trans_reg_by_port(dev_priv, port); + u32 val; + + val = intel_de_read(dev_priv, reg); + + if (port == PORT_E) + val |= DDIE_TRAINING_OVERRIDE_ENABLE | + DDIE_TRAINING_OVERRIDE_VALUE; + else + val |= DDI_TRAINING_OVERRIDE_ENABLE | + DDI_TRAINING_OVERRIDE_VALUE; + + intel_de_write(dev_priv, reg, val); + intel_de_posting_read(dev_priv, reg); + + udelay(1); + + if (port == PORT_E) + val &= ~(DDIE_TRAINING_OVERRIDE_ENABLE | + DDIE_TRAINING_OVERRIDE_VALUE); + else + val &= ~(DDI_TRAINING_OVERRIDE_ENABLE | + DDI_TRAINING_OVERRIDE_VALUE); + + intel_de_write(dev_priv, reg, val); + } + + intel_ddi_power_up_lanes(encoder, crtc_state); + + /* In HDMI/DVI mode, the port width, and swing/emphasis values + * are ignored so nothing special needs to be done besides + * enabling the port. + * + * On ADL_P the PHY link rate and lane count must be programmed but + * these are both 0 for HDMI. + */ + buf_ctl = dig_port->saved_port_bits | DDI_BUF_CTL_ENABLE; + if (IS_ALDERLAKE_P(dev_priv) && intel_phy_is_tc(dev_priv, phy)) { + drm_WARN_ON(&dev_priv->drm, !intel_tc_port_in_legacy_mode(dig_port)); + buf_ctl |= DDI_BUF_CTL_TC_PHY_OWNERSHIP; + } + intel_de_write(dev_priv, DDI_BUF_CTL(port), buf_ctl); + + intel_audio_codec_enable(encoder, crtc_state, conn_state); +} + +static void intel_enable_ddi(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + drm_WARN_ON(state->base.dev, crtc_state->has_pch_encoder); + + if (!intel_crtc_is_bigjoiner_slave(crtc_state)) + intel_ddi_enable_transcoder_func(encoder, crtc_state); + + intel_vrr_enable(encoder, crtc_state); + + intel_enable_transcoder(crtc_state); + + intel_crtc_vblank_on(crtc_state); + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + intel_enable_ddi_hdmi(state, encoder, crtc_state, conn_state); + else + intel_enable_ddi_dp(state, encoder, crtc_state, conn_state); + + /* Enable hdcp if it's desired */ + if (conn_state->content_protection == + DRM_MODE_CONTENT_PROTECTION_DESIRED) + intel_hdcp_enable(to_intel_connector(conn_state->connector), + crtc_state, + (u8)conn_state->hdcp_content_type); +} + +static void intel_disable_ddi_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + + intel_dp->link_trained = false; + + intel_audio_codec_disable(encoder, old_crtc_state, old_conn_state); + + intel_psr_disable(intel_dp, old_crtc_state); + intel_edp_backlight_off(old_conn_state); + /* Disable the decompression in DP Sink */ + intel_dp_sink_set_decompression_state(intel_dp, old_crtc_state, + false); + /* Disable Ignore_MSA bit in DP Sink */ + intel_dp_sink_set_msa_timing_par_ignore_state(intel_dp, old_crtc_state, + false); +} + +static void intel_disable_ddi_hdmi(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + struct drm_connector *connector = old_conn_state->connector; + + intel_audio_codec_disable(encoder, old_crtc_state, old_conn_state); + + if (!intel_hdmi_handle_sink_scrambling(encoder, connector, + false, false)) + drm_dbg_kms(&i915->drm, + "[CONNECTOR:%d:%s] Failed to reset sink scrambling/TMDS bit clock ratio\n", + connector->base.id, connector->name); +} + +static void intel_disable_ddi(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + intel_hdcp_disable(to_intel_connector(old_conn_state->connector)); + + if (intel_crtc_has_type(old_crtc_state, INTEL_OUTPUT_HDMI)) + intel_disable_ddi_hdmi(state, encoder, old_crtc_state, + old_conn_state); + else + intel_disable_ddi_dp(state, encoder, old_crtc_state, + old_conn_state); +} + +static void intel_ddi_update_pipe_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + intel_ddi_set_dp_msa(crtc_state, conn_state); + + intel_dp_set_infoframes(encoder, true, crtc_state, conn_state); + + intel_backlight_update(state, encoder, crtc_state, conn_state); + drm_connector_update_privacy_screen(conn_state); +} + +void intel_ddi_update_pipe(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + + if (!intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI) && + !intel_encoder_is_mst(encoder)) + intel_ddi_update_pipe_dp(state, encoder, crtc_state, + conn_state); + + intel_hdcp_update_pipe(state, encoder, crtc_state, conn_state); +} + +static void +intel_ddi_update_prepare(struct intel_atomic_state *state, + struct intel_encoder *encoder, + struct intel_crtc *crtc) +{ + struct drm_i915_private *i915 = to_i915(state->base.dev); + struct intel_crtc_state *crtc_state = + crtc ? intel_atomic_get_new_crtc_state(state, crtc) : NULL; + int required_lanes = crtc_state ? crtc_state->lane_count : 1; + + drm_WARN_ON(state->base.dev, crtc && crtc->active); + + intel_tc_port_get_link(enc_to_dig_port(encoder), + required_lanes); + if (crtc_state && crtc_state->hw.active) { + struct intel_crtc *slave_crtc; + + intel_update_active_dpll(state, crtc, encoder); + + for_each_intel_crtc_in_pipe_mask(&i915->drm, slave_crtc, + intel_crtc_bigjoiner_slave_pipes(crtc_state)) + intel_update_active_dpll(state, slave_crtc, encoder); + } +} + +static void +intel_ddi_update_complete(struct intel_atomic_state *state, + struct intel_encoder *encoder, + struct intel_crtc *crtc) +{ + intel_tc_port_put_link(enc_to_dig_port(encoder)); +} + +static void +intel_ddi_pre_pll_enable(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + enum phy phy = intel_port_to_phy(dev_priv, encoder->port); + bool is_tc_port = intel_phy_is_tc(dev_priv, phy); + + if (is_tc_port) + intel_tc_port_get_link(dig_port, crtc_state->lane_count); + + if (intel_crtc_has_dp_encoder(crtc_state) || is_tc_port) { + drm_WARN_ON(&dev_priv->drm, dig_port->aux_wakeref); + dig_port->aux_wakeref = + intel_display_power_get(dev_priv, + intel_ddi_main_link_aux_domain(dig_port)); + } + + if (is_tc_port && !intel_tc_port_in_tbt_alt_mode(dig_port)) + /* + * Program the lane count for static/dynamic connections on + * Type-C ports. Skip this step for TBT. + */ + intel_tc_port_set_fia_lane_count(dig_port, crtc_state->lane_count); + else if (IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv)) + bxt_ddi_phy_set_lane_optim_mask(encoder, + crtc_state->lane_lat_optim_mask); +} + +static void adlp_tbt_to_dp_alt_switch_wa(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum tc_port tc_port = intel_port_to_tc(i915, encoder->port); + int ln; + + for (ln = 0; ln < 2; ln++) + intel_dkl_phy_rmw(i915, DKL_PCS_DW5(tc_port), ln, DKL_PCS_DW5_CORE_SOFTRESET, 0); +} + +static void intel_ddi_prepare_link_retrain(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + struct intel_encoder *encoder = &dig_port->base; + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + enum port port = encoder->port; + u32 dp_tp_ctl, ddi_buf_ctl; + bool wait = false; + + dp_tp_ctl = intel_de_read(dev_priv, dp_tp_ctl_reg(encoder, crtc_state)); + + if (dp_tp_ctl & DP_TP_CTL_ENABLE) { + ddi_buf_ctl = intel_de_read(dev_priv, DDI_BUF_CTL(port)); + if (ddi_buf_ctl & DDI_BUF_CTL_ENABLE) { + intel_de_write(dev_priv, DDI_BUF_CTL(port), + ddi_buf_ctl & ~DDI_BUF_CTL_ENABLE); + wait = true; + } + + dp_tp_ctl &= ~(DP_TP_CTL_ENABLE | DP_TP_CTL_LINK_TRAIN_MASK); + dp_tp_ctl |= DP_TP_CTL_LINK_TRAIN_PAT1; + intel_de_write(dev_priv, dp_tp_ctl_reg(encoder, crtc_state), dp_tp_ctl); + intel_de_posting_read(dev_priv, dp_tp_ctl_reg(encoder, crtc_state)); + + if (wait) + intel_wait_ddi_buf_idle(dev_priv, port); + } + + dp_tp_ctl = DP_TP_CTL_ENABLE | DP_TP_CTL_LINK_TRAIN_PAT1; + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST)) { + dp_tp_ctl |= DP_TP_CTL_MODE_MST; + } else { + dp_tp_ctl |= DP_TP_CTL_MODE_SST; + if (drm_dp_enhanced_frame_cap(intel_dp->dpcd)) + dp_tp_ctl |= DP_TP_CTL_ENHANCED_FRAME_ENABLE; + } + intel_de_write(dev_priv, dp_tp_ctl_reg(encoder, crtc_state), dp_tp_ctl); + intel_de_posting_read(dev_priv, dp_tp_ctl_reg(encoder, crtc_state)); + + if (IS_ALDERLAKE_P(dev_priv) && + (intel_tc_port_in_dp_alt_mode(dig_port) || intel_tc_port_in_legacy_mode(dig_port))) + adlp_tbt_to_dp_alt_switch_wa(encoder); + + intel_dp->DP |= DDI_BUF_CTL_ENABLE; + intel_de_write(dev_priv, DDI_BUF_CTL(port), intel_dp->DP); + intel_de_posting_read(dev_priv, DDI_BUF_CTL(port)); + + intel_wait_ddi_buf_active(dev_priv, port); +} + +static void intel_ddi_set_link_train(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + u8 dp_train_pat) +{ + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + u32 temp; + + temp = intel_de_read(dev_priv, dp_tp_ctl_reg(encoder, crtc_state)); + + temp &= ~DP_TP_CTL_LINK_TRAIN_MASK; + switch (intel_dp_training_pattern_symbol(dp_train_pat)) { + case DP_TRAINING_PATTERN_DISABLE: + temp |= DP_TP_CTL_LINK_TRAIN_NORMAL; + break; + case DP_TRAINING_PATTERN_1: + temp |= DP_TP_CTL_LINK_TRAIN_PAT1; + break; + case DP_TRAINING_PATTERN_2: + temp |= DP_TP_CTL_LINK_TRAIN_PAT2; + break; + case DP_TRAINING_PATTERN_3: + temp |= DP_TP_CTL_LINK_TRAIN_PAT3; + break; + case DP_TRAINING_PATTERN_4: + temp |= DP_TP_CTL_LINK_TRAIN_PAT4; + break; + } + + intel_de_write(dev_priv, dp_tp_ctl_reg(encoder, crtc_state), temp); +} + +static void intel_ddi_set_idle_link_train(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + enum port port = encoder->port; + u32 val; + + val = intel_de_read(dev_priv, dp_tp_ctl_reg(encoder, crtc_state)); + val &= ~DP_TP_CTL_LINK_TRAIN_MASK; + val |= DP_TP_CTL_LINK_TRAIN_IDLE; + intel_de_write(dev_priv, dp_tp_ctl_reg(encoder, crtc_state), val); + + /* + * Until TGL on PORT_A we can have only eDP in SST mode. There the only + * reason we need to set idle transmission mode is to work around a HW + * issue where we enable the pipe while not in idle link-training mode. + * In this case there is requirement to wait for a minimum number of + * idle patterns to be sent. + */ + if (port == PORT_A && DISPLAY_VER(dev_priv) < 12) + return; + + if (intel_de_wait_for_set(dev_priv, + dp_tp_status_reg(encoder, crtc_state), + DP_TP_STATUS_IDLE_DONE, 1)) + drm_err(&dev_priv->drm, + "Timed out waiting for DP idle patterns\n"); +} + +static bool intel_ddi_is_audio_enabled(struct drm_i915_private *dev_priv, + enum transcoder cpu_transcoder) +{ + if (cpu_transcoder == TRANSCODER_EDP) + return false; + + if (!intel_display_power_is_enabled(dev_priv, POWER_DOMAIN_AUDIO_MMIO)) + return false; + + return intel_de_read(dev_priv, HSW_AUD_PIN_ELD_CP_VLD) & + AUDIO_OUTPUT_ENABLE(cpu_transcoder); +} + +void intel_ddi_compute_min_voltage_level(struct drm_i915_private *dev_priv, + struct intel_crtc_state *crtc_state) +{ + if (DISPLAY_VER(dev_priv) >= 12 && crtc_state->port_clock > 594000) + crtc_state->min_voltage_level = 2; + else if (IS_JSL_EHL(dev_priv) && crtc_state->port_clock > 594000) + crtc_state->min_voltage_level = 3; + else if (DISPLAY_VER(dev_priv) >= 11 && crtc_state->port_clock > 594000) + crtc_state->min_voltage_level = 1; +} + +static enum transcoder bdw_transcoder_master_readout(struct drm_i915_private *dev_priv, + enum transcoder cpu_transcoder) +{ + u32 master_select; + + if (DISPLAY_VER(dev_priv) >= 11) { + u32 ctl2 = intel_de_read(dev_priv, TRANS_DDI_FUNC_CTL2(cpu_transcoder)); + + if ((ctl2 & PORT_SYNC_MODE_ENABLE) == 0) + return INVALID_TRANSCODER; + + master_select = REG_FIELD_GET(PORT_SYNC_MODE_MASTER_SELECT_MASK, ctl2); + } else { + u32 ctl = intel_de_read(dev_priv, TRANS_DDI_FUNC_CTL(cpu_transcoder)); + + if ((ctl & TRANS_DDI_PORT_SYNC_ENABLE) == 0) + return INVALID_TRANSCODER; + + master_select = REG_FIELD_GET(TRANS_DDI_PORT_SYNC_MASTER_SELECT_MASK, ctl); + } + + if (master_select == 0) + return TRANSCODER_EDP; + else + return master_select - 1; +} + +static void bdw_get_trans_port_sync_config(struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + u32 transcoders = BIT(TRANSCODER_A) | BIT(TRANSCODER_B) | + BIT(TRANSCODER_C) | BIT(TRANSCODER_D); + enum transcoder cpu_transcoder; + + crtc_state->master_transcoder = + bdw_transcoder_master_readout(dev_priv, crtc_state->cpu_transcoder); + + for_each_cpu_transcoder_masked(dev_priv, cpu_transcoder, transcoders) { + enum intel_display_power_domain power_domain; + intel_wakeref_t trans_wakeref; + + power_domain = POWER_DOMAIN_TRANSCODER(cpu_transcoder); + trans_wakeref = intel_display_power_get_if_enabled(dev_priv, + power_domain); + + if (!trans_wakeref) + continue; + + if (bdw_transcoder_master_readout(dev_priv, cpu_transcoder) == + crtc_state->cpu_transcoder) + crtc_state->sync_mode_slaves_mask |= BIT(cpu_transcoder); + + intel_display_power_put(dev_priv, power_domain, trans_wakeref); + } + + drm_WARN_ON(&dev_priv->drm, + crtc_state->master_transcoder != INVALID_TRANSCODER && + crtc_state->sync_mode_slaves_mask); +} + +static void intel_ddi_read_func_ctl(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_crtc *crtc = to_intel_crtc(pipe_config->uapi.crtc); + enum transcoder cpu_transcoder = pipe_config->cpu_transcoder; + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + u32 temp, flags = 0; + + temp = intel_de_read(dev_priv, TRANS_DDI_FUNC_CTL(cpu_transcoder)); + if (temp & TRANS_DDI_PHSYNC) + flags |= DRM_MODE_FLAG_PHSYNC; + else + flags |= DRM_MODE_FLAG_NHSYNC; + if (temp & TRANS_DDI_PVSYNC) + flags |= DRM_MODE_FLAG_PVSYNC; + else + flags |= DRM_MODE_FLAG_NVSYNC; + + pipe_config->hw.adjusted_mode.flags |= flags; + + switch (temp & TRANS_DDI_BPC_MASK) { + case TRANS_DDI_BPC_6: + pipe_config->pipe_bpp = 18; + break; + case TRANS_DDI_BPC_8: + pipe_config->pipe_bpp = 24; + break; + case TRANS_DDI_BPC_10: + pipe_config->pipe_bpp = 30; + break; + case TRANS_DDI_BPC_12: + pipe_config->pipe_bpp = 36; + break; + default: + break; + } + + switch (temp & TRANS_DDI_MODE_SELECT_MASK) { + case TRANS_DDI_MODE_SELECT_HDMI: + pipe_config->has_hdmi_sink = true; + + pipe_config->infoframes.enable |= + intel_hdmi_infoframes_enabled(encoder, pipe_config); + + if (pipe_config->infoframes.enable) + pipe_config->has_infoframe = true; + + if (temp & TRANS_DDI_HDMI_SCRAMBLING) + pipe_config->hdmi_scrambling = true; + if (temp & TRANS_DDI_HIGH_TMDS_CHAR_RATE) + pipe_config->hdmi_high_tmds_clock_ratio = true; + fallthrough; + case TRANS_DDI_MODE_SELECT_DVI: + pipe_config->output_types |= BIT(INTEL_OUTPUT_HDMI); + pipe_config->lane_count = 4; + break; + case TRANS_DDI_MODE_SELECT_DP_SST: + if (encoder->type == INTEL_OUTPUT_EDP) + pipe_config->output_types |= BIT(INTEL_OUTPUT_EDP); + else + pipe_config->output_types |= BIT(INTEL_OUTPUT_DP); + pipe_config->lane_count = + ((temp & DDI_PORT_WIDTH_MASK) >> DDI_PORT_WIDTH_SHIFT) + 1; + + intel_cpu_transcoder_get_m1_n1(crtc, cpu_transcoder, + &pipe_config->dp_m_n); + intel_cpu_transcoder_get_m2_n2(crtc, cpu_transcoder, + &pipe_config->dp_m2_n2); + + if (DISPLAY_VER(dev_priv) >= 11) { + i915_reg_t dp_tp_ctl = dp_tp_ctl_reg(encoder, pipe_config); + + pipe_config->fec_enable = + intel_de_read(dev_priv, dp_tp_ctl) & DP_TP_CTL_FEC_ENABLE; + + drm_dbg_kms(&dev_priv->drm, + "[ENCODER:%d:%s] Fec status: %u\n", + encoder->base.base.id, encoder->base.name, + pipe_config->fec_enable); + } + + if (dig_port->lspcon.active && dig_port->dp.has_hdmi_sink) + pipe_config->infoframes.enable |= + intel_lspcon_infoframes_enabled(encoder, pipe_config); + else + pipe_config->infoframes.enable |= + intel_hdmi_infoframes_enabled(encoder, pipe_config); + break; + case TRANS_DDI_MODE_SELECT_FDI_OR_128B132B: + if (!HAS_DP20(dev_priv)) { + /* FDI */ + pipe_config->output_types |= BIT(INTEL_OUTPUT_ANALOG); + break; + } + fallthrough; /* 128b/132b */ + case TRANS_DDI_MODE_SELECT_DP_MST: + pipe_config->output_types |= BIT(INTEL_OUTPUT_DP_MST); + pipe_config->lane_count = + ((temp & DDI_PORT_WIDTH_MASK) >> DDI_PORT_WIDTH_SHIFT) + 1; + + if (DISPLAY_VER(dev_priv) >= 12) + pipe_config->mst_master_transcoder = + REG_FIELD_GET(TRANS_DDI_MST_TRANSPORT_SELECT_MASK, temp); + + intel_cpu_transcoder_get_m1_n1(crtc, cpu_transcoder, + &pipe_config->dp_m_n); + + pipe_config->infoframes.enable |= + intel_hdmi_infoframes_enabled(encoder, pipe_config); + break; + default: + break; + } +} + +static void intel_ddi_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + enum transcoder cpu_transcoder = pipe_config->cpu_transcoder; + + /* XXX: DSI transcoder paranoia */ + if (drm_WARN_ON(&dev_priv->drm, transcoder_is_dsi(cpu_transcoder))) + return; + + intel_ddi_read_func_ctl(encoder, pipe_config); + + intel_ddi_mso_get_config(encoder, pipe_config); + + pipe_config->has_audio = + intel_ddi_is_audio_enabled(dev_priv, cpu_transcoder); + + if (encoder->type == INTEL_OUTPUT_EDP) + intel_edp_fixup_vbt_bpp(encoder, pipe_config->pipe_bpp); + + ddi_dotclock_get(pipe_config); + + if (IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv)) + pipe_config->lane_lat_optim_mask = + bxt_ddi_phy_get_lane_lat_optim_mask(encoder); + + intel_ddi_compute_min_voltage_level(dev_priv, pipe_config); + + intel_hdmi_read_gcp_infoframe(encoder, pipe_config); + + intel_read_infoframe(encoder, pipe_config, + HDMI_INFOFRAME_TYPE_AVI, + &pipe_config->infoframes.avi); + intel_read_infoframe(encoder, pipe_config, + HDMI_INFOFRAME_TYPE_SPD, + &pipe_config->infoframes.spd); + intel_read_infoframe(encoder, pipe_config, + HDMI_INFOFRAME_TYPE_VENDOR, + &pipe_config->infoframes.hdmi); + intel_read_infoframe(encoder, pipe_config, + HDMI_INFOFRAME_TYPE_DRM, + &pipe_config->infoframes.drm); + + if (DISPLAY_VER(dev_priv) >= 8) + bdw_get_trans_port_sync_config(pipe_config); + + intel_read_dp_sdp(encoder, pipe_config, HDMI_PACKET_TYPE_GAMUT_METADATA); + intel_read_dp_sdp(encoder, pipe_config, DP_SDP_VSC); + + intel_psr_get_config(encoder, pipe_config); +} + +void intel_ddi_get_clock(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state, + struct intel_shared_dpll *pll) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum icl_port_dpll_id port_dpll_id = ICL_PORT_DPLL_DEFAULT; + struct icl_port_dpll *port_dpll = &crtc_state->icl_port_dplls[port_dpll_id]; + bool pll_active; + + if (drm_WARN_ON(&i915->drm, !pll)) + return; + + port_dpll->pll = pll; + pll_active = intel_dpll_get_hw_state(i915, pll, &port_dpll->hw_state); + drm_WARN_ON(&i915->drm, !pll_active); + + icl_set_active_port_dpll(crtc_state, port_dpll_id); + + crtc_state->port_clock = intel_dpll_get_freq(i915, crtc_state->shared_dpll, + &crtc_state->dpll_hw_state); +} + +static void dg2_ddi_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + intel_mpllb_readout_hw_state(encoder, &crtc_state->mpllb_state); + crtc_state->port_clock = intel_mpllb_calc_port_clock(encoder, &crtc_state->mpllb_state); + + intel_ddi_get_config(encoder, crtc_state); +} + +static void adls_ddi_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + intel_ddi_get_clock(encoder, crtc_state, adls_ddi_get_pll(encoder)); + intel_ddi_get_config(encoder, crtc_state); +} + +static void rkl_ddi_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + intel_ddi_get_clock(encoder, crtc_state, rkl_ddi_get_pll(encoder)); + intel_ddi_get_config(encoder, crtc_state); +} + +static void dg1_ddi_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + intel_ddi_get_clock(encoder, crtc_state, dg1_ddi_get_pll(encoder)); + intel_ddi_get_config(encoder, crtc_state); +} + +static void icl_ddi_combo_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + intel_ddi_get_clock(encoder, crtc_state, icl_ddi_combo_get_pll(encoder)); + intel_ddi_get_config(encoder, crtc_state); +} + +static void icl_ddi_tc_get_clock(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state, + struct intel_shared_dpll *pll) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum icl_port_dpll_id port_dpll_id; + struct icl_port_dpll *port_dpll; + bool pll_active; + + if (drm_WARN_ON(&i915->drm, !pll)) + return; + + if (intel_get_shared_dpll_id(i915, pll) == DPLL_ID_ICL_TBTPLL) + port_dpll_id = ICL_PORT_DPLL_DEFAULT; + else + port_dpll_id = ICL_PORT_DPLL_MG_PHY; + + port_dpll = &crtc_state->icl_port_dplls[port_dpll_id]; + + port_dpll->pll = pll; + pll_active = intel_dpll_get_hw_state(i915, pll, &port_dpll->hw_state); + drm_WARN_ON(&i915->drm, !pll_active); + + icl_set_active_port_dpll(crtc_state, port_dpll_id); + + if (intel_get_shared_dpll_id(i915, crtc_state->shared_dpll) == DPLL_ID_ICL_TBTPLL) + crtc_state->port_clock = icl_calc_tbt_pll_link(i915, encoder->port); + else + crtc_state->port_clock = intel_dpll_get_freq(i915, crtc_state->shared_dpll, + &crtc_state->dpll_hw_state); +} + +static void icl_ddi_tc_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + icl_ddi_tc_get_clock(encoder, crtc_state, icl_ddi_tc_get_pll(encoder)); + intel_ddi_get_config(encoder, crtc_state); +} + +static void bxt_ddi_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + intel_ddi_get_clock(encoder, crtc_state, bxt_ddi_get_pll(encoder)); + intel_ddi_get_config(encoder, crtc_state); +} + +static void skl_ddi_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + intel_ddi_get_clock(encoder, crtc_state, skl_ddi_get_pll(encoder)); + intel_ddi_get_config(encoder, crtc_state); +} + +void hsw_ddi_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + intel_ddi_get_clock(encoder, crtc_state, hsw_ddi_get_pll(encoder)); + intel_ddi_get_config(encoder, crtc_state); +} + +static void intel_ddi_sync_state(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum phy phy = intel_port_to_phy(i915, encoder->port); + + if (intel_phy_is_tc(i915, phy)) + intel_tc_port_sanitize_mode(enc_to_dig_port(encoder)); + + if (crtc_state && intel_crtc_has_dp_encoder(crtc_state)) + intel_dp_sync_state(encoder, crtc_state); +} + +static bool intel_ddi_initial_fastset_check(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum phy phy = intel_port_to_phy(i915, encoder->port); + bool fastset = true; + + if (intel_phy_is_tc(i915, phy)) { + drm_dbg_kms(&i915->drm, "[ENCODER:%d:%s] Forcing full modeset to compute TC port DPLLs\n", + encoder->base.base.id, encoder->base.name); + crtc_state->uapi.mode_changed = true; + fastset = false; + } + + if (intel_crtc_has_dp_encoder(crtc_state) && + !intel_dp_initial_fastset_check(encoder, crtc_state)) + fastset = false; + + return fastset; +} + +static enum intel_output_type +intel_ddi_compute_output_type(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + switch (conn_state->connector->connector_type) { + case DRM_MODE_CONNECTOR_HDMIA: + return INTEL_OUTPUT_HDMI; + case DRM_MODE_CONNECTOR_eDP: + return INTEL_OUTPUT_EDP; + case DRM_MODE_CONNECTOR_DisplayPort: + return INTEL_OUTPUT_DP; + default: + MISSING_CASE(conn_state->connector->connector_type); + return INTEL_OUTPUT_UNUSED; + } +} + +static int intel_ddi_compute_config(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config, + struct drm_connector_state *conn_state) +{ + struct intel_crtc *crtc = to_intel_crtc(pipe_config->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + enum port port = encoder->port; + int ret; + + if (HAS_TRANSCODER(dev_priv, TRANSCODER_EDP) && port == PORT_A) + pipe_config->cpu_transcoder = TRANSCODER_EDP; + + if (intel_crtc_has_type(pipe_config, INTEL_OUTPUT_HDMI)) { + ret = intel_hdmi_compute_config(encoder, pipe_config, conn_state); + } else { + ret = intel_dp_compute_config(encoder, pipe_config, conn_state); + } + + if (ret) + return ret; + + if (IS_HASWELL(dev_priv) && crtc->pipe == PIPE_A && + pipe_config->cpu_transcoder == TRANSCODER_EDP) + pipe_config->pch_pfit.force_thru = + pipe_config->pch_pfit.enabled || + pipe_config->crc_enabled; + + if (IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv)) + pipe_config->lane_lat_optim_mask = + bxt_ddi_phy_calc_lane_lat_optim_mask(pipe_config->lane_count); + + intel_ddi_compute_min_voltage_level(dev_priv, pipe_config); + + return 0; +} + +static bool mode_equal(const struct drm_display_mode *mode1, + const struct drm_display_mode *mode2) +{ + return drm_mode_match(mode1, mode2, + DRM_MODE_MATCH_TIMINGS | + DRM_MODE_MATCH_FLAGS | + DRM_MODE_MATCH_3D_FLAGS) && + mode1->clock == mode2->clock; /* we want an exact match */ +} + +static bool m_n_equal(const struct intel_link_m_n *m_n_1, + const struct intel_link_m_n *m_n_2) +{ + return m_n_1->tu == m_n_2->tu && + m_n_1->data_m == m_n_2->data_m && + m_n_1->data_n == m_n_2->data_n && + m_n_1->link_m == m_n_2->link_m && + m_n_1->link_n == m_n_2->link_n; +} + +static bool crtcs_port_sync_compatible(const struct intel_crtc_state *crtc_state1, + const struct intel_crtc_state *crtc_state2) +{ + return crtc_state1->hw.active && crtc_state2->hw.active && + crtc_state1->output_types == crtc_state2->output_types && + crtc_state1->output_format == crtc_state2->output_format && + crtc_state1->lane_count == crtc_state2->lane_count && + crtc_state1->port_clock == crtc_state2->port_clock && + mode_equal(&crtc_state1->hw.adjusted_mode, + &crtc_state2->hw.adjusted_mode) && + m_n_equal(&crtc_state1->dp_m_n, &crtc_state2->dp_m_n); +} + +static u8 +intel_ddi_port_sync_transcoders(const struct intel_crtc_state *ref_crtc_state, + int tile_group_id) +{ + struct drm_connector *connector; + const struct drm_connector_state *conn_state; + struct drm_i915_private *dev_priv = to_i915(ref_crtc_state->uapi.crtc->dev); + struct intel_atomic_state *state = + to_intel_atomic_state(ref_crtc_state->uapi.state); + u8 transcoders = 0; + int i; + + /* + * We don't enable port sync on BDW due to missing w/as and + * due to not having adjusted the modeset sequence appropriately. + */ + if (DISPLAY_VER(dev_priv) < 9) + return 0; + + if (!intel_crtc_has_type(ref_crtc_state, INTEL_OUTPUT_DP)) + return 0; + + for_each_new_connector_in_state(&state->base, connector, conn_state, i) { + struct intel_crtc *crtc = to_intel_crtc(conn_state->crtc); + const struct intel_crtc_state *crtc_state; + + if (!crtc) + continue; + + if (!connector->has_tile || + connector->tile_group->id != + tile_group_id) + continue; + crtc_state = intel_atomic_get_new_crtc_state(state, + crtc); + if (!crtcs_port_sync_compatible(ref_crtc_state, + crtc_state)) + continue; + transcoders |= BIT(crtc_state->cpu_transcoder); + } + + return transcoders; +} + +static int intel_ddi_compute_config_late(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + struct drm_connector *connector = conn_state->connector; + u8 port_sync_transcoders = 0; + + drm_dbg_kms(&i915->drm, "[ENCODER:%d:%s] [CRTC:%d:%s]", + encoder->base.base.id, encoder->base.name, + crtc_state->uapi.crtc->base.id, crtc_state->uapi.crtc->name); + + if (connector->has_tile) + port_sync_transcoders = intel_ddi_port_sync_transcoders(crtc_state, + connector->tile_group->id); + + /* + * EDP Transcoders cannot be ensalved + * make them a master always when present + */ + if (port_sync_transcoders & BIT(TRANSCODER_EDP)) + crtc_state->master_transcoder = TRANSCODER_EDP; + else + crtc_state->master_transcoder = ffs(port_sync_transcoders) - 1; + + if (crtc_state->master_transcoder == crtc_state->cpu_transcoder) { + crtc_state->master_transcoder = INVALID_TRANSCODER; + crtc_state->sync_mode_slaves_mask = + port_sync_transcoders & ~BIT(crtc_state->cpu_transcoder); + } + + return 0; +} + +static void intel_ddi_encoder_destroy(struct drm_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->dev); + struct intel_digital_port *dig_port = enc_to_dig_port(to_intel_encoder(encoder)); + enum phy phy = intel_port_to_phy(i915, dig_port->base.port); + + intel_dp_encoder_flush_work(encoder); + if (intel_phy_is_tc(i915, phy)) + intel_tc_port_flush_work(dig_port); + intel_display_power_flush_work(i915); + + drm_encoder_cleanup(encoder); + kfree(dig_port->hdcp_port_data.streams); + kfree(dig_port); +} + +static void intel_ddi_encoder_reset(struct drm_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->dev); + struct intel_dp *intel_dp = enc_to_intel_dp(to_intel_encoder(encoder)); + struct intel_digital_port *dig_port = enc_to_dig_port(to_intel_encoder(encoder)); + enum phy phy = intel_port_to_phy(i915, dig_port->base.port); + + intel_dp->reset_link_params = true; + + intel_pps_encoder_reset(intel_dp); + + if (intel_phy_is_tc(i915, phy)) + intel_tc_port_init_mode(dig_port); +} + +static const struct drm_encoder_funcs intel_ddi_funcs = { + .reset = intel_ddi_encoder_reset, + .destroy = intel_ddi_encoder_destroy, +}; + +static struct intel_connector * +intel_ddi_init_dp_connector(struct intel_digital_port *dig_port) +{ + struct intel_connector *connector; + enum port port = dig_port->base.port; + + connector = intel_connector_alloc(); + if (!connector) + return NULL; + + dig_port->dp.output_reg = DDI_BUF_CTL(port); + dig_port->dp.prepare_link_retrain = intel_ddi_prepare_link_retrain; + dig_port->dp.set_link_train = intel_ddi_set_link_train; + dig_port->dp.set_idle_link_train = intel_ddi_set_idle_link_train; + + dig_port->dp.voltage_max = intel_ddi_dp_voltage_max; + dig_port->dp.preemph_max = intel_ddi_dp_preemph_max; + + if (!intel_dp_init_connector(dig_port, connector)) { + kfree(connector); + return NULL; + } + + if (dig_port->base.type == INTEL_OUTPUT_EDP) { + struct drm_device *dev = dig_port->base.base.dev; + struct drm_privacy_screen *privacy_screen; + + privacy_screen = drm_privacy_screen_get(dev->dev, NULL); + if (!IS_ERR(privacy_screen)) { + drm_connector_attach_privacy_screen_provider(&connector->base, + privacy_screen); + } else if (PTR_ERR(privacy_screen) != -ENODEV) { + drm_warn(dev, "Error getting privacy-screen\n"); + } + } + + return connector; +} + +static int modeset_pipe(struct drm_crtc *crtc, + struct drm_modeset_acquire_ctx *ctx) +{ + struct drm_atomic_state *state; + struct drm_crtc_state *crtc_state; + int ret; + + state = drm_atomic_state_alloc(crtc->dev); + if (!state) + return -ENOMEM; + + state->acquire_ctx = ctx; + + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) { + ret = PTR_ERR(crtc_state); + goto out; + } + + crtc_state->connectors_changed = true; + + ret = drm_atomic_commit(state); +out: + drm_atomic_state_put(state); + + return ret; +} + +static int intel_hdmi_reset_link(struct intel_encoder *encoder, + struct drm_modeset_acquire_ctx *ctx) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_hdmi *hdmi = enc_to_intel_hdmi(encoder); + struct intel_connector *connector = hdmi->attached_connector; + struct i2c_adapter *adapter = + intel_gmbus_get_adapter(dev_priv, hdmi->ddc_bus); + struct drm_connector_state *conn_state; + struct intel_crtc_state *crtc_state; + struct intel_crtc *crtc; + u8 config; + int ret; + + if (!connector || connector->base.status != connector_status_connected) + return 0; + + ret = drm_modeset_lock(&dev_priv->drm.mode_config.connection_mutex, + ctx); + if (ret) + return ret; + + conn_state = connector->base.state; + + crtc = to_intel_crtc(conn_state->crtc); + if (!crtc) + return 0; + + ret = drm_modeset_lock(&crtc->base.mutex, ctx); + if (ret) + return ret; + + crtc_state = to_intel_crtc_state(crtc->base.state); + + drm_WARN_ON(&dev_priv->drm, + !intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)); + + if (!crtc_state->hw.active) + return 0; + + if (!crtc_state->hdmi_high_tmds_clock_ratio && + !crtc_state->hdmi_scrambling) + return 0; + + if (conn_state->commit && + !try_wait_for_completion(&conn_state->commit->hw_done)) + return 0; + + ret = drm_scdc_readb(adapter, SCDC_TMDS_CONFIG, &config); + if (ret < 0) { + drm_err(&dev_priv->drm, "Failed to read TMDS config: %d\n", + ret); + return 0; + } + + if (!!(config & SCDC_TMDS_BIT_CLOCK_RATIO_BY_40) == + crtc_state->hdmi_high_tmds_clock_ratio && + !!(config & SCDC_SCRAMBLING_ENABLE) == + crtc_state->hdmi_scrambling) + return 0; + + /* + * HDMI 2.0 says that one should not send scrambled data + * prior to configuring the sink scrambling, and that + * TMDS clock/data transmission should be suspended when + * changing the TMDS clock rate in the sink. So let's + * just do a full modeset here, even though some sinks + * would be perfectly happy if were to just reconfigure + * the SCDC settings on the fly. + */ + return modeset_pipe(&crtc->base, ctx); +} + +static enum intel_hotplug_state +intel_ddi_hotplug(struct intel_encoder *encoder, + struct intel_connector *connector) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + struct intel_dp *intel_dp = &dig_port->dp; + enum phy phy = intel_port_to_phy(i915, encoder->port); + bool is_tc = intel_phy_is_tc(i915, phy); + struct drm_modeset_acquire_ctx ctx; + enum intel_hotplug_state state; + int ret; + + if (intel_dp->compliance.test_active && + intel_dp->compliance.test_type == DP_TEST_LINK_PHY_TEST_PATTERN) { + intel_dp_phy_test(encoder); + /* just do the PHY test and nothing else */ + return INTEL_HOTPLUG_UNCHANGED; + } + + state = intel_encoder_hotplug(encoder, connector); + + drm_modeset_acquire_init(&ctx, 0); + + for (;;) { + if (connector->base.connector_type == DRM_MODE_CONNECTOR_HDMIA) + ret = intel_hdmi_reset_link(encoder, &ctx); + else + ret = intel_dp_retrain_link(encoder, &ctx); + + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + continue; + } + + break; + } + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); + drm_WARN(encoder->base.dev, ret, + "Acquiring modeset locks failed with %i\n", ret); + + /* + * Unpowered type-c dongles can take some time to boot and be + * responsible, so here giving some time to those dongles to power up + * and then retrying the probe. + * + * On many platforms the HDMI live state signal is known to be + * unreliable, so we can't use it to detect if a sink is connected or + * not. Instead we detect if it's connected based on whether we can + * read the EDID or not. That in turn has a problem during disconnect, + * since the HPD interrupt may be raised before the DDC lines get + * disconnected (due to how the required length of DDC vs. HPD + * connector pins are specified) and so we'll still be able to get a + * valid EDID. To solve this schedule another detection cycle if this + * time around we didn't detect any change in the sink's connection + * status. + * + * Type-c connectors which get their HPD signal deasserted then + * reasserted, without unplugging/replugging the sink from the + * connector, introduce a delay until the AUX channel communication + * becomes functional. Retry the detection for 5 seconds on type-c + * connectors to account for this delay. + */ + if (state == INTEL_HOTPLUG_UNCHANGED && + connector->hotplug_retries < (is_tc ? 5 : 1) && + !dig_port->dp.is_mst) + state = INTEL_HOTPLUG_RETRY; + + return state; +} + +static bool lpt_digital_port_connected(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + u32 bit = dev_priv->display.hotplug.pch_hpd[encoder->hpd_pin]; + + return intel_de_read(dev_priv, SDEISR) & bit; +} + +static bool hsw_digital_port_connected(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + u32 bit = dev_priv->display.hotplug.hpd[encoder->hpd_pin]; + + return intel_de_read(dev_priv, DEISR) & bit; +} + +static bool bdw_digital_port_connected(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + u32 bit = dev_priv->display.hotplug.hpd[encoder->hpd_pin]; + + return intel_de_read(dev_priv, GEN8_DE_PORT_ISR) & bit; +} + +static struct intel_connector * +intel_ddi_init_hdmi_connector(struct intel_digital_port *dig_port) +{ + struct intel_connector *connector; + enum port port = dig_port->base.port; + + connector = intel_connector_alloc(); + if (!connector) + return NULL; + + dig_port->hdmi.hdmi_reg = DDI_BUF_CTL(port); + intel_hdmi_init_connector(dig_port, connector); + + return connector; +} + +static bool intel_ddi_a_force_4_lanes(struct intel_digital_port *dig_port) +{ + struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev); + + if (dig_port->base.port != PORT_A) + return false; + + if (dig_port->saved_port_bits & DDI_A_4_LANES) + return false; + + /* Broxton/Geminilake: Bspec says that DDI_A_4_LANES is the only + * supported configuration + */ + if (IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv)) + return true; + + return false; +} + +static int +intel_ddi_max_lanes(struct intel_digital_port *dig_port) +{ + struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev); + enum port port = dig_port->base.port; + int max_lanes = 4; + + if (DISPLAY_VER(dev_priv) >= 11) + return max_lanes; + + if (port == PORT_A || port == PORT_E) { + if (intel_de_read(dev_priv, DDI_BUF_CTL(PORT_A)) & DDI_A_4_LANES) + max_lanes = port == PORT_A ? 4 : 0; + else + /* Both A and E share 2 lanes */ + max_lanes = 2; + } + + /* + * Some BIOS might fail to set this bit on port A if eDP + * wasn't lit up at boot. Force this bit set when needed + * so we use the proper lane count for our calculations. + */ + if (intel_ddi_a_force_4_lanes(dig_port)) { + drm_dbg_kms(&dev_priv->drm, + "Forcing DDI_A_4_LANES for port A\n"); + dig_port->saved_port_bits |= DDI_A_4_LANES; + max_lanes = 4; + } + + return max_lanes; +} + +static bool hti_uses_phy(struct drm_i915_private *i915, enum phy phy) +{ + return i915->hti_state & HDPORT_ENABLED && + i915->hti_state & HDPORT_DDI_USED(phy); +} + +static enum hpd_pin xelpd_hpd_pin(struct drm_i915_private *dev_priv, + enum port port) +{ + if (port >= PORT_D_XELPD) + return HPD_PORT_D + port - PORT_D_XELPD; + else if (port >= PORT_TC1) + return HPD_PORT_TC1 + port - PORT_TC1; + else + return HPD_PORT_A + port - PORT_A; +} + +static enum hpd_pin dg1_hpd_pin(struct drm_i915_private *dev_priv, + enum port port) +{ + if (port >= PORT_TC1) + return HPD_PORT_C + port - PORT_TC1; + else + return HPD_PORT_A + port - PORT_A; +} + +static enum hpd_pin tgl_hpd_pin(struct drm_i915_private *dev_priv, + enum port port) +{ + if (port >= PORT_TC1) + return HPD_PORT_TC1 + port - PORT_TC1; + else + return HPD_PORT_A + port - PORT_A; +} + +static enum hpd_pin rkl_hpd_pin(struct drm_i915_private *dev_priv, + enum port port) +{ + if (HAS_PCH_TGP(dev_priv)) + return tgl_hpd_pin(dev_priv, port); + + if (port >= PORT_TC1) + return HPD_PORT_C + port - PORT_TC1; + else + return HPD_PORT_A + port - PORT_A; +} + +static enum hpd_pin icl_hpd_pin(struct drm_i915_private *dev_priv, + enum port port) +{ + if (port >= PORT_C) + return HPD_PORT_TC1 + port - PORT_C; + else + return HPD_PORT_A + port - PORT_A; +} + +static enum hpd_pin ehl_hpd_pin(struct drm_i915_private *dev_priv, + enum port port) +{ + if (port == PORT_D) + return HPD_PORT_A; + + if (HAS_PCH_TGP(dev_priv)) + return icl_hpd_pin(dev_priv, port); + + return HPD_PORT_A + port - PORT_A; +} + +static enum hpd_pin skl_hpd_pin(struct drm_i915_private *dev_priv, enum port port) +{ + if (HAS_PCH_TGP(dev_priv)) + return icl_hpd_pin(dev_priv, port); + + return HPD_PORT_A + port - PORT_A; +} + +static bool intel_ddi_is_tc(struct drm_i915_private *i915, enum port port) +{ + if (DISPLAY_VER(i915) >= 12) + return port >= PORT_TC1; + else if (DISPLAY_VER(i915) >= 11) + return port >= PORT_C; + else + return false; +} + +static void intel_ddi_encoder_suspend(struct intel_encoder *encoder) +{ + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + enum phy phy = intel_port_to_phy(i915, encoder->port); + + intel_dp_encoder_suspend(encoder); + + if (!intel_phy_is_tc(i915, phy)) + return; + + intel_tc_port_flush_work(dig_port); +} + +static void intel_ddi_encoder_shutdown(struct intel_encoder *encoder) +{ + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + enum phy phy = intel_port_to_phy(i915, encoder->port); + + intel_dp_encoder_shutdown(encoder); + intel_hdmi_encoder_shutdown(encoder); + + if (!intel_phy_is_tc(i915, phy)) + return; + + intel_tc_port_flush_work(dig_port); +} + +#define port_tc_name(port) ((port) - PORT_TC1 + '1') +#define tc_port_name(tc_port) ((tc_port) - TC_PORT_1 + '1') + +void intel_ddi_init(struct drm_i915_private *dev_priv, enum port port) +{ + struct intel_digital_port *dig_port; + struct intel_encoder *encoder; + const struct intel_bios_encoder_data *devdata; + bool init_hdmi, init_dp; + enum phy phy = intel_port_to_phy(dev_priv, port); + + /* + * On platforms with HTI (aka HDPORT), if it's enabled at boot it may + * have taken over some of the PHYs and made them unavailable to the + * driver. In that case we should skip initializing the corresponding + * outputs. + */ + if (hti_uses_phy(dev_priv, phy)) { + drm_dbg_kms(&dev_priv->drm, "PORT %c / PHY %c reserved by HTI\n", + port_name(port), phy_name(phy)); + return; + } + + devdata = intel_bios_encoder_data_lookup(dev_priv, port); + if (!devdata) { + drm_dbg_kms(&dev_priv->drm, + "VBT says port %c is not present\n", + port_name(port)); + return; + } + + init_hdmi = intel_bios_encoder_supports_dvi(devdata) || + intel_bios_encoder_supports_hdmi(devdata); + init_dp = intel_bios_encoder_supports_dp(devdata); + + if (intel_bios_is_lspcon_present(dev_priv, port)) { + /* + * Lspcon device needs to be driven with DP connector + * with special detection sequence. So make sure DP + * is initialized before lspcon. + */ + init_dp = true; + init_hdmi = false; + drm_dbg_kms(&dev_priv->drm, "VBT says port %c has lspcon\n", + port_name(port)); + } + + if (!init_dp && !init_hdmi) { + drm_dbg_kms(&dev_priv->drm, + "VBT says port %c is not DVI/HDMI/DP compatible, respect it\n", + port_name(port)); + return; + } + + if (intel_phy_is_snps(dev_priv, phy) && + dev_priv->snps_phy_failed_calibration & BIT(phy)) { + drm_dbg_kms(&dev_priv->drm, + "SNPS PHY %c failed to calibrate, proceeding anyway\n", + phy_name(phy)); + } + + dig_port = kzalloc(sizeof(*dig_port), GFP_KERNEL); + if (!dig_port) + return; + + encoder = &dig_port->base; + encoder->devdata = devdata; + + if (DISPLAY_VER(dev_priv) >= 13 && port >= PORT_D_XELPD) { + drm_encoder_init(&dev_priv->drm, &encoder->base, &intel_ddi_funcs, + DRM_MODE_ENCODER_TMDS, + "DDI %c/PHY %c", + port_name(port - PORT_D_XELPD + PORT_D), + phy_name(phy)); + } else if (DISPLAY_VER(dev_priv) >= 12) { + enum tc_port tc_port = intel_port_to_tc(dev_priv, port); + + drm_encoder_init(&dev_priv->drm, &encoder->base, &intel_ddi_funcs, + DRM_MODE_ENCODER_TMDS, + "DDI %s%c/PHY %s%c", + port >= PORT_TC1 ? "TC" : "", + port >= PORT_TC1 ? port_tc_name(port) : port_name(port), + tc_port != TC_PORT_NONE ? "TC" : "", + tc_port != TC_PORT_NONE ? tc_port_name(tc_port) : phy_name(phy)); + } else if (DISPLAY_VER(dev_priv) >= 11) { + enum tc_port tc_port = intel_port_to_tc(dev_priv, port); + + drm_encoder_init(&dev_priv->drm, &encoder->base, &intel_ddi_funcs, + DRM_MODE_ENCODER_TMDS, + "DDI %c%s/PHY %s%c", + port_name(port), + port >= PORT_C ? " (TC)" : "", + tc_port != TC_PORT_NONE ? "TC" : "", + tc_port != TC_PORT_NONE ? tc_port_name(tc_port) : phy_name(phy)); + } else { + drm_encoder_init(&dev_priv->drm, &encoder->base, &intel_ddi_funcs, + DRM_MODE_ENCODER_TMDS, + "DDI %c/PHY %c", port_name(port), phy_name(phy)); + } + + mutex_init(&dig_port->hdcp_mutex); + dig_port->num_hdcp_streams = 0; + + encoder->hotplug = intel_ddi_hotplug; + encoder->compute_output_type = intel_ddi_compute_output_type; + encoder->compute_config = intel_ddi_compute_config; + encoder->compute_config_late = intel_ddi_compute_config_late; + encoder->enable = intel_enable_ddi; + encoder->pre_pll_enable = intel_ddi_pre_pll_enable; + encoder->pre_enable = intel_ddi_pre_enable; + encoder->disable = intel_disable_ddi; + encoder->post_disable = intel_ddi_post_disable; + encoder->update_pipe = intel_ddi_update_pipe; + encoder->get_hw_state = intel_ddi_get_hw_state; + encoder->sync_state = intel_ddi_sync_state; + encoder->initial_fastset_check = intel_ddi_initial_fastset_check; + encoder->suspend = intel_ddi_encoder_suspend; + encoder->shutdown = intel_ddi_encoder_shutdown; + encoder->get_power_domains = intel_ddi_get_power_domains; + + encoder->type = INTEL_OUTPUT_DDI; + encoder->power_domain = intel_display_power_ddi_lanes_domain(dev_priv, port); + encoder->port = port; + encoder->cloneable = 0; + encoder->pipe_mask = ~0; + + if (IS_DG2(dev_priv)) { + encoder->enable_clock = intel_mpllb_enable; + encoder->disable_clock = intel_mpllb_disable; + encoder->get_config = dg2_ddi_get_config; + } else if (IS_ALDERLAKE_S(dev_priv)) { + encoder->enable_clock = adls_ddi_enable_clock; + encoder->disable_clock = adls_ddi_disable_clock; + encoder->is_clock_enabled = adls_ddi_is_clock_enabled; + encoder->get_config = adls_ddi_get_config; + } else if (IS_ROCKETLAKE(dev_priv)) { + encoder->enable_clock = rkl_ddi_enable_clock; + encoder->disable_clock = rkl_ddi_disable_clock; + encoder->is_clock_enabled = rkl_ddi_is_clock_enabled; + encoder->get_config = rkl_ddi_get_config; + } else if (IS_DG1(dev_priv)) { + encoder->enable_clock = dg1_ddi_enable_clock; + encoder->disable_clock = dg1_ddi_disable_clock; + encoder->is_clock_enabled = dg1_ddi_is_clock_enabled; + encoder->get_config = dg1_ddi_get_config; + } else if (IS_JSL_EHL(dev_priv)) { + if (intel_ddi_is_tc(dev_priv, port)) { + encoder->enable_clock = jsl_ddi_tc_enable_clock; + encoder->disable_clock = jsl_ddi_tc_disable_clock; + encoder->is_clock_enabled = jsl_ddi_tc_is_clock_enabled; + encoder->get_config = icl_ddi_combo_get_config; + } else { + encoder->enable_clock = icl_ddi_combo_enable_clock; + encoder->disable_clock = icl_ddi_combo_disable_clock; + encoder->is_clock_enabled = icl_ddi_combo_is_clock_enabled; + encoder->get_config = icl_ddi_combo_get_config; + } + } else if (DISPLAY_VER(dev_priv) >= 11) { + if (intel_ddi_is_tc(dev_priv, port)) { + encoder->enable_clock = icl_ddi_tc_enable_clock; + encoder->disable_clock = icl_ddi_tc_disable_clock; + encoder->is_clock_enabled = icl_ddi_tc_is_clock_enabled; + encoder->get_config = icl_ddi_tc_get_config; + } else { + encoder->enable_clock = icl_ddi_combo_enable_clock; + encoder->disable_clock = icl_ddi_combo_disable_clock; + encoder->is_clock_enabled = icl_ddi_combo_is_clock_enabled; + encoder->get_config = icl_ddi_combo_get_config; + } + } else if (IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv)) { + /* BXT/GLK have fixed PLL->port mapping */ + encoder->get_config = bxt_ddi_get_config; + } else if (DISPLAY_VER(dev_priv) == 9) { + encoder->enable_clock = skl_ddi_enable_clock; + encoder->disable_clock = skl_ddi_disable_clock; + encoder->is_clock_enabled = skl_ddi_is_clock_enabled; + encoder->get_config = skl_ddi_get_config; + } else if (IS_BROADWELL(dev_priv) || IS_HASWELL(dev_priv)) { + encoder->enable_clock = hsw_ddi_enable_clock; + encoder->disable_clock = hsw_ddi_disable_clock; + encoder->is_clock_enabled = hsw_ddi_is_clock_enabled; + encoder->get_config = hsw_ddi_get_config; + } + + if (IS_DG2(dev_priv)) { + encoder->set_signal_levels = intel_snps_phy_set_signal_levels; + } else if (DISPLAY_VER(dev_priv) >= 12) { + if (intel_phy_is_combo(dev_priv, phy)) + encoder->set_signal_levels = icl_combo_phy_set_signal_levels; + else + encoder->set_signal_levels = tgl_dkl_phy_set_signal_levels; + } else if (DISPLAY_VER(dev_priv) >= 11) { + if (intel_phy_is_combo(dev_priv, phy)) + encoder->set_signal_levels = icl_combo_phy_set_signal_levels; + else + encoder->set_signal_levels = icl_mg_phy_set_signal_levels; + } else if (IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv)) { + encoder->set_signal_levels = bxt_ddi_phy_set_signal_levels; + } else { + encoder->set_signal_levels = hsw_set_signal_levels; + } + + intel_ddi_buf_trans_init(encoder); + + if (DISPLAY_VER(dev_priv) >= 13) + encoder->hpd_pin = xelpd_hpd_pin(dev_priv, port); + else if (IS_DG1(dev_priv)) + encoder->hpd_pin = dg1_hpd_pin(dev_priv, port); + else if (IS_ROCKETLAKE(dev_priv)) + encoder->hpd_pin = rkl_hpd_pin(dev_priv, port); + else if (DISPLAY_VER(dev_priv) >= 12) + encoder->hpd_pin = tgl_hpd_pin(dev_priv, port); + else if (IS_JSL_EHL(dev_priv)) + encoder->hpd_pin = ehl_hpd_pin(dev_priv, port); + else if (DISPLAY_VER(dev_priv) == 11) + encoder->hpd_pin = icl_hpd_pin(dev_priv, port); + else if (DISPLAY_VER(dev_priv) == 9 && !IS_BROXTON(dev_priv)) + encoder->hpd_pin = skl_hpd_pin(dev_priv, port); + else + encoder->hpd_pin = intel_hpd_pin_default(dev_priv, port); + + if (DISPLAY_VER(dev_priv) >= 11) + dig_port->saved_port_bits = + intel_de_read(dev_priv, DDI_BUF_CTL(port)) + & DDI_BUF_PORT_REVERSAL; + else + dig_port->saved_port_bits = + intel_de_read(dev_priv, DDI_BUF_CTL(port)) + & (DDI_BUF_PORT_REVERSAL | DDI_A_4_LANES); + + if (intel_bios_is_lane_reversal_needed(dev_priv, port)) + dig_port->saved_port_bits |= DDI_BUF_PORT_REVERSAL; + + dig_port->dp.output_reg = INVALID_MMIO_REG; + dig_port->max_lanes = intel_ddi_max_lanes(dig_port); + dig_port->aux_ch = intel_bios_port_aux_ch(dev_priv, port); + + if (intel_phy_is_tc(dev_priv, phy)) { + bool is_legacy = + !intel_bios_encoder_supports_typec_usb(devdata) && + !intel_bios_encoder_supports_tbt(devdata); + + intel_tc_port_init(dig_port, is_legacy); + + encoder->update_prepare = intel_ddi_update_prepare; + encoder->update_complete = intel_ddi_update_complete; + } + + drm_WARN_ON(&dev_priv->drm, port > PORT_I); + dig_port->ddi_io_power_domain = intel_display_power_ddi_io_domain(dev_priv, port); + + if (init_dp) { + if (!intel_ddi_init_dp_connector(dig_port)) + goto err; + + dig_port->hpd_pulse = intel_dp_hpd_pulse; + + if (dig_port->dp.mso_link_count) + encoder->pipe_mask = intel_ddi_splitter_pipe_mask(dev_priv); + } + + /* In theory we don't need the encoder->type check, but leave it just in + * case we have some really bad VBTs... */ + if (encoder->type != INTEL_OUTPUT_EDP && init_hdmi) { + if (!intel_ddi_init_hdmi_connector(dig_port)) + goto err; + } + + if (DISPLAY_VER(dev_priv) >= 11) { + if (intel_phy_is_tc(dev_priv, phy)) + dig_port->connected = intel_tc_port_connected; + else + dig_port->connected = lpt_digital_port_connected; + } else if (DISPLAY_VER(dev_priv) >= 8) { + if (port == PORT_A || IS_GEMINILAKE(dev_priv) || + IS_BROXTON(dev_priv)) + dig_port->connected = bdw_digital_port_connected; + else + dig_port->connected = lpt_digital_port_connected; + } else { + if (port == PORT_A) + dig_port->connected = hsw_digital_port_connected; + else + dig_port->connected = lpt_digital_port_connected; + } + + intel_infoframe_init(dig_port); + + return; + +err: + drm_encoder_cleanup(&encoder->base); + kfree(dig_port); +} diff --git a/drivers/gpu/drm/i915/display/intel_ddi.h b/drivers/gpu/drm/i915/display/intel_ddi.h new file mode 100644 index 000000000..d39076fac --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_ddi.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_DDI_H__ +#define __INTEL_DDI_H__ + +#include "i915_reg_defs.h" + +struct drm_connector_state; +struct drm_i915_private; +struct intel_atomic_state; +struct intel_connector; +struct intel_crtc; +struct intel_crtc_state; +struct intel_dp; +struct intel_dpll_hw_state; +struct intel_encoder; +struct intel_shared_dpll; +enum pipe; +enum port; +enum transcoder; + +i915_reg_t dp_tp_ctl_reg(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state); +i915_reg_t dp_tp_status_reg(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state); +void intel_ddi_fdi_post_disable(struct intel_atomic_state *state, + struct intel_encoder *intel_encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state); +void intel_ddi_enable_clock(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state); +void intel_ddi_disable_clock(struct intel_encoder *encoder); +void intel_ddi_get_clock(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state, + struct intel_shared_dpll *pll); +void hsw_ddi_enable_clock(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state); +void hsw_ddi_disable_clock(struct intel_encoder *encoder); +bool hsw_ddi_is_clock_enabled(struct intel_encoder *encoder); +void hsw_ddi_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state); +struct intel_shared_dpll *icl_ddi_combo_get_pll(struct intel_encoder *encoder); +void hsw_prepare_dp_ddi_buffers(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state); +void intel_wait_ddi_buf_idle(struct drm_i915_private *dev_priv, + enum port port); +void intel_ddi_init(struct drm_i915_private *dev_priv, enum port port); +bool intel_ddi_get_hw_state(struct intel_encoder *encoder, enum pipe *pipe); +void intel_ddi_enable_transcoder_func(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state); +void intel_ddi_disable_transcoder_func(const struct intel_crtc_state *crtc_state); +void intel_ddi_enable_pipe_clock(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state); +void intel_ddi_disable_pipe_clock(const struct intel_crtc_state *crtc_state); +void intel_ddi_set_dp_msa(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state); +bool intel_ddi_connector_get_hw_state(struct intel_connector *intel_connector); +void intel_ddi_set_vc_payload_alloc(const struct intel_crtc_state *crtc_state, + bool state); +void intel_ddi_compute_min_voltage_level(struct drm_i915_private *dev_priv, + struct intel_crtc_state *crtc_state); +int intel_ddi_toggle_hdcp_bits(struct intel_encoder *intel_encoder, + enum transcoder cpu_transcoder, + bool enable, u32 hdcp_mask); +void intel_ddi_sanitize_encoder_pll_mapping(struct intel_encoder *encoder); +int intel_ddi_level(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int lane); + +#endif /* __INTEL_DDI_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_ddi_buf_trans.c b/drivers/gpu/drm/i915/display/intel_ddi_buf_trans.c new file mode 100644 index 000000000..006a2e979 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_ddi_buf_trans.c @@ -0,0 +1,1662 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2020 Intel Corporation + */ + +#include "i915_drv.h" +#include "intel_ddi.h" +#include "intel_ddi_buf_trans.h" +#include "intel_de.h" +#include "intel_display_types.h" +#include "intel_dp.h" + +/* HDMI/DVI modes ignore everything but the last 2 items. So we share + * them for both DP and FDI transports, allowing those ports to + * automatically adapt to HDMI connections as well + */ +static const union intel_ddi_buf_trans_entry _hsw_trans_dp[] = { + { .hsw = { 0x00FFFFFF, 0x0006000E, 0x0 } }, + { .hsw = { 0x00D75FFF, 0x0005000A, 0x0 } }, + { .hsw = { 0x00C30FFF, 0x00040006, 0x0 } }, + { .hsw = { 0x80AAAFFF, 0x000B0000, 0x0 } }, + { .hsw = { 0x00FFFFFF, 0x0005000A, 0x0 } }, + { .hsw = { 0x00D75FFF, 0x000C0004, 0x0 } }, + { .hsw = { 0x80C30FFF, 0x000B0000, 0x0 } }, + { .hsw = { 0x00FFFFFF, 0x00040006, 0x0 } }, + { .hsw = { 0x80D75FFF, 0x000B0000, 0x0 } }, +}; + +static const struct intel_ddi_buf_trans hsw_trans_dp = { + .entries = _hsw_trans_dp, + .num_entries = ARRAY_SIZE(_hsw_trans_dp), +}; + +static const union intel_ddi_buf_trans_entry _hsw_trans_fdi[] = { + { .hsw = { 0x00FFFFFF, 0x0007000E, 0x0 } }, + { .hsw = { 0x00D75FFF, 0x000F000A, 0x0 } }, + { .hsw = { 0x00C30FFF, 0x00060006, 0x0 } }, + { .hsw = { 0x00AAAFFF, 0x001E0000, 0x0 } }, + { .hsw = { 0x00FFFFFF, 0x000F000A, 0x0 } }, + { .hsw = { 0x00D75FFF, 0x00160004, 0x0 } }, + { .hsw = { 0x00C30FFF, 0x001E0000, 0x0 } }, + { .hsw = { 0x00FFFFFF, 0x00060006, 0x0 } }, + { .hsw = { 0x00D75FFF, 0x001E0000, 0x0 } }, +}; + +static const struct intel_ddi_buf_trans hsw_trans_fdi = { + .entries = _hsw_trans_fdi, + .num_entries = ARRAY_SIZE(_hsw_trans_fdi), +}; + +static const union intel_ddi_buf_trans_entry _hsw_trans_hdmi[] = { + /* Idx NT mV d T mV d db */ + { .hsw = { 0x00FFFFFF, 0x0006000E, 0x0 } }, /* 0: 400 400 0 */ + { .hsw = { 0x00E79FFF, 0x000E000C, 0x0 } }, /* 1: 400 500 2 */ + { .hsw = { 0x00D75FFF, 0x0005000A, 0x0 } }, /* 2: 400 600 3.5 */ + { .hsw = { 0x00FFFFFF, 0x0005000A, 0x0 } }, /* 3: 600 600 0 */ + { .hsw = { 0x00E79FFF, 0x001D0007, 0x0 } }, /* 4: 600 750 2 */ + { .hsw = { 0x00D75FFF, 0x000C0004, 0x0 } }, /* 5: 600 900 3.5 */ + { .hsw = { 0x00FFFFFF, 0x00040006, 0x0 } }, /* 6: 800 800 0 */ + { .hsw = { 0x80E79FFF, 0x00030002, 0x0 } }, /* 7: 800 1000 2 */ + { .hsw = { 0x00FFFFFF, 0x00140005, 0x0 } }, /* 8: 850 850 0 */ + { .hsw = { 0x00FFFFFF, 0x000C0004, 0x0 } }, /* 9: 900 900 0 */ + { .hsw = { 0x00FFFFFF, 0x001C0003, 0x0 } }, /* 10: 950 950 0 */ + { .hsw = { 0x80FFFFFF, 0x00030002, 0x0 } }, /* 11: 1000 1000 0 */ +}; + +static const struct intel_ddi_buf_trans hsw_trans_hdmi = { + .entries = _hsw_trans_hdmi, + .num_entries = ARRAY_SIZE(_hsw_trans_hdmi), + .hdmi_default_entry = 6, +}; + +static const union intel_ddi_buf_trans_entry _bdw_trans_edp[] = { + { .hsw = { 0x00FFFFFF, 0x00000012, 0x0 } }, + { .hsw = { 0x00EBAFFF, 0x00020011, 0x0 } }, + { .hsw = { 0x00C71FFF, 0x0006000F, 0x0 } }, + { .hsw = { 0x00AAAFFF, 0x000E000A, 0x0 } }, + { .hsw = { 0x00FFFFFF, 0x00020011, 0x0 } }, + { .hsw = { 0x00DB6FFF, 0x0005000F, 0x0 } }, + { .hsw = { 0x00BEEFFF, 0x000A000C, 0x0 } }, + { .hsw = { 0x00FFFFFF, 0x0005000F, 0x0 } }, + { .hsw = { 0x00DB6FFF, 0x000A000C, 0x0 } }, +}; + +static const struct intel_ddi_buf_trans bdw_trans_edp = { + .entries = _bdw_trans_edp, + .num_entries = ARRAY_SIZE(_bdw_trans_edp), +}; + +static const union intel_ddi_buf_trans_entry _bdw_trans_dp[] = { + { .hsw = { 0x00FFFFFF, 0x0007000E, 0x0 } }, + { .hsw = { 0x00D75FFF, 0x000E000A, 0x0 } }, + { .hsw = { 0x00BEFFFF, 0x00140006, 0x0 } }, + { .hsw = { 0x80B2CFFF, 0x001B0002, 0x0 } }, + { .hsw = { 0x00FFFFFF, 0x000E000A, 0x0 } }, + { .hsw = { 0x00DB6FFF, 0x00160005, 0x0 } }, + { .hsw = { 0x80C71FFF, 0x001A0002, 0x0 } }, + { .hsw = { 0x00F7DFFF, 0x00180004, 0x0 } }, + { .hsw = { 0x80D75FFF, 0x001B0002, 0x0 } }, +}; + +static const struct intel_ddi_buf_trans bdw_trans_dp = { + .entries = _bdw_trans_dp, + .num_entries = ARRAY_SIZE(_bdw_trans_dp), +}; + +static const union intel_ddi_buf_trans_entry _bdw_trans_fdi[] = { + { .hsw = { 0x00FFFFFF, 0x0001000E, 0x0 } }, + { .hsw = { 0x00D75FFF, 0x0004000A, 0x0 } }, + { .hsw = { 0x00C30FFF, 0x00070006, 0x0 } }, + { .hsw = { 0x00AAAFFF, 0x000C0000, 0x0 } }, + { .hsw = { 0x00FFFFFF, 0x0004000A, 0x0 } }, + { .hsw = { 0x00D75FFF, 0x00090004, 0x0 } }, + { .hsw = { 0x00C30FFF, 0x000C0000, 0x0 } }, + { .hsw = { 0x00FFFFFF, 0x00070006, 0x0 } }, + { .hsw = { 0x00D75FFF, 0x000C0000, 0x0 } }, +}; + +static const struct intel_ddi_buf_trans bdw_trans_fdi = { + .entries = _bdw_trans_fdi, + .num_entries = ARRAY_SIZE(_bdw_trans_fdi), +}; + +static const union intel_ddi_buf_trans_entry _bdw_trans_hdmi[] = { + /* Idx NT mV d T mV df db */ + { .hsw = { 0x00FFFFFF, 0x0007000E, 0x0 } }, /* 0: 400 400 0 */ + { .hsw = { 0x00D75FFF, 0x000E000A, 0x0 } }, /* 1: 400 600 3.5 */ + { .hsw = { 0x00BEFFFF, 0x00140006, 0x0 } }, /* 2: 400 800 6 */ + { .hsw = { 0x00FFFFFF, 0x0009000D, 0x0 } }, /* 3: 450 450 0 */ + { .hsw = { 0x00FFFFFF, 0x000E000A, 0x0 } }, /* 4: 600 600 0 */ + { .hsw = { 0x00D7FFFF, 0x00140006, 0x0 } }, /* 5: 600 800 2.5 */ + { .hsw = { 0x80CB2FFF, 0x001B0002, 0x0 } }, /* 6: 600 1000 4.5 */ + { .hsw = { 0x00FFFFFF, 0x00140006, 0x0 } }, /* 7: 800 800 0 */ + { .hsw = { 0x80E79FFF, 0x001B0002, 0x0 } }, /* 8: 800 1000 2 */ + { .hsw = { 0x80FFFFFF, 0x001B0002, 0x0 } }, /* 9: 1000 1000 0 */ +}; + +static const struct intel_ddi_buf_trans bdw_trans_hdmi = { + .entries = _bdw_trans_hdmi, + .num_entries = ARRAY_SIZE(_bdw_trans_hdmi), + .hdmi_default_entry = 7, +}; + +/* Skylake H and S */ +static const union intel_ddi_buf_trans_entry _skl_trans_dp[] = { + { .hsw = { 0x00002016, 0x000000A0, 0x0 } }, + { .hsw = { 0x00005012, 0x0000009B, 0x0 } }, + { .hsw = { 0x00007011, 0x00000088, 0x0 } }, + { .hsw = { 0x80009010, 0x000000C0, 0x1 } }, + { .hsw = { 0x00002016, 0x0000009B, 0x0 } }, + { .hsw = { 0x00005012, 0x00000088, 0x0 } }, + { .hsw = { 0x80007011, 0x000000C0, 0x1 } }, + { .hsw = { 0x00002016, 0x000000DF, 0x0 } }, + { .hsw = { 0x80005012, 0x000000C0, 0x1 } }, +}; + +static const struct intel_ddi_buf_trans skl_trans_dp = { + .entries = _skl_trans_dp, + .num_entries = ARRAY_SIZE(_skl_trans_dp), +}; + +/* Skylake U */ +static const union intel_ddi_buf_trans_entry _skl_u_trans_dp[] = { + { .hsw = { 0x0000201B, 0x000000A2, 0x0 } }, + { .hsw = { 0x00005012, 0x00000088, 0x0 } }, + { .hsw = { 0x80007011, 0x000000CD, 0x1 } }, + { .hsw = { 0x80009010, 0x000000C0, 0x1 } }, + { .hsw = { 0x0000201B, 0x0000009D, 0x0 } }, + { .hsw = { 0x80005012, 0x000000C0, 0x1 } }, + { .hsw = { 0x80007011, 0x000000C0, 0x1 } }, + { .hsw = { 0x00002016, 0x00000088, 0x0 } }, + { .hsw = { 0x80005012, 0x000000C0, 0x1 } }, +}; + +static const struct intel_ddi_buf_trans skl_u_trans_dp = { + .entries = _skl_u_trans_dp, + .num_entries = ARRAY_SIZE(_skl_u_trans_dp), +}; + +/* Skylake Y */ +static const union intel_ddi_buf_trans_entry _skl_y_trans_dp[] = { + { .hsw = { 0x00000018, 0x000000A2, 0x0 } }, + { .hsw = { 0x00005012, 0x00000088, 0x0 } }, + { .hsw = { 0x80007011, 0x000000CD, 0x3 } }, + { .hsw = { 0x80009010, 0x000000C0, 0x3 } }, + { .hsw = { 0x00000018, 0x0000009D, 0x0 } }, + { .hsw = { 0x80005012, 0x000000C0, 0x3 } }, + { .hsw = { 0x80007011, 0x000000C0, 0x3 } }, + { .hsw = { 0x00000018, 0x00000088, 0x0 } }, + { .hsw = { 0x80005012, 0x000000C0, 0x3 } }, +}; + +static const struct intel_ddi_buf_trans skl_y_trans_dp = { + .entries = _skl_y_trans_dp, + .num_entries = ARRAY_SIZE(_skl_y_trans_dp), +}; + +/* Kabylake H and S */ +static const union intel_ddi_buf_trans_entry _kbl_trans_dp[] = { + { .hsw = { 0x00002016, 0x000000A0, 0x0 } }, + { .hsw = { 0x00005012, 0x0000009B, 0x0 } }, + { .hsw = { 0x00007011, 0x00000088, 0x0 } }, + { .hsw = { 0x80009010, 0x000000C0, 0x1 } }, + { .hsw = { 0x00002016, 0x0000009B, 0x0 } }, + { .hsw = { 0x00005012, 0x00000088, 0x0 } }, + { .hsw = { 0x80007011, 0x000000C0, 0x1 } }, + { .hsw = { 0x00002016, 0x00000097, 0x0 } }, + { .hsw = { 0x80005012, 0x000000C0, 0x1 } }, +}; + +static const struct intel_ddi_buf_trans kbl_trans_dp = { + .entries = _kbl_trans_dp, + .num_entries = ARRAY_SIZE(_kbl_trans_dp), +}; + +/* Kabylake U */ +static const union intel_ddi_buf_trans_entry _kbl_u_trans_dp[] = { + { .hsw = { 0x0000201B, 0x000000A1, 0x0 } }, + { .hsw = { 0x00005012, 0x00000088, 0x0 } }, + { .hsw = { 0x80007011, 0x000000CD, 0x3 } }, + { .hsw = { 0x80009010, 0x000000C0, 0x3 } }, + { .hsw = { 0x0000201B, 0x0000009D, 0x0 } }, + { .hsw = { 0x80005012, 0x000000C0, 0x3 } }, + { .hsw = { 0x80007011, 0x000000C0, 0x3 } }, + { .hsw = { 0x00002016, 0x0000004F, 0x0 } }, + { .hsw = { 0x80005012, 0x000000C0, 0x3 } }, +}; + +static const struct intel_ddi_buf_trans kbl_u_trans_dp = { + .entries = _kbl_u_trans_dp, + .num_entries = ARRAY_SIZE(_kbl_u_trans_dp), +}; + +/* Kabylake Y */ +static const union intel_ddi_buf_trans_entry _kbl_y_trans_dp[] = { + { .hsw = { 0x00001017, 0x000000A1, 0x0 } }, + { .hsw = { 0x00005012, 0x00000088, 0x0 } }, + { .hsw = { 0x80007011, 0x000000CD, 0x3 } }, + { .hsw = { 0x8000800F, 0x000000C0, 0x3 } }, + { .hsw = { 0x00001017, 0x0000009D, 0x0 } }, + { .hsw = { 0x80005012, 0x000000C0, 0x3 } }, + { .hsw = { 0x80007011, 0x000000C0, 0x3 } }, + { .hsw = { 0x00001017, 0x0000004C, 0x0 } }, + { .hsw = { 0x80005012, 0x000000C0, 0x3 } }, +}; + +static const struct intel_ddi_buf_trans kbl_y_trans_dp = { + .entries = _kbl_y_trans_dp, + .num_entries = ARRAY_SIZE(_kbl_y_trans_dp), +}; + +/* + * Skylake/Kabylake H and S + * eDP 1.4 low vswing translation parameters + */ +static const union intel_ddi_buf_trans_entry _skl_trans_edp[] = { + { .hsw = { 0x00000018, 0x000000A8, 0x0 } }, + { .hsw = { 0x00004013, 0x000000A9, 0x0 } }, + { .hsw = { 0x00007011, 0x000000A2, 0x0 } }, + { .hsw = { 0x00009010, 0x0000009C, 0x0 } }, + { .hsw = { 0x00000018, 0x000000A9, 0x0 } }, + { .hsw = { 0x00006013, 0x000000A2, 0x0 } }, + { .hsw = { 0x00007011, 0x000000A6, 0x0 } }, + { .hsw = { 0x00000018, 0x000000AB, 0x0 } }, + { .hsw = { 0x00007013, 0x0000009F, 0x0 } }, + { .hsw = { 0x00000018, 0x000000DF, 0x0 } }, +}; + +static const struct intel_ddi_buf_trans skl_trans_edp = { + .entries = _skl_trans_edp, + .num_entries = ARRAY_SIZE(_skl_trans_edp), +}; + +/* + * Skylake/Kabylake U + * eDP 1.4 low vswing translation parameters + */ +static const union intel_ddi_buf_trans_entry _skl_u_trans_edp[] = { + { .hsw = { 0x00000018, 0x000000A8, 0x0 } }, + { .hsw = { 0x00004013, 0x000000A9, 0x0 } }, + { .hsw = { 0x00007011, 0x000000A2, 0x0 } }, + { .hsw = { 0x00009010, 0x0000009C, 0x0 } }, + { .hsw = { 0x00000018, 0x000000A9, 0x0 } }, + { .hsw = { 0x00006013, 0x000000A2, 0x0 } }, + { .hsw = { 0x00007011, 0x000000A6, 0x0 } }, + { .hsw = { 0x00002016, 0x000000AB, 0x0 } }, + { .hsw = { 0x00005013, 0x0000009F, 0x0 } }, + { .hsw = { 0x00000018, 0x000000DF, 0x0 } }, +}; + +static const struct intel_ddi_buf_trans skl_u_trans_edp = { + .entries = _skl_u_trans_edp, + .num_entries = ARRAY_SIZE(_skl_u_trans_edp), +}; + +/* + * Skylake/Kabylake Y + * eDP 1.4 low vswing translation parameters + */ +static const union intel_ddi_buf_trans_entry _skl_y_trans_edp[] = { + { .hsw = { 0x00000018, 0x000000A8, 0x0 } }, + { .hsw = { 0x00004013, 0x000000AB, 0x0 } }, + { .hsw = { 0x00007011, 0x000000A4, 0x0 } }, + { .hsw = { 0x00009010, 0x000000DF, 0x0 } }, + { .hsw = { 0x00000018, 0x000000AA, 0x0 } }, + { .hsw = { 0x00006013, 0x000000A4, 0x0 } }, + { .hsw = { 0x00007011, 0x0000009D, 0x0 } }, + { .hsw = { 0x00000018, 0x000000A0, 0x0 } }, + { .hsw = { 0x00006012, 0x000000DF, 0x0 } }, + { .hsw = { 0x00000018, 0x0000008A, 0x0 } }, +}; + +static const struct intel_ddi_buf_trans skl_y_trans_edp = { + .entries = _skl_y_trans_edp, + .num_entries = ARRAY_SIZE(_skl_y_trans_edp), +}; + +/* Skylake/Kabylake U, H and S */ +static const union intel_ddi_buf_trans_entry _skl_trans_hdmi[] = { + { .hsw = { 0x00000018, 0x000000AC, 0x0 } }, + { .hsw = { 0x00005012, 0x0000009D, 0x0 } }, + { .hsw = { 0x00007011, 0x00000088, 0x0 } }, + { .hsw = { 0x00000018, 0x000000A1, 0x0 } }, + { .hsw = { 0x00000018, 0x00000098, 0x0 } }, + { .hsw = { 0x00004013, 0x00000088, 0x0 } }, + { .hsw = { 0x80006012, 0x000000CD, 0x1 } }, + { .hsw = { 0x00000018, 0x000000DF, 0x0 } }, + { .hsw = { 0x80003015, 0x000000CD, 0x1 } }, /* Default */ + { .hsw = { 0x80003015, 0x000000C0, 0x1 } }, + { .hsw = { 0x80000018, 0x000000C0, 0x1 } }, +}; + +static const struct intel_ddi_buf_trans skl_trans_hdmi = { + .entries = _skl_trans_hdmi, + .num_entries = ARRAY_SIZE(_skl_trans_hdmi), + .hdmi_default_entry = 8, +}; + +/* Skylake/Kabylake Y */ +static const union intel_ddi_buf_trans_entry _skl_y_trans_hdmi[] = { + { .hsw = { 0x00000018, 0x000000A1, 0x0 } }, + { .hsw = { 0x00005012, 0x000000DF, 0x0 } }, + { .hsw = { 0x80007011, 0x000000CB, 0x3 } }, + { .hsw = { 0x00000018, 0x000000A4, 0x0 } }, + { .hsw = { 0x00000018, 0x0000009D, 0x0 } }, + { .hsw = { 0x00004013, 0x00000080, 0x0 } }, + { .hsw = { 0x80006013, 0x000000C0, 0x3 } }, + { .hsw = { 0x00000018, 0x0000008A, 0x0 } }, + { .hsw = { 0x80003015, 0x000000C0, 0x3 } }, /* Default */ + { .hsw = { 0x80003015, 0x000000C0, 0x3 } }, + { .hsw = { 0x80000018, 0x000000C0, 0x3 } }, +}; + +static const struct intel_ddi_buf_trans skl_y_trans_hdmi = { + .entries = _skl_y_trans_hdmi, + .num_entries = ARRAY_SIZE(_skl_y_trans_hdmi), + .hdmi_default_entry = 8, +}; + +static const union intel_ddi_buf_trans_entry _bxt_trans_dp[] = { + /* Idx NT mV diff db */ + { .bxt = { 52, 0x9A, 0, 128, } }, /* 0: 400 0 */ + { .bxt = { 78, 0x9A, 0, 85, } }, /* 1: 400 3.5 */ + { .bxt = { 104, 0x9A, 0, 64, } }, /* 2: 400 6 */ + { .bxt = { 154, 0x9A, 0, 43, } }, /* 3: 400 9.5 */ + { .bxt = { 77, 0x9A, 0, 128, } }, /* 4: 600 0 */ + { .bxt = { 116, 0x9A, 0, 85, } }, /* 5: 600 3.5 */ + { .bxt = { 154, 0x9A, 0, 64, } }, /* 6: 600 6 */ + { .bxt = { 102, 0x9A, 0, 128, } }, /* 7: 800 0 */ + { .bxt = { 154, 0x9A, 0, 85, } }, /* 8: 800 3.5 */ + { .bxt = { 154, 0x9A, 1, 128, } }, /* 9: 1200 0 */ +}; + +static const struct intel_ddi_buf_trans bxt_trans_dp = { + .entries = _bxt_trans_dp, + .num_entries = ARRAY_SIZE(_bxt_trans_dp), +}; + +static const union intel_ddi_buf_trans_entry _bxt_trans_edp[] = { + /* Idx NT mV diff db */ + { .bxt = { 26, 0, 0, 128, } }, /* 0: 200 0 */ + { .bxt = { 38, 0, 0, 112, } }, /* 1: 200 1.5 */ + { .bxt = { 48, 0, 0, 96, } }, /* 2: 200 4 */ + { .bxt = { 54, 0, 0, 69, } }, /* 3: 200 6 */ + { .bxt = { 32, 0, 0, 128, } }, /* 4: 250 0 */ + { .bxt = { 48, 0, 0, 104, } }, /* 5: 250 1.5 */ + { .bxt = { 54, 0, 0, 85, } }, /* 6: 250 4 */ + { .bxt = { 43, 0, 0, 128, } }, /* 7: 300 0 */ + { .bxt = { 54, 0, 0, 101, } }, /* 8: 300 1.5 */ + { .bxt = { 48, 0, 0, 128, } }, /* 9: 300 0 */ +}; + +static const struct intel_ddi_buf_trans bxt_trans_edp = { + .entries = _bxt_trans_edp, + .num_entries = ARRAY_SIZE(_bxt_trans_edp), +}; + +/* BSpec has 2 recommended values - entries 0 and 8. + * Using the entry with higher vswing. + */ +static const union intel_ddi_buf_trans_entry _bxt_trans_hdmi[] = { + /* Idx NT mV diff db */ + { .bxt = { 52, 0x9A, 0, 128, } }, /* 0: 400 0 */ + { .bxt = { 52, 0x9A, 0, 85, } }, /* 1: 400 3.5 */ + { .bxt = { 52, 0x9A, 0, 64, } }, /* 2: 400 6 */ + { .bxt = { 42, 0x9A, 0, 43, } }, /* 3: 400 9.5 */ + { .bxt = { 77, 0x9A, 0, 128, } }, /* 4: 600 0 */ + { .bxt = { 77, 0x9A, 0, 85, } }, /* 5: 600 3.5 */ + { .bxt = { 77, 0x9A, 0, 64, } }, /* 6: 600 6 */ + { .bxt = { 102, 0x9A, 0, 128, } }, /* 7: 800 0 */ + { .bxt = { 102, 0x9A, 0, 85, } }, /* 8: 800 3.5 */ + { .bxt = { 154, 0x9A, 1, 128, } }, /* 9: 1200 0 */ +}; + +static const struct intel_ddi_buf_trans bxt_trans_hdmi = { + .entries = _bxt_trans_hdmi, + .num_entries = ARRAY_SIZE(_bxt_trans_hdmi), + .hdmi_default_entry = ARRAY_SIZE(_bxt_trans_hdmi) - 1, +}; + +/* icl_combo_phy_trans */ +static const union intel_ddi_buf_trans_entry _icl_combo_phy_trans_dp_hbr2_edp_hbr3[] = { + /* NT mV Trans mV db */ + { .icl = { 0xA, 0x35, 0x3F, 0x00, 0x00 } }, /* 350 350 0.0 */ + { .icl = { 0xA, 0x4F, 0x37, 0x00, 0x08 } }, /* 350 500 3.1 */ + { .icl = { 0xC, 0x71, 0x2F, 0x00, 0x10 } }, /* 350 700 6.0 */ + { .icl = { 0x6, 0x7F, 0x2B, 0x00, 0x14 } }, /* 350 900 8.2 */ + { .icl = { 0xA, 0x4C, 0x3F, 0x00, 0x00 } }, /* 500 500 0.0 */ + { .icl = { 0xC, 0x73, 0x34, 0x00, 0x0B } }, /* 500 700 2.9 */ + { .icl = { 0x6, 0x7F, 0x2F, 0x00, 0x10 } }, /* 500 900 5.1 */ + { .icl = { 0xC, 0x6C, 0x3C, 0x00, 0x03 } }, /* 650 700 0.6 */ + { .icl = { 0x6, 0x7F, 0x35, 0x00, 0x0A } }, /* 600 900 3.5 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 900 900 0.0 */ +}; + +static const struct intel_ddi_buf_trans icl_combo_phy_trans_dp_hbr2_edp_hbr3 = { + .entries = _icl_combo_phy_trans_dp_hbr2_edp_hbr3, + .num_entries = ARRAY_SIZE(_icl_combo_phy_trans_dp_hbr2_edp_hbr3), +}; + +static const union intel_ddi_buf_trans_entry _icl_combo_phy_trans_edp_hbr2[] = { + /* NT mV Trans mV db */ + { .icl = { 0x0, 0x7F, 0x3F, 0x00, 0x00 } }, /* 200 200 0.0 */ + { .icl = { 0x8, 0x7F, 0x38, 0x00, 0x07 } }, /* 200 250 1.9 */ + { .icl = { 0x1, 0x7F, 0x33, 0x00, 0x0C } }, /* 200 300 3.5 */ + { .icl = { 0x9, 0x7F, 0x31, 0x00, 0x0E } }, /* 200 350 4.9 */ + { .icl = { 0x8, 0x7F, 0x3F, 0x00, 0x00 } }, /* 250 250 0.0 */ + { .icl = { 0x1, 0x7F, 0x38, 0x00, 0x07 } }, /* 250 300 1.6 */ + { .icl = { 0x9, 0x7F, 0x35, 0x00, 0x0A } }, /* 250 350 2.9 */ + { .icl = { 0x1, 0x7F, 0x3F, 0x00, 0x00 } }, /* 300 300 0.0 */ + { .icl = { 0x9, 0x7F, 0x38, 0x00, 0x07 } }, /* 300 350 1.3 */ + { .icl = { 0x9, 0x7F, 0x3F, 0x00, 0x00 } }, /* 350 350 0.0 */ +}; + +static const struct intel_ddi_buf_trans icl_combo_phy_trans_edp_hbr2 = { + .entries = _icl_combo_phy_trans_edp_hbr2, + .num_entries = ARRAY_SIZE(_icl_combo_phy_trans_edp_hbr2), +}; + +static const union intel_ddi_buf_trans_entry _icl_combo_phy_trans_hdmi[] = { + /* NT mV Trans mV db */ + { .icl = { 0xA, 0x60, 0x3F, 0x00, 0x00 } }, /* 450 450 0.0 */ + { .icl = { 0xB, 0x73, 0x36, 0x00, 0x09 } }, /* 450 650 3.2 */ + { .icl = { 0x6, 0x7F, 0x31, 0x00, 0x0E } }, /* 450 850 5.5 */ + { .icl = { 0xB, 0x73, 0x3F, 0x00, 0x00 } }, /* 650 650 0.0 ALS */ + { .icl = { 0x6, 0x7F, 0x37, 0x00, 0x08 } }, /* 650 850 2.3 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 850 850 0.0 */ + { .icl = { 0x6, 0x7F, 0x35, 0x00, 0x0A } }, /* 600 850 3.0 */ +}; + +static const struct intel_ddi_buf_trans icl_combo_phy_trans_hdmi = { + .entries = _icl_combo_phy_trans_hdmi, + .num_entries = ARRAY_SIZE(_icl_combo_phy_trans_hdmi), + .hdmi_default_entry = ARRAY_SIZE(_icl_combo_phy_trans_hdmi) - 1, +}; + +static const union intel_ddi_buf_trans_entry _ehl_combo_phy_trans_dp[] = { + /* NT mV Trans mV db */ + { .icl = { 0xA, 0x33, 0x3F, 0x00, 0x00 } }, /* 350 350 0.0 */ + { .icl = { 0xA, 0x47, 0x38, 0x00, 0x07 } }, /* 350 500 3.1 */ + { .icl = { 0xC, 0x64, 0x33, 0x00, 0x0C } }, /* 350 700 6.0 */ + { .icl = { 0x6, 0x7F, 0x2F, 0x00, 0x10 } }, /* 350 900 8.2 */ + { .icl = { 0xA, 0x46, 0x3F, 0x00, 0x00 } }, /* 500 500 0.0 */ + { .icl = { 0xC, 0x64, 0x37, 0x00, 0x08 } }, /* 500 700 2.9 */ + { .icl = { 0x6, 0x7F, 0x32, 0x00, 0x0D } }, /* 500 900 5.1 */ + { .icl = { 0xC, 0x61, 0x3F, 0x00, 0x00 } }, /* 650 700 0.6 */ + { .icl = { 0x6, 0x7F, 0x37, 0x00, 0x08 } }, /* 600 900 3.5 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 900 900 0.0 */ +}; + +static const struct intel_ddi_buf_trans ehl_combo_phy_trans_dp = { + .entries = _ehl_combo_phy_trans_dp, + .num_entries = ARRAY_SIZE(_ehl_combo_phy_trans_dp), +}; + +static const union intel_ddi_buf_trans_entry _ehl_combo_phy_trans_edp_hbr2[] = { + /* NT mV Trans mV db */ + { .icl = { 0x8, 0x7F, 0x3F, 0x00, 0x00 } }, /* 200 200 0.0 */ + { .icl = { 0x8, 0x7F, 0x3F, 0x00, 0x00 } }, /* 200 250 1.9 */ + { .icl = { 0x1, 0x7F, 0x3D, 0x00, 0x02 } }, /* 200 300 3.5 */ + { .icl = { 0xA, 0x35, 0x39, 0x00, 0x06 } }, /* 200 350 4.9 */ + { .icl = { 0x8, 0x7F, 0x3F, 0x00, 0x00 } }, /* 250 250 0.0 */ + { .icl = { 0x1, 0x7F, 0x3C, 0x00, 0x03 } }, /* 250 300 1.6 */ + { .icl = { 0xA, 0x35, 0x39, 0x00, 0x06 } }, /* 250 350 2.9 */ + { .icl = { 0x1, 0x7F, 0x3F, 0x00, 0x00 } }, /* 300 300 0.0 */ + { .icl = { 0xA, 0x35, 0x38, 0x00, 0x07 } }, /* 300 350 1.3 */ + { .icl = { 0xA, 0x35, 0x3F, 0x00, 0x00 } }, /* 350 350 0.0 */ +}; + +static const struct intel_ddi_buf_trans ehl_combo_phy_trans_edp_hbr2 = { + .entries = _ehl_combo_phy_trans_edp_hbr2, + .num_entries = ARRAY_SIZE(_ehl_combo_phy_trans_edp_hbr2), +}; + +static const union intel_ddi_buf_trans_entry _jsl_combo_phy_trans_edp_hbr[] = { + /* NT mV Trans mV db */ + { .icl = { 0x8, 0x7F, 0x3F, 0x00, 0x00 } }, /* 200 200 0.0 */ + { .icl = { 0x8, 0x7F, 0x38, 0x00, 0x07 } }, /* 200 250 1.9 */ + { .icl = { 0x1, 0x7F, 0x33, 0x00, 0x0C } }, /* 200 300 3.5 */ + { .icl = { 0xA, 0x35, 0x36, 0x00, 0x09 } }, /* 200 350 4.9 */ + { .icl = { 0x8, 0x7F, 0x3F, 0x00, 0x00 } }, /* 250 250 0.0 */ + { .icl = { 0x1, 0x7F, 0x38, 0x00, 0x07 } }, /* 250 300 1.6 */ + { .icl = { 0xA, 0x35, 0x35, 0x00, 0x0A } }, /* 250 350 2.9 */ + { .icl = { 0x1, 0x7F, 0x3F, 0x00, 0x00 } }, /* 300 300 0.0 */ + { .icl = { 0xA, 0x35, 0x38, 0x00, 0x07 } }, /* 300 350 1.3 */ + { .icl = { 0xA, 0x35, 0x3F, 0x00, 0x00 } }, /* 350 350 0.0 */ +}; + +static const struct intel_ddi_buf_trans jsl_combo_phy_trans_edp_hbr = { + .entries = _jsl_combo_phy_trans_edp_hbr, + .num_entries = ARRAY_SIZE(_jsl_combo_phy_trans_edp_hbr), +}; + +static const union intel_ddi_buf_trans_entry _jsl_combo_phy_trans_edp_hbr2[] = { + /* NT mV Trans mV db */ + { .icl = { 0x8, 0x7F, 0x3F, 0x00, 0x00 } }, /* 200 200 0.0 */ + { .icl = { 0x8, 0x7F, 0x3F, 0x00, 0x00 } }, /* 200 250 1.9 */ + { .icl = { 0x1, 0x7F, 0x3D, 0x00, 0x02 } }, /* 200 300 3.5 */ + { .icl = { 0xA, 0x35, 0x38, 0x00, 0x07 } }, /* 200 350 4.9 */ + { .icl = { 0x8, 0x7F, 0x3F, 0x00, 0x00 } }, /* 250 250 0.0 */ + { .icl = { 0x1, 0x7F, 0x3F, 0x00, 0x00 } }, /* 250 300 1.6 */ + { .icl = { 0xA, 0x35, 0x3A, 0x00, 0x05 } }, /* 250 350 2.9 */ + { .icl = { 0x1, 0x7F, 0x3F, 0x00, 0x00 } }, /* 300 300 0.0 */ + { .icl = { 0xA, 0x35, 0x38, 0x00, 0x07 } }, /* 300 350 1.3 */ + { .icl = { 0xA, 0x35, 0x3F, 0x00, 0x00 } }, /* 350 350 0.0 */ +}; + +static const struct intel_ddi_buf_trans jsl_combo_phy_trans_edp_hbr2 = { + .entries = _jsl_combo_phy_trans_edp_hbr2, + .num_entries = ARRAY_SIZE(_jsl_combo_phy_trans_edp_hbr2), +}; + +static const union intel_ddi_buf_trans_entry _dg1_combo_phy_trans_dp_rbr_hbr[] = { + /* NT mV Trans mV db */ + { .icl = { 0xA, 0x32, 0x3F, 0x00, 0x00 } }, /* 350 350 0.0 */ + { .icl = { 0xA, 0x48, 0x35, 0x00, 0x0A } }, /* 350 500 3.1 */ + { .icl = { 0xC, 0x63, 0x2F, 0x00, 0x10 } }, /* 350 700 6.0 */ + { .icl = { 0x6, 0x7F, 0x2C, 0x00, 0x13 } }, /* 350 900 8.2 */ + { .icl = { 0xA, 0x43, 0x3F, 0x00, 0x00 } }, /* 500 500 0.0 */ + { .icl = { 0xC, 0x60, 0x36, 0x00, 0x09 } }, /* 500 700 2.9 */ + { .icl = { 0x6, 0x7F, 0x30, 0x00, 0x0F } }, /* 500 900 5.1 */ + { .icl = { 0xC, 0x60, 0x3F, 0x00, 0x00 } }, /* 650 700 0.6 */ + { .icl = { 0x6, 0x7F, 0x37, 0x00, 0x08 } }, /* 600 900 3.5 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 900 900 0.0 */ +}; + +static const struct intel_ddi_buf_trans dg1_combo_phy_trans_dp_rbr_hbr = { + .entries = _dg1_combo_phy_trans_dp_rbr_hbr, + .num_entries = ARRAY_SIZE(_dg1_combo_phy_trans_dp_rbr_hbr), +}; + +static const union intel_ddi_buf_trans_entry _dg1_combo_phy_trans_dp_hbr2_hbr3[] = { + /* NT mV Trans mV db */ + { .icl = { 0xA, 0x32, 0x3F, 0x00, 0x00 } }, /* 350 350 0.0 */ + { .icl = { 0xA, 0x48, 0x35, 0x00, 0x0A } }, /* 350 500 3.1 */ + { .icl = { 0xC, 0x63, 0x2F, 0x00, 0x10 } }, /* 350 700 6.0 */ + { .icl = { 0x6, 0x7F, 0x2C, 0x00, 0x13 } }, /* 350 900 8.2 */ + { .icl = { 0xA, 0x43, 0x3F, 0x00, 0x00 } }, /* 500 500 0.0 */ + { .icl = { 0xC, 0x60, 0x36, 0x00, 0x09 } }, /* 500 700 2.9 */ + { .icl = { 0x6, 0x7F, 0x30, 0x00, 0x0F } }, /* 500 900 5.1 */ + { .icl = { 0xC, 0x58, 0x3F, 0x00, 0x00 } }, /* 650 700 0.6 */ + { .icl = { 0x6, 0x7F, 0x35, 0x00, 0x0A } }, /* 600 900 3.5 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 900 900 0.0 */ +}; + +static const struct intel_ddi_buf_trans dg1_combo_phy_trans_dp_hbr2_hbr3 = { + .entries = _dg1_combo_phy_trans_dp_hbr2_hbr3, + .num_entries = ARRAY_SIZE(_dg1_combo_phy_trans_dp_hbr2_hbr3), +}; + +static const union intel_ddi_buf_trans_entry _icl_mg_phy_trans_rbr_hbr[] = { + /* Voltage swing pre-emphasis */ + { .mg = { 0x18, 0x00, 0x00 } }, /* 0 0 */ + { .mg = { 0x1D, 0x00, 0x05 } }, /* 0 1 */ + { .mg = { 0x24, 0x00, 0x0C } }, /* 0 2 */ + { .mg = { 0x2B, 0x00, 0x14 } }, /* 0 3 */ + { .mg = { 0x21, 0x00, 0x00 } }, /* 1 0 */ + { .mg = { 0x2B, 0x00, 0x08 } }, /* 1 1 */ + { .mg = { 0x30, 0x00, 0x0F } }, /* 1 2 */ + { .mg = { 0x31, 0x00, 0x03 } }, /* 2 0 */ + { .mg = { 0x34, 0x00, 0x0B } }, /* 2 1 */ + { .mg = { 0x3F, 0x00, 0x00 } }, /* 3 0 */ +}; + +static const struct intel_ddi_buf_trans icl_mg_phy_trans_rbr_hbr = { + .entries = _icl_mg_phy_trans_rbr_hbr, + .num_entries = ARRAY_SIZE(_icl_mg_phy_trans_rbr_hbr), +}; + +static const union intel_ddi_buf_trans_entry _icl_mg_phy_trans_hbr2_hbr3[] = { + /* Voltage swing pre-emphasis */ + { .mg = { 0x18, 0x00, 0x00 } }, /* 0 0 */ + { .mg = { 0x1D, 0x00, 0x05 } }, /* 0 1 */ + { .mg = { 0x24, 0x00, 0x0C } }, /* 0 2 */ + { .mg = { 0x2B, 0x00, 0x14 } }, /* 0 3 */ + { .mg = { 0x26, 0x00, 0x00 } }, /* 1 0 */ + { .mg = { 0x2C, 0x00, 0x07 } }, /* 1 1 */ + { .mg = { 0x33, 0x00, 0x0C } }, /* 1 2 */ + { .mg = { 0x2E, 0x00, 0x00 } }, /* 2 0 */ + { .mg = { 0x36, 0x00, 0x09 } }, /* 2 1 */ + { .mg = { 0x3F, 0x00, 0x00 } }, /* 3 0 */ +}; + +static const struct intel_ddi_buf_trans icl_mg_phy_trans_hbr2_hbr3 = { + .entries = _icl_mg_phy_trans_hbr2_hbr3, + .num_entries = ARRAY_SIZE(_icl_mg_phy_trans_hbr2_hbr3), +}; + +static const union intel_ddi_buf_trans_entry _icl_mg_phy_trans_hdmi[] = { + /* HDMI Preset VS Pre-emph */ + { .mg = { 0x1A, 0x0, 0x0 } }, /* 1 400mV 0dB */ + { .mg = { 0x20, 0x0, 0x0 } }, /* 2 500mV 0dB */ + { .mg = { 0x29, 0x0, 0x0 } }, /* 3 650mV 0dB */ + { .mg = { 0x32, 0x0, 0x0 } }, /* 4 800mV 0dB */ + { .mg = { 0x3F, 0x0, 0x0 } }, /* 5 1000mV 0dB */ + { .mg = { 0x3A, 0x0, 0x5 } }, /* 6 Full -1.5 dB */ + { .mg = { 0x39, 0x0, 0x6 } }, /* 7 Full -1.8 dB */ + { .mg = { 0x38, 0x0, 0x7 } }, /* 8 Full -2 dB */ + { .mg = { 0x37, 0x0, 0x8 } }, /* 9 Full -2.5 dB */ + { .mg = { 0x36, 0x0, 0x9 } }, /* 10 Full -3 dB */ +}; + +static const struct intel_ddi_buf_trans icl_mg_phy_trans_hdmi = { + .entries = _icl_mg_phy_trans_hdmi, + .num_entries = ARRAY_SIZE(_icl_mg_phy_trans_hdmi), + .hdmi_default_entry = ARRAY_SIZE(_icl_mg_phy_trans_hdmi) - 1, +}; + +static const union intel_ddi_buf_trans_entry _tgl_dkl_phy_trans_dp_hbr[] = { + /* VS pre-emp Non-trans mV Pre-emph dB */ + { .dkl = { 0x7, 0x0, 0x00 } }, /* 0 0 400mV 0 dB */ + { .dkl = { 0x5, 0x0, 0x05 } }, /* 0 1 400mV 3.5 dB */ + { .dkl = { 0x2, 0x0, 0x0B } }, /* 0 2 400mV 6 dB */ + { .dkl = { 0x0, 0x0, 0x18 } }, /* 0 3 400mV 9.5 dB */ + { .dkl = { 0x5, 0x0, 0x00 } }, /* 1 0 600mV 0 dB */ + { .dkl = { 0x2, 0x0, 0x08 } }, /* 1 1 600mV 3.5 dB */ + { .dkl = { 0x0, 0x0, 0x14 } }, /* 1 2 600mV 6 dB */ + { .dkl = { 0x2, 0x0, 0x00 } }, /* 2 0 800mV 0 dB */ + { .dkl = { 0x0, 0x0, 0x0B } }, /* 2 1 800mV 3.5 dB */ + { .dkl = { 0x0, 0x0, 0x00 } }, /* 3 0 1200mV 0 dB HDMI default */ +}; + +static const struct intel_ddi_buf_trans tgl_dkl_phy_trans_dp_hbr = { + .entries = _tgl_dkl_phy_trans_dp_hbr, + .num_entries = ARRAY_SIZE(_tgl_dkl_phy_trans_dp_hbr), +}; + +static const union intel_ddi_buf_trans_entry _tgl_dkl_phy_trans_dp_hbr2[] = { + /* VS pre-emp Non-trans mV Pre-emph dB */ + { .dkl = { 0x7, 0x0, 0x00 } }, /* 0 0 400mV 0 dB */ + { .dkl = { 0x5, 0x0, 0x05 } }, /* 0 1 400mV 3.5 dB */ + { .dkl = { 0x2, 0x0, 0x0B } }, /* 0 2 400mV 6 dB */ + { .dkl = { 0x0, 0x0, 0x19 } }, /* 0 3 400mV 9.5 dB */ + { .dkl = { 0x5, 0x0, 0x00 } }, /* 1 0 600mV 0 dB */ + { .dkl = { 0x2, 0x0, 0x08 } }, /* 1 1 600mV 3.5 dB */ + { .dkl = { 0x0, 0x0, 0x14 } }, /* 1 2 600mV 6 dB */ + { .dkl = { 0x2, 0x0, 0x00 } }, /* 2 0 800mV 0 dB */ + { .dkl = { 0x0, 0x0, 0x0B } }, /* 2 1 800mV 3.5 dB */ + { .dkl = { 0x0, 0x0, 0x00 } }, /* 3 0 1200mV 0 dB HDMI default */ +}; + +static const struct intel_ddi_buf_trans tgl_dkl_phy_trans_dp_hbr2 = { + .entries = _tgl_dkl_phy_trans_dp_hbr2, + .num_entries = ARRAY_SIZE(_tgl_dkl_phy_trans_dp_hbr2), +}; + +static const union intel_ddi_buf_trans_entry _tgl_dkl_phy_trans_hdmi[] = { + /* HDMI Preset VS Pre-emph */ + { .dkl = { 0x7, 0x0, 0x0 } }, /* 1 400mV 0dB */ + { .dkl = { 0x6, 0x0, 0x0 } }, /* 2 500mV 0dB */ + { .dkl = { 0x4, 0x0, 0x0 } }, /* 3 650mV 0dB */ + { .dkl = { 0x2, 0x0, 0x0 } }, /* 4 800mV 0dB */ + { .dkl = { 0x0, 0x0, 0x0 } }, /* 5 1000mV 0dB */ + { .dkl = { 0x0, 0x0, 0x5 } }, /* 6 Full -1.5 dB */ + { .dkl = { 0x0, 0x0, 0x6 } }, /* 7 Full -1.8 dB */ + { .dkl = { 0x0, 0x0, 0x7 } }, /* 8 Full -2 dB */ + { .dkl = { 0x0, 0x0, 0x8 } }, /* 9 Full -2.5 dB */ + { .dkl = { 0x0, 0x0, 0xA } }, /* 10 Full -3 dB */ +}; + +static const struct intel_ddi_buf_trans tgl_dkl_phy_trans_hdmi = { + .entries = _tgl_dkl_phy_trans_hdmi, + .num_entries = ARRAY_SIZE(_tgl_dkl_phy_trans_hdmi), + .hdmi_default_entry = ARRAY_SIZE(_tgl_dkl_phy_trans_hdmi) - 1, +}; + +static const union intel_ddi_buf_trans_entry _tgl_combo_phy_trans_dp_hbr[] = { + /* NT mV Trans mV db */ + { .icl = { 0xA, 0x32, 0x3F, 0x00, 0x00 } }, /* 350 350 0.0 */ + { .icl = { 0xA, 0x4F, 0x37, 0x00, 0x08 } }, /* 350 500 3.1 */ + { .icl = { 0xC, 0x71, 0x2F, 0x00, 0x10 } }, /* 350 700 6.0 */ + { .icl = { 0x6, 0x7D, 0x2B, 0x00, 0x14 } }, /* 350 900 8.2 */ + { .icl = { 0xA, 0x4C, 0x3F, 0x00, 0x00 } }, /* 500 500 0.0 */ + { .icl = { 0xC, 0x73, 0x34, 0x00, 0x0B } }, /* 500 700 2.9 */ + { .icl = { 0x6, 0x7F, 0x2F, 0x00, 0x10 } }, /* 500 900 5.1 */ + { .icl = { 0xC, 0x6C, 0x3C, 0x00, 0x03 } }, /* 650 700 0.6 */ + { .icl = { 0x6, 0x7F, 0x35, 0x00, 0x0A } }, /* 600 900 3.5 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 900 900 0.0 */ +}; + +static const struct intel_ddi_buf_trans tgl_combo_phy_trans_dp_hbr = { + .entries = _tgl_combo_phy_trans_dp_hbr, + .num_entries = ARRAY_SIZE(_tgl_combo_phy_trans_dp_hbr), +}; + +static const union intel_ddi_buf_trans_entry _tgl_combo_phy_trans_dp_hbr2[] = { + /* NT mV Trans mV db */ + { .icl = { 0xA, 0x35, 0x3F, 0x00, 0x00 } }, /* 350 350 0.0 */ + { .icl = { 0xA, 0x4F, 0x37, 0x00, 0x08 } }, /* 350 500 3.1 */ + { .icl = { 0xC, 0x63, 0x2F, 0x00, 0x10 } }, /* 350 700 6.0 */ + { .icl = { 0x6, 0x7F, 0x2B, 0x00, 0x14 } }, /* 350 900 8.2 */ + { .icl = { 0xA, 0x47, 0x3F, 0x00, 0x00 } }, /* 500 500 0.0 */ + { .icl = { 0xC, 0x63, 0x34, 0x00, 0x0B } }, /* 500 700 2.9 */ + { .icl = { 0x6, 0x7F, 0x2F, 0x00, 0x10 } }, /* 500 900 5.1 */ + { .icl = { 0xC, 0x61, 0x3C, 0x00, 0x03 } }, /* 650 700 0.6 */ + { .icl = { 0x6, 0x7B, 0x35, 0x00, 0x0A } }, /* 600 900 3.5 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 900 900 0.0 */ +}; + +static const struct intel_ddi_buf_trans tgl_combo_phy_trans_dp_hbr2 = { + .entries = _tgl_combo_phy_trans_dp_hbr2, + .num_entries = ARRAY_SIZE(_tgl_combo_phy_trans_dp_hbr2), +}; + +static const union intel_ddi_buf_trans_entry _tgl_uy_combo_phy_trans_dp_hbr2[] = { + /* NT mV Trans mV db */ + { .icl = { 0xA, 0x35, 0x3F, 0x00, 0x00 } }, /* 350 350 0.0 */ + { .icl = { 0xA, 0x4F, 0x36, 0x00, 0x09 } }, /* 350 500 3.1 */ + { .icl = { 0xC, 0x60, 0x32, 0x00, 0x0D } }, /* 350 700 6.0 */ + { .icl = { 0xC, 0x7F, 0x2D, 0x00, 0x12 } }, /* 350 900 8.2 */ + { .icl = { 0xC, 0x47, 0x3F, 0x00, 0x00 } }, /* 500 500 0.0 */ + { .icl = { 0xC, 0x6F, 0x36, 0x00, 0x09 } }, /* 500 700 2.9 */ + { .icl = { 0x6, 0x7D, 0x32, 0x00, 0x0D } }, /* 500 900 5.1 */ + { .icl = { 0x6, 0x60, 0x3C, 0x00, 0x03 } }, /* 650 700 0.6 */ + { .icl = { 0x6, 0x7F, 0x34, 0x00, 0x0B } }, /* 600 900 3.5 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 900 900 0.0 */ +}; + +static const struct intel_ddi_buf_trans tgl_uy_combo_phy_trans_dp_hbr2 = { + .entries = _tgl_uy_combo_phy_trans_dp_hbr2, + .num_entries = ARRAY_SIZE(_tgl_uy_combo_phy_trans_dp_hbr2), +}; + +/* + * Cloned the HOBL entry to comply with the voltage and pre-emphasis entries + * that DisplayPort specification requires + */ +static const union intel_ddi_buf_trans_entry _tgl_combo_phy_trans_edp_hbr2_hobl[] = { + /* VS pre-emp */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 0 0 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 0 1 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 0 2 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 0 3 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 1 0 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 1 1 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 1 2 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 2 0 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 2 1 */ +}; + +static const struct intel_ddi_buf_trans tgl_combo_phy_trans_edp_hbr2_hobl = { + .entries = _tgl_combo_phy_trans_edp_hbr2_hobl, + .num_entries = ARRAY_SIZE(_tgl_combo_phy_trans_edp_hbr2_hobl), +}; + +static const union intel_ddi_buf_trans_entry _rkl_combo_phy_trans_dp_hbr[] = { + /* NT mV Trans mV db */ + { .icl = { 0xA, 0x2F, 0x3F, 0x00, 0x00 } }, /* 350 350 0.0 */ + { .icl = { 0xA, 0x4F, 0x37, 0x00, 0x08 } }, /* 350 500 3.1 */ + { .icl = { 0xC, 0x63, 0x2F, 0x00, 0x10 } }, /* 350 700 6.0 */ + { .icl = { 0x6, 0x7D, 0x2A, 0x00, 0x15 } }, /* 350 900 8.2 */ + { .icl = { 0xA, 0x4C, 0x3F, 0x00, 0x00 } }, /* 500 500 0.0 */ + { .icl = { 0xC, 0x73, 0x34, 0x00, 0x0B } }, /* 500 700 2.9 */ + { .icl = { 0x6, 0x7F, 0x2F, 0x00, 0x10 } }, /* 500 900 5.1 */ + { .icl = { 0xC, 0x6E, 0x3E, 0x00, 0x01 } }, /* 650 700 0.6 */ + { .icl = { 0x6, 0x7F, 0x35, 0x00, 0x0A } }, /* 600 900 3.5 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 900 900 0.0 */ +}; + +static const struct intel_ddi_buf_trans rkl_combo_phy_trans_dp_hbr = { + .entries = _rkl_combo_phy_trans_dp_hbr, + .num_entries = ARRAY_SIZE(_rkl_combo_phy_trans_dp_hbr), +}; + +static const union intel_ddi_buf_trans_entry _rkl_combo_phy_trans_dp_hbr2_hbr3[] = { + /* NT mV Trans mV db */ + { .icl = { 0xA, 0x35, 0x3F, 0x00, 0x00 } }, /* 350 350 0.0 */ + { .icl = { 0xA, 0x50, 0x38, 0x00, 0x07 } }, /* 350 500 3.1 */ + { .icl = { 0xC, 0x61, 0x33, 0x00, 0x0C } }, /* 350 700 6.0 */ + { .icl = { 0x6, 0x7F, 0x2E, 0x00, 0x11 } }, /* 350 900 8.2 */ + { .icl = { 0xA, 0x47, 0x3F, 0x00, 0x00 } }, /* 500 500 0.0 */ + { .icl = { 0xC, 0x5F, 0x38, 0x00, 0x07 } }, /* 500 700 2.9 */ + { .icl = { 0x6, 0x7F, 0x2F, 0x00, 0x10 } }, /* 500 900 5.1 */ + { .icl = { 0xC, 0x5F, 0x3F, 0x00, 0x00 } }, /* 650 700 0.6 */ + { .icl = { 0x6, 0x7E, 0x36, 0x00, 0x09 } }, /* 600 900 3.5 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 900 900 0.0 */ +}; + +static const struct intel_ddi_buf_trans rkl_combo_phy_trans_dp_hbr2_hbr3 = { + .entries = _rkl_combo_phy_trans_dp_hbr2_hbr3, + .num_entries = ARRAY_SIZE(_rkl_combo_phy_trans_dp_hbr2_hbr3), +}; + +static const union intel_ddi_buf_trans_entry _adls_combo_phy_trans_dp_hbr2_hbr3[] = { + /* NT mV Trans mV db */ + { .icl = { 0xA, 0x35, 0x3F, 0x00, 0x00 } }, /* 350 350 0.0 */ + { .icl = { 0xA, 0x4F, 0x37, 0x00, 0x08 } }, /* 350 500 3.1 */ + { .icl = { 0xC, 0x63, 0x31, 0x00, 0x0E } }, /* 350 700 6.0 */ + { .icl = { 0x6, 0x7F, 0x2C, 0x00, 0x13 } }, /* 350 900 8.2 */ + { .icl = { 0xA, 0x47, 0x3F, 0x00, 0x00 } }, /* 500 500 0.0 */ + { .icl = { 0xC, 0x63, 0x37, 0x00, 0x08 } }, /* 500 700 2.9 */ + { .icl = { 0x6, 0x73, 0x32, 0x00, 0x0D } }, /* 500 900 5.1 */ + { .icl = { 0xC, 0x58, 0x3F, 0x00, 0x00 } }, /* 650 700 0.6 */ + { .icl = { 0x6, 0x7F, 0x35, 0x00, 0x0A } }, /* 600 900 3.5 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 900 900 0.0 */ +}; + +static const struct intel_ddi_buf_trans adls_combo_phy_trans_dp_hbr2_hbr3 = { + .entries = _adls_combo_phy_trans_dp_hbr2_hbr3, + .num_entries = ARRAY_SIZE(_adls_combo_phy_trans_dp_hbr2_hbr3), +}; + +static const union intel_ddi_buf_trans_entry _adls_combo_phy_trans_edp_hbr2[] = { + /* NT mV Trans mV db */ + { .icl = { 0x9, 0x73, 0x3D, 0x00, 0x02 } }, /* 200 200 0.0 */ + { .icl = { 0x9, 0x7A, 0x3C, 0x00, 0x03 } }, /* 200 250 1.9 */ + { .icl = { 0x9, 0x7F, 0x3B, 0x00, 0x04 } }, /* 200 300 3.5 */ + { .icl = { 0x4, 0x6C, 0x33, 0x00, 0x0C } }, /* 200 350 4.9 */ + { .icl = { 0x2, 0x73, 0x3A, 0x00, 0x05 } }, /* 250 250 0.0 */ + { .icl = { 0x2, 0x7C, 0x38, 0x00, 0x07 } }, /* 250 300 1.6 */ + { .icl = { 0x4, 0x5A, 0x36, 0x00, 0x09 } }, /* 250 350 2.9 */ + { .icl = { 0x4, 0x57, 0x3D, 0x00, 0x02 } }, /* 300 300 0.0 */ + { .icl = { 0x4, 0x65, 0x38, 0x00, 0x07 } }, /* 300 350 1.3 */ + { .icl = { 0x4, 0x6C, 0x3A, 0x00, 0x05 } }, /* 350 350 0.0 */ +}; + +static const struct intel_ddi_buf_trans adls_combo_phy_trans_edp_hbr2 = { + .entries = _adls_combo_phy_trans_edp_hbr2, + .num_entries = ARRAY_SIZE(_adls_combo_phy_trans_edp_hbr2), +}; + +static const union intel_ddi_buf_trans_entry _adls_combo_phy_trans_edp_hbr3[] = { + /* NT mV Trans mV db */ + { .icl = { 0xA, 0x35, 0x3F, 0x00, 0x00 } }, /* 350 350 0.0 */ + { .icl = { 0xA, 0x4F, 0x37, 0x00, 0x08 } }, /* 350 500 3.1 */ + { .icl = { 0xC, 0x63, 0x31, 0x00, 0x0E } }, /* 350 700 6.0 */ + { .icl = { 0x6, 0x7F, 0x2C, 0x00, 0x13 } }, /* 350 900 8.2 */ + { .icl = { 0xA, 0x47, 0x3F, 0x00, 0x00 } }, /* 500 500 0.0 */ + { .icl = { 0xC, 0x63, 0x37, 0x00, 0x08 } }, /* 500 700 2.9 */ + { .icl = { 0x6, 0x73, 0x32, 0x00, 0x0D } }, /* 500 900 5.1 */ + { .icl = { 0xC, 0x58, 0x3F, 0x00, 0x00 } }, /* 650 700 0.6 */ + { .icl = { 0x6, 0x7F, 0x35, 0x00, 0x0A } }, /* 600 900 3.5 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 900 900 0.0 */ +}; + +static const struct intel_ddi_buf_trans adls_combo_phy_trans_edp_hbr3 = { + .entries = _adls_combo_phy_trans_edp_hbr3, + .num_entries = ARRAY_SIZE(_adls_combo_phy_trans_edp_hbr3), +}; + +static const union intel_ddi_buf_trans_entry _adlp_combo_phy_trans_dp_hbr[] = { + /* NT mV Trans mV db */ + { .icl = { 0xA, 0x35, 0x3F, 0x00, 0x00 } }, /* 350 350 0.0 */ + { .icl = { 0xA, 0x4F, 0x37, 0x00, 0x08 } }, /* 350 500 3.1 */ + { .icl = { 0xC, 0x71, 0x31, 0x00, 0x0E } }, /* 350 700 6.0 */ + { .icl = { 0x6, 0x7F, 0x2C, 0x00, 0x13 } }, /* 350 900 8.2 */ + { .icl = { 0xA, 0x4C, 0x3F, 0x00, 0x00 } }, /* 500 500 0.0 */ + { .icl = { 0xC, 0x73, 0x34, 0x00, 0x0B } }, /* 500 700 2.9 */ + { .icl = { 0x6, 0x7F, 0x2F, 0x00, 0x10 } }, /* 500 900 5.1 */ + { .icl = { 0xC, 0x7C, 0x3C, 0x00, 0x03 } }, /* 650 700 0.6 */ + { .icl = { 0x6, 0x7F, 0x35, 0x00, 0x0A } }, /* 600 900 3.5 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 900 900 0.0 */ +}; + +static const struct intel_ddi_buf_trans adlp_combo_phy_trans_dp_hbr = { + .entries = _adlp_combo_phy_trans_dp_hbr, + .num_entries = ARRAY_SIZE(_adlp_combo_phy_trans_dp_hbr), +}; + +static const union intel_ddi_buf_trans_entry _adlp_combo_phy_trans_dp_hbr2_hbr3[] = { + /* NT mV Trans mV db */ + { .icl = { 0xA, 0x35, 0x3F, 0x00, 0x00 } }, /* 350 350 0.0 */ + { .icl = { 0xA, 0x4F, 0x37, 0x00, 0x08 } }, /* 350 500 3.1 */ + { .icl = { 0xC, 0x71, 0x30, 0x00, 0x0F } }, /* 350 700 6.0 */ + { .icl = { 0x6, 0x7F, 0x2B, 0x00, 0x14 } }, /* 350 900 8.2 */ + { .icl = { 0xA, 0x4C, 0x3F, 0x00, 0x00 } }, /* 500 500 0.0 */ + { .icl = { 0xC, 0x73, 0x34, 0x00, 0x0B } }, /* 500 700 2.9 */ + { .icl = { 0x6, 0x7F, 0x30, 0x00, 0x0F } }, /* 500 900 5.1 */ + { .icl = { 0xC, 0x63, 0x3F, 0x00, 0x00 } }, /* 650 700 0.6 */ + { .icl = { 0x6, 0x7F, 0x38, 0x00, 0x07 } }, /* 600 900 3.5 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 900 900 0.0 */ +}; + +static const union intel_ddi_buf_trans_entry _adlp_combo_phy_trans_edp_hbr2[] = { + /* NT mV Trans mV db */ + { .icl = { 0x4, 0x50, 0x38, 0x00, 0x07 } }, /* 200 200 0.0 */ + { .icl = { 0x4, 0x58, 0x35, 0x00, 0x0A } }, /* 200 250 1.9 */ + { .icl = { 0x4, 0x60, 0x34, 0x00, 0x0B } }, /* 200 300 3.5 */ + { .icl = { 0x4, 0x6A, 0x32, 0x00, 0x0D } }, /* 200 350 4.9 */ + { .icl = { 0x4, 0x5E, 0x38, 0x00, 0x07 } }, /* 250 250 0.0 */ + { .icl = { 0x4, 0x61, 0x36, 0x00, 0x09 } }, /* 250 300 1.6 */ + { .icl = { 0x4, 0x6B, 0x34, 0x00, 0x0B } }, /* 250 350 2.9 */ + { .icl = { 0x4, 0x69, 0x39, 0x00, 0x06 } }, /* 300 300 0.0 */ + { .icl = { 0x4, 0x73, 0x37, 0x00, 0x08 } }, /* 300 350 1.3 */ + { .icl = { 0x4, 0x7A, 0x38, 0x00, 0x07 } }, /* 350 350 0.0 */ +}; + +static const union intel_ddi_buf_trans_entry _adlp_combo_phy_trans_dp_hbr2_edp_hbr3[] = { + /* NT mV Trans mV db */ + { .icl = { 0xA, 0x35, 0x3F, 0x00, 0x00 } }, /* 350 350 0.0 */ + { .icl = { 0xA, 0x4F, 0x37, 0x00, 0x08 } }, /* 350 500 3.1 */ + { .icl = { 0xC, 0x71, 0x30, 0x00, 0x0f } }, /* 350 700 6.0 */ + { .icl = { 0x6, 0x7F, 0x2B, 0x00, 0x14 } }, /* 350 900 8.2 */ + { .icl = { 0xA, 0x4C, 0x3F, 0x00, 0x00 } }, /* 500 500 0.0 */ + { .icl = { 0xC, 0x73, 0x34, 0x00, 0x0B } }, /* 500 700 2.9 */ + { .icl = { 0x6, 0x7F, 0x30, 0x00, 0x0F } }, /* 500 900 5.1 */ + { .icl = { 0xC, 0x63, 0x3F, 0x00, 0x00 } }, /* 650 700 0.6 */ + { .icl = { 0x6, 0x7F, 0x38, 0x00, 0x07 } }, /* 600 900 3.5 */ + { .icl = { 0x6, 0x7F, 0x3F, 0x00, 0x00 } }, /* 900 900 0.0 */ +}; + +static const struct intel_ddi_buf_trans adlp_combo_phy_trans_dp_hbr2_hbr3 = { + .entries = _adlp_combo_phy_trans_dp_hbr2_hbr3, + .num_entries = ARRAY_SIZE(_adlp_combo_phy_trans_dp_hbr2_hbr3), +}; + +static const struct intel_ddi_buf_trans adlp_combo_phy_trans_edp_hbr3 = { + .entries = _adlp_combo_phy_trans_dp_hbr2_edp_hbr3, + .num_entries = ARRAY_SIZE(_adlp_combo_phy_trans_dp_hbr2_edp_hbr3), +}; + +static const struct intel_ddi_buf_trans adlp_combo_phy_trans_edp_up_to_hbr2 = { + .entries = _adlp_combo_phy_trans_edp_hbr2, + .num_entries = ARRAY_SIZE(_adlp_combo_phy_trans_edp_hbr2), +}; + +static const union intel_ddi_buf_trans_entry _adlp_dkl_phy_trans_dp_hbr[] = { + /* VS pre-emp Non-trans mV Pre-emph dB */ + { .dkl = { 0x7, 0x0, 0x01 } }, /* 0 0 400mV 0 dB */ + { .dkl = { 0x5, 0x0, 0x06 } }, /* 0 1 400mV 3.5 dB */ + { .dkl = { 0x2, 0x0, 0x0B } }, /* 0 2 400mV 6 dB */ + { .dkl = { 0x0, 0x0, 0x17 } }, /* 0 3 400mV 9.5 dB */ + { .dkl = { 0x5, 0x0, 0x00 } }, /* 1 0 600mV 0 dB */ + { .dkl = { 0x2, 0x0, 0x08 } }, /* 1 1 600mV 3.5 dB */ + { .dkl = { 0x0, 0x0, 0x14 } }, /* 1 2 600mV 6 dB */ + { .dkl = { 0x2, 0x0, 0x00 } }, /* 2 0 800mV 0 dB */ + { .dkl = { 0x0, 0x0, 0x0B } }, /* 2 1 800mV 3.5 dB */ + { .dkl = { 0x0, 0x0, 0x00 } }, /* 3 0 1200mV 0 dB */ +}; + +static const struct intel_ddi_buf_trans adlp_dkl_phy_trans_dp_hbr = { + .entries = _adlp_dkl_phy_trans_dp_hbr, + .num_entries = ARRAY_SIZE(_adlp_dkl_phy_trans_dp_hbr), +}; + +static const union intel_ddi_buf_trans_entry _adlp_dkl_phy_trans_dp_hbr2_hbr3[] = { + /* VS pre-emp Non-trans mV Pre-emph dB */ + { .dkl = { 0x7, 0x0, 0x00 } }, /* 0 0 400mV 0 dB */ + { .dkl = { 0x5, 0x0, 0x04 } }, /* 0 1 400mV 3.5 dB */ + { .dkl = { 0x2, 0x0, 0x0A } }, /* 0 2 400mV 6 dB */ + { .dkl = { 0x0, 0x0, 0x18 } }, /* 0 3 400mV 9.5 dB */ + { .dkl = { 0x5, 0x0, 0x00 } }, /* 1 0 600mV 0 dB */ + { .dkl = { 0x2, 0x0, 0x06 } }, /* 1 1 600mV 3.5 dB */ + { .dkl = { 0x0, 0x0, 0x14 } }, /* 1 2 600mV 6 dB */ + { .dkl = { 0x2, 0x0, 0x00 } }, /* 2 0 800mV 0 dB */ + { .dkl = { 0x0, 0x0, 0x09 } }, /* 2 1 800mV 3.5 dB */ + { .dkl = { 0x0, 0x0, 0x00 } }, /* 3 0 1200mV 0 dB */ +}; + +static const struct intel_ddi_buf_trans adlp_dkl_phy_trans_dp_hbr2_hbr3 = { + .entries = _adlp_dkl_phy_trans_dp_hbr2_hbr3, + .num_entries = ARRAY_SIZE(_adlp_dkl_phy_trans_dp_hbr2_hbr3), +}; + +static const union intel_ddi_buf_trans_entry _dg2_snps_trans[] = { + { .snps = { 25, 0, 0 } }, /* VS 0, pre-emph 0 */ + { .snps = { 32, 0, 6 } }, /* VS 0, pre-emph 1 */ + { .snps = { 35, 0, 10 } }, /* VS 0, pre-emph 2 */ + { .snps = { 43, 0, 17 } }, /* VS 0, pre-emph 3 */ + { .snps = { 35, 0, 0 } }, /* VS 1, pre-emph 0 */ + { .snps = { 45, 0, 8 } }, /* VS 1, pre-emph 1 */ + { .snps = { 48, 0, 14 } }, /* VS 1, pre-emph 2 */ + { .snps = { 47, 0, 0 } }, /* VS 2, pre-emph 0 */ + { .snps = { 55, 0, 7 } }, /* VS 2, pre-emph 1 */ + { .snps = { 62, 0, 0 } }, /* VS 3, pre-emph 0 */ +}; + +static const struct intel_ddi_buf_trans dg2_snps_trans = { + .entries = _dg2_snps_trans, + .num_entries = ARRAY_SIZE(_dg2_snps_trans), + .hdmi_default_entry = ARRAY_SIZE(_dg2_snps_trans) - 1, +}; + +static const union intel_ddi_buf_trans_entry _dg2_snps_trans_uhbr[] = { + { .snps = { 62, 0, 0 } }, /* preset 0 */ + { .snps = { 55, 0, 7 } }, /* preset 1 */ + { .snps = { 50, 0, 12 } }, /* preset 2 */ + { .snps = { 44, 0, 18 } }, /* preset 3 */ + { .snps = { 35, 0, 21 } }, /* preset 4 */ + { .snps = { 59, 3, 0 } }, /* preset 5 */ + { .snps = { 53, 3, 6 } }, /* preset 6 */ + { .snps = { 48, 3, 11 } }, /* preset 7 */ + { .snps = { 42, 5, 15 } }, /* preset 8 */ + { .snps = { 37, 5, 20 } }, /* preset 9 */ + { .snps = { 56, 6, 0 } }, /* preset 10 */ + { .snps = { 48, 7, 7 } }, /* preset 11 */ + { .snps = { 45, 7, 10 } }, /* preset 12 */ + { .snps = { 39, 8, 15 } }, /* preset 13 */ + { .snps = { 48, 14, 0 } }, /* preset 14 */ + { .snps = { 45, 4, 4 } }, /* preset 15 */ +}; + +static const struct intel_ddi_buf_trans dg2_snps_trans_uhbr = { + .entries = _dg2_snps_trans_uhbr, + .num_entries = ARRAY_SIZE(_dg2_snps_trans_uhbr), +}; + +bool is_hobl_buf_trans(const struct intel_ddi_buf_trans *table) +{ + return table == &tgl_combo_phy_trans_edp_hbr2_hobl; +} + +static bool use_edp_hobl(struct intel_encoder *encoder) +{ + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + struct intel_connector *connector = intel_dp->attached_connector; + + return connector->panel.vbt.edp.hobl && !intel_dp->hobl_failed; +} + +static bool use_edp_low_vswing(struct intel_encoder *encoder) +{ + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + struct intel_connector *connector = intel_dp->attached_connector; + + return connector->panel.vbt.edp.low_vswing; +} + +static const struct intel_ddi_buf_trans * +intel_get_buf_trans(const struct intel_ddi_buf_trans *trans, int *num_entries) +{ + *num_entries = trans->num_entries; + return trans; +} + +static const struct intel_ddi_buf_trans * +hsw_get_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_ANALOG)) + return intel_get_buf_trans(&hsw_trans_fdi, n_entries); + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return intel_get_buf_trans(&hsw_trans_hdmi, n_entries); + else + return intel_get_buf_trans(&hsw_trans_dp, n_entries); +} + +static const struct intel_ddi_buf_trans * +bdw_get_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_ANALOG)) + return intel_get_buf_trans(&bdw_trans_fdi, n_entries); + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return intel_get_buf_trans(&bdw_trans_hdmi, n_entries); + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP) && + use_edp_low_vswing(encoder)) + return intel_get_buf_trans(&bdw_trans_edp, n_entries); + else + return intel_get_buf_trans(&bdw_trans_dp, n_entries); +} + +static int skl_buf_trans_num_entries(enum port port, int n_entries) +{ + /* Only DDIA and DDIE can select the 10th register with DP */ + if (port == PORT_A || port == PORT_E) + return min(n_entries, 10); + else + return min(n_entries, 9); +} + +static const struct intel_ddi_buf_trans * +_skl_get_buf_trans_dp(struct intel_encoder *encoder, + const struct intel_ddi_buf_trans *trans, + int *n_entries) +{ + trans = intel_get_buf_trans(trans, n_entries); + *n_entries = skl_buf_trans_num_entries(encoder->port, *n_entries); + return trans; +} + +static const struct intel_ddi_buf_trans * +skl_y_get_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return intel_get_buf_trans(&skl_y_trans_hdmi, n_entries); + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP) && + use_edp_low_vswing(encoder)) + return _skl_get_buf_trans_dp(encoder, &skl_y_trans_edp, n_entries); + else + return _skl_get_buf_trans_dp(encoder, &skl_y_trans_dp, n_entries); +} + +static const struct intel_ddi_buf_trans * +skl_u_get_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return intel_get_buf_trans(&skl_trans_hdmi, n_entries); + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP) && + use_edp_low_vswing(encoder)) + return _skl_get_buf_trans_dp(encoder, &skl_u_trans_edp, n_entries); + else + return _skl_get_buf_trans_dp(encoder, &skl_u_trans_dp, n_entries); +} + +static const struct intel_ddi_buf_trans * +skl_get_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return intel_get_buf_trans(&skl_trans_hdmi, n_entries); + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP) && + use_edp_low_vswing(encoder)) + return _skl_get_buf_trans_dp(encoder, &skl_trans_edp, n_entries); + else + return _skl_get_buf_trans_dp(encoder, &skl_trans_dp, n_entries); +} + +static const struct intel_ddi_buf_trans * +kbl_y_get_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return intel_get_buf_trans(&skl_y_trans_hdmi, n_entries); + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP) && + use_edp_low_vswing(encoder)) + return _skl_get_buf_trans_dp(encoder, &skl_y_trans_edp, n_entries); + else + return _skl_get_buf_trans_dp(encoder, &kbl_y_trans_dp, n_entries); +} + +static const struct intel_ddi_buf_trans * +kbl_u_get_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return intel_get_buf_trans(&skl_trans_hdmi, n_entries); + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP) && + use_edp_low_vswing(encoder)) + return _skl_get_buf_trans_dp(encoder, &skl_u_trans_edp, n_entries); + else + return _skl_get_buf_trans_dp(encoder, &kbl_u_trans_dp, n_entries); +} + +static const struct intel_ddi_buf_trans * +kbl_get_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return intel_get_buf_trans(&skl_trans_hdmi, n_entries); + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP) && + use_edp_low_vswing(encoder)) + return _skl_get_buf_trans_dp(encoder, &skl_trans_edp, n_entries); + else + return _skl_get_buf_trans_dp(encoder, &kbl_trans_dp, n_entries); +} + +static const struct intel_ddi_buf_trans * +bxt_get_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return intel_get_buf_trans(&bxt_trans_hdmi, n_entries); + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP) && + use_edp_low_vswing(encoder)) + return intel_get_buf_trans(&bxt_trans_edp, n_entries); + else + return intel_get_buf_trans(&bxt_trans_dp, n_entries); +} + +static const struct intel_ddi_buf_trans * +icl_get_combo_buf_trans_dp(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + return intel_get_buf_trans(&icl_combo_phy_trans_dp_hbr2_edp_hbr3, + n_entries); +} + +static const struct intel_ddi_buf_trans * +icl_get_combo_buf_trans_edp(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (crtc_state->port_clock > 540000) { + return intel_get_buf_trans(&icl_combo_phy_trans_dp_hbr2_edp_hbr3, + n_entries); + } else if (use_edp_low_vswing(encoder)) { + return intel_get_buf_trans(&icl_combo_phy_trans_edp_hbr2, + n_entries); + } + + return icl_get_combo_buf_trans_dp(encoder, crtc_state, n_entries); +} + +static const struct intel_ddi_buf_trans * +icl_get_combo_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return intel_get_buf_trans(&icl_combo_phy_trans_hdmi, n_entries); + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP)) + return icl_get_combo_buf_trans_edp(encoder, crtc_state, n_entries); + else + return icl_get_combo_buf_trans_dp(encoder, crtc_state, n_entries); +} + +static const struct intel_ddi_buf_trans * +icl_get_mg_buf_trans_dp(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (crtc_state->port_clock > 270000) { + return intel_get_buf_trans(&icl_mg_phy_trans_hbr2_hbr3, + n_entries); + } else { + return intel_get_buf_trans(&icl_mg_phy_trans_rbr_hbr, + n_entries); + } +} + +static const struct intel_ddi_buf_trans * +icl_get_mg_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return intel_get_buf_trans(&icl_mg_phy_trans_hdmi, n_entries); + else + return icl_get_mg_buf_trans_dp(encoder, crtc_state, n_entries); +} + +static const struct intel_ddi_buf_trans * +ehl_get_combo_buf_trans_edp(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (crtc_state->port_clock > 270000) + return intel_get_buf_trans(&ehl_combo_phy_trans_edp_hbr2, n_entries); + else + return intel_get_buf_trans(&icl_combo_phy_trans_edp_hbr2, n_entries); +} + +static const struct intel_ddi_buf_trans * +ehl_get_combo_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return intel_get_buf_trans(&icl_combo_phy_trans_hdmi, n_entries); + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP) && + use_edp_low_vswing(encoder)) + return ehl_get_combo_buf_trans_edp(encoder, crtc_state, n_entries); + else + return intel_get_buf_trans(&ehl_combo_phy_trans_dp, n_entries); +} + +static const struct intel_ddi_buf_trans * +jsl_get_combo_buf_trans_edp(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (crtc_state->port_clock > 270000) + return intel_get_buf_trans(&jsl_combo_phy_trans_edp_hbr2, n_entries); + else + return intel_get_buf_trans(&jsl_combo_phy_trans_edp_hbr, n_entries); +} + +static const struct intel_ddi_buf_trans * +jsl_get_combo_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return intel_get_buf_trans(&icl_combo_phy_trans_hdmi, n_entries); + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP) && + use_edp_low_vswing(encoder)) + return jsl_get_combo_buf_trans_edp(encoder, crtc_state, n_entries); + else + return intel_get_buf_trans(&icl_combo_phy_trans_dp_hbr2_edp_hbr3, n_entries); +} + +static const struct intel_ddi_buf_trans * +tgl_get_combo_buf_trans_dp(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + + if (crtc_state->port_clock > 270000) { + if (IS_TGL_UY(dev_priv)) { + return intel_get_buf_trans(&tgl_uy_combo_phy_trans_dp_hbr2, + n_entries); + } else { + return intel_get_buf_trans(&tgl_combo_phy_trans_dp_hbr2, + n_entries); + } + } else { + return intel_get_buf_trans(&tgl_combo_phy_trans_dp_hbr, + n_entries); + } +} + +static const struct intel_ddi_buf_trans * +tgl_get_combo_buf_trans_edp(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (crtc_state->port_clock > 540000) { + return intel_get_buf_trans(&icl_combo_phy_trans_dp_hbr2_edp_hbr3, + n_entries); + } else if (use_edp_hobl(encoder)) { + return intel_get_buf_trans(&tgl_combo_phy_trans_edp_hbr2_hobl, + n_entries); + } else if (use_edp_low_vswing(encoder)) { + return intel_get_buf_trans(&icl_combo_phy_trans_edp_hbr2, + n_entries); + } + + return tgl_get_combo_buf_trans_dp(encoder, crtc_state, n_entries); +} + +static const struct intel_ddi_buf_trans * +tgl_get_combo_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return intel_get_buf_trans(&icl_combo_phy_trans_hdmi, n_entries); + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP)) + return tgl_get_combo_buf_trans_edp(encoder, crtc_state, n_entries); + else + return tgl_get_combo_buf_trans_dp(encoder, crtc_state, n_entries); +} + +static const struct intel_ddi_buf_trans * +dg1_get_combo_buf_trans_dp(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (crtc_state->port_clock > 270000) + return intel_get_buf_trans(&dg1_combo_phy_trans_dp_hbr2_hbr3, + n_entries); + else + return intel_get_buf_trans(&dg1_combo_phy_trans_dp_rbr_hbr, + n_entries); +} + +static const struct intel_ddi_buf_trans * +dg1_get_combo_buf_trans_edp(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (crtc_state->port_clock > 540000) + return intel_get_buf_trans(&icl_combo_phy_trans_dp_hbr2_edp_hbr3, + n_entries); + else if (use_edp_hobl(encoder)) + return intel_get_buf_trans(&tgl_combo_phy_trans_edp_hbr2_hobl, + n_entries); + else if (use_edp_low_vswing(encoder)) + return intel_get_buf_trans(&icl_combo_phy_trans_edp_hbr2, + n_entries); + else + return dg1_get_combo_buf_trans_dp(encoder, crtc_state, n_entries); +} + +static const struct intel_ddi_buf_trans * +dg1_get_combo_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return intel_get_buf_trans(&icl_combo_phy_trans_hdmi, n_entries); + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP)) + return dg1_get_combo_buf_trans_edp(encoder, crtc_state, n_entries); + else + return dg1_get_combo_buf_trans_dp(encoder, crtc_state, n_entries); +} + +static const struct intel_ddi_buf_trans * +rkl_get_combo_buf_trans_dp(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (crtc_state->port_clock > 270000) + return intel_get_buf_trans(&rkl_combo_phy_trans_dp_hbr2_hbr3, n_entries); + else + return intel_get_buf_trans(&rkl_combo_phy_trans_dp_hbr, n_entries); +} + +static const struct intel_ddi_buf_trans * +rkl_get_combo_buf_trans_edp(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (crtc_state->port_clock > 540000) { + return intel_get_buf_trans(&icl_combo_phy_trans_dp_hbr2_edp_hbr3, + n_entries); + } else if (use_edp_hobl(encoder)) { + return intel_get_buf_trans(&tgl_combo_phy_trans_edp_hbr2_hobl, + n_entries); + } else if (use_edp_low_vswing(encoder)) { + return intel_get_buf_trans(&icl_combo_phy_trans_edp_hbr2, + n_entries); + } + + return rkl_get_combo_buf_trans_dp(encoder, crtc_state, n_entries); +} + +static const struct intel_ddi_buf_trans * +rkl_get_combo_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return intel_get_buf_trans(&icl_combo_phy_trans_hdmi, n_entries); + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP)) + return rkl_get_combo_buf_trans_edp(encoder, crtc_state, n_entries); + else + return rkl_get_combo_buf_trans_dp(encoder, crtc_state, n_entries); +} + +static const struct intel_ddi_buf_trans * +adls_get_combo_buf_trans_dp(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (crtc_state->port_clock > 270000) + return intel_get_buf_trans(&adls_combo_phy_trans_dp_hbr2_hbr3, n_entries); + else + return intel_get_buf_trans(&tgl_combo_phy_trans_dp_hbr, n_entries); +} + +static const struct intel_ddi_buf_trans * +adls_get_combo_buf_trans_edp(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (crtc_state->port_clock > 540000) + return intel_get_buf_trans(&adls_combo_phy_trans_edp_hbr3, n_entries); + else if (use_edp_hobl(encoder)) + return intel_get_buf_trans(&tgl_combo_phy_trans_edp_hbr2_hobl, n_entries); + else if (use_edp_low_vswing(encoder)) + return intel_get_buf_trans(&adls_combo_phy_trans_edp_hbr2, n_entries); + else + return adls_get_combo_buf_trans_dp(encoder, crtc_state, n_entries); +} + +static const struct intel_ddi_buf_trans * +adls_get_combo_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return intel_get_buf_trans(&icl_combo_phy_trans_hdmi, n_entries); + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP)) + return adls_get_combo_buf_trans_edp(encoder, crtc_state, n_entries); + else + return adls_get_combo_buf_trans_dp(encoder, crtc_state, n_entries); +} + +static const struct intel_ddi_buf_trans * +adlp_get_combo_buf_trans_dp(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (crtc_state->port_clock > 270000) + return intel_get_buf_trans(&adlp_combo_phy_trans_dp_hbr2_hbr3, n_entries); + else + return intel_get_buf_trans(&adlp_combo_phy_trans_dp_hbr, n_entries); +} + +static const struct intel_ddi_buf_trans * +adlp_get_combo_buf_trans_edp(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (crtc_state->port_clock > 540000) { + return intel_get_buf_trans(&adlp_combo_phy_trans_edp_hbr3, + n_entries); + } else if (use_edp_hobl(encoder)) { + return intel_get_buf_trans(&tgl_combo_phy_trans_edp_hbr2_hobl, + n_entries); + } else if (use_edp_low_vswing(encoder)) { + return intel_get_buf_trans(&adlp_combo_phy_trans_edp_up_to_hbr2, + n_entries); + } + + return adlp_get_combo_buf_trans_dp(encoder, crtc_state, n_entries); +} + +static const struct intel_ddi_buf_trans * +adlp_get_combo_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return intel_get_buf_trans(&icl_combo_phy_trans_hdmi, n_entries); + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP)) + return adlp_get_combo_buf_trans_edp(encoder, crtc_state, n_entries); + else + return adlp_get_combo_buf_trans_dp(encoder, crtc_state, n_entries); +} + +static const struct intel_ddi_buf_trans * +tgl_get_dkl_buf_trans_dp(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (crtc_state->port_clock > 270000) { + return intel_get_buf_trans(&tgl_dkl_phy_trans_dp_hbr2, + n_entries); + } else { + return intel_get_buf_trans(&tgl_dkl_phy_trans_dp_hbr, + n_entries); + } +} + +static const struct intel_ddi_buf_trans * +tgl_get_dkl_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return intel_get_buf_trans(&tgl_dkl_phy_trans_hdmi, n_entries); + else + return tgl_get_dkl_buf_trans_dp(encoder, crtc_state, n_entries); +} + +static const struct intel_ddi_buf_trans * +adlp_get_dkl_buf_trans_dp(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (crtc_state->port_clock > 270000) { + return intel_get_buf_trans(&adlp_dkl_phy_trans_dp_hbr2_hbr3, + n_entries); + } else { + return intel_get_buf_trans(&adlp_dkl_phy_trans_dp_hbr, + n_entries); + } +} + +static const struct intel_ddi_buf_trans * +adlp_get_dkl_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return intel_get_buf_trans(&tgl_dkl_phy_trans_hdmi, n_entries); + else + return adlp_get_dkl_buf_trans_dp(encoder, crtc_state, n_entries); +} + +static const struct intel_ddi_buf_trans * +dg2_get_snps_buf_trans(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries) +{ + if (intel_crtc_has_dp_encoder(crtc_state) && + intel_dp_is_uhbr(crtc_state)) + return intel_get_buf_trans(&dg2_snps_trans_uhbr, n_entries); + else + return intel_get_buf_trans(&dg2_snps_trans, n_entries); +} + +void intel_ddi_buf_trans_init(struct intel_encoder *encoder) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + enum phy phy = intel_port_to_phy(i915, encoder->port); + + if (IS_DG2(i915)) { + encoder->get_buf_trans = dg2_get_snps_buf_trans; + } else if (IS_ALDERLAKE_P(i915)) { + if (intel_phy_is_combo(i915, phy)) + encoder->get_buf_trans = adlp_get_combo_buf_trans; + else + encoder->get_buf_trans = adlp_get_dkl_buf_trans; + } else if (IS_ALDERLAKE_S(i915)) { + encoder->get_buf_trans = adls_get_combo_buf_trans; + } else if (IS_ROCKETLAKE(i915)) { + encoder->get_buf_trans = rkl_get_combo_buf_trans; + } else if (IS_DG1(i915)) { + encoder->get_buf_trans = dg1_get_combo_buf_trans; + } else if (DISPLAY_VER(i915) >= 12) { + if (intel_phy_is_combo(i915, phy)) + encoder->get_buf_trans = tgl_get_combo_buf_trans; + else + encoder->get_buf_trans = tgl_get_dkl_buf_trans; + } else if (DISPLAY_VER(i915) == 11) { + if (IS_PLATFORM(i915, INTEL_JASPERLAKE)) + encoder->get_buf_trans = jsl_get_combo_buf_trans; + else if (IS_PLATFORM(i915, INTEL_ELKHARTLAKE)) + encoder->get_buf_trans = ehl_get_combo_buf_trans; + else if (intel_phy_is_combo(i915, phy)) + encoder->get_buf_trans = icl_get_combo_buf_trans; + else + encoder->get_buf_trans = icl_get_mg_buf_trans; + } else if (IS_GEMINILAKE(i915) || IS_BROXTON(i915)) { + encoder->get_buf_trans = bxt_get_buf_trans; + } else if (IS_CML_ULX(i915) || IS_CFL_ULX(i915) || IS_KBL_ULX(i915)) { + encoder->get_buf_trans = kbl_y_get_buf_trans; + } else if (IS_CML_ULT(i915) || IS_CFL_ULT(i915) || IS_KBL_ULT(i915)) { + encoder->get_buf_trans = kbl_u_get_buf_trans; + } else if (IS_COMETLAKE(i915) || IS_COFFEELAKE(i915) || IS_KABYLAKE(i915)) { + encoder->get_buf_trans = kbl_get_buf_trans; + } else if (IS_SKL_ULX(i915)) { + encoder->get_buf_trans = skl_y_get_buf_trans; + } else if (IS_SKL_ULT(i915)) { + encoder->get_buf_trans = skl_u_get_buf_trans; + } else if (IS_SKYLAKE(i915)) { + encoder->get_buf_trans = skl_get_buf_trans; + } else if (IS_BROADWELL(i915)) { + encoder->get_buf_trans = bdw_get_buf_trans; + } else if (IS_HASWELL(i915)) { + encoder->get_buf_trans = hsw_get_buf_trans; + } else { + MISSING_CASE(INTEL_INFO(i915)->platform); + } +} diff --git a/drivers/gpu/drm/i915/display/intel_ddi_buf_trans.h b/drivers/gpu/drm/i915/display/intel_ddi_buf_trans.h new file mode 100644 index 000000000..2133984a5 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_ddi_buf_trans.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2020 Intel Corporation + */ + +#ifndef _INTEL_DDI_BUF_TRANS_H_ +#define _INTEL_DDI_BUF_TRANS_H_ + +#include + +struct drm_i915_private; +struct intel_encoder; +struct intel_crtc_state; + +struct hsw_ddi_buf_trans { + u32 trans1; /* balance leg enable, de-emph level */ + u32 trans2; /* vref sel, vswing */ + u8 i_boost; /* SKL: I_boost; valid: 0x0, 0x1, 0x3, 0x7 */ +}; + +struct bxt_ddi_buf_trans { + u8 margin; /* swing value */ + u8 scale; /* scale value */ + u8 enable; /* scale enable */ + u8 deemphasis; +}; + +struct icl_ddi_buf_trans { + u8 dw2_swing_sel; + u8 dw7_n_scalar; + u8 dw4_cursor_coeff; + u8 dw4_post_cursor_2; + u8 dw4_post_cursor_1; +}; + +struct icl_mg_phy_ddi_buf_trans { + u8 cri_txdeemph_override_11_6; + u8 cri_txdeemph_override_5_0; + u8 cri_txdeemph_override_17_12; +}; + +struct tgl_dkl_phy_ddi_buf_trans { + u8 vswing; + u8 preshoot; + u8 de_emphasis; +}; + +struct dg2_snps_phy_buf_trans { + u8 vswing; + u8 pre_cursor; + u8 post_cursor; +}; + +union intel_ddi_buf_trans_entry { + struct hsw_ddi_buf_trans hsw; + struct bxt_ddi_buf_trans bxt; + struct icl_ddi_buf_trans icl; + struct icl_mg_phy_ddi_buf_trans mg; + struct tgl_dkl_phy_ddi_buf_trans dkl; + struct dg2_snps_phy_buf_trans snps; +}; + +struct intel_ddi_buf_trans { + const union intel_ddi_buf_trans_entry *entries; + u8 num_entries; + u8 hdmi_default_entry; +}; + +bool is_hobl_buf_trans(const struct intel_ddi_buf_trans *table); + +void intel_ddi_buf_trans_init(struct intel_encoder *encoder); + +#endif diff --git a/drivers/gpu/drm/i915/display/intel_de.h b/drivers/gpu/drm/i915/display/intel_de.h new file mode 100644 index 000000000..9c104f65e --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_de.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_DE_H__ +#define __INTEL_DE_H__ + +#include "i915_drv.h" +#include "i915_trace.h" +#include "intel_uncore.h" + +static inline u32 +intel_de_read(struct drm_i915_private *i915, i915_reg_t reg) +{ + return intel_uncore_read(&i915->uncore, reg); +} + +static inline void +intel_de_posting_read(struct drm_i915_private *i915, i915_reg_t reg) +{ + intel_uncore_posting_read(&i915->uncore, reg); +} + +static inline void +intel_de_write(struct drm_i915_private *i915, i915_reg_t reg, u32 val) +{ + intel_uncore_write(&i915->uncore, reg, val); +} + +static inline void +intel_de_rmw(struct drm_i915_private *i915, i915_reg_t reg, u32 clear, u32 set) +{ + intel_uncore_rmw(&i915->uncore, reg, clear, set); +} + +static inline int +intel_de_wait_for_register(struct drm_i915_private *i915, i915_reg_t reg, + u32 mask, u32 value, unsigned int timeout) +{ + return intel_wait_for_register(&i915->uncore, reg, mask, value, timeout); +} + +static inline int +intel_de_wait_for_set(struct drm_i915_private *i915, i915_reg_t reg, + u32 mask, unsigned int timeout) +{ + return intel_de_wait_for_register(i915, reg, mask, mask, timeout); +} + +static inline int +intel_de_wait_for_clear(struct drm_i915_private *i915, i915_reg_t reg, + u32 mask, unsigned int timeout) +{ + return intel_de_wait_for_register(i915, reg, mask, 0, timeout); +} + +/* + * Unlocked mmio-accessors, think carefully before using these. + * + * Certain architectures will die if the same cacheline is concurrently accessed + * by different clients (e.g. on Ivybridge). Access to registers should + * therefore generally be serialised, by either the dev_priv->uncore.lock or + * a more localised lock guarding all access to that bank of registers. + */ +static inline u32 +intel_de_read_fw(struct drm_i915_private *i915, i915_reg_t reg) +{ + u32 val; + + val = intel_uncore_read_fw(&i915->uncore, reg); + trace_i915_reg_rw(false, reg, val, sizeof(val), true); + + return val; +} + +static inline void +intel_de_write_fw(struct drm_i915_private *i915, i915_reg_t reg, u32 val) +{ + trace_i915_reg_rw(true, reg, val, sizeof(val), true); + intel_uncore_write_fw(&i915->uncore, reg, val); +} + +#endif /* __INTEL_DE_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c new file mode 100644 index 000000000..fb8d1d634 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_display.c @@ -0,0 +1,9140 @@ +/* + * Copyright © 2006-2007 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Eric Anholt + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "display/intel_audio.h" +#include "display/intel_crt.h" +#include "display/intel_ddi.h" +#include "display/intel_display_debugfs.h" +#include "display/intel_display_power.h" +#include "display/intel_dp.h" +#include "display/intel_dp_mst.h" +#include "display/intel_dpll.h" +#include "display/intel_dpll_mgr.h" +#include "display/intel_drrs.h" +#include "display/intel_dsi.h" +#include "display/intel_dvo.h" +#include "display/intel_fb.h" +#include "display/intel_gmbus.h" +#include "display/intel_hdmi.h" +#include "display/intel_lvds.h" +#include "display/intel_sdvo.h" +#include "display/intel_snps_phy.h" +#include "display/intel_tv.h" +#include "display/intel_vdsc.h" +#include "display/intel_vrr.h" + +#include "gem/i915_gem_lmem.h" +#include "gem/i915_gem_object.h" + +#include "gt/gen8_ppgtt.h" + +#include "g4x_dp.h" +#include "g4x_hdmi.h" +#include "hsw_ips.h" +#include "i915_drv.h" +#include "i915_utils.h" +#include "icl_dsi.h" +#include "intel_acpi.h" +#include "intel_atomic.h" +#include "intel_atomic_plane.h" +#include "intel_bw.h" +#include "intel_cdclk.h" +#include "intel_color.h" +#include "intel_crtc.h" +#include "intel_crtc_state_dump.h" +#include "intel_de.h" +#include "intel_display_types.h" +#include "intel_dmc.h" +#include "intel_dp_link_training.h" +#include "intel_dpt.h" +#include "intel_dsb.h" +#include "intel_fbc.h" +#include "intel_fbdev.h" +#include "intel_fdi.h" +#include "intel_fifo_underrun.h" +#include "intel_frontbuffer.h" +#include "intel_hdcp.h" +#include "intel_hotplug.h" +#include "intel_modeset_verify.h" +#include "intel_modeset_setup.h" +#include "intel_overlay.h" +#include "intel_panel.h" +#include "intel_pch_display.h" +#include "intel_pch_refclk.h" +#include "intel_pcode.h" +#include "intel_pipe_crc.h" +#include "intel_plane_initial.h" +#include "intel_pm.h" +#include "intel_pps.h" +#include "intel_psr.h" +#include "intel_quirks.h" +#include "intel_sprite.h" +#include "intel_tc.h" +#include "intel_vga.h" +#include "i9xx_plane.h" +#include "skl_scaler.h" +#include "skl_universal_plane.h" +#include "skl_watermark.h" +#include "vlv_dsi.h" +#include "vlv_dsi_pll.h" +#include "vlv_dsi_regs.h" +#include "vlv_sideband.h" + +static void intel_set_transcoder_timings(const struct intel_crtc_state *crtc_state); +static void intel_set_pipe_src_size(const struct intel_crtc_state *crtc_state); +static void hsw_set_transconf(const struct intel_crtc_state *crtc_state); +static void bdw_set_pipemisc(const struct intel_crtc_state *crtc_state); +static void ilk_pfit_enable(const struct intel_crtc_state *crtc_state); + +/** + * intel_update_watermarks - update FIFO watermark values based on current modes + * @dev_priv: i915 device + * + * Calculate watermark values for the various WM regs based on current mode + * and plane configuration. + * + * There are several cases to deal with here: + * - normal (i.e. non-self-refresh) + * - self-refresh (SR) mode + * - lines are large relative to FIFO size (buffer can hold up to 2) + * - lines are small relative to FIFO size (buffer can hold more than 2 + * lines), so need to account for TLB latency + * + * The normal calculation is: + * watermark = dotclock * bytes per pixel * latency + * where latency is platform & configuration dependent (we assume pessimal + * values here). + * + * The SR calculation is: + * watermark = (trunc(latency/line time)+1) * surface width * + * bytes per pixel + * where + * line time = htotal / dotclock + * surface width = hdisplay for normal plane and 64 for cursor + * and latency is assumed to be high, as above. + * + * The final value programmed to the register should always be rounded up, + * and include an extra 2 entries to account for clock crossings. + * + * We don't use the sprite, so we can ignore that. And on Crestline we have + * to set the non-SR watermarks to 8. + */ +void intel_update_watermarks(struct drm_i915_private *dev_priv) +{ + if (dev_priv->display.funcs.wm->update_wm) + dev_priv->display.funcs.wm->update_wm(dev_priv); +} + +static int intel_compute_pipe_wm(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + if (dev_priv->display.funcs.wm->compute_pipe_wm) + return dev_priv->display.funcs.wm->compute_pipe_wm(state, crtc); + return 0; +} + +static int intel_compute_intermediate_wm(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + if (!dev_priv->display.funcs.wm->compute_intermediate_wm) + return 0; + if (drm_WARN_ON(&dev_priv->drm, + !dev_priv->display.funcs.wm->compute_pipe_wm)) + return 0; + return dev_priv->display.funcs.wm->compute_intermediate_wm(state, crtc); +} + +static bool intel_initial_watermarks(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + if (dev_priv->display.funcs.wm->initial_watermarks) { + dev_priv->display.funcs.wm->initial_watermarks(state, crtc); + return true; + } + return false; +} + +static void intel_atomic_update_watermarks(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + if (dev_priv->display.funcs.wm->atomic_update_watermarks) + dev_priv->display.funcs.wm->atomic_update_watermarks(state, crtc); +} + +static void intel_optimize_watermarks(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + if (dev_priv->display.funcs.wm->optimize_watermarks) + dev_priv->display.funcs.wm->optimize_watermarks(state, crtc); +} + +static int intel_compute_global_watermarks(struct intel_atomic_state *state) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + if (dev_priv->display.funcs.wm->compute_global_watermarks) + return dev_priv->display.funcs.wm->compute_global_watermarks(state); + return 0; +} + +/* returns HPLL frequency in kHz */ +int vlv_get_hpll_vco(struct drm_i915_private *dev_priv) +{ + int hpll_freq, vco_freq[] = { 800, 1600, 2000, 2400 }; + + /* Obtain SKU information */ + hpll_freq = vlv_cck_read(dev_priv, CCK_FUSE_REG) & + CCK_FUSE_HPLL_FREQ_MASK; + + return vco_freq[hpll_freq] * 1000; +} + +int vlv_get_cck_clock(struct drm_i915_private *dev_priv, + const char *name, u32 reg, int ref_freq) +{ + u32 val; + int divider; + + val = vlv_cck_read(dev_priv, reg); + divider = val & CCK_FREQUENCY_VALUES; + + drm_WARN(&dev_priv->drm, (val & CCK_FREQUENCY_STATUS) != + (divider << CCK_FREQUENCY_STATUS_SHIFT), + "%s change in progress\n", name); + + return DIV_ROUND_CLOSEST(ref_freq << 1, divider + 1); +} + +int vlv_get_cck_clock_hpll(struct drm_i915_private *dev_priv, + const char *name, u32 reg) +{ + int hpll; + + vlv_cck_get(dev_priv); + + if (dev_priv->hpll_freq == 0) + dev_priv->hpll_freq = vlv_get_hpll_vco(dev_priv); + + hpll = vlv_get_cck_clock(dev_priv, name, reg, dev_priv->hpll_freq); + + vlv_cck_put(dev_priv); + + return hpll; +} + +static void intel_update_czclk(struct drm_i915_private *dev_priv) +{ + if (!(IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv))) + return; + + dev_priv->czclk_freq = vlv_get_cck_clock_hpll(dev_priv, "czclk", + CCK_CZ_CLOCK_CONTROL); + + drm_dbg(&dev_priv->drm, "CZ clock rate: %d kHz\n", + dev_priv->czclk_freq); +} + +static bool is_hdr_mode(const struct intel_crtc_state *crtc_state) +{ + return (crtc_state->active_planes & + ~(icl_hdr_plane_mask() | BIT(PLANE_CURSOR))) == 0; +} + +/* WA Display #0827: Gen9:all */ +static void +skl_wa_827(struct drm_i915_private *dev_priv, enum pipe pipe, bool enable) +{ + if (enable) + intel_de_write(dev_priv, CLKGATE_DIS_PSL(pipe), + intel_de_read(dev_priv, CLKGATE_DIS_PSL(pipe)) | DUPS1_GATING_DIS | DUPS2_GATING_DIS); + else + intel_de_write(dev_priv, CLKGATE_DIS_PSL(pipe), + intel_de_read(dev_priv, CLKGATE_DIS_PSL(pipe)) & ~(DUPS1_GATING_DIS | DUPS2_GATING_DIS)); +} + +/* Wa_2006604312:icl,ehl */ +static void +icl_wa_scalerclkgating(struct drm_i915_private *dev_priv, enum pipe pipe, + bool enable) +{ + if (enable) + intel_de_write(dev_priv, CLKGATE_DIS_PSL(pipe), + intel_de_read(dev_priv, CLKGATE_DIS_PSL(pipe)) | DPFR_GATING_DIS); + else + intel_de_write(dev_priv, CLKGATE_DIS_PSL(pipe), + intel_de_read(dev_priv, CLKGATE_DIS_PSL(pipe)) & ~DPFR_GATING_DIS); +} + +/* Wa_1604331009:icl,jsl,ehl */ +static void +icl_wa_cursorclkgating(struct drm_i915_private *dev_priv, enum pipe pipe, + bool enable) +{ + intel_de_rmw(dev_priv, CLKGATE_DIS_PSL(pipe), CURSOR_GATING_DIS, + enable ? CURSOR_GATING_DIS : 0); +} + +static bool +is_trans_port_sync_slave(const struct intel_crtc_state *crtc_state) +{ + return crtc_state->master_transcoder != INVALID_TRANSCODER; +} + +static bool +is_trans_port_sync_master(const struct intel_crtc_state *crtc_state) +{ + return crtc_state->sync_mode_slaves_mask != 0; +} + +bool +is_trans_port_sync_mode(const struct intel_crtc_state *crtc_state) +{ + return is_trans_port_sync_master(crtc_state) || + is_trans_port_sync_slave(crtc_state); +} + +static enum pipe bigjoiner_master_pipe(const struct intel_crtc_state *crtc_state) +{ + return ffs(crtc_state->bigjoiner_pipes) - 1; +} + +u8 intel_crtc_bigjoiner_slave_pipes(const struct intel_crtc_state *crtc_state) +{ + if (crtc_state->bigjoiner_pipes) + return crtc_state->bigjoiner_pipes & ~BIT(bigjoiner_master_pipe(crtc_state)); + else + return 0; +} + +bool intel_crtc_is_bigjoiner_slave(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + return crtc_state->bigjoiner_pipes && + crtc->pipe != bigjoiner_master_pipe(crtc_state); +} + +bool intel_crtc_is_bigjoiner_master(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + return crtc_state->bigjoiner_pipes && + crtc->pipe == bigjoiner_master_pipe(crtc_state); +} + +static int intel_bigjoiner_num_pipes(const struct intel_crtc_state *crtc_state) +{ + return hweight8(crtc_state->bigjoiner_pipes); +} + +struct intel_crtc *intel_master_crtc(const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(crtc_state->uapi.crtc->dev); + + if (intel_crtc_is_bigjoiner_slave(crtc_state)) + return intel_crtc_for_pipe(i915, bigjoiner_master_pipe(crtc_state)); + else + return to_intel_crtc(crtc_state->uapi.crtc); +} + +static bool pipe_scanline_is_moving(struct drm_i915_private *dev_priv, + enum pipe pipe) +{ + i915_reg_t reg = PIPEDSL(pipe); + u32 line1, line2; + + line1 = intel_de_read(dev_priv, reg) & PIPEDSL_LINE_MASK; + msleep(5); + line2 = intel_de_read(dev_priv, reg) & PIPEDSL_LINE_MASK; + + return line1 != line2; +} + +static void wait_for_pipe_scanline_moving(struct intel_crtc *crtc, bool state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + + /* Wait for the display line to settle/start moving */ + if (wait_for(pipe_scanline_is_moving(dev_priv, pipe) == state, 100)) + drm_err(&dev_priv->drm, + "pipe %c scanline %s wait timed out\n", + pipe_name(pipe), str_on_off(state)); +} + +static void intel_wait_for_pipe_scanline_stopped(struct intel_crtc *crtc) +{ + wait_for_pipe_scanline_moving(crtc, false); +} + +static void intel_wait_for_pipe_scanline_moving(struct intel_crtc *crtc) +{ + wait_for_pipe_scanline_moving(crtc, true); +} + +static void +intel_wait_for_pipe_off(const struct intel_crtc_state *old_crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + + if (DISPLAY_VER(dev_priv) >= 4) { + enum transcoder cpu_transcoder = old_crtc_state->cpu_transcoder; + + /* Wait for the Pipe State to go off */ + if (intel_de_wait_for_clear(dev_priv, PIPECONF(cpu_transcoder), + PIPECONF_STATE_ENABLE, 100)) + drm_WARN(&dev_priv->drm, 1, "pipe_off wait timed out\n"); + } else { + intel_wait_for_pipe_scanline_stopped(crtc); + } +} + +void assert_transcoder(struct drm_i915_private *dev_priv, + enum transcoder cpu_transcoder, bool state) +{ + bool cur_state; + enum intel_display_power_domain power_domain; + intel_wakeref_t wakeref; + + /* we keep both pipes enabled on 830 */ + if (IS_I830(dev_priv)) + state = true; + + power_domain = POWER_DOMAIN_TRANSCODER(cpu_transcoder); + wakeref = intel_display_power_get_if_enabled(dev_priv, power_domain); + if (wakeref) { + u32 val = intel_de_read(dev_priv, PIPECONF(cpu_transcoder)); + cur_state = !!(val & PIPECONF_ENABLE); + + intel_display_power_put(dev_priv, power_domain, wakeref); + } else { + cur_state = false; + } + + I915_STATE_WARN(cur_state != state, + "transcoder %s assertion failure (expected %s, current %s)\n", + transcoder_name(cpu_transcoder), + str_on_off(state), str_on_off(cur_state)); +} + +static void assert_plane(struct intel_plane *plane, bool state) +{ + enum pipe pipe; + bool cur_state; + + cur_state = plane->get_hw_state(plane, &pipe); + + I915_STATE_WARN(cur_state != state, + "%s assertion failure (expected %s, current %s)\n", + plane->base.name, str_on_off(state), + str_on_off(cur_state)); +} + +#define assert_plane_enabled(p) assert_plane(p, true) +#define assert_plane_disabled(p) assert_plane(p, false) + +static void assert_planes_disabled(struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_plane *plane; + + for_each_intel_plane_on_crtc(&dev_priv->drm, crtc, plane) + assert_plane_disabled(plane); +} + +void vlv_wait_port_ready(struct drm_i915_private *dev_priv, + struct intel_digital_port *dig_port, + unsigned int expected_mask) +{ + u32 port_mask; + i915_reg_t dpll_reg; + + switch (dig_port->base.port) { + default: + MISSING_CASE(dig_port->base.port); + fallthrough; + case PORT_B: + port_mask = DPLL_PORTB_READY_MASK; + dpll_reg = DPLL(0); + break; + case PORT_C: + port_mask = DPLL_PORTC_READY_MASK; + dpll_reg = DPLL(0); + expected_mask <<= 4; + break; + case PORT_D: + port_mask = DPLL_PORTD_READY_MASK; + dpll_reg = DPIO_PHY_STATUS; + break; + } + + if (intel_de_wait_for_register(dev_priv, dpll_reg, + port_mask, expected_mask, 1000)) + drm_WARN(&dev_priv->drm, 1, + "timed out waiting for [ENCODER:%d:%s] port ready: got 0x%x, expected 0x%x\n", + dig_port->base.base.base.id, dig_port->base.base.name, + intel_de_read(dev_priv, dpll_reg) & port_mask, + expected_mask); +} + +void intel_enable_transcoder(const struct intel_crtc_state *new_crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(new_crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum transcoder cpu_transcoder = new_crtc_state->cpu_transcoder; + enum pipe pipe = crtc->pipe; + i915_reg_t reg; + u32 val; + + drm_dbg_kms(&dev_priv->drm, "enabling pipe %c\n", pipe_name(pipe)); + + assert_planes_disabled(crtc); + + /* + * A pipe without a PLL won't actually be able to drive bits from + * a plane. On ILK+ the pipe PLLs are integrated, so we don't + * need the check. + */ + if (HAS_GMCH(dev_priv)) { + if (intel_crtc_has_type(new_crtc_state, INTEL_OUTPUT_DSI)) + assert_dsi_pll_enabled(dev_priv); + else + assert_pll_enabled(dev_priv, pipe); + } else { + if (new_crtc_state->has_pch_encoder) { + /* if driving the PCH, we need FDI enabled */ + assert_fdi_rx_pll_enabled(dev_priv, + intel_crtc_pch_transcoder(crtc)); + assert_fdi_tx_pll_enabled(dev_priv, + (enum pipe) cpu_transcoder); + } + /* FIXME: assert CPU port conditions for SNB+ */ + } + + /* Wa_22012358565:adl-p */ + if (DISPLAY_VER(dev_priv) == 13) + intel_de_rmw(dev_priv, PIPE_ARB_CTL(pipe), + 0, PIPE_ARB_USE_PROG_SLOTS); + + reg = PIPECONF(cpu_transcoder); + val = intel_de_read(dev_priv, reg); + if (val & PIPECONF_ENABLE) { + /* we keep both pipes enabled on 830 */ + drm_WARN_ON(&dev_priv->drm, !IS_I830(dev_priv)); + return; + } + + intel_de_write(dev_priv, reg, val | PIPECONF_ENABLE); + intel_de_posting_read(dev_priv, reg); + + /* + * Until the pipe starts PIPEDSL reads will return a stale value, + * which causes an apparent vblank timestamp jump when PIPEDSL + * resets to its proper value. That also messes up the frame count + * when it's derived from the timestamps. So let's wait for the + * pipe to start properly before we call drm_crtc_vblank_on() + */ + if (intel_crtc_max_vblank_count(new_crtc_state) == 0) + intel_wait_for_pipe_scanline_moving(crtc); +} + +void intel_disable_transcoder(const struct intel_crtc_state *old_crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum transcoder cpu_transcoder = old_crtc_state->cpu_transcoder; + enum pipe pipe = crtc->pipe; + i915_reg_t reg; + u32 val; + + drm_dbg_kms(&dev_priv->drm, "disabling pipe %c\n", pipe_name(pipe)); + + /* + * Make sure planes won't keep trying to pump pixels to us, + * or we might hang the display. + */ + assert_planes_disabled(crtc); + + reg = PIPECONF(cpu_transcoder); + val = intel_de_read(dev_priv, reg); + if ((val & PIPECONF_ENABLE) == 0) + return; + + /* + * Double wide has implications for planes + * so best keep it disabled when not needed. + */ + if (old_crtc_state->double_wide) + val &= ~PIPECONF_DOUBLE_WIDE; + + /* Don't disable pipe or pipe PLLs if needed */ + if (!IS_I830(dev_priv)) + val &= ~PIPECONF_ENABLE; + + if (DISPLAY_VER(dev_priv) >= 14) + intel_de_rmw(dev_priv, MTL_CHICKEN_TRANS(cpu_transcoder), + FECSTALL_DIS_DPTSTREAM_DPTTG, 0); + else if (DISPLAY_VER(dev_priv) >= 12) + intel_de_rmw(dev_priv, CHICKEN_TRANS(cpu_transcoder), + FECSTALL_DIS_DPTSTREAM_DPTTG, 0); + + intel_de_write(dev_priv, reg, val); + if ((val & PIPECONF_ENABLE) == 0) + intel_wait_for_pipe_off(old_crtc_state); +} + +unsigned int intel_rotation_info_size(const struct intel_rotation_info *rot_info) +{ + unsigned int size = 0; + int i; + + for (i = 0 ; i < ARRAY_SIZE(rot_info->plane); i++) + size += rot_info->plane[i].dst_stride * rot_info->plane[i].width; + + return size; +} + +unsigned int intel_remapped_info_size(const struct intel_remapped_info *rem_info) +{ + unsigned int size = 0; + int i; + + for (i = 0 ; i < ARRAY_SIZE(rem_info->plane); i++) { + unsigned int plane_size; + + if (rem_info->plane[i].linear) + plane_size = rem_info->plane[i].size; + else + plane_size = rem_info->plane[i].dst_stride * rem_info->plane[i].height; + + if (plane_size == 0) + continue; + + if (rem_info->plane_alignment) + size = ALIGN(size, rem_info->plane_alignment); + + size += plane_size; + } + + return size; +} + +bool intel_plane_uses_fence(const struct intel_plane_state *plane_state) +{ + struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane); + struct drm_i915_private *dev_priv = to_i915(plane->base.dev); + + return DISPLAY_VER(dev_priv) < 4 || + (plane->fbc && + plane_state->view.gtt.type == I915_GTT_VIEW_NORMAL); +} + +/* + * Convert the x/y offsets into a linear offset. + * Only valid with 0/180 degree rotation, which is fine since linear + * offset is only used with linear buffers on pre-hsw and tiled buffers + * with gen2/3, and 90/270 degree rotations isn't supported on any of them. + */ +u32 intel_fb_xy_to_linear(int x, int y, + const struct intel_plane_state *state, + int color_plane) +{ + const struct drm_framebuffer *fb = state->hw.fb; + unsigned int cpp = fb->format->cpp[color_plane]; + unsigned int pitch = state->view.color_plane[color_plane].mapping_stride; + + return y * pitch + x * cpp; +} + +/* + * Add the x/y offsets derived from fb->offsets[] to the user + * specified plane src x/y offsets. The resulting x/y offsets + * specify the start of scanout from the beginning of the gtt mapping. + */ +void intel_add_fb_offsets(int *x, int *y, + const struct intel_plane_state *state, + int color_plane) + +{ + *x += state->view.color_plane[color_plane].x; + *y += state->view.color_plane[color_plane].y; +} + +u32 intel_plane_fb_max_stride(struct drm_i915_private *dev_priv, + u32 pixel_format, u64 modifier) +{ + struct intel_crtc *crtc; + struct intel_plane *plane; + + if (!HAS_DISPLAY(dev_priv)) + return 0; + + /* + * We assume the primary plane for pipe A has + * the highest stride limits of them all, + * if in case pipe A is disabled, use the first pipe from pipe_mask. + */ + crtc = intel_first_crtc(dev_priv); + if (!crtc) + return 0; + + plane = to_intel_plane(crtc->base.primary); + + return plane->max_stride(plane, pixel_format, modifier, + DRM_MODE_ROTATE_0); +} + +void intel_set_plane_visible(struct intel_crtc_state *crtc_state, + struct intel_plane_state *plane_state, + bool visible) +{ + struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane); + + plane_state->uapi.visible = visible; + + if (visible) + crtc_state->uapi.plane_mask |= drm_plane_mask(&plane->base); + else + crtc_state->uapi.plane_mask &= ~drm_plane_mask(&plane->base); +} + +void intel_plane_fixup_bitmasks(struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + struct drm_plane *plane; + + /* + * Active_planes aliases if multiple "primary" or cursor planes + * have been used on the same (or wrong) pipe. plane_mask uses + * unique ids, hence we can use that to reconstruct active_planes. + */ + crtc_state->enabled_planes = 0; + crtc_state->active_planes = 0; + + drm_for_each_plane_mask(plane, &dev_priv->drm, + crtc_state->uapi.plane_mask) { + crtc_state->enabled_planes |= BIT(to_intel_plane(plane)->id); + crtc_state->active_planes |= BIT(to_intel_plane(plane)->id); + } +} + +void intel_plane_disable_noatomic(struct intel_crtc *crtc, + struct intel_plane *plane) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_crtc_state *crtc_state = + to_intel_crtc_state(crtc->base.state); + struct intel_plane_state *plane_state = + to_intel_plane_state(plane->base.state); + + drm_dbg_kms(&dev_priv->drm, + "Disabling [PLANE:%d:%s] on [CRTC:%d:%s]\n", + plane->base.base.id, plane->base.name, + crtc->base.base.id, crtc->base.name); + + intel_set_plane_visible(crtc_state, plane_state, false); + intel_plane_fixup_bitmasks(crtc_state); + crtc_state->data_rate[plane->id] = 0; + crtc_state->data_rate_y[plane->id] = 0; + crtc_state->rel_data_rate[plane->id] = 0; + crtc_state->rel_data_rate_y[plane->id] = 0; + crtc_state->min_cdclk[plane->id] = 0; + + if ((crtc_state->active_planes & ~BIT(PLANE_CURSOR)) == 0 && + hsw_ips_disable(crtc_state)) { + crtc_state->ips_enabled = false; + intel_crtc_wait_for_next_vblank(crtc); + } + + /* + * Vblank time updates from the shadow to live plane control register + * are blocked if the memory self-refresh mode is active at that + * moment. So to make sure the plane gets truly disabled, disable + * first the self-refresh mode. The self-refresh enable bit in turn + * will be checked/applied by the HW only at the next frame start + * event which is after the vblank start event, so we need to have a + * wait-for-vblank between disabling the plane and the pipe. + */ + if (HAS_GMCH(dev_priv) && + intel_set_memory_cxsr(dev_priv, false)) + intel_crtc_wait_for_next_vblank(crtc); + + /* + * Gen2 reports pipe underruns whenever all planes are disabled. + * So disable underrun reporting before all the planes get disabled. + */ + if (DISPLAY_VER(dev_priv) == 2 && !crtc_state->active_planes) + intel_set_cpu_fifo_underrun_reporting(dev_priv, crtc->pipe, false); + + intel_plane_disable_arm(plane, crtc_state); + intel_crtc_wait_for_next_vblank(crtc); +} + +unsigned int +intel_plane_fence_y_offset(const struct intel_plane_state *plane_state) +{ + int x = 0, y = 0; + + intel_plane_adjust_aligned_offset(&x, &y, plane_state, 0, + plane_state->view.color_plane[0].offset, 0); + + return y; +} + +static int +__intel_display_resume(struct drm_i915_private *i915, + struct drm_atomic_state *state, + struct drm_modeset_acquire_ctx *ctx) +{ + struct drm_crtc_state *crtc_state; + struct drm_crtc *crtc; + int i, ret; + + intel_modeset_setup_hw_state(i915, ctx); + intel_vga_redisable(i915); + + if (!state) + return 0; + + /* + * We've duplicated the state, pointers to the old state are invalid. + * + * Don't attempt to use the old state until we commit the duplicated state. + */ + for_each_new_crtc_in_state(state, crtc, crtc_state, i) { + /* + * Force recalculation even if we restore + * current state. With fast modeset this may not result + * in a modeset when the state is compatible. + */ + crtc_state->mode_changed = true; + } + + /* ignore any reset values/BIOS leftovers in the WM registers */ + if (!HAS_GMCH(i915)) + to_intel_atomic_state(state)->skip_intermediate_wm = true; + + ret = drm_atomic_helper_commit_duplicated_state(state, ctx); + + drm_WARN_ON(&i915->drm, ret == -EDEADLK); + + return ret; +} + +static bool gpu_reset_clobbers_display(struct drm_i915_private *dev_priv) +{ + return (INTEL_INFO(dev_priv)->gpu_reset_clobbers_display && + intel_has_gpu_reset(to_gt(dev_priv))); +} + +void intel_display_prepare_reset(struct drm_i915_private *dev_priv) +{ + struct drm_device *dev = &dev_priv->drm; + struct drm_modeset_acquire_ctx *ctx = &dev_priv->reset_ctx; + struct drm_atomic_state *state; + int ret; + + if (!HAS_DISPLAY(dev_priv)) + return; + + /* reset doesn't touch the display */ + if (!dev_priv->params.force_reset_modeset_test && + !gpu_reset_clobbers_display(dev_priv)) + return; + + /* We have a modeset vs reset deadlock, defensively unbreak it. */ + set_bit(I915_RESET_MODESET, &to_gt(dev_priv)->reset.flags); + smp_mb__after_atomic(); + wake_up_bit(&to_gt(dev_priv)->reset.flags, I915_RESET_MODESET); + + if (atomic_read(&dev_priv->gpu_error.pending_fb_pin)) { + drm_dbg_kms(&dev_priv->drm, + "Modeset potentially stuck, unbreaking through wedging\n"); + intel_gt_set_wedged(to_gt(dev_priv)); + } + + /* + * Need mode_config.mutex so that we don't + * trample ongoing ->detect() and whatnot. + */ + mutex_lock(&dev->mode_config.mutex); + drm_modeset_acquire_init(ctx, 0); + while (1) { + ret = drm_modeset_lock_all_ctx(dev, ctx); + if (ret != -EDEADLK) + break; + + drm_modeset_backoff(ctx); + } + /* + * Disabling the crtcs gracefully seems nicer. Also the + * g33 docs say we should at least disable all the planes. + */ + state = drm_atomic_helper_duplicate_state(dev, ctx); + if (IS_ERR(state)) { + ret = PTR_ERR(state); + drm_err(&dev_priv->drm, "Duplicating state failed with %i\n", + ret); + return; + } + + ret = drm_atomic_helper_disable_all(dev, ctx); + if (ret) { + drm_err(&dev_priv->drm, "Suspending crtc's failed with %i\n", + ret); + drm_atomic_state_put(state); + return; + } + + dev_priv->modeset_restore_state = state; + state->acquire_ctx = ctx; +} + +void intel_display_finish_reset(struct drm_i915_private *i915) +{ + struct drm_modeset_acquire_ctx *ctx = &i915->reset_ctx; + struct drm_atomic_state *state; + int ret; + + if (!HAS_DISPLAY(i915)) + return; + + /* reset doesn't touch the display */ + if (!test_bit(I915_RESET_MODESET, &to_gt(i915)->reset.flags)) + return; + + state = fetch_and_zero(&i915->modeset_restore_state); + if (!state) + goto unlock; + + /* reset doesn't touch the display */ + if (!gpu_reset_clobbers_display(i915)) { + /* for testing only restore the display */ + ret = __intel_display_resume(i915, state, ctx); + if (ret) + drm_err(&i915->drm, + "Restoring old state failed with %i\n", ret); + } else { + /* + * The display has been reset as well, + * so need a full re-initialization. + */ + intel_pps_unlock_regs_wa(i915); + intel_modeset_init_hw(i915); + intel_init_clock_gating(i915); + intel_hpd_init(i915); + + ret = __intel_display_resume(i915, state, ctx); + if (ret) + drm_err(&i915->drm, + "Restoring old state failed with %i\n", ret); + + intel_hpd_poll_disable(i915); + } + + drm_atomic_state_put(state); +unlock: + drm_modeset_drop_locks(ctx); + drm_modeset_acquire_fini(ctx); + mutex_unlock(&i915->drm.mode_config.mutex); + + clear_bit_unlock(I915_RESET_MODESET, &to_gt(i915)->reset.flags); +} + +static void icl_set_pipe_chicken(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + u32 tmp; + + tmp = intel_de_read(dev_priv, PIPE_CHICKEN(pipe)); + + /* + * Display WA #1153: icl + * enable hardware to bypass the alpha math + * and rounding for per-pixel values 00 and 0xff + */ + tmp |= PER_PIXEL_ALPHA_BYPASS_EN; + /* + * Display WA # 1605353570: icl + * Set the pixel rounding bit to 1 for allowing + * passthrough of Frame buffer pixels unmodified + * across pipe + */ + tmp |= PIXEL_ROUNDING_TRUNC_FB_PASSTHRU; + + /* + * Underrun recovery must always be disabled on display 13+. + * DG2 chicken bit meaning is inverted compared to other platforms. + */ + if (IS_DG2(dev_priv)) + tmp &= ~UNDERRUN_RECOVERY_ENABLE_DG2; + else if (DISPLAY_VER(dev_priv) >= 13) + tmp |= UNDERRUN_RECOVERY_DISABLE_ADLP; + + /* Wa_14010547955:dg2 */ + if (IS_DG2_DISPLAY_STEP(dev_priv, STEP_B0, STEP_FOREVER)) + tmp |= DG2_RENDER_CCSTAG_4_3_EN; + + intel_de_write(dev_priv, PIPE_CHICKEN(pipe), tmp); +} + +bool intel_has_pending_fb_unpin(struct drm_i915_private *dev_priv) +{ + struct drm_crtc *crtc; + bool cleanup_done; + + drm_for_each_crtc(crtc, &dev_priv->drm) { + struct drm_crtc_commit *commit; + spin_lock(&crtc->commit_lock); + commit = list_first_entry_or_null(&crtc->commit_list, + struct drm_crtc_commit, commit_entry); + cleanup_done = commit ? + try_wait_for_completion(&commit->cleanup_done) : true; + spin_unlock(&crtc->commit_lock); + + if (cleanup_done) + continue; + + intel_crtc_wait_for_next_vblank(to_intel_crtc(crtc)); + + return true; + } + + return false; +} + +/* + * Finds the encoder associated with the given CRTC. This can only be + * used when we know that the CRTC isn't feeding multiple encoders! + */ +struct intel_encoder * +intel_get_crtc_new_encoder(const struct intel_atomic_state *state, + const struct intel_crtc_state *crtc_state) +{ + const struct drm_connector_state *connector_state; + const struct drm_connector *connector; + struct intel_encoder *encoder = NULL; + struct intel_crtc *master_crtc; + int num_encoders = 0; + int i; + + master_crtc = intel_master_crtc(crtc_state); + + for_each_new_connector_in_state(&state->base, connector, connector_state, i) { + if (connector_state->crtc != &master_crtc->base) + continue; + + encoder = to_intel_encoder(connector_state->best_encoder); + num_encoders++; + } + + drm_WARN(state->base.dev, num_encoders != 1, + "%d encoders for pipe %c\n", + num_encoders, pipe_name(master_crtc->pipe)); + + return encoder; +} + +static void cpt_verify_modeset(struct drm_i915_private *dev_priv, + enum pipe pipe) +{ + i915_reg_t dslreg = PIPEDSL(pipe); + u32 temp; + + temp = intel_de_read(dev_priv, dslreg); + udelay(500); + if (wait_for(intel_de_read(dev_priv, dslreg) != temp, 5)) { + if (wait_for(intel_de_read(dev_priv, dslreg) != temp, 5)) + drm_err(&dev_priv->drm, + "mode set failed: pipe %c stuck\n", + pipe_name(pipe)); + } +} + +static void ilk_pfit_enable(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + const struct drm_rect *dst = &crtc_state->pch_pfit.dst; + enum pipe pipe = crtc->pipe; + int width = drm_rect_width(dst); + int height = drm_rect_height(dst); + int x = dst->x1; + int y = dst->y1; + + if (!crtc_state->pch_pfit.enabled) + return; + + /* Force use of hard-coded filter coefficients + * as some pre-programmed values are broken, + * e.g. x201. + */ + if (IS_IVYBRIDGE(dev_priv) || IS_HASWELL(dev_priv)) + intel_de_write_fw(dev_priv, PF_CTL(pipe), PF_ENABLE | + PF_FILTER_MED_3x3 | PF_PIPE_SEL_IVB(pipe)); + else + intel_de_write_fw(dev_priv, PF_CTL(pipe), PF_ENABLE | + PF_FILTER_MED_3x3); + intel_de_write_fw(dev_priv, PF_WIN_POS(pipe), x << 16 | y); + intel_de_write_fw(dev_priv, PF_WIN_SZ(pipe), width << 16 | height); +} + +static void intel_crtc_dpms_overlay_disable(struct intel_crtc *crtc) +{ + if (crtc->overlay) + (void) intel_overlay_switch_off(crtc->overlay); + + /* Let userspace switch the overlay on again. In most cases userspace + * has to recompute where to put it anyway. + */ +} + +static bool needs_nv12_wa(const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + + if (!crtc_state->nv12_planes) + return false; + + /* WA Display #0827: Gen9:all */ + if (DISPLAY_VER(dev_priv) == 9) + return true; + + return false; +} + +static bool needs_scalerclk_wa(const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + + /* Wa_2006604312:icl,ehl */ + if (crtc_state->scaler_state.scaler_users > 0 && DISPLAY_VER(dev_priv) == 11) + return true; + + return false; +} + +static bool needs_cursorclk_wa(const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + + /* Wa_1604331009:icl,jsl,ehl */ + if (is_hdr_mode(crtc_state) && + crtc_state->active_planes & BIT(PLANE_CURSOR) && + DISPLAY_VER(dev_priv) == 11) + return true; + + return false; +} + +static void intel_async_flip_vtd_wa(struct drm_i915_private *i915, + enum pipe pipe, bool enable) +{ + if (DISPLAY_VER(i915) == 9) { + /* + * "Plane N strech max must be programmed to 11b (x1) + * when Async flips are enabled on that plane." + */ + intel_de_rmw(i915, CHICKEN_PIPESL_1(pipe), + SKL_PLANE1_STRETCH_MAX_MASK, + enable ? SKL_PLANE1_STRETCH_MAX_X1 : SKL_PLANE1_STRETCH_MAX_X8); + } else { + /* Also needed on HSW/BDW albeit undocumented */ + intel_de_rmw(i915, CHICKEN_PIPESL_1(pipe), + HSW_PRI_STRETCH_MAX_MASK, + enable ? HSW_PRI_STRETCH_MAX_X1 : HSW_PRI_STRETCH_MAX_X8); + } +} + +static bool needs_async_flip_vtd_wa(const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(crtc_state->uapi.crtc->dev); + + return crtc_state->uapi.async_flip && i915_vtd_active(i915) && + (DISPLAY_VER(i915) == 9 || IS_BROADWELL(i915) || IS_HASWELL(i915)); +} + +static bool planes_enabling(const struct intel_crtc_state *old_crtc_state, + const struct intel_crtc_state *new_crtc_state) +{ + return (!old_crtc_state->active_planes || intel_crtc_needs_modeset(new_crtc_state)) && + new_crtc_state->active_planes; +} + +static bool planes_disabling(const struct intel_crtc_state *old_crtc_state, + const struct intel_crtc_state *new_crtc_state) +{ + return old_crtc_state->active_planes && + (!new_crtc_state->active_planes || intel_crtc_needs_modeset(new_crtc_state)); +} + +static void intel_post_plane_update(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + const struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + enum pipe pipe = crtc->pipe; + + intel_frontbuffer_flip(dev_priv, new_crtc_state->fb_bits); + + if (new_crtc_state->update_wm_post && new_crtc_state->hw.active) + intel_update_watermarks(dev_priv); + + hsw_ips_post_update(state, crtc); + intel_fbc_post_update(state, crtc); + + if (needs_async_flip_vtd_wa(old_crtc_state) && + !needs_async_flip_vtd_wa(new_crtc_state)) + intel_async_flip_vtd_wa(dev_priv, pipe, false); + + if (needs_nv12_wa(old_crtc_state) && + !needs_nv12_wa(new_crtc_state)) + skl_wa_827(dev_priv, pipe, false); + + if (needs_scalerclk_wa(old_crtc_state) && + !needs_scalerclk_wa(new_crtc_state)) + icl_wa_scalerclkgating(dev_priv, pipe, false); + + if (needs_cursorclk_wa(old_crtc_state) && + !needs_cursorclk_wa(new_crtc_state)) + icl_wa_cursorclkgating(dev_priv, pipe, false); + + intel_drrs_activate(new_crtc_state); +} + +static void intel_crtc_enable_flip_done(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + u8 update_planes = crtc_state->update_planes; + const struct intel_plane_state *plane_state; + struct intel_plane *plane; + int i; + + for_each_new_intel_plane_in_state(state, plane, plane_state, i) { + if (plane->pipe == crtc->pipe && + update_planes & BIT(plane->id)) + plane->enable_flip_done(plane); + } +} + +static void intel_crtc_disable_flip_done(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + u8 update_planes = crtc_state->update_planes; + const struct intel_plane_state *plane_state; + struct intel_plane *plane; + int i; + + for_each_new_intel_plane_in_state(state, plane, plane_state, i) { + if (plane->pipe == crtc->pipe && + update_planes & BIT(plane->id)) + plane->disable_flip_done(plane); + } +} + +static void intel_crtc_async_flip_disable_wa(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + const struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + u8 update_planes = new_crtc_state->update_planes; + const struct intel_plane_state *old_plane_state; + struct intel_plane *plane; + bool need_vbl_wait = false; + int i; + + for_each_old_intel_plane_in_state(state, plane, old_plane_state, i) { + if (plane->need_async_flip_disable_wa && + plane->pipe == crtc->pipe && + update_planes & BIT(plane->id)) { + /* + * Apart from the async flip bit we want to + * preserve the old state for the plane. + */ + plane->async_flip(plane, old_crtc_state, + old_plane_state, false); + need_vbl_wait = true; + } + } + + if (need_vbl_wait) + intel_crtc_wait_for_next_vblank(crtc); +} + +static void intel_pre_plane_update(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + const struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + enum pipe pipe = crtc->pipe; + + intel_drrs_deactivate(old_crtc_state); + + intel_psr_pre_plane_update(state, crtc); + + if (hsw_ips_pre_update(state, crtc)) + intel_crtc_wait_for_next_vblank(crtc); + + if (intel_fbc_pre_update(state, crtc)) + intel_crtc_wait_for_next_vblank(crtc); + + if (!needs_async_flip_vtd_wa(old_crtc_state) && + needs_async_flip_vtd_wa(new_crtc_state)) + intel_async_flip_vtd_wa(dev_priv, pipe, true); + + /* Display WA 827 */ + if (!needs_nv12_wa(old_crtc_state) && + needs_nv12_wa(new_crtc_state)) + skl_wa_827(dev_priv, pipe, true); + + /* Wa_2006604312:icl,ehl */ + if (!needs_scalerclk_wa(old_crtc_state) && + needs_scalerclk_wa(new_crtc_state)) + icl_wa_scalerclkgating(dev_priv, pipe, true); + + /* Wa_1604331009:icl,jsl,ehl */ + if (!needs_cursorclk_wa(old_crtc_state) && + needs_cursorclk_wa(new_crtc_state)) + icl_wa_cursorclkgating(dev_priv, pipe, true); + + /* + * Vblank time updates from the shadow to live plane control register + * are blocked if the memory self-refresh mode is active at that + * moment. So to make sure the plane gets truly disabled, disable + * first the self-refresh mode. The self-refresh enable bit in turn + * will be checked/applied by the HW only at the next frame start + * event which is after the vblank start event, so we need to have a + * wait-for-vblank between disabling the plane and the pipe. + */ + if (HAS_GMCH(dev_priv) && old_crtc_state->hw.active && + new_crtc_state->disable_cxsr && intel_set_memory_cxsr(dev_priv, false)) + intel_crtc_wait_for_next_vblank(crtc); + + /* + * IVB workaround: must disable low power watermarks for at least + * one frame before enabling scaling. LP watermarks can be re-enabled + * when scaling is disabled. + * + * WaCxSRDisabledForSpriteScaling:ivb + */ + if (old_crtc_state->hw.active && + new_crtc_state->disable_lp_wm && ilk_disable_lp_wm(dev_priv)) + intel_crtc_wait_for_next_vblank(crtc); + + /* + * If we're doing a modeset we don't need to do any + * pre-vblank watermark programming here. + */ + if (!intel_crtc_needs_modeset(new_crtc_state)) { + /* + * For platforms that support atomic watermarks, program the + * 'intermediate' watermarks immediately. On pre-gen9 platforms, these + * will be the intermediate values that are safe for both pre- and + * post- vblank; when vblank happens, the 'active' values will be set + * to the final 'target' values and we'll do this again to get the + * optimal watermarks. For gen9+ platforms, the values we program here + * will be the final target values which will get automatically latched + * at vblank time; no further programming will be necessary. + * + * If a platform hasn't been transitioned to atomic watermarks yet, + * we'll continue to update watermarks the old way, if flags tell + * us to. + */ + if (!intel_initial_watermarks(state, crtc)) + if (new_crtc_state->update_wm_pre) + intel_update_watermarks(dev_priv); + } + + /* + * Gen2 reports pipe underruns whenever all planes are disabled. + * So disable underrun reporting before all the planes get disabled. + * + * We do this after .initial_watermarks() so that we have a + * chance of catching underruns with the intermediate watermarks + * vs. the old plane configuration. + */ + if (DISPLAY_VER(dev_priv) == 2 && planes_disabling(old_crtc_state, new_crtc_state)) + intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, false); + + /* + * WA for platforms where async address update enable bit + * is double buffered and only latched at start of vblank. + */ + if (old_crtc_state->uapi.async_flip && !new_crtc_state->uapi.async_flip) + intel_crtc_async_flip_disable_wa(state, crtc); +} + +static void intel_crtc_disable_planes(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + const struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + unsigned int update_mask = new_crtc_state->update_planes; + const struct intel_plane_state *old_plane_state; + struct intel_plane *plane; + unsigned fb_bits = 0; + int i; + + intel_crtc_dpms_overlay_disable(crtc); + + for_each_old_intel_plane_in_state(state, plane, old_plane_state, i) { + if (crtc->pipe != plane->pipe || + !(update_mask & BIT(plane->id))) + continue; + + intel_plane_disable_arm(plane, new_crtc_state); + + if (old_plane_state->uapi.visible) + fb_bits |= plane->frontbuffer_bit; + } + + intel_frontbuffer_flip(dev_priv, fb_bits); +} + +/* + * intel_connector_primary_encoder - get the primary encoder for a connector + * @connector: connector for which to return the encoder + * + * Returns the primary encoder for a connector. There is a 1:1 mapping from + * all connectors to their encoder, except for DP-MST connectors which have + * both a virtual and a primary encoder. These DP-MST primary encoders can be + * pointed to by as many DP-MST connectors as there are pipes. + */ +static struct intel_encoder * +intel_connector_primary_encoder(struct intel_connector *connector) +{ + struct intel_encoder *encoder; + + if (connector->mst_port) + return &dp_to_dig_port(connector->mst_port)->base; + + encoder = intel_attached_encoder(connector); + drm_WARN_ON(connector->base.dev, !encoder); + + return encoder; +} + +static void intel_encoders_update_prepare(struct intel_atomic_state *state) +{ + struct drm_i915_private *i915 = to_i915(state->base.dev); + struct intel_crtc_state *new_crtc_state, *old_crtc_state; + struct intel_crtc *crtc; + struct drm_connector_state *new_conn_state; + struct drm_connector *connector; + int i; + + /* + * Make sure the DPLL state is up-to-date for fastset TypeC ports after non-blocking commits. + * TODO: Update the DPLL state for all cases in the encoder->update_prepare() hook. + */ + if (i915->display.dpll.mgr) { + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { + if (intel_crtc_needs_modeset(new_crtc_state)) + continue; + + new_crtc_state->shared_dpll = old_crtc_state->shared_dpll; + new_crtc_state->dpll_hw_state = old_crtc_state->dpll_hw_state; + } + } + + if (!state->modeset) + return; + + for_each_new_connector_in_state(&state->base, connector, new_conn_state, + i) { + struct intel_connector *intel_connector; + struct intel_encoder *encoder; + struct intel_crtc *crtc; + + if (!intel_connector_needs_modeset(state, connector)) + continue; + + intel_connector = to_intel_connector(connector); + encoder = intel_connector_primary_encoder(intel_connector); + if (!encoder->update_prepare) + continue; + + crtc = new_conn_state->crtc ? + to_intel_crtc(new_conn_state->crtc) : NULL; + encoder->update_prepare(state, encoder, crtc); + } +} + +static void intel_encoders_update_complete(struct intel_atomic_state *state) +{ + struct drm_connector_state *new_conn_state; + struct drm_connector *connector; + int i; + + if (!state->modeset) + return; + + for_each_new_connector_in_state(&state->base, connector, new_conn_state, + i) { + struct intel_connector *intel_connector; + struct intel_encoder *encoder; + struct intel_crtc *crtc; + + if (!intel_connector_needs_modeset(state, connector)) + continue; + + intel_connector = to_intel_connector(connector); + encoder = intel_connector_primary_encoder(intel_connector); + if (!encoder->update_complete) + continue; + + crtc = new_conn_state->crtc ? + to_intel_crtc(new_conn_state->crtc) : NULL; + encoder->update_complete(state, encoder, crtc); + } +} + +static void intel_encoders_pre_pll_enable(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + const struct drm_connector_state *conn_state; + struct drm_connector *conn; + int i; + + for_each_new_connector_in_state(&state->base, conn, conn_state, i) { + struct intel_encoder *encoder = + to_intel_encoder(conn_state->best_encoder); + + if (conn_state->crtc != &crtc->base) + continue; + + if (encoder->pre_pll_enable) + encoder->pre_pll_enable(state, encoder, + crtc_state, conn_state); + } +} + +static void intel_encoders_pre_enable(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + const struct drm_connector_state *conn_state; + struct drm_connector *conn; + int i; + + for_each_new_connector_in_state(&state->base, conn, conn_state, i) { + struct intel_encoder *encoder = + to_intel_encoder(conn_state->best_encoder); + + if (conn_state->crtc != &crtc->base) + continue; + + if (encoder->pre_enable) + encoder->pre_enable(state, encoder, + crtc_state, conn_state); + } +} + +static void intel_encoders_enable(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + const struct drm_connector_state *conn_state; + struct drm_connector *conn; + int i; + + for_each_new_connector_in_state(&state->base, conn, conn_state, i) { + struct intel_encoder *encoder = + to_intel_encoder(conn_state->best_encoder); + + if (conn_state->crtc != &crtc->base) + continue; + + if (encoder->enable) + encoder->enable(state, encoder, + crtc_state, conn_state); + intel_opregion_notify_encoder(encoder, true); + } +} + +static void intel_encoders_disable(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + const struct drm_connector_state *old_conn_state; + struct drm_connector *conn; + int i; + + for_each_old_connector_in_state(&state->base, conn, old_conn_state, i) { + struct intel_encoder *encoder = + to_intel_encoder(old_conn_state->best_encoder); + + if (old_conn_state->crtc != &crtc->base) + continue; + + intel_opregion_notify_encoder(encoder, false); + if (encoder->disable) + encoder->disable(state, encoder, + old_crtc_state, old_conn_state); + } +} + +static void intel_encoders_post_disable(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + const struct drm_connector_state *old_conn_state; + struct drm_connector *conn; + int i; + + for_each_old_connector_in_state(&state->base, conn, old_conn_state, i) { + struct intel_encoder *encoder = + to_intel_encoder(old_conn_state->best_encoder); + + if (old_conn_state->crtc != &crtc->base) + continue; + + if (encoder->post_disable) + encoder->post_disable(state, encoder, + old_crtc_state, old_conn_state); + } +} + +static void intel_encoders_post_pll_disable(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + const struct drm_connector_state *old_conn_state; + struct drm_connector *conn; + int i; + + for_each_old_connector_in_state(&state->base, conn, old_conn_state, i) { + struct intel_encoder *encoder = + to_intel_encoder(old_conn_state->best_encoder); + + if (old_conn_state->crtc != &crtc->base) + continue; + + if (encoder->post_pll_disable) + encoder->post_pll_disable(state, encoder, + old_crtc_state, old_conn_state); + } +} + +static void intel_encoders_update_pipe(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + const struct drm_connector_state *conn_state; + struct drm_connector *conn; + int i; + + for_each_new_connector_in_state(&state->base, conn, conn_state, i) { + struct intel_encoder *encoder = + to_intel_encoder(conn_state->best_encoder); + + if (conn_state->crtc != &crtc->base) + continue; + + if (encoder->update_pipe) + encoder->update_pipe(state, encoder, + crtc_state, conn_state); + } +} + +static void intel_disable_primary_plane(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct intel_plane *plane = to_intel_plane(crtc->base.primary); + + plane->disable_arm(plane, crtc_state); +} + +static void ilk_configure_cpu_transcoder(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + + if (crtc_state->has_pch_encoder) { + intel_cpu_transcoder_set_m1_n1(crtc, cpu_transcoder, + &crtc_state->fdi_m_n); + } else if (intel_crtc_has_dp_encoder(crtc_state)) { + intel_cpu_transcoder_set_m1_n1(crtc, cpu_transcoder, + &crtc_state->dp_m_n); + intel_cpu_transcoder_set_m2_n2(crtc, cpu_transcoder, + &crtc_state->dp_m2_n2); + } + + intel_set_transcoder_timings(crtc_state); + + ilk_set_pipeconf(crtc_state); +} + +static void ilk_crtc_enable(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + + if (drm_WARN_ON(&dev_priv->drm, crtc->active)) + return; + + /* + * Sometimes spurious CPU pipe underruns happen during FDI + * training, at least with VGA+HDMI cloning. Suppress them. + * + * On ILK we get an occasional spurious CPU pipe underruns + * between eDP port A enable and vdd enable. Also PCH port + * enable seems to result in the occasional CPU pipe underrun. + * + * Spurious PCH underruns also occur during PCH enabling. + */ + intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, false); + intel_set_pch_fifo_underrun_reporting(dev_priv, pipe, false); + + ilk_configure_cpu_transcoder(new_crtc_state); + + intel_set_pipe_src_size(new_crtc_state); + + crtc->active = true; + + intel_encoders_pre_enable(state, crtc); + + if (new_crtc_state->has_pch_encoder) { + ilk_pch_pre_enable(state, crtc); + } else { + assert_fdi_tx_disabled(dev_priv, pipe); + assert_fdi_rx_disabled(dev_priv, pipe); + } + + ilk_pfit_enable(new_crtc_state); + + /* + * On ILK+ LUT must be loaded before the pipe is running but with + * clocks enabled + */ + intel_color_load_luts(new_crtc_state); + intel_color_commit_noarm(new_crtc_state); + intel_color_commit_arm(new_crtc_state); + /* update DSPCNTR to configure gamma for pipe bottom color */ + intel_disable_primary_plane(new_crtc_state); + + intel_initial_watermarks(state, crtc); + intel_enable_transcoder(new_crtc_state); + + if (new_crtc_state->has_pch_encoder) + ilk_pch_enable(state, crtc); + + intel_crtc_vblank_on(new_crtc_state); + + intel_encoders_enable(state, crtc); + + if (HAS_PCH_CPT(dev_priv)) + cpt_verify_modeset(dev_priv, pipe); + + /* + * Must wait for vblank to avoid spurious PCH FIFO underruns. + * And a second vblank wait is needed at least on ILK with + * some interlaced HDMI modes. Let's do the double wait always + * in case there are more corner cases we don't know about. + */ + if (new_crtc_state->has_pch_encoder) { + intel_crtc_wait_for_next_vblank(crtc); + intel_crtc_wait_for_next_vblank(crtc); + } + intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, true); + intel_set_pch_fifo_underrun_reporting(dev_priv, pipe, true); +} + +static void glk_pipe_scaler_clock_gating_wa(struct drm_i915_private *dev_priv, + enum pipe pipe, bool apply) +{ + u32 val = intel_de_read(dev_priv, CLKGATE_DIS_PSL(pipe)); + u32 mask = DPF_GATING_DIS | DPF_RAM_GATING_DIS | DPFR_GATING_DIS; + + if (apply) + val |= mask; + else + val &= ~mask; + + intel_de_write(dev_priv, CLKGATE_DIS_PSL(pipe), val); +} + +static void hsw_set_linetime_wm(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + + intel_de_write(dev_priv, WM_LINETIME(crtc->pipe), + HSW_LINETIME(crtc_state->linetime) | + HSW_IPS_LINETIME(crtc_state->ips_linetime)); +} + +static void hsw_set_frame_start_delay(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum transcoder transcoder = crtc_state->cpu_transcoder; + i915_reg_t reg = DISPLAY_VER(dev_priv) >= 14 ? MTL_CHICKEN_TRANS(transcoder) : + CHICKEN_TRANS(transcoder); + u32 val; + + val = intel_de_read(dev_priv, reg); + val &= ~HSW_FRAME_START_DELAY_MASK; + val |= HSW_FRAME_START_DELAY(crtc_state->framestart_delay - 1); + intel_de_write(dev_priv, reg, val); +} + +static void icl_ddi_bigjoiner_pre_enable(struct intel_atomic_state *state, + const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *master_crtc = intel_master_crtc(crtc_state); + + /* + * Enable sequence steps 1-7 on bigjoiner master + */ + if (intel_crtc_is_bigjoiner_slave(crtc_state)) + intel_encoders_pre_pll_enable(state, master_crtc); + + if (crtc_state->shared_dpll) + intel_enable_shared_dpll(crtc_state); + + if (intel_crtc_is_bigjoiner_slave(crtc_state)) + intel_encoders_pre_enable(state, master_crtc); +} + +static void hsw_configure_cpu_transcoder(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + + if (crtc_state->has_pch_encoder) { + intel_cpu_transcoder_set_m1_n1(crtc, cpu_transcoder, + &crtc_state->fdi_m_n); + } else if (intel_crtc_has_dp_encoder(crtc_state)) { + intel_cpu_transcoder_set_m1_n1(crtc, cpu_transcoder, + &crtc_state->dp_m_n); + intel_cpu_transcoder_set_m2_n2(crtc, cpu_transcoder, + &crtc_state->dp_m2_n2); + } + + intel_set_transcoder_timings(crtc_state); + + if (cpu_transcoder != TRANSCODER_EDP) + intel_de_write(dev_priv, PIPE_MULT(cpu_transcoder), + crtc_state->pixel_multiplier - 1); + + hsw_set_frame_start_delay(crtc_state); + + hsw_set_transconf(crtc_state); +} + +static void hsw_crtc_enable(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe, hsw_workaround_pipe; + enum transcoder cpu_transcoder = new_crtc_state->cpu_transcoder; + bool psl_clkgate_wa; + + if (drm_WARN_ON(&dev_priv->drm, crtc->active)) + return; + + if (!new_crtc_state->bigjoiner_pipes) { + intel_encoders_pre_pll_enable(state, crtc); + + if (new_crtc_state->shared_dpll) + intel_enable_shared_dpll(new_crtc_state); + + intel_encoders_pre_enable(state, crtc); + } else { + icl_ddi_bigjoiner_pre_enable(state, new_crtc_state); + } + + intel_dsc_enable(new_crtc_state); + + if (DISPLAY_VER(dev_priv) >= 13) + intel_uncompressed_joiner_enable(new_crtc_state); + + intel_set_pipe_src_size(new_crtc_state); + if (DISPLAY_VER(dev_priv) >= 9 || IS_BROADWELL(dev_priv)) + bdw_set_pipemisc(new_crtc_state); + + if (!intel_crtc_is_bigjoiner_slave(new_crtc_state) && + !transcoder_is_dsi(cpu_transcoder)) + hsw_configure_cpu_transcoder(new_crtc_state); + + crtc->active = true; + + /* Display WA #1180: WaDisableScalarClockGating: glk */ + psl_clkgate_wa = DISPLAY_VER(dev_priv) == 10 && + new_crtc_state->pch_pfit.enabled; + if (psl_clkgate_wa) + glk_pipe_scaler_clock_gating_wa(dev_priv, pipe, true); + + if (DISPLAY_VER(dev_priv) >= 9) + skl_pfit_enable(new_crtc_state); + else + ilk_pfit_enable(new_crtc_state); + + /* + * On ILK+ LUT must be loaded before the pipe is running but with + * clocks enabled + */ + intel_color_load_luts(new_crtc_state); + intel_color_commit_noarm(new_crtc_state); + intel_color_commit_arm(new_crtc_state); + /* update DSPCNTR to configure gamma/csc for pipe bottom color */ + if (DISPLAY_VER(dev_priv) < 9) + intel_disable_primary_plane(new_crtc_state); + + hsw_set_linetime_wm(new_crtc_state); + + if (DISPLAY_VER(dev_priv) >= 11) + icl_set_pipe_chicken(new_crtc_state); + + intel_initial_watermarks(state, crtc); + + if (intel_crtc_is_bigjoiner_slave(new_crtc_state)) + intel_crtc_vblank_on(new_crtc_state); + + intel_encoders_enable(state, crtc); + + if (psl_clkgate_wa) { + intel_crtc_wait_for_next_vblank(crtc); + glk_pipe_scaler_clock_gating_wa(dev_priv, pipe, false); + } + + /* If we change the relative order between pipe/planes enabling, we need + * to change the workaround. */ + hsw_workaround_pipe = new_crtc_state->hsw_workaround_pipe; + if (IS_HASWELL(dev_priv) && hsw_workaround_pipe != INVALID_PIPE) { + struct intel_crtc *wa_crtc; + + wa_crtc = intel_crtc_for_pipe(dev_priv, hsw_workaround_pipe); + + intel_crtc_wait_for_next_vblank(wa_crtc); + intel_crtc_wait_for_next_vblank(wa_crtc); + } +} + +void ilk_pfit_disable(const struct intel_crtc_state *old_crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + + /* To avoid upsetting the power well on haswell only disable the pfit if + * it's in use. The hw state code will make sure we get this right. */ + if (!old_crtc_state->pch_pfit.enabled) + return; + + intel_de_write_fw(dev_priv, PF_CTL(pipe), 0); + intel_de_write_fw(dev_priv, PF_WIN_POS(pipe), 0); + intel_de_write_fw(dev_priv, PF_WIN_SZ(pipe), 0); +} + +static void ilk_crtc_disable(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + + /* + * Sometimes spurious CPU pipe underruns happen when the + * pipe is already disabled, but FDI RX/TX is still enabled. + * Happens at least with VGA+HDMI cloning. Suppress them. + */ + intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, false); + intel_set_pch_fifo_underrun_reporting(dev_priv, pipe, false); + + intel_encoders_disable(state, crtc); + + intel_crtc_vblank_off(old_crtc_state); + + intel_disable_transcoder(old_crtc_state); + + ilk_pfit_disable(old_crtc_state); + + if (old_crtc_state->has_pch_encoder) + ilk_pch_disable(state, crtc); + + intel_encoders_post_disable(state, crtc); + + if (old_crtc_state->has_pch_encoder) + ilk_pch_post_disable(state, crtc); + + intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, true); + intel_set_pch_fifo_underrun_reporting(dev_priv, pipe, true); +} + +static void hsw_crtc_disable(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + + /* + * FIXME collapse everything to one hook. + * Need care with mst->ddi interactions. + */ + if (!intel_crtc_is_bigjoiner_slave(old_crtc_state)) { + intel_encoders_disable(state, crtc); + intel_encoders_post_disable(state, crtc); + } +} + +static void i9xx_pfit_enable(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + + if (!crtc_state->gmch_pfit.control) + return; + + /* + * The panel fitter should only be adjusted whilst the pipe is disabled, + * according to register description and PRM. + */ + drm_WARN_ON(&dev_priv->drm, + intel_de_read(dev_priv, PFIT_CONTROL) & PFIT_ENABLE); + assert_transcoder_disabled(dev_priv, crtc_state->cpu_transcoder); + + intel_de_write(dev_priv, PFIT_PGM_RATIOS, + crtc_state->gmch_pfit.pgm_ratios); + intel_de_write(dev_priv, PFIT_CONTROL, crtc_state->gmch_pfit.control); + + /* Border color in case we don't scale up to the full screen. Black by + * default, change to something else for debugging. */ + intel_de_write(dev_priv, BCLRPAT(crtc->pipe), 0); +} + +bool intel_phy_is_combo(struct drm_i915_private *dev_priv, enum phy phy) +{ + if (phy == PHY_NONE) + return false; + else if (IS_ALDERLAKE_S(dev_priv)) + return phy <= PHY_E; + else if (IS_DG1(dev_priv) || IS_ROCKETLAKE(dev_priv)) + return phy <= PHY_D; + else if (IS_JSL_EHL(dev_priv)) + return phy <= PHY_C; + else if (IS_ALDERLAKE_P(dev_priv) || IS_DISPLAY_VER(dev_priv, 11, 12)) + return phy <= PHY_B; + else + /* + * DG2 outputs labelled as "combo PHY" in the bspec use + * SNPS PHYs with completely different programming, + * hence we always return false here. + */ + return false; +} + +bool intel_phy_is_tc(struct drm_i915_private *dev_priv, enum phy phy) +{ + if (IS_DG2(dev_priv)) + /* DG2's "TC1" output uses a SNPS PHY */ + return false; + else if (IS_ALDERLAKE_P(dev_priv)) + return phy >= PHY_F && phy <= PHY_I; + else if (IS_TIGERLAKE(dev_priv)) + return phy >= PHY_D && phy <= PHY_I; + else if (IS_ICELAKE(dev_priv)) + return phy >= PHY_C && phy <= PHY_F; + else + return false; +} + +bool intel_phy_is_snps(struct drm_i915_private *dev_priv, enum phy phy) +{ + if (phy == PHY_NONE) + return false; + else if (IS_DG2(dev_priv)) + /* + * All four "combo" ports and the TC1 port (PHY E) use + * Synopsis PHYs. + */ + return phy <= PHY_E; + + return false; +} + +enum phy intel_port_to_phy(struct drm_i915_private *i915, enum port port) +{ + if (DISPLAY_VER(i915) >= 13 && port >= PORT_D_XELPD) + return PHY_D + port - PORT_D_XELPD; + else if (DISPLAY_VER(i915) >= 13 && port >= PORT_TC1) + return PHY_F + port - PORT_TC1; + else if (IS_ALDERLAKE_S(i915) && port >= PORT_TC1) + return PHY_B + port - PORT_TC1; + else if ((IS_DG1(i915) || IS_ROCKETLAKE(i915)) && port >= PORT_TC1) + return PHY_C + port - PORT_TC1; + else if (IS_JSL_EHL(i915) && port == PORT_D) + return PHY_A; + + return PHY_A + port - PORT_A; +} + +enum tc_port intel_port_to_tc(struct drm_i915_private *dev_priv, enum port port) +{ + if (!intel_phy_is_tc(dev_priv, intel_port_to_phy(dev_priv, port))) + return TC_PORT_NONE; + + if (DISPLAY_VER(dev_priv) >= 12) + return TC_PORT_1 + port - PORT_TC1; + else + return TC_PORT_1 + port - PORT_C; +} + +enum intel_display_power_domain +intel_aux_power_domain(struct intel_digital_port *dig_port) +{ + struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev); + + if (intel_tc_port_in_tbt_alt_mode(dig_port)) + return intel_display_power_tbt_aux_domain(i915, dig_port->aux_ch); + + return intel_display_power_legacy_aux_domain(i915, dig_port->aux_ch); +} + +static void get_crtc_power_domains(struct intel_crtc_state *crtc_state, + struct intel_power_domain_mask *mask) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + struct drm_encoder *encoder; + enum pipe pipe = crtc->pipe; + + bitmap_zero(mask->bits, POWER_DOMAIN_NUM); + + if (!crtc_state->hw.active) + return; + + set_bit(POWER_DOMAIN_PIPE(pipe), mask->bits); + set_bit(POWER_DOMAIN_TRANSCODER(cpu_transcoder), mask->bits); + if (crtc_state->pch_pfit.enabled || + crtc_state->pch_pfit.force_thru) + set_bit(POWER_DOMAIN_PIPE_PANEL_FITTER(pipe), mask->bits); + + drm_for_each_encoder_mask(encoder, &dev_priv->drm, + crtc_state->uapi.encoder_mask) { + struct intel_encoder *intel_encoder = to_intel_encoder(encoder); + + set_bit(intel_encoder->power_domain, mask->bits); + } + + if (HAS_DDI(dev_priv) && crtc_state->has_audio) + set_bit(POWER_DOMAIN_AUDIO_MMIO, mask->bits); + + if (crtc_state->shared_dpll) + set_bit(POWER_DOMAIN_DISPLAY_CORE, mask->bits); + + if (crtc_state->dsc.compression_enable) + set_bit(intel_dsc_power_domain(crtc, cpu_transcoder), mask->bits); +} + +void intel_modeset_get_crtc_power_domains(struct intel_crtc_state *crtc_state, + struct intel_power_domain_mask *old_domains) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum intel_display_power_domain domain; + struct intel_power_domain_mask domains, new_domains; + + get_crtc_power_domains(crtc_state, &domains); + + bitmap_andnot(new_domains.bits, + domains.bits, + crtc->enabled_power_domains.mask.bits, + POWER_DOMAIN_NUM); + bitmap_andnot(old_domains->bits, + crtc->enabled_power_domains.mask.bits, + domains.bits, + POWER_DOMAIN_NUM); + + for_each_power_domain(domain, &new_domains) + intel_display_power_get_in_set(dev_priv, + &crtc->enabled_power_domains, + domain); +} + +void intel_modeset_put_crtc_power_domains(struct intel_crtc *crtc, + struct intel_power_domain_mask *domains) +{ + intel_display_power_put_mask_in_set(to_i915(crtc->base.dev), + &crtc->enabled_power_domains, + domains); +} + +static void i9xx_configure_cpu_transcoder(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + + if (intel_crtc_has_dp_encoder(crtc_state)) { + intel_cpu_transcoder_set_m1_n1(crtc, cpu_transcoder, + &crtc_state->dp_m_n); + intel_cpu_transcoder_set_m2_n2(crtc, cpu_transcoder, + &crtc_state->dp_m2_n2); + } + + intel_set_transcoder_timings(crtc_state); + + i9xx_set_pipeconf(crtc_state); +} + +static void valleyview_crtc_enable(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + + if (drm_WARN_ON(&dev_priv->drm, crtc->active)) + return; + + i9xx_configure_cpu_transcoder(new_crtc_state); + + intel_set_pipe_src_size(new_crtc_state); + + if (IS_CHERRYVIEW(dev_priv) && pipe == PIPE_B) { + intel_de_write(dev_priv, CHV_BLEND(pipe), CHV_BLEND_LEGACY); + intel_de_write(dev_priv, CHV_CANVAS(pipe), 0); + } + + crtc->active = true; + + intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, true); + + intel_encoders_pre_pll_enable(state, crtc); + + if (IS_CHERRYVIEW(dev_priv)) + chv_enable_pll(new_crtc_state); + else + vlv_enable_pll(new_crtc_state); + + intel_encoders_pre_enable(state, crtc); + + i9xx_pfit_enable(new_crtc_state); + + intel_color_load_luts(new_crtc_state); + intel_color_commit_noarm(new_crtc_state); + intel_color_commit_arm(new_crtc_state); + /* update DSPCNTR to configure gamma for pipe bottom color */ + intel_disable_primary_plane(new_crtc_state); + + intel_initial_watermarks(state, crtc); + intel_enable_transcoder(new_crtc_state); + + intel_crtc_vblank_on(new_crtc_state); + + intel_encoders_enable(state, crtc); +} + +static void i9xx_crtc_enable(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + + if (drm_WARN_ON(&dev_priv->drm, crtc->active)) + return; + + i9xx_configure_cpu_transcoder(new_crtc_state); + + intel_set_pipe_src_size(new_crtc_state); + + crtc->active = true; + + if (DISPLAY_VER(dev_priv) != 2) + intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, true); + + intel_encoders_pre_enable(state, crtc); + + i9xx_enable_pll(new_crtc_state); + + i9xx_pfit_enable(new_crtc_state); + + intel_color_load_luts(new_crtc_state); + intel_color_commit_noarm(new_crtc_state); + intel_color_commit_arm(new_crtc_state); + /* update DSPCNTR to configure gamma for pipe bottom color */ + intel_disable_primary_plane(new_crtc_state); + + if (!intel_initial_watermarks(state, crtc)) + intel_update_watermarks(dev_priv); + intel_enable_transcoder(new_crtc_state); + + intel_crtc_vblank_on(new_crtc_state); + + intel_encoders_enable(state, crtc); + + /* prevents spurious underruns */ + if (DISPLAY_VER(dev_priv) == 2) + intel_crtc_wait_for_next_vblank(crtc); +} + +static void i9xx_pfit_disable(const struct intel_crtc_state *old_crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + + if (!old_crtc_state->gmch_pfit.control) + return; + + assert_transcoder_disabled(dev_priv, old_crtc_state->cpu_transcoder); + + drm_dbg_kms(&dev_priv->drm, "disabling pfit, current: 0x%08x\n", + intel_de_read(dev_priv, PFIT_CONTROL)); + intel_de_write(dev_priv, PFIT_CONTROL, 0); +} + +static void i9xx_crtc_disable(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + + /* + * On gen2 planes are double buffered but the pipe isn't, so we must + * wait for planes to fully turn off before disabling the pipe. + */ + if (DISPLAY_VER(dev_priv) == 2) + intel_crtc_wait_for_next_vblank(crtc); + + intel_encoders_disable(state, crtc); + + intel_crtc_vblank_off(old_crtc_state); + + intel_disable_transcoder(old_crtc_state); + + i9xx_pfit_disable(old_crtc_state); + + intel_encoders_post_disable(state, crtc); + + if (!intel_crtc_has_type(old_crtc_state, INTEL_OUTPUT_DSI)) { + if (IS_CHERRYVIEW(dev_priv)) + chv_disable_pll(dev_priv, pipe); + else if (IS_VALLEYVIEW(dev_priv)) + vlv_disable_pll(dev_priv, pipe); + else + i9xx_disable_pll(old_crtc_state); + } + + intel_encoders_post_pll_disable(state, crtc); + + if (DISPLAY_VER(dev_priv) != 2) + intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, false); + + if (!dev_priv->display.funcs.wm->initial_watermarks) + intel_update_watermarks(dev_priv); + + /* clock the pipe down to 640x480@60 to potentially save power */ + if (IS_I830(dev_priv)) + i830_enable_pipe(dev_priv, pipe); +} + + +/* + * turn all crtc's off, but do not adjust state + * This has to be paired with a call to intel_modeset_setup_hw_state. + */ +int intel_display_suspend(struct drm_device *dev) +{ + struct drm_i915_private *dev_priv = to_i915(dev); + struct drm_atomic_state *state; + int ret; + + if (!HAS_DISPLAY(dev_priv)) + return 0; + + state = drm_atomic_helper_suspend(dev); + ret = PTR_ERR_OR_ZERO(state); + if (ret) + drm_err(&dev_priv->drm, "Suspending crtc's failed with %i\n", + ret); + else + dev_priv->modeset_restore_state = state; + return ret; +} + +void intel_encoder_destroy(struct drm_encoder *encoder) +{ + struct intel_encoder *intel_encoder = to_intel_encoder(encoder); + + drm_encoder_cleanup(encoder); + kfree(intel_encoder); +} + +static bool intel_crtc_supports_double_wide(const struct intel_crtc *crtc) +{ + const struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + + /* GDG double wide on either pipe, otherwise pipe A only */ + return DISPLAY_VER(dev_priv) < 4 && + (crtc->pipe == PIPE_A || IS_I915G(dev_priv)); +} + +static u32 ilk_pipe_pixel_rate(const struct intel_crtc_state *crtc_state) +{ + u32 pixel_rate = crtc_state->hw.pipe_mode.crtc_clock; + struct drm_rect src; + + /* + * We only use IF-ID interlacing. If we ever use + * PF-ID we'll need to adjust the pixel_rate here. + */ + + if (!crtc_state->pch_pfit.enabled) + return pixel_rate; + + drm_rect_init(&src, 0, 0, + drm_rect_width(&crtc_state->pipe_src) << 16, + drm_rect_height(&crtc_state->pipe_src) << 16); + + return intel_adjusted_rate(&src, &crtc_state->pch_pfit.dst, + pixel_rate); +} + +static void intel_mode_from_crtc_timings(struct drm_display_mode *mode, + const struct drm_display_mode *timings) +{ + mode->hdisplay = timings->crtc_hdisplay; + mode->htotal = timings->crtc_htotal; + mode->hsync_start = timings->crtc_hsync_start; + mode->hsync_end = timings->crtc_hsync_end; + + mode->vdisplay = timings->crtc_vdisplay; + mode->vtotal = timings->crtc_vtotal; + mode->vsync_start = timings->crtc_vsync_start; + mode->vsync_end = timings->crtc_vsync_end; + + mode->flags = timings->flags; + mode->type = DRM_MODE_TYPE_DRIVER; + + mode->clock = timings->crtc_clock; + + drm_mode_set_name(mode); +} + +static void intel_crtc_compute_pixel_rate(struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + + if (HAS_GMCH(dev_priv)) + /* FIXME calculate proper pipe pixel rate for GMCH pfit */ + crtc_state->pixel_rate = + crtc_state->hw.pipe_mode.crtc_clock; + else + crtc_state->pixel_rate = + ilk_pipe_pixel_rate(crtc_state); +} + +static void intel_bigjoiner_adjust_timings(const struct intel_crtc_state *crtc_state, + struct drm_display_mode *mode) +{ + int num_pipes = intel_bigjoiner_num_pipes(crtc_state); + + if (num_pipes < 2) + return; + + mode->crtc_clock /= num_pipes; + mode->crtc_hdisplay /= num_pipes; + mode->crtc_hblank_start /= num_pipes; + mode->crtc_hblank_end /= num_pipes; + mode->crtc_hsync_start /= num_pipes; + mode->crtc_hsync_end /= num_pipes; + mode->crtc_htotal /= num_pipes; +} + +static void intel_splitter_adjust_timings(const struct intel_crtc_state *crtc_state, + struct drm_display_mode *mode) +{ + int overlap = crtc_state->splitter.pixel_overlap; + int n = crtc_state->splitter.link_count; + + if (!crtc_state->splitter.enable) + return; + + /* + * eDP MSO uses segment timings from EDID for transcoder + * timings, but full mode for everything else. + * + * h_full = (h_segment - pixel_overlap) * link_count + */ + mode->crtc_hdisplay = (mode->crtc_hdisplay - overlap) * n; + mode->crtc_hblank_start = (mode->crtc_hblank_start - overlap) * n; + mode->crtc_hblank_end = (mode->crtc_hblank_end - overlap) * n; + mode->crtc_hsync_start = (mode->crtc_hsync_start - overlap) * n; + mode->crtc_hsync_end = (mode->crtc_hsync_end - overlap) * n; + mode->crtc_htotal = (mode->crtc_htotal - overlap) * n; + mode->crtc_clock *= n; +} + +static void intel_crtc_readout_derived_state(struct intel_crtc_state *crtc_state) +{ + struct drm_display_mode *mode = &crtc_state->hw.mode; + struct drm_display_mode *pipe_mode = &crtc_state->hw.pipe_mode; + struct drm_display_mode *adjusted_mode = &crtc_state->hw.adjusted_mode; + + /* + * Start with the adjusted_mode crtc timings, which + * have been filled with the transcoder timings. + */ + drm_mode_copy(pipe_mode, adjusted_mode); + + /* Expand MSO per-segment transcoder timings to full */ + intel_splitter_adjust_timings(crtc_state, pipe_mode); + + /* + * We want the full numbers in adjusted_mode normal timings, + * adjusted_mode crtc timings are left with the raw transcoder + * timings. + */ + intel_mode_from_crtc_timings(adjusted_mode, pipe_mode); + + /* Populate the "user" mode with full numbers */ + drm_mode_copy(mode, pipe_mode); + intel_mode_from_crtc_timings(mode, mode); + mode->hdisplay = drm_rect_width(&crtc_state->pipe_src) * + (intel_bigjoiner_num_pipes(crtc_state) ?: 1); + mode->vdisplay = drm_rect_height(&crtc_state->pipe_src); + + /* Derive per-pipe timings in case bigjoiner is used */ + intel_bigjoiner_adjust_timings(crtc_state, pipe_mode); + intel_mode_from_crtc_timings(pipe_mode, pipe_mode); + + intel_crtc_compute_pixel_rate(crtc_state); +} + +void intel_encoder_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + encoder->get_config(encoder, crtc_state); + + intel_crtc_readout_derived_state(crtc_state); +} + +static void intel_bigjoiner_compute_pipe_src(struct intel_crtc_state *crtc_state) +{ + int num_pipes = intel_bigjoiner_num_pipes(crtc_state); + int width, height; + + if (num_pipes < 2) + return; + + width = drm_rect_width(&crtc_state->pipe_src); + height = drm_rect_height(&crtc_state->pipe_src); + + drm_rect_init(&crtc_state->pipe_src, 0, 0, + width / num_pipes, height); +} + +static int intel_crtc_compute_pipe_src(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + + intel_bigjoiner_compute_pipe_src(crtc_state); + + /* + * Pipe horizontal size must be even in: + * - DVO ganged mode + * - LVDS dual channel mode + * - Double wide pipe + */ + if (drm_rect_width(&crtc_state->pipe_src) & 1) { + if (crtc_state->double_wide) { + drm_dbg_kms(&i915->drm, + "[CRTC:%d:%s] Odd pipe source width not supported with double wide pipe\n", + crtc->base.base.id, crtc->base.name); + return -EINVAL; + } + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS) && + intel_is_dual_link_lvds(i915)) { + drm_dbg_kms(&i915->drm, + "[CRTC:%d:%s] Odd pipe source width not supported with dual link LVDS\n", + crtc->base.base.id, crtc->base.name); + return -EINVAL; + } + } + + return 0; +} + +static int intel_crtc_compute_pipe_mode(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + struct drm_display_mode *adjusted_mode = &crtc_state->hw.adjusted_mode; + struct drm_display_mode *pipe_mode = &crtc_state->hw.pipe_mode; + int clock_limit = i915->max_dotclk_freq; + + /* + * Start with the adjusted_mode crtc timings, which + * have been filled with the transcoder timings. + */ + drm_mode_copy(pipe_mode, adjusted_mode); + + /* Expand MSO per-segment transcoder timings to full */ + intel_splitter_adjust_timings(crtc_state, pipe_mode); + + /* Derive per-pipe timings in case bigjoiner is used */ + intel_bigjoiner_adjust_timings(crtc_state, pipe_mode); + intel_mode_from_crtc_timings(pipe_mode, pipe_mode); + + if (DISPLAY_VER(i915) < 4) { + clock_limit = i915->display.cdclk.max_cdclk_freq * 9 / 10; + + /* + * Enable double wide mode when the dot clock + * is > 90% of the (display) core speed. + */ + if (intel_crtc_supports_double_wide(crtc) && + pipe_mode->crtc_clock > clock_limit) { + clock_limit = i915->max_dotclk_freq; + crtc_state->double_wide = true; + } + } + + if (pipe_mode->crtc_clock > clock_limit) { + drm_dbg_kms(&i915->drm, + "[CRTC:%d:%s] requested pixel clock (%d kHz) too high (max: %d kHz, double wide: %s)\n", + crtc->base.base.id, crtc->base.name, + pipe_mode->crtc_clock, clock_limit, + str_yes_no(crtc_state->double_wide)); + return -EINVAL; + } + + return 0; +} + +static int intel_crtc_compute_config(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + int ret; + + ret = intel_dpll_crtc_compute_clock(state, crtc); + if (ret) + return ret; + + ret = intel_crtc_compute_pipe_src(crtc_state); + if (ret) + return ret; + + ret = intel_crtc_compute_pipe_mode(crtc_state); + if (ret) + return ret; + + intel_crtc_compute_pixel_rate(crtc_state); + + if (crtc_state->has_pch_encoder) + return ilk_fdi_compute_config(crtc, crtc_state); + + return 0; +} + +static void +intel_reduce_m_n_ratio(u32 *num, u32 *den) +{ + while (*num > DATA_LINK_M_N_MASK || + *den > DATA_LINK_M_N_MASK) { + *num >>= 1; + *den >>= 1; + } +} + +static void compute_m_n(u32 *ret_m, u32 *ret_n, + u32 m, u32 n, u32 constant_n) +{ + if (constant_n) + *ret_n = constant_n; + else + *ret_n = min_t(unsigned int, roundup_pow_of_two(n), DATA_LINK_N_MAX); + + *ret_m = div_u64(mul_u32_u32(m, *ret_n), n); + intel_reduce_m_n_ratio(ret_m, ret_n); +} + +void +intel_link_compute_m_n(u16 bits_per_pixel, int nlanes, + int pixel_clock, int link_clock, + struct intel_link_m_n *m_n, + bool fec_enable) +{ + u32 data_clock = bits_per_pixel * pixel_clock; + + if (fec_enable) + data_clock = intel_dp_mode_to_fec_clock(data_clock); + + /* + * Windows/BIOS uses fixed M/N values always. Follow suit. + * + * Also several DP dongles in particular seem to be fussy + * about too large link M/N values. Presumably the 20bit + * value used by Windows/BIOS is acceptable to everyone. + */ + m_n->tu = 64; + compute_m_n(&m_n->data_m, &m_n->data_n, + data_clock, link_clock * nlanes * 8, + 0x8000000); + + compute_m_n(&m_n->link_m, &m_n->link_n, + pixel_clock, link_clock, + 0x80000); +} + +static void intel_panel_sanitize_ssc(struct drm_i915_private *dev_priv) +{ + /* + * There may be no VBT; and if the BIOS enabled SSC we can + * just keep using it to avoid unnecessary flicker. Whereas if the + * BIOS isn't using it, don't assume it will work even if the VBT + * indicates as much. + */ + if (HAS_PCH_IBX(dev_priv) || HAS_PCH_CPT(dev_priv)) { + bool bios_lvds_use_ssc = intel_de_read(dev_priv, + PCH_DREF_CONTROL) & + DREF_SSC1_ENABLE; + + if (dev_priv->display.vbt.lvds_use_ssc != bios_lvds_use_ssc) { + drm_dbg_kms(&dev_priv->drm, + "SSC %s by BIOS, overriding VBT which says %s\n", + str_enabled_disabled(bios_lvds_use_ssc), + str_enabled_disabled(dev_priv->display.vbt.lvds_use_ssc)); + dev_priv->display.vbt.lvds_use_ssc = bios_lvds_use_ssc; + } + } +} + +void intel_zero_m_n(struct intel_link_m_n *m_n) +{ + /* corresponds to 0 register value */ + memset(m_n, 0, sizeof(*m_n)); + m_n->tu = 1; +} + +void intel_set_m_n(struct drm_i915_private *i915, + const struct intel_link_m_n *m_n, + i915_reg_t data_m_reg, i915_reg_t data_n_reg, + i915_reg_t link_m_reg, i915_reg_t link_n_reg) +{ + intel_de_write(i915, data_m_reg, TU_SIZE(m_n->tu) | m_n->data_m); + intel_de_write(i915, data_n_reg, m_n->data_n); + intel_de_write(i915, link_m_reg, m_n->link_m); + /* + * On BDW+ writing LINK_N arms the double buffered update + * of all the M/N registers, so it must be written last. + */ + intel_de_write(i915, link_n_reg, m_n->link_n); +} + +bool intel_cpu_transcoder_has_m2_n2(struct drm_i915_private *dev_priv, + enum transcoder transcoder) +{ + if (IS_HASWELL(dev_priv)) + return transcoder == TRANSCODER_EDP; + + return IS_DISPLAY_VER(dev_priv, 5, 7) || IS_CHERRYVIEW(dev_priv); +} + +void intel_cpu_transcoder_set_m1_n1(struct intel_crtc *crtc, + enum transcoder transcoder, + const struct intel_link_m_n *m_n) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + + if (DISPLAY_VER(dev_priv) >= 5) + intel_set_m_n(dev_priv, m_n, + PIPE_DATA_M1(transcoder), PIPE_DATA_N1(transcoder), + PIPE_LINK_M1(transcoder), PIPE_LINK_N1(transcoder)); + else + intel_set_m_n(dev_priv, m_n, + PIPE_DATA_M_G4X(pipe), PIPE_DATA_N_G4X(pipe), + PIPE_LINK_M_G4X(pipe), PIPE_LINK_N_G4X(pipe)); +} + +void intel_cpu_transcoder_set_m2_n2(struct intel_crtc *crtc, + enum transcoder transcoder, + const struct intel_link_m_n *m_n) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + + if (!intel_cpu_transcoder_has_m2_n2(dev_priv, transcoder)) + return; + + intel_set_m_n(dev_priv, m_n, + PIPE_DATA_M2(transcoder), PIPE_DATA_N2(transcoder), + PIPE_LINK_M2(transcoder), PIPE_LINK_N2(transcoder)); +} + +static void intel_set_transcoder_timings(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + const struct drm_display_mode *adjusted_mode = &crtc_state->hw.adjusted_mode; + u32 crtc_vtotal, crtc_vblank_end; + int vsyncshift = 0; + + /* We need to be careful not to changed the adjusted mode, for otherwise + * the hw state checker will get angry at the mismatch. */ + crtc_vtotal = adjusted_mode->crtc_vtotal; + crtc_vblank_end = adjusted_mode->crtc_vblank_end; + + if (adjusted_mode->flags & DRM_MODE_FLAG_INTERLACE) { + /* the chip adds 2 halflines automatically */ + crtc_vtotal -= 1; + crtc_vblank_end -= 1; + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_SDVO)) + vsyncshift = (adjusted_mode->crtc_htotal - 1) / 2; + else + vsyncshift = adjusted_mode->crtc_hsync_start - + adjusted_mode->crtc_htotal / 2; + if (vsyncshift < 0) + vsyncshift += adjusted_mode->crtc_htotal; + } + + if (DISPLAY_VER(dev_priv) > 3) + intel_de_write(dev_priv, VSYNCSHIFT(cpu_transcoder), + vsyncshift); + + intel_de_write(dev_priv, HTOTAL(cpu_transcoder), + (adjusted_mode->crtc_hdisplay - 1) | ((adjusted_mode->crtc_htotal - 1) << 16)); + intel_de_write(dev_priv, HBLANK(cpu_transcoder), + (adjusted_mode->crtc_hblank_start - 1) | ((adjusted_mode->crtc_hblank_end - 1) << 16)); + intel_de_write(dev_priv, HSYNC(cpu_transcoder), + (adjusted_mode->crtc_hsync_start - 1) | ((adjusted_mode->crtc_hsync_end - 1) << 16)); + + intel_de_write(dev_priv, VTOTAL(cpu_transcoder), + (adjusted_mode->crtc_vdisplay - 1) | ((crtc_vtotal - 1) << 16)); + intel_de_write(dev_priv, VBLANK(cpu_transcoder), + (adjusted_mode->crtc_vblank_start - 1) | ((crtc_vblank_end - 1) << 16)); + intel_de_write(dev_priv, VSYNC(cpu_transcoder), + (adjusted_mode->crtc_vsync_start - 1) | ((adjusted_mode->crtc_vsync_end - 1) << 16)); + + /* Workaround: when the EDP input selection is B, the VTOTAL_B must be + * programmed with the VTOTAL_EDP value. Same for VTOTAL_C. This is + * documented on the DDI_FUNC_CTL register description, EDP Input Select + * bits. */ + if (IS_HASWELL(dev_priv) && cpu_transcoder == TRANSCODER_EDP && + (pipe == PIPE_B || pipe == PIPE_C)) + intel_de_write(dev_priv, VTOTAL(pipe), + intel_de_read(dev_priv, VTOTAL(cpu_transcoder))); + +} + +static void intel_set_pipe_src_size(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + int width = drm_rect_width(&crtc_state->pipe_src); + int height = drm_rect_height(&crtc_state->pipe_src); + enum pipe pipe = crtc->pipe; + + /* pipesrc controls the size that is scaled from, which should + * always be the user's requested size. + */ + intel_de_write(dev_priv, PIPESRC(pipe), + PIPESRC_WIDTH(width - 1) | PIPESRC_HEIGHT(height - 1)); +} + +static bool intel_pipe_is_interlaced(const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + + if (DISPLAY_VER(dev_priv) == 2) + return false; + + if (DISPLAY_VER(dev_priv) >= 9 || + IS_BROADWELL(dev_priv) || IS_HASWELL(dev_priv)) + return intel_de_read(dev_priv, PIPECONF(cpu_transcoder)) & PIPECONF_INTERLACE_MASK_HSW; + else + return intel_de_read(dev_priv, PIPECONF(cpu_transcoder)) & PIPECONF_INTERLACE_MASK; +} + +static void intel_get_transcoder_timings(struct intel_crtc *crtc, + struct intel_crtc_state *pipe_config) +{ + struct drm_device *dev = crtc->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + enum transcoder cpu_transcoder = pipe_config->cpu_transcoder; + u32 tmp; + + tmp = intel_de_read(dev_priv, HTOTAL(cpu_transcoder)); + pipe_config->hw.adjusted_mode.crtc_hdisplay = (tmp & 0xffff) + 1; + pipe_config->hw.adjusted_mode.crtc_htotal = ((tmp >> 16) & 0xffff) + 1; + + if (!transcoder_is_dsi(cpu_transcoder)) { + tmp = intel_de_read(dev_priv, HBLANK(cpu_transcoder)); + pipe_config->hw.adjusted_mode.crtc_hblank_start = + (tmp & 0xffff) + 1; + pipe_config->hw.adjusted_mode.crtc_hblank_end = + ((tmp >> 16) & 0xffff) + 1; + } + tmp = intel_de_read(dev_priv, HSYNC(cpu_transcoder)); + pipe_config->hw.adjusted_mode.crtc_hsync_start = (tmp & 0xffff) + 1; + pipe_config->hw.adjusted_mode.crtc_hsync_end = ((tmp >> 16) & 0xffff) + 1; + + tmp = intel_de_read(dev_priv, VTOTAL(cpu_transcoder)); + pipe_config->hw.adjusted_mode.crtc_vdisplay = (tmp & 0xffff) + 1; + pipe_config->hw.adjusted_mode.crtc_vtotal = ((tmp >> 16) & 0xffff) + 1; + + if (!transcoder_is_dsi(cpu_transcoder)) { + tmp = intel_de_read(dev_priv, VBLANK(cpu_transcoder)); + pipe_config->hw.adjusted_mode.crtc_vblank_start = + (tmp & 0xffff) + 1; + pipe_config->hw.adjusted_mode.crtc_vblank_end = + ((tmp >> 16) & 0xffff) + 1; + } + tmp = intel_de_read(dev_priv, VSYNC(cpu_transcoder)); + pipe_config->hw.adjusted_mode.crtc_vsync_start = (tmp & 0xffff) + 1; + pipe_config->hw.adjusted_mode.crtc_vsync_end = ((tmp >> 16) & 0xffff) + 1; + + if (intel_pipe_is_interlaced(pipe_config)) { + pipe_config->hw.adjusted_mode.flags |= DRM_MODE_FLAG_INTERLACE; + pipe_config->hw.adjusted_mode.crtc_vtotal += 1; + pipe_config->hw.adjusted_mode.crtc_vblank_end += 1; + } +} + +static void intel_bigjoiner_adjust_pipe_src(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + int num_pipes = intel_bigjoiner_num_pipes(crtc_state); + enum pipe master_pipe, pipe = crtc->pipe; + int width; + + if (num_pipes < 2) + return; + + master_pipe = bigjoiner_master_pipe(crtc_state); + width = drm_rect_width(&crtc_state->pipe_src); + + drm_rect_translate_to(&crtc_state->pipe_src, + (pipe - master_pipe) * width, 0); +} + +static void intel_get_pipe_src_size(struct intel_crtc *crtc, + struct intel_crtc_state *pipe_config) +{ + struct drm_device *dev = crtc->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + u32 tmp; + + tmp = intel_de_read(dev_priv, PIPESRC(crtc->pipe)); + + drm_rect_init(&pipe_config->pipe_src, 0, 0, + REG_FIELD_GET(PIPESRC_WIDTH_MASK, tmp) + 1, + REG_FIELD_GET(PIPESRC_HEIGHT_MASK, tmp) + 1); + + intel_bigjoiner_adjust_pipe_src(pipe_config); +} + +void i9xx_set_pipeconf(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + u32 pipeconf = 0; + + /* + * - We keep both pipes enabled on 830 + * - During modeset the pipe is still disabled and must remain so + * - During fastset the pipe is already enabled and must remain so + */ + if (IS_I830(dev_priv) || !intel_crtc_needs_modeset(crtc_state)) + pipeconf |= PIPECONF_ENABLE; + + if (crtc_state->double_wide) + pipeconf |= PIPECONF_DOUBLE_WIDE; + + /* only g4x and later have fancy bpc/dither controls */ + if (IS_G4X(dev_priv) || IS_VALLEYVIEW(dev_priv) || + IS_CHERRYVIEW(dev_priv)) { + /* Bspec claims that we can't use dithering for 30bpp pipes. */ + if (crtc_state->dither && crtc_state->pipe_bpp != 30) + pipeconf |= PIPECONF_DITHER_EN | + PIPECONF_DITHER_TYPE_SP; + + switch (crtc_state->pipe_bpp) { + default: + /* Case prevented by intel_choose_pipe_bpp_dither. */ + MISSING_CASE(crtc_state->pipe_bpp); + fallthrough; + case 18: + pipeconf |= PIPECONF_BPC_6; + break; + case 24: + pipeconf |= PIPECONF_BPC_8; + break; + case 30: + pipeconf |= PIPECONF_BPC_10; + break; + } + } + + if (crtc_state->hw.adjusted_mode.flags & DRM_MODE_FLAG_INTERLACE) { + if (DISPLAY_VER(dev_priv) < 4 || + intel_crtc_has_type(crtc_state, INTEL_OUTPUT_SDVO)) + pipeconf |= PIPECONF_INTERLACE_W_FIELD_INDICATION; + else + pipeconf |= PIPECONF_INTERLACE_W_SYNC_SHIFT; + } else { + pipeconf |= PIPECONF_INTERLACE_PROGRESSIVE; + } + + if ((IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) && + crtc_state->limited_color_range) + pipeconf |= PIPECONF_COLOR_RANGE_SELECT; + + pipeconf |= PIPECONF_GAMMA_MODE(crtc_state->gamma_mode); + + pipeconf |= PIPECONF_FRAME_START_DELAY(crtc_state->framestart_delay - 1); + + intel_de_write(dev_priv, PIPECONF(crtc->pipe), pipeconf); + intel_de_posting_read(dev_priv, PIPECONF(crtc->pipe)); +} + +static bool i9xx_has_pfit(struct drm_i915_private *dev_priv) +{ + if (IS_I830(dev_priv)) + return false; + + return DISPLAY_VER(dev_priv) >= 4 || + IS_PINEVIEW(dev_priv) || IS_MOBILE(dev_priv); +} + +static void i9xx_get_pfit_config(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + u32 tmp; + + if (!i9xx_has_pfit(dev_priv)) + return; + + tmp = intel_de_read(dev_priv, PFIT_CONTROL); + if (!(tmp & PFIT_ENABLE)) + return; + + /* Check whether the pfit is attached to our pipe. */ + if (DISPLAY_VER(dev_priv) < 4) { + if (crtc->pipe != PIPE_B) + return; + } else { + if ((tmp & PFIT_PIPE_MASK) != (crtc->pipe << PFIT_PIPE_SHIFT)) + return; + } + + crtc_state->gmch_pfit.control = tmp; + crtc_state->gmch_pfit.pgm_ratios = + intel_de_read(dev_priv, PFIT_PGM_RATIOS); +} + +static void vlv_crtc_clock_get(struct intel_crtc *crtc, + struct intel_crtc_state *pipe_config) +{ + struct drm_device *dev = crtc->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + enum pipe pipe = crtc->pipe; + struct dpll clock; + u32 mdiv; + int refclk = 100000; + + /* In case of DSI, DPLL will not be used */ + if ((pipe_config->dpll_hw_state.dpll & DPLL_VCO_ENABLE) == 0) + return; + + vlv_dpio_get(dev_priv); + mdiv = vlv_dpio_read(dev_priv, pipe, VLV_PLL_DW3(pipe)); + vlv_dpio_put(dev_priv); + + clock.m1 = (mdiv >> DPIO_M1DIV_SHIFT) & 7; + clock.m2 = mdiv & DPIO_M2DIV_MASK; + clock.n = (mdiv >> DPIO_N_SHIFT) & 0xf; + clock.p1 = (mdiv >> DPIO_P1_SHIFT) & 7; + clock.p2 = (mdiv >> DPIO_P2_SHIFT) & 0x1f; + + pipe_config->port_clock = vlv_calc_dpll_params(refclk, &clock); +} + +static void chv_crtc_clock_get(struct intel_crtc *crtc, + struct intel_crtc_state *pipe_config) +{ + struct drm_device *dev = crtc->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + enum pipe pipe = crtc->pipe; + enum dpio_channel port = vlv_pipe_to_channel(pipe); + struct dpll clock; + u32 cmn_dw13, pll_dw0, pll_dw1, pll_dw2, pll_dw3; + int refclk = 100000; + + /* In case of DSI, DPLL will not be used */ + if ((pipe_config->dpll_hw_state.dpll & DPLL_VCO_ENABLE) == 0) + return; + + vlv_dpio_get(dev_priv); + cmn_dw13 = vlv_dpio_read(dev_priv, pipe, CHV_CMN_DW13(port)); + pll_dw0 = vlv_dpio_read(dev_priv, pipe, CHV_PLL_DW0(port)); + pll_dw1 = vlv_dpio_read(dev_priv, pipe, CHV_PLL_DW1(port)); + pll_dw2 = vlv_dpio_read(dev_priv, pipe, CHV_PLL_DW2(port)); + pll_dw3 = vlv_dpio_read(dev_priv, pipe, CHV_PLL_DW3(port)); + vlv_dpio_put(dev_priv); + + clock.m1 = (pll_dw1 & 0x7) == DPIO_CHV_M1_DIV_BY_2 ? 2 : 0; + clock.m2 = (pll_dw0 & 0xff) << 22; + if (pll_dw3 & DPIO_CHV_FRAC_DIV_EN) + clock.m2 |= pll_dw2 & 0x3fffff; + clock.n = (pll_dw1 >> DPIO_CHV_N_DIV_SHIFT) & 0xf; + clock.p1 = (cmn_dw13 >> DPIO_CHV_P1_DIV_SHIFT) & 0x7; + clock.p2 = (cmn_dw13 >> DPIO_CHV_P2_DIV_SHIFT) & 0x1f; + + pipe_config->port_clock = chv_calc_dpll_params(refclk, &clock); +} + +static enum intel_output_format +bdw_get_pipemisc_output_format(struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + u32 tmp; + + tmp = intel_de_read(dev_priv, PIPEMISC(crtc->pipe)); + + if (tmp & PIPEMISC_YUV420_ENABLE) { + /* We support 4:2:0 in full blend mode only */ + drm_WARN_ON(&dev_priv->drm, + (tmp & PIPEMISC_YUV420_MODE_FULL_BLEND) == 0); + + return INTEL_OUTPUT_FORMAT_YCBCR420; + } else if (tmp & PIPEMISC_OUTPUT_COLORSPACE_YUV) { + return INTEL_OUTPUT_FORMAT_YCBCR444; + } else { + return INTEL_OUTPUT_FORMAT_RGB; + } +} + +static void i9xx_get_pipe_color_config(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct intel_plane *plane = to_intel_plane(crtc->base.primary); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; + u32 tmp; + + tmp = intel_de_read(dev_priv, DSPCNTR(i9xx_plane)); + + if (tmp & DISP_PIPE_GAMMA_ENABLE) + crtc_state->gamma_enable = true; + + if (!HAS_GMCH(dev_priv) && + tmp & DISP_PIPE_CSC_ENABLE) + crtc_state->csc_enable = true; +} + +static bool i9xx_get_pipe_config(struct intel_crtc *crtc, + struct intel_crtc_state *pipe_config) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum intel_display_power_domain power_domain; + intel_wakeref_t wakeref; + u32 tmp; + bool ret; + + power_domain = POWER_DOMAIN_PIPE(crtc->pipe); + wakeref = intel_display_power_get_if_enabled(dev_priv, power_domain); + if (!wakeref) + return false; + + pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB; + pipe_config->cpu_transcoder = (enum transcoder) crtc->pipe; + pipe_config->shared_dpll = NULL; + + ret = false; + + tmp = intel_de_read(dev_priv, PIPECONF(crtc->pipe)); + if (!(tmp & PIPECONF_ENABLE)) + goto out; + + if (IS_G4X(dev_priv) || IS_VALLEYVIEW(dev_priv) || + IS_CHERRYVIEW(dev_priv)) { + switch (tmp & PIPECONF_BPC_MASK) { + case PIPECONF_BPC_6: + pipe_config->pipe_bpp = 18; + break; + case PIPECONF_BPC_8: + pipe_config->pipe_bpp = 24; + break; + case PIPECONF_BPC_10: + pipe_config->pipe_bpp = 30; + break; + default: + MISSING_CASE(tmp); + break; + } + } + + if ((IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) && + (tmp & PIPECONF_COLOR_RANGE_SELECT)) + pipe_config->limited_color_range = true; + + pipe_config->gamma_mode = REG_FIELD_GET(PIPECONF_GAMMA_MODE_MASK_I9XX, tmp); + + pipe_config->framestart_delay = REG_FIELD_GET(PIPECONF_FRAME_START_DELAY_MASK, tmp) + 1; + + if (IS_CHERRYVIEW(dev_priv)) + pipe_config->cgm_mode = intel_de_read(dev_priv, + CGM_PIPE_MODE(crtc->pipe)); + + i9xx_get_pipe_color_config(pipe_config); + intel_color_get_config(pipe_config); + + if (DISPLAY_VER(dev_priv) < 4) + pipe_config->double_wide = tmp & PIPECONF_DOUBLE_WIDE; + + intel_get_transcoder_timings(crtc, pipe_config); + intel_get_pipe_src_size(crtc, pipe_config); + + i9xx_get_pfit_config(pipe_config); + + if (DISPLAY_VER(dev_priv) >= 4) { + /* No way to read it out on pipes B and C */ + if (IS_CHERRYVIEW(dev_priv) && crtc->pipe != PIPE_A) + tmp = dev_priv->chv_dpll_md[crtc->pipe]; + else + tmp = intel_de_read(dev_priv, DPLL_MD(crtc->pipe)); + pipe_config->pixel_multiplier = + ((tmp & DPLL_MD_UDI_MULTIPLIER_MASK) + >> DPLL_MD_UDI_MULTIPLIER_SHIFT) + 1; + pipe_config->dpll_hw_state.dpll_md = tmp; + } else if (IS_I945G(dev_priv) || IS_I945GM(dev_priv) || + IS_G33(dev_priv) || IS_PINEVIEW(dev_priv)) { + tmp = intel_de_read(dev_priv, DPLL(crtc->pipe)); + pipe_config->pixel_multiplier = + ((tmp & SDVO_MULTIPLIER_MASK) + >> SDVO_MULTIPLIER_SHIFT_HIRES) + 1; + } else { + /* Note that on i915G/GM the pixel multiplier is in the sdvo + * port and will be fixed up in the encoder->get_config + * function. */ + pipe_config->pixel_multiplier = 1; + } + pipe_config->dpll_hw_state.dpll = intel_de_read(dev_priv, + DPLL(crtc->pipe)); + if (!IS_VALLEYVIEW(dev_priv) && !IS_CHERRYVIEW(dev_priv)) { + pipe_config->dpll_hw_state.fp0 = intel_de_read(dev_priv, + FP0(crtc->pipe)); + pipe_config->dpll_hw_state.fp1 = intel_de_read(dev_priv, + FP1(crtc->pipe)); + } else { + /* Mask out read-only status bits. */ + pipe_config->dpll_hw_state.dpll &= ~(DPLL_LOCK_VLV | + DPLL_PORTC_READY_MASK | + DPLL_PORTB_READY_MASK); + } + + if (IS_CHERRYVIEW(dev_priv)) + chv_crtc_clock_get(crtc, pipe_config); + else if (IS_VALLEYVIEW(dev_priv)) + vlv_crtc_clock_get(crtc, pipe_config); + else + i9xx_crtc_clock_get(crtc, pipe_config); + + /* + * Normally the dotclock is filled in by the encoder .get_config() + * but in case the pipe is enabled w/o any ports we need a sane + * default. + */ + pipe_config->hw.adjusted_mode.crtc_clock = + pipe_config->port_clock / pipe_config->pixel_multiplier; + + ret = true; + +out: + intel_display_power_put(dev_priv, power_domain, wakeref); + + return ret; +} + +void ilk_set_pipeconf(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + u32 val = 0; + + /* + * - During modeset the pipe is still disabled and must remain so + * - During fastset the pipe is already enabled and must remain so + */ + if (!intel_crtc_needs_modeset(crtc_state)) + val |= PIPECONF_ENABLE; + + switch (crtc_state->pipe_bpp) { + default: + /* Case prevented by intel_choose_pipe_bpp_dither. */ + MISSING_CASE(crtc_state->pipe_bpp); + fallthrough; + case 18: + val |= PIPECONF_BPC_6; + break; + case 24: + val |= PIPECONF_BPC_8; + break; + case 30: + val |= PIPECONF_BPC_10; + break; + case 36: + val |= PIPECONF_BPC_12; + break; + } + + if (crtc_state->dither) + val |= PIPECONF_DITHER_EN | PIPECONF_DITHER_TYPE_SP; + + if (crtc_state->hw.adjusted_mode.flags & DRM_MODE_FLAG_INTERLACE) + val |= PIPECONF_INTERLACE_IF_ID_ILK; + else + val |= PIPECONF_INTERLACE_PF_PD_ILK; + + /* + * This would end up with an odd purple hue over + * the entire display. Make sure we don't do it. + */ + drm_WARN_ON(&dev_priv->drm, crtc_state->limited_color_range && + crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB); + + if (crtc_state->limited_color_range && + !intel_crtc_has_type(crtc_state, INTEL_OUTPUT_SDVO)) + val |= PIPECONF_COLOR_RANGE_SELECT; + + if (crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB) + val |= PIPECONF_OUTPUT_COLORSPACE_YUV709; + + val |= PIPECONF_GAMMA_MODE(crtc_state->gamma_mode); + + val |= PIPECONF_FRAME_START_DELAY(crtc_state->framestart_delay - 1); + val |= PIPECONF_MSA_TIMING_DELAY(crtc_state->msa_timing_delay); + + intel_de_write(dev_priv, PIPECONF(pipe), val); + intel_de_posting_read(dev_priv, PIPECONF(pipe)); +} + +static void hsw_set_transconf(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + u32 val = 0; + + /* + * - During modeset the pipe is still disabled and must remain so + * - During fastset the pipe is already enabled and must remain so + */ + if (!intel_crtc_needs_modeset(crtc_state)) + val |= PIPECONF_ENABLE; + + if (IS_HASWELL(dev_priv) && crtc_state->dither) + val |= PIPECONF_DITHER_EN | PIPECONF_DITHER_TYPE_SP; + + if (crtc_state->hw.adjusted_mode.flags & DRM_MODE_FLAG_INTERLACE) + val |= PIPECONF_INTERLACE_IF_ID_ILK; + else + val |= PIPECONF_INTERLACE_PF_PD_ILK; + + if (IS_HASWELL(dev_priv) && + crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB) + val |= PIPECONF_OUTPUT_COLORSPACE_YUV_HSW; + + intel_de_write(dev_priv, PIPECONF(cpu_transcoder), val); + intel_de_posting_read(dev_priv, PIPECONF(cpu_transcoder)); +} + +static void bdw_set_pipemisc(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + u32 val = 0; + + switch (crtc_state->pipe_bpp) { + case 18: + val |= PIPEMISC_BPC_6; + break; + case 24: + val |= PIPEMISC_BPC_8; + break; + case 30: + val |= PIPEMISC_BPC_10; + break; + case 36: + /* Port output 12BPC defined for ADLP+ */ + if (DISPLAY_VER(dev_priv) > 12) + val |= PIPEMISC_BPC_12_ADLP; + break; + default: + MISSING_CASE(crtc_state->pipe_bpp); + break; + } + + if (crtc_state->dither) + val |= PIPEMISC_DITHER_ENABLE | PIPEMISC_DITHER_TYPE_SP; + + if (crtc_state->output_format == INTEL_OUTPUT_FORMAT_YCBCR420 || + crtc_state->output_format == INTEL_OUTPUT_FORMAT_YCBCR444) + val |= PIPEMISC_OUTPUT_COLORSPACE_YUV; + + if (crtc_state->output_format == INTEL_OUTPUT_FORMAT_YCBCR420) + val |= PIPEMISC_YUV420_ENABLE | + PIPEMISC_YUV420_MODE_FULL_BLEND; + + if (DISPLAY_VER(dev_priv) >= 11 && is_hdr_mode(crtc_state)) + val |= PIPEMISC_HDR_MODE_PRECISION; + + if (DISPLAY_VER(dev_priv) >= 12) + val |= PIPEMISC_PIXEL_ROUNDING_TRUNC; + + intel_de_write(dev_priv, PIPEMISC(crtc->pipe), val); +} + +int bdw_get_pipemisc_bpp(struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + u32 tmp; + + tmp = intel_de_read(dev_priv, PIPEMISC(crtc->pipe)); + + switch (tmp & PIPEMISC_BPC_MASK) { + case PIPEMISC_BPC_6: + return 18; + case PIPEMISC_BPC_8: + return 24; + case PIPEMISC_BPC_10: + return 30; + /* + * PORT OUTPUT 12 BPC defined for ADLP+. + * + * TODO: + * For previous platforms with DSI interface, bits 5:7 + * are used for storing pipe_bpp irrespective of dithering. + * Since the value of 12 BPC is not defined for these bits + * on older platforms, need to find a workaround for 12 BPC + * MIPI DSI HW readout. + */ + case PIPEMISC_BPC_12_ADLP: + if (DISPLAY_VER(dev_priv) > 12) + return 36; + fallthrough; + default: + MISSING_CASE(tmp); + return 0; + } +} + +int ilk_get_lanes_required(int target_clock, int link_bw, int bpp) +{ + /* + * Account for spread spectrum to avoid + * oversubscribing the link. Max center spread + * is 2.5%; use 5% for safety's sake. + */ + u32 bps = target_clock * bpp * 21 / 20; + return DIV_ROUND_UP(bps, link_bw * 8); +} + +void intel_get_m_n(struct drm_i915_private *i915, + struct intel_link_m_n *m_n, + i915_reg_t data_m_reg, i915_reg_t data_n_reg, + i915_reg_t link_m_reg, i915_reg_t link_n_reg) +{ + m_n->link_m = intel_de_read(i915, link_m_reg) & DATA_LINK_M_N_MASK; + m_n->link_n = intel_de_read(i915, link_n_reg) & DATA_LINK_M_N_MASK; + m_n->data_m = intel_de_read(i915, data_m_reg) & DATA_LINK_M_N_MASK; + m_n->data_n = intel_de_read(i915, data_n_reg) & DATA_LINK_M_N_MASK; + m_n->tu = REG_FIELD_GET(TU_SIZE_MASK, intel_de_read(i915, data_m_reg)) + 1; +} + +void intel_cpu_transcoder_get_m1_n1(struct intel_crtc *crtc, + enum transcoder transcoder, + struct intel_link_m_n *m_n) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + + if (DISPLAY_VER(dev_priv) >= 5) + intel_get_m_n(dev_priv, m_n, + PIPE_DATA_M1(transcoder), PIPE_DATA_N1(transcoder), + PIPE_LINK_M1(transcoder), PIPE_LINK_N1(transcoder)); + else + intel_get_m_n(dev_priv, m_n, + PIPE_DATA_M_G4X(pipe), PIPE_DATA_N_G4X(pipe), + PIPE_LINK_M_G4X(pipe), PIPE_LINK_N_G4X(pipe)); +} + +void intel_cpu_transcoder_get_m2_n2(struct intel_crtc *crtc, + enum transcoder transcoder, + struct intel_link_m_n *m_n) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + + if (!intel_cpu_transcoder_has_m2_n2(dev_priv, transcoder)) + return; + + intel_get_m_n(dev_priv, m_n, + PIPE_DATA_M2(transcoder), PIPE_DATA_N2(transcoder), + PIPE_LINK_M2(transcoder), PIPE_LINK_N2(transcoder)); +} + +static void ilk_get_pfit_pos_size(struct intel_crtc_state *crtc_state, + u32 pos, u32 size) +{ + drm_rect_init(&crtc_state->pch_pfit.dst, + pos >> 16, pos & 0xffff, + size >> 16, size & 0xffff); +} + +static void skl_get_pfit_config(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_crtc_scaler_state *scaler_state = &crtc_state->scaler_state; + int id = -1; + int i; + + /* find scaler attached to this pipe */ + for (i = 0; i < crtc->num_scalers; i++) { + u32 ctl, pos, size; + + ctl = intel_de_read(dev_priv, SKL_PS_CTRL(crtc->pipe, i)); + if ((ctl & (PS_SCALER_EN | PS_PLANE_SEL_MASK)) != PS_SCALER_EN) + continue; + + id = i; + crtc_state->pch_pfit.enabled = true; + + pos = intel_de_read(dev_priv, SKL_PS_WIN_POS(crtc->pipe, i)); + size = intel_de_read(dev_priv, SKL_PS_WIN_SZ(crtc->pipe, i)); + + ilk_get_pfit_pos_size(crtc_state, pos, size); + + scaler_state->scalers[i].in_use = true; + break; + } + + scaler_state->scaler_id = id; + if (id >= 0) + scaler_state->scaler_users |= (1 << SKL_CRTC_INDEX); + else + scaler_state->scaler_users &= ~(1 << SKL_CRTC_INDEX); +} + +static void ilk_get_pfit_config(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + u32 ctl, pos, size; + + ctl = intel_de_read(dev_priv, PF_CTL(crtc->pipe)); + if ((ctl & PF_ENABLE) == 0) + return; + + crtc_state->pch_pfit.enabled = true; + + pos = intel_de_read(dev_priv, PF_WIN_POS(crtc->pipe)); + size = intel_de_read(dev_priv, PF_WIN_SZ(crtc->pipe)); + + ilk_get_pfit_pos_size(crtc_state, pos, size); + + /* + * We currently do not free assignements of panel fitters on + * ivb/hsw (since we don't use the higher upscaling modes which + * differentiates them) so just WARN about this case for now. + */ + drm_WARN_ON(&dev_priv->drm, DISPLAY_VER(dev_priv) == 7 && + (ctl & PF_PIPE_SEL_MASK_IVB) != PF_PIPE_SEL_IVB(crtc->pipe)); +} + +static bool ilk_get_pipe_config(struct intel_crtc *crtc, + struct intel_crtc_state *pipe_config) +{ + struct drm_device *dev = crtc->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + enum intel_display_power_domain power_domain; + intel_wakeref_t wakeref; + u32 tmp; + bool ret; + + power_domain = POWER_DOMAIN_PIPE(crtc->pipe); + wakeref = intel_display_power_get_if_enabled(dev_priv, power_domain); + if (!wakeref) + return false; + + pipe_config->cpu_transcoder = (enum transcoder) crtc->pipe; + pipe_config->shared_dpll = NULL; + + ret = false; + tmp = intel_de_read(dev_priv, PIPECONF(crtc->pipe)); + if (!(tmp & PIPECONF_ENABLE)) + goto out; + + switch (tmp & PIPECONF_BPC_MASK) { + case PIPECONF_BPC_6: + pipe_config->pipe_bpp = 18; + break; + case PIPECONF_BPC_8: + pipe_config->pipe_bpp = 24; + break; + case PIPECONF_BPC_10: + pipe_config->pipe_bpp = 30; + break; + case PIPECONF_BPC_12: + pipe_config->pipe_bpp = 36; + break; + default: + break; + } + + if (tmp & PIPECONF_COLOR_RANGE_SELECT) + pipe_config->limited_color_range = true; + + switch (tmp & PIPECONF_OUTPUT_COLORSPACE_MASK) { + case PIPECONF_OUTPUT_COLORSPACE_YUV601: + case PIPECONF_OUTPUT_COLORSPACE_YUV709: + pipe_config->output_format = INTEL_OUTPUT_FORMAT_YCBCR444; + break; + default: + pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB; + break; + } + + pipe_config->gamma_mode = REG_FIELD_GET(PIPECONF_GAMMA_MODE_MASK_ILK, tmp); + + pipe_config->framestart_delay = REG_FIELD_GET(PIPECONF_FRAME_START_DELAY_MASK, tmp) + 1; + + pipe_config->msa_timing_delay = REG_FIELD_GET(PIPECONF_MSA_TIMING_DELAY_MASK, tmp); + + pipe_config->csc_mode = intel_de_read(dev_priv, + PIPE_CSC_MODE(crtc->pipe)); + + i9xx_get_pipe_color_config(pipe_config); + intel_color_get_config(pipe_config); + + pipe_config->pixel_multiplier = 1; + + ilk_pch_get_config(pipe_config); + + intel_get_transcoder_timings(crtc, pipe_config); + intel_get_pipe_src_size(crtc, pipe_config); + + ilk_get_pfit_config(pipe_config); + + ret = true; + +out: + intel_display_power_put(dev_priv, power_domain, wakeref); + + return ret; +} + +static u8 bigjoiner_pipes(struct drm_i915_private *i915) +{ + u8 pipes; + + if (DISPLAY_VER(i915) >= 12) + pipes = BIT(PIPE_A) | BIT(PIPE_B) | BIT(PIPE_C) | BIT(PIPE_D); + else if (DISPLAY_VER(i915) >= 11) + pipes = BIT(PIPE_B) | BIT(PIPE_C); + else + pipes = 0; + + return pipes & RUNTIME_INFO(i915)->pipe_mask; +} + +static bool transcoder_ddi_func_is_enabled(struct drm_i915_private *dev_priv, + enum transcoder cpu_transcoder) +{ + enum intel_display_power_domain power_domain; + intel_wakeref_t wakeref; + u32 tmp = 0; + + power_domain = POWER_DOMAIN_TRANSCODER(cpu_transcoder); + + with_intel_display_power_if_enabled(dev_priv, power_domain, wakeref) + tmp = intel_de_read(dev_priv, TRANS_DDI_FUNC_CTL(cpu_transcoder)); + + return tmp & TRANS_DDI_FUNC_ENABLE; +} + +static void enabled_bigjoiner_pipes(struct drm_i915_private *dev_priv, + u8 *master_pipes, u8 *slave_pipes) +{ + struct intel_crtc *crtc; + + *master_pipes = 0; + *slave_pipes = 0; + + for_each_intel_crtc_in_pipe_mask(&dev_priv->drm, crtc, + bigjoiner_pipes(dev_priv)) { + enum intel_display_power_domain power_domain; + enum pipe pipe = crtc->pipe; + intel_wakeref_t wakeref; + + power_domain = intel_dsc_power_domain(crtc, (enum transcoder) pipe); + with_intel_display_power_if_enabled(dev_priv, power_domain, wakeref) { + u32 tmp = intel_de_read(dev_priv, ICL_PIPE_DSS_CTL1(pipe)); + + if (!(tmp & BIG_JOINER_ENABLE)) + continue; + + if (tmp & MASTER_BIG_JOINER_ENABLE) + *master_pipes |= BIT(pipe); + else + *slave_pipes |= BIT(pipe); + } + + if (DISPLAY_VER(dev_priv) < 13) + continue; + + power_domain = POWER_DOMAIN_PIPE(pipe); + with_intel_display_power_if_enabled(dev_priv, power_domain, wakeref) { + u32 tmp = intel_de_read(dev_priv, ICL_PIPE_DSS_CTL1(pipe)); + + if (tmp & UNCOMPRESSED_JOINER_MASTER) + *master_pipes |= BIT(pipe); + if (tmp & UNCOMPRESSED_JOINER_SLAVE) + *slave_pipes |= BIT(pipe); + } + } + + /* Bigjoiner pipes should always be consecutive master and slave */ + drm_WARN(&dev_priv->drm, *slave_pipes != *master_pipes << 1, + "Bigjoiner misconfigured (master pipes 0x%x, slave pipes 0x%x)\n", + *master_pipes, *slave_pipes); +} + +static enum pipe get_bigjoiner_master_pipe(enum pipe pipe, u8 master_pipes, u8 slave_pipes) +{ + if ((slave_pipes & BIT(pipe)) == 0) + return pipe; + + /* ignore everything above our pipe */ + master_pipes &= ~GENMASK(7, pipe); + + /* highest remaining bit should be our master pipe */ + return fls(master_pipes) - 1; +} + +static u8 get_bigjoiner_slave_pipes(enum pipe pipe, u8 master_pipes, u8 slave_pipes) +{ + enum pipe master_pipe, next_master_pipe; + + master_pipe = get_bigjoiner_master_pipe(pipe, master_pipes, slave_pipes); + + if ((master_pipes & BIT(master_pipe)) == 0) + return 0; + + /* ignore our master pipe and everything below it */ + master_pipes &= ~GENMASK(master_pipe, 0); + /* make sure a high bit is set for the ffs() */ + master_pipes |= BIT(7); + /* lowest remaining bit should be the next master pipe */ + next_master_pipe = ffs(master_pipes) - 1; + + return slave_pipes & GENMASK(next_master_pipe - 1, master_pipe); +} + +static u8 hsw_panel_transcoders(struct drm_i915_private *i915) +{ + u8 panel_transcoder_mask = BIT(TRANSCODER_EDP); + + if (DISPLAY_VER(i915) >= 11) + panel_transcoder_mask |= BIT(TRANSCODER_DSI_0) | BIT(TRANSCODER_DSI_1); + + return panel_transcoder_mask; +} + +static u8 hsw_enabled_transcoders(struct intel_crtc *crtc) +{ + struct drm_device *dev = crtc->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + u8 panel_transcoder_mask = hsw_panel_transcoders(dev_priv); + enum transcoder cpu_transcoder; + u8 master_pipes, slave_pipes; + u8 enabled_transcoders = 0; + + /* + * XXX: Do intel_display_power_get_if_enabled before reading this (for + * consistency and less surprising code; it's in always on power). + */ + for_each_cpu_transcoder_masked(dev_priv, cpu_transcoder, + panel_transcoder_mask) { + enum intel_display_power_domain power_domain; + intel_wakeref_t wakeref; + enum pipe trans_pipe; + u32 tmp = 0; + + power_domain = POWER_DOMAIN_TRANSCODER(cpu_transcoder); + with_intel_display_power_if_enabled(dev_priv, power_domain, wakeref) + tmp = intel_de_read(dev_priv, TRANS_DDI_FUNC_CTL(cpu_transcoder)); + + if (!(tmp & TRANS_DDI_FUNC_ENABLE)) + continue; + + switch (tmp & TRANS_DDI_EDP_INPUT_MASK) { + default: + drm_WARN(dev, 1, + "unknown pipe linked to transcoder %s\n", + transcoder_name(cpu_transcoder)); + fallthrough; + case TRANS_DDI_EDP_INPUT_A_ONOFF: + case TRANS_DDI_EDP_INPUT_A_ON: + trans_pipe = PIPE_A; + break; + case TRANS_DDI_EDP_INPUT_B_ONOFF: + trans_pipe = PIPE_B; + break; + case TRANS_DDI_EDP_INPUT_C_ONOFF: + trans_pipe = PIPE_C; + break; + case TRANS_DDI_EDP_INPUT_D_ONOFF: + trans_pipe = PIPE_D; + break; + } + + if (trans_pipe == crtc->pipe) + enabled_transcoders |= BIT(cpu_transcoder); + } + + /* single pipe or bigjoiner master */ + cpu_transcoder = (enum transcoder) crtc->pipe; + if (transcoder_ddi_func_is_enabled(dev_priv, cpu_transcoder)) + enabled_transcoders |= BIT(cpu_transcoder); + + /* bigjoiner slave -> consider the master pipe's transcoder as well */ + enabled_bigjoiner_pipes(dev_priv, &master_pipes, &slave_pipes); + if (slave_pipes & BIT(crtc->pipe)) { + cpu_transcoder = (enum transcoder) + get_bigjoiner_master_pipe(crtc->pipe, master_pipes, slave_pipes); + if (transcoder_ddi_func_is_enabled(dev_priv, cpu_transcoder)) + enabled_transcoders |= BIT(cpu_transcoder); + } + + return enabled_transcoders; +} + +static bool has_edp_transcoders(u8 enabled_transcoders) +{ + return enabled_transcoders & BIT(TRANSCODER_EDP); +} + +static bool has_dsi_transcoders(u8 enabled_transcoders) +{ + return enabled_transcoders & (BIT(TRANSCODER_DSI_0) | + BIT(TRANSCODER_DSI_1)); +} + +static bool has_pipe_transcoders(u8 enabled_transcoders) +{ + return enabled_transcoders & ~(BIT(TRANSCODER_EDP) | + BIT(TRANSCODER_DSI_0) | + BIT(TRANSCODER_DSI_1)); +} + +static void assert_enabled_transcoders(struct drm_i915_private *i915, + u8 enabled_transcoders) +{ + /* Only one type of transcoder please */ + drm_WARN_ON(&i915->drm, + has_edp_transcoders(enabled_transcoders) + + has_dsi_transcoders(enabled_transcoders) + + has_pipe_transcoders(enabled_transcoders) > 1); + + /* Only DSI transcoders can be ganged */ + drm_WARN_ON(&i915->drm, + !has_dsi_transcoders(enabled_transcoders) && + !is_power_of_2(enabled_transcoders)); +} + +static bool hsw_get_transcoder_state(struct intel_crtc *crtc, + struct intel_crtc_state *pipe_config, + struct intel_display_power_domain_set *power_domain_set) +{ + struct drm_device *dev = crtc->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + unsigned long enabled_transcoders; + u32 tmp; + + enabled_transcoders = hsw_enabled_transcoders(crtc); + if (!enabled_transcoders) + return false; + + assert_enabled_transcoders(dev_priv, enabled_transcoders); + + /* + * With the exception of DSI we should only ever have + * a single enabled transcoder. With DSI let's just + * pick the first one. + */ + pipe_config->cpu_transcoder = ffs(enabled_transcoders) - 1; + + if (!intel_display_power_get_in_set_if_enabled(dev_priv, power_domain_set, + POWER_DOMAIN_TRANSCODER(pipe_config->cpu_transcoder))) + return false; + + if (hsw_panel_transcoders(dev_priv) & BIT(pipe_config->cpu_transcoder)) { + tmp = intel_de_read(dev_priv, TRANS_DDI_FUNC_CTL(pipe_config->cpu_transcoder)); + + if ((tmp & TRANS_DDI_EDP_INPUT_MASK) == TRANS_DDI_EDP_INPUT_A_ONOFF) + pipe_config->pch_pfit.force_thru = true; + } + + tmp = intel_de_read(dev_priv, PIPECONF(pipe_config->cpu_transcoder)); + + return tmp & PIPECONF_ENABLE; +} + +static bool bxt_get_dsi_transcoder_state(struct intel_crtc *crtc, + struct intel_crtc_state *pipe_config, + struct intel_display_power_domain_set *power_domain_set) +{ + struct drm_device *dev = crtc->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + enum transcoder cpu_transcoder; + enum port port; + u32 tmp; + + for_each_port_masked(port, BIT(PORT_A) | BIT(PORT_C)) { + if (port == PORT_A) + cpu_transcoder = TRANSCODER_DSI_A; + else + cpu_transcoder = TRANSCODER_DSI_C; + + if (!intel_display_power_get_in_set_if_enabled(dev_priv, power_domain_set, + POWER_DOMAIN_TRANSCODER(cpu_transcoder))) + continue; + + /* + * The PLL needs to be enabled with a valid divider + * configuration, otherwise accessing DSI registers will hang + * the machine. See BSpec North Display Engine + * registers/MIPI[BXT]. We can break out here early, since we + * need the same DSI PLL to be enabled for both DSI ports. + */ + if (!bxt_dsi_pll_is_enabled(dev_priv)) + break; + + /* XXX: this works for video mode only */ + tmp = intel_de_read(dev_priv, BXT_MIPI_PORT_CTRL(port)); + if (!(tmp & DPI_ENABLE)) + continue; + + tmp = intel_de_read(dev_priv, MIPI_CTRL(port)); + if ((tmp & BXT_PIPE_SELECT_MASK) != BXT_PIPE_SELECT(crtc->pipe)) + continue; + + pipe_config->cpu_transcoder = cpu_transcoder; + break; + } + + return transcoder_is_dsi(pipe_config->cpu_transcoder); +} + +static void intel_bigjoiner_get_config(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + u8 master_pipes, slave_pipes; + enum pipe pipe = crtc->pipe; + + enabled_bigjoiner_pipes(i915, &master_pipes, &slave_pipes); + + if (((master_pipes | slave_pipes) & BIT(pipe)) == 0) + return; + + crtc_state->bigjoiner_pipes = + BIT(get_bigjoiner_master_pipe(pipe, master_pipes, slave_pipes)) | + get_bigjoiner_slave_pipes(pipe, master_pipes, slave_pipes); +} + +static bool hsw_get_pipe_config(struct intel_crtc *crtc, + struct intel_crtc_state *pipe_config) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_display_power_domain_set power_domain_set = { }; + bool active; + u32 tmp; + + if (!intel_display_power_get_in_set_if_enabled(dev_priv, &power_domain_set, + POWER_DOMAIN_PIPE(crtc->pipe))) + return false; + + pipe_config->shared_dpll = NULL; + + active = hsw_get_transcoder_state(crtc, pipe_config, &power_domain_set); + + if ((IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv)) && + bxt_get_dsi_transcoder_state(crtc, pipe_config, &power_domain_set)) { + drm_WARN_ON(&dev_priv->drm, active); + active = true; + } + + if (!active) + goto out; + + intel_dsc_get_config(pipe_config); + intel_bigjoiner_get_config(pipe_config); + + if (!transcoder_is_dsi(pipe_config->cpu_transcoder) || + DISPLAY_VER(dev_priv) >= 11) + intel_get_transcoder_timings(crtc, pipe_config); + + if (HAS_VRR(dev_priv) && !transcoder_is_dsi(pipe_config->cpu_transcoder)) + intel_vrr_get_config(crtc, pipe_config); + + intel_get_pipe_src_size(crtc, pipe_config); + + if (IS_HASWELL(dev_priv)) { + u32 tmp = intel_de_read(dev_priv, + PIPECONF(pipe_config->cpu_transcoder)); + + if (tmp & PIPECONF_OUTPUT_COLORSPACE_YUV_HSW) + pipe_config->output_format = INTEL_OUTPUT_FORMAT_YCBCR444; + else + pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB; + } else { + pipe_config->output_format = + bdw_get_pipemisc_output_format(crtc); + } + + pipe_config->gamma_mode = intel_de_read(dev_priv, + GAMMA_MODE(crtc->pipe)); + + pipe_config->csc_mode = intel_de_read(dev_priv, + PIPE_CSC_MODE(crtc->pipe)); + + if (DISPLAY_VER(dev_priv) >= 9) { + tmp = intel_de_read(dev_priv, SKL_BOTTOM_COLOR(crtc->pipe)); + + if (tmp & SKL_BOTTOM_COLOR_GAMMA_ENABLE) + pipe_config->gamma_enable = true; + + if (tmp & SKL_BOTTOM_COLOR_CSC_ENABLE) + pipe_config->csc_enable = true; + } else { + i9xx_get_pipe_color_config(pipe_config); + } + + intel_color_get_config(pipe_config); + + tmp = intel_de_read(dev_priv, WM_LINETIME(crtc->pipe)); + pipe_config->linetime = REG_FIELD_GET(HSW_LINETIME_MASK, tmp); + if (IS_BROADWELL(dev_priv) || IS_HASWELL(dev_priv)) + pipe_config->ips_linetime = + REG_FIELD_GET(HSW_IPS_LINETIME_MASK, tmp); + + if (intel_display_power_get_in_set_if_enabled(dev_priv, &power_domain_set, + POWER_DOMAIN_PIPE_PANEL_FITTER(crtc->pipe))) { + if (DISPLAY_VER(dev_priv) >= 9) + skl_get_pfit_config(pipe_config); + else + ilk_get_pfit_config(pipe_config); + } + + hsw_ips_get_config(pipe_config); + + if (pipe_config->cpu_transcoder != TRANSCODER_EDP && + !transcoder_is_dsi(pipe_config->cpu_transcoder)) { + pipe_config->pixel_multiplier = + intel_de_read(dev_priv, + PIPE_MULT(pipe_config->cpu_transcoder)) + 1; + } else { + pipe_config->pixel_multiplier = 1; + } + + if (!transcoder_is_dsi(pipe_config->cpu_transcoder)) { + tmp = intel_de_read(dev_priv, DISPLAY_VER(dev_priv) >= 14 ? + MTL_CHICKEN_TRANS(pipe_config->cpu_transcoder) : + CHICKEN_TRANS(pipe_config->cpu_transcoder)); + + pipe_config->framestart_delay = REG_FIELD_GET(HSW_FRAME_START_DELAY_MASK, tmp) + 1; + } else { + /* no idea if this is correct */ + pipe_config->framestart_delay = 1; + } + +out: + intel_display_power_put_all_in_set(dev_priv, &power_domain_set); + + return active; +} + +bool intel_crtc_get_pipe_config(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + + if (!i915->display.funcs.display->get_pipe_config(crtc, crtc_state)) + return false; + + crtc_state->hw.active = true; + + intel_crtc_readout_derived_state(crtc_state); + + return true; +} + +/* VESA 640x480x72Hz mode to set on the pipe */ +static const struct drm_display_mode load_detect_mode = { + DRM_MODE("640x480", DRM_MODE_TYPE_DEFAULT, 31500, 640, 664, + 704, 832, 0, 480, 489, 491, 520, 0, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), +}; + +static int intel_modeset_disable_planes(struct drm_atomic_state *state, + struct drm_crtc *crtc) +{ + struct drm_plane *plane; + struct drm_plane_state *plane_state; + int ret, i; + + ret = drm_atomic_add_affected_planes(state, crtc); + if (ret) + return ret; + + for_each_new_plane_in_state(state, plane, plane_state, i) { + if (plane_state->crtc != crtc) + continue; + + ret = drm_atomic_set_crtc_for_plane(plane_state, NULL); + if (ret) + return ret; + + drm_atomic_set_fb_for_plane(plane_state, NULL); + } + + return 0; +} + +int intel_get_load_detect_pipe(struct drm_connector *connector, + struct intel_load_detect_pipe *old, + struct drm_modeset_acquire_ctx *ctx) +{ + struct intel_encoder *encoder = + intel_attached_encoder(to_intel_connector(connector)); + struct intel_crtc *possible_crtc; + struct intel_crtc *crtc = NULL; + struct drm_device *dev = encoder->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + struct drm_mode_config *config = &dev->mode_config; + struct drm_atomic_state *state = NULL, *restore_state = NULL; + struct drm_connector_state *connector_state; + struct intel_crtc_state *crtc_state; + int ret; + + drm_dbg_kms(&dev_priv->drm, "[CONNECTOR:%d:%s], [ENCODER:%d:%s]\n", + connector->base.id, connector->name, + encoder->base.base.id, encoder->base.name); + + old->restore_state = NULL; + + drm_WARN_ON(dev, !drm_modeset_is_locked(&config->connection_mutex)); + + /* + * Algorithm gets a little messy: + * + * - if the connector already has an assigned crtc, use it (but make + * sure it's on first) + * + * - try to find the first unused crtc that can drive this connector, + * and use that if we find one + */ + + /* See if we already have a CRTC for this connector */ + if (connector->state->crtc) { + crtc = to_intel_crtc(connector->state->crtc); + + ret = drm_modeset_lock(&crtc->base.mutex, ctx); + if (ret) + goto fail; + + /* Make sure the crtc and connector are running */ + goto found; + } + + /* Find an unused one (if possible) */ + for_each_intel_crtc(dev, possible_crtc) { + if (!(encoder->base.possible_crtcs & + drm_crtc_mask(&possible_crtc->base))) + continue; + + ret = drm_modeset_lock(&possible_crtc->base.mutex, ctx); + if (ret) + goto fail; + + if (possible_crtc->base.state->enable) { + drm_modeset_unlock(&possible_crtc->base.mutex); + continue; + } + + crtc = possible_crtc; + break; + } + + /* + * If we didn't find an unused CRTC, don't use any. + */ + if (!crtc) { + drm_dbg_kms(&dev_priv->drm, + "no pipe available for load-detect\n"); + ret = -ENODEV; + goto fail; + } + +found: + state = drm_atomic_state_alloc(dev); + restore_state = drm_atomic_state_alloc(dev); + if (!state || !restore_state) { + ret = -ENOMEM; + goto fail; + } + + state->acquire_ctx = ctx; + restore_state->acquire_ctx = ctx; + + connector_state = drm_atomic_get_connector_state(state, connector); + if (IS_ERR(connector_state)) { + ret = PTR_ERR(connector_state); + goto fail; + } + + ret = drm_atomic_set_crtc_for_connector(connector_state, &crtc->base); + if (ret) + goto fail; + + crtc_state = intel_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) { + ret = PTR_ERR(crtc_state); + goto fail; + } + + crtc_state->uapi.active = true; + + ret = drm_atomic_set_mode_for_crtc(&crtc_state->uapi, + &load_detect_mode); + if (ret) + goto fail; + + ret = intel_modeset_disable_planes(state, &crtc->base); + if (ret) + goto fail; + + ret = PTR_ERR_OR_ZERO(drm_atomic_get_connector_state(restore_state, connector)); + if (!ret) + ret = PTR_ERR_OR_ZERO(drm_atomic_get_crtc_state(restore_state, &crtc->base)); + if (!ret) + ret = drm_atomic_add_affected_planes(restore_state, &crtc->base); + if (ret) { + drm_dbg_kms(&dev_priv->drm, + "Failed to create a copy of old state to restore: %i\n", + ret); + goto fail; + } + + ret = drm_atomic_commit(state); + if (ret) { + drm_dbg_kms(&dev_priv->drm, + "failed to set mode on load-detect pipe\n"); + goto fail; + } + + old->restore_state = restore_state; + drm_atomic_state_put(state); + + /* let the connector get through one full cycle before testing */ + intel_crtc_wait_for_next_vblank(crtc); + + return true; + +fail: + if (state) { + drm_atomic_state_put(state); + state = NULL; + } + if (restore_state) { + drm_atomic_state_put(restore_state); + restore_state = NULL; + } + + if (ret == -EDEADLK) + return ret; + + return false; +} + +void intel_release_load_detect_pipe(struct drm_connector *connector, + struct intel_load_detect_pipe *old, + struct drm_modeset_acquire_ctx *ctx) +{ + struct intel_encoder *intel_encoder = + intel_attached_encoder(to_intel_connector(connector)); + struct drm_i915_private *i915 = to_i915(intel_encoder->base.dev); + struct drm_encoder *encoder = &intel_encoder->base; + struct drm_atomic_state *state = old->restore_state; + int ret; + + drm_dbg_kms(&i915->drm, "[CONNECTOR:%d:%s], [ENCODER:%d:%s]\n", + connector->base.id, connector->name, + encoder->base.id, encoder->name); + + if (!state) + return; + + ret = drm_atomic_helper_commit_duplicated_state(state, ctx); + if (ret) + drm_dbg_kms(&i915->drm, + "Couldn't release load detect pipe: %i\n", ret); + drm_atomic_state_put(state); +} + +static int i9xx_pll_refclk(struct drm_device *dev, + const struct intel_crtc_state *pipe_config) +{ + struct drm_i915_private *dev_priv = to_i915(dev); + u32 dpll = pipe_config->dpll_hw_state.dpll; + + if ((dpll & PLL_REF_INPUT_MASK) == PLLB_REF_INPUT_SPREADSPECTRUMIN) + return dev_priv->display.vbt.lvds_ssc_freq; + else if (HAS_PCH_SPLIT(dev_priv)) + return 120000; + else if (DISPLAY_VER(dev_priv) != 2) + return 96000; + else + return 48000; +} + +/* Returns the clock of the currently programmed mode of the given pipe. */ +void i9xx_crtc_clock_get(struct intel_crtc *crtc, + struct intel_crtc_state *pipe_config) +{ + struct drm_device *dev = crtc->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + u32 dpll = pipe_config->dpll_hw_state.dpll; + u32 fp; + struct dpll clock; + int port_clock; + int refclk = i9xx_pll_refclk(dev, pipe_config); + + if ((dpll & DISPLAY_RATE_SELECT_FPA1) == 0) + fp = pipe_config->dpll_hw_state.fp0; + else + fp = pipe_config->dpll_hw_state.fp1; + + clock.m1 = (fp & FP_M1_DIV_MASK) >> FP_M1_DIV_SHIFT; + if (IS_PINEVIEW(dev_priv)) { + clock.n = ffs((fp & FP_N_PINEVIEW_DIV_MASK) >> FP_N_DIV_SHIFT) - 1; + clock.m2 = (fp & FP_M2_PINEVIEW_DIV_MASK) >> FP_M2_DIV_SHIFT; + } else { + clock.n = (fp & FP_N_DIV_MASK) >> FP_N_DIV_SHIFT; + clock.m2 = (fp & FP_M2_DIV_MASK) >> FP_M2_DIV_SHIFT; + } + + if (DISPLAY_VER(dev_priv) != 2) { + if (IS_PINEVIEW(dev_priv)) + clock.p1 = ffs((dpll & DPLL_FPA01_P1_POST_DIV_MASK_PINEVIEW) >> + DPLL_FPA01_P1_POST_DIV_SHIFT_PINEVIEW); + else + clock.p1 = ffs((dpll & DPLL_FPA01_P1_POST_DIV_MASK) >> + DPLL_FPA01_P1_POST_DIV_SHIFT); + + switch (dpll & DPLL_MODE_MASK) { + case DPLLB_MODE_DAC_SERIAL: + clock.p2 = dpll & DPLL_DAC_SERIAL_P2_CLOCK_DIV_5 ? + 5 : 10; + break; + case DPLLB_MODE_LVDS: + clock.p2 = dpll & DPLLB_LVDS_P2_CLOCK_DIV_7 ? + 7 : 14; + break; + default: + drm_dbg_kms(&dev_priv->drm, + "Unknown DPLL mode %08x in programmed " + "mode\n", (int)(dpll & DPLL_MODE_MASK)); + return; + } + + if (IS_PINEVIEW(dev_priv)) + port_clock = pnv_calc_dpll_params(refclk, &clock); + else + port_clock = i9xx_calc_dpll_params(refclk, &clock); + } else { + enum pipe lvds_pipe; + + if (IS_I85X(dev_priv) && + intel_lvds_port_enabled(dev_priv, LVDS, &lvds_pipe) && + lvds_pipe == crtc->pipe) { + u32 lvds = intel_de_read(dev_priv, LVDS); + + clock.p1 = ffs((dpll & DPLL_FPA01_P1_POST_DIV_MASK_I830_LVDS) >> + DPLL_FPA01_P1_POST_DIV_SHIFT); + + if (lvds & LVDS_CLKB_POWER_UP) + clock.p2 = 7; + else + clock.p2 = 14; + } else { + if (dpll & PLL_P1_DIVIDE_BY_TWO) + clock.p1 = 2; + else { + clock.p1 = ((dpll & DPLL_FPA01_P1_POST_DIV_MASK_I830) >> + DPLL_FPA01_P1_POST_DIV_SHIFT) + 2; + } + if (dpll & PLL_P2_DIVIDE_BY_4) + clock.p2 = 4; + else + clock.p2 = 2; + } + + port_clock = i9xx_calc_dpll_params(refclk, &clock); + } + + /* + * This value includes pixel_multiplier. We will use + * port_clock to compute adjusted_mode.crtc_clock in the + * encoder's get_config() function. + */ + pipe_config->port_clock = port_clock; +} + +int intel_dotclock_calculate(int link_freq, + const struct intel_link_m_n *m_n) +{ + /* + * The calculation for the data clock is: + * pixel_clock = ((m/n)*(link_clock * nr_lanes))/bpp + * But we want to avoid losing precison if possible, so: + * pixel_clock = ((m * link_clock * nr_lanes)/(n*bpp)) + * + * and the link clock is simpler: + * link_clock = (m * link_clock) / n + */ + + if (!m_n->link_n) + return 0; + + return DIV_ROUND_UP_ULL(mul_u32_u32(m_n->link_m, link_freq), + m_n->link_n); +} + +int intel_crtc_dotclock(const struct intel_crtc_state *pipe_config) +{ + int dotclock; + + if (intel_crtc_has_dp_encoder(pipe_config)) + dotclock = intel_dotclock_calculate(pipe_config->port_clock, + &pipe_config->dp_m_n); + else if (pipe_config->has_hdmi_sink && pipe_config->pipe_bpp > 24) + dotclock = DIV_ROUND_CLOSEST(pipe_config->port_clock * 24, + pipe_config->pipe_bpp); + else + dotclock = pipe_config->port_clock; + + if (pipe_config->output_format == INTEL_OUTPUT_FORMAT_YCBCR420 && + !intel_crtc_has_dp_encoder(pipe_config)) + dotclock *= 2; + + if (pipe_config->pixel_multiplier) + dotclock /= pipe_config->pixel_multiplier; + + return dotclock; +} + +/* Returns the currently programmed mode of the given encoder. */ +struct drm_display_mode * +intel_encoder_current_mode(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_crtc_state *crtc_state; + struct drm_display_mode *mode; + struct intel_crtc *crtc; + enum pipe pipe; + + if (!encoder->get_hw_state(encoder, &pipe)) + return NULL; + + crtc = intel_crtc_for_pipe(dev_priv, pipe); + + mode = kzalloc(sizeof(*mode), GFP_KERNEL); + if (!mode) + return NULL; + + crtc_state = intel_crtc_state_alloc(crtc); + if (!crtc_state) { + kfree(mode); + return NULL; + } + + if (!intel_crtc_get_pipe_config(crtc_state)) { + kfree(crtc_state); + kfree(mode); + return NULL; + } + + intel_encoder_get_config(encoder, crtc_state); + + intel_mode_from_crtc_timings(mode, &crtc_state->hw.adjusted_mode); + + kfree(crtc_state); + + return mode; +} + +static bool encoders_cloneable(const struct intel_encoder *a, + const struct intel_encoder *b) +{ + /* masks could be asymmetric, so check both ways */ + return a == b || (a->cloneable & (1 << b->type) && + b->cloneable & (1 << a->type)); +} + +static bool check_single_encoder_cloning(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder) +{ + struct intel_encoder *source_encoder; + struct drm_connector *connector; + struct drm_connector_state *connector_state; + int i; + + for_each_new_connector_in_state(&state->base, connector, connector_state, i) { + if (connector_state->crtc != &crtc->base) + continue; + + source_encoder = + to_intel_encoder(connector_state->best_encoder); + if (!encoders_cloneable(encoder, source_encoder)) + return false; + } + + return true; +} + +static int icl_add_linked_planes(struct intel_atomic_state *state) +{ + struct intel_plane *plane, *linked; + struct intel_plane_state *plane_state, *linked_plane_state; + int i; + + for_each_new_intel_plane_in_state(state, plane, plane_state, i) { + linked = plane_state->planar_linked_plane; + + if (!linked) + continue; + + linked_plane_state = intel_atomic_get_plane_state(state, linked); + if (IS_ERR(linked_plane_state)) + return PTR_ERR(linked_plane_state); + + drm_WARN_ON(state->base.dev, + linked_plane_state->planar_linked_plane != plane); + drm_WARN_ON(state->base.dev, + linked_plane_state->planar_slave == plane_state->planar_slave); + } + + return 0; +} + +static int icl_check_nv12_planes(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_atomic_state *state = to_intel_atomic_state(crtc_state->uapi.state); + struct intel_plane *plane, *linked; + struct intel_plane_state *plane_state; + int i; + + if (DISPLAY_VER(dev_priv) < 11) + return 0; + + /* + * Destroy all old plane links and make the slave plane invisible + * in the crtc_state->active_planes mask. + */ + for_each_new_intel_plane_in_state(state, plane, plane_state, i) { + if (plane->pipe != crtc->pipe || !plane_state->planar_linked_plane) + continue; + + plane_state->planar_linked_plane = NULL; + if (plane_state->planar_slave && !plane_state->uapi.visible) { + crtc_state->enabled_planes &= ~BIT(plane->id); + crtc_state->active_planes &= ~BIT(plane->id); + crtc_state->update_planes |= BIT(plane->id); + crtc_state->data_rate[plane->id] = 0; + crtc_state->rel_data_rate[plane->id] = 0; + } + + plane_state->planar_slave = false; + } + + if (!crtc_state->nv12_planes) + return 0; + + for_each_new_intel_plane_in_state(state, plane, plane_state, i) { + struct intel_plane_state *linked_state = NULL; + + if (plane->pipe != crtc->pipe || + !(crtc_state->nv12_planes & BIT(plane->id))) + continue; + + for_each_intel_plane_on_crtc(&dev_priv->drm, crtc, linked) { + if (!icl_is_nv12_y_plane(dev_priv, linked->id)) + continue; + + if (crtc_state->active_planes & BIT(linked->id)) + continue; + + linked_state = intel_atomic_get_plane_state(state, linked); + if (IS_ERR(linked_state)) + return PTR_ERR(linked_state); + + break; + } + + if (!linked_state) { + drm_dbg_kms(&dev_priv->drm, + "Need %d free Y planes for planar YUV\n", + hweight8(crtc_state->nv12_planes)); + + return -EINVAL; + } + + plane_state->planar_linked_plane = linked; + + linked_state->planar_slave = true; + linked_state->planar_linked_plane = plane; + crtc_state->enabled_planes |= BIT(linked->id); + crtc_state->active_planes |= BIT(linked->id); + crtc_state->update_planes |= BIT(linked->id); + crtc_state->data_rate[linked->id] = + crtc_state->data_rate_y[plane->id]; + crtc_state->rel_data_rate[linked->id] = + crtc_state->rel_data_rate_y[plane->id]; + drm_dbg_kms(&dev_priv->drm, "Using %s as Y plane for %s\n", + linked->base.name, plane->base.name); + + /* Copy parameters to slave plane */ + linked_state->ctl = plane_state->ctl | PLANE_CTL_YUV420_Y_PLANE; + linked_state->color_ctl = plane_state->color_ctl; + linked_state->view = plane_state->view; + linked_state->decrypt = plane_state->decrypt; + + intel_plane_copy_hw_state(linked_state, plane_state); + linked_state->uapi.src = plane_state->uapi.src; + linked_state->uapi.dst = plane_state->uapi.dst; + + if (icl_is_hdr_plane(dev_priv, plane->id)) { + if (linked->id == PLANE_SPRITE5) + plane_state->cus_ctl |= PLANE_CUS_Y_PLANE_7_ICL; + else if (linked->id == PLANE_SPRITE4) + plane_state->cus_ctl |= PLANE_CUS_Y_PLANE_6_ICL; + else if (linked->id == PLANE_SPRITE3) + plane_state->cus_ctl |= PLANE_CUS_Y_PLANE_5_RKL; + else if (linked->id == PLANE_SPRITE2) + plane_state->cus_ctl |= PLANE_CUS_Y_PLANE_4_RKL; + else + MISSING_CASE(linked->id); + } + } + + return 0; +} + +static bool c8_planes_changed(const struct intel_crtc_state *new_crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(new_crtc_state->uapi.crtc); + struct intel_atomic_state *state = + to_intel_atomic_state(new_crtc_state->uapi.state); + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + + return !old_crtc_state->c8_planes != !new_crtc_state->c8_planes; +} + +static u16 hsw_linetime_wm(const struct intel_crtc_state *crtc_state) +{ + const struct drm_display_mode *pipe_mode = + &crtc_state->hw.pipe_mode; + int linetime_wm; + + if (!crtc_state->hw.enable) + return 0; + + linetime_wm = DIV_ROUND_CLOSEST(pipe_mode->crtc_htotal * 1000 * 8, + pipe_mode->crtc_clock); + + return min(linetime_wm, 0x1ff); +} + +static u16 hsw_ips_linetime_wm(const struct intel_crtc_state *crtc_state, + const struct intel_cdclk_state *cdclk_state) +{ + const struct drm_display_mode *pipe_mode = + &crtc_state->hw.pipe_mode; + int linetime_wm; + + if (!crtc_state->hw.enable) + return 0; + + linetime_wm = DIV_ROUND_CLOSEST(pipe_mode->crtc_htotal * 1000 * 8, + cdclk_state->logical.cdclk); + + return min(linetime_wm, 0x1ff); +} + +static u16 skl_linetime_wm(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + const struct drm_display_mode *pipe_mode = + &crtc_state->hw.pipe_mode; + int linetime_wm; + + if (!crtc_state->hw.enable) + return 0; + + linetime_wm = DIV_ROUND_UP(pipe_mode->crtc_htotal * 1000 * 8, + crtc_state->pixel_rate); + + /* Display WA #1135: BXT:ALL GLK:ALL */ + if ((IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv)) && + skl_watermark_ipc_enabled(dev_priv)) + linetime_wm /= 2; + + return min(linetime_wm, 0x1ff); +} + +static int hsw_compute_linetime_wm(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + const struct intel_cdclk_state *cdclk_state; + + if (DISPLAY_VER(dev_priv) >= 9) + crtc_state->linetime = skl_linetime_wm(crtc_state); + else + crtc_state->linetime = hsw_linetime_wm(crtc_state); + + if (!hsw_crtc_supports_ips(crtc)) + return 0; + + cdclk_state = intel_atomic_get_cdclk_state(state); + if (IS_ERR(cdclk_state)) + return PTR_ERR(cdclk_state); + + crtc_state->ips_linetime = hsw_ips_linetime_wm(crtc_state, + cdclk_state); + + return 0; +} + +static int intel_crtc_atomic_check(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + bool mode_changed = intel_crtc_needs_modeset(crtc_state); + int ret; + + if (DISPLAY_VER(dev_priv) < 5 && !IS_G4X(dev_priv) && + mode_changed && !crtc_state->hw.active) + crtc_state->update_wm_post = true; + + if (mode_changed) { + ret = intel_dpll_crtc_get_shared_dpll(state, crtc); + if (ret) + return ret; + } + + /* + * May need to update pipe gamma enable bits + * when C8 planes are getting enabled/disabled. + */ + if (c8_planes_changed(crtc_state)) + crtc_state->uapi.color_mgmt_changed = true; + + if (mode_changed || crtc_state->update_pipe || + crtc_state->uapi.color_mgmt_changed) { + ret = intel_color_check(crtc_state); + if (ret) + return ret; + } + + ret = intel_compute_pipe_wm(state, crtc); + if (ret) { + drm_dbg_kms(&dev_priv->drm, + "Target pipe watermarks are invalid\n"); + return ret; + } + + /* + * Calculate 'intermediate' watermarks that satisfy both the + * old state and the new state. We can program these + * immediately. + */ + ret = intel_compute_intermediate_wm(state, crtc); + if (ret) { + drm_dbg_kms(&dev_priv->drm, + "No valid intermediate pipe watermarks are possible\n"); + return ret; + } + + if (DISPLAY_VER(dev_priv) >= 9) { + if (mode_changed || crtc_state->update_pipe) { + ret = skl_update_scaler_crtc(crtc_state); + if (ret) + return ret; + } + + ret = intel_atomic_setup_scalers(dev_priv, crtc, crtc_state); + if (ret) + return ret; + } + + if (HAS_IPS(dev_priv)) { + ret = hsw_ips_compute_config(state, crtc); + if (ret) + return ret; + } + + if (DISPLAY_VER(dev_priv) >= 9 || + IS_BROADWELL(dev_priv) || IS_HASWELL(dev_priv)) { + ret = hsw_compute_linetime_wm(state, crtc); + if (ret) + return ret; + + } + + ret = intel_psr2_sel_fetch_update(state, crtc); + if (ret) + return ret; + + return 0; +} + +static int +compute_sink_pipe_bpp(const struct drm_connector_state *conn_state, + struct intel_crtc_state *crtc_state) +{ + struct drm_connector *connector = conn_state->connector; + struct drm_i915_private *i915 = to_i915(crtc_state->uapi.crtc->dev); + const struct drm_display_info *info = &connector->display_info; + int bpp; + + switch (conn_state->max_bpc) { + case 6 ... 7: + bpp = 6 * 3; + break; + case 8 ... 9: + bpp = 8 * 3; + break; + case 10 ... 11: + bpp = 10 * 3; + break; + case 12 ... 16: + bpp = 12 * 3; + break; + default: + MISSING_CASE(conn_state->max_bpc); + return -EINVAL; + } + + if (bpp < crtc_state->pipe_bpp) { + drm_dbg_kms(&i915->drm, + "[CONNECTOR:%d:%s] Limiting display bpp to %d " + "(EDID bpp %d, max requested bpp %d, max platform bpp %d)\n", + connector->base.id, connector->name, + bpp, 3 * info->bpc, + 3 * conn_state->max_requested_bpc, + crtc_state->pipe_bpp); + + crtc_state->pipe_bpp = bpp; + } + + return 0; +} + +static int +compute_baseline_pipe_bpp(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct drm_connector *connector; + struct drm_connector_state *connector_state; + int bpp, i; + + if ((IS_G4X(dev_priv) || IS_VALLEYVIEW(dev_priv) || + IS_CHERRYVIEW(dev_priv))) + bpp = 10*3; + else if (DISPLAY_VER(dev_priv) >= 5) + bpp = 12*3; + else + bpp = 8*3; + + crtc_state->pipe_bpp = bpp; + + /* Clamp display bpp to connector max bpp */ + for_each_new_connector_in_state(&state->base, connector, connector_state, i) { + int ret; + + if (connector_state->crtc != &crtc->base) + continue; + + ret = compute_sink_pipe_bpp(connector_state, crtc_state); + if (ret) + return ret; + } + + return 0; +} + +static bool check_digital_port_conflicts(struct intel_atomic_state *state) +{ + struct drm_device *dev = state->base.dev; + struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; + unsigned int used_ports = 0; + unsigned int used_mst_ports = 0; + bool ret = true; + + /* + * We're going to peek into connector->state, + * hence connection_mutex must be held. + */ + drm_modeset_lock_assert_held(&dev->mode_config.connection_mutex); + + /* + * Walk the connector list instead of the encoder + * list to detect the problem on ddi platforms + * where there's just one encoder per digital port. + */ + drm_connector_list_iter_begin(dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + struct drm_connector_state *connector_state; + struct intel_encoder *encoder; + + connector_state = + drm_atomic_get_new_connector_state(&state->base, + connector); + if (!connector_state) + connector_state = connector->state; + + if (!connector_state->best_encoder) + continue; + + encoder = to_intel_encoder(connector_state->best_encoder); + + drm_WARN_ON(dev, !connector_state->crtc); + + switch (encoder->type) { + case INTEL_OUTPUT_DDI: + if (drm_WARN_ON(dev, !HAS_DDI(to_i915(dev)))) + break; + fallthrough; + case INTEL_OUTPUT_DP: + case INTEL_OUTPUT_HDMI: + case INTEL_OUTPUT_EDP: + /* the same port mustn't appear more than once */ + if (used_ports & BIT(encoder->port)) + ret = false; + + used_ports |= BIT(encoder->port); + break; + case INTEL_OUTPUT_DP_MST: + used_mst_ports |= + 1 << encoder->port; + break; + default: + break; + } + } + drm_connector_list_iter_end(&conn_iter); + + /* can't mix MST and SST/HDMI on the same port */ + if (used_ports & used_mst_ports) + return false; + + return ret; +} + +static void +intel_crtc_copy_uapi_to_hw_state_nomodeset(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + WARN_ON(intel_crtc_is_bigjoiner_slave(crtc_state)); + + drm_property_replace_blob(&crtc_state->hw.degamma_lut, + crtc_state->uapi.degamma_lut); + drm_property_replace_blob(&crtc_state->hw.gamma_lut, + crtc_state->uapi.gamma_lut); + drm_property_replace_blob(&crtc_state->hw.ctm, + crtc_state->uapi.ctm); +} + +static void +intel_crtc_copy_uapi_to_hw_state_modeset(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + WARN_ON(intel_crtc_is_bigjoiner_slave(crtc_state)); + + crtc_state->hw.enable = crtc_state->uapi.enable; + crtc_state->hw.active = crtc_state->uapi.active; + drm_mode_copy(&crtc_state->hw.mode, + &crtc_state->uapi.mode); + drm_mode_copy(&crtc_state->hw.adjusted_mode, + &crtc_state->uapi.adjusted_mode); + crtc_state->hw.scaling_filter = crtc_state->uapi.scaling_filter; + + intel_crtc_copy_uapi_to_hw_state_nomodeset(state, crtc); +} + +static void +copy_bigjoiner_crtc_state_nomodeset(struct intel_atomic_state *state, + struct intel_crtc *slave_crtc) +{ + struct intel_crtc_state *slave_crtc_state = + intel_atomic_get_new_crtc_state(state, slave_crtc); + struct intel_crtc *master_crtc = intel_master_crtc(slave_crtc_state); + const struct intel_crtc_state *master_crtc_state = + intel_atomic_get_new_crtc_state(state, master_crtc); + + drm_property_replace_blob(&slave_crtc_state->hw.degamma_lut, + master_crtc_state->hw.degamma_lut); + drm_property_replace_blob(&slave_crtc_state->hw.gamma_lut, + master_crtc_state->hw.gamma_lut); + drm_property_replace_blob(&slave_crtc_state->hw.ctm, + master_crtc_state->hw.ctm); + + slave_crtc_state->uapi.color_mgmt_changed = master_crtc_state->uapi.color_mgmt_changed; +} + +static int +copy_bigjoiner_crtc_state_modeset(struct intel_atomic_state *state, + struct intel_crtc *slave_crtc) +{ + struct intel_crtc_state *slave_crtc_state = + intel_atomic_get_new_crtc_state(state, slave_crtc); + struct intel_crtc *master_crtc = intel_master_crtc(slave_crtc_state); + const struct intel_crtc_state *master_crtc_state = + intel_atomic_get_new_crtc_state(state, master_crtc); + struct intel_crtc_state *saved_state; + + WARN_ON(master_crtc_state->bigjoiner_pipes != + slave_crtc_state->bigjoiner_pipes); + + saved_state = kmemdup(master_crtc_state, sizeof(*saved_state), GFP_KERNEL); + if (!saved_state) + return -ENOMEM; + + /* preserve some things from the slave's original crtc state */ + saved_state->uapi = slave_crtc_state->uapi; + saved_state->scaler_state = slave_crtc_state->scaler_state; + saved_state->shared_dpll = slave_crtc_state->shared_dpll; + saved_state->crc_enabled = slave_crtc_state->crc_enabled; + + intel_crtc_free_hw_state(slave_crtc_state); + memcpy(slave_crtc_state, saved_state, sizeof(*slave_crtc_state)); + kfree(saved_state); + + /* Re-init hw state */ + memset(&slave_crtc_state->hw, 0, sizeof(slave_crtc_state->hw)); + slave_crtc_state->hw.enable = master_crtc_state->hw.enable; + slave_crtc_state->hw.active = master_crtc_state->hw.active; + drm_mode_copy(&slave_crtc_state->hw.mode, + &master_crtc_state->hw.mode); + drm_mode_copy(&slave_crtc_state->hw.pipe_mode, + &master_crtc_state->hw.pipe_mode); + drm_mode_copy(&slave_crtc_state->hw.adjusted_mode, + &master_crtc_state->hw.adjusted_mode); + slave_crtc_state->hw.scaling_filter = master_crtc_state->hw.scaling_filter; + + copy_bigjoiner_crtc_state_nomodeset(state, slave_crtc); + + slave_crtc_state->uapi.mode_changed = master_crtc_state->uapi.mode_changed; + slave_crtc_state->uapi.connectors_changed = master_crtc_state->uapi.connectors_changed; + slave_crtc_state->uapi.active_changed = master_crtc_state->uapi.active_changed; + + WARN_ON(master_crtc_state->bigjoiner_pipes != + slave_crtc_state->bigjoiner_pipes); + + return 0; +} + +static int +intel_crtc_prepare_cleared_state(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_crtc_state *saved_state; + + saved_state = intel_crtc_state_alloc(crtc); + if (!saved_state) + return -ENOMEM; + + /* free the old crtc_state->hw members */ + intel_crtc_free_hw_state(crtc_state); + + /* FIXME: before the switch to atomic started, a new pipe_config was + * kzalloc'd. Code that depends on any field being zero should be + * fixed, so that the crtc_state can be safely duplicated. For now, + * only fields that are know to not cause problems are preserved. */ + + saved_state->uapi = crtc_state->uapi; + saved_state->inherited = crtc_state->inherited; + saved_state->scaler_state = crtc_state->scaler_state; + saved_state->shared_dpll = crtc_state->shared_dpll; + saved_state->dpll_hw_state = crtc_state->dpll_hw_state; + memcpy(saved_state->icl_port_dplls, crtc_state->icl_port_dplls, + sizeof(saved_state->icl_port_dplls)); + saved_state->crc_enabled = crtc_state->crc_enabled; + if (IS_G4X(dev_priv) || + IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) + saved_state->wm = crtc_state->wm; + + memcpy(crtc_state, saved_state, sizeof(*crtc_state)); + kfree(saved_state); + + intel_crtc_copy_uapi_to_hw_state_modeset(state, crtc); + + return 0; +} + +static int +intel_modeset_pipe_config(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct drm_connector *connector; + struct drm_connector_state *connector_state; + int pipe_src_w, pipe_src_h; + int base_bpp, ret, i; + bool retry = true; + + crtc_state->cpu_transcoder = (enum transcoder) crtc->pipe; + + crtc_state->framestart_delay = 1; + + /* + * Sanitize sync polarity flags based on requested ones. If neither + * positive or negative polarity is requested, treat this as meaning + * negative polarity. + */ + if (!(crtc_state->hw.adjusted_mode.flags & + (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NHSYNC))) + crtc_state->hw.adjusted_mode.flags |= DRM_MODE_FLAG_NHSYNC; + + if (!(crtc_state->hw.adjusted_mode.flags & + (DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC))) + crtc_state->hw.adjusted_mode.flags |= DRM_MODE_FLAG_NVSYNC; + + ret = compute_baseline_pipe_bpp(state, crtc); + if (ret) + return ret; + + base_bpp = crtc_state->pipe_bpp; + + /* + * Determine the real pipe dimensions. Note that stereo modes can + * increase the actual pipe size due to the frame doubling and + * insertion of additional space for blanks between the frame. This + * is stored in the crtc timings. We use the requested mode to do this + * computation to clearly distinguish it from the adjusted mode, which + * can be changed by the connectors in the below retry loop. + */ + drm_mode_get_hv_timing(&crtc_state->hw.mode, + &pipe_src_w, &pipe_src_h); + drm_rect_init(&crtc_state->pipe_src, 0, 0, + pipe_src_w, pipe_src_h); + + for_each_new_connector_in_state(&state->base, connector, connector_state, i) { + struct intel_encoder *encoder = + to_intel_encoder(connector_state->best_encoder); + + if (connector_state->crtc != &crtc->base) + continue; + + if (!check_single_encoder_cloning(state, crtc, encoder)) { + drm_dbg_kms(&i915->drm, + "[ENCODER:%d:%s] rejecting invalid cloning configuration\n", + encoder->base.base.id, encoder->base.name); + return -EINVAL; + } + + /* + * Determine output_types before calling the .compute_config() + * hooks so that the hooks can use this information safely. + */ + if (encoder->compute_output_type) + crtc_state->output_types |= + BIT(encoder->compute_output_type(encoder, crtc_state, + connector_state)); + else + crtc_state->output_types |= BIT(encoder->type); + } + +encoder_retry: + /* Ensure the port clock defaults are reset when retrying. */ + crtc_state->port_clock = 0; + crtc_state->pixel_multiplier = 1; + + /* Fill in default crtc timings, allow encoders to overwrite them. */ + drm_mode_set_crtcinfo(&crtc_state->hw.adjusted_mode, + CRTC_STEREO_DOUBLE); + + /* Pass our mode to the connectors and the CRTC to give them a chance to + * adjust it according to limitations or connector properties, and also + * a chance to reject the mode entirely. + */ + for_each_new_connector_in_state(&state->base, connector, connector_state, i) { + struct intel_encoder *encoder = + to_intel_encoder(connector_state->best_encoder); + + if (connector_state->crtc != &crtc->base) + continue; + + ret = encoder->compute_config(encoder, crtc_state, + connector_state); + if (ret == -EDEADLK) + return ret; + if (ret < 0) { + drm_dbg_kms(&i915->drm, "[ENCODER:%d:%s] config failure: %d\n", + encoder->base.base.id, encoder->base.name, ret); + return ret; + } + } + + /* Set default port clock if not overwritten by the encoder. Needs to be + * done afterwards in case the encoder adjusts the mode. */ + if (!crtc_state->port_clock) + crtc_state->port_clock = crtc_state->hw.adjusted_mode.crtc_clock + * crtc_state->pixel_multiplier; + + ret = intel_crtc_compute_config(state, crtc); + if (ret == -EDEADLK) + return ret; + if (ret == -EAGAIN) { + if (drm_WARN(&i915->drm, !retry, + "[CRTC:%d:%s] loop in pipe configuration computation\n", + crtc->base.base.id, crtc->base.name)) + return -EINVAL; + + drm_dbg_kms(&i915->drm, "[CRTC:%d:%s] bw constrained, retrying\n", + crtc->base.base.id, crtc->base.name); + retry = false; + goto encoder_retry; + } + if (ret < 0) { + drm_dbg_kms(&i915->drm, "[CRTC:%d:%s] config failure: %d\n", + crtc->base.base.id, crtc->base.name, ret); + return ret; + } + + /* Dithering seems to not pass-through bits correctly when it should, so + * only enable it on 6bpc panels and when its not a compliance + * test requesting 6bpc video pattern. + */ + crtc_state->dither = (crtc_state->pipe_bpp == 6*3) && + !crtc_state->dither_force_disable; + drm_dbg_kms(&i915->drm, + "[CRTC:%d:%s] hw max bpp: %i, pipe bpp: %i, dithering: %i\n", + crtc->base.base.id, crtc->base.name, + base_bpp, crtc_state->pipe_bpp, crtc_state->dither); + + return 0; +} + +static int +intel_modeset_pipe_config_late(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct drm_connector_state *conn_state; + struct drm_connector *connector; + int i; + + intel_bigjoiner_adjust_pipe_src(crtc_state); + + for_each_new_connector_in_state(&state->base, connector, + conn_state, i) { + struct intel_encoder *encoder = + to_intel_encoder(conn_state->best_encoder); + int ret; + + if (conn_state->crtc != &crtc->base || + !encoder->compute_config_late) + continue; + + ret = encoder->compute_config_late(encoder, crtc_state, + conn_state); + if (ret) + return ret; + } + + return 0; +} + +bool intel_fuzzy_clock_check(int clock1, int clock2) +{ + int diff; + + if (clock1 == clock2) + return true; + + if (!clock1 || !clock2) + return false; + + diff = abs(clock1 - clock2); + + if (((((diff + clock1 + clock2) * 100)) / (clock1 + clock2)) < 105) + return true; + + return false; +} + +static bool +intel_compare_link_m_n(const struct intel_link_m_n *m_n, + const struct intel_link_m_n *m2_n2) +{ + return m_n->tu == m2_n2->tu && + m_n->data_m == m2_n2->data_m && + m_n->data_n == m2_n2->data_n && + m_n->link_m == m2_n2->link_m && + m_n->link_n == m2_n2->link_n; +} + +static bool +intel_compare_infoframe(const union hdmi_infoframe *a, + const union hdmi_infoframe *b) +{ + return memcmp(a, b, sizeof(*a)) == 0; +} + +static bool +intel_compare_dp_vsc_sdp(const struct drm_dp_vsc_sdp *a, + const struct drm_dp_vsc_sdp *b) +{ + return memcmp(a, b, sizeof(*a)) == 0; +} + +static void +pipe_config_infoframe_mismatch(struct drm_i915_private *dev_priv, + bool fastset, const char *name, + const union hdmi_infoframe *a, + const union hdmi_infoframe *b) +{ + if (fastset) { + if (!drm_debug_enabled(DRM_UT_KMS)) + return; + + drm_dbg_kms(&dev_priv->drm, + "fastset mismatch in %s infoframe\n", name); + drm_dbg_kms(&dev_priv->drm, "expected:\n"); + hdmi_infoframe_log(KERN_DEBUG, dev_priv->drm.dev, a); + drm_dbg_kms(&dev_priv->drm, "found:\n"); + hdmi_infoframe_log(KERN_DEBUG, dev_priv->drm.dev, b); + } else { + drm_err(&dev_priv->drm, "mismatch in %s infoframe\n", name); + drm_err(&dev_priv->drm, "expected:\n"); + hdmi_infoframe_log(KERN_ERR, dev_priv->drm.dev, a); + drm_err(&dev_priv->drm, "found:\n"); + hdmi_infoframe_log(KERN_ERR, dev_priv->drm.dev, b); + } +} + +static void +pipe_config_dp_vsc_sdp_mismatch(struct drm_i915_private *dev_priv, + bool fastset, const char *name, + const struct drm_dp_vsc_sdp *a, + const struct drm_dp_vsc_sdp *b) +{ + if (fastset) { + if (!drm_debug_enabled(DRM_UT_KMS)) + return; + + drm_dbg_kms(&dev_priv->drm, + "fastset mismatch in %s dp sdp\n", name); + drm_dbg_kms(&dev_priv->drm, "expected:\n"); + drm_dp_vsc_sdp_log(KERN_DEBUG, dev_priv->drm.dev, a); + drm_dbg_kms(&dev_priv->drm, "found:\n"); + drm_dp_vsc_sdp_log(KERN_DEBUG, dev_priv->drm.dev, b); + } else { + drm_err(&dev_priv->drm, "mismatch in %s dp sdp\n", name); + drm_err(&dev_priv->drm, "expected:\n"); + drm_dp_vsc_sdp_log(KERN_ERR, dev_priv->drm.dev, a); + drm_err(&dev_priv->drm, "found:\n"); + drm_dp_vsc_sdp_log(KERN_ERR, dev_priv->drm.dev, b); + } +} + +static void __printf(4, 5) +pipe_config_mismatch(bool fastset, const struct intel_crtc *crtc, + const char *name, const char *format, ...) +{ + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + struct va_format vaf; + va_list args; + + va_start(args, format); + vaf.fmt = format; + vaf.va = &args; + + if (fastset) + drm_dbg_kms(&i915->drm, + "[CRTC:%d:%s] fastset mismatch in %s %pV\n", + crtc->base.base.id, crtc->base.name, name, &vaf); + else + drm_err(&i915->drm, "[CRTC:%d:%s] mismatch in %s %pV\n", + crtc->base.base.id, crtc->base.name, name, &vaf); + + va_end(args); +} + +static bool fastboot_enabled(struct drm_i915_private *dev_priv) +{ + if (dev_priv->params.fastboot != -1) + return dev_priv->params.fastboot; + + /* Enable fastboot by default on Skylake and newer */ + if (DISPLAY_VER(dev_priv) >= 9) + return true; + + /* Enable fastboot by default on VLV and CHV */ + if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) + return true; + + /* Disabled by default on all others */ + return false; +} + +bool +intel_pipe_config_compare(const struct intel_crtc_state *current_config, + const struct intel_crtc_state *pipe_config, + bool fastset) +{ + struct drm_i915_private *dev_priv = to_i915(current_config->uapi.crtc->dev); + struct intel_crtc *crtc = to_intel_crtc(pipe_config->uapi.crtc); + bool ret = true; + u32 bp_gamma = 0; + bool fixup_inherited = fastset && + current_config->inherited && !pipe_config->inherited; + + if (fixup_inherited && !fastboot_enabled(dev_priv)) { + drm_dbg_kms(&dev_priv->drm, + "initial modeset and fastboot not set\n"); + ret = false; + } + +#define PIPE_CONF_CHECK_X(name) do { \ + if (current_config->name != pipe_config->name) { \ + pipe_config_mismatch(fastset, crtc, __stringify(name), \ + "(expected 0x%08x, found 0x%08x)", \ + current_config->name, \ + pipe_config->name); \ + ret = false; \ + } \ +} while (0) + +#define PIPE_CONF_CHECK_X_WITH_MASK(name, mask) do { \ + if ((current_config->name & (mask)) != (pipe_config->name & (mask))) { \ + pipe_config_mismatch(fastset, crtc, __stringify(name), \ + "(expected 0x%08x, found 0x%08x)", \ + current_config->name & (mask), \ + pipe_config->name & (mask)); \ + ret = false; \ + } \ +} while (0) + +#define PIPE_CONF_CHECK_I(name) do { \ + if (current_config->name != pipe_config->name) { \ + pipe_config_mismatch(fastset, crtc, __stringify(name), \ + "(expected %i, found %i)", \ + current_config->name, \ + pipe_config->name); \ + ret = false; \ + } \ +} while (0) + +#define PIPE_CONF_CHECK_BOOL(name) do { \ + if (current_config->name != pipe_config->name) { \ + pipe_config_mismatch(fastset, crtc, __stringify(name), \ + "(expected %s, found %s)", \ + str_yes_no(current_config->name), \ + str_yes_no(pipe_config->name)); \ + ret = false; \ + } \ +} while (0) + +/* + * Checks state where we only read out the enabling, but not the entire + * state itself (like full infoframes or ELD for audio). These states + * require a full modeset on bootup to fix up. + */ +#define PIPE_CONF_CHECK_BOOL_INCOMPLETE(name) do { \ + if (!fixup_inherited || (!current_config->name && !pipe_config->name)) { \ + PIPE_CONF_CHECK_BOOL(name); \ + } else { \ + pipe_config_mismatch(fastset, crtc, __stringify(name), \ + "unable to verify whether state matches exactly, forcing modeset (expected %s, found %s)", \ + str_yes_no(current_config->name), \ + str_yes_no(pipe_config->name)); \ + ret = false; \ + } \ +} while (0) + +#define PIPE_CONF_CHECK_P(name) do { \ + if (current_config->name != pipe_config->name) { \ + pipe_config_mismatch(fastset, crtc, __stringify(name), \ + "(expected %p, found %p)", \ + current_config->name, \ + pipe_config->name); \ + ret = false; \ + } \ +} while (0) + +#define PIPE_CONF_CHECK_M_N(name) do { \ + if (!intel_compare_link_m_n(¤t_config->name, \ + &pipe_config->name)) { \ + pipe_config_mismatch(fastset, crtc, __stringify(name), \ + "(expected tu %i data %i/%i link %i/%i, " \ + "found tu %i, data %i/%i link %i/%i)", \ + current_config->name.tu, \ + current_config->name.data_m, \ + current_config->name.data_n, \ + current_config->name.link_m, \ + current_config->name.link_n, \ + pipe_config->name.tu, \ + pipe_config->name.data_m, \ + pipe_config->name.data_n, \ + pipe_config->name.link_m, \ + pipe_config->name.link_n); \ + ret = false; \ + } \ +} while (0) + +#define PIPE_CONF_CHECK_TIMINGS(name) do { \ + PIPE_CONF_CHECK_I(name.crtc_hdisplay); \ + PIPE_CONF_CHECK_I(name.crtc_htotal); \ + PIPE_CONF_CHECK_I(name.crtc_hblank_start); \ + PIPE_CONF_CHECK_I(name.crtc_hblank_end); \ + PIPE_CONF_CHECK_I(name.crtc_hsync_start); \ + PIPE_CONF_CHECK_I(name.crtc_hsync_end); \ + PIPE_CONF_CHECK_I(name.crtc_vdisplay); \ + PIPE_CONF_CHECK_I(name.crtc_vtotal); \ + PIPE_CONF_CHECK_I(name.crtc_vblank_start); \ + PIPE_CONF_CHECK_I(name.crtc_vblank_end); \ + PIPE_CONF_CHECK_I(name.crtc_vsync_start); \ + PIPE_CONF_CHECK_I(name.crtc_vsync_end); \ +} while (0) + +#define PIPE_CONF_CHECK_RECT(name) do { \ + PIPE_CONF_CHECK_I(name.x1); \ + PIPE_CONF_CHECK_I(name.x2); \ + PIPE_CONF_CHECK_I(name.y1); \ + PIPE_CONF_CHECK_I(name.y2); \ +} while (0) + +/* This is required for BDW+ where there is only one set of registers for + * switching between high and low RR. + * This macro can be used whenever a comparison has to be made between one + * hw state and multiple sw state variables. + */ +#define PIPE_CONF_CHECK_M_N_ALT(name, alt_name) do { \ + if (!intel_compare_link_m_n(¤t_config->name, \ + &pipe_config->name) && \ + !intel_compare_link_m_n(¤t_config->alt_name, \ + &pipe_config->name)) { \ + pipe_config_mismatch(fastset, crtc, __stringify(name), \ + "(expected tu %i data %i/%i link %i/%i, " \ + "or tu %i data %i/%i link %i/%i, " \ + "found tu %i, data %i/%i link %i/%i)", \ + current_config->name.tu, \ + current_config->name.data_m, \ + current_config->name.data_n, \ + current_config->name.link_m, \ + current_config->name.link_n, \ + current_config->alt_name.tu, \ + current_config->alt_name.data_m, \ + current_config->alt_name.data_n, \ + current_config->alt_name.link_m, \ + current_config->alt_name.link_n, \ + pipe_config->name.tu, \ + pipe_config->name.data_m, \ + pipe_config->name.data_n, \ + pipe_config->name.link_m, \ + pipe_config->name.link_n); \ + ret = false; \ + } \ +} while (0) + +#define PIPE_CONF_CHECK_FLAGS(name, mask) do { \ + if ((current_config->name ^ pipe_config->name) & (mask)) { \ + pipe_config_mismatch(fastset, crtc, __stringify(name), \ + "(%x) (expected %i, found %i)", \ + (mask), \ + current_config->name & (mask), \ + pipe_config->name & (mask)); \ + ret = false; \ + } \ +} while (0) + +#define PIPE_CONF_CHECK_INFOFRAME(name) do { \ + if (!intel_compare_infoframe(¤t_config->infoframes.name, \ + &pipe_config->infoframes.name)) { \ + pipe_config_infoframe_mismatch(dev_priv, fastset, __stringify(name), \ + ¤t_config->infoframes.name, \ + &pipe_config->infoframes.name); \ + ret = false; \ + } \ +} while (0) + +#define PIPE_CONF_CHECK_DP_VSC_SDP(name) do { \ + if (!current_config->has_psr && !pipe_config->has_psr && \ + !intel_compare_dp_vsc_sdp(¤t_config->infoframes.name, \ + &pipe_config->infoframes.name)) { \ + pipe_config_dp_vsc_sdp_mismatch(dev_priv, fastset, __stringify(name), \ + ¤t_config->infoframes.name, \ + &pipe_config->infoframes.name); \ + ret = false; \ + } \ +} while (0) + +#define PIPE_CONF_CHECK_COLOR_LUT(name1, name2, bit_precision) do { \ + if (current_config->name1 != pipe_config->name1) { \ + pipe_config_mismatch(fastset, crtc, __stringify(name1), \ + "(expected %i, found %i, won't compare lut values)", \ + current_config->name1, \ + pipe_config->name1); \ + ret = false;\ + } else { \ + if (!intel_color_lut_equal(current_config->name2, \ + pipe_config->name2, pipe_config->name1, \ + bit_precision)) { \ + pipe_config_mismatch(fastset, crtc, __stringify(name2), \ + "hw_state doesn't match sw_state"); \ + ret = false; \ + } \ + } \ +} while (0) + +#define PIPE_CONF_QUIRK(quirk) \ + ((current_config->quirks | pipe_config->quirks) & (quirk)) + + PIPE_CONF_CHECK_I(hw.enable); + PIPE_CONF_CHECK_I(hw.active); + + PIPE_CONF_CHECK_I(cpu_transcoder); + PIPE_CONF_CHECK_I(mst_master_transcoder); + + PIPE_CONF_CHECK_BOOL(has_pch_encoder); + PIPE_CONF_CHECK_I(fdi_lanes); + PIPE_CONF_CHECK_M_N(fdi_m_n); + + PIPE_CONF_CHECK_I(lane_count); + PIPE_CONF_CHECK_X(lane_lat_optim_mask); + + if (HAS_DOUBLE_BUFFERED_M_N(dev_priv)) { + if (!fastset || !pipe_config->seamless_m_n) + PIPE_CONF_CHECK_M_N_ALT(dp_m_n, dp_m2_n2); + } else { + PIPE_CONF_CHECK_M_N(dp_m_n); + PIPE_CONF_CHECK_M_N(dp_m2_n2); + } + + PIPE_CONF_CHECK_X(output_types); + + PIPE_CONF_CHECK_I(framestart_delay); + PIPE_CONF_CHECK_I(msa_timing_delay); + + PIPE_CONF_CHECK_TIMINGS(hw.pipe_mode); + PIPE_CONF_CHECK_TIMINGS(hw.adjusted_mode); + + PIPE_CONF_CHECK_I(pixel_multiplier); + + PIPE_CONF_CHECK_FLAGS(hw.adjusted_mode.flags, + DRM_MODE_FLAG_INTERLACE); + + if (!PIPE_CONF_QUIRK(PIPE_CONFIG_QUIRK_MODE_SYNC_FLAGS)) { + PIPE_CONF_CHECK_FLAGS(hw.adjusted_mode.flags, + DRM_MODE_FLAG_PHSYNC); + PIPE_CONF_CHECK_FLAGS(hw.adjusted_mode.flags, + DRM_MODE_FLAG_NHSYNC); + PIPE_CONF_CHECK_FLAGS(hw.adjusted_mode.flags, + DRM_MODE_FLAG_PVSYNC); + PIPE_CONF_CHECK_FLAGS(hw.adjusted_mode.flags, + DRM_MODE_FLAG_NVSYNC); + } + + PIPE_CONF_CHECK_I(output_format); + PIPE_CONF_CHECK_BOOL(has_hdmi_sink); + if ((DISPLAY_VER(dev_priv) < 8 && !IS_HASWELL(dev_priv)) || + IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) + PIPE_CONF_CHECK_BOOL(limited_color_range); + + PIPE_CONF_CHECK_BOOL(hdmi_scrambling); + PIPE_CONF_CHECK_BOOL(hdmi_high_tmds_clock_ratio); + PIPE_CONF_CHECK_BOOL(has_infoframe); + PIPE_CONF_CHECK_BOOL(fec_enable); + + PIPE_CONF_CHECK_BOOL_INCOMPLETE(has_audio); + + PIPE_CONF_CHECK_X(gmch_pfit.control); + /* pfit ratios are autocomputed by the hw on gen4+ */ + if (DISPLAY_VER(dev_priv) < 4) + PIPE_CONF_CHECK_X(gmch_pfit.pgm_ratios); + PIPE_CONF_CHECK_X(gmch_pfit.lvds_border_bits); + + /* + * Changing the EDP transcoder input mux + * (A_ONOFF vs. A_ON) requires a full modeset. + */ + PIPE_CONF_CHECK_BOOL(pch_pfit.force_thru); + + if (!fastset) { + PIPE_CONF_CHECK_RECT(pipe_src); + + PIPE_CONF_CHECK_BOOL(pch_pfit.enabled); + PIPE_CONF_CHECK_RECT(pch_pfit.dst); + + PIPE_CONF_CHECK_I(scaler_state.scaler_id); + PIPE_CONF_CHECK_I(pixel_rate); + + PIPE_CONF_CHECK_X(gamma_mode); + if (IS_CHERRYVIEW(dev_priv)) + PIPE_CONF_CHECK_X(cgm_mode); + else + PIPE_CONF_CHECK_X(csc_mode); + PIPE_CONF_CHECK_BOOL(gamma_enable); + PIPE_CONF_CHECK_BOOL(csc_enable); + + PIPE_CONF_CHECK_I(linetime); + PIPE_CONF_CHECK_I(ips_linetime); + + bp_gamma = intel_color_get_gamma_bit_precision(pipe_config); + if (bp_gamma) + PIPE_CONF_CHECK_COLOR_LUT(gamma_mode, hw.gamma_lut, bp_gamma); + + if (current_config->active_planes) { + PIPE_CONF_CHECK_BOOL(has_psr); + PIPE_CONF_CHECK_BOOL(has_psr2); + PIPE_CONF_CHECK_BOOL(enable_psr2_sel_fetch); + PIPE_CONF_CHECK_I(dc3co_exitline); + } + } + + PIPE_CONF_CHECK_BOOL(double_wide); + + if (dev_priv->display.dpll.mgr) { + PIPE_CONF_CHECK_P(shared_dpll); + + PIPE_CONF_CHECK_X(dpll_hw_state.dpll); + PIPE_CONF_CHECK_X(dpll_hw_state.dpll_md); + PIPE_CONF_CHECK_X(dpll_hw_state.fp0); + PIPE_CONF_CHECK_X(dpll_hw_state.fp1); + PIPE_CONF_CHECK_X(dpll_hw_state.wrpll); + PIPE_CONF_CHECK_X(dpll_hw_state.spll); + PIPE_CONF_CHECK_X(dpll_hw_state.ctrl1); + PIPE_CONF_CHECK_X(dpll_hw_state.cfgcr1); + PIPE_CONF_CHECK_X(dpll_hw_state.cfgcr2); + PIPE_CONF_CHECK_X(dpll_hw_state.cfgcr0); + PIPE_CONF_CHECK_X(dpll_hw_state.div0); + PIPE_CONF_CHECK_X(dpll_hw_state.ebb0); + PIPE_CONF_CHECK_X(dpll_hw_state.ebb4); + PIPE_CONF_CHECK_X(dpll_hw_state.pll0); + PIPE_CONF_CHECK_X(dpll_hw_state.pll1); + PIPE_CONF_CHECK_X(dpll_hw_state.pll2); + PIPE_CONF_CHECK_X(dpll_hw_state.pll3); + PIPE_CONF_CHECK_X(dpll_hw_state.pll6); + PIPE_CONF_CHECK_X(dpll_hw_state.pll8); + PIPE_CONF_CHECK_X(dpll_hw_state.pll9); + PIPE_CONF_CHECK_X(dpll_hw_state.pll10); + PIPE_CONF_CHECK_X(dpll_hw_state.pcsdw12); + PIPE_CONF_CHECK_X(dpll_hw_state.mg_refclkin_ctl); + PIPE_CONF_CHECK_X(dpll_hw_state.mg_clktop2_coreclkctl1); + PIPE_CONF_CHECK_X(dpll_hw_state.mg_clktop2_hsclkctl); + PIPE_CONF_CHECK_X(dpll_hw_state.mg_pll_div0); + PIPE_CONF_CHECK_X(dpll_hw_state.mg_pll_div1); + PIPE_CONF_CHECK_X(dpll_hw_state.mg_pll_lf); + PIPE_CONF_CHECK_X(dpll_hw_state.mg_pll_frac_lock); + PIPE_CONF_CHECK_X(dpll_hw_state.mg_pll_ssc); + PIPE_CONF_CHECK_X(dpll_hw_state.mg_pll_bias); + PIPE_CONF_CHECK_X(dpll_hw_state.mg_pll_tdc_coldst_bias); + } + + PIPE_CONF_CHECK_X(dsi_pll.ctrl); + PIPE_CONF_CHECK_X(dsi_pll.div); + + if (IS_G4X(dev_priv) || DISPLAY_VER(dev_priv) >= 5) + PIPE_CONF_CHECK_I(pipe_bpp); + + if (!fastset || !pipe_config->seamless_m_n) { + PIPE_CONF_CHECK_I(hw.pipe_mode.crtc_clock); + PIPE_CONF_CHECK_I(hw.adjusted_mode.crtc_clock); + } + PIPE_CONF_CHECK_I(port_clock); + + PIPE_CONF_CHECK_I(min_voltage_level); + + if (current_config->has_psr || pipe_config->has_psr) + PIPE_CONF_CHECK_X_WITH_MASK(infoframes.enable, + ~intel_hdmi_infoframe_enable(DP_SDP_VSC)); + else + PIPE_CONF_CHECK_X(infoframes.enable); + + PIPE_CONF_CHECK_X(infoframes.gcp); + PIPE_CONF_CHECK_INFOFRAME(avi); + PIPE_CONF_CHECK_INFOFRAME(spd); + PIPE_CONF_CHECK_INFOFRAME(hdmi); + PIPE_CONF_CHECK_INFOFRAME(drm); + PIPE_CONF_CHECK_DP_VSC_SDP(vsc); + + PIPE_CONF_CHECK_X(sync_mode_slaves_mask); + PIPE_CONF_CHECK_I(master_transcoder); + PIPE_CONF_CHECK_X(bigjoiner_pipes); + + PIPE_CONF_CHECK_I(dsc.compression_enable); + PIPE_CONF_CHECK_I(dsc.dsc_split); + PIPE_CONF_CHECK_I(dsc.compressed_bpp); + + PIPE_CONF_CHECK_BOOL(splitter.enable); + PIPE_CONF_CHECK_I(splitter.link_count); + PIPE_CONF_CHECK_I(splitter.pixel_overlap); + + PIPE_CONF_CHECK_BOOL(vrr.enable); + PIPE_CONF_CHECK_I(vrr.vmin); + PIPE_CONF_CHECK_I(vrr.vmax); + PIPE_CONF_CHECK_I(vrr.flipline); + PIPE_CONF_CHECK_I(vrr.pipeline_full); + PIPE_CONF_CHECK_I(vrr.guardband); + +#undef PIPE_CONF_CHECK_X +#undef PIPE_CONF_CHECK_I +#undef PIPE_CONF_CHECK_BOOL +#undef PIPE_CONF_CHECK_BOOL_INCOMPLETE +#undef PIPE_CONF_CHECK_P +#undef PIPE_CONF_CHECK_FLAGS +#undef PIPE_CONF_CHECK_COLOR_LUT +#undef PIPE_CONF_CHECK_TIMINGS +#undef PIPE_CONF_CHECK_RECT +#undef PIPE_CONF_QUIRK + + return ret; +} + +static void +intel_verify_planes(struct intel_atomic_state *state) +{ + struct intel_plane *plane; + const struct intel_plane_state *plane_state; + int i; + + for_each_new_intel_plane_in_state(state, plane, + plane_state, i) + assert_plane(plane, plane_state->planar_slave || + plane_state->uapi.visible); +} + +int intel_modeset_all_pipes(struct intel_atomic_state *state) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_crtc *crtc; + + /* + * Add all pipes to the state, and force + * a modeset on all the active ones. + */ + for_each_intel_crtc(&dev_priv->drm, crtc) { + struct intel_crtc_state *crtc_state; + int ret; + + crtc_state = intel_atomic_get_crtc_state(&state->base, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + if (!crtc_state->hw.active || + drm_atomic_crtc_needs_modeset(&crtc_state->uapi)) + continue; + + crtc_state->uapi.mode_changed = true; + + ret = drm_atomic_add_affected_connectors(&state->base, + &crtc->base); + if (ret) + return ret; + + ret = intel_dp_mst_add_topology_state_for_crtc(state, crtc); + if (ret) + return ret; + + ret = intel_atomic_add_affected_planes(state, crtc); + if (ret) + return ret; + + crtc_state->update_planes |= crtc_state->active_planes; + } + + return 0; +} + +void intel_crtc_update_active_timings(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct drm_display_mode adjusted_mode; + + drm_mode_init(&adjusted_mode, &crtc_state->hw.adjusted_mode); + + if (crtc_state->vrr.enable) { + adjusted_mode.crtc_vtotal = crtc_state->vrr.vmax; + adjusted_mode.crtc_vblank_end = crtc_state->vrr.vmax; + adjusted_mode.crtc_vblank_start = intel_vrr_vmin_vblank_start(crtc_state); + crtc->vmax_vblank_start = intel_vrr_vmax_vblank_start(crtc_state); + } + + drm_calc_timestamping_constants(&crtc->base, &adjusted_mode); + + crtc->mode_flags = crtc_state->mode_flags; + + /* + * The scanline counter increments at the leading edge of hsync. + * + * On most platforms it starts counting from vtotal-1 on the + * first active line. That means the scanline counter value is + * always one less than what we would expect. Ie. just after + * start of vblank, which also occurs at start of hsync (on the + * last active line), the scanline counter will read vblank_start-1. + * + * On gen2 the scanline counter starts counting from 1 instead + * of vtotal-1, so we have to subtract one (or rather add vtotal-1 + * to keep the value positive), instead of adding one. + * + * On HSW+ the behaviour of the scanline counter depends on the output + * type. For DP ports it behaves like most other platforms, but on HDMI + * there's an extra 1 line difference. So we need to add two instead of + * one to the value. + * + * On VLV/CHV DSI the scanline counter would appear to increment + * approx. 1/3 of a scanline before start of vblank. Unfortunately + * that means we can't tell whether we're in vblank or not while + * we're on that particular line. We must still set scanline_offset + * to 1 so that the vblank timestamps come out correct when we query + * the scanline counter from within the vblank interrupt handler. + * However if queried just before the start of vblank we'll get an + * answer that's slightly in the future. + */ + if (DISPLAY_VER(dev_priv) == 2) { + int vtotal; + + vtotal = adjusted_mode.crtc_vtotal; + if (adjusted_mode.flags & DRM_MODE_FLAG_INTERLACE) + vtotal /= 2; + + crtc->scanline_offset = vtotal - 1; + } else if (HAS_DDI(dev_priv) && + intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) { + crtc->scanline_offset = 2; + } else { + crtc->scanline_offset = 1; + } +} + +/* + * This implements the workaround described in the "notes" section of the mode + * set sequence documentation. When going from no pipes or single pipe to + * multiple pipes, and planes are enabled after the pipe, we need to wait at + * least 2 vblanks on the first pipe before enabling planes on the second pipe. + */ +static int hsw_mode_set_planes_workaround(struct intel_atomic_state *state) +{ + struct intel_crtc_state *crtc_state; + struct intel_crtc *crtc; + struct intel_crtc_state *first_crtc_state = NULL; + struct intel_crtc_state *other_crtc_state = NULL; + enum pipe first_pipe = INVALID_PIPE, enabled_pipe = INVALID_PIPE; + int i; + + /* look at all crtc's that are going to be enabled in during modeset */ + for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) { + if (!crtc_state->hw.active || + !intel_crtc_needs_modeset(crtc_state)) + continue; + + if (first_crtc_state) { + other_crtc_state = crtc_state; + break; + } else { + first_crtc_state = crtc_state; + first_pipe = crtc->pipe; + } + } + + /* No workaround needed? */ + if (!first_crtc_state) + return 0; + + /* w/a possibly needed, check how many crtc's are already enabled. */ + for_each_intel_crtc(state->base.dev, crtc) { + crtc_state = intel_atomic_get_crtc_state(&state->base, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + crtc_state->hsw_workaround_pipe = INVALID_PIPE; + + if (!crtc_state->hw.active || + intel_crtc_needs_modeset(crtc_state)) + continue; + + /* 2 or more enabled crtcs means no need for w/a */ + if (enabled_pipe != INVALID_PIPE) + return 0; + + enabled_pipe = crtc->pipe; + } + + if (enabled_pipe != INVALID_PIPE) + first_crtc_state->hsw_workaround_pipe = enabled_pipe; + else if (other_crtc_state) + other_crtc_state->hsw_workaround_pipe = first_pipe; + + return 0; +} + +u8 intel_calc_active_pipes(struct intel_atomic_state *state, + u8 active_pipes) +{ + const struct intel_crtc_state *crtc_state; + struct intel_crtc *crtc; + int i; + + for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) { + if (crtc_state->hw.active) + active_pipes |= BIT(crtc->pipe); + else + active_pipes &= ~BIT(crtc->pipe); + } + + return active_pipes; +} + +static int intel_modeset_checks(struct intel_atomic_state *state) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + + state->modeset = true; + + if (IS_HASWELL(dev_priv)) + return hsw_mode_set_planes_workaround(state); + + return 0; +} + +static void intel_crtc_check_fastset(const struct intel_crtc_state *old_crtc_state, + struct intel_crtc_state *new_crtc_state) +{ + if (!intel_pipe_config_compare(old_crtc_state, new_crtc_state, true)) + return; + + new_crtc_state->uapi.mode_changed = false; + new_crtc_state->update_pipe = true; +} + +static int intel_crtc_add_planes_to_state(struct intel_atomic_state *state, + struct intel_crtc *crtc, + u8 plane_ids_mask) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_plane *plane; + + for_each_intel_plane_on_crtc(&dev_priv->drm, crtc, plane) { + struct intel_plane_state *plane_state; + + if ((plane_ids_mask & BIT(plane->id)) == 0) + continue; + + plane_state = intel_atomic_get_plane_state(state, plane); + if (IS_ERR(plane_state)) + return PTR_ERR(plane_state); + } + + return 0; +} + +int intel_atomic_add_affected_planes(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + const struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + return intel_crtc_add_planes_to_state(state, crtc, + old_crtc_state->enabled_planes | + new_crtc_state->enabled_planes); +} + +static bool active_planes_affects_min_cdclk(struct drm_i915_private *dev_priv) +{ + /* See {hsw,vlv,ivb}_plane_ratio() */ + return IS_BROADWELL(dev_priv) || IS_HASWELL(dev_priv) || + IS_CHERRYVIEW(dev_priv) || IS_VALLEYVIEW(dev_priv) || + IS_IVYBRIDGE(dev_priv); +} + +static int intel_crtc_add_bigjoiner_planes(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_crtc *other) +{ + const struct intel_plane_state *plane_state; + struct intel_plane *plane; + u8 plane_ids = 0; + int i; + + for_each_new_intel_plane_in_state(state, plane, plane_state, i) { + if (plane->pipe == crtc->pipe) + plane_ids |= BIT(plane->id); + } + + return intel_crtc_add_planes_to_state(state, other, plane_ids); +} + +static int intel_bigjoiner_add_affected_planes(struct intel_atomic_state *state) +{ + struct drm_i915_private *i915 = to_i915(state->base.dev); + const struct intel_crtc_state *crtc_state; + struct intel_crtc *crtc; + int i; + + for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) { + struct intel_crtc *other; + + for_each_intel_crtc_in_pipe_mask(&i915->drm, other, + crtc_state->bigjoiner_pipes) { + int ret; + + if (crtc == other) + continue; + + ret = intel_crtc_add_bigjoiner_planes(state, crtc, other); + if (ret) + return ret; + } + } + + return 0; +} + +static int intel_atomic_check_planes(struct intel_atomic_state *state) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_crtc_state *old_crtc_state, *new_crtc_state; + struct intel_plane_state *plane_state; + struct intel_plane *plane; + struct intel_crtc *crtc; + int i, ret; + + ret = icl_add_linked_planes(state); + if (ret) + return ret; + + ret = intel_bigjoiner_add_affected_planes(state); + if (ret) + return ret; + + for_each_new_intel_plane_in_state(state, plane, plane_state, i) { + ret = intel_plane_atomic_check(state, plane); + if (ret) { + drm_dbg_atomic(&dev_priv->drm, + "[PLANE:%d:%s] atomic driver check failed\n", + plane->base.base.id, plane->base.name); + return ret; + } + } + + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, + new_crtc_state, i) { + u8 old_active_planes, new_active_planes; + + ret = icl_check_nv12_planes(new_crtc_state); + if (ret) + return ret; + + /* + * On some platforms the number of active planes affects + * the planes' minimum cdclk calculation. Add such planes + * to the state before we compute the minimum cdclk. + */ + if (!active_planes_affects_min_cdclk(dev_priv)) + continue; + + old_active_planes = old_crtc_state->active_planes & ~BIT(PLANE_CURSOR); + new_active_planes = new_crtc_state->active_planes & ~BIT(PLANE_CURSOR); + + if (hweight8(old_active_planes) == hweight8(new_active_planes)) + continue; + + ret = intel_crtc_add_planes_to_state(state, crtc, new_active_planes); + if (ret) + return ret; + } + + return 0; +} + +static int intel_atomic_check_crtcs(struct intel_atomic_state *state) +{ + struct intel_crtc_state *crtc_state; + struct intel_crtc *crtc; + int i; + + for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) { + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + int ret; + + ret = intel_crtc_atomic_check(state, crtc); + if (ret) { + drm_dbg_atomic(&i915->drm, + "[CRTC:%d:%s] atomic driver check failed\n", + crtc->base.base.id, crtc->base.name); + return ret; + } + } + + return 0; +} + +static bool intel_cpu_transcoders_need_modeset(struct intel_atomic_state *state, + u8 transcoders) +{ + const struct intel_crtc_state *new_crtc_state; + struct intel_crtc *crtc; + int i; + + for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state, i) { + if (new_crtc_state->hw.enable && + transcoders & BIT(new_crtc_state->cpu_transcoder) && + intel_crtc_needs_modeset(new_crtc_state)) + return true; + } + + return false; +} + +static bool intel_pipes_need_modeset(struct intel_atomic_state *state, + u8 pipes) +{ + const struct intel_crtc_state *new_crtc_state; + struct intel_crtc *crtc; + int i; + + for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state, i) { + if (new_crtc_state->hw.enable && + pipes & BIT(crtc->pipe) && + intel_crtc_needs_modeset(new_crtc_state)) + return true; + } + + return false; +} + +static int intel_atomic_check_bigjoiner(struct intel_atomic_state *state, + struct intel_crtc *master_crtc) +{ + struct drm_i915_private *i915 = to_i915(state->base.dev); + struct intel_crtc_state *master_crtc_state = + intel_atomic_get_new_crtc_state(state, master_crtc); + struct intel_crtc *slave_crtc; + + if (!master_crtc_state->bigjoiner_pipes) + return 0; + + /* sanity check */ + if (drm_WARN_ON(&i915->drm, + master_crtc->pipe != bigjoiner_master_pipe(master_crtc_state))) + return -EINVAL; + + if (master_crtc_state->bigjoiner_pipes & ~bigjoiner_pipes(i915)) { + drm_dbg_kms(&i915->drm, + "[CRTC:%d:%s] Cannot act as big joiner master " + "(need 0x%x as pipes, only 0x%x possible)\n", + master_crtc->base.base.id, master_crtc->base.name, + master_crtc_state->bigjoiner_pipes, bigjoiner_pipes(i915)); + return -EINVAL; + } + + for_each_intel_crtc_in_pipe_mask(&i915->drm, slave_crtc, + intel_crtc_bigjoiner_slave_pipes(master_crtc_state)) { + struct intel_crtc_state *slave_crtc_state; + int ret; + + slave_crtc_state = intel_atomic_get_crtc_state(&state->base, slave_crtc); + if (IS_ERR(slave_crtc_state)) + return PTR_ERR(slave_crtc_state); + + /* master being enabled, slave was already configured? */ + if (slave_crtc_state->uapi.enable) { + drm_dbg_kms(&i915->drm, + "[CRTC:%d:%s] Slave is enabled as normal CRTC, but " + "[CRTC:%d:%s] claiming this CRTC for bigjoiner.\n", + slave_crtc->base.base.id, slave_crtc->base.name, + master_crtc->base.base.id, master_crtc->base.name); + return -EINVAL; + } + + /* + * The state copy logic assumes the master crtc gets processed + * before the slave crtc during the main compute_config loop. + * This works because the crtcs are created in pipe order, + * and the hardware requires master pipe < slave pipe as well. + * Should that change we need to rethink the logic. + */ + if (WARN_ON(drm_crtc_index(&master_crtc->base) > + drm_crtc_index(&slave_crtc->base))) + return -EINVAL; + + drm_dbg_kms(&i915->drm, + "[CRTC:%d:%s] Used as slave for big joiner master [CRTC:%d:%s]\n", + slave_crtc->base.base.id, slave_crtc->base.name, + master_crtc->base.base.id, master_crtc->base.name); + + slave_crtc_state->bigjoiner_pipes = + master_crtc_state->bigjoiner_pipes; + + ret = copy_bigjoiner_crtc_state_modeset(state, slave_crtc); + if (ret) + return ret; + } + + return 0; +} + +static void kill_bigjoiner_slave(struct intel_atomic_state *state, + struct intel_crtc *master_crtc) +{ + struct drm_i915_private *i915 = to_i915(state->base.dev); + struct intel_crtc_state *master_crtc_state = + intel_atomic_get_new_crtc_state(state, master_crtc); + struct intel_crtc *slave_crtc; + + for_each_intel_crtc_in_pipe_mask(&i915->drm, slave_crtc, + intel_crtc_bigjoiner_slave_pipes(master_crtc_state)) { + struct intel_crtc_state *slave_crtc_state = + intel_atomic_get_new_crtc_state(state, slave_crtc); + + slave_crtc_state->bigjoiner_pipes = 0; + + intel_crtc_copy_uapi_to_hw_state_modeset(state, slave_crtc); + } + + master_crtc_state->bigjoiner_pipes = 0; +} + +/** + * DOC: asynchronous flip implementation + * + * Asynchronous page flip is the implementation for the DRM_MODE_PAGE_FLIP_ASYNC + * flag. Currently async flip is only supported via the drmModePageFlip IOCTL. + * Correspondingly, support is currently added for primary plane only. + * + * Async flip can only change the plane surface address, so anything else + * changing is rejected from the intel_async_flip_check_hw() function. + * Once this check is cleared, flip done interrupt is enabled using + * the intel_crtc_enable_flip_done() function. + * + * As soon as the surface address register is written, flip done interrupt is + * generated and the requested events are sent to the usersapce in the interrupt + * handler itself. The timestamp and sequence sent during the flip done event + * correspond to the last vblank and have no relation to the actual time when + * the flip done event was sent. + */ +static int intel_async_flip_check_uapi(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *i915 = to_i915(state->base.dev); + const struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + const struct intel_plane_state *old_plane_state; + struct intel_plane_state *new_plane_state; + struct intel_plane *plane; + int i; + + if (!new_crtc_state->uapi.async_flip) + return 0; + + if (!new_crtc_state->uapi.active) { + drm_dbg_kms(&i915->drm, + "[CRTC:%d:%s] not active\n", + crtc->base.base.id, crtc->base.name); + return -EINVAL; + } + + if (intel_crtc_needs_modeset(new_crtc_state)) { + drm_dbg_kms(&i915->drm, + "[CRTC:%d:%s] modeset required\n", + crtc->base.base.id, crtc->base.name); + return -EINVAL; + } + + /* + * FIXME: Bigjoiner+async flip is busted currently. + * Remove this check once the issues are fixed. + */ + if (new_crtc_state->bigjoiner_pipes) { + drm_dbg_kms(&i915->drm, + "[CRTC:%d:%s] async flip disallowed with bigjoiner\n", + crtc->base.base.id, crtc->base.name); + return -EINVAL; + } + + for_each_oldnew_intel_plane_in_state(state, plane, old_plane_state, + new_plane_state, i) { + if (plane->pipe != crtc->pipe) + continue; + + /* + * TODO: Async flip is only supported through the page flip IOCTL + * as of now. So support currently added for primary plane only. + * Support for other planes on platforms on which supports + * this(vlv/chv and icl+) should be added when async flip is + * enabled in the atomic IOCTL path. + */ + if (!plane->async_flip) { + drm_dbg_kms(&i915->drm, + "[PLANE:%d:%s] async flip not supported\n", + plane->base.base.id, plane->base.name); + return -EINVAL; + } + + if (!old_plane_state->uapi.fb || !new_plane_state->uapi.fb) { + drm_dbg_kms(&i915->drm, + "[PLANE:%d:%s] no old or new framebuffer\n", + plane->base.base.id, plane->base.name); + return -EINVAL; + } + } + + return 0; +} + +static int intel_async_flip_check_hw(struct intel_atomic_state *state, struct intel_crtc *crtc) +{ + struct drm_i915_private *i915 = to_i915(state->base.dev); + const struct intel_crtc_state *old_crtc_state, *new_crtc_state; + const struct intel_plane_state *new_plane_state, *old_plane_state; + struct intel_plane *plane; + int i; + + old_crtc_state = intel_atomic_get_old_crtc_state(state, crtc); + new_crtc_state = intel_atomic_get_new_crtc_state(state, crtc); + + if (!new_crtc_state->uapi.async_flip) + return 0; + + if (!new_crtc_state->hw.active) { + drm_dbg_kms(&i915->drm, + "[CRTC:%d:%s] not active\n", + crtc->base.base.id, crtc->base.name); + return -EINVAL; + } + + if (intel_crtc_needs_modeset(new_crtc_state)) { + drm_dbg_kms(&i915->drm, + "[CRTC:%d:%s] modeset required\n", + crtc->base.base.id, crtc->base.name); + return -EINVAL; + } + + if (old_crtc_state->active_planes != new_crtc_state->active_planes) { + drm_dbg_kms(&i915->drm, + "[CRTC:%d:%s] Active planes cannot be in async flip\n", + crtc->base.base.id, crtc->base.name); + return -EINVAL; + } + + for_each_oldnew_intel_plane_in_state(state, plane, old_plane_state, + new_plane_state, i) { + if (plane->pipe != crtc->pipe) + continue; + + /* + * Only async flip capable planes should be in the state + * if we're really about to ask the hardware to perform + * an async flip. We should never get this far otherwise. + */ + if (drm_WARN_ON(&i915->drm, + new_crtc_state->do_async_flip && !plane->async_flip)) + return -EINVAL; + + /* + * Only check async flip capable planes other planes + * may be involved in the initial commit due to + * the wm0/ddb optimization. + * + * TODO maybe should track which planes actually + * were requested to do the async flip... + */ + if (!plane->async_flip) + continue; + + /* + * FIXME: This check is kept generic for all platforms. + * Need to verify this for all gen9 platforms to enable + * this selectively if required. + */ + switch (new_plane_state->hw.fb->modifier) { + case I915_FORMAT_MOD_X_TILED: + case I915_FORMAT_MOD_Y_TILED: + case I915_FORMAT_MOD_Yf_TILED: + case I915_FORMAT_MOD_4_TILED: + break; + default: + drm_dbg_kms(&i915->drm, + "[PLANE:%d:%s] Modifier does not support async flips\n", + plane->base.base.id, plane->base.name); + return -EINVAL; + } + + if (new_plane_state->hw.fb->format->num_planes > 1) { + drm_dbg_kms(&i915->drm, + "[PLANE:%d:%s] Planar formats do not support async flips\n", + plane->base.base.id, plane->base.name); + return -EINVAL; + } + + if (old_plane_state->view.color_plane[0].mapping_stride != + new_plane_state->view.color_plane[0].mapping_stride) { + drm_dbg_kms(&i915->drm, + "[PLANE:%d:%s] Stride cannot be changed in async flip\n", + plane->base.base.id, plane->base.name); + return -EINVAL; + } + + if (old_plane_state->hw.fb->modifier != + new_plane_state->hw.fb->modifier) { + drm_dbg_kms(&i915->drm, + "[PLANE:%d:%s] Modifier cannot be changed in async flip\n", + plane->base.base.id, plane->base.name); + return -EINVAL; + } + + if (old_plane_state->hw.fb->format != + new_plane_state->hw.fb->format) { + drm_dbg_kms(&i915->drm, + "[PLANE:%d:%s] Pixel format cannot be changed in async flip\n", + plane->base.base.id, plane->base.name); + return -EINVAL; + } + + if (old_plane_state->hw.rotation != + new_plane_state->hw.rotation) { + drm_dbg_kms(&i915->drm, + "[PLANE:%d:%s] Rotation cannot be changed in async flip\n", + plane->base.base.id, plane->base.name); + return -EINVAL; + } + + if (!drm_rect_equals(&old_plane_state->uapi.src, &new_plane_state->uapi.src) || + !drm_rect_equals(&old_plane_state->uapi.dst, &new_plane_state->uapi.dst)) { + drm_dbg_kms(&i915->drm, + "[PLANE:%d:%s] Size/co-ordinates cannot be changed in async flip\n", + plane->base.base.id, plane->base.name); + return -EINVAL; + } + + if (old_plane_state->hw.alpha != new_plane_state->hw.alpha) { + drm_dbg_kms(&i915->drm, + "[PLANES:%d:%s] Alpha value cannot be changed in async flip\n", + plane->base.base.id, plane->base.name); + return -EINVAL; + } + + if (old_plane_state->hw.pixel_blend_mode != + new_plane_state->hw.pixel_blend_mode) { + drm_dbg_kms(&i915->drm, + "[PLANE:%d:%s] Pixel blend mode cannot be changed in async flip\n", + plane->base.base.id, plane->base.name); + return -EINVAL; + } + + if (old_plane_state->hw.color_encoding != new_plane_state->hw.color_encoding) { + drm_dbg_kms(&i915->drm, + "[PLANE:%d:%s] Color encoding cannot be changed in async flip\n", + plane->base.base.id, plane->base.name); + return -EINVAL; + } + + if (old_plane_state->hw.color_range != new_plane_state->hw.color_range) { + drm_dbg_kms(&i915->drm, + "[PLANE:%d:%s] Color range cannot be changed in async flip\n", + plane->base.base.id, plane->base.name); + return -EINVAL; + } + + /* plane decryption is allow to change only in synchronous flips */ + if (old_plane_state->decrypt != new_plane_state->decrypt) { + drm_dbg_kms(&i915->drm, + "[PLANE:%d:%s] Decryption cannot be changed in async flip\n", + plane->base.base.id, plane->base.name); + return -EINVAL; + } + } + + return 0; +} + +static int intel_bigjoiner_add_affected_crtcs(struct intel_atomic_state *state) +{ + struct drm_i915_private *i915 = to_i915(state->base.dev); + struct intel_crtc_state *crtc_state; + struct intel_crtc *crtc; + u8 affected_pipes = 0; + u8 modeset_pipes = 0; + int i; + + for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) { + affected_pipes |= crtc_state->bigjoiner_pipes; + if (intel_crtc_needs_modeset(crtc_state)) + modeset_pipes |= crtc_state->bigjoiner_pipes; + } + + for_each_intel_crtc_in_pipe_mask(&i915->drm, crtc, affected_pipes) { + crtc_state = intel_atomic_get_crtc_state(&state->base, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + } + + for_each_intel_crtc_in_pipe_mask(&i915->drm, crtc, modeset_pipes) { + int ret; + + crtc_state = intel_atomic_get_new_crtc_state(state, crtc); + + crtc_state->uapi.mode_changed = true; + + ret = drm_atomic_add_affected_connectors(&state->base, &crtc->base); + if (ret) + return ret; + + ret = intel_atomic_add_affected_planes(state, crtc); + if (ret) + return ret; + } + + for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) { + /* Kill old bigjoiner link, we may re-establish afterwards */ + if (intel_crtc_needs_modeset(crtc_state) && + intel_crtc_is_bigjoiner_master(crtc_state)) + kill_bigjoiner_slave(state, crtc); + } + + return 0; +} + +/** + * intel_atomic_check - validate state object + * @dev: drm device + * @_state: state to validate + */ +static int intel_atomic_check(struct drm_device *dev, + struct drm_atomic_state *_state) +{ + struct drm_i915_private *dev_priv = to_i915(dev); + struct intel_atomic_state *state = to_intel_atomic_state(_state); + struct intel_crtc_state *old_crtc_state, *new_crtc_state; + struct intel_crtc *crtc; + int ret, i; + bool any_ms = false; + + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, + new_crtc_state, i) { + if (new_crtc_state->inherited != old_crtc_state->inherited) + new_crtc_state->uapi.mode_changed = true; + + if (new_crtc_state->uapi.scaling_filter != + old_crtc_state->uapi.scaling_filter) + new_crtc_state->uapi.mode_changed = true; + } + + intel_vrr_check_modeset(state); + + ret = drm_atomic_helper_check_modeset(dev, &state->base); + if (ret) + goto fail; + + for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state, i) { + ret = intel_async_flip_check_uapi(state, crtc); + if (ret) + return ret; + } + + ret = intel_bigjoiner_add_affected_crtcs(state); + if (ret) + goto fail; + + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, + new_crtc_state, i) { + if (!intel_crtc_needs_modeset(new_crtc_state)) { + if (intel_crtc_is_bigjoiner_slave(new_crtc_state)) + copy_bigjoiner_crtc_state_nomodeset(state, crtc); + else + intel_crtc_copy_uapi_to_hw_state_nomodeset(state, crtc); + continue; + } + + if (intel_crtc_is_bigjoiner_slave(new_crtc_state)) { + drm_WARN_ON(&dev_priv->drm, new_crtc_state->uapi.enable); + continue; + } + + ret = intel_crtc_prepare_cleared_state(state, crtc); + if (ret) + goto fail; + + if (!new_crtc_state->hw.enable) + continue; + + ret = intel_modeset_pipe_config(state, crtc); + if (ret) + goto fail; + + ret = intel_atomic_check_bigjoiner(state, crtc); + if (ret) + goto fail; + } + + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, + new_crtc_state, i) { + if (!intel_crtc_needs_modeset(new_crtc_state)) + continue; + + if (new_crtc_state->hw.enable) { + ret = intel_modeset_pipe_config_late(state, crtc); + if (ret) + goto fail; + } + + intel_crtc_check_fastset(old_crtc_state, new_crtc_state); + } + + /** + * Check if fastset is allowed by external dependencies like other + * pipes and transcoders. + * + * Right now it only forces a fullmodeset when the MST master + * transcoder did not changed but the pipe of the master transcoder + * needs a fullmodeset so all slaves also needs to do a fullmodeset or + * in case of port synced crtcs, if one of the synced crtcs + * needs a full modeset, all other synced crtcs should be + * forced a full modeset. + */ + for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state, i) { + if (!new_crtc_state->hw.enable || intel_crtc_needs_modeset(new_crtc_state)) + continue; + + if (intel_dp_mst_is_slave_trans(new_crtc_state)) { + enum transcoder master = new_crtc_state->mst_master_transcoder; + + if (intel_cpu_transcoders_need_modeset(state, BIT(master))) { + new_crtc_state->uapi.mode_changed = true; + new_crtc_state->update_pipe = false; + } + } + + if (is_trans_port_sync_mode(new_crtc_state)) { + u8 trans = new_crtc_state->sync_mode_slaves_mask; + + if (new_crtc_state->master_transcoder != INVALID_TRANSCODER) + trans |= BIT(new_crtc_state->master_transcoder); + + if (intel_cpu_transcoders_need_modeset(state, trans)) { + new_crtc_state->uapi.mode_changed = true; + new_crtc_state->update_pipe = false; + } + } + + if (new_crtc_state->bigjoiner_pipes) { + if (intel_pipes_need_modeset(state, new_crtc_state->bigjoiner_pipes)) { + new_crtc_state->uapi.mode_changed = true; + new_crtc_state->update_pipe = false; + } + } + } + + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, + new_crtc_state, i) { + if (!intel_crtc_needs_modeset(new_crtc_state)) + continue; + + any_ms = true; + + intel_release_shared_dplls(state, crtc); + } + + if (any_ms && !check_digital_port_conflicts(state)) { + drm_dbg_kms(&dev_priv->drm, + "rejecting conflicting digital port configuration\n"); + ret = -EINVAL; + goto fail; + } + + ret = drm_dp_mst_atomic_check(&state->base); + if (ret) + goto fail; + + ret = intel_atomic_check_planes(state); + if (ret) + goto fail; + + ret = intel_compute_global_watermarks(state); + if (ret) + goto fail; + + ret = intel_bw_atomic_check(state); + if (ret) + goto fail; + + ret = intel_cdclk_atomic_check(state, &any_ms); + if (ret) + goto fail; + + if (intel_any_crtc_needs_modeset(state)) + any_ms = true; + + if (any_ms) { + ret = intel_modeset_checks(state); + if (ret) + goto fail; + + ret = intel_modeset_calc_cdclk(state); + if (ret) + return ret; + } + + ret = intel_atomic_check_crtcs(state); + if (ret) + goto fail; + + ret = intel_fbc_atomic_check(state); + if (ret) + goto fail; + + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, + new_crtc_state, i) { + ret = intel_async_flip_check_hw(state, crtc); + if (ret) + goto fail; + + if (!intel_crtc_needs_modeset(new_crtc_state) && + !new_crtc_state->update_pipe) + continue; + + intel_crtc_state_dump(new_crtc_state, state, + intel_crtc_needs_modeset(new_crtc_state) ? + "modeset" : "fastset"); + } + + return 0; + + fail: + if (ret == -EDEADLK) + return ret; + + /* + * FIXME would probably be nice to know which crtc specifically + * caused the failure, in cases where we can pinpoint it. + */ + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, + new_crtc_state, i) + intel_crtc_state_dump(new_crtc_state, state, "failed"); + + return ret; +} + +static int intel_atomic_prepare_commit(struct intel_atomic_state *state) +{ + struct intel_crtc_state *crtc_state; + struct intel_crtc *crtc; + int i, ret; + + ret = drm_atomic_helper_prepare_planes(state->base.dev, &state->base); + if (ret < 0) + return ret; + + for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) { + bool mode_changed = intel_crtc_needs_modeset(crtc_state); + + if (mode_changed || crtc_state->update_pipe || + crtc_state->uapi.color_mgmt_changed) { + intel_dsb_prepare(crtc_state); + } + } + + return 0; +} + +void intel_crtc_arm_fifo_underrun(struct intel_crtc *crtc, + struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + + if (DISPLAY_VER(dev_priv) != 2 || crtc_state->active_planes) + intel_set_cpu_fifo_underrun_reporting(dev_priv, crtc->pipe, true); + + if (crtc_state->has_pch_encoder) { + enum pipe pch_transcoder = + intel_crtc_pch_transcoder(crtc); + + intel_set_pch_fifo_underrun_reporting(dev_priv, pch_transcoder, true); + } +} + +static void intel_pipe_fastset(const struct intel_crtc_state *old_crtc_state, + const struct intel_crtc_state *new_crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(new_crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + + /* + * Update pipe size and adjust fitter if needed: the reason for this is + * that in compute_mode_changes we check the native mode (not the pfit + * mode) to see if we can flip rather than do a full mode set. In the + * fastboot case, we'll flip, but if we don't update the pipesrc and + * pfit state, we'll end up with a big fb scanned out into the wrong + * sized surface. + */ + intel_set_pipe_src_size(new_crtc_state); + + /* on skylake this is done by detaching scalers */ + if (DISPLAY_VER(dev_priv) >= 9) { + if (new_crtc_state->pch_pfit.enabled) + skl_pfit_enable(new_crtc_state); + } else if (HAS_PCH_SPLIT(dev_priv)) { + if (new_crtc_state->pch_pfit.enabled) + ilk_pfit_enable(new_crtc_state); + else if (old_crtc_state->pch_pfit.enabled) + ilk_pfit_disable(old_crtc_state); + } + + /* + * The register is supposedly single buffered so perhaps + * not 100% correct to do this here. But SKL+ calculate + * this based on the adjust pixel rate so pfit changes do + * affect it and so it must be updated for fastsets. + * HSW/BDW only really need this here for fastboot, after + * that the value should not change without a full modeset. + */ + if (DISPLAY_VER(dev_priv) >= 9 || + IS_BROADWELL(dev_priv) || IS_HASWELL(dev_priv)) + hsw_set_linetime_wm(new_crtc_state); + + if (new_crtc_state->seamless_m_n) + intel_cpu_transcoder_set_m1_n1(crtc, new_crtc_state->cpu_transcoder, + &new_crtc_state->dp_m_n); +} + +static void commit_pipe_pre_planes(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + const struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + bool modeset = intel_crtc_needs_modeset(new_crtc_state); + + /* + * During modesets pipe configuration was programmed as the + * CRTC was enabled. + */ + if (!modeset) { + if (new_crtc_state->uapi.color_mgmt_changed || + new_crtc_state->update_pipe) + intel_color_commit_arm(new_crtc_state); + + if (DISPLAY_VER(dev_priv) >= 9 || IS_BROADWELL(dev_priv)) + bdw_set_pipemisc(new_crtc_state); + + if (new_crtc_state->update_pipe) + intel_pipe_fastset(old_crtc_state, new_crtc_state); + } + + intel_psr2_program_trans_man_trk_ctl(new_crtc_state); + + intel_atomic_update_watermarks(state, crtc); +} + +static void commit_pipe_post_planes(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + const struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + /* + * Disable the scaler(s) after the plane(s) so that we don't + * get a catastrophic underrun even if the two operations + * end up happening in two different frames. + */ + if (DISPLAY_VER(dev_priv) >= 9 && + !intel_crtc_needs_modeset(new_crtc_state)) + skl_detach_scalers(new_crtc_state); +} + +static void intel_enable_crtc(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + const struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + if (!intel_crtc_needs_modeset(new_crtc_state)) + return; + + intel_crtc_update_active_timings(new_crtc_state); + + dev_priv->display.funcs.display->crtc_enable(state, crtc); + + if (intel_crtc_is_bigjoiner_slave(new_crtc_state)) + return; + + /* vblanks work again, re-enable pipe CRC. */ + intel_crtc_enable_pipe_crc(crtc); +} + +static void intel_update_crtc(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *i915 = to_i915(state->base.dev); + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + bool modeset = intel_crtc_needs_modeset(new_crtc_state); + + if (!modeset) { + if (new_crtc_state->preload_luts && + (new_crtc_state->uapi.color_mgmt_changed || + new_crtc_state->update_pipe)) + intel_color_load_luts(new_crtc_state); + + intel_pre_plane_update(state, crtc); + + if (new_crtc_state->update_pipe) + intel_encoders_update_pipe(state, crtc); + + if (DISPLAY_VER(i915) >= 11 && + new_crtc_state->update_pipe) + icl_set_pipe_chicken(new_crtc_state); + } + + intel_fbc_update(state, crtc); + + if (!modeset && + (new_crtc_state->uapi.color_mgmt_changed || + new_crtc_state->update_pipe)) + intel_color_commit_noarm(new_crtc_state); + + intel_crtc_planes_update_noarm(state, crtc); + + /* Perform vblank evasion around commit operation */ + intel_pipe_update_start(new_crtc_state); + + commit_pipe_pre_planes(state, crtc); + + intel_crtc_planes_update_arm(state, crtc); + + commit_pipe_post_planes(state, crtc); + + intel_pipe_update_end(new_crtc_state); + + /* + * We usually enable FIFO underrun interrupts as part of the + * CRTC enable sequence during modesets. But when we inherit a + * valid pipe configuration from the BIOS we need to take care + * of enabling them on the CRTC's first fastset. + */ + if (new_crtc_state->update_pipe && !modeset && + old_crtc_state->inherited) + intel_crtc_arm_fifo_underrun(crtc, new_crtc_state); +} + +static void intel_old_crtc_state_disables(struct intel_atomic_state *state, + struct intel_crtc_state *old_crtc_state, + struct intel_crtc_state *new_crtc_state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + + /* + * We need to disable pipe CRC before disabling the pipe, + * or we race against vblank off. + */ + intel_crtc_disable_pipe_crc(crtc); + + dev_priv->display.funcs.display->crtc_disable(state, crtc); + crtc->active = false; + intel_fbc_disable(crtc); + intel_disable_shared_dpll(old_crtc_state); + + /* FIXME unify this for all platforms */ + if (!new_crtc_state->hw.active && + !HAS_GMCH(dev_priv)) + intel_initial_watermarks(state, crtc); +} + +static void intel_commit_modeset_disables(struct intel_atomic_state *state) +{ + struct intel_crtc_state *new_crtc_state, *old_crtc_state; + struct intel_crtc *crtc; + u32 handled = 0; + int i; + + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, + new_crtc_state, i) { + if (!intel_crtc_needs_modeset(new_crtc_state)) + continue; + + if (!old_crtc_state->hw.active) + continue; + + intel_pre_plane_update(state, crtc); + intel_crtc_disable_planes(state, crtc); + } + + /* Only disable port sync and MST slaves */ + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, + new_crtc_state, i) { + if (!intel_crtc_needs_modeset(new_crtc_state)) + continue; + + if (!old_crtc_state->hw.active) + continue; + + /* In case of Transcoder port Sync master slave CRTCs can be + * assigned in any order and we need to make sure that + * slave CRTCs are disabled first and then master CRTC since + * Slave vblanks are masked till Master Vblanks. + */ + if (!is_trans_port_sync_slave(old_crtc_state) && + !intel_dp_mst_is_slave_trans(old_crtc_state) && + !intel_crtc_is_bigjoiner_slave(old_crtc_state)) + continue; + + intel_old_crtc_state_disables(state, old_crtc_state, + new_crtc_state, crtc); + handled |= BIT(crtc->pipe); + } + + /* Disable everything else left on */ + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, + new_crtc_state, i) { + if (!intel_crtc_needs_modeset(new_crtc_state) || + (handled & BIT(crtc->pipe))) + continue; + + if (!old_crtc_state->hw.active) + continue; + + intel_old_crtc_state_disables(state, old_crtc_state, + new_crtc_state, crtc); + } +} + +static void intel_commit_modeset_enables(struct intel_atomic_state *state) +{ + struct intel_crtc_state *new_crtc_state; + struct intel_crtc *crtc; + int i; + + for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state, i) { + if (!new_crtc_state->hw.active) + continue; + + intel_enable_crtc(state, crtc); + intel_update_crtc(state, crtc); + } +} + +static void skl_commit_modeset_enables(struct intel_atomic_state *state) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_crtc *crtc; + struct intel_crtc_state *old_crtc_state, *new_crtc_state; + struct skl_ddb_entry entries[I915_MAX_PIPES] = {}; + u8 update_pipes = 0, modeset_pipes = 0; + int i; + + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { + enum pipe pipe = crtc->pipe; + + if (!new_crtc_state->hw.active) + continue; + + /* ignore allocations for crtc's that have been turned off. */ + if (!intel_crtc_needs_modeset(new_crtc_state)) { + entries[pipe] = old_crtc_state->wm.skl.ddb; + update_pipes |= BIT(pipe); + } else { + modeset_pipes |= BIT(pipe); + } + } + + /* + * Whenever the number of active pipes changes, we need to make sure we + * update the pipes in the right order so that their ddb allocations + * never overlap with each other between CRTC updates. Otherwise we'll + * cause pipe underruns and other bad stuff. + * + * So first lets enable all pipes that do not need a fullmodeset as + * those don't have any external dependency. + */ + while (update_pipes) { + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, + new_crtc_state, i) { + enum pipe pipe = crtc->pipe; + + if ((update_pipes & BIT(pipe)) == 0) + continue; + + if (skl_ddb_allocation_overlaps(&new_crtc_state->wm.skl.ddb, + entries, I915_MAX_PIPES, pipe)) + continue; + + entries[pipe] = new_crtc_state->wm.skl.ddb; + update_pipes &= ~BIT(pipe); + + intel_update_crtc(state, crtc); + + /* + * If this is an already active pipe, it's DDB changed, + * and this isn't the last pipe that needs updating + * then we need to wait for a vblank to pass for the + * new ddb allocation to take effect. + */ + if (!skl_ddb_entry_equal(&new_crtc_state->wm.skl.ddb, + &old_crtc_state->wm.skl.ddb) && + (update_pipes | modeset_pipes)) + intel_crtc_wait_for_next_vblank(crtc); + } + } + + update_pipes = modeset_pipes; + + /* + * Enable all pipes that needs a modeset and do not depends on other + * pipes + */ + for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state, i) { + enum pipe pipe = crtc->pipe; + + if ((modeset_pipes & BIT(pipe)) == 0) + continue; + + if (intel_dp_mst_is_slave_trans(new_crtc_state) || + is_trans_port_sync_master(new_crtc_state) || + intel_crtc_is_bigjoiner_master(new_crtc_state)) + continue; + + modeset_pipes &= ~BIT(pipe); + + intel_enable_crtc(state, crtc); + } + + /* + * Then we enable all remaining pipes that depend on other + * pipes: MST slaves and port sync masters, big joiner master + */ + for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state, i) { + enum pipe pipe = crtc->pipe; + + if ((modeset_pipes & BIT(pipe)) == 0) + continue; + + modeset_pipes &= ~BIT(pipe); + + intel_enable_crtc(state, crtc); + } + + /* + * Finally we do the plane updates/etc. for all pipes that got enabled. + */ + for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state, i) { + enum pipe pipe = crtc->pipe; + + if ((update_pipes & BIT(pipe)) == 0) + continue; + + drm_WARN_ON(&dev_priv->drm, skl_ddb_allocation_overlaps(&new_crtc_state->wm.skl.ddb, + entries, I915_MAX_PIPES, pipe)); + + entries[pipe] = new_crtc_state->wm.skl.ddb; + update_pipes &= ~BIT(pipe); + + intel_update_crtc(state, crtc); + } + + drm_WARN_ON(&dev_priv->drm, modeset_pipes); + drm_WARN_ON(&dev_priv->drm, update_pipes); +} + +static void intel_atomic_helper_free_state(struct drm_i915_private *dev_priv) +{ + struct intel_atomic_state *state, *next; + struct llist_node *freed; + + freed = llist_del_all(&dev_priv->display.atomic_helper.free_list); + llist_for_each_entry_safe(state, next, freed, freed) + drm_atomic_state_put(&state->base); +} + +static void intel_atomic_helper_free_state_worker(struct work_struct *work) +{ + struct drm_i915_private *dev_priv = + container_of(work, typeof(*dev_priv), display.atomic_helper.free_work); + + intel_atomic_helper_free_state(dev_priv); +} + +static void intel_atomic_commit_fence_wait(struct intel_atomic_state *intel_state) +{ + struct wait_queue_entry wait_fence, wait_reset; + struct drm_i915_private *dev_priv = to_i915(intel_state->base.dev); + + init_wait_entry(&wait_fence, 0); + init_wait_entry(&wait_reset, 0); + for (;;) { + prepare_to_wait(&intel_state->commit_ready.wait, + &wait_fence, TASK_UNINTERRUPTIBLE); + prepare_to_wait(bit_waitqueue(&to_gt(dev_priv)->reset.flags, + I915_RESET_MODESET), + &wait_reset, TASK_UNINTERRUPTIBLE); + + + if (i915_sw_fence_done(&intel_state->commit_ready) || + test_bit(I915_RESET_MODESET, &to_gt(dev_priv)->reset.flags)) + break; + + schedule(); + } + finish_wait(&intel_state->commit_ready.wait, &wait_fence); + finish_wait(bit_waitqueue(&to_gt(dev_priv)->reset.flags, + I915_RESET_MODESET), + &wait_reset); +} + +static void intel_cleanup_dsbs(struct intel_atomic_state *state) +{ + struct intel_crtc_state *old_crtc_state, *new_crtc_state; + struct intel_crtc *crtc; + int i; + + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, + new_crtc_state, i) + intel_dsb_cleanup(old_crtc_state); +} + +static void intel_atomic_cleanup_work(struct work_struct *work) +{ + struct intel_atomic_state *state = + container_of(work, struct intel_atomic_state, base.commit_work); + struct drm_i915_private *i915 = to_i915(state->base.dev); + + intel_cleanup_dsbs(state); + drm_atomic_helper_cleanup_planes(&i915->drm, &state->base); + drm_atomic_helper_commit_cleanup_done(&state->base); + drm_atomic_state_put(&state->base); + + intel_atomic_helper_free_state(i915); +} + +static void intel_atomic_prepare_plane_clear_colors(struct intel_atomic_state *state) +{ + struct drm_i915_private *i915 = to_i915(state->base.dev); + struct intel_plane *plane; + struct intel_plane_state *plane_state; + int i; + + for_each_new_intel_plane_in_state(state, plane, plane_state, i) { + struct drm_framebuffer *fb = plane_state->hw.fb; + int cc_plane; + int ret; + + if (!fb) + continue; + + cc_plane = intel_fb_rc_ccs_cc_plane(fb); + if (cc_plane < 0) + continue; + + /* + * The layout of the fast clear color value expected by HW + * (the DRM ABI requiring this value to be located in fb at + * offset 0 of cc plane, plane #2 previous generations or + * plane #1 for flat ccs): + * - 4 x 4 bytes per-channel value + * (in surface type specific float/int format provided by the fb user) + * - 8 bytes native color value used by the display + * (converted/written by GPU during a fast clear operation using the + * above per-channel values) + * + * The commit's FB prepare hook already ensured that FB obj is pinned and the + * caller made sure that the object is synced wrt. the related color clear value + * GPU write on it. + */ + ret = i915_gem_object_read_from_page(intel_fb_obj(fb), + fb->offsets[cc_plane] + 16, + &plane_state->ccval, + sizeof(plane_state->ccval)); + /* The above could only fail if the FB obj has an unexpected backing store type. */ + drm_WARN_ON(&i915->drm, ret); + } +} + +static void intel_atomic_commit_tail(struct intel_atomic_state *state) +{ + struct drm_device *dev = state->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + struct intel_crtc_state *new_crtc_state, *old_crtc_state; + struct intel_crtc *crtc; + struct intel_power_domain_mask put_domains[I915_MAX_PIPES] = {}; + intel_wakeref_t wakeref = 0; + int i; + + intel_atomic_commit_fence_wait(state); + + drm_atomic_helper_wait_for_dependencies(&state->base); + drm_dp_mst_atomic_wait_for_dependencies(&state->base); + + if (state->modeset) + wakeref = intel_display_power_get(dev_priv, POWER_DOMAIN_MODESET); + + intel_atomic_prepare_plane_clear_colors(state); + + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, + new_crtc_state, i) { + if (intel_crtc_needs_modeset(new_crtc_state) || + new_crtc_state->update_pipe) { + intel_modeset_get_crtc_power_domains(new_crtc_state, &put_domains[crtc->pipe]); + } + } + + intel_commit_modeset_disables(state); + + /* FIXME: Eventually get rid of our crtc->config pointer */ + for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state, i) + crtc->config = new_crtc_state; + + if (state->modeset) { + drm_atomic_helper_update_legacy_modeset_state(dev, &state->base); + + intel_set_cdclk_pre_plane_update(state); + + intel_modeset_verify_disabled(dev_priv, state); + } + + intel_sagv_pre_plane_update(state); + + /* Complete the events for pipes that have now been disabled */ + for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state, i) { + bool modeset = intel_crtc_needs_modeset(new_crtc_state); + + /* Complete events for now disable pipes here. */ + if (modeset && !new_crtc_state->hw.active && new_crtc_state->uapi.event) { + spin_lock_irq(&dev->event_lock); + drm_crtc_send_vblank_event(&crtc->base, + new_crtc_state->uapi.event); + spin_unlock_irq(&dev->event_lock); + + new_crtc_state->uapi.event = NULL; + } + } + + intel_encoders_update_prepare(state); + + intel_dbuf_pre_plane_update(state); + intel_mbus_dbox_update(state); + + for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state, i) { + if (new_crtc_state->do_async_flip) + intel_crtc_enable_flip_done(state, crtc); + } + + /* Now enable the clocks, plane, pipe, and connectors that we set up. */ + dev_priv->display.funcs.display->commit_modeset_enables(state); + + intel_encoders_update_complete(state); + + if (state->modeset) + intel_set_cdclk_post_plane_update(state); + + intel_wait_for_vblank_workers(state); + + /* FIXME: We should call drm_atomic_helper_commit_hw_done() here + * already, but still need the state for the delayed optimization. To + * fix this: + * - wrap the optimization/post_plane_update stuff into a per-crtc work. + * - schedule that vblank worker _before_ calling hw_done + * - at the start of commit_tail, cancel it _synchrously + * - switch over to the vblank wait helper in the core after that since + * we don't need out special handling any more. + */ + drm_atomic_helper_wait_for_flip_done(dev, &state->base); + + for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state, i) { + if (new_crtc_state->do_async_flip) + intel_crtc_disable_flip_done(state, crtc); + } + + /* + * Now that the vblank has passed, we can go ahead and program the + * optimal watermarks on platforms that need two-step watermark + * programming. + * + * TODO: Move this (and other cleanup) to an async worker eventually. + */ + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, + new_crtc_state, i) { + /* + * Gen2 reports pipe underruns whenever all planes are disabled. + * So re-enable underrun reporting after some planes get enabled. + * + * We do this before .optimize_watermarks() so that we have a + * chance of catching underruns with the intermediate watermarks + * vs. the new plane configuration. + */ + if (DISPLAY_VER(dev_priv) == 2 && planes_enabling(old_crtc_state, new_crtc_state)) + intel_set_cpu_fifo_underrun_reporting(dev_priv, crtc->pipe, true); + + intel_optimize_watermarks(state, crtc); + } + + intel_dbuf_post_plane_update(state); + intel_psr_post_plane_update(state); + + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { + intel_post_plane_update(state, crtc); + + intel_modeset_put_crtc_power_domains(crtc, &put_domains[crtc->pipe]); + + intel_modeset_verify_crtc(crtc, state, old_crtc_state, new_crtc_state); + + /* + * DSB cleanup is done in cleanup_work aligning with framebuffer + * cleanup. So copy and reset the dsb structure to sync with + * commit_done and later do dsb cleanup in cleanup_work. + */ + old_crtc_state->dsb = fetch_and_zero(&new_crtc_state->dsb); + } + + /* Underruns don't always raise interrupts, so check manually */ + intel_check_cpu_fifo_underruns(dev_priv); + intel_check_pch_fifo_underruns(dev_priv); + + if (state->modeset) + intel_verify_planes(state); + + intel_sagv_post_plane_update(state); + + drm_atomic_helper_commit_hw_done(&state->base); + + if (state->modeset) { + /* As one of the primary mmio accessors, KMS has a high + * likelihood of triggering bugs in unclaimed access. After we + * finish modesetting, see if an error has been flagged, and if + * so enable debugging for the next modeset - and hope we catch + * the culprit. + */ + intel_uncore_arm_unclaimed_mmio_detection(&dev_priv->uncore); + intel_display_power_put(dev_priv, POWER_DOMAIN_MODESET, wakeref); + } + intel_runtime_pm_put(&dev_priv->runtime_pm, state->wakeref); + + /* + * Defer the cleanup of the old state to a separate worker to not + * impede the current task (userspace for blocking modesets) that + * are executed inline. For out-of-line asynchronous modesets/flips, + * deferring to a new worker seems overkill, but we would place a + * schedule point (cond_resched()) here anyway to keep latencies + * down. + */ + INIT_WORK(&state->base.commit_work, intel_atomic_cleanup_work); + queue_work(system_highpri_wq, &state->base.commit_work); +} + +static void intel_atomic_commit_work(struct work_struct *work) +{ + struct intel_atomic_state *state = + container_of(work, struct intel_atomic_state, base.commit_work); + + intel_atomic_commit_tail(state); +} + +static int +intel_atomic_commit_ready(struct i915_sw_fence *fence, + enum i915_sw_fence_notify notify) +{ + struct intel_atomic_state *state = + container_of(fence, struct intel_atomic_state, commit_ready); + + switch (notify) { + case FENCE_COMPLETE: + /* we do blocking waits in the worker, nothing to do here */ + break; + case FENCE_FREE: + { + struct intel_atomic_helper *helper = + &to_i915(state->base.dev)->display.atomic_helper; + + if (llist_add(&state->freed, &helper->free_list)) + schedule_work(&helper->free_work); + break; + } + } + + return NOTIFY_DONE; +} + +static void intel_atomic_track_fbs(struct intel_atomic_state *state) +{ + struct intel_plane_state *old_plane_state, *new_plane_state; + struct intel_plane *plane; + int i; + + for_each_oldnew_intel_plane_in_state(state, plane, old_plane_state, + new_plane_state, i) + intel_frontbuffer_track(to_intel_frontbuffer(old_plane_state->hw.fb), + to_intel_frontbuffer(new_plane_state->hw.fb), + plane->frontbuffer_bit); +} + +static int intel_atomic_commit(struct drm_device *dev, + struct drm_atomic_state *_state, + bool nonblock) +{ + struct intel_atomic_state *state = to_intel_atomic_state(_state); + struct drm_i915_private *dev_priv = to_i915(dev); + int ret = 0; + + state->wakeref = intel_runtime_pm_get(&dev_priv->runtime_pm); + + drm_atomic_state_get(&state->base); + i915_sw_fence_init(&state->commit_ready, + intel_atomic_commit_ready); + + /* + * The intel_legacy_cursor_update() fast path takes care + * of avoiding the vblank waits for simple cursor + * movement and flips. For cursor on/off and size changes, + * we want to perform the vblank waits so that watermark + * updates happen during the correct frames. Gen9+ have + * double buffered watermarks and so shouldn't need this. + * + * Unset state->legacy_cursor_update before the call to + * drm_atomic_helper_setup_commit() because otherwise + * drm_atomic_helper_wait_for_flip_done() is a noop and + * we get FIFO underruns because we didn't wait + * for vblank. + * + * FIXME doing watermarks and fb cleanup from a vblank worker + * (assuming we had any) would solve these problems. + */ + if (DISPLAY_VER(dev_priv) < 9 && state->base.legacy_cursor_update) { + struct intel_crtc_state *new_crtc_state; + struct intel_crtc *crtc; + int i; + + for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state, i) + if (new_crtc_state->wm.need_postvbl_update || + new_crtc_state->update_wm_post) + state->base.legacy_cursor_update = false; + } + + ret = intel_atomic_prepare_commit(state); + if (ret) { + drm_dbg_atomic(&dev_priv->drm, + "Preparing state failed with %i\n", ret); + i915_sw_fence_commit(&state->commit_ready); + intel_runtime_pm_put(&dev_priv->runtime_pm, state->wakeref); + return ret; + } + + ret = drm_atomic_helper_setup_commit(&state->base, nonblock); + if (!ret) + ret = drm_atomic_helper_swap_state(&state->base, true); + if (!ret) + intel_atomic_swap_global_state(state); + + if (ret) { + struct intel_crtc_state *new_crtc_state; + struct intel_crtc *crtc; + int i; + + i915_sw_fence_commit(&state->commit_ready); + + for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state, i) + intel_dsb_cleanup(new_crtc_state); + + drm_atomic_helper_cleanup_planes(dev, &state->base); + intel_runtime_pm_put(&dev_priv->runtime_pm, state->wakeref); + return ret; + } + intel_shared_dpll_swap_state(state); + intel_atomic_track_fbs(state); + + drm_atomic_state_get(&state->base); + INIT_WORK(&state->base.commit_work, intel_atomic_commit_work); + + i915_sw_fence_commit(&state->commit_ready); + if (nonblock && state->modeset) { + queue_work(dev_priv->display.wq.modeset, &state->base.commit_work); + } else if (nonblock) { + queue_work(dev_priv->display.wq.flip, &state->base.commit_work); + } else { + if (state->modeset) + flush_workqueue(dev_priv->display.wq.modeset); + intel_atomic_commit_tail(state); + } + + return 0; +} + +/** + * intel_plane_destroy - destroy a plane + * @plane: plane to destroy + * + * Common destruction function for all types of planes (primary, cursor, + * sprite). + */ +void intel_plane_destroy(struct drm_plane *plane) +{ + drm_plane_cleanup(plane); + kfree(to_intel_plane(plane)); +} + +static void intel_plane_possible_crtcs_init(struct drm_i915_private *dev_priv) +{ + struct intel_plane *plane; + + for_each_intel_plane(&dev_priv->drm, plane) { + struct intel_crtc *crtc = intel_crtc_for_pipe(dev_priv, + plane->pipe); + + plane->base.possible_crtcs = drm_crtc_mask(&crtc->base); + } +} + + +int intel_get_pipe_from_crtc_id_ioctl(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_i915_get_pipe_from_crtc_id *pipe_from_crtc_id = data; + struct drm_crtc *drmmode_crtc; + struct intel_crtc *crtc; + + drmmode_crtc = drm_crtc_find(dev, file, pipe_from_crtc_id->crtc_id); + if (!drmmode_crtc) + return -ENOENT; + + crtc = to_intel_crtc(drmmode_crtc); + pipe_from_crtc_id->pipe = crtc->pipe; + + return 0; +} + +static u32 intel_encoder_possible_clones(struct intel_encoder *encoder) +{ + struct drm_device *dev = encoder->base.dev; + struct intel_encoder *source_encoder; + u32 possible_clones = 0; + + for_each_intel_encoder(dev, source_encoder) { + if (encoders_cloneable(encoder, source_encoder)) + possible_clones |= drm_encoder_mask(&source_encoder->base); + } + + return possible_clones; +} + +static u32 intel_encoder_possible_crtcs(struct intel_encoder *encoder) +{ + struct drm_device *dev = encoder->base.dev; + struct intel_crtc *crtc; + u32 possible_crtcs = 0; + + for_each_intel_crtc_in_pipe_mask(dev, crtc, encoder->pipe_mask) + possible_crtcs |= drm_crtc_mask(&crtc->base); + + return possible_crtcs; +} + +static bool ilk_has_edp_a(struct drm_i915_private *dev_priv) +{ + if (!IS_MOBILE(dev_priv)) + return false; + + if ((intel_de_read(dev_priv, DP_A) & DP_DETECTED) == 0) + return false; + + if (IS_IRONLAKE(dev_priv) && (intel_de_read(dev_priv, FUSE_STRAP) & ILK_eDP_A_DISABLE)) + return false; + + return true; +} + +static bool intel_ddi_crt_present(struct drm_i915_private *dev_priv) +{ + if (DISPLAY_VER(dev_priv) >= 9) + return false; + + if (IS_HSW_ULT(dev_priv) || IS_BDW_ULT(dev_priv)) + return false; + + if (HAS_PCH_LPT_H(dev_priv) && + intel_de_read(dev_priv, SFUSE_STRAP) & SFUSE_STRAP_CRT_DISABLED) + return false; + + /* DDI E can't be used if DDI A requires 4 lanes */ + if (intel_de_read(dev_priv, DDI_BUF_CTL(PORT_A)) & DDI_A_4_LANES) + return false; + + if (!dev_priv->display.vbt.int_crt_support) + return false; + + return true; +} + +static void intel_setup_outputs(struct drm_i915_private *dev_priv) +{ + struct intel_encoder *encoder; + bool dpd_is_edp = false; + + intel_pps_unlock_regs_wa(dev_priv); + + if (!HAS_DISPLAY(dev_priv)) + return; + + if (IS_DG2(dev_priv)) { + intel_ddi_init(dev_priv, PORT_A); + intel_ddi_init(dev_priv, PORT_B); + intel_ddi_init(dev_priv, PORT_C); + intel_ddi_init(dev_priv, PORT_D_XELPD); + intel_ddi_init(dev_priv, PORT_TC1); + } else if (IS_ALDERLAKE_P(dev_priv)) { + intel_ddi_init(dev_priv, PORT_A); + intel_ddi_init(dev_priv, PORT_B); + intel_ddi_init(dev_priv, PORT_TC1); + intel_ddi_init(dev_priv, PORT_TC2); + intel_ddi_init(dev_priv, PORT_TC3); + intel_ddi_init(dev_priv, PORT_TC4); + icl_dsi_init(dev_priv); + } else if (IS_ALDERLAKE_S(dev_priv)) { + intel_ddi_init(dev_priv, PORT_A); + intel_ddi_init(dev_priv, PORT_TC1); + intel_ddi_init(dev_priv, PORT_TC2); + intel_ddi_init(dev_priv, PORT_TC3); + intel_ddi_init(dev_priv, PORT_TC4); + } else if (IS_DG1(dev_priv) || IS_ROCKETLAKE(dev_priv)) { + intel_ddi_init(dev_priv, PORT_A); + intel_ddi_init(dev_priv, PORT_B); + intel_ddi_init(dev_priv, PORT_TC1); + intel_ddi_init(dev_priv, PORT_TC2); + } else if (DISPLAY_VER(dev_priv) >= 12) { + intel_ddi_init(dev_priv, PORT_A); + intel_ddi_init(dev_priv, PORT_B); + intel_ddi_init(dev_priv, PORT_TC1); + intel_ddi_init(dev_priv, PORT_TC2); + intel_ddi_init(dev_priv, PORT_TC3); + intel_ddi_init(dev_priv, PORT_TC4); + intel_ddi_init(dev_priv, PORT_TC5); + intel_ddi_init(dev_priv, PORT_TC6); + icl_dsi_init(dev_priv); + } else if (IS_JSL_EHL(dev_priv)) { + intel_ddi_init(dev_priv, PORT_A); + intel_ddi_init(dev_priv, PORT_B); + intel_ddi_init(dev_priv, PORT_C); + intel_ddi_init(dev_priv, PORT_D); + icl_dsi_init(dev_priv); + } else if (DISPLAY_VER(dev_priv) == 11) { + intel_ddi_init(dev_priv, PORT_A); + intel_ddi_init(dev_priv, PORT_B); + intel_ddi_init(dev_priv, PORT_C); + intel_ddi_init(dev_priv, PORT_D); + intel_ddi_init(dev_priv, PORT_E); + intel_ddi_init(dev_priv, PORT_F); + icl_dsi_init(dev_priv); + } else if (IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv)) { + intel_ddi_init(dev_priv, PORT_A); + intel_ddi_init(dev_priv, PORT_B); + intel_ddi_init(dev_priv, PORT_C); + vlv_dsi_init(dev_priv); + } else if (DISPLAY_VER(dev_priv) >= 9) { + intel_ddi_init(dev_priv, PORT_A); + intel_ddi_init(dev_priv, PORT_B); + intel_ddi_init(dev_priv, PORT_C); + intel_ddi_init(dev_priv, PORT_D); + intel_ddi_init(dev_priv, PORT_E); + } else if (HAS_DDI(dev_priv)) { + u32 found; + + if (intel_ddi_crt_present(dev_priv)) + intel_crt_init(dev_priv); + + /* Haswell uses DDI functions to detect digital outputs. */ + found = intel_de_read(dev_priv, DDI_BUF_CTL(PORT_A)) & DDI_INIT_DISPLAY_DETECTED; + if (found) + intel_ddi_init(dev_priv, PORT_A); + + found = intel_de_read(dev_priv, SFUSE_STRAP); + if (found & SFUSE_STRAP_DDIB_DETECTED) + intel_ddi_init(dev_priv, PORT_B); + if (found & SFUSE_STRAP_DDIC_DETECTED) + intel_ddi_init(dev_priv, PORT_C); + if (found & SFUSE_STRAP_DDID_DETECTED) + intel_ddi_init(dev_priv, PORT_D); + if (found & SFUSE_STRAP_DDIF_DETECTED) + intel_ddi_init(dev_priv, PORT_F); + } else if (HAS_PCH_SPLIT(dev_priv)) { + int found; + + /* + * intel_edp_init_connector() depends on this completing first, + * to prevent the registration of both eDP and LVDS and the + * incorrect sharing of the PPS. + */ + intel_lvds_init(dev_priv); + intel_crt_init(dev_priv); + + dpd_is_edp = intel_dp_is_port_edp(dev_priv, PORT_D); + + if (ilk_has_edp_a(dev_priv)) + g4x_dp_init(dev_priv, DP_A, PORT_A); + + if (intel_de_read(dev_priv, PCH_HDMIB) & SDVO_DETECTED) { + /* PCH SDVOB multiplex with HDMIB */ + found = intel_sdvo_init(dev_priv, PCH_SDVOB, PORT_B); + if (!found) + g4x_hdmi_init(dev_priv, PCH_HDMIB, PORT_B); + if (!found && (intel_de_read(dev_priv, PCH_DP_B) & DP_DETECTED)) + g4x_dp_init(dev_priv, PCH_DP_B, PORT_B); + } + + if (intel_de_read(dev_priv, PCH_HDMIC) & SDVO_DETECTED) + g4x_hdmi_init(dev_priv, PCH_HDMIC, PORT_C); + + if (!dpd_is_edp && intel_de_read(dev_priv, PCH_HDMID) & SDVO_DETECTED) + g4x_hdmi_init(dev_priv, PCH_HDMID, PORT_D); + + if (intel_de_read(dev_priv, PCH_DP_C) & DP_DETECTED) + g4x_dp_init(dev_priv, PCH_DP_C, PORT_C); + + if (intel_de_read(dev_priv, PCH_DP_D) & DP_DETECTED) + g4x_dp_init(dev_priv, PCH_DP_D, PORT_D); + } else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { + bool has_edp, has_port; + + if (IS_VALLEYVIEW(dev_priv) && dev_priv->display.vbt.int_crt_support) + intel_crt_init(dev_priv); + + /* + * The DP_DETECTED bit is the latched state of the DDC + * SDA pin at boot. However since eDP doesn't require DDC + * (no way to plug in a DP->HDMI dongle) the DDC pins for + * eDP ports may have been muxed to an alternate function. + * Thus we can't rely on the DP_DETECTED bit alone to detect + * eDP ports. Consult the VBT as well as DP_DETECTED to + * detect eDP ports. + * + * Sadly the straps seem to be missing sometimes even for HDMI + * ports (eg. on Voyo V3 - CHT x7-Z8700), so check both strap + * and VBT for the presence of the port. Additionally we can't + * trust the port type the VBT declares as we've seen at least + * HDMI ports that the VBT claim are DP or eDP. + */ + has_edp = intel_dp_is_port_edp(dev_priv, PORT_B); + has_port = intel_bios_is_port_present(dev_priv, PORT_B); + if (intel_de_read(dev_priv, VLV_DP_B) & DP_DETECTED || has_port) + has_edp &= g4x_dp_init(dev_priv, VLV_DP_B, PORT_B); + if ((intel_de_read(dev_priv, VLV_HDMIB) & SDVO_DETECTED || has_port) && !has_edp) + g4x_hdmi_init(dev_priv, VLV_HDMIB, PORT_B); + + has_edp = intel_dp_is_port_edp(dev_priv, PORT_C); + has_port = intel_bios_is_port_present(dev_priv, PORT_C); + if (intel_de_read(dev_priv, VLV_DP_C) & DP_DETECTED || has_port) + has_edp &= g4x_dp_init(dev_priv, VLV_DP_C, PORT_C); + if ((intel_de_read(dev_priv, VLV_HDMIC) & SDVO_DETECTED || has_port) && !has_edp) + g4x_hdmi_init(dev_priv, VLV_HDMIC, PORT_C); + + if (IS_CHERRYVIEW(dev_priv)) { + /* + * eDP not supported on port D, + * so no need to worry about it + */ + has_port = intel_bios_is_port_present(dev_priv, PORT_D); + if (intel_de_read(dev_priv, CHV_DP_D) & DP_DETECTED || has_port) + g4x_dp_init(dev_priv, CHV_DP_D, PORT_D); + if (intel_de_read(dev_priv, CHV_HDMID) & SDVO_DETECTED || has_port) + g4x_hdmi_init(dev_priv, CHV_HDMID, PORT_D); + } + + vlv_dsi_init(dev_priv); + } else if (IS_PINEVIEW(dev_priv)) { + intel_lvds_init(dev_priv); + intel_crt_init(dev_priv); + } else if (IS_DISPLAY_VER(dev_priv, 3, 4)) { + bool found = false; + + if (IS_MOBILE(dev_priv)) + intel_lvds_init(dev_priv); + + intel_crt_init(dev_priv); + + if (intel_de_read(dev_priv, GEN3_SDVOB) & SDVO_DETECTED) { + drm_dbg_kms(&dev_priv->drm, "probing SDVOB\n"); + found = intel_sdvo_init(dev_priv, GEN3_SDVOB, PORT_B); + if (!found && IS_G4X(dev_priv)) { + drm_dbg_kms(&dev_priv->drm, + "probing HDMI on SDVOB\n"); + g4x_hdmi_init(dev_priv, GEN4_HDMIB, PORT_B); + } + + if (!found && IS_G4X(dev_priv)) + g4x_dp_init(dev_priv, DP_B, PORT_B); + } + + /* Before G4X SDVOC doesn't have its own detect register */ + + if (intel_de_read(dev_priv, GEN3_SDVOB) & SDVO_DETECTED) { + drm_dbg_kms(&dev_priv->drm, "probing SDVOC\n"); + found = intel_sdvo_init(dev_priv, GEN3_SDVOC, PORT_C); + } + + if (!found && (intel_de_read(dev_priv, GEN3_SDVOC) & SDVO_DETECTED)) { + + if (IS_G4X(dev_priv)) { + drm_dbg_kms(&dev_priv->drm, + "probing HDMI on SDVOC\n"); + g4x_hdmi_init(dev_priv, GEN4_HDMIC, PORT_C); + } + if (IS_G4X(dev_priv)) + g4x_dp_init(dev_priv, DP_C, PORT_C); + } + + if (IS_G4X(dev_priv) && (intel_de_read(dev_priv, DP_D) & DP_DETECTED)) + g4x_dp_init(dev_priv, DP_D, PORT_D); + + if (SUPPORTS_TV(dev_priv)) + intel_tv_init(dev_priv); + } else if (DISPLAY_VER(dev_priv) == 2) { + if (IS_I85X(dev_priv)) + intel_lvds_init(dev_priv); + + intel_crt_init(dev_priv); + intel_dvo_init(dev_priv); + } + + for_each_intel_encoder(&dev_priv->drm, encoder) { + encoder->base.possible_crtcs = + intel_encoder_possible_crtcs(encoder); + encoder->base.possible_clones = + intel_encoder_possible_clones(encoder); + } + + intel_init_pch_refclk(dev_priv); + + drm_helper_move_panel_connectors_to_head(&dev_priv->drm); +} + +static int max_dotclock(struct drm_i915_private *i915) +{ + int max_dotclock = i915->max_dotclk_freq; + + /* icl+ might use bigjoiner */ + if (DISPLAY_VER(i915) >= 11) + max_dotclock *= 2; + + return max_dotclock; +} + +static enum drm_mode_status +intel_mode_valid(struct drm_device *dev, + const struct drm_display_mode *mode) +{ + struct drm_i915_private *dev_priv = to_i915(dev); + int hdisplay_max, htotal_max; + int vdisplay_max, vtotal_max; + + /* + * Can't reject DBLSCAN here because Xorg ddxen can add piles + * of DBLSCAN modes to the output's mode list when they detect + * the scaling mode property on the connector. And they don't + * ask the kernel to validate those modes in any way until + * modeset time at which point the client gets a protocol error. + * So in order to not upset those clients we silently ignore the + * DBLSCAN flag on such connectors. For other connectors we will + * reject modes with the DBLSCAN flag in encoder->compute_config(). + * And we always reject DBLSCAN modes in connector->mode_valid() + * as we never want such modes on the connector's mode list. + */ + + if (mode->vscan > 1) + return MODE_NO_VSCAN; + + if (mode->flags & DRM_MODE_FLAG_HSKEW) + return MODE_H_ILLEGAL; + + if (mode->flags & (DRM_MODE_FLAG_CSYNC | + DRM_MODE_FLAG_NCSYNC | + DRM_MODE_FLAG_PCSYNC)) + return MODE_HSYNC; + + if (mode->flags & (DRM_MODE_FLAG_BCAST | + DRM_MODE_FLAG_PIXMUX | + DRM_MODE_FLAG_CLKDIV2)) + return MODE_BAD; + + /* + * Reject clearly excessive dotclocks early to + * avoid having to worry about huge integers later. + */ + if (mode->clock > max_dotclock(dev_priv)) + return MODE_CLOCK_HIGH; + + /* Transcoder timing limits */ + if (DISPLAY_VER(dev_priv) >= 11) { + hdisplay_max = 16384; + vdisplay_max = 8192; + htotal_max = 16384; + vtotal_max = 8192; + } else if (DISPLAY_VER(dev_priv) >= 9 || + IS_BROADWELL(dev_priv) || IS_HASWELL(dev_priv)) { + hdisplay_max = 8192; /* FDI max 4096 handled elsewhere */ + vdisplay_max = 4096; + htotal_max = 8192; + vtotal_max = 8192; + } else if (DISPLAY_VER(dev_priv) >= 3) { + hdisplay_max = 4096; + vdisplay_max = 4096; + htotal_max = 8192; + vtotal_max = 8192; + } else { + hdisplay_max = 2048; + vdisplay_max = 2048; + htotal_max = 4096; + vtotal_max = 4096; + } + + if (mode->hdisplay > hdisplay_max || + mode->hsync_start > htotal_max || + mode->hsync_end > htotal_max || + mode->htotal > htotal_max) + return MODE_H_ILLEGAL; + + if (mode->vdisplay > vdisplay_max || + mode->vsync_start > vtotal_max || + mode->vsync_end > vtotal_max || + mode->vtotal > vtotal_max) + return MODE_V_ILLEGAL; + + return MODE_OK; +} + +enum drm_mode_status intel_cpu_transcoder_mode_valid(struct drm_i915_private *dev_priv, + const struct drm_display_mode *mode) +{ + /* + * Additional transcoder timing limits, + * excluding BXT/GLK DSI transcoders. + */ + if (DISPLAY_VER(dev_priv) >= 5) { + if (mode->hdisplay < 64 || + mode->htotal - mode->hdisplay < 32) + return MODE_H_ILLEGAL; + + if (mode->vtotal - mode->vdisplay < 5) + return MODE_V_ILLEGAL; + } else { + if (mode->htotal - mode->hdisplay < 32) + return MODE_H_ILLEGAL; + + if (mode->vtotal - mode->vdisplay < 3) + return MODE_V_ILLEGAL; + } + + /* + * Cantiga+ cannot handle modes with a hsync front porch of 0. + * WaPruneModeWithIncorrectHsyncOffset:ctg,elk,ilk,snb,ivb,vlv,hsw. + */ + if ((DISPLAY_VER(dev_priv) > 4 || IS_G4X(dev_priv)) && + mode->hsync_start == mode->hdisplay) + return MODE_H_ILLEGAL; + + return MODE_OK; +} + +enum drm_mode_status +intel_mode_valid_max_plane_size(struct drm_i915_private *dev_priv, + const struct drm_display_mode *mode, + bool bigjoiner) +{ + int plane_width_max, plane_height_max; + + /* + * intel_mode_valid() should be + * sufficient on older platforms. + */ + if (DISPLAY_VER(dev_priv) < 9) + return MODE_OK; + + /* + * Most people will probably want a fullscreen + * plane so let's not advertize modes that are + * too big for that. + */ + if (DISPLAY_VER(dev_priv) >= 11) { + plane_width_max = 5120 << bigjoiner; + plane_height_max = 4320; + } else { + plane_width_max = 5120; + plane_height_max = 4096; + } + + if (mode->hdisplay > plane_width_max) + return MODE_H_ILLEGAL; + + if (mode->vdisplay > plane_height_max) + return MODE_V_ILLEGAL; + + return MODE_OK; +} + +static const struct drm_mode_config_funcs intel_mode_funcs = { + .fb_create = intel_user_framebuffer_create, + .get_format_info = intel_fb_get_format_info, + .output_poll_changed = intel_fbdev_output_poll_changed, + .mode_valid = intel_mode_valid, + .atomic_check = intel_atomic_check, + .atomic_commit = intel_atomic_commit, + .atomic_state_alloc = intel_atomic_state_alloc, + .atomic_state_clear = intel_atomic_state_clear, + .atomic_state_free = intel_atomic_state_free, +}; + +static const struct intel_display_funcs skl_display_funcs = { + .get_pipe_config = hsw_get_pipe_config, + .crtc_enable = hsw_crtc_enable, + .crtc_disable = hsw_crtc_disable, + .commit_modeset_enables = skl_commit_modeset_enables, + .get_initial_plane_config = skl_get_initial_plane_config, +}; + +static const struct intel_display_funcs ddi_display_funcs = { + .get_pipe_config = hsw_get_pipe_config, + .crtc_enable = hsw_crtc_enable, + .crtc_disable = hsw_crtc_disable, + .commit_modeset_enables = intel_commit_modeset_enables, + .get_initial_plane_config = i9xx_get_initial_plane_config, +}; + +static const struct intel_display_funcs pch_split_display_funcs = { + .get_pipe_config = ilk_get_pipe_config, + .crtc_enable = ilk_crtc_enable, + .crtc_disable = ilk_crtc_disable, + .commit_modeset_enables = intel_commit_modeset_enables, + .get_initial_plane_config = i9xx_get_initial_plane_config, +}; + +static const struct intel_display_funcs vlv_display_funcs = { + .get_pipe_config = i9xx_get_pipe_config, + .crtc_enable = valleyview_crtc_enable, + .crtc_disable = i9xx_crtc_disable, + .commit_modeset_enables = intel_commit_modeset_enables, + .get_initial_plane_config = i9xx_get_initial_plane_config, +}; + +static const struct intel_display_funcs i9xx_display_funcs = { + .get_pipe_config = i9xx_get_pipe_config, + .crtc_enable = i9xx_crtc_enable, + .crtc_disable = i9xx_crtc_disable, + .commit_modeset_enables = intel_commit_modeset_enables, + .get_initial_plane_config = i9xx_get_initial_plane_config, +}; + +/** + * intel_init_display_hooks - initialize the display modesetting hooks + * @dev_priv: device private + */ +void intel_init_display_hooks(struct drm_i915_private *dev_priv) +{ + if (!HAS_DISPLAY(dev_priv)) + return; + + intel_init_cdclk_hooks(dev_priv); + intel_audio_hooks_init(dev_priv); + + intel_dpll_init_clock_hook(dev_priv); + + if (DISPLAY_VER(dev_priv) >= 9) { + dev_priv->display.funcs.display = &skl_display_funcs; + } else if (HAS_DDI(dev_priv)) { + dev_priv->display.funcs.display = &ddi_display_funcs; + } else if (HAS_PCH_SPLIT(dev_priv)) { + dev_priv->display.funcs.display = &pch_split_display_funcs; + } else if (IS_CHERRYVIEW(dev_priv) || + IS_VALLEYVIEW(dev_priv)) { + dev_priv->display.funcs.display = &vlv_display_funcs; + } else { + dev_priv->display.funcs.display = &i9xx_display_funcs; + } + + intel_fdi_init_hook(dev_priv); +} + +void intel_modeset_init_hw(struct drm_i915_private *i915) +{ + struct intel_cdclk_state *cdclk_state; + + if (!HAS_DISPLAY(i915)) + return; + + cdclk_state = to_intel_cdclk_state(i915->display.cdclk.obj.state); + + intel_update_cdclk(i915); + intel_cdclk_dump_config(i915, &i915->display.cdclk.hw, "Current CDCLK"); + cdclk_state->logical = cdclk_state->actual = i915->display.cdclk.hw; +} + +static int sanitize_watermarks_add_affected(struct drm_atomic_state *state) +{ + struct drm_plane *plane; + struct intel_crtc *crtc; + + for_each_intel_crtc(state->dev, crtc) { + struct intel_crtc_state *crtc_state; + + crtc_state = intel_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + if (crtc_state->hw.active) { + /* + * Preserve the inherited flag to avoid + * taking the full modeset path. + */ + crtc_state->inherited = true; + } + } + + drm_for_each_plane(plane, state->dev) { + struct drm_plane_state *plane_state; + + plane_state = drm_atomic_get_plane_state(state, plane); + if (IS_ERR(plane_state)) + return PTR_ERR(plane_state); + } + + return 0; +} + +/* + * Calculate what we think the watermarks should be for the state we've read + * out of the hardware and then immediately program those watermarks so that + * we ensure the hardware settings match our internal state. + * + * We can calculate what we think WM's should be by creating a duplicate of the + * current state (which was constructed during hardware readout) and running it + * through the atomic check code to calculate new watermark values in the + * state object. + */ +static void sanitize_watermarks(struct drm_i915_private *dev_priv) +{ + struct drm_atomic_state *state; + struct intel_atomic_state *intel_state; + struct intel_crtc *crtc; + struct intel_crtc_state *crtc_state; + struct drm_modeset_acquire_ctx ctx; + int ret; + int i; + + /* Only supported on platforms that use atomic watermark design */ + if (!dev_priv->display.funcs.wm->optimize_watermarks) + return; + + state = drm_atomic_state_alloc(&dev_priv->drm); + if (drm_WARN_ON(&dev_priv->drm, !state)) + return; + + intel_state = to_intel_atomic_state(state); + + drm_modeset_acquire_init(&ctx, 0); + +retry: + state->acquire_ctx = &ctx; + + /* + * Hardware readout is the only time we don't want to calculate + * intermediate watermarks (since we don't trust the current + * watermarks). + */ + if (!HAS_GMCH(dev_priv)) + intel_state->skip_intermediate_wm = true; + + ret = sanitize_watermarks_add_affected(state); + if (ret) + goto fail; + + ret = intel_atomic_check(&dev_priv->drm, state); + if (ret) + goto fail; + + /* Write calculated watermark values back */ + for_each_new_intel_crtc_in_state(intel_state, crtc, crtc_state, i) { + crtc_state->wm.need_postvbl_update = true; + intel_optimize_watermarks(intel_state, crtc); + + to_intel_crtc_state(crtc->base.state)->wm = crtc_state->wm; + } + +fail: + if (ret == -EDEADLK) { + drm_atomic_state_clear(state); + drm_modeset_backoff(&ctx); + goto retry; + } + + /* + * If we fail here, it means that the hardware appears to be + * programmed in a way that shouldn't be possible, given our + * understanding of watermark requirements. This might mean a + * mistake in the hardware readout code or a mistake in the + * watermark calculations for a given platform. Raise a WARN + * so that this is noticeable. + * + * If this actually happens, we'll have to just leave the + * BIOS-programmed watermarks untouched and hope for the best. + */ + drm_WARN(&dev_priv->drm, ret, + "Could not determine valid watermarks for inherited state\n"); + + drm_atomic_state_put(state); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +static int intel_initial_commit(struct drm_device *dev) +{ + struct drm_atomic_state *state = NULL; + struct drm_modeset_acquire_ctx ctx; + struct intel_crtc *crtc; + int ret = 0; + + state = drm_atomic_state_alloc(dev); + if (!state) + return -ENOMEM; + + drm_modeset_acquire_init(&ctx, 0); + +retry: + state->acquire_ctx = &ctx; + + for_each_intel_crtc(dev, crtc) { + struct intel_crtc_state *crtc_state = + intel_atomic_get_crtc_state(state, crtc); + + if (IS_ERR(crtc_state)) { + ret = PTR_ERR(crtc_state); + goto out; + } + + if (crtc_state->hw.active) { + struct intel_encoder *encoder; + + /* + * We've not yet detected sink capabilities + * (audio,infoframes,etc.) and thus we don't want to + * force a full state recomputation yet. We want that to + * happen only for the first real commit from userspace. + * So preserve the inherited flag for the time being. + */ + crtc_state->inherited = true; + + ret = drm_atomic_add_affected_planes(state, &crtc->base); + if (ret) + goto out; + + /* + * FIXME hack to force a LUT update to avoid the + * plane update forcing the pipe gamma on without + * having a proper LUT loaded. Remove once we + * have readout for pipe gamma enable. + */ + crtc_state->uapi.color_mgmt_changed = true; + + for_each_intel_encoder_mask(dev, encoder, + crtc_state->uapi.encoder_mask) { + if (encoder->initial_fastset_check && + !encoder->initial_fastset_check(encoder, crtc_state)) { + ret = drm_atomic_add_affected_connectors(state, + &crtc->base); + if (ret) + goto out; + } + } + } + } + + ret = drm_atomic_commit(state); + +out: + if (ret == -EDEADLK) { + drm_atomic_state_clear(state); + drm_modeset_backoff(&ctx); + goto retry; + } + + drm_atomic_state_put(state); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); + + return ret; +} + +static const struct drm_mode_config_helper_funcs intel_mode_config_funcs = { + .atomic_commit_setup = drm_dp_mst_atomic_setup_commit, +}; + +static void intel_mode_config_init(struct drm_i915_private *i915) +{ + struct drm_mode_config *mode_config = &i915->drm.mode_config; + + drm_mode_config_init(&i915->drm); + INIT_LIST_HEAD(&i915->global_obj_list); + + mode_config->min_width = 0; + mode_config->min_height = 0; + + mode_config->preferred_depth = 24; + mode_config->prefer_shadow = 1; + + mode_config->funcs = &intel_mode_funcs; + mode_config->helper_private = &intel_mode_config_funcs; + + mode_config->async_page_flip = HAS_ASYNC_FLIPS(i915); + + /* + * Maximum framebuffer dimensions, chosen to match + * the maximum render engine surface size on gen4+. + */ + if (DISPLAY_VER(i915) >= 7) { + mode_config->max_width = 16384; + mode_config->max_height = 16384; + } else if (DISPLAY_VER(i915) >= 4) { + mode_config->max_width = 8192; + mode_config->max_height = 8192; + } else if (DISPLAY_VER(i915) == 3) { + mode_config->max_width = 4096; + mode_config->max_height = 4096; + } else { + mode_config->max_width = 2048; + mode_config->max_height = 2048; + } + + if (IS_I845G(i915) || IS_I865G(i915)) { + mode_config->cursor_width = IS_I845G(i915) ? 64 : 512; + mode_config->cursor_height = 1023; + } else if (IS_I830(i915) || IS_I85X(i915) || + IS_I915G(i915) || IS_I915GM(i915)) { + mode_config->cursor_width = 64; + mode_config->cursor_height = 64; + } else { + mode_config->cursor_width = 256; + mode_config->cursor_height = 256; + } +} + +static void intel_mode_config_cleanup(struct drm_i915_private *i915) +{ + intel_atomic_global_obj_cleanup(i915); + drm_mode_config_cleanup(&i915->drm); +} + +/* part #1: call before irq install */ +int intel_modeset_init_noirq(struct drm_i915_private *i915) +{ + int ret; + + if (i915_inject_probe_failure(i915)) + return -ENODEV; + + if (HAS_DISPLAY(i915)) { + ret = drm_vblank_init(&i915->drm, + INTEL_NUM_PIPES(i915)); + if (ret) + return ret; + } + + intel_bios_init(i915); + + ret = intel_vga_register(i915); + if (ret) + goto cleanup_bios; + + /* FIXME: completely on the wrong abstraction layer */ + intel_power_domains_init_hw(i915, false); + + if (!HAS_DISPLAY(i915)) + return 0; + + intel_dmc_ucode_init(i915); + + i915->display.wq.modeset = alloc_ordered_workqueue("i915_modeset", 0); + i915->display.wq.flip = alloc_workqueue("i915_flip", WQ_HIGHPRI | + WQ_UNBOUND, WQ_UNBOUND_MAX_ACTIVE); + + intel_mode_config_init(i915); + + ret = intel_cdclk_init(i915); + if (ret) + goto cleanup_vga_client_pw_domain_dmc; + + ret = intel_dbuf_init(i915); + if (ret) + goto cleanup_vga_client_pw_domain_dmc; + + ret = intel_bw_init(i915); + if (ret) + goto cleanup_vga_client_pw_domain_dmc; + + init_llist_head(&i915->display.atomic_helper.free_list); + INIT_WORK(&i915->display.atomic_helper.free_work, + intel_atomic_helper_free_state_worker); + + intel_init_quirks(i915); + + intel_fbc_init(i915); + + return 0; + +cleanup_vga_client_pw_domain_dmc: + intel_dmc_ucode_fini(i915); + intel_power_domains_driver_remove(i915); + intel_vga_unregister(i915); +cleanup_bios: + intel_bios_driver_remove(i915); + + return ret; +} + +/* part #2: call after irq install, but before gem init */ +int intel_modeset_init_nogem(struct drm_i915_private *i915) +{ + struct drm_device *dev = &i915->drm; + enum pipe pipe; + struct intel_crtc *crtc; + int ret; + + if (!HAS_DISPLAY(i915)) + return 0; + + intel_init_pm(i915); + + intel_panel_sanitize_ssc(i915); + + intel_pps_setup(i915); + + intel_gmbus_setup(i915); + + drm_dbg_kms(&i915->drm, "%d display pipe%s available.\n", + INTEL_NUM_PIPES(i915), + INTEL_NUM_PIPES(i915) > 1 ? "s" : ""); + + for_each_pipe(i915, pipe) { + ret = intel_crtc_init(i915, pipe); + if (ret) { + intel_mode_config_cleanup(i915); + return ret; + } + } + + intel_plane_possible_crtcs_init(i915); + intel_shared_dpll_init(i915); + intel_fdi_pll_freq_update(i915); + + intel_update_czclk(i915); + intel_modeset_init_hw(i915); + intel_dpll_update_ref_clks(i915); + + intel_hdcp_component_init(i915); + + if (i915->display.cdclk.max_cdclk_freq == 0) + intel_update_max_cdclk(i915); + + /* + * If the platform has HTI, we need to find out whether it has reserved + * any display resources before we create our display outputs. + */ + if (INTEL_INFO(i915)->display.has_hti) + i915->hti_state = intel_de_read(i915, HDPORT_STATE); + + /* Just disable it once at startup */ + intel_vga_disable(i915); + intel_setup_outputs(i915); + + drm_modeset_lock_all(dev); + intel_modeset_setup_hw_state(i915, dev->mode_config.acquire_ctx); + intel_acpi_assign_connector_fwnodes(i915); + drm_modeset_unlock_all(dev); + + for_each_intel_crtc(dev, crtc) { + if (!to_intel_crtc_state(crtc->base.state)->uapi.active) + continue; + intel_crtc_initial_plane_config(crtc); + } + + /* + * Make sure hardware watermarks really match the state we read out. + * Note that we need to do this after reconstructing the BIOS fb's + * since the watermark calculation done here will use pstate->fb. + */ + if (!HAS_GMCH(i915)) + sanitize_watermarks(i915); + + return 0; +} + +/* part #3: call after gem init */ +int intel_modeset_init(struct drm_i915_private *i915) +{ + int ret; + + if (!HAS_DISPLAY(i915)) + return 0; + + /* + * Force all active planes to recompute their states. So that on + * mode_setcrtc after probe, all the intel_plane_state variables + * are already calculated and there is no assert_plane warnings + * during bootup. + */ + ret = intel_initial_commit(&i915->drm); + if (ret) + drm_dbg_kms(&i915->drm, "Initial modeset failed, %d\n", ret); + + intel_overlay_setup(i915); + + ret = intel_fbdev_init(&i915->drm); + if (ret) + return ret; + + /* Only enable hotplug handling once the fbdev is fully set up. */ + intel_hpd_init(i915); + intel_hpd_poll_disable(i915); + + skl_watermark_ipc_init(i915); + + return 0; +} + +void i830_enable_pipe(struct drm_i915_private *dev_priv, enum pipe pipe) +{ + struct intel_crtc *crtc = intel_crtc_for_pipe(dev_priv, pipe); + /* 640x480@60Hz, ~25175 kHz */ + struct dpll clock = { + .m1 = 18, + .m2 = 7, + .p1 = 13, + .p2 = 4, + .n = 2, + }; + u32 dpll, fp; + int i; + + drm_WARN_ON(&dev_priv->drm, + i9xx_calc_dpll_params(48000, &clock) != 25154); + + drm_dbg_kms(&dev_priv->drm, + "enabling pipe %c due to force quirk (vco=%d dot=%d)\n", + pipe_name(pipe), clock.vco, clock.dot); + + fp = i9xx_dpll_compute_fp(&clock); + dpll = DPLL_DVO_2X_MODE | + DPLL_VGA_MODE_DIS | + ((clock.p1 - 2) << DPLL_FPA01_P1_POST_DIV_SHIFT) | + PLL_P2_DIVIDE_BY_4 | + PLL_REF_INPUT_DREFCLK | + DPLL_VCO_ENABLE; + + intel_de_write(dev_priv, HTOTAL(pipe), (640 - 1) | ((800 - 1) << 16)); + intel_de_write(dev_priv, HBLANK(pipe), (640 - 1) | ((800 - 1) << 16)); + intel_de_write(dev_priv, HSYNC(pipe), (656 - 1) | ((752 - 1) << 16)); + intel_de_write(dev_priv, VTOTAL(pipe), (480 - 1) | ((525 - 1) << 16)); + intel_de_write(dev_priv, VBLANK(pipe), (480 - 1) | ((525 - 1) << 16)); + intel_de_write(dev_priv, VSYNC(pipe), (490 - 1) | ((492 - 1) << 16)); + intel_de_write(dev_priv, PIPESRC(pipe), ((640 - 1) << 16) | (480 - 1)); + + intel_de_write(dev_priv, FP0(pipe), fp); + intel_de_write(dev_priv, FP1(pipe), fp); + + /* + * Apparently we need to have VGA mode enabled prior to changing + * the P1/P2 dividers. Otherwise the DPLL will keep using the old + * dividers, even though the register value does change. + */ + intel_de_write(dev_priv, DPLL(pipe), dpll & ~DPLL_VGA_MODE_DIS); + intel_de_write(dev_priv, DPLL(pipe), dpll); + + /* Wait for the clocks to stabilize. */ + intel_de_posting_read(dev_priv, DPLL(pipe)); + udelay(150); + + /* The pixel multiplier can only be updated once the + * DPLL is enabled and the clocks are stable. + * + * So write it again. + */ + intel_de_write(dev_priv, DPLL(pipe), dpll); + + /* We do this three times for luck */ + for (i = 0; i < 3 ; i++) { + intel_de_write(dev_priv, DPLL(pipe), dpll); + intel_de_posting_read(dev_priv, DPLL(pipe)); + udelay(150); /* wait for warmup */ + } + + intel_de_write(dev_priv, PIPECONF(pipe), PIPECONF_ENABLE); + intel_de_posting_read(dev_priv, PIPECONF(pipe)); + + intel_wait_for_pipe_scanline_moving(crtc); +} + +void i830_disable_pipe(struct drm_i915_private *dev_priv, enum pipe pipe) +{ + struct intel_crtc *crtc = intel_crtc_for_pipe(dev_priv, pipe); + + drm_dbg_kms(&dev_priv->drm, "disabling pipe %c due to force quirk\n", + pipe_name(pipe)); + + drm_WARN_ON(&dev_priv->drm, + intel_de_read(dev_priv, DSPCNTR(PLANE_A)) & DISP_ENABLE); + drm_WARN_ON(&dev_priv->drm, + intel_de_read(dev_priv, DSPCNTR(PLANE_B)) & DISP_ENABLE); + drm_WARN_ON(&dev_priv->drm, + intel_de_read(dev_priv, DSPCNTR(PLANE_C)) & DISP_ENABLE); + drm_WARN_ON(&dev_priv->drm, + intel_de_read(dev_priv, CURCNTR(PIPE_A)) & MCURSOR_MODE_MASK); + drm_WARN_ON(&dev_priv->drm, + intel_de_read(dev_priv, CURCNTR(PIPE_B)) & MCURSOR_MODE_MASK); + + intel_de_write(dev_priv, PIPECONF(pipe), 0); + intel_de_posting_read(dev_priv, PIPECONF(pipe)); + + intel_wait_for_pipe_scanline_stopped(crtc); + + intel_de_write(dev_priv, DPLL(pipe), DPLL_VGA_MODE_DIS); + intel_de_posting_read(dev_priv, DPLL(pipe)); +} + +void intel_display_resume(struct drm_device *dev) +{ + struct drm_i915_private *i915 = to_i915(dev); + struct drm_atomic_state *state = i915->modeset_restore_state; + struct drm_modeset_acquire_ctx ctx; + int ret; + + if (!HAS_DISPLAY(i915)) + return; + + i915->modeset_restore_state = NULL; + if (state) + state->acquire_ctx = &ctx; + + drm_modeset_acquire_init(&ctx, 0); + + while (1) { + ret = drm_modeset_lock_all_ctx(dev, &ctx); + if (ret != -EDEADLK) + break; + + drm_modeset_backoff(&ctx); + } + + if (!ret) + ret = __intel_display_resume(i915, state, &ctx); + + skl_watermark_ipc_update(i915); + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); + + if (ret) + drm_err(&i915->drm, + "Restoring old state failed with %i\n", ret); + if (state) + drm_atomic_state_put(state); +} + +static void intel_hpd_poll_fini(struct drm_i915_private *i915) +{ + struct intel_connector *connector; + struct drm_connector_list_iter conn_iter; + + /* Kill all the work that may have been queued by hpd. */ + drm_connector_list_iter_begin(&i915->drm, &conn_iter); + for_each_intel_connector_iter(connector, &conn_iter) { + if (connector->modeset_retry_work.func) + cancel_work_sync(&connector->modeset_retry_work); + if (connector->hdcp.shim) { + cancel_delayed_work_sync(&connector->hdcp.check_work); + cancel_work_sync(&connector->hdcp.prop_work); + } + } + drm_connector_list_iter_end(&conn_iter); +} + +/* part #1: call before irq uninstall */ +void intel_modeset_driver_remove(struct drm_i915_private *i915) +{ + if (!HAS_DISPLAY(i915)) + return; + + flush_workqueue(i915->display.wq.flip); + flush_workqueue(i915->display.wq.modeset); + + flush_work(&i915->display.atomic_helper.free_work); + drm_WARN_ON(&i915->drm, !llist_empty(&i915->display.atomic_helper.free_list)); + + /* + * MST topology needs to be suspended so we don't have any calls to + * fbdev after it's finalized. MST will be destroyed later as part of + * drm_mode_config_cleanup() + */ + intel_dp_mst_suspend(i915); +} + +/* part #2: call after irq uninstall */ +void intel_modeset_driver_remove_noirq(struct drm_i915_private *i915) +{ + if (!HAS_DISPLAY(i915)) + return; + + /* + * Due to the hpd irq storm handling the hotplug work can re-arm the + * poll handlers. Hence disable polling after hpd handling is shut down. + */ + intel_hpd_poll_fini(i915); + + /* poll work can call into fbdev, hence clean that up afterwards */ + intel_fbdev_fini(i915); + + intel_unregister_dsm_handler(); + + /* flush any delayed tasks or pending work */ + flush_scheduled_work(); + + intel_hdcp_component_fini(i915); + + intel_mode_config_cleanup(i915); + + intel_overlay_cleanup(i915); + + intel_gmbus_teardown(i915); + + destroy_workqueue(i915->display.wq.flip); + destroy_workqueue(i915->display.wq.modeset); + + intel_fbc_cleanup(i915); +} + +/* part #3: call after gem init */ +void intel_modeset_driver_remove_nogem(struct drm_i915_private *i915) +{ + intel_dmc_ucode_fini(i915); + + intel_power_domains_driver_remove(i915); + + intel_vga_unregister(i915); + + intel_bios_driver_remove(i915); +} + +bool intel_modeset_probe_defer(struct pci_dev *pdev) +{ + struct drm_privacy_screen *privacy_screen; + + /* + * apple-gmux is needed on dual GPU MacBook Pro + * to probe the panel if we're the inactive GPU. + */ + if (vga_switcheroo_client_probe_defer(pdev)) + return true; + + /* If the LCD panel has a privacy-screen, wait for it */ + privacy_screen = drm_privacy_screen_get(&pdev->dev, NULL); + if (IS_ERR(privacy_screen) && PTR_ERR(privacy_screen) == -EPROBE_DEFER) + return true; + + drm_privacy_screen_put(privacy_screen); + + return false; +} + +void intel_display_driver_register(struct drm_i915_private *i915) +{ + if (!HAS_DISPLAY(i915)) + return; + + intel_display_debugfs_register(i915); + + /* Must be done after probing outputs */ + intel_opregion_register(i915); + intel_acpi_video_register(i915); + + intel_audio_init(i915); + + /* + * Some ports require correctly set-up hpd registers for + * detection to work properly (leading to ghost connected + * connector status), e.g. VGA on gm45. Hence we can only set + * up the initial fbdev config after hpd irqs are fully + * enabled. We do it last so that the async config cannot run + * before the connectors are registered. + */ + intel_fbdev_initial_config_async(&i915->drm); + + /* + * We need to coordinate the hotplugs with the asynchronous + * fbdev configuration, for which we use the + * fbdev->async_cookie. + */ + drm_kms_helper_poll_init(&i915->drm); +} + +void intel_display_driver_unregister(struct drm_i915_private *i915) +{ + if (!HAS_DISPLAY(i915)) + return; + + intel_fbdev_unregister(i915); + intel_audio_deinit(i915); + + /* + * After flushing the fbdev (incl. a late async config which + * will have delayed queuing of a hotplug event), then flush + * the hotplug events. + */ + drm_kms_helper_poll_fini(&i915->drm); + drm_atomic_helper_shutdown(&i915->drm); + + acpi_video_unregister(); + intel_opregion_unregister(i915); +} + +bool intel_scanout_needs_vtd_wa(struct drm_i915_private *i915) +{ + return DISPLAY_VER(i915) >= 6 && i915_vtd_active(i915); +} diff --git a/drivers/gpu/drm/i915/display/intel_display.h b/drivers/gpu/drm/i915/display/intel_display.h new file mode 100644 index 000000000..b4f941674 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_display.h @@ -0,0 +1,721 @@ +/* + * Copyright © 2006-2019 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +#ifndef _INTEL_DISPLAY_H_ +#define _INTEL_DISPLAY_H_ + +#include + +#include "i915_reg_defs.h" + +enum drm_scaling_filter; +struct dpll; +struct drm_connector; +struct drm_device; +struct drm_display_mode; +struct drm_encoder; +struct drm_file; +struct drm_format_info; +struct drm_framebuffer; +struct drm_i915_gem_object; +struct drm_i915_private; +struct drm_mode_fb_cmd2; +struct drm_modeset_acquire_ctx; +struct drm_plane; +struct drm_plane_state; +struct i915_address_space; +struct i915_gtt_view; +struct intel_atomic_state; +struct intel_crtc; +struct intel_crtc_state; +struct intel_digital_port; +struct intel_dp; +struct intel_encoder; +struct intel_initial_plane_config; +struct intel_load_detect_pipe; +struct intel_plane; +struct intel_plane_state; +struct intel_power_domain_mask; +struct intel_remapped_info; +struct intel_rotation_info; +struct pci_dev; + +enum i915_gpio { + GPIOA, + GPIOB, + GPIOC, + GPIOD, + GPIOE, + GPIOF, + GPIOG, + GPIOH, + __GPIOI_UNUSED, + GPIOJ, + GPIOK, + GPIOL, + GPIOM, + GPION, + GPIOO, +}; + +/* + * Keep the pipe enum values fixed: the code assumes that PIPE_A=0, the + * rest have consecutive values and match the enum values of transcoders + * with a 1:1 transcoder -> pipe mapping. + */ +enum pipe { + INVALID_PIPE = -1, + + PIPE_A = 0, + PIPE_B, + PIPE_C, + PIPE_D, + _PIPE_EDP, + + I915_MAX_PIPES = _PIPE_EDP +}; + +#define pipe_name(p) ((p) + 'A') + +enum transcoder { + INVALID_TRANSCODER = -1, + /* + * The following transcoders have a 1:1 transcoder -> pipe mapping, + * keep their values fixed: the code assumes that TRANSCODER_A=0, the + * rest have consecutive values and match the enum values of the pipes + * they map to. + */ + TRANSCODER_A = PIPE_A, + TRANSCODER_B = PIPE_B, + TRANSCODER_C = PIPE_C, + TRANSCODER_D = PIPE_D, + + /* + * The following transcoders can map to any pipe, their enum value + * doesn't need to stay fixed. + */ + TRANSCODER_EDP, + TRANSCODER_DSI_0, + TRANSCODER_DSI_1, + TRANSCODER_DSI_A = TRANSCODER_DSI_0, /* legacy DSI */ + TRANSCODER_DSI_C = TRANSCODER_DSI_1, /* legacy DSI */ + + I915_MAX_TRANSCODERS +}; + +static inline const char *transcoder_name(enum transcoder transcoder) +{ + switch (transcoder) { + case TRANSCODER_A: + return "A"; + case TRANSCODER_B: + return "B"; + case TRANSCODER_C: + return "C"; + case TRANSCODER_D: + return "D"; + case TRANSCODER_EDP: + return "EDP"; + case TRANSCODER_DSI_A: + return "DSI A"; + case TRANSCODER_DSI_C: + return "DSI C"; + default: + return ""; + } +} + +static inline bool transcoder_is_dsi(enum transcoder transcoder) +{ + return transcoder == TRANSCODER_DSI_A || transcoder == TRANSCODER_DSI_C; +} + +/* + * Global legacy plane identifier. Valid only for primary/sprite + * planes on pre-g4x, and only for primary planes on g4x-bdw. + */ +enum i9xx_plane_id { + PLANE_A, + PLANE_B, + PLANE_C, +}; + +#define plane_name(p) ((p) + 'A') +#define sprite_name(p, s) ((p) * RUNTIME_INFO(dev_priv)->num_sprites[(p)] + (s) + 'A') + +/* + * Per-pipe plane identifier. + * I915_MAX_PLANES in the enum below is the maximum (across all platforms) + * number of planes per CRTC. Not all platforms really have this many planes, + * which means some arrays of size I915_MAX_PLANES may have unused entries + * between the topmost sprite plane and the cursor plane. + * + * This is expected to be passed to various register macros + * (eg. PLANE_CTL(), PS_PLANE_SEL(), etc.) so adjust with care. + */ +enum plane_id { + PLANE_PRIMARY, + PLANE_SPRITE0, + PLANE_SPRITE1, + PLANE_SPRITE2, + PLANE_SPRITE3, + PLANE_SPRITE4, + PLANE_SPRITE5, + PLANE_CURSOR, + + I915_MAX_PLANES, +}; + +#define for_each_plane_id_on_crtc(__crtc, __p) \ + for ((__p) = PLANE_PRIMARY; (__p) < I915_MAX_PLANES; (__p)++) \ + for_each_if((__crtc)->plane_ids_mask & BIT(__p)) + +#define for_each_dbuf_slice(__dev_priv, __slice) \ + for ((__slice) = DBUF_S1; (__slice) < I915_MAX_DBUF_SLICES; (__slice)++) \ + for_each_if(INTEL_INFO(__dev_priv)->display.dbuf.slice_mask & BIT(__slice)) + +#define for_each_dbuf_slice_in_mask(__dev_priv, __slice, __mask) \ + for_each_dbuf_slice((__dev_priv), (__slice)) \ + for_each_if((__mask) & BIT(__slice)) + +enum port { + PORT_NONE = -1, + + PORT_A = 0, + PORT_B, + PORT_C, + PORT_D, + PORT_E, + PORT_F, + PORT_G, + PORT_H, + PORT_I, + + /* tgl+ */ + PORT_TC1 = PORT_D, + PORT_TC2, + PORT_TC3, + PORT_TC4, + PORT_TC5, + PORT_TC6, + + /* XE_LPD repositions D/E offsets and bitfields */ + PORT_D_XELPD = PORT_TC5, + PORT_E_XELPD, + + I915_MAX_PORTS +}; + +#define port_name(p) ((p) + 'A') + +/* + * Ports identifier referenced from other drivers. + * Expected to remain stable over time + */ +static inline const char *port_identifier(enum port port) +{ + switch (port) { + case PORT_A: + return "Port A"; + case PORT_B: + return "Port B"; + case PORT_C: + return "Port C"; + case PORT_D: + return "Port D"; + case PORT_E: + return "Port E"; + case PORT_F: + return "Port F"; + case PORT_G: + return "Port G"; + case PORT_H: + return "Port H"; + case PORT_I: + return "Port I"; + default: + return ""; + } +} + +enum tc_port { + TC_PORT_NONE = -1, + + TC_PORT_1 = 0, + TC_PORT_2, + TC_PORT_3, + TC_PORT_4, + TC_PORT_5, + TC_PORT_6, + + I915_MAX_TC_PORTS +}; + +enum tc_port_mode { + TC_PORT_DISCONNECTED, + TC_PORT_TBT_ALT, + TC_PORT_DP_ALT, + TC_PORT_LEGACY, +}; + +enum dpio_channel { + DPIO_CH0, + DPIO_CH1 +}; + +enum dpio_phy { + DPIO_PHY0, + DPIO_PHY1, + DPIO_PHY2, +}; + +enum aux_ch { + AUX_CH_A, + AUX_CH_B, + AUX_CH_C, + AUX_CH_D, + AUX_CH_E, /* ICL+ */ + AUX_CH_F, + AUX_CH_G, + AUX_CH_H, + AUX_CH_I, + + /* tgl+ */ + AUX_CH_USBC1 = AUX_CH_D, + AUX_CH_USBC2, + AUX_CH_USBC3, + AUX_CH_USBC4, + AUX_CH_USBC5, + AUX_CH_USBC6, + + /* XE_LPD repositions D/E offsets and bitfields */ + AUX_CH_D_XELPD = AUX_CH_USBC5, + AUX_CH_E_XELPD, +}; + +#define aux_ch_name(a) ((a) + 'A') + +/* Used by dp and fdi links */ +struct intel_link_m_n { + u32 tu; + u32 data_m; + u32 data_n; + u32 link_m; + u32 link_n; +}; + +enum phy { + PHY_NONE = -1, + + PHY_A = 0, + PHY_B, + PHY_C, + PHY_D, + PHY_E, + PHY_F, + PHY_G, + PHY_H, + PHY_I, + + I915_MAX_PHYS +}; + +#define phy_name(a) ((a) + 'A') + +enum phy_fia { + FIA1, + FIA2, + FIA3, +}; + +enum hpd_pin { + HPD_NONE = 0, + HPD_TV = HPD_NONE, /* TV is known to be unreliable */ + HPD_CRT, + HPD_SDVO_B, + HPD_SDVO_C, + HPD_PORT_A, + HPD_PORT_B, + HPD_PORT_C, + HPD_PORT_D, + HPD_PORT_E, + HPD_PORT_TC1, + HPD_PORT_TC2, + HPD_PORT_TC3, + HPD_PORT_TC4, + HPD_PORT_TC5, + HPD_PORT_TC6, + + HPD_NUM_PINS +}; + +#define for_each_hpd_pin(__pin) \ + for ((__pin) = (HPD_NONE + 1); (__pin) < HPD_NUM_PINS; (__pin)++) + +#define for_each_pipe(__dev_priv, __p) \ + for ((__p) = 0; (__p) < I915_MAX_PIPES; (__p)++) \ + for_each_if(RUNTIME_INFO(__dev_priv)->pipe_mask & BIT(__p)) + +#define for_each_pipe_masked(__dev_priv, __p, __mask) \ + for_each_pipe(__dev_priv, __p) \ + for_each_if((__mask) & BIT(__p)) + +#define for_each_cpu_transcoder(__dev_priv, __t) \ + for ((__t) = 0; (__t) < I915_MAX_TRANSCODERS; (__t)++) \ + for_each_if (RUNTIME_INFO(__dev_priv)->cpu_transcoder_mask & BIT(__t)) + +#define for_each_cpu_transcoder_masked(__dev_priv, __t, __mask) \ + for_each_cpu_transcoder(__dev_priv, __t) \ + for_each_if ((__mask) & BIT(__t)) + +#define for_each_sprite(__dev_priv, __p, __s) \ + for ((__s) = 0; \ + (__s) < RUNTIME_INFO(__dev_priv)->num_sprites[(__p)]; \ + (__s)++) + +#define for_each_port(__port) \ + for ((__port) = PORT_A; (__port) < I915_MAX_PORTS; (__port)++) + +#define for_each_port_masked(__port, __ports_mask) \ + for_each_port(__port) \ + for_each_if((__ports_mask) & BIT(__port)) + +#define for_each_phy_masked(__phy, __phys_mask) \ + for ((__phy) = PHY_A; (__phy) < I915_MAX_PHYS; (__phy)++) \ + for_each_if((__phys_mask) & BIT(__phy)) + +#define for_each_crtc(dev, crtc) \ + list_for_each_entry(crtc, &(dev)->mode_config.crtc_list, head) + +#define for_each_intel_plane(dev, intel_plane) \ + list_for_each_entry(intel_plane, \ + &(dev)->mode_config.plane_list, \ + base.head) + +#define for_each_intel_plane_mask(dev, intel_plane, plane_mask) \ + list_for_each_entry(intel_plane, \ + &(dev)->mode_config.plane_list, \ + base.head) \ + for_each_if((plane_mask) & \ + drm_plane_mask(&intel_plane->base)) + +#define for_each_intel_plane_on_crtc(dev, intel_crtc, intel_plane) \ + list_for_each_entry(intel_plane, \ + &(dev)->mode_config.plane_list, \ + base.head) \ + for_each_if((intel_plane)->pipe == (intel_crtc)->pipe) + +#define for_each_intel_crtc(dev, intel_crtc) \ + list_for_each_entry(intel_crtc, \ + &(dev)->mode_config.crtc_list, \ + base.head) + +#define for_each_intel_crtc_in_pipe_mask(dev, intel_crtc, pipe_mask) \ + list_for_each_entry(intel_crtc, \ + &(dev)->mode_config.crtc_list, \ + base.head) \ + for_each_if((pipe_mask) & BIT(intel_crtc->pipe)) + +#define for_each_intel_encoder(dev, intel_encoder) \ + list_for_each_entry(intel_encoder, \ + &(dev)->mode_config.encoder_list, \ + base.head) + +#define for_each_intel_encoder_mask(dev, intel_encoder, encoder_mask) \ + list_for_each_entry(intel_encoder, \ + &(dev)->mode_config.encoder_list, \ + base.head) \ + for_each_if((encoder_mask) & \ + drm_encoder_mask(&intel_encoder->base)) + +#define for_each_intel_encoder_mask_with_psr(dev, intel_encoder, encoder_mask) \ + list_for_each_entry((intel_encoder), &(dev)->mode_config.encoder_list, base.head) \ + for_each_if(((encoder_mask) & drm_encoder_mask(&(intel_encoder)->base)) && \ + intel_encoder_can_psr(intel_encoder)) + +#define for_each_intel_dp(dev, intel_encoder) \ + for_each_intel_encoder(dev, intel_encoder) \ + for_each_if(intel_encoder_is_dp(intel_encoder)) + +#define for_each_intel_encoder_with_psr(dev, intel_encoder) \ + for_each_intel_encoder((dev), (intel_encoder)) \ + for_each_if(intel_encoder_can_psr(intel_encoder)) + +#define for_each_intel_connector_iter(intel_connector, iter) \ + while ((intel_connector = to_intel_connector(drm_connector_list_iter_next(iter)))) + +#define for_each_encoder_on_crtc(dev, __crtc, intel_encoder) \ + list_for_each_entry((intel_encoder), &(dev)->mode_config.encoder_list, base.head) \ + for_each_if((intel_encoder)->base.crtc == (__crtc)) + +#define for_each_connector_on_encoder(dev, __encoder, intel_connector) \ + list_for_each_entry((intel_connector), &(dev)->mode_config.connector_list, base.head) \ + for_each_if((intel_connector)->base.encoder == (__encoder)) + +#define for_each_old_intel_plane_in_state(__state, plane, old_plane_state, __i) \ + for ((__i) = 0; \ + (__i) < (__state)->base.dev->mode_config.num_total_plane && \ + ((plane) = to_intel_plane((__state)->base.planes[__i].ptr), \ + (old_plane_state) = to_intel_plane_state((__state)->base.planes[__i].old_state), 1); \ + (__i)++) \ + for_each_if(plane) + +#define for_each_new_intel_plane_in_state(__state, plane, new_plane_state, __i) \ + for ((__i) = 0; \ + (__i) < (__state)->base.dev->mode_config.num_total_plane && \ + ((plane) = to_intel_plane((__state)->base.planes[__i].ptr), \ + (new_plane_state) = to_intel_plane_state((__state)->base.planes[__i].new_state), 1); \ + (__i)++) \ + for_each_if(plane) + +#define for_each_new_intel_crtc_in_state(__state, crtc, new_crtc_state, __i) \ + for ((__i) = 0; \ + (__i) < (__state)->base.dev->mode_config.num_crtc && \ + ((crtc) = to_intel_crtc((__state)->base.crtcs[__i].ptr), \ + (new_crtc_state) = to_intel_crtc_state((__state)->base.crtcs[__i].new_state), 1); \ + (__i)++) \ + for_each_if(crtc) + +#define for_each_oldnew_intel_plane_in_state(__state, plane, old_plane_state, new_plane_state, __i) \ + for ((__i) = 0; \ + (__i) < (__state)->base.dev->mode_config.num_total_plane && \ + ((plane) = to_intel_plane((__state)->base.planes[__i].ptr), \ + (old_plane_state) = to_intel_plane_state((__state)->base.planes[__i].old_state), \ + (new_plane_state) = to_intel_plane_state((__state)->base.planes[__i].new_state), 1); \ + (__i)++) \ + for_each_if(plane) + +#define for_each_oldnew_intel_crtc_in_state(__state, crtc, old_crtc_state, new_crtc_state, __i) \ + for ((__i) = 0; \ + (__i) < (__state)->base.dev->mode_config.num_crtc && \ + ((crtc) = to_intel_crtc((__state)->base.crtcs[__i].ptr), \ + (old_crtc_state) = to_intel_crtc_state((__state)->base.crtcs[__i].old_state), \ + (new_crtc_state) = to_intel_crtc_state((__state)->base.crtcs[__i].new_state), 1); \ + (__i)++) \ + for_each_if(crtc) + +#define for_each_oldnew_intel_crtc_in_state_reverse(__state, crtc, old_crtc_state, new_crtc_state, __i) \ + for ((__i) = (__state)->base.dev->mode_config.num_crtc - 1; \ + (__i) >= 0 && \ + ((crtc) = to_intel_crtc((__state)->base.crtcs[__i].ptr), \ + (old_crtc_state) = to_intel_crtc_state((__state)->base.crtcs[__i].old_state), \ + (new_crtc_state) = to_intel_crtc_state((__state)->base.crtcs[__i].new_state), 1); \ + (__i)--) \ + for_each_if(crtc) + +#define intel_atomic_crtc_state_for_each_plane_state( \ + plane, plane_state, \ + crtc_state) \ + for_each_intel_plane_mask(((crtc_state)->uapi.state->dev), (plane), \ + ((crtc_state)->uapi.plane_mask)) \ + for_each_if ((plane_state = \ + to_intel_plane_state(__drm_atomic_get_current_plane_state((crtc_state)->uapi.state, &plane->base)))) + +#define for_each_new_intel_connector_in_state(__state, connector, new_connector_state, __i) \ + for ((__i) = 0; \ + (__i) < (__state)->base.num_connector; \ + (__i)++) \ + for_each_if ((__state)->base.connectors[__i].ptr && \ + ((connector) = to_intel_connector((__state)->base.connectors[__i].ptr), \ + (new_connector_state) = to_intel_digital_connector_state((__state)->base.connectors[__i].new_state), 1)) + +int intel_atomic_add_affected_planes(struct intel_atomic_state *state, + struct intel_crtc *crtc); +u8 intel_calc_active_pipes(struct intel_atomic_state *state, + u8 active_pipes); +void intel_link_compute_m_n(u16 bpp, int nlanes, + int pixel_clock, int link_clock, + struct intel_link_m_n *m_n, + bool fec_enable); +u32 intel_plane_fb_max_stride(struct drm_i915_private *dev_priv, + u32 pixel_format, u64 modifier); +enum drm_mode_status +intel_mode_valid_max_plane_size(struct drm_i915_private *dev_priv, + const struct drm_display_mode *mode, + bool bigjoiner); +enum drm_mode_status +intel_cpu_transcoder_mode_valid(struct drm_i915_private *i915, + const struct drm_display_mode *mode); +enum phy intel_port_to_phy(struct drm_i915_private *i915, enum port port); +bool is_trans_port_sync_mode(const struct intel_crtc_state *state); +bool intel_crtc_is_bigjoiner_slave(const struct intel_crtc_state *crtc_state); +bool intel_crtc_is_bigjoiner_master(const struct intel_crtc_state *crtc_state); +u8 intel_crtc_bigjoiner_slave_pipes(const struct intel_crtc_state *crtc_state); +struct intel_crtc *intel_master_crtc(const struct intel_crtc_state *crtc_state); +bool intel_crtc_get_pipe_config(struct intel_crtc_state *crtc_state); +bool intel_pipe_config_compare(const struct intel_crtc_state *current_config, + const struct intel_crtc_state *pipe_config, + bool fastset); +void intel_crtc_update_active_timings(const struct intel_crtc_state *crtc_state); + +void intel_plane_destroy(struct drm_plane *plane); +void i9xx_set_pipeconf(const struct intel_crtc_state *crtc_state); +void ilk_set_pipeconf(const struct intel_crtc_state *crtc_state); +void intel_enable_transcoder(const struct intel_crtc_state *new_crtc_state); +void intel_disable_transcoder(const struct intel_crtc_state *old_crtc_state); +void i830_enable_pipe(struct drm_i915_private *dev_priv, enum pipe pipe); +void i830_disable_pipe(struct drm_i915_private *dev_priv, enum pipe pipe); +int vlv_get_hpll_vco(struct drm_i915_private *dev_priv); +int vlv_get_cck_clock(struct drm_i915_private *dev_priv, + const char *name, u32 reg, int ref_freq); +int vlv_get_cck_clock_hpll(struct drm_i915_private *dev_priv, + const char *name, u32 reg); +void intel_init_display_hooks(struct drm_i915_private *dev_priv); +unsigned int intel_fb_xy_to_linear(int x, int y, + const struct intel_plane_state *state, + int plane); +void intel_add_fb_offsets(int *x, int *y, + const struct intel_plane_state *state, int plane); +unsigned int intel_rotation_info_size(const struct intel_rotation_info *rot_info); +unsigned int intel_remapped_info_size(const struct intel_remapped_info *rem_info); +bool intel_has_pending_fb_unpin(struct drm_i915_private *dev_priv); +int intel_display_suspend(struct drm_device *dev); +void intel_encoder_destroy(struct drm_encoder *encoder); +struct drm_display_mode * +intel_encoder_current_mode(struct intel_encoder *encoder); +void intel_encoder_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state); +bool intel_phy_is_combo(struct drm_i915_private *dev_priv, enum phy phy); +bool intel_phy_is_tc(struct drm_i915_private *dev_priv, enum phy phy); +bool intel_phy_is_snps(struct drm_i915_private *dev_priv, enum phy phy); +enum tc_port intel_port_to_tc(struct drm_i915_private *dev_priv, + enum port port); +int intel_get_pipe_from_crtc_id_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); + +int ilk_get_lanes_required(int target_clock, int link_bw, int bpp); +void vlv_wait_port_ready(struct drm_i915_private *dev_priv, + struct intel_digital_port *dig_port, + unsigned int expected_mask); +int intel_get_load_detect_pipe(struct drm_connector *connector, + struct intel_load_detect_pipe *old, + struct drm_modeset_acquire_ctx *ctx); +void intel_release_load_detect_pipe(struct drm_connector *connector, + struct intel_load_detect_pipe *old, + struct drm_modeset_acquire_ctx *ctx); +struct drm_framebuffer * +intel_framebuffer_create(struct drm_i915_gem_object *obj, + struct drm_mode_fb_cmd2 *mode_cmd); + +bool intel_fuzzy_clock_check(int clock1, int clock2); + +void intel_display_prepare_reset(struct drm_i915_private *dev_priv); +void intel_display_finish_reset(struct drm_i915_private *dev_priv); +void intel_zero_m_n(struct intel_link_m_n *m_n); +void intel_set_m_n(struct drm_i915_private *i915, + const struct intel_link_m_n *m_n, + i915_reg_t data_m_reg, i915_reg_t data_n_reg, + i915_reg_t link_m_reg, i915_reg_t link_n_reg); +void intel_get_m_n(struct drm_i915_private *i915, + struct intel_link_m_n *m_n, + i915_reg_t data_m_reg, i915_reg_t data_n_reg, + i915_reg_t link_m_reg, i915_reg_t link_n_reg); +bool intel_cpu_transcoder_has_m2_n2(struct drm_i915_private *dev_priv, + enum transcoder transcoder); +void intel_cpu_transcoder_set_m1_n1(struct intel_crtc *crtc, + enum transcoder cpu_transcoder, + const struct intel_link_m_n *m_n); +void intel_cpu_transcoder_set_m2_n2(struct intel_crtc *crtc, + enum transcoder cpu_transcoder, + const struct intel_link_m_n *m_n); +void intel_cpu_transcoder_get_m1_n1(struct intel_crtc *crtc, + enum transcoder cpu_transcoder, + struct intel_link_m_n *m_n); +void intel_cpu_transcoder_get_m2_n2(struct intel_crtc *crtc, + enum transcoder cpu_transcoder, + struct intel_link_m_n *m_n); +void i9xx_crtc_clock_get(struct intel_crtc *crtc, + struct intel_crtc_state *pipe_config); +int intel_dotclock_calculate(int link_freq, const struct intel_link_m_n *m_n); +int intel_crtc_dotclock(const struct intel_crtc_state *pipe_config); +enum intel_display_power_domain intel_port_to_power_domain(struct intel_digital_port *dig_port); +enum intel_display_power_domain +intel_aux_power_domain(struct intel_digital_port *dig_port); +void intel_crtc_arm_fifo_underrun(struct intel_crtc *crtc, + struct intel_crtc_state *crtc_state); +void ilk_pfit_disable(const struct intel_crtc_state *old_crtc_state); + +int bdw_get_pipemisc_bpp(struct intel_crtc *crtc); +unsigned int intel_plane_fence_y_offset(const struct intel_plane_state *plane_state); + +bool intel_plane_uses_fence(const struct intel_plane_state *plane_state); + +struct intel_encoder * +intel_get_crtc_new_encoder(const struct intel_atomic_state *state, + const struct intel_crtc_state *crtc_state); +void intel_plane_disable_noatomic(struct intel_crtc *crtc, + struct intel_plane *plane); +void intel_set_plane_visible(struct intel_crtc_state *crtc_state, + struct intel_plane_state *plane_state, + bool visible); +void intel_plane_fixup_bitmasks(struct intel_crtc_state *crtc_state); + +void intel_display_driver_register(struct drm_i915_private *i915); +void intel_display_driver_unregister(struct drm_i915_private *i915); + +void intel_update_watermarks(struct drm_i915_private *i915); + +/* modesetting */ +bool intel_modeset_probe_defer(struct pci_dev *pdev); +void intel_modeset_init_hw(struct drm_i915_private *i915); +int intel_modeset_init_noirq(struct drm_i915_private *i915); +int intel_modeset_init_nogem(struct drm_i915_private *i915); +int intel_modeset_init(struct drm_i915_private *i915); +void intel_modeset_driver_remove(struct drm_i915_private *i915); +void intel_modeset_driver_remove_noirq(struct drm_i915_private *i915); +void intel_modeset_driver_remove_nogem(struct drm_i915_private *i915); +void intel_display_resume(struct drm_device *dev); +int intel_modeset_all_pipes(struct intel_atomic_state *state); +void intel_modeset_get_crtc_power_domains(struct intel_crtc_state *crtc_state, + struct intel_power_domain_mask *old_domains); +void intel_modeset_put_crtc_power_domains(struct intel_crtc *crtc, + struct intel_power_domain_mask *domains); + +/* modesetting asserts */ +void assert_transcoder(struct drm_i915_private *dev_priv, + enum transcoder cpu_transcoder, bool state); +#define assert_transcoder_enabled(d, t) assert_transcoder(d, t, true) +#define assert_transcoder_disabled(d, t) assert_transcoder(d, t, false) + +/* Use I915_STATE_WARN(x) and I915_STATE_WARN_ON() (rather than WARN() and + * WARN_ON()) for hw state sanity checks to check for unexpected conditions + * which may not necessarily be a user visible problem. This will either + * WARN() or DRM_ERROR() depending on the verbose_checks moduleparam, to + * enable distros and users to tailor their preferred amount of i915 abrt + * spam. + */ +#define I915_STATE_WARN(condition, format...) ({ \ + int __ret_warn_on = !!(condition); \ + if (unlikely(__ret_warn_on)) \ + if (!WARN(i915_modparams.verbose_state_checks, format)) \ + DRM_ERROR(format); \ + unlikely(__ret_warn_on); \ +}) + +#define I915_STATE_WARN_ON(x) \ + I915_STATE_WARN((x), "%s", "WARN_ON(" __stringify(x) ")") + +bool intel_scanout_needs_vtd_wa(struct drm_i915_private *i915); + +#endif diff --git a/drivers/gpu/drm/i915/display/intel_display_core.h b/drivers/gpu/drm/i915/display/intel_display_core.h new file mode 100644 index 000000000..9b51148e8 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_display_core.h @@ -0,0 +1,426 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2022 Intel Corporation + */ + +#ifndef __INTEL_DISPLAY_CORE_H__ +#define __INTEL_DISPLAY_CORE_H__ + +#include +#include +#include +#include +#include +#include + +#include + +#include "intel_cdclk.h" +#include "intel_display.h" +#include "intel_display_power.h" +#include "intel_dmc.h" +#include "intel_dpll_mgr.h" +#include "intel_fbc.h" +#include "intel_global_state.h" +#include "intel_gmbus.h" +#include "intel_opregion.h" +#include "intel_pm_types.h" + +struct drm_i915_private; +struct drm_property; +struct i915_audio_component; +struct i915_hdcp_comp_master; +struct intel_atomic_state; +struct intel_audio_funcs; +struct intel_bios_encoder_data; +struct intel_cdclk_funcs; +struct intel_cdclk_vals; +struct intel_color_funcs; +struct intel_crtc; +struct intel_crtc_state; +struct intel_dpll_funcs; +struct intel_dpll_mgr; +struct intel_fbdev; +struct intel_fdi_funcs; +struct intel_hotplug_funcs; +struct intel_initial_plane_config; +struct intel_overlay; + +/* Amount of SAGV/QGV points, BSpec precisely defines this */ +#define I915_NUM_QGV_POINTS 8 + +/* Amount of PSF GV points, BSpec precisely defines this */ +#define I915_NUM_PSF_GV_POINTS 3 + +struct intel_display_funcs { + /* + * Returns the active state of the crtc, and if the crtc is active, + * fills out the pipe-config with the hw state. + */ + bool (*get_pipe_config)(struct intel_crtc *, + struct intel_crtc_state *); + void (*get_initial_plane_config)(struct intel_crtc *, + struct intel_initial_plane_config *); + void (*crtc_enable)(struct intel_atomic_state *state, + struct intel_crtc *crtc); + void (*crtc_disable)(struct intel_atomic_state *state, + struct intel_crtc *crtc); + void (*commit_modeset_enables)(struct intel_atomic_state *state); +}; + +/* functions used for watermark calcs for display. */ +struct intel_wm_funcs { + /* update_wm is for legacy wm management */ + void (*update_wm)(struct drm_i915_private *dev_priv); + int (*compute_pipe_wm)(struct intel_atomic_state *state, + struct intel_crtc *crtc); + int (*compute_intermediate_wm)(struct intel_atomic_state *state, + struct intel_crtc *crtc); + void (*initial_watermarks)(struct intel_atomic_state *state, + struct intel_crtc *crtc); + void (*atomic_update_watermarks)(struct intel_atomic_state *state, + struct intel_crtc *crtc); + void (*optimize_watermarks)(struct intel_atomic_state *state, + struct intel_crtc *crtc); + int (*compute_global_watermarks)(struct intel_atomic_state *state); +}; + +struct intel_audio { + /* hda/i915 audio component */ + struct i915_audio_component *component; + bool component_registered; + /* mutex for audio/video sync */ + struct mutex mutex; + int power_refcount; + u32 freq_cntrl; + + /* Used to save the pipe-to-encoder mapping for audio */ + struct intel_encoder *encoder_map[I915_MAX_PIPES]; + + /* necessary resource sharing with HDMI LPE audio driver. */ + struct { + struct platform_device *platdev; + int irq; + } lpe; +}; + +/* + * dpll and cdclk state is protected by connection_mutex dpll.lock serializes + * intel_{prepare,enable,disable}_shared_dpll. Must be global rather than per + * dpll, because on some platforms plls share registers. + */ +struct intel_dpll { + struct mutex lock; + + int num_shared_dpll; + struct intel_shared_dpll shared_dplls[I915_NUM_PLLS]; + const struct intel_dpll_mgr *mgr; + + struct { + int nssc; + int ssc; + } ref_clks; +}; + +struct intel_frontbuffer_tracking { + spinlock_t lock; + + /* + * Tracking bits for delayed frontbuffer flushing du to gpu activity or + * scheduled flips. + */ + unsigned busy_bits; + unsigned flip_bits; +}; + +struct intel_hotplug { + struct delayed_work hotplug_work; + + const u32 *hpd, *pch_hpd; + + struct { + unsigned long last_jiffies; + int count; + enum { + HPD_ENABLED = 0, + HPD_DISABLED = 1, + HPD_MARK_DISABLED = 2 + } state; + } stats[HPD_NUM_PINS]; + u32 event_bits; + u32 retry_bits; + struct delayed_work reenable_work; + + u32 long_port_mask; + u32 short_port_mask; + struct work_struct dig_port_work; + + struct work_struct poll_init_work; + bool poll_enabled; + + unsigned int hpd_storm_threshold; + /* Whether or not to count short HPD IRQs in HPD storms */ + u8 hpd_short_storm_enabled; + + /* + * if we get a HPD irq from DP and a HPD irq from non-DP + * the non-DP HPD could block the workqueue on a mode config + * mutex getting, that userspace may have taken. However + * userspace is waiting on the DP workqueue to run which is + * blocked behind the non-DP one. + */ + struct workqueue_struct *dp_wq; +}; + +struct intel_vbt_data { + /* bdb version */ + u16 version; + + /* Feature bits */ + unsigned int int_tv_support:1; + unsigned int int_crt_support:1; + unsigned int lvds_use_ssc:1; + unsigned int int_lvds_support:1; + unsigned int display_clock_mode:1; + unsigned int fdi_rx_polarity_inverted:1; + int lvds_ssc_freq; + enum drm_panel_orientation orientation; + + bool override_afc_startup; + u8 override_afc_startup_val; + + int crt_ddc_pin; + + struct list_head display_devices; + struct list_head bdb_blocks; + + struct intel_bios_encoder_data *ports[I915_MAX_PORTS]; /* Non-NULL if port present. */ + struct sdvo_device_mapping { + u8 initialized; + u8 dvo_port; + u8 slave_addr; + u8 dvo_wiring; + u8 i2c_pin; + u8 ddc_pin; + } sdvo_mappings[2]; +}; + +struct intel_wm { + /* + * Raw watermark latency values: + * in 0.1us units for WM0, + * in 0.5us units for WM1+. + */ + /* primary */ + u16 pri_latency[5]; + /* sprite */ + u16 spr_latency[5]; + /* cursor */ + u16 cur_latency[5]; + /* + * Raw watermark memory latency values + * for SKL for all 8 levels + * in 1us units. + */ + u16 skl_latency[8]; + + /* current hardware state */ + union { + struct ilk_wm_values hw; + struct vlv_wm_values vlv; + struct g4x_wm_values g4x; + }; + + u8 max_level; + + /* + * Should be held around atomic WM register writing; also + * protects * intel_crtc->wm.active and + * crtc_state->wm.need_postvbl_update. + */ + struct mutex wm_mutex; + + bool ipc_enabled; +}; + +struct intel_display { + /* Display functions */ + struct { + /* Top level crtc-ish functions */ + const struct intel_display_funcs *display; + + /* Display CDCLK functions */ + const struct intel_cdclk_funcs *cdclk; + + /* Display pll funcs */ + const struct intel_dpll_funcs *dpll; + + /* irq display functions */ + const struct intel_hotplug_funcs *hotplug; + + /* pm display functions */ + const struct intel_wm_funcs *wm; + + /* fdi display functions */ + const struct intel_fdi_funcs *fdi; + + /* Display internal color functions */ + const struct intel_color_funcs *color; + + /* Display internal audio functions */ + const struct intel_audio_funcs *audio; + } funcs; + + /* Grouping using anonymous structs. Keep sorted. */ + struct intel_atomic_helper { + struct llist_head free_list; + struct work_struct free_work; + } atomic_helper; + + struct { + /* backlight registers and fields in struct intel_panel */ + struct mutex lock; + } backlight; + + struct { + struct intel_global_obj obj; + + struct intel_bw_info { + /* for each QGV point */ + unsigned int deratedbw[I915_NUM_QGV_POINTS]; + /* for each PSF GV point */ + unsigned int psf_bw[I915_NUM_PSF_GV_POINTS]; + u8 num_qgv_points; + u8 num_psf_gv_points; + u8 num_planes; + } max[6]; + } bw; + + struct { + /* The current hardware cdclk configuration */ + struct intel_cdclk_config hw; + + /* cdclk, divider, and ratio table from bspec */ + const struct intel_cdclk_vals *table; + + struct intel_global_obj obj; + + unsigned int max_cdclk_freq; + } cdclk; + + struct { + /* The current hardware dbuf configuration */ + u8 enabled_slices; + + struct intel_global_obj obj; + } dbuf; + + struct { + /* + * dkl.phy_lock protects against concurrent access of the + * Dekel TypeC PHYs. + */ + spinlock_t phy_lock; + } dkl; + + struct { + /* VLV/CHV/BXT/GLK DSI MMIO register base address */ + u32 mmio_base; + } dsi; + + struct { + /* list of fbdev register on this device */ + struct intel_fbdev *fbdev; + struct work_struct suspend_work; + } fbdev; + + struct { + unsigned int pll_freq; + u32 rx_config; + } fdi; + + struct { + /* + * Base address of where the gmbus and gpio blocks are located + * (either on PCH or on SoC for platforms without PCH). + */ + u32 mmio_base; + + /* + * gmbus.mutex protects against concurrent usage of the single + * hw gmbus controller on different i2c buses. + */ + struct mutex mutex; + + struct intel_gmbus *bus[GMBUS_NUM_PINS]; + + wait_queue_head_t wait_queue; + } gmbus; + + struct { + struct i915_hdcp_comp_master *master; + bool comp_added; + + /* Mutex to protect the above hdcp component related values. */ + struct mutex comp_mutex; + } hdcp; + + struct { + struct i915_power_domains domains; + + /* Shadow for DISPLAY_PHY_CONTROL which can't be safely read */ + u32 chv_phy_control; + + /* perform PHY state sanity checks? */ + bool chv_phy_assert[2]; + } power; + + struct { + u32 mmio_base; + + /* protects panel power sequencer state */ + struct mutex mutex; + } pps; + + struct { + struct drm_property *broadcast_rgb; + struct drm_property *force_audio; + } properties; + + struct { + unsigned long mask; + } quirks; + + struct { + enum { + I915_SAGV_UNKNOWN = 0, + I915_SAGV_DISABLED, + I915_SAGV_ENABLED, + I915_SAGV_NOT_CONTROLLED + } status; + + u32 block_time_us; + } sagv; + + struct { + /* ordered wq for modesets */ + struct workqueue_struct *modeset; + + /* unbound hipri wq for page flips/plane updates */ + struct workqueue_struct *flip; + } wq; + + /* Grouping using named structs. Keep sorted. */ + struct intel_audio audio; + struct intel_dmc dmc; + struct intel_dpll dpll; + struct intel_fbc *fbc[I915_MAX_FBCS]; + struct intel_frontbuffer_tracking fb_tracking; + struct intel_hotplug hotplug; + struct intel_opregion opregion; + struct intel_overlay *overlay; + struct intel_vbt_data vbt; + struct intel_wm wm; +}; + +#endif /* __INTEL_DISPLAY_CORE_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_display_debugfs.c b/drivers/gpu/drm/i915/display/intel_display_debugfs.c new file mode 100644 index 000000000..7c7253a25 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_display_debugfs.c @@ -0,0 +1,2254 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2020 Intel Corporation + */ + +#include + +#include +#include + +#include "i915_debugfs.h" +#include "intel_de.h" +#include "intel_display_debugfs.h" +#include "intel_display_power.h" +#include "intel_display_power_well.h" +#include "intel_display_types.h" +#include "intel_dmc.h" +#include "intel_dp.h" +#include "intel_dp_mst.h" +#include "intel_drrs.h" +#include "intel_fbc.h" +#include "intel_fbdev.h" +#include "intel_hdcp.h" +#include "intel_hdmi.h" +#include "intel_panel.h" +#include "intel_pm.h" +#include "intel_psr.h" +#include "intel_sprite.h" +#include "skl_watermark.h" + +static inline struct drm_i915_private *node_to_i915(struct drm_info_node *node) +{ + return to_i915(node->minor->dev); +} + +static int i915_frontbuffer_tracking(struct seq_file *m, void *unused) +{ + struct drm_i915_private *dev_priv = node_to_i915(m->private); + + seq_printf(m, "FB tracking busy bits: 0x%08x\n", + dev_priv->display.fb_tracking.busy_bits); + + seq_printf(m, "FB tracking flip bits: 0x%08x\n", + dev_priv->display.fb_tracking.flip_bits); + + return 0; +} + +static int i915_ips_status(struct seq_file *m, void *unused) +{ + struct drm_i915_private *dev_priv = node_to_i915(m->private); + intel_wakeref_t wakeref; + + if (!HAS_IPS(dev_priv)) + return -ENODEV; + + wakeref = intel_runtime_pm_get(&dev_priv->runtime_pm); + + seq_printf(m, "Enabled by kernel parameter: %s\n", + str_yes_no(dev_priv->params.enable_ips)); + + if (DISPLAY_VER(dev_priv) >= 8) { + seq_puts(m, "Currently: unknown\n"); + } else { + if (intel_de_read(dev_priv, IPS_CTL) & IPS_ENABLE) + seq_puts(m, "Currently: enabled\n"); + else + seq_puts(m, "Currently: disabled\n"); + } + + intel_runtime_pm_put(&dev_priv->runtime_pm, wakeref); + + return 0; +} + +static int i915_sr_status(struct seq_file *m, void *unused) +{ + struct drm_i915_private *dev_priv = node_to_i915(m->private); + intel_wakeref_t wakeref; + bool sr_enabled = false; + + wakeref = intel_display_power_get(dev_priv, POWER_DOMAIN_INIT); + + if (DISPLAY_VER(dev_priv) >= 9) + /* no global SR status; inspect per-plane WM */; + else if (HAS_PCH_SPLIT(dev_priv)) + sr_enabled = intel_de_read(dev_priv, WM1_LP_ILK) & WM_LP_ENABLE; + else if (IS_I965GM(dev_priv) || IS_G4X(dev_priv) || + IS_I945G(dev_priv) || IS_I945GM(dev_priv)) + sr_enabled = intel_de_read(dev_priv, FW_BLC_SELF) & FW_BLC_SELF_EN; + else if (IS_I915GM(dev_priv)) + sr_enabled = intel_de_read(dev_priv, INSTPM) & INSTPM_SELF_EN; + else if (IS_PINEVIEW(dev_priv)) + sr_enabled = intel_de_read(dev_priv, DSPFW3) & PINEVIEW_SELF_REFRESH_EN; + else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) + sr_enabled = intel_de_read(dev_priv, FW_BLC_SELF_VLV) & FW_CSPWRDWNEN; + + intel_display_power_put(dev_priv, POWER_DOMAIN_INIT, wakeref); + + seq_printf(m, "self-refresh: %s\n", str_enabled_disabled(sr_enabled)); + + return 0; +} + +static int i915_opregion(struct seq_file *m, void *unused) +{ + struct drm_i915_private *i915 = node_to_i915(m->private); + struct intel_opregion *opregion = &i915->display.opregion; + + if (opregion->header) + seq_write(m, opregion->header, OPREGION_SIZE); + + return 0; +} + +static int i915_vbt(struct seq_file *m, void *unused) +{ + struct drm_i915_private *i915 = node_to_i915(m->private); + struct intel_opregion *opregion = &i915->display.opregion; + + if (opregion->vbt) + seq_write(m, opregion->vbt, opregion->vbt_size); + + return 0; +} + +static int i915_gem_framebuffer_info(struct seq_file *m, void *data) +{ + struct drm_i915_private *dev_priv = node_to_i915(m->private); + struct drm_device *dev = &dev_priv->drm; + struct intel_framebuffer *fbdev_fb = NULL; + struct drm_framebuffer *drm_fb; + +#ifdef CONFIG_DRM_FBDEV_EMULATION + fbdev_fb = intel_fbdev_framebuffer(dev_priv->display.fbdev.fbdev); + if (fbdev_fb) { + seq_printf(m, "fbcon size: %d x %d, depth %d, %d bpp, modifier 0x%llx, refcount %d, obj ", + fbdev_fb->base.width, + fbdev_fb->base.height, + fbdev_fb->base.format->depth, + fbdev_fb->base.format->cpp[0] * 8, + fbdev_fb->base.modifier, + drm_framebuffer_read_refcount(&fbdev_fb->base)); + i915_debugfs_describe_obj(m, intel_fb_obj(&fbdev_fb->base)); + seq_putc(m, '\n'); + } +#endif + + mutex_lock(&dev->mode_config.fb_lock); + drm_for_each_fb(drm_fb, dev) { + struct intel_framebuffer *fb = to_intel_framebuffer(drm_fb); + if (fb == fbdev_fb) + continue; + + seq_printf(m, "user size: %d x %d, depth %d, %d bpp, modifier 0x%llx, refcount %d, obj ", + fb->base.width, + fb->base.height, + fb->base.format->depth, + fb->base.format->cpp[0] * 8, + fb->base.modifier, + drm_framebuffer_read_refcount(&fb->base)); + i915_debugfs_describe_obj(m, intel_fb_obj(&fb->base)); + seq_putc(m, '\n'); + } + mutex_unlock(&dev->mode_config.fb_lock); + + return 0; +} + +static int i915_psr_sink_status_show(struct seq_file *m, void *data) +{ + u8 val; + static const char * const sink_status[] = { + "inactive", + "transition to active, capture and display", + "active, display from RFB", + "active, capture and display on sink device timings", + "transition to inactive, capture and display, timing re-sync", + "reserved", + "reserved", + "sink internal error", + }; + struct drm_connector *connector = m->private; + struct intel_dp *intel_dp = + intel_attached_dp(to_intel_connector(connector)); + int ret; + + if (!CAN_PSR(intel_dp)) { + seq_puts(m, "PSR Unsupported\n"); + return -ENODEV; + } + + if (connector->status != connector_status_connected) + return -ENODEV; + + ret = drm_dp_dpcd_readb(&intel_dp->aux, DP_PSR_STATUS, &val); + + if (ret == 1) { + const char *str = "unknown"; + + val &= DP_PSR_SINK_STATE_MASK; + if (val < ARRAY_SIZE(sink_status)) + str = sink_status[val]; + seq_printf(m, "Sink PSR status: 0x%x [%s]\n", val, str); + } else { + return ret; + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(i915_psr_sink_status); + +static void +psr_source_status(struct intel_dp *intel_dp, struct seq_file *m) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + const char *status = "unknown"; + u32 val, status_val; + + if (intel_dp->psr.psr2_enabled) { + static const char * const live_status[] = { + "IDLE", + "CAPTURE", + "CAPTURE_FS", + "SLEEP", + "BUFON_FW", + "ML_UP", + "SU_STANDBY", + "FAST_SLEEP", + "DEEP_SLEEP", + "BUF_ON", + "TG_ON" + }; + val = intel_de_read(dev_priv, + EDP_PSR2_STATUS(intel_dp->psr.transcoder)); + status_val = REG_FIELD_GET(EDP_PSR2_STATUS_STATE_MASK, val); + if (status_val < ARRAY_SIZE(live_status)) + status = live_status[status_val]; + } else { + static const char * const live_status[] = { + "IDLE", + "SRDONACK", + "SRDENT", + "BUFOFF", + "BUFON", + "AUXACK", + "SRDOFFACK", + "SRDENT_ON", + }; + val = intel_de_read(dev_priv, + EDP_PSR_STATUS(intel_dp->psr.transcoder)); + status_val = (val & EDP_PSR_STATUS_STATE_MASK) >> + EDP_PSR_STATUS_STATE_SHIFT; + if (status_val < ARRAY_SIZE(live_status)) + status = live_status[status_val]; + } + + seq_printf(m, "Source PSR status: %s [0x%08x]\n", status, val); +} + +static int intel_psr_status(struct seq_file *m, struct intel_dp *intel_dp) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + struct intel_psr *psr = &intel_dp->psr; + intel_wakeref_t wakeref; + const char *status; + bool enabled; + u32 val; + + seq_printf(m, "Sink support: %s", str_yes_no(psr->sink_support)); + if (psr->sink_support) + seq_printf(m, " [0x%02x]", intel_dp->psr_dpcd[0]); + seq_puts(m, "\n"); + + if (!psr->sink_support) + return 0; + + wakeref = intel_runtime_pm_get(&dev_priv->runtime_pm); + mutex_lock(&psr->lock); + + if (psr->enabled) + status = psr->psr2_enabled ? "PSR2 enabled" : "PSR1 enabled"; + else + status = "disabled"; + seq_printf(m, "PSR mode: %s\n", status); + + if (!psr->enabled) { + seq_printf(m, "PSR sink not reliable: %s\n", + str_yes_no(psr->sink_not_reliable)); + + goto unlock; + } + + if (psr->psr2_enabled) { + val = intel_de_read(dev_priv, + EDP_PSR2_CTL(intel_dp->psr.transcoder)); + enabled = val & EDP_PSR2_ENABLE; + } else { + val = intel_de_read(dev_priv, + EDP_PSR_CTL(intel_dp->psr.transcoder)); + enabled = val & EDP_PSR_ENABLE; + } + seq_printf(m, "Source PSR ctl: %s [0x%08x]\n", + str_enabled_disabled(enabled), val); + psr_source_status(intel_dp, m); + seq_printf(m, "Busy frontbuffer bits: 0x%08x\n", + psr->busy_frontbuffer_bits); + + /* + * SKL+ Perf counter is reset to 0 everytime DC state is entered + */ + if (IS_HASWELL(dev_priv) || IS_BROADWELL(dev_priv)) { + val = intel_de_read(dev_priv, + EDP_PSR_PERF_CNT(intel_dp->psr.transcoder)); + val &= EDP_PSR_PERF_CNT_MASK; + seq_printf(m, "Performance counter: %u\n", val); + } + + if (psr->debug & I915_PSR_DEBUG_IRQ) { + seq_printf(m, "Last attempted entry at: %lld\n", + psr->last_entry_attempt); + seq_printf(m, "Last exit at: %lld\n", psr->last_exit); + } + + if (psr->psr2_enabled) { + u32 su_frames_val[3]; + int frame; + + /* + * Reading all 3 registers before hand to minimize crossing a + * frame boundary between register reads + */ + for (frame = 0; frame < PSR2_SU_STATUS_FRAMES; frame += 3) { + val = intel_de_read(dev_priv, + PSR2_SU_STATUS(intel_dp->psr.transcoder, frame)); + su_frames_val[frame / 3] = val; + } + + seq_puts(m, "Frame:\tPSR2 SU blocks:\n"); + + for (frame = 0; frame < PSR2_SU_STATUS_FRAMES; frame++) { + u32 su_blocks; + + su_blocks = su_frames_val[frame / 3] & + PSR2_SU_STATUS_MASK(frame); + su_blocks = su_blocks >> PSR2_SU_STATUS_SHIFT(frame); + seq_printf(m, "%d\t%d\n", frame, su_blocks); + } + + seq_printf(m, "PSR2 selective fetch: %s\n", + str_enabled_disabled(psr->psr2_sel_fetch_enabled)); + } + +unlock: + mutex_unlock(&psr->lock); + intel_runtime_pm_put(&dev_priv->runtime_pm, wakeref); + + return 0; +} + +static int i915_edp_psr_status(struct seq_file *m, void *data) +{ + struct drm_i915_private *dev_priv = node_to_i915(m->private); + struct intel_dp *intel_dp = NULL; + struct intel_encoder *encoder; + + if (!HAS_PSR(dev_priv)) + return -ENODEV; + + /* Find the first EDP which supports PSR */ + for_each_intel_encoder_with_psr(&dev_priv->drm, encoder) { + intel_dp = enc_to_intel_dp(encoder); + break; + } + + if (!intel_dp) + return -ENODEV; + + return intel_psr_status(m, intel_dp); +} + +static int +i915_edp_psr_debug_set(void *data, u64 val) +{ + struct drm_i915_private *dev_priv = data; + struct intel_encoder *encoder; + intel_wakeref_t wakeref; + int ret = -ENODEV; + + if (!HAS_PSR(dev_priv)) + return ret; + + for_each_intel_encoder_with_psr(&dev_priv->drm, encoder) { + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + + drm_dbg_kms(&dev_priv->drm, "Setting PSR debug to %llx\n", val); + + wakeref = intel_runtime_pm_get(&dev_priv->runtime_pm); + + // TODO: split to each transcoder's PSR debug state + ret = intel_psr_debug_set(intel_dp, val); + + intel_runtime_pm_put(&dev_priv->runtime_pm, wakeref); + } + + return ret; +} + +static int +i915_edp_psr_debug_get(void *data, u64 *val) +{ + struct drm_i915_private *dev_priv = data; + struct intel_encoder *encoder; + + if (!HAS_PSR(dev_priv)) + return -ENODEV; + + for_each_intel_encoder_with_psr(&dev_priv->drm, encoder) { + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + + // TODO: split to each transcoder's PSR debug state + *val = READ_ONCE(intel_dp->psr.debug); + return 0; + } + + return -ENODEV; +} + +DEFINE_SIMPLE_ATTRIBUTE(i915_edp_psr_debug_fops, + i915_edp_psr_debug_get, i915_edp_psr_debug_set, + "%llu\n"); + +static int i915_power_domain_info(struct seq_file *m, void *unused) +{ + struct drm_i915_private *i915 = node_to_i915(m->private); + + intel_display_power_debug(i915, m); + + return 0; +} + +static void intel_seq_print_mode(struct seq_file *m, int tabs, + const struct drm_display_mode *mode) +{ + int i; + + for (i = 0; i < tabs; i++) + seq_putc(m, '\t'); + + seq_printf(m, DRM_MODE_FMT "\n", DRM_MODE_ARG(mode)); +} + +static void intel_encoder_info(struct seq_file *m, + struct intel_crtc *crtc, + struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = node_to_i915(m->private); + struct drm_connector_list_iter conn_iter; + struct drm_connector *connector; + + seq_printf(m, "\t[ENCODER:%d:%s]: connectors:\n", + encoder->base.base.id, encoder->base.name); + + drm_connector_list_iter_begin(&dev_priv->drm, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + const struct drm_connector_state *conn_state = + connector->state; + + if (conn_state->best_encoder != &encoder->base) + continue; + + seq_printf(m, "\t\t[CONNECTOR:%d:%s]\n", + connector->base.id, connector->name); + } + drm_connector_list_iter_end(&conn_iter); +} + +static void intel_panel_info(struct seq_file *m, + struct intel_connector *connector) +{ + const struct drm_display_mode *fixed_mode; + + if (list_empty(&connector->panel.fixed_modes)) + return; + + seq_puts(m, "\tfixed modes:\n"); + + list_for_each_entry(fixed_mode, &connector->panel.fixed_modes, head) + intel_seq_print_mode(m, 2, fixed_mode); +} + +static void intel_hdcp_info(struct seq_file *m, + struct intel_connector *intel_connector) +{ + bool hdcp_cap, hdcp2_cap; + + if (!intel_connector->hdcp.shim) { + seq_puts(m, "No Connector Support"); + goto out; + } + + hdcp_cap = intel_hdcp_capable(intel_connector); + hdcp2_cap = intel_hdcp2_capable(intel_connector); + + if (hdcp_cap) + seq_puts(m, "HDCP1.4 "); + if (hdcp2_cap) + seq_puts(m, "HDCP2.2 "); + + if (!hdcp_cap && !hdcp2_cap) + seq_puts(m, "None"); + +out: + seq_puts(m, "\n"); +} + +static void intel_dp_info(struct seq_file *m, + struct intel_connector *intel_connector) +{ + struct intel_encoder *intel_encoder = intel_attached_encoder(intel_connector); + struct intel_dp *intel_dp = enc_to_intel_dp(intel_encoder); + const struct drm_property_blob *edid = intel_connector->base.edid_blob_ptr; + + seq_printf(m, "\tDPCD rev: %x\n", intel_dp->dpcd[DP_DPCD_REV]); + seq_printf(m, "\taudio support: %s\n", + str_yes_no(intel_dp->has_audio)); + + drm_dp_downstream_debug(m, intel_dp->dpcd, intel_dp->downstream_ports, + edid ? edid->data : NULL, &intel_dp->aux); +} + +static void intel_dp_mst_info(struct seq_file *m, + struct intel_connector *intel_connector) +{ + bool has_audio = intel_connector->port->has_audio; + + seq_printf(m, "\taudio support: %s\n", str_yes_no(has_audio)); +} + +static void intel_hdmi_info(struct seq_file *m, + struct intel_connector *intel_connector) +{ + struct intel_encoder *intel_encoder = intel_attached_encoder(intel_connector); + struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(intel_encoder); + + seq_printf(m, "\taudio support: %s\n", + str_yes_no(intel_hdmi->has_audio)); +} + +static void intel_connector_info(struct seq_file *m, + struct drm_connector *connector) +{ + struct intel_connector *intel_connector = to_intel_connector(connector); + const struct drm_connector_state *conn_state = connector->state; + struct intel_encoder *encoder = + to_intel_encoder(conn_state->best_encoder); + const struct drm_display_mode *mode; + + seq_printf(m, "[CONNECTOR:%d:%s]: status: %s\n", + connector->base.id, connector->name, + drm_get_connector_status_name(connector->status)); + + if (connector->status == connector_status_disconnected) + return; + + seq_printf(m, "\tphysical dimensions: %dx%dmm\n", + connector->display_info.width_mm, + connector->display_info.height_mm); + seq_printf(m, "\tsubpixel order: %s\n", + drm_get_subpixel_order_name(connector->display_info.subpixel_order)); + seq_printf(m, "\tCEA rev: %d\n", connector->display_info.cea_rev); + + if (!encoder) + return; + + switch (connector->connector_type) { + case DRM_MODE_CONNECTOR_DisplayPort: + case DRM_MODE_CONNECTOR_eDP: + if (encoder->type == INTEL_OUTPUT_DP_MST) + intel_dp_mst_info(m, intel_connector); + else + intel_dp_info(m, intel_connector); + break; + case DRM_MODE_CONNECTOR_HDMIA: + if (encoder->type == INTEL_OUTPUT_HDMI || + encoder->type == INTEL_OUTPUT_DDI) + intel_hdmi_info(m, intel_connector); + break; + default: + break; + } + + seq_puts(m, "\tHDCP version: "); + intel_hdcp_info(m, intel_connector); + + seq_printf(m, "\tmax bpc: %u\n", connector->display_info.bpc); + + intel_panel_info(m, intel_connector); + + seq_printf(m, "\tmodes:\n"); + list_for_each_entry(mode, &connector->modes, head) + intel_seq_print_mode(m, 2, mode); +} + +static const char *plane_type(enum drm_plane_type type) +{ + switch (type) { + case DRM_PLANE_TYPE_OVERLAY: + return "OVL"; + case DRM_PLANE_TYPE_PRIMARY: + return "PRI"; + case DRM_PLANE_TYPE_CURSOR: + return "CUR"; + /* + * Deliberately omitting default: to generate compiler warnings + * when a new drm_plane_type gets added. + */ + } + + return "unknown"; +} + +static void plane_rotation(char *buf, size_t bufsize, unsigned int rotation) +{ + /* + * According to doc only one DRM_MODE_ROTATE_ is allowed but this + * will print them all to visualize if the values are misused + */ + snprintf(buf, bufsize, + "%s%s%s%s%s%s(0x%08x)", + (rotation & DRM_MODE_ROTATE_0) ? "0 " : "", + (rotation & DRM_MODE_ROTATE_90) ? "90 " : "", + (rotation & DRM_MODE_ROTATE_180) ? "180 " : "", + (rotation & DRM_MODE_ROTATE_270) ? "270 " : "", + (rotation & DRM_MODE_REFLECT_X) ? "FLIPX " : "", + (rotation & DRM_MODE_REFLECT_Y) ? "FLIPY " : "", + rotation); +} + +static const char *plane_visibility(const struct intel_plane_state *plane_state) +{ + if (plane_state->uapi.visible) + return "visible"; + + if (plane_state->planar_slave) + return "planar-slave"; + + return "hidden"; +} + +static void intel_plane_uapi_info(struct seq_file *m, struct intel_plane *plane) +{ + const struct intel_plane_state *plane_state = + to_intel_plane_state(plane->base.state); + const struct drm_framebuffer *fb = plane_state->uapi.fb; + struct drm_rect src, dst; + char rot_str[48]; + + src = drm_plane_state_src(&plane_state->uapi); + dst = drm_plane_state_dest(&plane_state->uapi); + + plane_rotation(rot_str, sizeof(rot_str), + plane_state->uapi.rotation); + + seq_puts(m, "\t\tuapi: [FB:"); + if (fb) + seq_printf(m, "%d] %p4cc,0x%llx,%dx%d", fb->base.id, + &fb->format->format, fb->modifier, fb->width, + fb->height); + else + seq_puts(m, "0] n/a,0x0,0x0,"); + seq_printf(m, ", visible=%s, src=" DRM_RECT_FP_FMT ", dst=" DRM_RECT_FMT + ", rotation=%s\n", plane_visibility(plane_state), + DRM_RECT_FP_ARG(&src), DRM_RECT_ARG(&dst), rot_str); + + if (plane_state->planar_linked_plane) + seq_printf(m, "\t\tplanar: Linked to [PLANE:%d:%s] as a %s\n", + plane_state->planar_linked_plane->base.base.id, plane_state->planar_linked_plane->base.name, + plane_state->planar_slave ? "slave" : "master"); +} + +static void intel_plane_hw_info(struct seq_file *m, struct intel_plane *plane) +{ + const struct intel_plane_state *plane_state = + to_intel_plane_state(plane->base.state); + const struct drm_framebuffer *fb = plane_state->hw.fb; + char rot_str[48]; + + if (!fb) + return; + + plane_rotation(rot_str, sizeof(rot_str), + plane_state->hw.rotation); + + seq_printf(m, "\t\thw: [FB:%d] %p4cc,0x%llx,%dx%d, visible=%s, src=" + DRM_RECT_FP_FMT ", dst=" DRM_RECT_FMT ", rotation=%s\n", + fb->base.id, &fb->format->format, + fb->modifier, fb->width, fb->height, + str_yes_no(plane_state->uapi.visible), + DRM_RECT_FP_ARG(&plane_state->uapi.src), + DRM_RECT_ARG(&plane_state->uapi.dst), + rot_str); +} + +static void intel_plane_info(struct seq_file *m, struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = node_to_i915(m->private); + struct intel_plane *plane; + + for_each_intel_plane_on_crtc(&dev_priv->drm, crtc, plane) { + seq_printf(m, "\t[PLANE:%d:%s]: type=%s\n", + plane->base.base.id, plane->base.name, + plane_type(plane->base.type)); + intel_plane_uapi_info(m, plane); + intel_plane_hw_info(m, plane); + } +} + +static void intel_scaler_info(struct seq_file *m, struct intel_crtc *crtc) +{ + const struct intel_crtc_state *crtc_state = + to_intel_crtc_state(crtc->base.state); + int num_scalers = crtc->num_scalers; + int i; + + /* Not all platformas have a scaler */ + if (num_scalers) { + seq_printf(m, "\tnum_scalers=%d, scaler_users=%x scaler_id=%d scaling_filter=%d", + num_scalers, + crtc_state->scaler_state.scaler_users, + crtc_state->scaler_state.scaler_id, + crtc_state->hw.scaling_filter); + + for (i = 0; i < num_scalers; i++) { + const struct intel_scaler *sc = + &crtc_state->scaler_state.scalers[i]; + + seq_printf(m, ", scalers[%d]: use=%s, mode=%x", + i, str_yes_no(sc->in_use), sc->mode); + } + seq_puts(m, "\n"); + } else { + seq_puts(m, "\tNo scalers available on this platform\n"); + } +} + +#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_VBLANK_EVADE) +static void crtc_updates_info(struct seq_file *m, + struct intel_crtc *crtc, + const char *hdr) +{ + u64 count; + int row; + + count = 0; + for (row = 0; row < ARRAY_SIZE(crtc->debug.vbl.times); row++) + count += crtc->debug.vbl.times[row]; + seq_printf(m, "%sUpdates: %llu\n", hdr, count); + if (!count) + return; + + for (row = 0; row < ARRAY_SIZE(crtc->debug.vbl.times); row++) { + char columns[80] = " |"; + unsigned int x; + + if (row & 1) { + const char *units; + + if (row > 10) { + x = 1000000; + units = "ms"; + } else { + x = 1000; + units = "us"; + } + + snprintf(columns, sizeof(columns), "%4ld%s |", + DIV_ROUND_CLOSEST(BIT(row + 9), x), units); + } + + if (crtc->debug.vbl.times[row]) { + x = ilog2(crtc->debug.vbl.times[row]); + memset(columns + 8, '*', x); + columns[8 + x] = '\0'; + } + + seq_printf(m, "%s%s\n", hdr, columns); + } + + seq_printf(m, "%sMin update: %lluns\n", + hdr, crtc->debug.vbl.min); + seq_printf(m, "%sMax update: %lluns\n", + hdr, crtc->debug.vbl.max); + seq_printf(m, "%sAverage update: %lluns\n", + hdr, div64_u64(crtc->debug.vbl.sum, count)); + seq_printf(m, "%sOverruns > %uus: %u\n", + hdr, VBLANK_EVASION_TIME_US, crtc->debug.vbl.over); +} + +static int crtc_updates_show(struct seq_file *m, void *data) +{ + crtc_updates_info(m, m->private, ""); + return 0; +} + +static int crtc_updates_open(struct inode *inode, struct file *file) +{ + return single_open(file, crtc_updates_show, inode->i_private); +} + +static ssize_t crtc_updates_write(struct file *file, + const char __user *ubuf, + size_t len, loff_t *offp) +{ + struct seq_file *m = file->private_data; + struct intel_crtc *crtc = m->private; + + /* May race with an update. Meh. */ + memset(&crtc->debug.vbl, 0, sizeof(crtc->debug.vbl)); + + return len; +} + +static const struct file_operations crtc_updates_fops = { + .owner = THIS_MODULE, + .open = crtc_updates_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = crtc_updates_write +}; + +static void crtc_updates_add(struct drm_crtc *crtc) +{ + debugfs_create_file("i915_update_info", 0644, crtc->debugfs_entry, + to_intel_crtc(crtc), &crtc_updates_fops); +} + +#else +static void crtc_updates_info(struct seq_file *m, + struct intel_crtc *crtc, + const char *hdr) +{ +} + +static void crtc_updates_add(struct drm_crtc *crtc) +{ +} +#endif + +static void intel_crtc_info(struct seq_file *m, struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = node_to_i915(m->private); + const struct intel_crtc_state *crtc_state = + to_intel_crtc_state(crtc->base.state); + struct intel_encoder *encoder; + + seq_printf(m, "[CRTC:%d:%s]:\n", + crtc->base.base.id, crtc->base.name); + + seq_printf(m, "\tuapi: enable=%s, active=%s, mode=" DRM_MODE_FMT "\n", + str_yes_no(crtc_state->uapi.enable), + str_yes_no(crtc_state->uapi.active), + DRM_MODE_ARG(&crtc_state->uapi.mode)); + + seq_printf(m, "\thw: enable=%s, active=%s\n", + str_yes_no(crtc_state->hw.enable), str_yes_no(crtc_state->hw.active)); + seq_printf(m, "\tadjusted_mode=" DRM_MODE_FMT "\n", + DRM_MODE_ARG(&crtc_state->hw.adjusted_mode)); + seq_printf(m, "\tpipe__mode=" DRM_MODE_FMT "\n", + DRM_MODE_ARG(&crtc_state->hw.pipe_mode)); + + seq_printf(m, "\tpipe src=" DRM_RECT_FMT ", dither=%s, bpp=%d\n", + DRM_RECT_ARG(&crtc_state->pipe_src), + str_yes_no(crtc_state->dither), crtc_state->pipe_bpp); + + intel_scaler_info(m, crtc); + + if (crtc_state->bigjoiner_pipes) + seq_printf(m, "\tLinked to 0x%x pipes as a %s\n", + crtc_state->bigjoiner_pipes, + intel_crtc_is_bigjoiner_slave(crtc_state) ? "slave" : "master"); + + for_each_intel_encoder_mask(&dev_priv->drm, encoder, + crtc_state->uapi.encoder_mask) + intel_encoder_info(m, crtc, encoder); + + intel_plane_info(m, crtc); + + seq_printf(m, "\tunderrun reporting: cpu=%s pch=%s\n", + str_yes_no(!crtc->cpu_fifo_underrun_disabled), + str_yes_no(!crtc->pch_fifo_underrun_disabled)); + + crtc_updates_info(m, crtc, "\t"); +} + +static int i915_display_info(struct seq_file *m, void *unused) +{ + struct drm_i915_private *dev_priv = node_to_i915(m->private); + struct drm_device *dev = &dev_priv->drm; + struct intel_crtc *crtc; + struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; + intel_wakeref_t wakeref; + + wakeref = intel_runtime_pm_get(&dev_priv->runtime_pm); + + drm_modeset_lock_all(dev); + + seq_printf(m, "CRTC info\n"); + seq_printf(m, "---------\n"); + for_each_intel_crtc(dev, crtc) + intel_crtc_info(m, crtc); + + seq_printf(m, "\n"); + seq_printf(m, "Connector info\n"); + seq_printf(m, "--------------\n"); + drm_connector_list_iter_begin(dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) + intel_connector_info(m, connector); + drm_connector_list_iter_end(&conn_iter); + + drm_modeset_unlock_all(dev); + + intel_runtime_pm_put(&dev_priv->runtime_pm, wakeref); + + return 0; +} + +static int i915_shared_dplls_info(struct seq_file *m, void *unused) +{ + struct drm_i915_private *dev_priv = node_to_i915(m->private); + struct drm_device *dev = &dev_priv->drm; + int i; + + drm_modeset_lock_all(dev); + + seq_printf(m, "PLL refclks: non-SSC: %d kHz, SSC: %d kHz\n", + dev_priv->display.dpll.ref_clks.nssc, + dev_priv->display.dpll.ref_clks.ssc); + + for (i = 0; i < dev_priv->display.dpll.num_shared_dpll; i++) { + struct intel_shared_dpll *pll = &dev_priv->display.dpll.shared_dplls[i]; + + seq_printf(m, "DPLL%i: %s, id: %i\n", i, pll->info->name, + pll->info->id); + seq_printf(m, " pipe_mask: 0x%x, active: 0x%x, on: %s\n", + pll->state.pipe_mask, pll->active_mask, + str_yes_no(pll->on)); + seq_printf(m, " tracked hardware state:\n"); + seq_printf(m, " dpll: 0x%08x\n", pll->state.hw_state.dpll); + seq_printf(m, " dpll_md: 0x%08x\n", + pll->state.hw_state.dpll_md); + seq_printf(m, " fp0: 0x%08x\n", pll->state.hw_state.fp0); + seq_printf(m, " fp1: 0x%08x\n", pll->state.hw_state.fp1); + seq_printf(m, " wrpll: 0x%08x\n", pll->state.hw_state.wrpll); + seq_printf(m, " cfgcr0: 0x%08x\n", pll->state.hw_state.cfgcr0); + seq_printf(m, " cfgcr1: 0x%08x\n", pll->state.hw_state.cfgcr1); + seq_printf(m, " div0: 0x%08x\n", pll->state.hw_state.div0); + seq_printf(m, " mg_refclkin_ctl: 0x%08x\n", + pll->state.hw_state.mg_refclkin_ctl); + seq_printf(m, " mg_clktop2_coreclkctl1: 0x%08x\n", + pll->state.hw_state.mg_clktop2_coreclkctl1); + seq_printf(m, " mg_clktop2_hsclkctl: 0x%08x\n", + pll->state.hw_state.mg_clktop2_hsclkctl); + seq_printf(m, " mg_pll_div0: 0x%08x\n", + pll->state.hw_state.mg_pll_div0); + seq_printf(m, " mg_pll_div1: 0x%08x\n", + pll->state.hw_state.mg_pll_div1); + seq_printf(m, " mg_pll_lf: 0x%08x\n", + pll->state.hw_state.mg_pll_lf); + seq_printf(m, " mg_pll_frac_lock: 0x%08x\n", + pll->state.hw_state.mg_pll_frac_lock); + seq_printf(m, " mg_pll_ssc: 0x%08x\n", + pll->state.hw_state.mg_pll_ssc); + seq_printf(m, " mg_pll_bias: 0x%08x\n", + pll->state.hw_state.mg_pll_bias); + seq_printf(m, " mg_pll_tdc_coldst_bias: 0x%08x\n", + pll->state.hw_state.mg_pll_tdc_coldst_bias); + } + drm_modeset_unlock_all(dev); + + return 0; +} + +static int i915_ddb_info(struct seq_file *m, void *unused) +{ + struct drm_i915_private *dev_priv = node_to_i915(m->private); + struct drm_device *dev = &dev_priv->drm; + struct skl_ddb_entry *entry; + struct intel_crtc *crtc; + + if (DISPLAY_VER(dev_priv) < 9) + return -ENODEV; + + drm_modeset_lock_all(dev); + + seq_printf(m, "%-15s%8s%8s%8s\n", "", "Start", "End", "Size"); + + for_each_intel_crtc(&dev_priv->drm, crtc) { + struct intel_crtc_state *crtc_state = + to_intel_crtc_state(crtc->base.state); + enum pipe pipe = crtc->pipe; + enum plane_id plane_id; + + seq_printf(m, "Pipe %c\n", pipe_name(pipe)); + + for_each_plane_id_on_crtc(crtc, plane_id) { + entry = &crtc_state->wm.skl.plane_ddb[plane_id]; + seq_printf(m, " Plane%-8d%8u%8u%8u\n", plane_id + 1, + entry->start, entry->end, + skl_ddb_entry_size(entry)); + } + + entry = &crtc_state->wm.skl.plane_ddb[PLANE_CURSOR]; + seq_printf(m, " %-13s%8u%8u%8u\n", "Cursor", entry->start, + entry->end, skl_ddb_entry_size(entry)); + } + + drm_modeset_unlock_all(dev); + + return 0; +} + +static int i915_drrs_status(struct seq_file *m, void *unused) +{ + struct drm_i915_private *dev_priv = node_to_i915(m->private); + struct drm_connector_list_iter conn_iter; + struct intel_connector *connector; + struct intel_crtc *crtc; + + drm_connector_list_iter_begin(&dev_priv->drm, &conn_iter); + for_each_intel_connector_iter(connector, &conn_iter) { + seq_printf(m, "[CONNECTOR:%d:%s] DRRS type: %s\n", + connector->base.base.id, connector->base.name, + intel_drrs_type_str(intel_panel_drrs_type(connector))); + } + drm_connector_list_iter_end(&conn_iter); + + seq_puts(m, "\n"); + + for_each_intel_crtc(&dev_priv->drm, crtc) { + const struct intel_crtc_state *crtc_state = + to_intel_crtc_state(crtc->base.state); + + seq_printf(m, "[CRTC:%d:%s]:\n", + crtc->base.base.id, crtc->base.name); + + mutex_lock(&crtc->drrs.mutex); + + /* DRRS Supported */ + seq_printf(m, "\tDRRS Enabled: %s\n", + str_yes_no(crtc_state->has_drrs)); + + seq_printf(m, "\tDRRS Active: %s\n", + str_yes_no(intel_drrs_is_active(crtc))); + + seq_printf(m, "\tBusy_frontbuffer_bits: 0x%X\n", + crtc->drrs.busy_frontbuffer_bits); + + seq_printf(m, "\tDRRS refresh rate: %s\n", + crtc->drrs.refresh_rate == DRRS_REFRESH_RATE_LOW ? + "low" : "high"); + + mutex_unlock(&crtc->drrs.mutex); + } + + return 0; +} + +static bool +intel_lpsp_power_well_enabled(struct drm_i915_private *i915, + enum i915_power_well_id power_well_id) +{ + intel_wakeref_t wakeref; + bool is_enabled; + + wakeref = intel_runtime_pm_get(&i915->runtime_pm); + is_enabled = intel_display_power_well_is_enabled(i915, + power_well_id); + intel_runtime_pm_put(&i915->runtime_pm, wakeref); + + return is_enabled; +} + +static int i915_lpsp_status(struct seq_file *m, void *unused) +{ + struct drm_i915_private *i915 = node_to_i915(m->private); + bool lpsp_enabled = false; + + if (DISPLAY_VER(i915) >= 13 || IS_DISPLAY_VER(i915, 9, 10)) { + lpsp_enabled = !intel_lpsp_power_well_enabled(i915, SKL_DISP_PW_2); + } else if (IS_DISPLAY_VER(i915, 11, 12)) { + lpsp_enabled = !intel_lpsp_power_well_enabled(i915, ICL_DISP_PW_3); + } else if (IS_HASWELL(i915) || IS_BROADWELL(i915)) { + lpsp_enabled = !intel_lpsp_power_well_enabled(i915, HSW_DISP_PW_GLOBAL); + } else { + seq_puts(m, "LPSP: not supported\n"); + return 0; + } + + seq_printf(m, "LPSP: %s\n", str_enabled_disabled(lpsp_enabled)); + + return 0; +} + +static int i915_dp_mst_info(struct seq_file *m, void *unused) +{ + struct drm_i915_private *dev_priv = node_to_i915(m->private); + struct drm_device *dev = &dev_priv->drm; + struct intel_encoder *intel_encoder; + struct intel_digital_port *dig_port; + struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; + + drm_connector_list_iter_begin(dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + if (connector->connector_type != DRM_MODE_CONNECTOR_DisplayPort) + continue; + + intel_encoder = intel_attached_encoder(to_intel_connector(connector)); + if (!intel_encoder || intel_encoder->type == INTEL_OUTPUT_DP_MST) + continue; + + dig_port = enc_to_dig_port(intel_encoder); + if (!intel_dp_mst_source_support(&dig_port->dp)) + continue; + + seq_printf(m, "MST Source Port [ENCODER:%d:%s]\n", + dig_port->base.base.base.id, + dig_port->base.base.name); + drm_dp_mst_dump_topology(m, &dig_port->dp.mst_mgr); + } + drm_connector_list_iter_end(&conn_iter); + + return 0; +} + +static ssize_t i915_displayport_test_active_write(struct file *file, + const char __user *ubuf, + size_t len, loff_t *offp) +{ + char *input_buffer; + int status = 0; + struct drm_device *dev; + struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; + struct intel_dp *intel_dp; + int val = 0; + + dev = ((struct seq_file *)file->private_data)->private; + + if (len == 0) + return 0; + + input_buffer = memdup_user_nul(ubuf, len); + if (IS_ERR(input_buffer)) + return PTR_ERR(input_buffer); + + drm_dbg(&to_i915(dev)->drm, + "Copied %d bytes from user\n", (unsigned int)len); + + drm_connector_list_iter_begin(dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + struct intel_encoder *encoder; + + if (connector->connector_type != + DRM_MODE_CONNECTOR_DisplayPort) + continue; + + encoder = to_intel_encoder(connector->encoder); + if (encoder && encoder->type == INTEL_OUTPUT_DP_MST) + continue; + + if (encoder && connector->status == connector_status_connected) { + intel_dp = enc_to_intel_dp(encoder); + status = kstrtoint(input_buffer, 10, &val); + if (status < 0) + break; + drm_dbg(&to_i915(dev)->drm, + "Got %d for test active\n", val); + /* To prevent erroneous activation of the compliance + * testing code, only accept an actual value of 1 here + */ + if (val == 1) + intel_dp->compliance.test_active = true; + else + intel_dp->compliance.test_active = false; + } + } + drm_connector_list_iter_end(&conn_iter); + kfree(input_buffer); + if (status < 0) + return status; + + *offp += len; + return len; +} + +static int i915_displayport_test_active_show(struct seq_file *m, void *data) +{ + struct drm_i915_private *dev_priv = m->private; + struct drm_device *dev = &dev_priv->drm; + struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; + struct intel_dp *intel_dp; + + drm_connector_list_iter_begin(dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + struct intel_encoder *encoder; + + if (connector->connector_type != + DRM_MODE_CONNECTOR_DisplayPort) + continue; + + encoder = to_intel_encoder(connector->encoder); + if (encoder && encoder->type == INTEL_OUTPUT_DP_MST) + continue; + + if (encoder && connector->status == connector_status_connected) { + intel_dp = enc_to_intel_dp(encoder); + if (intel_dp->compliance.test_active) + seq_puts(m, "1"); + else + seq_puts(m, "0"); + } else + seq_puts(m, "0"); + } + drm_connector_list_iter_end(&conn_iter); + + return 0; +} + +static int i915_displayport_test_active_open(struct inode *inode, + struct file *file) +{ + return single_open(file, i915_displayport_test_active_show, + inode->i_private); +} + +static const struct file_operations i915_displayport_test_active_fops = { + .owner = THIS_MODULE, + .open = i915_displayport_test_active_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = i915_displayport_test_active_write +}; + +static int i915_displayport_test_data_show(struct seq_file *m, void *data) +{ + struct drm_i915_private *dev_priv = m->private; + struct drm_device *dev = &dev_priv->drm; + struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; + struct intel_dp *intel_dp; + + drm_connector_list_iter_begin(dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + struct intel_encoder *encoder; + + if (connector->connector_type != + DRM_MODE_CONNECTOR_DisplayPort) + continue; + + encoder = to_intel_encoder(connector->encoder); + if (encoder && encoder->type == INTEL_OUTPUT_DP_MST) + continue; + + if (encoder && connector->status == connector_status_connected) { + intel_dp = enc_to_intel_dp(encoder); + if (intel_dp->compliance.test_type == + DP_TEST_LINK_EDID_READ) + seq_printf(m, "%lx", + intel_dp->compliance.test_data.edid); + else if (intel_dp->compliance.test_type == + DP_TEST_LINK_VIDEO_PATTERN) { + seq_printf(m, "hdisplay: %d\n", + intel_dp->compliance.test_data.hdisplay); + seq_printf(m, "vdisplay: %d\n", + intel_dp->compliance.test_data.vdisplay); + seq_printf(m, "bpc: %u\n", + intel_dp->compliance.test_data.bpc); + } else if (intel_dp->compliance.test_type == + DP_TEST_LINK_PHY_TEST_PATTERN) { + seq_printf(m, "pattern: %d\n", + intel_dp->compliance.test_data.phytest.phy_pattern); + seq_printf(m, "Number of lanes: %d\n", + intel_dp->compliance.test_data.phytest.num_lanes); + seq_printf(m, "Link Rate: %d\n", + intel_dp->compliance.test_data.phytest.link_rate); + seq_printf(m, "level: %02x\n", + intel_dp->train_set[0]); + } + } else + seq_puts(m, "0"); + } + drm_connector_list_iter_end(&conn_iter); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(i915_displayport_test_data); + +static int i915_displayport_test_type_show(struct seq_file *m, void *data) +{ + struct drm_i915_private *dev_priv = m->private; + struct drm_device *dev = &dev_priv->drm; + struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; + struct intel_dp *intel_dp; + + drm_connector_list_iter_begin(dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + struct intel_encoder *encoder; + + if (connector->connector_type != + DRM_MODE_CONNECTOR_DisplayPort) + continue; + + encoder = to_intel_encoder(connector->encoder); + if (encoder && encoder->type == INTEL_OUTPUT_DP_MST) + continue; + + if (encoder && connector->status == connector_status_connected) { + intel_dp = enc_to_intel_dp(encoder); + seq_printf(m, "%02lx\n", intel_dp->compliance.test_type); + } else + seq_puts(m, "0"); + } + drm_connector_list_iter_end(&conn_iter); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(i915_displayport_test_type); + +static void wm_latency_show(struct seq_file *m, const u16 wm[8]) +{ + struct drm_i915_private *dev_priv = m->private; + struct drm_device *dev = &dev_priv->drm; + int level; + int num_levels; + + if (IS_CHERRYVIEW(dev_priv)) + num_levels = 3; + else if (IS_VALLEYVIEW(dev_priv)) + num_levels = 1; + else if (IS_G4X(dev_priv)) + num_levels = 3; + else + num_levels = ilk_wm_max_level(dev_priv) + 1; + + drm_modeset_lock_all(dev); + + for (level = 0; level < num_levels; level++) { + unsigned int latency = wm[level]; + + /* + * - WM1+ latency values in 0.5us units + * - latencies are in us on gen9/vlv/chv + */ + if (DISPLAY_VER(dev_priv) >= 9 || + IS_VALLEYVIEW(dev_priv) || + IS_CHERRYVIEW(dev_priv) || + IS_G4X(dev_priv)) + latency *= 10; + else if (level > 0) + latency *= 5; + + seq_printf(m, "WM%d %u (%u.%u usec)\n", + level, wm[level], latency / 10, latency % 10); + } + + drm_modeset_unlock_all(dev); +} + +static int pri_wm_latency_show(struct seq_file *m, void *data) +{ + struct drm_i915_private *dev_priv = m->private; + const u16 *latencies; + + if (DISPLAY_VER(dev_priv) >= 9) + latencies = dev_priv->display.wm.skl_latency; + else + latencies = dev_priv->display.wm.pri_latency; + + wm_latency_show(m, latencies); + + return 0; +} + +static int spr_wm_latency_show(struct seq_file *m, void *data) +{ + struct drm_i915_private *dev_priv = m->private; + const u16 *latencies; + + if (DISPLAY_VER(dev_priv) >= 9) + latencies = dev_priv->display.wm.skl_latency; + else + latencies = dev_priv->display.wm.spr_latency; + + wm_latency_show(m, latencies); + + return 0; +} + +static int cur_wm_latency_show(struct seq_file *m, void *data) +{ + struct drm_i915_private *dev_priv = m->private; + const u16 *latencies; + + if (DISPLAY_VER(dev_priv) >= 9) + latencies = dev_priv->display.wm.skl_latency; + else + latencies = dev_priv->display.wm.cur_latency; + + wm_latency_show(m, latencies); + + return 0; +} + +static int pri_wm_latency_open(struct inode *inode, struct file *file) +{ + struct drm_i915_private *dev_priv = inode->i_private; + + if (DISPLAY_VER(dev_priv) < 5 && !IS_G4X(dev_priv)) + return -ENODEV; + + return single_open(file, pri_wm_latency_show, dev_priv); +} + +static int spr_wm_latency_open(struct inode *inode, struct file *file) +{ + struct drm_i915_private *dev_priv = inode->i_private; + + if (HAS_GMCH(dev_priv)) + return -ENODEV; + + return single_open(file, spr_wm_latency_show, dev_priv); +} + +static int cur_wm_latency_open(struct inode *inode, struct file *file) +{ + struct drm_i915_private *dev_priv = inode->i_private; + + if (HAS_GMCH(dev_priv)) + return -ENODEV; + + return single_open(file, cur_wm_latency_show, dev_priv); +} + +static ssize_t wm_latency_write(struct file *file, const char __user *ubuf, + size_t len, loff_t *offp, u16 wm[8]) +{ + struct seq_file *m = file->private_data; + struct drm_i915_private *dev_priv = m->private; + struct drm_device *dev = &dev_priv->drm; + u16 new[8] = { 0 }; + int num_levels; + int level; + int ret; + char tmp[32]; + + if (IS_CHERRYVIEW(dev_priv)) + num_levels = 3; + else if (IS_VALLEYVIEW(dev_priv)) + num_levels = 1; + else if (IS_G4X(dev_priv)) + num_levels = 3; + else + num_levels = ilk_wm_max_level(dev_priv) + 1; + + if (len >= sizeof(tmp)) + return -EINVAL; + + if (copy_from_user(tmp, ubuf, len)) + return -EFAULT; + + tmp[len] = '\0'; + + ret = sscanf(tmp, "%hu %hu %hu %hu %hu %hu %hu %hu", + &new[0], &new[1], &new[2], &new[3], + &new[4], &new[5], &new[6], &new[7]); + if (ret != num_levels) + return -EINVAL; + + drm_modeset_lock_all(dev); + + for (level = 0; level < num_levels; level++) + wm[level] = new[level]; + + drm_modeset_unlock_all(dev); + + return len; +} + + +static ssize_t pri_wm_latency_write(struct file *file, const char __user *ubuf, + size_t len, loff_t *offp) +{ + struct seq_file *m = file->private_data; + struct drm_i915_private *dev_priv = m->private; + u16 *latencies; + + if (DISPLAY_VER(dev_priv) >= 9) + latencies = dev_priv->display.wm.skl_latency; + else + latencies = dev_priv->display.wm.pri_latency; + + return wm_latency_write(file, ubuf, len, offp, latencies); +} + +static ssize_t spr_wm_latency_write(struct file *file, const char __user *ubuf, + size_t len, loff_t *offp) +{ + struct seq_file *m = file->private_data; + struct drm_i915_private *dev_priv = m->private; + u16 *latencies; + + if (DISPLAY_VER(dev_priv) >= 9) + latencies = dev_priv->display.wm.skl_latency; + else + latencies = dev_priv->display.wm.spr_latency; + + return wm_latency_write(file, ubuf, len, offp, latencies); +} + +static ssize_t cur_wm_latency_write(struct file *file, const char __user *ubuf, + size_t len, loff_t *offp) +{ + struct seq_file *m = file->private_data; + struct drm_i915_private *dev_priv = m->private; + u16 *latencies; + + if (DISPLAY_VER(dev_priv) >= 9) + latencies = dev_priv->display.wm.skl_latency; + else + latencies = dev_priv->display.wm.cur_latency; + + return wm_latency_write(file, ubuf, len, offp, latencies); +} + +static const struct file_operations i915_pri_wm_latency_fops = { + .owner = THIS_MODULE, + .open = pri_wm_latency_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = pri_wm_latency_write +}; + +static const struct file_operations i915_spr_wm_latency_fops = { + .owner = THIS_MODULE, + .open = spr_wm_latency_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = spr_wm_latency_write +}; + +static const struct file_operations i915_cur_wm_latency_fops = { + .owner = THIS_MODULE, + .open = cur_wm_latency_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = cur_wm_latency_write +}; + +static int i915_hpd_storm_ctl_show(struct seq_file *m, void *data) +{ + struct drm_i915_private *dev_priv = m->private; + struct intel_hotplug *hotplug = &dev_priv->display.hotplug; + + /* Synchronize with everything first in case there's been an HPD + * storm, but we haven't finished handling it in the kernel yet + */ + intel_synchronize_irq(dev_priv); + flush_work(&dev_priv->display.hotplug.dig_port_work); + flush_delayed_work(&dev_priv->display.hotplug.hotplug_work); + + seq_printf(m, "Threshold: %d\n", hotplug->hpd_storm_threshold); + seq_printf(m, "Detected: %s\n", + str_yes_no(delayed_work_pending(&hotplug->reenable_work))); + + return 0; +} + +static ssize_t i915_hpd_storm_ctl_write(struct file *file, + const char __user *ubuf, size_t len, + loff_t *offp) +{ + struct seq_file *m = file->private_data; + struct drm_i915_private *dev_priv = m->private; + struct intel_hotplug *hotplug = &dev_priv->display.hotplug; + unsigned int new_threshold; + int i; + char *newline; + char tmp[16]; + + if (len >= sizeof(tmp)) + return -EINVAL; + + if (copy_from_user(tmp, ubuf, len)) + return -EFAULT; + + tmp[len] = '\0'; + + /* Strip newline, if any */ + newline = strchr(tmp, '\n'); + if (newline) + *newline = '\0'; + + if (strcmp(tmp, "reset") == 0) + new_threshold = HPD_STORM_DEFAULT_THRESHOLD; + else if (kstrtouint(tmp, 10, &new_threshold) != 0) + return -EINVAL; + + if (new_threshold > 0) + drm_dbg_kms(&dev_priv->drm, + "Setting HPD storm detection threshold to %d\n", + new_threshold); + else + drm_dbg_kms(&dev_priv->drm, "Disabling HPD storm detection\n"); + + spin_lock_irq(&dev_priv->irq_lock); + hotplug->hpd_storm_threshold = new_threshold; + /* Reset the HPD storm stats so we don't accidentally trigger a storm */ + for_each_hpd_pin(i) + hotplug->stats[i].count = 0; + spin_unlock_irq(&dev_priv->irq_lock); + + /* Re-enable hpd immediately if we were in an irq storm */ + flush_delayed_work(&dev_priv->display.hotplug.reenable_work); + + return len; +} + +static int i915_hpd_storm_ctl_open(struct inode *inode, struct file *file) +{ + return single_open(file, i915_hpd_storm_ctl_show, inode->i_private); +} + +static const struct file_operations i915_hpd_storm_ctl_fops = { + .owner = THIS_MODULE, + .open = i915_hpd_storm_ctl_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = i915_hpd_storm_ctl_write +}; + +static int i915_hpd_short_storm_ctl_show(struct seq_file *m, void *data) +{ + struct drm_i915_private *dev_priv = m->private; + + seq_printf(m, "Enabled: %s\n", + str_yes_no(dev_priv->display.hotplug.hpd_short_storm_enabled)); + + return 0; +} + +static int +i915_hpd_short_storm_ctl_open(struct inode *inode, struct file *file) +{ + return single_open(file, i915_hpd_short_storm_ctl_show, + inode->i_private); +} + +static ssize_t i915_hpd_short_storm_ctl_write(struct file *file, + const char __user *ubuf, + size_t len, loff_t *offp) +{ + struct seq_file *m = file->private_data; + struct drm_i915_private *dev_priv = m->private; + struct intel_hotplug *hotplug = &dev_priv->display.hotplug; + char *newline; + char tmp[16]; + int i; + bool new_state; + + if (len >= sizeof(tmp)) + return -EINVAL; + + if (copy_from_user(tmp, ubuf, len)) + return -EFAULT; + + tmp[len] = '\0'; + + /* Strip newline, if any */ + newline = strchr(tmp, '\n'); + if (newline) + *newline = '\0'; + + /* Reset to the "default" state for this system */ + if (strcmp(tmp, "reset") == 0) + new_state = !HAS_DP_MST(dev_priv); + else if (kstrtobool(tmp, &new_state) != 0) + return -EINVAL; + + drm_dbg_kms(&dev_priv->drm, "%sabling HPD short storm detection\n", + new_state ? "En" : "Dis"); + + spin_lock_irq(&dev_priv->irq_lock); + hotplug->hpd_short_storm_enabled = new_state; + /* Reset the HPD storm stats so we don't accidentally trigger a storm */ + for_each_hpd_pin(i) + hotplug->stats[i].count = 0; + spin_unlock_irq(&dev_priv->irq_lock); + + /* Re-enable hpd immediately if we were in an irq storm */ + flush_delayed_work(&dev_priv->display.hotplug.reenable_work); + + return len; +} + +static const struct file_operations i915_hpd_short_storm_ctl_fops = { + .owner = THIS_MODULE, + .open = i915_hpd_short_storm_ctl_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = i915_hpd_short_storm_ctl_write, +}; + +static int i915_drrs_ctl_set(void *data, u64 val) +{ + struct drm_i915_private *dev_priv = data; + struct drm_device *dev = &dev_priv->drm; + struct intel_crtc *crtc; + + for_each_intel_crtc(dev, crtc) { + struct intel_crtc_state *crtc_state; + struct drm_crtc_commit *commit; + int ret; + + ret = drm_modeset_lock_single_interruptible(&crtc->base.mutex); + if (ret) + return ret; + + crtc_state = to_intel_crtc_state(crtc->base.state); + + if (!crtc_state->hw.active || + !crtc_state->has_drrs) + goto out; + + commit = crtc_state->uapi.commit; + if (commit) { + ret = wait_for_completion_interruptible(&commit->hw_done); + if (ret) + goto out; + } + + drm_dbg(&dev_priv->drm, + "Manually %sactivating DRRS\n", val ? "" : "de"); + + if (val) + intel_drrs_activate(crtc_state); + else + intel_drrs_deactivate(crtc_state); + +out: + drm_modeset_unlock(&crtc->base.mutex); + if (ret) + return ret; + } + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(i915_drrs_ctl_fops, NULL, i915_drrs_ctl_set, "%llu\n"); + +static ssize_t +i915_fifo_underrun_reset_write(struct file *filp, + const char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + struct drm_i915_private *dev_priv = filp->private_data; + struct intel_crtc *crtc; + struct drm_device *dev = &dev_priv->drm; + int ret; + bool reset; + + ret = kstrtobool_from_user(ubuf, cnt, &reset); + if (ret) + return ret; + + if (!reset) + return cnt; + + for_each_intel_crtc(dev, crtc) { + struct drm_crtc_commit *commit; + struct intel_crtc_state *crtc_state; + + ret = drm_modeset_lock_single_interruptible(&crtc->base.mutex); + if (ret) + return ret; + + crtc_state = to_intel_crtc_state(crtc->base.state); + commit = crtc_state->uapi.commit; + if (commit) { + ret = wait_for_completion_interruptible(&commit->hw_done); + if (!ret) + ret = wait_for_completion_interruptible(&commit->flip_done); + } + + if (!ret && crtc_state->hw.active) { + drm_dbg_kms(&dev_priv->drm, + "Re-arming FIFO underruns on pipe %c\n", + pipe_name(crtc->pipe)); + + intel_crtc_arm_fifo_underrun(crtc, crtc_state); + } + + drm_modeset_unlock(&crtc->base.mutex); + + if (ret) + return ret; + } + + intel_fbc_reset_underrun(dev_priv); + + return cnt; +} + +static const struct file_operations i915_fifo_underrun_reset_ops = { + .owner = THIS_MODULE, + .open = simple_open, + .write = i915_fifo_underrun_reset_write, + .llseek = default_llseek, +}; + +static const struct drm_info_list intel_display_debugfs_list[] = { + {"i915_frontbuffer_tracking", i915_frontbuffer_tracking, 0}, + {"i915_ips_status", i915_ips_status, 0}, + {"i915_sr_status", i915_sr_status, 0}, + {"i915_opregion", i915_opregion, 0}, + {"i915_vbt", i915_vbt, 0}, + {"i915_gem_framebuffer", i915_gem_framebuffer_info, 0}, + {"i915_edp_psr_status", i915_edp_psr_status, 0}, + {"i915_power_domain_info", i915_power_domain_info, 0}, + {"i915_display_info", i915_display_info, 0}, + {"i915_shared_dplls_info", i915_shared_dplls_info, 0}, + {"i915_dp_mst_info", i915_dp_mst_info, 0}, + {"i915_ddb_info", i915_ddb_info, 0}, + {"i915_drrs_status", i915_drrs_status, 0}, + {"i915_lpsp_status", i915_lpsp_status, 0}, +}; + +static const struct { + const char *name; + const struct file_operations *fops; +} intel_display_debugfs_files[] = { + {"i915_fifo_underrun_reset", &i915_fifo_underrun_reset_ops}, + {"i915_pri_wm_latency", &i915_pri_wm_latency_fops}, + {"i915_spr_wm_latency", &i915_spr_wm_latency_fops}, + {"i915_cur_wm_latency", &i915_cur_wm_latency_fops}, + {"i915_dp_test_data", &i915_displayport_test_data_fops}, + {"i915_dp_test_type", &i915_displayport_test_type_fops}, + {"i915_dp_test_active", &i915_displayport_test_active_fops}, + {"i915_hpd_storm_ctl", &i915_hpd_storm_ctl_fops}, + {"i915_hpd_short_storm_ctl", &i915_hpd_short_storm_ctl_fops}, + {"i915_drrs_ctl", &i915_drrs_ctl_fops}, + {"i915_edp_psr_debug", &i915_edp_psr_debug_fops}, +}; + +void intel_display_debugfs_register(struct drm_i915_private *i915) +{ + struct drm_minor *minor = i915->drm.primary; + int i; + + for (i = 0; i < ARRAY_SIZE(intel_display_debugfs_files); i++) { + debugfs_create_file(intel_display_debugfs_files[i].name, + S_IRUGO | S_IWUSR, + minor->debugfs_root, + to_i915(minor->dev), + intel_display_debugfs_files[i].fops); + } + + drm_debugfs_create_files(intel_display_debugfs_list, + ARRAY_SIZE(intel_display_debugfs_list), + minor->debugfs_root, minor); + + intel_dmc_debugfs_register(i915); + intel_fbc_debugfs_register(i915); + skl_watermark_ipc_debugfs_register(i915); +} + +static int i915_panel_show(struct seq_file *m, void *data) +{ + struct drm_connector *connector = m->private; + struct intel_dp *intel_dp = + intel_attached_dp(to_intel_connector(connector)); + + if (connector->status != connector_status_connected) + return -ENODEV; + + seq_printf(m, "Panel power up delay: %d\n", + intel_dp->pps.panel_power_up_delay); + seq_printf(m, "Panel power down delay: %d\n", + intel_dp->pps.panel_power_down_delay); + seq_printf(m, "Backlight on delay: %d\n", + intel_dp->pps.backlight_on_delay); + seq_printf(m, "Backlight off delay: %d\n", + intel_dp->pps.backlight_off_delay); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(i915_panel); + +static int i915_hdcp_sink_capability_show(struct seq_file *m, void *data) +{ + struct drm_connector *connector = m->private; + struct drm_i915_private *i915 = to_i915(connector->dev); + struct intel_connector *intel_connector = to_intel_connector(connector); + int ret; + + ret = drm_modeset_lock_single_interruptible(&i915->drm.mode_config.connection_mutex); + if (ret) + return ret; + + if (!connector->encoder || connector->status != connector_status_connected) { + ret = -ENODEV; + goto out; + } + + seq_printf(m, "%s:%d HDCP version: ", connector->name, + connector->base.id); + intel_hdcp_info(m, intel_connector); + +out: + drm_modeset_unlock(&i915->drm.mode_config.connection_mutex); + + return ret; +} +DEFINE_SHOW_ATTRIBUTE(i915_hdcp_sink_capability); + +static int i915_psr_status_show(struct seq_file *m, void *data) +{ + struct drm_connector *connector = m->private; + struct intel_dp *intel_dp = + intel_attached_dp(to_intel_connector(connector)); + + return intel_psr_status(m, intel_dp); +} +DEFINE_SHOW_ATTRIBUTE(i915_psr_status); + +static int i915_lpsp_capability_show(struct seq_file *m, void *data) +{ + struct drm_connector *connector = m->private; + struct drm_i915_private *i915 = to_i915(connector->dev); + struct intel_encoder *encoder; + bool lpsp_capable = false; + + encoder = intel_attached_encoder(to_intel_connector(connector)); + if (!encoder) + return -ENODEV; + + if (connector->status != connector_status_connected) + return -ENODEV; + + if (DISPLAY_VER(i915) >= 13) + lpsp_capable = encoder->port <= PORT_B; + else if (DISPLAY_VER(i915) >= 12) + /* + * Actually TGL can drive LPSP on port till DDI_C + * but there is no physical connected DDI_C on TGL sku's, + * even driver is not initilizing DDI_C port for gen12. + */ + lpsp_capable = encoder->port <= PORT_B; + else if (DISPLAY_VER(i915) == 11) + lpsp_capable = (connector->connector_type == DRM_MODE_CONNECTOR_DSI || + connector->connector_type == DRM_MODE_CONNECTOR_eDP); + else if (IS_DISPLAY_VER(i915, 9, 10)) + lpsp_capable = (encoder->port == PORT_A && + (connector->connector_type == DRM_MODE_CONNECTOR_DSI || + connector->connector_type == DRM_MODE_CONNECTOR_eDP || + connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort)); + else if (IS_HASWELL(i915) || IS_BROADWELL(i915)) + lpsp_capable = connector->connector_type == DRM_MODE_CONNECTOR_eDP; + + seq_printf(m, "LPSP: %s\n", lpsp_capable ? "capable" : "incapable"); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(i915_lpsp_capability); + +static int i915_dsc_fec_support_show(struct seq_file *m, void *data) +{ + struct drm_connector *connector = m->private; + struct drm_device *dev = connector->dev; + struct drm_crtc *crtc; + struct intel_dp *intel_dp; + struct drm_modeset_acquire_ctx ctx; + struct intel_crtc_state *crtc_state = NULL; + int ret = 0; + bool try_again = false; + + drm_modeset_acquire_init(&ctx, DRM_MODESET_ACQUIRE_INTERRUPTIBLE); + + do { + try_again = false; + ret = drm_modeset_lock(&dev->mode_config.connection_mutex, + &ctx); + if (ret) { + if (ret == -EDEADLK && !drm_modeset_backoff(&ctx)) { + try_again = true; + continue; + } + break; + } + crtc = connector->state->crtc; + if (connector->status != connector_status_connected || !crtc) { + ret = -ENODEV; + break; + } + ret = drm_modeset_lock(&crtc->mutex, &ctx); + if (ret == -EDEADLK) { + ret = drm_modeset_backoff(&ctx); + if (!ret) { + try_again = true; + continue; + } + break; + } else if (ret) { + break; + } + intel_dp = intel_attached_dp(to_intel_connector(connector)); + crtc_state = to_intel_crtc_state(crtc->state); + seq_printf(m, "DSC_Enabled: %s\n", + str_yes_no(crtc_state->dsc.compression_enable)); + seq_printf(m, "DSC_Sink_Support: %s\n", + str_yes_no(drm_dp_sink_supports_dsc(intel_dp->dsc_dpcd))); + seq_printf(m, "Force_DSC_Enable: %s\n", + str_yes_no(intel_dp->force_dsc_en)); + if (!intel_dp_is_edp(intel_dp)) + seq_printf(m, "FEC_Sink_Support: %s\n", + str_yes_no(drm_dp_sink_supports_fec(intel_dp->fec_capable))); + } while (try_again); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); + + return ret; +} + +static ssize_t i915_dsc_fec_support_write(struct file *file, + const char __user *ubuf, + size_t len, loff_t *offp) +{ + bool dsc_enable = false; + int ret; + struct drm_connector *connector = + ((struct seq_file *)file->private_data)->private; + struct intel_encoder *encoder = intel_attached_encoder(to_intel_connector(connector)); + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + + if (len == 0) + return 0; + + drm_dbg(&i915->drm, + "Copied %zu bytes from user to force DSC\n", len); + + ret = kstrtobool_from_user(ubuf, len, &dsc_enable); + if (ret < 0) + return ret; + + drm_dbg(&i915->drm, "Got %s for DSC Enable\n", + (dsc_enable) ? "true" : "false"); + intel_dp->force_dsc_en = dsc_enable; + + *offp += len; + return len; +} + +static int i915_dsc_fec_support_open(struct inode *inode, + struct file *file) +{ + return single_open(file, i915_dsc_fec_support_show, + inode->i_private); +} + +static const struct file_operations i915_dsc_fec_support_fops = { + .owner = THIS_MODULE, + .open = i915_dsc_fec_support_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = i915_dsc_fec_support_write +}; + +static int i915_dsc_bpc_show(struct seq_file *m, void *data) +{ + struct drm_connector *connector = m->private; + struct drm_device *dev = connector->dev; + struct drm_crtc *crtc; + struct intel_crtc_state *crtc_state; + struct intel_encoder *encoder = intel_attached_encoder(to_intel_connector(connector)); + int ret; + + if (!encoder) + return -ENODEV; + + ret = drm_modeset_lock_single_interruptible(&dev->mode_config.connection_mutex); + if (ret) + return ret; + + crtc = connector->state->crtc; + if (connector->status != connector_status_connected || !crtc) { + ret = -ENODEV; + goto out; + } + + crtc_state = to_intel_crtc_state(crtc->state); + seq_printf(m, "Input_BPC: %d\n", crtc_state->dsc.config.bits_per_component); + +out: drm_modeset_unlock(&dev->mode_config.connection_mutex); + + return ret; +} + +static ssize_t i915_dsc_bpc_write(struct file *file, + const char __user *ubuf, + size_t len, loff_t *offp) +{ + struct drm_connector *connector = + ((struct seq_file *)file->private_data)->private; + struct intel_encoder *encoder = intel_attached_encoder(to_intel_connector(connector)); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + int dsc_bpc = 0; + int ret; + + ret = kstrtoint_from_user(ubuf, len, 0, &dsc_bpc); + if (ret < 0) + return ret; + + intel_dp->force_dsc_bpc = dsc_bpc; + *offp += len; + + return len; +} + +static int i915_dsc_bpc_open(struct inode *inode, + struct file *file) +{ + return single_open(file, i915_dsc_bpc_show, inode->i_private); +} + +static const struct file_operations i915_dsc_bpc_fops = { + .owner = THIS_MODULE, + .open = i915_dsc_bpc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = i915_dsc_bpc_write +}; + +/* + * Returns the Current CRTC's bpc. + * Example usage: cat /sys/kernel/debug/dri/0/crtc-0/i915_current_bpc + */ +static int i915_current_bpc_show(struct seq_file *m, void *data) +{ + struct intel_crtc *crtc = to_intel_crtc(m->private); + struct intel_crtc_state *crtc_state; + int ret; + + ret = drm_modeset_lock_single_interruptible(&crtc->base.mutex); + if (ret) + return ret; + + crtc_state = to_intel_crtc_state(crtc->base.state); + seq_printf(m, "Current: %u\n", crtc_state->pipe_bpp / 3); + + drm_modeset_unlock(&crtc->base.mutex); + + return ret; +} +DEFINE_SHOW_ATTRIBUTE(i915_current_bpc); + +/** + * intel_connector_debugfs_add - add i915 specific connector debugfs files + * @connector: pointer to a registered drm_connector + * + * Cleanup will be done by drm_connector_unregister() through a call to + * drm_debugfs_connector_remove(). + */ +void intel_connector_debugfs_add(struct intel_connector *intel_connector) +{ + struct drm_connector *connector = &intel_connector->base; + struct dentry *root = connector->debugfs_entry; + struct drm_i915_private *dev_priv = to_i915(connector->dev); + + /* The connector must have been registered beforehands. */ + if (!root) + return; + + if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) { + debugfs_create_file("i915_panel_timings", S_IRUGO, root, + connector, &i915_panel_fops); + debugfs_create_file("i915_psr_sink_status", S_IRUGO, root, + connector, &i915_psr_sink_status_fops); + } + + if (HAS_PSR(dev_priv) && + connector->connector_type == DRM_MODE_CONNECTOR_eDP) { + debugfs_create_file("i915_psr_status", 0444, root, + connector, &i915_psr_status_fops); + } + + if (connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort || + connector->connector_type == DRM_MODE_CONNECTOR_HDMIA || + connector->connector_type == DRM_MODE_CONNECTOR_HDMIB) { + debugfs_create_file("i915_hdcp_sink_capability", S_IRUGO, root, + connector, &i915_hdcp_sink_capability_fops); + } + + if (DISPLAY_VER(dev_priv) >= 11 && + ((connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort && + !to_intel_connector(connector)->mst_port) || + connector->connector_type == DRM_MODE_CONNECTOR_eDP)) { + debugfs_create_file("i915_dsc_fec_support", 0644, root, + connector, &i915_dsc_fec_support_fops); + + debugfs_create_file("i915_dsc_bpc", 0644, root, + connector, &i915_dsc_bpc_fops); + } + + if (connector->connector_type == DRM_MODE_CONNECTOR_DSI || + connector->connector_type == DRM_MODE_CONNECTOR_eDP || + connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort || + connector->connector_type == DRM_MODE_CONNECTOR_HDMIA || + connector->connector_type == DRM_MODE_CONNECTOR_HDMIB) + debugfs_create_file("i915_lpsp_capability", 0444, root, + connector, &i915_lpsp_capability_fops); +} + +/** + * intel_crtc_debugfs_add - add i915 specific crtc debugfs files + * @crtc: pointer to a drm_crtc + * + * Failure to add debugfs entries should generally be ignored. + */ +void intel_crtc_debugfs_add(struct drm_crtc *crtc) +{ + if (!crtc->debugfs_entry) + return; + + crtc_updates_add(crtc); + intel_fbc_crtc_debugfs_add(to_intel_crtc(crtc)); + + debugfs_create_file("i915_current_bpc", 0444, crtc->debugfs_entry, crtc, + &i915_current_bpc_fops); +} diff --git a/drivers/gpu/drm/i915/display/intel_display_debugfs.h b/drivers/gpu/drm/i915/display/intel_display_debugfs.h new file mode 100644 index 000000000..d3a79c07c --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_display_debugfs.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2020 Intel Corporation + */ + +#ifndef __INTEL_DISPLAY_DEBUGFS_H__ +#define __INTEL_DISPLAY_DEBUGFS_H__ + +struct drm_crtc; +struct drm_i915_private; +struct intel_connector; + +#ifdef CONFIG_DEBUG_FS +void intel_display_debugfs_register(struct drm_i915_private *i915); +void intel_connector_debugfs_add(struct intel_connector *connector); +void intel_crtc_debugfs_add(struct drm_crtc *crtc); +#else +static inline void intel_display_debugfs_register(struct drm_i915_private *i915) {} +static inline void intel_connector_debugfs_add(struct intel_connector *connector) {} +static inline void intel_crtc_debugfs_add(struct drm_crtc *crtc) {} +#endif + +#endif /* __INTEL_DISPLAY_DEBUGFS_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_display_power.c b/drivers/gpu/drm/i915/display/intel_display_power.c new file mode 100644 index 000000000..1a63da28f --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_display_power.c @@ -0,0 +1,2489 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#include + +#include "i915_drv.h" +#include "i915_irq.h" +#include "intel_backlight_regs.h" +#include "intel_cdclk.h" +#include "intel_combo_phy.h" +#include "intel_de.h" +#include "intel_display_power.h" +#include "intel_display_power_map.h" +#include "intel_display_power_well.h" +#include "intel_display_types.h" +#include "intel_dmc.h" +#include "intel_mchbar_regs.h" +#include "intel_pch_refclk.h" +#include "intel_pcode.h" +#include "intel_snps_phy.h" +#include "skl_watermark.h" +#include "vlv_sideband.h" + +#define for_each_power_domain_well(__dev_priv, __power_well, __domain) \ + for_each_power_well(__dev_priv, __power_well) \ + for_each_if(test_bit((__domain), (__power_well)->domains.bits)) + +#define for_each_power_domain_well_reverse(__dev_priv, __power_well, __domain) \ + for_each_power_well_reverse(__dev_priv, __power_well) \ + for_each_if(test_bit((__domain), (__power_well)->domains.bits)) + +const char * +intel_display_power_domain_str(enum intel_display_power_domain domain) +{ + switch (domain) { + case POWER_DOMAIN_DISPLAY_CORE: + return "DISPLAY_CORE"; + case POWER_DOMAIN_PIPE_A: + return "PIPE_A"; + case POWER_DOMAIN_PIPE_B: + return "PIPE_B"; + case POWER_DOMAIN_PIPE_C: + return "PIPE_C"; + case POWER_DOMAIN_PIPE_D: + return "PIPE_D"; + case POWER_DOMAIN_PIPE_PANEL_FITTER_A: + return "PIPE_PANEL_FITTER_A"; + case POWER_DOMAIN_PIPE_PANEL_FITTER_B: + return "PIPE_PANEL_FITTER_B"; + case POWER_DOMAIN_PIPE_PANEL_FITTER_C: + return "PIPE_PANEL_FITTER_C"; + case POWER_DOMAIN_PIPE_PANEL_FITTER_D: + return "PIPE_PANEL_FITTER_D"; + case POWER_DOMAIN_TRANSCODER_A: + return "TRANSCODER_A"; + case POWER_DOMAIN_TRANSCODER_B: + return "TRANSCODER_B"; + case POWER_DOMAIN_TRANSCODER_C: + return "TRANSCODER_C"; + case POWER_DOMAIN_TRANSCODER_D: + return "TRANSCODER_D"; + case POWER_DOMAIN_TRANSCODER_EDP: + return "TRANSCODER_EDP"; + case POWER_DOMAIN_TRANSCODER_DSI_A: + return "TRANSCODER_DSI_A"; + case POWER_DOMAIN_TRANSCODER_DSI_C: + return "TRANSCODER_DSI_C"; + case POWER_DOMAIN_TRANSCODER_VDSC_PW2: + return "TRANSCODER_VDSC_PW2"; + case POWER_DOMAIN_PORT_DDI_LANES_A: + return "PORT_DDI_LANES_A"; + case POWER_DOMAIN_PORT_DDI_LANES_B: + return "PORT_DDI_LANES_B"; + case POWER_DOMAIN_PORT_DDI_LANES_C: + return "PORT_DDI_LANES_C"; + case POWER_DOMAIN_PORT_DDI_LANES_D: + return "PORT_DDI_LANES_D"; + case POWER_DOMAIN_PORT_DDI_LANES_E: + return "PORT_DDI_LANES_E"; + case POWER_DOMAIN_PORT_DDI_LANES_F: + return "PORT_DDI_LANES_F"; + case POWER_DOMAIN_PORT_DDI_LANES_TC1: + return "PORT_DDI_LANES_TC1"; + case POWER_DOMAIN_PORT_DDI_LANES_TC2: + return "PORT_DDI_LANES_TC2"; + case POWER_DOMAIN_PORT_DDI_LANES_TC3: + return "PORT_DDI_LANES_TC3"; + case POWER_DOMAIN_PORT_DDI_LANES_TC4: + return "PORT_DDI_LANES_TC4"; + case POWER_DOMAIN_PORT_DDI_LANES_TC5: + return "PORT_DDI_LANES_TC5"; + case POWER_DOMAIN_PORT_DDI_LANES_TC6: + return "PORT_DDI_LANES_TC6"; + case POWER_DOMAIN_PORT_DDI_IO_A: + return "PORT_DDI_IO_A"; + case POWER_DOMAIN_PORT_DDI_IO_B: + return "PORT_DDI_IO_B"; + case POWER_DOMAIN_PORT_DDI_IO_C: + return "PORT_DDI_IO_C"; + case POWER_DOMAIN_PORT_DDI_IO_D: + return "PORT_DDI_IO_D"; + case POWER_DOMAIN_PORT_DDI_IO_E: + return "PORT_DDI_IO_E"; + case POWER_DOMAIN_PORT_DDI_IO_F: + return "PORT_DDI_IO_F"; + case POWER_DOMAIN_PORT_DDI_IO_TC1: + return "PORT_DDI_IO_TC1"; + case POWER_DOMAIN_PORT_DDI_IO_TC2: + return "PORT_DDI_IO_TC2"; + case POWER_DOMAIN_PORT_DDI_IO_TC3: + return "PORT_DDI_IO_TC3"; + case POWER_DOMAIN_PORT_DDI_IO_TC4: + return "PORT_DDI_IO_TC4"; + case POWER_DOMAIN_PORT_DDI_IO_TC5: + return "PORT_DDI_IO_TC5"; + case POWER_DOMAIN_PORT_DDI_IO_TC6: + return "PORT_DDI_IO_TC6"; + case POWER_DOMAIN_PORT_DSI: + return "PORT_DSI"; + case POWER_DOMAIN_PORT_CRT: + return "PORT_CRT"; + case POWER_DOMAIN_PORT_OTHER: + return "PORT_OTHER"; + case POWER_DOMAIN_VGA: + return "VGA"; + case POWER_DOMAIN_AUDIO_MMIO: + return "AUDIO_MMIO"; + case POWER_DOMAIN_AUDIO_PLAYBACK: + return "AUDIO_PLAYBACK"; + case POWER_DOMAIN_AUX_A: + return "AUX_A"; + case POWER_DOMAIN_AUX_B: + return "AUX_B"; + case POWER_DOMAIN_AUX_C: + return "AUX_C"; + case POWER_DOMAIN_AUX_D: + return "AUX_D"; + case POWER_DOMAIN_AUX_E: + return "AUX_E"; + case POWER_DOMAIN_AUX_F: + return "AUX_F"; + case POWER_DOMAIN_AUX_USBC1: + return "AUX_USBC1"; + case POWER_DOMAIN_AUX_USBC2: + return "AUX_USBC2"; + case POWER_DOMAIN_AUX_USBC3: + return "AUX_USBC3"; + case POWER_DOMAIN_AUX_USBC4: + return "AUX_USBC4"; + case POWER_DOMAIN_AUX_USBC5: + return "AUX_USBC5"; + case POWER_DOMAIN_AUX_USBC6: + return "AUX_USBC6"; + case POWER_DOMAIN_AUX_IO_A: + return "AUX_IO_A"; + case POWER_DOMAIN_AUX_TBT1: + return "AUX_TBT1"; + case POWER_DOMAIN_AUX_TBT2: + return "AUX_TBT2"; + case POWER_DOMAIN_AUX_TBT3: + return "AUX_TBT3"; + case POWER_DOMAIN_AUX_TBT4: + return "AUX_TBT4"; + case POWER_DOMAIN_AUX_TBT5: + return "AUX_TBT5"; + case POWER_DOMAIN_AUX_TBT6: + return "AUX_TBT6"; + case POWER_DOMAIN_GMBUS: + return "GMBUS"; + case POWER_DOMAIN_INIT: + return "INIT"; + case POWER_DOMAIN_MODESET: + return "MODESET"; + case POWER_DOMAIN_GT_IRQ: + return "GT_IRQ"; + case POWER_DOMAIN_DC_OFF: + return "DC_OFF"; + case POWER_DOMAIN_TC_COLD_OFF: + return "TC_COLD_OFF"; + default: + MISSING_CASE(domain); + return "?"; + } +} + +/** + * __intel_display_power_is_enabled - unlocked check for a power domain + * @dev_priv: i915 device instance + * @domain: power domain to check + * + * This is the unlocked version of intel_display_power_is_enabled() and should + * only be used from error capture and recovery code where deadlocks are + * possible. + * + * Returns: + * True when the power domain is enabled, false otherwise. + */ +bool __intel_display_power_is_enabled(struct drm_i915_private *dev_priv, + enum intel_display_power_domain domain) +{ + struct i915_power_well *power_well; + bool is_enabled; + + if (dev_priv->runtime_pm.suspended) + return false; + + is_enabled = true; + + for_each_power_domain_well_reverse(dev_priv, power_well, domain) { + if (intel_power_well_is_always_on(power_well)) + continue; + + if (!intel_power_well_is_enabled_cached(power_well)) { + is_enabled = false; + break; + } + } + + return is_enabled; +} + +/** + * intel_display_power_is_enabled - check for a power domain + * @dev_priv: i915 device instance + * @domain: power domain to check + * + * This function can be used to check the hw power domain state. It is mostly + * used in hardware state readout functions. Everywhere else code should rely + * upon explicit power domain reference counting to ensure that the hardware + * block is powered up before accessing it. + * + * Callers must hold the relevant modesetting locks to ensure that concurrent + * threads can't disable the power well while the caller tries to read a few + * registers. + * + * Returns: + * True when the power domain is enabled, false otherwise. + */ +bool intel_display_power_is_enabled(struct drm_i915_private *dev_priv, + enum intel_display_power_domain domain) +{ + struct i915_power_domains *power_domains; + bool ret; + + power_domains = &dev_priv->display.power.domains; + + mutex_lock(&power_domains->lock); + ret = __intel_display_power_is_enabled(dev_priv, domain); + mutex_unlock(&power_domains->lock); + + return ret; +} + +static u32 +sanitize_target_dc_state(struct drm_i915_private *dev_priv, + u32 target_dc_state) +{ + static const u32 states[] = { + DC_STATE_EN_UPTO_DC6, + DC_STATE_EN_UPTO_DC5, + DC_STATE_EN_DC3CO, + DC_STATE_DISABLE, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(states) - 1; i++) { + if (target_dc_state != states[i]) + continue; + + if (dev_priv->display.dmc.allowed_dc_mask & target_dc_state) + break; + + target_dc_state = states[i + 1]; + } + + return target_dc_state; +} + +/** + * intel_display_power_set_target_dc_state - Set target dc state. + * @dev_priv: i915 device + * @state: state which needs to be set as target_dc_state. + * + * This function set the "DC off" power well target_dc_state, + * based upon this target_dc_stste, "DC off" power well will + * enable desired DC state. + */ +void intel_display_power_set_target_dc_state(struct drm_i915_private *dev_priv, + u32 state) +{ + struct i915_power_well *power_well; + bool dc_off_enabled; + struct i915_power_domains *power_domains = &dev_priv->display.power.domains; + + mutex_lock(&power_domains->lock); + power_well = lookup_power_well(dev_priv, SKL_DISP_DC_OFF); + + if (drm_WARN_ON(&dev_priv->drm, !power_well)) + goto unlock; + + state = sanitize_target_dc_state(dev_priv, state); + + if (state == dev_priv->display.dmc.target_dc_state) + goto unlock; + + dc_off_enabled = intel_power_well_is_enabled(dev_priv, power_well); + /* + * If DC off power well is disabled, need to enable and disable the + * DC off power well to effect target DC state. + */ + if (!dc_off_enabled) + intel_power_well_enable(dev_priv, power_well); + + dev_priv->display.dmc.target_dc_state = state; + + if (!dc_off_enabled) + intel_power_well_disable(dev_priv, power_well); + +unlock: + mutex_unlock(&power_domains->lock); +} + +#define POWER_DOMAIN_MASK (GENMASK_ULL(POWER_DOMAIN_NUM - 1, 0)) + +static void __async_put_domains_mask(struct i915_power_domains *power_domains, + struct intel_power_domain_mask *mask) +{ + bitmap_or(mask->bits, + power_domains->async_put_domains[0].bits, + power_domains->async_put_domains[1].bits, + POWER_DOMAIN_NUM); +} + +#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_RUNTIME_PM) + +static bool +assert_async_put_domain_masks_disjoint(struct i915_power_domains *power_domains) +{ + struct drm_i915_private *i915 = container_of(power_domains, + struct drm_i915_private, + display.power.domains); + + return !drm_WARN_ON(&i915->drm, + bitmap_intersects(power_domains->async_put_domains[0].bits, + power_domains->async_put_domains[1].bits, + POWER_DOMAIN_NUM)); +} + +static bool +__async_put_domains_state_ok(struct i915_power_domains *power_domains) +{ + struct drm_i915_private *i915 = container_of(power_domains, + struct drm_i915_private, + display.power.domains); + struct intel_power_domain_mask async_put_mask; + enum intel_display_power_domain domain; + bool err = false; + + err |= !assert_async_put_domain_masks_disjoint(power_domains); + __async_put_domains_mask(power_domains, &async_put_mask); + err |= drm_WARN_ON(&i915->drm, + !!power_domains->async_put_wakeref != + !bitmap_empty(async_put_mask.bits, POWER_DOMAIN_NUM)); + + for_each_power_domain(domain, &async_put_mask) + err |= drm_WARN_ON(&i915->drm, + power_domains->domain_use_count[domain] != 1); + + return !err; +} + +static void print_power_domains(struct i915_power_domains *power_domains, + const char *prefix, struct intel_power_domain_mask *mask) +{ + struct drm_i915_private *i915 = container_of(power_domains, + struct drm_i915_private, + display.power.domains); + enum intel_display_power_domain domain; + + drm_dbg(&i915->drm, "%s (%d):\n", prefix, bitmap_weight(mask->bits, POWER_DOMAIN_NUM)); + for_each_power_domain(domain, mask) + drm_dbg(&i915->drm, "%s use_count %d\n", + intel_display_power_domain_str(domain), + power_domains->domain_use_count[domain]); +} + +static void +print_async_put_domains_state(struct i915_power_domains *power_domains) +{ + struct drm_i915_private *i915 = container_of(power_domains, + struct drm_i915_private, + display.power.domains); + + drm_dbg(&i915->drm, "async_put_wakeref %u\n", + power_domains->async_put_wakeref); + + print_power_domains(power_domains, "async_put_domains[0]", + &power_domains->async_put_domains[0]); + print_power_domains(power_domains, "async_put_domains[1]", + &power_domains->async_put_domains[1]); +} + +static void +verify_async_put_domains_state(struct i915_power_domains *power_domains) +{ + if (!__async_put_domains_state_ok(power_domains)) + print_async_put_domains_state(power_domains); +} + +#else + +static void +assert_async_put_domain_masks_disjoint(struct i915_power_domains *power_domains) +{ +} + +static void +verify_async_put_domains_state(struct i915_power_domains *power_domains) +{ +} + +#endif /* CONFIG_DRM_I915_DEBUG_RUNTIME_PM */ + +static void async_put_domains_mask(struct i915_power_domains *power_domains, + struct intel_power_domain_mask *mask) + +{ + assert_async_put_domain_masks_disjoint(power_domains); + + __async_put_domains_mask(power_domains, mask); +} + +static void +async_put_domains_clear_domain(struct i915_power_domains *power_domains, + enum intel_display_power_domain domain) +{ + assert_async_put_domain_masks_disjoint(power_domains); + + clear_bit(domain, power_domains->async_put_domains[0].bits); + clear_bit(domain, power_domains->async_put_domains[1].bits); +} + +static bool +intel_display_power_grab_async_put_ref(struct drm_i915_private *dev_priv, + enum intel_display_power_domain domain) +{ + struct i915_power_domains *power_domains = &dev_priv->display.power.domains; + struct intel_power_domain_mask async_put_mask; + bool ret = false; + + async_put_domains_mask(power_domains, &async_put_mask); + if (!test_bit(domain, async_put_mask.bits)) + goto out_verify; + + async_put_domains_clear_domain(power_domains, domain); + + ret = true; + + async_put_domains_mask(power_domains, &async_put_mask); + if (!bitmap_empty(async_put_mask.bits, POWER_DOMAIN_NUM)) + goto out_verify; + + cancel_delayed_work(&power_domains->async_put_work); + intel_runtime_pm_put_raw(&dev_priv->runtime_pm, + fetch_and_zero(&power_domains->async_put_wakeref)); +out_verify: + verify_async_put_domains_state(power_domains); + + return ret; +} + +static void +__intel_display_power_get_domain(struct drm_i915_private *dev_priv, + enum intel_display_power_domain domain) +{ + struct i915_power_domains *power_domains = &dev_priv->display.power.domains; + struct i915_power_well *power_well; + + if (intel_display_power_grab_async_put_ref(dev_priv, domain)) + return; + + for_each_power_domain_well(dev_priv, power_well, domain) + intel_power_well_get(dev_priv, power_well); + + power_domains->domain_use_count[domain]++; +} + +/** + * intel_display_power_get - grab a power domain reference + * @dev_priv: i915 device instance + * @domain: power domain to reference + * + * This function grabs a power domain reference for @domain and ensures that the + * power domain and all its parents are powered up. Therefore users should only + * grab a reference to the innermost power domain they need. + * + * Any power domain reference obtained by this function must have a symmetric + * call to intel_display_power_put() to release the reference again. + */ +intel_wakeref_t intel_display_power_get(struct drm_i915_private *dev_priv, + enum intel_display_power_domain domain) +{ + struct i915_power_domains *power_domains = &dev_priv->display.power.domains; + intel_wakeref_t wakeref = intel_runtime_pm_get(&dev_priv->runtime_pm); + + mutex_lock(&power_domains->lock); + __intel_display_power_get_domain(dev_priv, domain); + mutex_unlock(&power_domains->lock); + + return wakeref; +} + +/** + * intel_display_power_get_if_enabled - grab a reference for an enabled display power domain + * @dev_priv: i915 device instance + * @domain: power domain to reference + * + * This function grabs a power domain reference for @domain and ensures that the + * power domain and all its parents are powered up. Therefore users should only + * grab a reference to the innermost power domain they need. + * + * Any power domain reference obtained by this function must have a symmetric + * call to intel_display_power_put() to release the reference again. + */ +intel_wakeref_t +intel_display_power_get_if_enabled(struct drm_i915_private *dev_priv, + enum intel_display_power_domain domain) +{ + struct i915_power_domains *power_domains = &dev_priv->display.power.domains; + intel_wakeref_t wakeref; + bool is_enabled; + + wakeref = intel_runtime_pm_get_if_in_use(&dev_priv->runtime_pm); + if (!wakeref) + return false; + + mutex_lock(&power_domains->lock); + + if (__intel_display_power_is_enabled(dev_priv, domain)) { + __intel_display_power_get_domain(dev_priv, domain); + is_enabled = true; + } else { + is_enabled = false; + } + + mutex_unlock(&power_domains->lock); + + if (!is_enabled) { + intel_runtime_pm_put(&dev_priv->runtime_pm, wakeref); + wakeref = 0; + } + + return wakeref; +} + +static void +__intel_display_power_put_domain(struct drm_i915_private *dev_priv, + enum intel_display_power_domain domain) +{ + struct i915_power_domains *power_domains; + struct i915_power_well *power_well; + const char *name = intel_display_power_domain_str(domain); + struct intel_power_domain_mask async_put_mask; + + power_domains = &dev_priv->display.power.domains; + + drm_WARN(&dev_priv->drm, !power_domains->domain_use_count[domain], + "Use count on domain %s is already zero\n", + name); + async_put_domains_mask(power_domains, &async_put_mask); + drm_WARN(&dev_priv->drm, + test_bit(domain, async_put_mask.bits), + "Async disabling of domain %s is pending\n", + name); + + power_domains->domain_use_count[domain]--; + + for_each_power_domain_well_reverse(dev_priv, power_well, domain) + intel_power_well_put(dev_priv, power_well); +} + +static void __intel_display_power_put(struct drm_i915_private *dev_priv, + enum intel_display_power_domain domain) +{ + struct i915_power_domains *power_domains = &dev_priv->display.power.domains; + + mutex_lock(&power_domains->lock); + __intel_display_power_put_domain(dev_priv, domain); + mutex_unlock(&power_domains->lock); +} + +static void +queue_async_put_domains_work(struct i915_power_domains *power_domains, + intel_wakeref_t wakeref) +{ + struct drm_i915_private *i915 = container_of(power_domains, + struct drm_i915_private, + display.power.domains); + drm_WARN_ON(&i915->drm, power_domains->async_put_wakeref); + power_domains->async_put_wakeref = wakeref; + drm_WARN_ON(&i915->drm, !queue_delayed_work(system_unbound_wq, + &power_domains->async_put_work, + msecs_to_jiffies(100))); +} + +static void +release_async_put_domains(struct i915_power_domains *power_domains, + struct intel_power_domain_mask *mask) +{ + struct drm_i915_private *dev_priv = + container_of(power_domains, struct drm_i915_private, + display.power.domains); + struct intel_runtime_pm *rpm = &dev_priv->runtime_pm; + enum intel_display_power_domain domain; + intel_wakeref_t wakeref; + + /* + * The caller must hold already raw wakeref, upgrade that to a proper + * wakeref to make the state checker happy about the HW access during + * power well disabling. + */ + assert_rpm_raw_wakeref_held(rpm); + wakeref = intel_runtime_pm_get(rpm); + + for_each_power_domain(domain, mask) { + /* Clear before put, so put's sanity check is happy. */ + async_put_domains_clear_domain(power_domains, domain); + __intel_display_power_put_domain(dev_priv, domain); + } + + intel_runtime_pm_put(rpm, wakeref); +} + +static void +intel_display_power_put_async_work(struct work_struct *work) +{ + struct drm_i915_private *dev_priv = + container_of(work, struct drm_i915_private, + display.power.domains.async_put_work.work); + struct i915_power_domains *power_domains = &dev_priv->display.power.domains; + struct intel_runtime_pm *rpm = &dev_priv->runtime_pm; + intel_wakeref_t new_work_wakeref = intel_runtime_pm_get_raw(rpm); + intel_wakeref_t old_work_wakeref = 0; + + mutex_lock(&power_domains->lock); + + /* + * Bail out if all the domain refs pending to be released were grabbed + * by subsequent gets or a flush_work. + */ + old_work_wakeref = fetch_and_zero(&power_domains->async_put_wakeref); + if (!old_work_wakeref) + goto out_verify; + + release_async_put_domains(power_domains, + &power_domains->async_put_domains[0]); + + /* Requeue the work if more domains were async put meanwhile. */ + if (!bitmap_empty(power_domains->async_put_domains[1].bits, POWER_DOMAIN_NUM)) { + bitmap_copy(power_domains->async_put_domains[0].bits, + power_domains->async_put_domains[1].bits, + POWER_DOMAIN_NUM); + bitmap_zero(power_domains->async_put_domains[1].bits, + POWER_DOMAIN_NUM); + queue_async_put_domains_work(power_domains, + fetch_and_zero(&new_work_wakeref)); + } else { + /* + * Cancel the work that got queued after this one got dequeued, + * since here we released the corresponding async-put reference. + */ + cancel_delayed_work(&power_domains->async_put_work); + } + +out_verify: + verify_async_put_domains_state(power_domains); + + mutex_unlock(&power_domains->lock); + + if (old_work_wakeref) + intel_runtime_pm_put_raw(rpm, old_work_wakeref); + if (new_work_wakeref) + intel_runtime_pm_put_raw(rpm, new_work_wakeref); +} + +/** + * intel_display_power_put_async - release a power domain reference asynchronously + * @i915: i915 device instance + * @domain: power domain to reference + * @wakeref: wakeref acquired for the reference that is being released + * + * This function drops the power domain reference obtained by + * intel_display_power_get*() and schedules a work to power down the + * corresponding hardware block if this is the last reference. + */ +void __intel_display_power_put_async(struct drm_i915_private *i915, + enum intel_display_power_domain domain, + intel_wakeref_t wakeref) +{ + struct i915_power_domains *power_domains = &i915->display.power.domains; + struct intel_runtime_pm *rpm = &i915->runtime_pm; + intel_wakeref_t work_wakeref = intel_runtime_pm_get_raw(rpm); + + mutex_lock(&power_domains->lock); + + if (power_domains->domain_use_count[domain] > 1) { + __intel_display_power_put_domain(i915, domain); + + goto out_verify; + } + + drm_WARN_ON(&i915->drm, power_domains->domain_use_count[domain] != 1); + + /* Let a pending work requeue itself or queue a new one. */ + if (power_domains->async_put_wakeref) { + set_bit(domain, power_domains->async_put_domains[1].bits); + } else { + set_bit(domain, power_domains->async_put_domains[0].bits); + queue_async_put_domains_work(power_domains, + fetch_and_zero(&work_wakeref)); + } + +out_verify: + verify_async_put_domains_state(power_domains); + + mutex_unlock(&power_domains->lock); + + if (work_wakeref) + intel_runtime_pm_put_raw(rpm, work_wakeref); + + intel_runtime_pm_put(rpm, wakeref); +} + +/** + * intel_display_power_flush_work - flushes the async display power disabling work + * @i915: i915 device instance + * + * Flushes any pending work that was scheduled by a preceding + * intel_display_power_put_async() call, completing the disabling of the + * corresponding power domains. + * + * Note that the work handler function may still be running after this + * function returns; to ensure that the work handler isn't running use + * intel_display_power_flush_work_sync() instead. + */ +void intel_display_power_flush_work(struct drm_i915_private *i915) +{ + struct i915_power_domains *power_domains = &i915->display.power.domains; + struct intel_power_domain_mask async_put_mask; + intel_wakeref_t work_wakeref; + + mutex_lock(&power_domains->lock); + + work_wakeref = fetch_and_zero(&power_domains->async_put_wakeref); + if (!work_wakeref) + goto out_verify; + + async_put_domains_mask(power_domains, &async_put_mask); + release_async_put_domains(power_domains, &async_put_mask); + cancel_delayed_work(&power_domains->async_put_work); + +out_verify: + verify_async_put_domains_state(power_domains); + + mutex_unlock(&power_domains->lock); + + if (work_wakeref) + intel_runtime_pm_put_raw(&i915->runtime_pm, work_wakeref); +} + +/** + * intel_display_power_flush_work_sync - flushes and syncs the async display power disabling work + * @i915: i915 device instance + * + * Like intel_display_power_flush_work(), but also ensure that the work + * handler function is not running any more when this function returns. + */ +static void +intel_display_power_flush_work_sync(struct drm_i915_private *i915) +{ + struct i915_power_domains *power_domains = &i915->display.power.domains; + + intel_display_power_flush_work(i915); + cancel_delayed_work_sync(&power_domains->async_put_work); + + verify_async_put_domains_state(power_domains); + + drm_WARN_ON(&i915->drm, power_domains->async_put_wakeref); +} + +#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_RUNTIME_PM) +/** + * intel_display_power_put - release a power domain reference + * @dev_priv: i915 device instance + * @domain: power domain to reference + * @wakeref: wakeref acquired for the reference that is being released + * + * This function drops the power domain reference obtained by + * intel_display_power_get() and might power down the corresponding hardware + * block right away if this is the last reference. + */ +void intel_display_power_put(struct drm_i915_private *dev_priv, + enum intel_display_power_domain domain, + intel_wakeref_t wakeref) +{ + __intel_display_power_put(dev_priv, domain); + intel_runtime_pm_put(&dev_priv->runtime_pm, wakeref); +} +#else +/** + * intel_display_power_put_unchecked - release an unchecked power domain reference + * @dev_priv: i915 device instance + * @domain: power domain to reference + * + * This function drops the power domain reference obtained by + * intel_display_power_get() and might power down the corresponding hardware + * block right away if this is the last reference. + * + * This function is only for the power domain code's internal use to suppress wakeref + * tracking when the correspondig debug kconfig option is disabled, should not + * be used otherwise. + */ +void intel_display_power_put_unchecked(struct drm_i915_private *dev_priv, + enum intel_display_power_domain domain) +{ + __intel_display_power_put(dev_priv, domain); + intel_runtime_pm_put_unchecked(&dev_priv->runtime_pm); +} +#endif + +void +intel_display_power_get_in_set(struct drm_i915_private *i915, + struct intel_display_power_domain_set *power_domain_set, + enum intel_display_power_domain domain) +{ + intel_wakeref_t __maybe_unused wf; + + drm_WARN_ON(&i915->drm, test_bit(domain, power_domain_set->mask.bits)); + + wf = intel_display_power_get(i915, domain); +#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_RUNTIME_PM) + power_domain_set->wakerefs[domain] = wf; +#endif + set_bit(domain, power_domain_set->mask.bits); +} + +bool +intel_display_power_get_in_set_if_enabled(struct drm_i915_private *i915, + struct intel_display_power_domain_set *power_domain_set, + enum intel_display_power_domain domain) +{ + intel_wakeref_t wf; + + drm_WARN_ON(&i915->drm, test_bit(domain, power_domain_set->mask.bits)); + + wf = intel_display_power_get_if_enabled(i915, domain); + if (!wf) + return false; + +#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_RUNTIME_PM) + power_domain_set->wakerefs[domain] = wf; +#endif + set_bit(domain, power_domain_set->mask.bits); + + return true; +} + +void +intel_display_power_put_mask_in_set(struct drm_i915_private *i915, + struct intel_display_power_domain_set *power_domain_set, + struct intel_power_domain_mask *mask) +{ + enum intel_display_power_domain domain; + + drm_WARN_ON(&i915->drm, + !bitmap_subset(mask->bits, power_domain_set->mask.bits, POWER_DOMAIN_NUM)); + + for_each_power_domain(domain, mask) { + intel_wakeref_t __maybe_unused wf = -1; + +#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_RUNTIME_PM) + wf = fetch_and_zero(&power_domain_set->wakerefs[domain]); +#endif + intel_display_power_put(i915, domain, wf); + clear_bit(domain, power_domain_set->mask.bits); + } +} + +static int +sanitize_disable_power_well_option(const struct drm_i915_private *dev_priv, + int disable_power_well) +{ + if (disable_power_well >= 0) + return !!disable_power_well; + + return 1; +} + +static u32 get_allowed_dc_mask(const struct drm_i915_private *dev_priv, + int enable_dc) +{ + u32 mask; + int requested_dc; + int max_dc; + + if (!HAS_DISPLAY(dev_priv)) + return 0; + + if (IS_DG2(dev_priv)) + max_dc = 1; + else if (IS_DG1(dev_priv)) + max_dc = 3; + else if (DISPLAY_VER(dev_priv) >= 12) + max_dc = 4; + else if (IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv)) + max_dc = 1; + else if (DISPLAY_VER(dev_priv) >= 9) + max_dc = 2; + else + max_dc = 0; + + /* + * DC9 has a separate HW flow from the rest of the DC states, + * not depending on the DMC firmware. It's needed by system + * suspend/resume, so allow it unconditionally. + */ + mask = IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv) || + DISPLAY_VER(dev_priv) >= 11 ? + DC_STATE_EN_DC9 : 0; + + if (!dev_priv->params.disable_power_well) + max_dc = 0; + + if (enable_dc >= 0 && enable_dc <= max_dc) { + requested_dc = enable_dc; + } else if (enable_dc == -1) { + requested_dc = max_dc; + } else if (enable_dc > max_dc && enable_dc <= 4) { + drm_dbg_kms(&dev_priv->drm, + "Adjusting requested max DC state (%d->%d)\n", + enable_dc, max_dc); + requested_dc = max_dc; + } else { + drm_err(&dev_priv->drm, + "Unexpected value for enable_dc (%d)\n", enable_dc); + requested_dc = max_dc; + } + + switch (requested_dc) { + case 4: + mask |= DC_STATE_EN_DC3CO | DC_STATE_EN_UPTO_DC6; + break; + case 3: + mask |= DC_STATE_EN_DC3CO | DC_STATE_EN_UPTO_DC5; + break; + case 2: + mask |= DC_STATE_EN_UPTO_DC6; + break; + case 1: + mask |= DC_STATE_EN_UPTO_DC5; + break; + } + + drm_dbg_kms(&dev_priv->drm, "Allowed DC state mask %02x\n", mask); + + return mask; +} + +/** + * intel_power_domains_init - initializes the power domain structures + * @dev_priv: i915 device instance + * + * Initializes the power domain structures for @dev_priv depending upon the + * supported platform. + */ +int intel_power_domains_init(struct drm_i915_private *dev_priv) +{ + struct i915_power_domains *power_domains = &dev_priv->display.power.domains; + + dev_priv->params.disable_power_well = + sanitize_disable_power_well_option(dev_priv, + dev_priv->params.disable_power_well); + dev_priv->display.dmc.allowed_dc_mask = + get_allowed_dc_mask(dev_priv, dev_priv->params.enable_dc); + + dev_priv->display.dmc.target_dc_state = + sanitize_target_dc_state(dev_priv, DC_STATE_EN_UPTO_DC6); + + mutex_init(&power_domains->lock); + + INIT_DELAYED_WORK(&power_domains->async_put_work, + intel_display_power_put_async_work); + + return intel_display_power_map_init(power_domains); +} + +/** + * intel_power_domains_cleanup - clean up power domains resources + * @dev_priv: i915 device instance + * + * Release any resources acquired by intel_power_domains_init() + */ +void intel_power_domains_cleanup(struct drm_i915_private *dev_priv) +{ + intel_display_power_map_cleanup(&dev_priv->display.power.domains); +} + +static void intel_power_domains_sync_hw(struct drm_i915_private *dev_priv) +{ + struct i915_power_domains *power_domains = &dev_priv->display.power.domains; + struct i915_power_well *power_well; + + mutex_lock(&power_domains->lock); + for_each_power_well(dev_priv, power_well) + intel_power_well_sync_hw(dev_priv, power_well); + mutex_unlock(&power_domains->lock); +} + +static void gen9_dbuf_slice_set(struct drm_i915_private *dev_priv, + enum dbuf_slice slice, bool enable) +{ + i915_reg_t reg = DBUF_CTL_S(slice); + bool state; + + intel_de_rmw(dev_priv, reg, DBUF_POWER_REQUEST, + enable ? DBUF_POWER_REQUEST : 0); + intel_de_posting_read(dev_priv, reg); + udelay(10); + + state = intel_de_read(dev_priv, reg) & DBUF_POWER_STATE; + drm_WARN(&dev_priv->drm, enable != state, + "DBuf slice %d power %s timeout!\n", + slice, str_enable_disable(enable)); +} + +void gen9_dbuf_slices_update(struct drm_i915_private *dev_priv, + u8 req_slices) +{ + struct i915_power_domains *power_domains = &dev_priv->display.power.domains; + u8 slice_mask = INTEL_INFO(dev_priv)->display.dbuf.slice_mask; + enum dbuf_slice slice; + + drm_WARN(&dev_priv->drm, req_slices & ~slice_mask, + "Invalid set of dbuf slices (0x%x) requested (total dbuf slices 0x%x)\n", + req_slices, slice_mask); + + drm_dbg_kms(&dev_priv->drm, "Updating dbuf slices to 0x%x\n", + req_slices); + + /* + * Might be running this in parallel to gen9_dc_off_power_well_enable + * being called from intel_dp_detect for instance, + * which causes assertion triggered by race condition, + * as gen9_assert_dbuf_enabled might preempt this when registers + * were already updated, while dev_priv was not. + */ + mutex_lock(&power_domains->lock); + + for_each_dbuf_slice(dev_priv, slice) + gen9_dbuf_slice_set(dev_priv, slice, req_slices & BIT(slice)); + + dev_priv->display.dbuf.enabled_slices = req_slices; + + mutex_unlock(&power_domains->lock); +} + +static void gen9_dbuf_enable(struct drm_i915_private *dev_priv) +{ + dev_priv->display.dbuf.enabled_slices = + intel_enabled_dbuf_slices_mask(dev_priv); + + /* + * Just power up at least 1 slice, we will + * figure out later which slices we have and what we need. + */ + gen9_dbuf_slices_update(dev_priv, BIT(DBUF_S1) | + dev_priv->display.dbuf.enabled_slices); +} + +static void gen9_dbuf_disable(struct drm_i915_private *dev_priv) +{ + gen9_dbuf_slices_update(dev_priv, 0); +} + +static void gen12_dbuf_slices_config(struct drm_i915_private *dev_priv) +{ + enum dbuf_slice slice; + + if (IS_ALDERLAKE_P(dev_priv)) + return; + + for_each_dbuf_slice(dev_priv, slice) + intel_de_rmw(dev_priv, DBUF_CTL_S(slice), + DBUF_TRACKER_STATE_SERVICE_MASK, + DBUF_TRACKER_STATE_SERVICE(8)); +} + +static void icl_mbus_init(struct drm_i915_private *dev_priv) +{ + unsigned long abox_regs = INTEL_INFO(dev_priv)->display.abox_mask; + u32 mask, val, i; + + if (IS_ALDERLAKE_P(dev_priv) || DISPLAY_VER(dev_priv) >= 14) + return; + + mask = MBUS_ABOX_BT_CREDIT_POOL1_MASK | + MBUS_ABOX_BT_CREDIT_POOL2_MASK | + MBUS_ABOX_B_CREDIT_MASK | + MBUS_ABOX_BW_CREDIT_MASK; + val = MBUS_ABOX_BT_CREDIT_POOL1(16) | + MBUS_ABOX_BT_CREDIT_POOL2(16) | + MBUS_ABOX_B_CREDIT(1) | + MBUS_ABOX_BW_CREDIT(1); + + /* + * gen12 platforms that use abox1 and abox2 for pixel data reads still + * expect us to program the abox_ctl0 register as well, even though + * we don't have to program other instance-0 registers like BW_BUDDY. + */ + if (DISPLAY_VER(dev_priv) == 12) + abox_regs |= BIT(0); + + for_each_set_bit(i, &abox_regs, sizeof(abox_regs)) + intel_de_rmw(dev_priv, MBUS_ABOX_CTL(i), mask, val); +} + +static void hsw_assert_cdclk(struct drm_i915_private *dev_priv) +{ + u32 val = intel_de_read(dev_priv, LCPLL_CTL); + + /* + * The LCPLL register should be turned on by the BIOS. For now + * let's just check its state and print errors in case + * something is wrong. Don't even try to turn it on. + */ + + if (val & LCPLL_CD_SOURCE_FCLK) + drm_err(&dev_priv->drm, "CDCLK source is not LCPLL\n"); + + if (val & LCPLL_PLL_DISABLE) + drm_err(&dev_priv->drm, "LCPLL is disabled\n"); + + if ((val & LCPLL_REF_MASK) != LCPLL_REF_NON_SSC) + drm_err(&dev_priv->drm, "LCPLL not using non-SSC reference\n"); +} + +static void assert_can_disable_lcpll(struct drm_i915_private *dev_priv) +{ + struct drm_device *dev = &dev_priv->drm; + struct intel_crtc *crtc; + + for_each_intel_crtc(dev, crtc) + I915_STATE_WARN(crtc->active, "CRTC for pipe %c enabled\n", + pipe_name(crtc->pipe)); + + I915_STATE_WARN(intel_de_read(dev_priv, HSW_PWR_WELL_CTL2), + "Display power well on\n"); + I915_STATE_WARN(intel_de_read(dev_priv, SPLL_CTL) & SPLL_PLL_ENABLE, + "SPLL enabled\n"); + I915_STATE_WARN(intel_de_read(dev_priv, WRPLL_CTL(0)) & WRPLL_PLL_ENABLE, + "WRPLL1 enabled\n"); + I915_STATE_WARN(intel_de_read(dev_priv, WRPLL_CTL(1)) & WRPLL_PLL_ENABLE, + "WRPLL2 enabled\n"); + I915_STATE_WARN(intel_de_read(dev_priv, PP_STATUS(0)) & PP_ON, + "Panel power on\n"); + I915_STATE_WARN(intel_de_read(dev_priv, BLC_PWM_CPU_CTL2) & BLM_PWM_ENABLE, + "CPU PWM1 enabled\n"); + if (IS_HASWELL(dev_priv)) + I915_STATE_WARN(intel_de_read(dev_priv, HSW_BLC_PWM2_CTL) & BLM_PWM_ENABLE, + "CPU PWM2 enabled\n"); + I915_STATE_WARN(intel_de_read(dev_priv, BLC_PWM_PCH_CTL1) & BLM_PCH_PWM_ENABLE, + "PCH PWM1 enabled\n"); + I915_STATE_WARN(intel_de_read(dev_priv, UTIL_PIN_CTL) & UTIL_PIN_ENABLE, + "Utility pin enabled\n"); + I915_STATE_WARN(intel_de_read(dev_priv, PCH_GTC_CTL) & PCH_GTC_ENABLE, + "PCH GTC enabled\n"); + + /* + * In theory we can still leave IRQs enabled, as long as only the HPD + * interrupts remain enabled. We used to check for that, but since it's + * gen-specific and since we only disable LCPLL after we fully disable + * the interrupts, the check below should be enough. + */ + I915_STATE_WARN(intel_irqs_enabled(dev_priv), "IRQs enabled\n"); +} + +static u32 hsw_read_dcomp(struct drm_i915_private *dev_priv) +{ + if (IS_HASWELL(dev_priv)) + return intel_de_read(dev_priv, D_COMP_HSW); + else + return intel_de_read(dev_priv, D_COMP_BDW); +} + +static void hsw_write_dcomp(struct drm_i915_private *dev_priv, u32 val) +{ + if (IS_HASWELL(dev_priv)) { + if (snb_pcode_write(&dev_priv->uncore, GEN6_PCODE_WRITE_D_COMP, val)) + drm_dbg_kms(&dev_priv->drm, + "Failed to write to D_COMP\n"); + } else { + intel_de_write(dev_priv, D_COMP_BDW, val); + intel_de_posting_read(dev_priv, D_COMP_BDW); + } +} + +/* + * This function implements pieces of two sequences from BSpec: + * - Sequence for display software to disable LCPLL + * - Sequence for display software to allow package C8+ + * The steps implemented here are just the steps that actually touch the LCPLL + * register. Callers should take care of disabling all the display engine + * functions, doing the mode unset, fixing interrupts, etc. + */ +static void hsw_disable_lcpll(struct drm_i915_private *dev_priv, + bool switch_to_fclk, bool allow_power_down) +{ + u32 val; + + assert_can_disable_lcpll(dev_priv); + + val = intel_de_read(dev_priv, LCPLL_CTL); + + if (switch_to_fclk) { + val |= LCPLL_CD_SOURCE_FCLK; + intel_de_write(dev_priv, LCPLL_CTL, val); + + if (wait_for_us(intel_de_read(dev_priv, LCPLL_CTL) & + LCPLL_CD_SOURCE_FCLK_DONE, 1)) + drm_err(&dev_priv->drm, "Switching to FCLK failed\n"); + + val = intel_de_read(dev_priv, LCPLL_CTL); + } + + val |= LCPLL_PLL_DISABLE; + intel_de_write(dev_priv, LCPLL_CTL, val); + intel_de_posting_read(dev_priv, LCPLL_CTL); + + if (intel_de_wait_for_clear(dev_priv, LCPLL_CTL, LCPLL_PLL_LOCK, 1)) + drm_err(&dev_priv->drm, "LCPLL still locked\n"); + + val = hsw_read_dcomp(dev_priv); + val |= D_COMP_COMP_DISABLE; + hsw_write_dcomp(dev_priv, val); + ndelay(100); + + if (wait_for((hsw_read_dcomp(dev_priv) & + D_COMP_RCOMP_IN_PROGRESS) == 0, 1)) + drm_err(&dev_priv->drm, "D_COMP RCOMP still in progress\n"); + + if (allow_power_down) { + val = intel_de_read(dev_priv, LCPLL_CTL); + val |= LCPLL_POWER_DOWN_ALLOW; + intel_de_write(dev_priv, LCPLL_CTL, val); + intel_de_posting_read(dev_priv, LCPLL_CTL); + } +} + +/* + * Fully restores LCPLL, disallowing power down and switching back to LCPLL + * source. + */ +static void hsw_restore_lcpll(struct drm_i915_private *dev_priv) +{ + u32 val; + + val = intel_de_read(dev_priv, LCPLL_CTL); + + if ((val & (LCPLL_PLL_LOCK | LCPLL_PLL_DISABLE | LCPLL_CD_SOURCE_FCLK | + LCPLL_POWER_DOWN_ALLOW)) == LCPLL_PLL_LOCK) + return; + + /* + * Make sure we're not on PC8 state before disabling PC8, otherwise + * we'll hang the machine. To prevent PC8 state, just enable force_wake. + */ + intel_uncore_forcewake_get(&dev_priv->uncore, FORCEWAKE_ALL); + + if (val & LCPLL_POWER_DOWN_ALLOW) { + val &= ~LCPLL_POWER_DOWN_ALLOW; + intel_de_write(dev_priv, LCPLL_CTL, val); + intel_de_posting_read(dev_priv, LCPLL_CTL); + } + + val = hsw_read_dcomp(dev_priv); + val |= D_COMP_COMP_FORCE; + val &= ~D_COMP_COMP_DISABLE; + hsw_write_dcomp(dev_priv, val); + + val = intel_de_read(dev_priv, LCPLL_CTL); + val &= ~LCPLL_PLL_DISABLE; + intel_de_write(dev_priv, LCPLL_CTL, val); + + if (intel_de_wait_for_set(dev_priv, LCPLL_CTL, LCPLL_PLL_LOCK, 5)) + drm_err(&dev_priv->drm, "LCPLL not locked yet\n"); + + if (val & LCPLL_CD_SOURCE_FCLK) { + val = intel_de_read(dev_priv, LCPLL_CTL); + val &= ~LCPLL_CD_SOURCE_FCLK; + intel_de_write(dev_priv, LCPLL_CTL, val); + + if (wait_for_us((intel_de_read(dev_priv, LCPLL_CTL) & + LCPLL_CD_SOURCE_FCLK_DONE) == 0, 1)) + drm_err(&dev_priv->drm, + "Switching back to LCPLL failed\n"); + } + + intel_uncore_forcewake_put(&dev_priv->uncore, FORCEWAKE_ALL); + + intel_update_cdclk(dev_priv); + intel_cdclk_dump_config(dev_priv, &dev_priv->display.cdclk.hw, "Current CDCLK"); +} + +/* + * Package states C8 and deeper are really deep PC states that can only be + * reached when all the devices on the system allow it, so even if the graphics + * device allows PC8+, it doesn't mean the system will actually get to these + * states. Our driver only allows PC8+ when going into runtime PM. + * + * The requirements for PC8+ are that all the outputs are disabled, the power + * well is disabled and most interrupts are disabled, and these are also + * requirements for runtime PM. When these conditions are met, we manually do + * the other conditions: disable the interrupts, clocks and switch LCPLL refclk + * to Fclk. If we're in PC8+ and we get an non-hotplug interrupt, we can hard + * hang the machine. + * + * When we really reach PC8 or deeper states (not just when we allow it) we lose + * the state of some registers, so when we come back from PC8+ we need to + * restore this state. We don't get into PC8+ if we're not in RC6, so we don't + * need to take care of the registers kept by RC6. Notice that this happens even + * if we don't put the device in PCI D3 state (which is what currently happens + * because of the runtime PM support). + * + * For more, read "Display Sequences for Package C8" on the hardware + * documentation. + */ +static void hsw_enable_pc8(struct drm_i915_private *dev_priv) +{ + u32 val; + + drm_dbg_kms(&dev_priv->drm, "Enabling package C8+\n"); + + if (HAS_PCH_LPT_LP(dev_priv)) { + val = intel_de_read(dev_priv, SOUTH_DSPCLK_GATE_D); + val &= ~PCH_LP_PARTITION_LEVEL_DISABLE; + intel_de_write(dev_priv, SOUTH_DSPCLK_GATE_D, val); + } + + lpt_disable_clkout_dp(dev_priv); + hsw_disable_lcpll(dev_priv, true, true); +} + +static void hsw_disable_pc8(struct drm_i915_private *dev_priv) +{ + u32 val; + + drm_dbg_kms(&dev_priv->drm, "Disabling package C8+\n"); + + hsw_restore_lcpll(dev_priv); + intel_init_pch_refclk(dev_priv); + + if (HAS_PCH_LPT_LP(dev_priv)) { + val = intel_de_read(dev_priv, SOUTH_DSPCLK_GATE_D); + val |= PCH_LP_PARTITION_LEVEL_DISABLE; + intel_de_write(dev_priv, SOUTH_DSPCLK_GATE_D, val); + } +} + +static void intel_pch_reset_handshake(struct drm_i915_private *dev_priv, + bool enable) +{ + i915_reg_t reg; + u32 reset_bits, val; + + if (IS_IVYBRIDGE(dev_priv)) { + reg = GEN7_MSG_CTL; + reset_bits = WAIT_FOR_PCH_FLR_ACK | WAIT_FOR_PCH_RESET_ACK; + } else { + reg = HSW_NDE_RSTWRN_OPT; + reset_bits = RESET_PCH_HANDSHAKE_ENABLE; + } + + if (DISPLAY_VER(dev_priv) >= 14) + reset_bits |= MTL_RESET_PICA_HANDSHAKE_EN; + + val = intel_de_read(dev_priv, reg); + + if (enable) + val |= reset_bits; + else + val &= ~reset_bits; + + intel_de_write(dev_priv, reg, val); +} + +static void skl_display_core_init(struct drm_i915_private *dev_priv, + bool resume) +{ + struct i915_power_domains *power_domains = &dev_priv->display.power.domains; + struct i915_power_well *well; + + gen9_set_dc_state(dev_priv, DC_STATE_DISABLE); + + /* enable PCH reset handshake */ + intel_pch_reset_handshake(dev_priv, !HAS_PCH_NOP(dev_priv)); + + if (!HAS_DISPLAY(dev_priv)) + return; + + /* enable PG1 and Misc I/O */ + mutex_lock(&power_domains->lock); + + well = lookup_power_well(dev_priv, SKL_DISP_PW_1); + intel_power_well_enable(dev_priv, well); + + well = lookup_power_well(dev_priv, SKL_DISP_PW_MISC_IO); + intel_power_well_enable(dev_priv, well); + + mutex_unlock(&power_domains->lock); + + intel_cdclk_init_hw(dev_priv); + + gen9_dbuf_enable(dev_priv); + + if (resume) + intel_dmc_load_program(dev_priv); +} + +static void skl_display_core_uninit(struct drm_i915_private *dev_priv) +{ + struct i915_power_domains *power_domains = &dev_priv->display.power.domains; + struct i915_power_well *well; + + if (!HAS_DISPLAY(dev_priv)) + return; + + gen9_disable_dc_states(dev_priv); + /* TODO: disable DMC program */ + + gen9_dbuf_disable(dev_priv); + + intel_cdclk_uninit_hw(dev_priv); + + /* The spec doesn't call for removing the reset handshake flag */ + /* disable PG1 and Misc I/O */ + + mutex_lock(&power_domains->lock); + + /* + * BSpec says to keep the MISC IO power well enabled here, only + * remove our request for power well 1. + * Note that even though the driver's request is removed power well 1 + * may stay enabled after this due to DMC's own request on it. + */ + well = lookup_power_well(dev_priv, SKL_DISP_PW_1); + intel_power_well_disable(dev_priv, well); + + mutex_unlock(&power_domains->lock); + + usleep_range(10, 30); /* 10 us delay per Bspec */ +} + +static void bxt_display_core_init(struct drm_i915_private *dev_priv, bool resume) +{ + struct i915_power_domains *power_domains = &dev_priv->display.power.domains; + struct i915_power_well *well; + + gen9_set_dc_state(dev_priv, DC_STATE_DISABLE); + + /* + * NDE_RSTWRN_OPT RST PCH Handshake En must always be 0b on BXT + * or else the reset will hang because there is no PCH to respond. + * Move the handshake programming to initialization sequence. + * Previously was left up to BIOS. + */ + intel_pch_reset_handshake(dev_priv, false); + + if (!HAS_DISPLAY(dev_priv)) + return; + + /* Enable PG1 */ + mutex_lock(&power_domains->lock); + + well = lookup_power_well(dev_priv, SKL_DISP_PW_1); + intel_power_well_enable(dev_priv, well); + + mutex_unlock(&power_domains->lock); + + intel_cdclk_init_hw(dev_priv); + + gen9_dbuf_enable(dev_priv); + + if (resume) + intel_dmc_load_program(dev_priv); +} + +static void bxt_display_core_uninit(struct drm_i915_private *dev_priv) +{ + struct i915_power_domains *power_domains = &dev_priv->display.power.domains; + struct i915_power_well *well; + + if (!HAS_DISPLAY(dev_priv)) + return; + + gen9_disable_dc_states(dev_priv); + /* TODO: disable DMC program */ + + gen9_dbuf_disable(dev_priv); + + intel_cdclk_uninit_hw(dev_priv); + + /* The spec doesn't call for removing the reset handshake flag */ + + /* + * Disable PW1 (PG1). + * Note that even though the driver's request is removed power well 1 + * may stay enabled after this due to DMC's own request on it. + */ + mutex_lock(&power_domains->lock); + + well = lookup_power_well(dev_priv, SKL_DISP_PW_1); + intel_power_well_disable(dev_priv, well); + + mutex_unlock(&power_domains->lock); + + usleep_range(10, 30); /* 10 us delay per Bspec */ +} + +struct buddy_page_mask { + u32 page_mask; + u8 type; + u8 num_channels; +}; + +static const struct buddy_page_mask tgl_buddy_page_masks[] = { + { .num_channels = 1, .type = INTEL_DRAM_DDR4, .page_mask = 0xF }, + { .num_channels = 1, .type = INTEL_DRAM_DDR5, .page_mask = 0xF }, + { .num_channels = 2, .type = INTEL_DRAM_LPDDR4, .page_mask = 0x1C }, + { .num_channels = 2, .type = INTEL_DRAM_LPDDR5, .page_mask = 0x1C }, + { .num_channels = 2, .type = INTEL_DRAM_DDR4, .page_mask = 0x1F }, + { .num_channels = 2, .type = INTEL_DRAM_DDR5, .page_mask = 0x1E }, + { .num_channels = 4, .type = INTEL_DRAM_LPDDR4, .page_mask = 0x38 }, + { .num_channels = 4, .type = INTEL_DRAM_LPDDR5, .page_mask = 0x38 }, + {} +}; + +static const struct buddy_page_mask wa_1409767108_buddy_page_masks[] = { + { .num_channels = 1, .type = INTEL_DRAM_LPDDR4, .page_mask = 0x1 }, + { .num_channels = 1, .type = INTEL_DRAM_DDR4, .page_mask = 0x1 }, + { .num_channels = 1, .type = INTEL_DRAM_DDR5, .page_mask = 0x1 }, + { .num_channels = 1, .type = INTEL_DRAM_LPDDR5, .page_mask = 0x1 }, + { .num_channels = 2, .type = INTEL_DRAM_LPDDR4, .page_mask = 0x3 }, + { .num_channels = 2, .type = INTEL_DRAM_DDR4, .page_mask = 0x3 }, + { .num_channels = 2, .type = INTEL_DRAM_DDR5, .page_mask = 0x3 }, + { .num_channels = 2, .type = INTEL_DRAM_LPDDR5, .page_mask = 0x3 }, + {} +}; + +static void tgl_bw_buddy_init(struct drm_i915_private *dev_priv) +{ + enum intel_dram_type type = dev_priv->dram_info.type; + u8 num_channels = dev_priv->dram_info.num_channels; + const struct buddy_page_mask *table; + unsigned long abox_mask = INTEL_INFO(dev_priv)->display.abox_mask; + int config, i; + + /* BW_BUDDY registers are not used on dgpu's beyond DG1 */ + if (IS_DGFX(dev_priv) && !IS_DG1(dev_priv)) + return; + + if (IS_ALDERLAKE_S(dev_priv) || + IS_DG1_DISPLAY_STEP(dev_priv, STEP_A0, STEP_B0) || + IS_RKL_DISPLAY_STEP(dev_priv, STEP_A0, STEP_B0) || + IS_TGL_DISPLAY_STEP(dev_priv, STEP_A0, STEP_C0)) + /* Wa_1409767108:tgl,dg1,adl-s */ + table = wa_1409767108_buddy_page_masks; + else + table = tgl_buddy_page_masks; + + for (config = 0; table[config].page_mask != 0; config++) + if (table[config].num_channels == num_channels && + table[config].type == type) + break; + + if (table[config].page_mask == 0) { + drm_dbg(&dev_priv->drm, + "Unknown memory configuration; disabling address buddy logic.\n"); + for_each_set_bit(i, &abox_mask, sizeof(abox_mask)) + intel_de_write(dev_priv, BW_BUDDY_CTL(i), + BW_BUDDY_DISABLE); + } else { + for_each_set_bit(i, &abox_mask, sizeof(abox_mask)) { + intel_de_write(dev_priv, BW_BUDDY_PAGE_MASK(i), + table[config].page_mask); + + /* Wa_22010178259:tgl,dg1,rkl,adl-s */ + if (DISPLAY_VER(dev_priv) == 12) + intel_de_rmw(dev_priv, BW_BUDDY_CTL(i), + BW_BUDDY_TLB_REQ_TIMER_MASK, + BW_BUDDY_TLB_REQ_TIMER(0x8)); + } + } +} + +static void icl_display_core_init(struct drm_i915_private *dev_priv, + bool resume) +{ + struct i915_power_domains *power_domains = &dev_priv->display.power.domains; + struct i915_power_well *well; + u32 val; + + gen9_set_dc_state(dev_priv, DC_STATE_DISABLE); + + /* Wa_14011294188:ehl,jsl,tgl,rkl,adl-s */ + if (INTEL_PCH_TYPE(dev_priv) >= PCH_TGP && + INTEL_PCH_TYPE(dev_priv) < PCH_DG1) + intel_de_rmw(dev_priv, SOUTH_DSPCLK_GATE_D, 0, + PCH_DPMGUNIT_CLOCK_GATE_DISABLE); + + /* 1. Enable PCH reset handshake. */ + intel_pch_reset_handshake(dev_priv, !HAS_PCH_NOP(dev_priv)); + + if (!HAS_DISPLAY(dev_priv)) + return; + + /* 2. Initialize all combo phys */ + intel_combo_phy_init(dev_priv); + + /* + * 3. Enable Power Well 1 (PG1). + * The AUX IO power wells will be enabled on demand. + */ + mutex_lock(&power_domains->lock); + well = lookup_power_well(dev_priv, SKL_DISP_PW_1); + intel_power_well_enable(dev_priv, well); + mutex_unlock(&power_domains->lock); + + /* 4. Enable CDCLK. */ + intel_cdclk_init_hw(dev_priv); + + if (DISPLAY_VER(dev_priv) >= 12) + gen12_dbuf_slices_config(dev_priv); + + /* 5. Enable DBUF. */ + gen9_dbuf_enable(dev_priv); + + /* 6. Setup MBUS. */ + icl_mbus_init(dev_priv); + + /* 7. Program arbiter BW_BUDDY registers */ + if (DISPLAY_VER(dev_priv) >= 12) + tgl_bw_buddy_init(dev_priv); + + /* 8. Ensure PHYs have completed calibration and adaptation */ + if (IS_DG2(dev_priv)) + intel_snps_phy_wait_for_calibration(dev_priv); + + if (resume) + intel_dmc_load_program(dev_priv); + + /* Wa_14011508470:tgl,dg1,rkl,adl-s,adl-p */ + if (DISPLAY_VER(dev_priv) >= 12) { + val = DCPR_CLEAR_MEMSTAT_DIS | DCPR_SEND_RESP_IMM | + DCPR_MASK_LPMODE | DCPR_MASK_MAXLATENCY_MEMUP_CLR; + intel_uncore_rmw(&dev_priv->uncore, GEN11_CHICKEN_DCPR_2, 0, val); + } + + /* Wa_14011503030:xelpd */ + if (DISPLAY_VER(dev_priv) >= 13) + intel_de_write(dev_priv, XELPD_DISPLAY_ERR_FATAL_MASK, ~0); +} + +static void icl_display_core_uninit(struct drm_i915_private *dev_priv) +{ + struct i915_power_domains *power_domains = &dev_priv->display.power.domains; + struct i915_power_well *well; + + if (!HAS_DISPLAY(dev_priv)) + return; + + gen9_disable_dc_states(dev_priv); + intel_dmc_disable_program(dev_priv); + + /* 1. Disable all display engine functions -> aready done */ + + /* 2. Disable DBUF */ + gen9_dbuf_disable(dev_priv); + + /* 3. Disable CD clock */ + intel_cdclk_uninit_hw(dev_priv); + + /* + * 4. Disable Power Well 1 (PG1). + * The AUX IO power wells are toggled on demand, so they are already + * disabled at this point. + */ + mutex_lock(&power_domains->lock); + well = lookup_power_well(dev_priv, SKL_DISP_PW_1); + intel_power_well_disable(dev_priv, well); + mutex_unlock(&power_domains->lock); + + /* 5. */ + intel_combo_phy_uninit(dev_priv); +} + +static void chv_phy_control_init(struct drm_i915_private *dev_priv) +{ + struct i915_power_well *cmn_bc = + lookup_power_well(dev_priv, VLV_DISP_PW_DPIO_CMN_BC); + struct i915_power_well *cmn_d = + lookup_power_well(dev_priv, CHV_DISP_PW_DPIO_CMN_D); + + /* + * DISPLAY_PHY_CONTROL can get corrupted if read. As a + * workaround never ever read DISPLAY_PHY_CONTROL, and + * instead maintain a shadow copy ourselves. Use the actual + * power well state and lane status to reconstruct the + * expected initial value. + */ + dev_priv->display.power.chv_phy_control = + PHY_LDO_SEQ_DELAY(PHY_LDO_DELAY_600NS, DPIO_PHY0) | + PHY_LDO_SEQ_DELAY(PHY_LDO_DELAY_600NS, DPIO_PHY1) | + PHY_CH_POWER_MODE(PHY_CH_DEEP_PSR, DPIO_PHY0, DPIO_CH0) | + PHY_CH_POWER_MODE(PHY_CH_DEEP_PSR, DPIO_PHY0, DPIO_CH1) | + PHY_CH_POWER_MODE(PHY_CH_DEEP_PSR, DPIO_PHY1, DPIO_CH0); + + /* + * If all lanes are disabled we leave the override disabled + * with all power down bits cleared to match the state we + * would use after disabling the port. Otherwise enable the + * override and set the lane powerdown bits accding to the + * current lane status. + */ + if (intel_power_well_is_enabled(dev_priv, cmn_bc)) { + u32 status = intel_de_read(dev_priv, DPLL(PIPE_A)); + unsigned int mask; + + mask = status & DPLL_PORTB_READY_MASK; + if (mask == 0xf) + mask = 0x0; + else + dev_priv->display.power.chv_phy_control |= + PHY_CH_POWER_DOWN_OVRD_EN(DPIO_PHY0, DPIO_CH0); + + dev_priv->display.power.chv_phy_control |= + PHY_CH_POWER_DOWN_OVRD(mask, DPIO_PHY0, DPIO_CH0); + + mask = (status & DPLL_PORTC_READY_MASK) >> 4; + if (mask == 0xf) + mask = 0x0; + else + dev_priv->display.power.chv_phy_control |= + PHY_CH_POWER_DOWN_OVRD_EN(DPIO_PHY0, DPIO_CH1); + + dev_priv->display.power.chv_phy_control |= + PHY_CH_POWER_DOWN_OVRD(mask, DPIO_PHY0, DPIO_CH1); + + dev_priv->display.power.chv_phy_control |= PHY_COM_LANE_RESET_DEASSERT(DPIO_PHY0); + + dev_priv->display.power.chv_phy_assert[DPIO_PHY0] = false; + } else { + dev_priv->display.power.chv_phy_assert[DPIO_PHY0] = true; + } + + if (intel_power_well_is_enabled(dev_priv, cmn_d)) { + u32 status = intel_de_read(dev_priv, DPIO_PHY_STATUS); + unsigned int mask; + + mask = status & DPLL_PORTD_READY_MASK; + + if (mask == 0xf) + mask = 0x0; + else + dev_priv->display.power.chv_phy_control |= + PHY_CH_POWER_DOWN_OVRD_EN(DPIO_PHY1, DPIO_CH0); + + dev_priv->display.power.chv_phy_control |= + PHY_CH_POWER_DOWN_OVRD(mask, DPIO_PHY1, DPIO_CH0); + + dev_priv->display.power.chv_phy_control |= PHY_COM_LANE_RESET_DEASSERT(DPIO_PHY1); + + dev_priv->display.power.chv_phy_assert[DPIO_PHY1] = false; + } else { + dev_priv->display.power.chv_phy_assert[DPIO_PHY1] = true; + } + + drm_dbg_kms(&dev_priv->drm, "Initial PHY_CONTROL=0x%08x\n", + dev_priv->display.power.chv_phy_control); + + /* Defer application of initial phy_control to enabling the powerwell */ +} + +static void vlv_cmnlane_wa(struct drm_i915_private *dev_priv) +{ + struct i915_power_well *cmn = + lookup_power_well(dev_priv, VLV_DISP_PW_DPIO_CMN_BC); + struct i915_power_well *disp2d = + lookup_power_well(dev_priv, VLV_DISP_PW_DISP2D); + + /* If the display might be already active skip this */ + if (intel_power_well_is_enabled(dev_priv, cmn) && + intel_power_well_is_enabled(dev_priv, disp2d) && + intel_de_read(dev_priv, DPIO_CTL) & DPIO_CMNRST) + return; + + drm_dbg_kms(&dev_priv->drm, "toggling display PHY side reset\n"); + + /* cmnlane needs DPLL registers */ + intel_power_well_enable(dev_priv, disp2d); + + /* + * From VLV2A0_DP_eDP_HDMI_DPIO_driver_vbios_notes_11.docx: + * Need to assert and de-assert PHY SB reset by gating the + * common lane power, then un-gating it. + * Simply ungating isn't enough to reset the PHY enough to get + * ports and lanes running. + */ + intel_power_well_disable(dev_priv, cmn); +} + +static bool vlv_punit_is_power_gated(struct drm_i915_private *dev_priv, u32 reg0) +{ + bool ret; + + vlv_punit_get(dev_priv); + ret = (vlv_punit_read(dev_priv, reg0) & SSPM0_SSC_MASK) == SSPM0_SSC_PWR_GATE; + vlv_punit_put(dev_priv); + + return ret; +} + +static void assert_ved_power_gated(struct drm_i915_private *dev_priv) +{ + drm_WARN(&dev_priv->drm, + !vlv_punit_is_power_gated(dev_priv, PUNIT_REG_VEDSSPM0), + "VED not power gated\n"); +} + +static void assert_isp_power_gated(struct drm_i915_private *dev_priv) +{ + static const struct pci_device_id isp_ids[] = { + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0f38)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x22b8)}, + {} + }; + + drm_WARN(&dev_priv->drm, !pci_dev_present(isp_ids) && + !vlv_punit_is_power_gated(dev_priv, PUNIT_REG_ISPSSPM0), + "ISP not power gated\n"); +} + +static void intel_power_domains_verify_state(struct drm_i915_private *dev_priv); + +/** + * intel_power_domains_init_hw - initialize hardware power domain state + * @i915: i915 device instance + * @resume: Called from resume code paths or not + * + * This function initializes the hardware power domain state and enables all + * power wells belonging to the INIT power domain. Power wells in other + * domains (and not in the INIT domain) are referenced or disabled by + * intel_modeset_readout_hw_state(). After that the reference count of each + * power well must match its HW enabled state, see + * intel_power_domains_verify_state(). + * + * It will return with power domains disabled (to be enabled later by + * intel_power_domains_enable()) and must be paired with + * intel_power_domains_driver_remove(). + */ +void intel_power_domains_init_hw(struct drm_i915_private *i915, bool resume) +{ + struct i915_power_domains *power_domains = &i915->display.power.domains; + + power_domains->initializing = true; + + if (DISPLAY_VER(i915) >= 11) { + icl_display_core_init(i915, resume); + } else if (IS_GEMINILAKE(i915) || IS_BROXTON(i915)) { + bxt_display_core_init(i915, resume); + } else if (DISPLAY_VER(i915) == 9) { + skl_display_core_init(i915, resume); + } else if (IS_CHERRYVIEW(i915)) { + mutex_lock(&power_domains->lock); + chv_phy_control_init(i915); + mutex_unlock(&power_domains->lock); + assert_isp_power_gated(i915); + } else if (IS_VALLEYVIEW(i915)) { + mutex_lock(&power_domains->lock); + vlv_cmnlane_wa(i915); + mutex_unlock(&power_domains->lock); + assert_ved_power_gated(i915); + assert_isp_power_gated(i915); + } else if (IS_BROADWELL(i915) || IS_HASWELL(i915)) { + hsw_assert_cdclk(i915); + intel_pch_reset_handshake(i915, !HAS_PCH_NOP(i915)); + } else if (IS_IVYBRIDGE(i915)) { + intel_pch_reset_handshake(i915, !HAS_PCH_NOP(i915)); + } + + /* + * Keep all power wells enabled for any dependent HW access during + * initialization and to make sure we keep BIOS enabled display HW + * resources powered until display HW readout is complete. We drop + * this reference in intel_power_domains_enable(). + */ + drm_WARN_ON(&i915->drm, power_domains->init_wakeref); + power_domains->init_wakeref = + intel_display_power_get(i915, POWER_DOMAIN_INIT); + + /* Disable power support if the user asked so. */ + if (!i915->params.disable_power_well) { + drm_WARN_ON(&i915->drm, power_domains->disable_wakeref); + i915->display.power.domains.disable_wakeref = intel_display_power_get(i915, + POWER_DOMAIN_INIT); + } + intel_power_domains_sync_hw(i915); + + power_domains->initializing = false; +} + +/** + * intel_power_domains_driver_remove - deinitialize hw power domain state + * @i915: i915 device instance + * + * De-initializes the display power domain HW state. It also ensures that the + * device stays powered up so that the driver can be reloaded. + * + * It must be called with power domains already disabled (after a call to + * intel_power_domains_disable()) and must be paired with + * intel_power_domains_init_hw(). + */ +void intel_power_domains_driver_remove(struct drm_i915_private *i915) +{ + intel_wakeref_t wakeref __maybe_unused = + fetch_and_zero(&i915->display.power.domains.init_wakeref); + + /* Remove the refcount we took to keep power well support disabled. */ + if (!i915->params.disable_power_well) + intel_display_power_put(i915, POWER_DOMAIN_INIT, + fetch_and_zero(&i915->display.power.domains.disable_wakeref)); + + intel_display_power_flush_work_sync(i915); + + intel_power_domains_verify_state(i915); + + /* Keep the power well enabled, but cancel its rpm wakeref. */ + intel_runtime_pm_put(&i915->runtime_pm, wakeref); +} + +/** + * intel_power_domains_sanitize_state - sanitize power domains state + * @i915: i915 device instance + * + * Sanitize the power domains state during driver loading and system resume. + * The function will disable all display power wells that BIOS has enabled + * without a user for it (any user for a power well has taken a reference + * on it by the time this function is called, after the state of all the + * pipe, encoder, etc. HW resources have been sanitized). + */ +void intel_power_domains_sanitize_state(struct drm_i915_private *i915) +{ + struct i915_power_domains *power_domains = &i915->display.power.domains; + struct i915_power_well *power_well; + + mutex_lock(&power_domains->lock); + + for_each_power_well_reverse(i915, power_well) { + if (power_well->desc->always_on || power_well->count || + !intel_power_well_is_enabled(i915, power_well)) + continue; + + drm_dbg_kms(&i915->drm, + "BIOS left unused %s power well enabled, disabling it\n", + intel_power_well_name(power_well)); + intel_power_well_disable(i915, power_well); + } + + mutex_unlock(&power_domains->lock); +} + +/** + * intel_power_domains_enable - enable toggling of display power wells + * @i915: i915 device instance + * + * Enable the ondemand enabling/disabling of the display power wells. Note that + * power wells not belonging to POWER_DOMAIN_INIT are allowed to be toggled + * only at specific points of the display modeset sequence, thus they are not + * affected by the intel_power_domains_enable()/disable() calls. The purpose + * of these function is to keep the rest of power wells enabled until the end + * of display HW readout (which will acquire the power references reflecting + * the current HW state). + */ +void intel_power_domains_enable(struct drm_i915_private *i915) +{ + intel_wakeref_t wakeref __maybe_unused = + fetch_and_zero(&i915->display.power.domains.init_wakeref); + + intel_display_power_put(i915, POWER_DOMAIN_INIT, wakeref); + intel_power_domains_verify_state(i915); +} + +/** + * intel_power_domains_disable - disable toggling of display power wells + * @i915: i915 device instance + * + * Disable the ondemand enabling/disabling of the display power wells. See + * intel_power_domains_enable() for which power wells this call controls. + */ +void intel_power_domains_disable(struct drm_i915_private *i915) +{ + struct i915_power_domains *power_domains = &i915->display.power.domains; + + drm_WARN_ON(&i915->drm, power_domains->init_wakeref); + power_domains->init_wakeref = + intel_display_power_get(i915, POWER_DOMAIN_INIT); + + intel_power_domains_verify_state(i915); +} + +/** + * intel_power_domains_suspend - suspend power domain state + * @i915: i915 device instance + * @suspend_mode: specifies the target suspend state (idle, mem, hibernation) + * + * This function prepares the hardware power domain state before entering + * system suspend. + * + * It must be called with power domains already disabled (after a call to + * intel_power_domains_disable()) and paired with intel_power_domains_resume(). + */ +void intel_power_domains_suspend(struct drm_i915_private *i915, + enum i915_drm_suspend_mode suspend_mode) +{ + struct i915_power_domains *power_domains = &i915->display.power.domains; + intel_wakeref_t wakeref __maybe_unused = + fetch_and_zero(&power_domains->init_wakeref); + + intel_display_power_put(i915, POWER_DOMAIN_INIT, wakeref); + + /* + * In case of suspend-to-idle (aka S0ix) on a DMC platform without DC9 + * support don't manually deinit the power domains. This also means the + * DMC firmware will stay active, it will power down any HW + * resources as required and also enable deeper system power states + * that would be blocked if the firmware was inactive. + */ + if (!(i915->display.dmc.allowed_dc_mask & DC_STATE_EN_DC9) && + suspend_mode == I915_DRM_SUSPEND_IDLE && + intel_dmc_has_payload(i915)) { + intel_display_power_flush_work(i915); + intel_power_domains_verify_state(i915); + return; + } + + /* + * Even if power well support was disabled we still want to disable + * power wells if power domains must be deinitialized for suspend. + */ + if (!i915->params.disable_power_well) + intel_display_power_put(i915, POWER_DOMAIN_INIT, + fetch_and_zero(&i915->display.power.domains.disable_wakeref)); + + intel_display_power_flush_work(i915); + intel_power_domains_verify_state(i915); + + if (DISPLAY_VER(i915) >= 11) + icl_display_core_uninit(i915); + else if (IS_GEMINILAKE(i915) || IS_BROXTON(i915)) + bxt_display_core_uninit(i915); + else if (DISPLAY_VER(i915) == 9) + skl_display_core_uninit(i915); + + power_domains->display_core_suspended = true; +} + +/** + * intel_power_domains_resume - resume power domain state + * @i915: i915 device instance + * + * This function resume the hardware power domain state during system resume. + * + * It will return with power domain support disabled (to be enabled later by + * intel_power_domains_enable()) and must be paired with + * intel_power_domains_suspend(). + */ +void intel_power_domains_resume(struct drm_i915_private *i915) +{ + struct i915_power_domains *power_domains = &i915->display.power.domains; + + if (power_domains->display_core_suspended) { + intel_power_domains_init_hw(i915, true); + power_domains->display_core_suspended = false; + } else { + drm_WARN_ON(&i915->drm, power_domains->init_wakeref); + power_domains->init_wakeref = + intel_display_power_get(i915, POWER_DOMAIN_INIT); + } + + intel_power_domains_verify_state(i915); +} + +#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_RUNTIME_PM) + +static void intel_power_domains_dump_info(struct drm_i915_private *i915) +{ + struct i915_power_domains *power_domains = &i915->display.power.domains; + struct i915_power_well *power_well; + + for_each_power_well(i915, power_well) { + enum intel_display_power_domain domain; + + drm_dbg(&i915->drm, "%-25s %d\n", + intel_power_well_name(power_well), intel_power_well_refcount(power_well)); + + for_each_power_domain(domain, intel_power_well_domains(power_well)) + drm_dbg(&i915->drm, " %-23s %d\n", + intel_display_power_domain_str(domain), + power_domains->domain_use_count[domain]); + } +} + +/** + * intel_power_domains_verify_state - verify the HW/SW state for all power wells + * @i915: i915 device instance + * + * Verify if the reference count of each power well matches its HW enabled + * state and the total refcount of the domains it belongs to. This must be + * called after modeset HW state sanitization, which is responsible for + * acquiring reference counts for any power wells in use and disabling the + * ones left on by BIOS but not required by any active output. + */ +static void intel_power_domains_verify_state(struct drm_i915_private *i915) +{ + struct i915_power_domains *power_domains = &i915->display.power.domains; + struct i915_power_well *power_well; + bool dump_domain_info; + + mutex_lock(&power_domains->lock); + + verify_async_put_domains_state(power_domains); + + dump_domain_info = false; + for_each_power_well(i915, power_well) { + enum intel_display_power_domain domain; + int domains_count; + bool enabled; + + enabled = intel_power_well_is_enabled(i915, power_well); + if ((intel_power_well_refcount(power_well) || + intel_power_well_is_always_on(power_well)) != + enabled) + drm_err(&i915->drm, + "power well %s state mismatch (refcount %d/enabled %d)", + intel_power_well_name(power_well), + intel_power_well_refcount(power_well), enabled); + + domains_count = 0; + for_each_power_domain(domain, intel_power_well_domains(power_well)) + domains_count += power_domains->domain_use_count[domain]; + + if (intel_power_well_refcount(power_well) != domains_count) { + drm_err(&i915->drm, + "power well %s refcount/domain refcount mismatch " + "(refcount %d/domains refcount %d)\n", + intel_power_well_name(power_well), + intel_power_well_refcount(power_well), + domains_count); + dump_domain_info = true; + } + } + + if (dump_domain_info) { + static bool dumped; + + if (!dumped) { + intel_power_domains_dump_info(i915); + dumped = true; + } + } + + mutex_unlock(&power_domains->lock); +} + +#else + +static void intel_power_domains_verify_state(struct drm_i915_private *i915) +{ +} + +#endif + +void intel_display_power_suspend_late(struct drm_i915_private *i915) +{ + if (DISPLAY_VER(i915) >= 11 || IS_GEMINILAKE(i915) || + IS_BROXTON(i915)) { + bxt_enable_dc9(i915); + } else if (IS_HASWELL(i915) || IS_BROADWELL(i915)) { + hsw_enable_pc8(i915); + } + + /* Tweaked Wa_14010685332:cnp,icp,jsp,mcc,tgp,adp */ + if (INTEL_PCH_TYPE(i915) >= PCH_CNP && INTEL_PCH_TYPE(i915) < PCH_DG1) + intel_de_rmw(i915, SOUTH_CHICKEN1, SBCLK_RUN_REFCLK_DIS, SBCLK_RUN_REFCLK_DIS); +} + +void intel_display_power_resume_early(struct drm_i915_private *i915) +{ + if (DISPLAY_VER(i915) >= 11 || IS_GEMINILAKE(i915) || + IS_BROXTON(i915)) { + gen9_sanitize_dc_state(i915); + bxt_disable_dc9(i915); + } else if (IS_HASWELL(i915) || IS_BROADWELL(i915)) { + hsw_disable_pc8(i915); + } + + /* Tweaked Wa_14010685332:cnp,icp,jsp,mcc,tgp,adp */ + if (INTEL_PCH_TYPE(i915) >= PCH_CNP && INTEL_PCH_TYPE(i915) < PCH_DG1) + intel_de_rmw(i915, SOUTH_CHICKEN1, SBCLK_RUN_REFCLK_DIS, 0); +} + +void intel_display_power_suspend(struct drm_i915_private *i915) +{ + if (DISPLAY_VER(i915) >= 11) { + icl_display_core_uninit(i915); + bxt_enable_dc9(i915); + } else if (IS_GEMINILAKE(i915) || IS_BROXTON(i915)) { + bxt_display_core_uninit(i915); + bxt_enable_dc9(i915); + } else if (IS_HASWELL(i915) || IS_BROADWELL(i915)) { + hsw_enable_pc8(i915); + } +} + +void intel_display_power_resume(struct drm_i915_private *i915) +{ + if (DISPLAY_VER(i915) >= 11) { + bxt_disable_dc9(i915); + icl_display_core_init(i915, true); + if (intel_dmc_has_payload(i915)) { + if (i915->display.dmc.allowed_dc_mask & + DC_STATE_EN_UPTO_DC6) + skl_enable_dc6(i915); + else if (i915->display.dmc.allowed_dc_mask & + DC_STATE_EN_UPTO_DC5) + gen9_enable_dc5(i915); + } + } else if (IS_GEMINILAKE(i915) || IS_BROXTON(i915)) { + bxt_disable_dc9(i915); + bxt_display_core_init(i915, true); + if (intel_dmc_has_payload(i915) && + (i915->display.dmc.allowed_dc_mask & DC_STATE_EN_UPTO_DC5)) + gen9_enable_dc5(i915); + } else if (IS_HASWELL(i915) || IS_BROADWELL(i915)) { + hsw_disable_pc8(i915); + } +} + +void intel_display_power_debug(struct drm_i915_private *i915, struct seq_file *m) +{ + struct i915_power_domains *power_domains = &i915->display.power.domains; + int i; + + mutex_lock(&power_domains->lock); + + seq_printf(m, "%-25s %s\n", "Power well/domain", "Use count"); + for (i = 0; i < power_domains->power_well_count; i++) { + struct i915_power_well *power_well; + enum intel_display_power_domain power_domain; + + power_well = &power_domains->power_wells[i]; + seq_printf(m, "%-25s %d\n", intel_power_well_name(power_well), + intel_power_well_refcount(power_well)); + + for_each_power_domain(power_domain, intel_power_well_domains(power_well)) + seq_printf(m, " %-23s %d\n", + intel_display_power_domain_str(power_domain), + power_domains->domain_use_count[power_domain]); + } + + mutex_unlock(&power_domains->lock); +} + +struct intel_ddi_port_domains { + enum port port_start; + enum port port_end; + enum aux_ch aux_ch_start; + enum aux_ch aux_ch_end; + + enum intel_display_power_domain ddi_lanes; + enum intel_display_power_domain ddi_io; + enum intel_display_power_domain aux_legacy_usbc; + enum intel_display_power_domain aux_tbt; +}; + +static const struct intel_ddi_port_domains +i9xx_port_domains[] = { + { + .port_start = PORT_A, + .port_end = PORT_F, + .aux_ch_start = AUX_CH_A, + .aux_ch_end = AUX_CH_F, + + .ddi_lanes = POWER_DOMAIN_PORT_DDI_LANES_A, + .ddi_io = POWER_DOMAIN_PORT_DDI_IO_A, + .aux_legacy_usbc = POWER_DOMAIN_AUX_A, + .aux_tbt = POWER_DOMAIN_INVALID, + }, +}; + +static const struct intel_ddi_port_domains +d11_port_domains[] = { + { + .port_start = PORT_A, + .port_end = PORT_B, + .aux_ch_start = AUX_CH_A, + .aux_ch_end = AUX_CH_B, + + .ddi_lanes = POWER_DOMAIN_PORT_DDI_LANES_A, + .ddi_io = POWER_DOMAIN_PORT_DDI_IO_A, + .aux_legacy_usbc = POWER_DOMAIN_AUX_A, + .aux_tbt = POWER_DOMAIN_INVALID, + }, { + .port_start = PORT_C, + .port_end = PORT_F, + .aux_ch_start = AUX_CH_C, + .aux_ch_end = AUX_CH_F, + + .ddi_lanes = POWER_DOMAIN_PORT_DDI_LANES_C, + .ddi_io = POWER_DOMAIN_PORT_DDI_IO_C, + .aux_legacy_usbc = POWER_DOMAIN_AUX_C, + .aux_tbt = POWER_DOMAIN_AUX_TBT1, + }, +}; + +static const struct intel_ddi_port_domains +d12_port_domains[] = { + { + .port_start = PORT_A, + .port_end = PORT_C, + .aux_ch_start = AUX_CH_A, + .aux_ch_end = AUX_CH_C, + + .ddi_lanes = POWER_DOMAIN_PORT_DDI_LANES_A, + .ddi_io = POWER_DOMAIN_PORT_DDI_IO_A, + .aux_legacy_usbc = POWER_DOMAIN_AUX_A, + .aux_tbt = POWER_DOMAIN_INVALID, + }, { + .port_start = PORT_TC1, + .port_end = PORT_TC6, + .aux_ch_start = AUX_CH_USBC1, + .aux_ch_end = AUX_CH_USBC6, + + .ddi_lanes = POWER_DOMAIN_PORT_DDI_LANES_TC1, + .ddi_io = POWER_DOMAIN_PORT_DDI_IO_TC1, + .aux_legacy_usbc = POWER_DOMAIN_AUX_USBC1, + .aux_tbt = POWER_DOMAIN_AUX_TBT1, + }, +}; + +static const struct intel_ddi_port_domains +d13_port_domains[] = { + { + .port_start = PORT_A, + .port_end = PORT_C, + .aux_ch_start = AUX_CH_A, + .aux_ch_end = AUX_CH_C, + + .ddi_lanes = POWER_DOMAIN_PORT_DDI_LANES_A, + .ddi_io = POWER_DOMAIN_PORT_DDI_IO_A, + .aux_legacy_usbc = POWER_DOMAIN_AUX_A, + .aux_tbt = POWER_DOMAIN_INVALID, + }, { + .port_start = PORT_TC1, + .port_end = PORT_TC4, + .aux_ch_start = AUX_CH_USBC1, + .aux_ch_end = AUX_CH_USBC4, + + .ddi_lanes = POWER_DOMAIN_PORT_DDI_LANES_TC1, + .ddi_io = POWER_DOMAIN_PORT_DDI_IO_TC1, + .aux_legacy_usbc = POWER_DOMAIN_AUX_USBC1, + .aux_tbt = POWER_DOMAIN_AUX_TBT1, + }, { + .port_start = PORT_D_XELPD, + .port_end = PORT_E_XELPD, + .aux_ch_start = AUX_CH_D_XELPD, + .aux_ch_end = AUX_CH_E_XELPD, + + .ddi_lanes = POWER_DOMAIN_PORT_DDI_LANES_D, + .ddi_io = POWER_DOMAIN_PORT_DDI_IO_D, + .aux_legacy_usbc = POWER_DOMAIN_AUX_D, + .aux_tbt = POWER_DOMAIN_INVALID, + }, +}; + +static void +intel_port_domains_for_platform(struct drm_i915_private *i915, + const struct intel_ddi_port_domains **domains, + int *domains_size) +{ + if (DISPLAY_VER(i915) >= 13) { + *domains = d13_port_domains; + *domains_size = ARRAY_SIZE(d13_port_domains); + } else if (DISPLAY_VER(i915) >= 12) { + *domains = d12_port_domains; + *domains_size = ARRAY_SIZE(d12_port_domains); + } else if (DISPLAY_VER(i915) >= 11) { + *domains = d11_port_domains; + *domains_size = ARRAY_SIZE(d11_port_domains); + } else { + *domains = i9xx_port_domains; + *domains_size = ARRAY_SIZE(i9xx_port_domains); + } +} + +static const struct intel_ddi_port_domains * +intel_port_domains_for_port(struct drm_i915_private *i915, enum port port) +{ + const struct intel_ddi_port_domains *domains; + int domains_size; + int i; + + intel_port_domains_for_platform(i915, &domains, &domains_size); + for (i = 0; i < domains_size; i++) + if (port >= domains[i].port_start && port <= domains[i].port_end) + return &domains[i]; + + return NULL; +} + +enum intel_display_power_domain +intel_display_power_ddi_io_domain(struct drm_i915_private *i915, enum port port) +{ + const struct intel_ddi_port_domains *domains = intel_port_domains_for_port(i915, port); + + if (drm_WARN_ON(&i915->drm, !domains || domains->ddi_io == POWER_DOMAIN_INVALID)) + return POWER_DOMAIN_PORT_DDI_IO_A; + + return domains->ddi_io + (int)(port - domains->port_start); +} + +enum intel_display_power_domain +intel_display_power_ddi_lanes_domain(struct drm_i915_private *i915, enum port port) +{ + const struct intel_ddi_port_domains *domains = intel_port_domains_for_port(i915, port); + + if (drm_WARN_ON(&i915->drm, !domains || domains->ddi_lanes == POWER_DOMAIN_INVALID)) + return POWER_DOMAIN_PORT_DDI_LANES_A; + + return domains->ddi_lanes + (int)(port - domains->port_start); +} + +static const struct intel_ddi_port_domains * +intel_port_domains_for_aux_ch(struct drm_i915_private *i915, enum aux_ch aux_ch) +{ + const struct intel_ddi_port_domains *domains; + int domains_size; + int i; + + intel_port_domains_for_platform(i915, &domains, &domains_size); + for (i = 0; i < domains_size; i++) + if (aux_ch >= domains[i].aux_ch_start && aux_ch <= domains[i].aux_ch_end) + return &domains[i]; + + return NULL; +} + +enum intel_display_power_domain +intel_display_power_legacy_aux_domain(struct drm_i915_private *i915, enum aux_ch aux_ch) +{ + const struct intel_ddi_port_domains *domains = intel_port_domains_for_aux_ch(i915, aux_ch); + + if (drm_WARN_ON(&i915->drm, !domains || domains->aux_legacy_usbc == POWER_DOMAIN_INVALID)) + return POWER_DOMAIN_AUX_A; + + return domains->aux_legacy_usbc + (int)(aux_ch - domains->aux_ch_start); +} + +enum intel_display_power_domain +intel_display_power_tbt_aux_domain(struct drm_i915_private *i915, enum aux_ch aux_ch) +{ + const struct intel_ddi_port_domains *domains = intel_port_domains_for_aux_ch(i915, aux_ch); + + if (drm_WARN_ON(&i915->drm, !domains || domains->aux_tbt == POWER_DOMAIN_INVALID)) + return POWER_DOMAIN_AUX_TBT1; + + return domains->aux_tbt + (int)(aux_ch - domains->aux_ch_start); +} diff --git a/drivers/gpu/drm/i915/display/intel_display_power.h b/drivers/gpu/drm/i915/display/intel_display_power.h new file mode 100644 index 000000000..7136ea3f2 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_display_power.h @@ -0,0 +1,279 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_DISPLAY_POWER_H__ +#define __INTEL_DISPLAY_POWER_H__ + +#include "intel_runtime_pm.h" + +enum aux_ch; +enum dpio_channel; +enum dpio_phy; +enum port; +struct drm_i915_private; +struct i915_power_well; +struct intel_encoder; + +/* + * Keep the pipe, transcoder, port (DDI_LANES,DDI_IO,AUX) domain instances + * consecutive, so that the pipe,transcoder,port -> power domain macros + * work correctly. + */ +enum intel_display_power_domain { + POWER_DOMAIN_DISPLAY_CORE, + POWER_DOMAIN_PIPE_A, + POWER_DOMAIN_PIPE_B, + POWER_DOMAIN_PIPE_C, + POWER_DOMAIN_PIPE_D, + POWER_DOMAIN_PIPE_PANEL_FITTER_A, + POWER_DOMAIN_PIPE_PANEL_FITTER_B, + POWER_DOMAIN_PIPE_PANEL_FITTER_C, + POWER_DOMAIN_PIPE_PANEL_FITTER_D, + POWER_DOMAIN_TRANSCODER_A, + POWER_DOMAIN_TRANSCODER_B, + POWER_DOMAIN_TRANSCODER_C, + POWER_DOMAIN_TRANSCODER_D, + POWER_DOMAIN_TRANSCODER_EDP, + POWER_DOMAIN_TRANSCODER_DSI_A, + POWER_DOMAIN_TRANSCODER_DSI_C, + + /* VDSC/joining for eDP/DSI transcoder (ICL) or pipe A (TGL) */ + POWER_DOMAIN_TRANSCODER_VDSC_PW2, + + POWER_DOMAIN_PORT_DDI_LANES_A, + POWER_DOMAIN_PORT_DDI_LANES_B, + POWER_DOMAIN_PORT_DDI_LANES_C, + POWER_DOMAIN_PORT_DDI_LANES_D, + POWER_DOMAIN_PORT_DDI_LANES_E, + POWER_DOMAIN_PORT_DDI_LANES_F, + + POWER_DOMAIN_PORT_DDI_LANES_TC1, + POWER_DOMAIN_PORT_DDI_LANES_TC2, + POWER_DOMAIN_PORT_DDI_LANES_TC3, + POWER_DOMAIN_PORT_DDI_LANES_TC4, + POWER_DOMAIN_PORT_DDI_LANES_TC5, + POWER_DOMAIN_PORT_DDI_LANES_TC6, + + POWER_DOMAIN_PORT_DDI_IO_A, + POWER_DOMAIN_PORT_DDI_IO_B, + POWER_DOMAIN_PORT_DDI_IO_C, + POWER_DOMAIN_PORT_DDI_IO_D, + POWER_DOMAIN_PORT_DDI_IO_E, + POWER_DOMAIN_PORT_DDI_IO_F, + + POWER_DOMAIN_PORT_DDI_IO_TC1, + POWER_DOMAIN_PORT_DDI_IO_TC2, + POWER_DOMAIN_PORT_DDI_IO_TC3, + POWER_DOMAIN_PORT_DDI_IO_TC4, + POWER_DOMAIN_PORT_DDI_IO_TC5, + POWER_DOMAIN_PORT_DDI_IO_TC6, + + POWER_DOMAIN_PORT_DSI, + POWER_DOMAIN_PORT_CRT, + POWER_DOMAIN_PORT_OTHER, + POWER_DOMAIN_VGA, + POWER_DOMAIN_AUDIO_MMIO, + POWER_DOMAIN_AUDIO_PLAYBACK, + POWER_DOMAIN_AUX_A, + POWER_DOMAIN_AUX_B, + POWER_DOMAIN_AUX_C, + POWER_DOMAIN_AUX_D, + POWER_DOMAIN_AUX_E, + POWER_DOMAIN_AUX_F, + + POWER_DOMAIN_AUX_USBC1, + POWER_DOMAIN_AUX_USBC2, + POWER_DOMAIN_AUX_USBC3, + POWER_DOMAIN_AUX_USBC4, + POWER_DOMAIN_AUX_USBC5, + POWER_DOMAIN_AUX_USBC6, + + POWER_DOMAIN_AUX_IO_A, + + POWER_DOMAIN_AUX_TBT1, + POWER_DOMAIN_AUX_TBT2, + POWER_DOMAIN_AUX_TBT3, + POWER_DOMAIN_AUX_TBT4, + POWER_DOMAIN_AUX_TBT5, + POWER_DOMAIN_AUX_TBT6, + + POWER_DOMAIN_GMBUS, + POWER_DOMAIN_MODESET, + POWER_DOMAIN_GT_IRQ, + POWER_DOMAIN_DC_OFF, + POWER_DOMAIN_TC_COLD_OFF, + POWER_DOMAIN_INIT, + + POWER_DOMAIN_NUM, + POWER_DOMAIN_INVALID = POWER_DOMAIN_NUM, +}; + +#define POWER_DOMAIN_PIPE(pipe) ((pipe) + POWER_DOMAIN_PIPE_A) +#define POWER_DOMAIN_PIPE_PANEL_FITTER(pipe) \ + ((pipe) + POWER_DOMAIN_PIPE_PANEL_FITTER_A) +#define POWER_DOMAIN_TRANSCODER(tran) \ + ((tran) == TRANSCODER_EDP ? POWER_DOMAIN_TRANSCODER_EDP : \ + (tran) + POWER_DOMAIN_TRANSCODER_A) + +struct intel_power_domain_mask { + DECLARE_BITMAP(bits, POWER_DOMAIN_NUM); +}; + +struct i915_power_domains { + /* + * Power wells needed for initialization at driver init and suspend + * time are on. They are kept on until after the first modeset. + */ + bool initializing; + bool display_core_suspended; + int power_well_count; + + intel_wakeref_t init_wakeref; + intel_wakeref_t disable_wakeref; + + struct mutex lock; + int domain_use_count[POWER_DOMAIN_NUM]; + + struct delayed_work async_put_work; + intel_wakeref_t async_put_wakeref; + struct intel_power_domain_mask async_put_domains[2]; + + struct i915_power_well *power_wells; +}; + +struct intel_display_power_domain_set { + struct intel_power_domain_mask mask; +#ifdef CONFIG_DRM_I915_DEBUG_RUNTIME_PM + intel_wakeref_t wakerefs[POWER_DOMAIN_NUM]; +#endif +}; + +#define for_each_power_domain(__domain, __mask) \ + for ((__domain) = 0; (__domain) < POWER_DOMAIN_NUM; (__domain)++) \ + for_each_if(test_bit((__domain), (__mask)->bits)) + +int intel_power_domains_init(struct drm_i915_private *dev_priv); +void intel_power_domains_cleanup(struct drm_i915_private *dev_priv); +void intel_power_domains_init_hw(struct drm_i915_private *dev_priv, bool resume); +void intel_power_domains_driver_remove(struct drm_i915_private *dev_priv); +void intel_power_domains_enable(struct drm_i915_private *dev_priv); +void intel_power_domains_disable(struct drm_i915_private *dev_priv); +void intel_power_domains_suspend(struct drm_i915_private *dev_priv, + enum i915_drm_suspend_mode); +void intel_power_domains_resume(struct drm_i915_private *dev_priv); +void intel_power_domains_sanitize_state(struct drm_i915_private *dev_priv); + +void intel_display_power_suspend_late(struct drm_i915_private *i915); +void intel_display_power_resume_early(struct drm_i915_private *i915); +void intel_display_power_suspend(struct drm_i915_private *i915); +void intel_display_power_resume(struct drm_i915_private *i915); +void intel_display_power_set_target_dc_state(struct drm_i915_private *dev_priv, + u32 state); + +const char * +intel_display_power_domain_str(enum intel_display_power_domain domain); + +bool intel_display_power_is_enabled(struct drm_i915_private *dev_priv, + enum intel_display_power_domain domain); +bool __intel_display_power_is_enabled(struct drm_i915_private *dev_priv, + enum intel_display_power_domain domain); +intel_wakeref_t intel_display_power_get(struct drm_i915_private *dev_priv, + enum intel_display_power_domain domain); +intel_wakeref_t +intel_display_power_get_if_enabled(struct drm_i915_private *dev_priv, + enum intel_display_power_domain domain); +void __intel_display_power_put_async(struct drm_i915_private *i915, + enum intel_display_power_domain domain, + intel_wakeref_t wakeref); +void intel_display_power_flush_work(struct drm_i915_private *i915); +#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_RUNTIME_PM) +void intel_display_power_put(struct drm_i915_private *dev_priv, + enum intel_display_power_domain domain, + intel_wakeref_t wakeref); +static inline void +intel_display_power_put_async(struct drm_i915_private *i915, + enum intel_display_power_domain domain, + intel_wakeref_t wakeref) +{ + __intel_display_power_put_async(i915, domain, wakeref); +} +#else +void intel_display_power_put_unchecked(struct drm_i915_private *dev_priv, + enum intel_display_power_domain domain); + +static inline void +intel_display_power_put(struct drm_i915_private *i915, + enum intel_display_power_domain domain, + intel_wakeref_t wakeref) +{ + intel_display_power_put_unchecked(i915, domain); +} + +static inline void +intel_display_power_put_async(struct drm_i915_private *i915, + enum intel_display_power_domain domain, + intel_wakeref_t wakeref) +{ + __intel_display_power_put_async(i915, domain, -1); +} +#endif + +void +intel_display_power_get_in_set(struct drm_i915_private *i915, + struct intel_display_power_domain_set *power_domain_set, + enum intel_display_power_domain domain); + +bool +intel_display_power_get_in_set_if_enabled(struct drm_i915_private *i915, + struct intel_display_power_domain_set *power_domain_set, + enum intel_display_power_domain domain); + +void +intel_display_power_put_mask_in_set(struct drm_i915_private *i915, + struct intel_display_power_domain_set *power_domain_set, + struct intel_power_domain_mask *mask); + +static inline void +intel_display_power_put_all_in_set(struct drm_i915_private *i915, + struct intel_display_power_domain_set *power_domain_set) +{ + intel_display_power_put_mask_in_set(i915, power_domain_set, &power_domain_set->mask); +} + +void intel_display_power_debug(struct drm_i915_private *i915, struct seq_file *m); + +enum intel_display_power_domain +intel_display_power_ddi_lanes_domain(struct drm_i915_private *i915, enum port port); +enum intel_display_power_domain +intel_display_power_ddi_io_domain(struct drm_i915_private *i915, enum port port); +enum intel_display_power_domain +intel_display_power_legacy_aux_domain(struct drm_i915_private *i915, enum aux_ch aux_ch); +enum intel_display_power_domain +intel_display_power_tbt_aux_domain(struct drm_i915_private *i915, enum aux_ch aux_ch); + +/* + * FIXME: We should probably switch this to a 0-based scheme to be consistent + * with how we now name/number DBUF_CTL instances. + */ +enum dbuf_slice { + DBUF_S1, + DBUF_S2, + DBUF_S3, + DBUF_S4, + I915_MAX_DBUF_SLICES +}; + +void gen9_dbuf_slices_update(struct drm_i915_private *dev_priv, + u8 req_slices); + +#define with_intel_display_power(i915, domain, wf) \ + for ((wf) = intel_display_power_get((i915), (domain)); (wf); \ + intel_display_power_put_async((i915), (domain), (wf)), (wf) = 0) + +#define with_intel_display_power_if_enabled(i915, domain, wf) \ + for ((wf) = intel_display_power_get_if_enabled((i915), (domain)); (wf); \ + intel_display_power_put_async((i915), (domain), (wf)), (wf) = 0) + +#endif /* __INTEL_DISPLAY_POWER_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_display_power_map.c b/drivers/gpu/drm/i915/display/intel_display_power_map.c new file mode 100644 index 000000000..dc04afc6c --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_display_power_map.c @@ -0,0 +1,1614 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2022 Intel Corporation + */ + +#include "i915_drv.h" +#include "i915_reg.h" + +#include "vlv_sideband_reg.h" + +#include "intel_display_power_map.h" +#include "intel_display_power_well.h" + +#define __LIST_INLINE_ELEMS(__elem_type, ...) \ + ((__elem_type[]) { __VA_ARGS__ }) + +#define __LIST(__elems) { \ + .list = __elems, \ + .count = ARRAY_SIZE(__elems), \ +} + +#define I915_PW_DOMAINS(...) \ + (const struct i915_power_domain_list) \ + __LIST(__LIST_INLINE_ELEMS(const enum intel_display_power_domain, __VA_ARGS__)) + +#define I915_DECL_PW_DOMAINS(__name, ...) \ + static const struct i915_power_domain_list __name = I915_PW_DOMAINS(__VA_ARGS__) + +/* Zero-length list assigns all power domains, a NULL list assigns none. */ +#define I915_PW_DOMAINS_NONE NULL +#define I915_PW_DOMAINS_ALL /* zero-length list */ + +#define I915_PW_INSTANCES(...) \ + (const struct i915_power_well_instance_list) \ + __LIST(__LIST_INLINE_ELEMS(const struct i915_power_well_instance, __VA_ARGS__)) + +#define I915_PW(_name, _domain_list, ...) \ + { .name = _name, .domain_list = _domain_list, ## __VA_ARGS__ } + + +struct i915_power_well_desc_list { + const struct i915_power_well_desc *list; + u8 count; +}; + +#define I915_PW_DESCRIPTORS(x) __LIST(x) + + +I915_DECL_PW_DOMAINS(i9xx_pwdoms_always_on, I915_PW_DOMAINS_ALL); + +static const struct i915_power_well_desc i9xx_power_wells_always_on[] = { + { + .instances = &I915_PW_INSTANCES( + I915_PW("always-on", &i9xx_pwdoms_always_on), + ), + .ops = &i9xx_always_on_power_well_ops, + .always_on = true, + }, +}; + +static const struct i915_power_well_desc_list i9xx_power_wells[] = { + I915_PW_DESCRIPTORS(i9xx_power_wells_always_on), +}; + +I915_DECL_PW_DOMAINS(i830_pwdoms_pipes, + POWER_DOMAIN_PIPE_A, + POWER_DOMAIN_PIPE_B, + POWER_DOMAIN_PIPE_PANEL_FITTER_A, + POWER_DOMAIN_PIPE_PANEL_FITTER_B, + POWER_DOMAIN_TRANSCODER_A, + POWER_DOMAIN_TRANSCODER_B, + POWER_DOMAIN_INIT); + +static const struct i915_power_well_desc i830_power_wells_main[] = { + { + .instances = &I915_PW_INSTANCES( + I915_PW("pipes", &i830_pwdoms_pipes), + ), + .ops = &i830_pipes_power_well_ops, + }, +}; + +static const struct i915_power_well_desc_list i830_power_wells[] = { + I915_PW_DESCRIPTORS(i9xx_power_wells_always_on), + I915_PW_DESCRIPTORS(i830_power_wells_main), +}; + +I915_DECL_PW_DOMAINS(hsw_pwdoms_display, + POWER_DOMAIN_PIPE_B, + POWER_DOMAIN_PIPE_C, + POWER_DOMAIN_PIPE_PANEL_FITTER_A, + POWER_DOMAIN_PIPE_PANEL_FITTER_B, + POWER_DOMAIN_PIPE_PANEL_FITTER_C, + POWER_DOMAIN_TRANSCODER_A, + POWER_DOMAIN_TRANSCODER_B, + POWER_DOMAIN_TRANSCODER_C, + POWER_DOMAIN_PORT_DDI_LANES_B, + POWER_DOMAIN_PORT_DDI_LANES_C, + POWER_DOMAIN_PORT_DDI_LANES_D, + POWER_DOMAIN_PORT_CRT, /* DDI E */ + POWER_DOMAIN_VGA, + POWER_DOMAIN_AUDIO_MMIO, + POWER_DOMAIN_AUDIO_PLAYBACK, + POWER_DOMAIN_INIT); + +static const struct i915_power_well_desc hsw_power_wells_main[] = { + { + .instances = &I915_PW_INSTANCES( + I915_PW("display", &hsw_pwdoms_display, + .hsw.idx = HSW_PW_CTL_IDX_GLOBAL, + .id = HSW_DISP_PW_GLOBAL), + ), + .ops = &hsw_power_well_ops, + .has_vga = true, + }, +}; + +static const struct i915_power_well_desc_list hsw_power_wells[] = { + I915_PW_DESCRIPTORS(i9xx_power_wells_always_on), + I915_PW_DESCRIPTORS(hsw_power_wells_main), +}; + +I915_DECL_PW_DOMAINS(bdw_pwdoms_display, + POWER_DOMAIN_PIPE_B, + POWER_DOMAIN_PIPE_C, + POWER_DOMAIN_PIPE_PANEL_FITTER_B, + POWER_DOMAIN_PIPE_PANEL_FITTER_C, + POWER_DOMAIN_TRANSCODER_A, + POWER_DOMAIN_TRANSCODER_B, + POWER_DOMAIN_TRANSCODER_C, + POWER_DOMAIN_PORT_DDI_LANES_B, + POWER_DOMAIN_PORT_DDI_LANES_C, + POWER_DOMAIN_PORT_DDI_LANES_D, + POWER_DOMAIN_PORT_CRT, /* DDI E */ + POWER_DOMAIN_VGA, + POWER_DOMAIN_AUDIO_MMIO, + POWER_DOMAIN_AUDIO_PLAYBACK, + POWER_DOMAIN_INIT); + +static const struct i915_power_well_desc bdw_power_wells_main[] = { + { + .instances = &I915_PW_INSTANCES( + I915_PW("display", &bdw_pwdoms_display, + .hsw.idx = HSW_PW_CTL_IDX_GLOBAL, + .id = HSW_DISP_PW_GLOBAL), + ), + .ops = &hsw_power_well_ops, + .has_vga = true, + .irq_pipe_mask = BIT(PIPE_B) | BIT(PIPE_C), + }, +}; + +static const struct i915_power_well_desc_list bdw_power_wells[] = { + I915_PW_DESCRIPTORS(i9xx_power_wells_always_on), + I915_PW_DESCRIPTORS(bdw_power_wells_main), +}; + +I915_DECL_PW_DOMAINS(vlv_pwdoms_display, + POWER_DOMAIN_DISPLAY_CORE, + POWER_DOMAIN_PIPE_A, + POWER_DOMAIN_PIPE_B, + POWER_DOMAIN_PIPE_PANEL_FITTER_A, + POWER_DOMAIN_PIPE_PANEL_FITTER_B, + POWER_DOMAIN_TRANSCODER_A, + POWER_DOMAIN_TRANSCODER_B, + POWER_DOMAIN_PORT_DDI_LANES_B, + POWER_DOMAIN_PORT_DDI_LANES_C, + POWER_DOMAIN_PORT_DSI, + POWER_DOMAIN_PORT_CRT, + POWER_DOMAIN_VGA, + POWER_DOMAIN_AUDIO_MMIO, + POWER_DOMAIN_AUDIO_PLAYBACK, + POWER_DOMAIN_AUX_B, + POWER_DOMAIN_AUX_C, + POWER_DOMAIN_GMBUS, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(vlv_pwdoms_dpio_cmn_bc, + POWER_DOMAIN_PORT_DDI_LANES_B, + POWER_DOMAIN_PORT_DDI_LANES_C, + POWER_DOMAIN_PORT_CRT, + POWER_DOMAIN_AUX_B, + POWER_DOMAIN_AUX_C, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(vlv_pwdoms_dpio_tx_bc_lanes, + POWER_DOMAIN_PORT_DDI_LANES_B, + POWER_DOMAIN_PORT_DDI_LANES_C, + POWER_DOMAIN_AUX_B, + POWER_DOMAIN_AUX_C, + POWER_DOMAIN_INIT); + +static const struct i915_power_well_desc vlv_power_wells_main[] = { + { + .instances = &I915_PW_INSTANCES( + I915_PW("display", &vlv_pwdoms_display, + .vlv.idx = PUNIT_PWGT_IDX_DISP2D, + .id = VLV_DISP_PW_DISP2D), + ), + .ops = &vlv_display_power_well_ops, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("dpio-tx-b-01", &vlv_pwdoms_dpio_tx_bc_lanes, + .vlv.idx = PUNIT_PWGT_IDX_DPIO_TX_B_LANES_01), + I915_PW("dpio-tx-b-23", &vlv_pwdoms_dpio_tx_bc_lanes, + .vlv.idx = PUNIT_PWGT_IDX_DPIO_TX_B_LANES_23), + I915_PW("dpio-tx-c-01", &vlv_pwdoms_dpio_tx_bc_lanes, + .vlv.idx = PUNIT_PWGT_IDX_DPIO_TX_C_LANES_01), + I915_PW("dpio-tx-c-23", &vlv_pwdoms_dpio_tx_bc_lanes, + .vlv.idx = PUNIT_PWGT_IDX_DPIO_TX_C_LANES_23), + ), + .ops = &vlv_dpio_power_well_ops, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("dpio-common", &vlv_pwdoms_dpio_cmn_bc, + .vlv.idx = PUNIT_PWGT_IDX_DPIO_CMN_BC, + .id = VLV_DISP_PW_DPIO_CMN_BC), + ), + .ops = &vlv_dpio_cmn_power_well_ops, + }, +}; + +static const struct i915_power_well_desc_list vlv_power_wells[] = { + I915_PW_DESCRIPTORS(i9xx_power_wells_always_on), + I915_PW_DESCRIPTORS(vlv_power_wells_main), +}; + +I915_DECL_PW_DOMAINS(chv_pwdoms_display, + POWER_DOMAIN_DISPLAY_CORE, + POWER_DOMAIN_PIPE_A, + POWER_DOMAIN_PIPE_B, + POWER_DOMAIN_PIPE_C, + POWER_DOMAIN_PIPE_PANEL_FITTER_A, + POWER_DOMAIN_PIPE_PANEL_FITTER_B, + POWER_DOMAIN_PIPE_PANEL_FITTER_C, + POWER_DOMAIN_TRANSCODER_A, + POWER_DOMAIN_TRANSCODER_B, + POWER_DOMAIN_TRANSCODER_C, + POWER_DOMAIN_PORT_DDI_LANES_B, + POWER_DOMAIN_PORT_DDI_LANES_C, + POWER_DOMAIN_PORT_DDI_LANES_D, + POWER_DOMAIN_PORT_DSI, + POWER_DOMAIN_VGA, + POWER_DOMAIN_AUDIO_MMIO, + POWER_DOMAIN_AUDIO_PLAYBACK, + POWER_DOMAIN_AUX_B, + POWER_DOMAIN_AUX_C, + POWER_DOMAIN_AUX_D, + POWER_DOMAIN_GMBUS, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(chv_pwdoms_dpio_cmn_bc, + POWER_DOMAIN_PORT_DDI_LANES_B, + POWER_DOMAIN_PORT_DDI_LANES_C, + POWER_DOMAIN_AUX_B, + POWER_DOMAIN_AUX_C, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(chv_pwdoms_dpio_cmn_d, + POWER_DOMAIN_PORT_DDI_LANES_D, + POWER_DOMAIN_AUX_D, + POWER_DOMAIN_INIT); + +static const struct i915_power_well_desc chv_power_wells_main[] = { + { + /* + * Pipe A power well is the new disp2d well. Pipe B and C + * power wells don't actually exist. Pipe A power well is + * required for any pipe to work. + */ + .instances = &I915_PW_INSTANCES( + I915_PW("display", &chv_pwdoms_display), + ), + .ops = &chv_pipe_power_well_ops, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("dpio-common-bc", &chv_pwdoms_dpio_cmn_bc, + .vlv.idx = PUNIT_PWGT_IDX_DPIO_CMN_BC, + .id = VLV_DISP_PW_DPIO_CMN_BC), + I915_PW("dpio-common-d", &chv_pwdoms_dpio_cmn_d, + .vlv.idx = PUNIT_PWGT_IDX_DPIO_CMN_D, + .id = CHV_DISP_PW_DPIO_CMN_D), + ), + .ops = &chv_dpio_cmn_power_well_ops, + }, +}; + +static const struct i915_power_well_desc_list chv_power_wells[] = { + I915_PW_DESCRIPTORS(i9xx_power_wells_always_on), + I915_PW_DESCRIPTORS(chv_power_wells_main), +}; + +#define SKL_PW_2_POWER_DOMAINS \ + POWER_DOMAIN_PIPE_B, \ + POWER_DOMAIN_PIPE_C, \ + POWER_DOMAIN_PIPE_PANEL_FITTER_B, \ + POWER_DOMAIN_PIPE_PANEL_FITTER_C, \ + POWER_DOMAIN_TRANSCODER_A, \ + POWER_DOMAIN_TRANSCODER_B, \ + POWER_DOMAIN_TRANSCODER_C, \ + POWER_DOMAIN_PORT_DDI_LANES_B, \ + POWER_DOMAIN_PORT_DDI_LANES_C, \ + POWER_DOMAIN_PORT_DDI_LANES_D, \ + POWER_DOMAIN_PORT_DDI_LANES_E, \ + POWER_DOMAIN_VGA, \ + POWER_DOMAIN_AUDIO_MMIO, \ + POWER_DOMAIN_AUDIO_PLAYBACK, \ + POWER_DOMAIN_AUX_B, \ + POWER_DOMAIN_AUX_C, \ + POWER_DOMAIN_AUX_D + +I915_DECL_PW_DOMAINS(skl_pwdoms_pw_2, + SKL_PW_2_POWER_DOMAINS, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(skl_pwdoms_dc_off, + SKL_PW_2_POWER_DOMAINS, + POWER_DOMAIN_AUX_A, + POWER_DOMAIN_MODESET, + POWER_DOMAIN_GT_IRQ, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(skl_pwdoms_ddi_io_a_e, + POWER_DOMAIN_PORT_DDI_IO_A, + POWER_DOMAIN_PORT_DDI_IO_E, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(skl_pwdoms_ddi_io_b, + POWER_DOMAIN_PORT_DDI_IO_B, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(skl_pwdoms_ddi_io_c, + POWER_DOMAIN_PORT_DDI_IO_C, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(skl_pwdoms_ddi_io_d, + POWER_DOMAIN_PORT_DDI_IO_D, + POWER_DOMAIN_INIT); + +static const struct i915_power_well_desc skl_power_wells_pw_1[] = { + { + /* Handled by the DMC firmware */ + .instances = &I915_PW_INSTANCES( + I915_PW("PW_1", I915_PW_DOMAINS_NONE, + .hsw.idx = SKL_PW_CTL_IDX_PW_1, + .id = SKL_DISP_PW_1), + ), + .ops = &hsw_power_well_ops, + .always_on = true, + .has_fuses = true, + }, +}; + +static const struct i915_power_well_desc skl_power_wells_main[] = { + { + /* Handled by the DMC firmware */ + .instances = &I915_PW_INSTANCES( + I915_PW("MISC_IO", I915_PW_DOMAINS_NONE, + .hsw.idx = SKL_PW_CTL_IDX_MISC_IO, + .id = SKL_DISP_PW_MISC_IO), + ), + .ops = &hsw_power_well_ops, + .always_on = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("DC_off", &skl_pwdoms_dc_off, + .id = SKL_DISP_DC_OFF), + ), + .ops = &gen9_dc_off_power_well_ops, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_2", &skl_pwdoms_pw_2, + .hsw.idx = SKL_PW_CTL_IDX_PW_2, + .id = SKL_DISP_PW_2), + ), + .ops = &hsw_power_well_ops, + .has_vga = true, + .irq_pipe_mask = BIT(PIPE_B) | BIT(PIPE_C), + .has_fuses = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("DDI_IO_A_E", &skl_pwdoms_ddi_io_a_e, .hsw.idx = SKL_PW_CTL_IDX_DDI_A_E), + I915_PW("DDI_IO_B", &skl_pwdoms_ddi_io_b, .hsw.idx = SKL_PW_CTL_IDX_DDI_B), + I915_PW("DDI_IO_C", &skl_pwdoms_ddi_io_c, .hsw.idx = SKL_PW_CTL_IDX_DDI_C), + I915_PW("DDI_IO_D", &skl_pwdoms_ddi_io_d, .hsw.idx = SKL_PW_CTL_IDX_DDI_D), + ), + .ops = &hsw_power_well_ops, + }, +}; + +static const struct i915_power_well_desc_list skl_power_wells[] = { + I915_PW_DESCRIPTORS(i9xx_power_wells_always_on), + I915_PW_DESCRIPTORS(skl_power_wells_pw_1), + I915_PW_DESCRIPTORS(skl_power_wells_main), +}; + +#define BXT_PW_2_POWER_DOMAINS \ + POWER_DOMAIN_PIPE_B, \ + POWER_DOMAIN_PIPE_C, \ + POWER_DOMAIN_PIPE_PANEL_FITTER_B, \ + POWER_DOMAIN_PIPE_PANEL_FITTER_C, \ + POWER_DOMAIN_TRANSCODER_A, \ + POWER_DOMAIN_TRANSCODER_B, \ + POWER_DOMAIN_TRANSCODER_C, \ + POWER_DOMAIN_PORT_DDI_LANES_B, \ + POWER_DOMAIN_PORT_DDI_LANES_C, \ + POWER_DOMAIN_VGA, \ + POWER_DOMAIN_AUDIO_MMIO, \ + POWER_DOMAIN_AUDIO_PLAYBACK, \ + POWER_DOMAIN_AUX_B, \ + POWER_DOMAIN_AUX_C + +I915_DECL_PW_DOMAINS(bxt_pwdoms_pw_2, + BXT_PW_2_POWER_DOMAINS, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(bxt_pwdoms_dc_off, + BXT_PW_2_POWER_DOMAINS, + POWER_DOMAIN_AUX_A, + POWER_DOMAIN_GMBUS, + POWER_DOMAIN_MODESET, + POWER_DOMAIN_GT_IRQ, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(bxt_pwdoms_dpio_cmn_a, + POWER_DOMAIN_PORT_DDI_LANES_A, + POWER_DOMAIN_AUX_A, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(bxt_pwdoms_dpio_cmn_bc, + POWER_DOMAIN_PORT_DDI_LANES_B, + POWER_DOMAIN_PORT_DDI_LANES_C, + POWER_DOMAIN_AUX_B, + POWER_DOMAIN_AUX_C, + POWER_DOMAIN_INIT); + +static const struct i915_power_well_desc bxt_power_wells_main[] = { + { + .instances = &I915_PW_INSTANCES( + I915_PW("DC_off", &bxt_pwdoms_dc_off, + .id = SKL_DISP_DC_OFF), + ), + .ops = &gen9_dc_off_power_well_ops, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_2", &bxt_pwdoms_pw_2, + .hsw.idx = SKL_PW_CTL_IDX_PW_2, + .id = SKL_DISP_PW_2), + ), + .ops = &hsw_power_well_ops, + .has_vga = true, + .irq_pipe_mask = BIT(PIPE_B) | BIT(PIPE_C), + .has_fuses = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("dpio-common-a", &bxt_pwdoms_dpio_cmn_a, + .bxt.phy = DPIO_PHY1, + .id = BXT_DISP_PW_DPIO_CMN_A), + I915_PW("dpio-common-bc", &bxt_pwdoms_dpio_cmn_bc, + .bxt.phy = DPIO_PHY0, + .id = VLV_DISP_PW_DPIO_CMN_BC), + ), + .ops = &bxt_dpio_cmn_power_well_ops, + }, +}; + +static const struct i915_power_well_desc_list bxt_power_wells[] = { + I915_PW_DESCRIPTORS(i9xx_power_wells_always_on), + I915_PW_DESCRIPTORS(skl_power_wells_pw_1), + I915_PW_DESCRIPTORS(bxt_power_wells_main), +}; + +#define GLK_PW_2_POWER_DOMAINS \ + POWER_DOMAIN_PIPE_B, \ + POWER_DOMAIN_PIPE_C, \ + POWER_DOMAIN_PIPE_PANEL_FITTER_B, \ + POWER_DOMAIN_PIPE_PANEL_FITTER_C, \ + POWER_DOMAIN_TRANSCODER_A, \ + POWER_DOMAIN_TRANSCODER_B, \ + POWER_DOMAIN_TRANSCODER_C, \ + POWER_DOMAIN_PORT_DDI_LANES_B, \ + POWER_DOMAIN_PORT_DDI_LANES_C, \ + POWER_DOMAIN_VGA, \ + POWER_DOMAIN_AUDIO_MMIO, \ + POWER_DOMAIN_AUDIO_PLAYBACK, \ + POWER_DOMAIN_AUX_B, \ + POWER_DOMAIN_AUX_C + +I915_DECL_PW_DOMAINS(glk_pwdoms_pw_2, + GLK_PW_2_POWER_DOMAINS, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(glk_pwdoms_dc_off, + GLK_PW_2_POWER_DOMAINS, + POWER_DOMAIN_AUX_A, + POWER_DOMAIN_GMBUS, + POWER_DOMAIN_MODESET, + POWER_DOMAIN_GT_IRQ, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(glk_pwdoms_ddi_io_a, POWER_DOMAIN_PORT_DDI_IO_A); +I915_DECL_PW_DOMAINS(glk_pwdoms_ddi_io_b, POWER_DOMAIN_PORT_DDI_IO_B); +I915_DECL_PW_DOMAINS(glk_pwdoms_ddi_io_c, POWER_DOMAIN_PORT_DDI_IO_C); + +I915_DECL_PW_DOMAINS(glk_pwdoms_dpio_cmn_a, + POWER_DOMAIN_PORT_DDI_LANES_A, + POWER_DOMAIN_AUX_A, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(glk_pwdoms_dpio_cmn_b, + POWER_DOMAIN_PORT_DDI_LANES_B, + POWER_DOMAIN_AUX_B, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(glk_pwdoms_dpio_cmn_c, + POWER_DOMAIN_PORT_DDI_LANES_C, + POWER_DOMAIN_AUX_C, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(glk_pwdoms_aux_a, + POWER_DOMAIN_AUX_A, + POWER_DOMAIN_AUX_IO_A, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(glk_pwdoms_aux_b, + POWER_DOMAIN_AUX_B, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(glk_pwdoms_aux_c, + POWER_DOMAIN_AUX_C, + POWER_DOMAIN_INIT); + +static const struct i915_power_well_desc glk_power_wells_main[] = { + { + .instances = &I915_PW_INSTANCES( + I915_PW("DC_off", &glk_pwdoms_dc_off, + .id = SKL_DISP_DC_OFF), + ), + .ops = &gen9_dc_off_power_well_ops, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_2", &glk_pwdoms_pw_2, + .hsw.idx = SKL_PW_CTL_IDX_PW_2, + .id = SKL_DISP_PW_2), + ), + .ops = &hsw_power_well_ops, + .has_vga = true, + .irq_pipe_mask = BIT(PIPE_B) | BIT(PIPE_C), + .has_fuses = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("dpio-common-a", &glk_pwdoms_dpio_cmn_a, + .bxt.phy = DPIO_PHY1, + .id = BXT_DISP_PW_DPIO_CMN_A), + I915_PW("dpio-common-b", &glk_pwdoms_dpio_cmn_b, + .bxt.phy = DPIO_PHY0, + .id = VLV_DISP_PW_DPIO_CMN_BC), + I915_PW("dpio-common-c", &glk_pwdoms_dpio_cmn_c, + .bxt.phy = DPIO_PHY2, + .id = GLK_DISP_PW_DPIO_CMN_C), + ), + .ops = &bxt_dpio_cmn_power_well_ops, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("AUX_A", &glk_pwdoms_aux_a, .hsw.idx = GLK_PW_CTL_IDX_AUX_A), + I915_PW("AUX_B", &glk_pwdoms_aux_b, .hsw.idx = GLK_PW_CTL_IDX_AUX_B), + I915_PW("AUX_C", &glk_pwdoms_aux_c, .hsw.idx = GLK_PW_CTL_IDX_AUX_C), + I915_PW("DDI_IO_A", &glk_pwdoms_ddi_io_a, .hsw.idx = GLK_PW_CTL_IDX_DDI_A), + I915_PW("DDI_IO_B", &glk_pwdoms_ddi_io_b, .hsw.idx = SKL_PW_CTL_IDX_DDI_B), + I915_PW("DDI_IO_C", &glk_pwdoms_ddi_io_c, .hsw.idx = SKL_PW_CTL_IDX_DDI_C), + ), + .ops = &hsw_power_well_ops, + }, +}; + +static const struct i915_power_well_desc_list glk_power_wells[] = { + I915_PW_DESCRIPTORS(i9xx_power_wells_always_on), + I915_PW_DESCRIPTORS(skl_power_wells_pw_1), + I915_PW_DESCRIPTORS(glk_power_wells_main), +}; + +/* + * ICL PW_0/PG_0 domains (HW/DMC control): + * - PCI + * - clocks except port PLL + * - central power except FBC + * - shared functions except pipe interrupts, pipe MBUS, DBUF registers + * ICL PW_1/PG_1 domains (HW/DMC control): + * - DBUF function + * - PIPE_A and its planes, except VGA + * - transcoder EDP + PSR + * - transcoder DSI + * - DDI_A + * - FBC + */ +#define ICL_PW_4_POWER_DOMAINS \ + POWER_DOMAIN_PIPE_C, \ + POWER_DOMAIN_PIPE_PANEL_FITTER_C + +I915_DECL_PW_DOMAINS(icl_pwdoms_pw_4, + ICL_PW_4_POWER_DOMAINS, + POWER_DOMAIN_INIT); + /* VDSC/joining */ + +#define ICL_PW_3_POWER_DOMAINS \ + ICL_PW_4_POWER_DOMAINS, \ + POWER_DOMAIN_PIPE_B, \ + POWER_DOMAIN_PIPE_PANEL_FITTER_B, \ + POWER_DOMAIN_TRANSCODER_A, \ + POWER_DOMAIN_TRANSCODER_B, \ + POWER_DOMAIN_TRANSCODER_C, \ + POWER_DOMAIN_PORT_DDI_LANES_B, \ + POWER_DOMAIN_PORT_DDI_LANES_C, \ + POWER_DOMAIN_PORT_DDI_LANES_D, \ + POWER_DOMAIN_PORT_DDI_LANES_E, \ + POWER_DOMAIN_PORT_DDI_LANES_F, \ + POWER_DOMAIN_VGA, \ + POWER_DOMAIN_AUDIO_MMIO, \ + POWER_DOMAIN_AUDIO_PLAYBACK, \ + POWER_DOMAIN_AUX_B, \ + POWER_DOMAIN_AUX_C, \ + POWER_DOMAIN_AUX_D, \ + POWER_DOMAIN_AUX_E, \ + POWER_DOMAIN_AUX_F, \ + POWER_DOMAIN_AUX_TBT1, \ + POWER_DOMAIN_AUX_TBT2, \ + POWER_DOMAIN_AUX_TBT3, \ + POWER_DOMAIN_AUX_TBT4 + +I915_DECL_PW_DOMAINS(icl_pwdoms_pw_3, + ICL_PW_3_POWER_DOMAINS, + POWER_DOMAIN_INIT); + /* + * - transcoder WD + * - KVMR (HW control) + */ + +#define ICL_PW_2_POWER_DOMAINS \ + ICL_PW_3_POWER_DOMAINS, \ + POWER_DOMAIN_TRANSCODER_VDSC_PW2 + +I915_DECL_PW_DOMAINS(icl_pwdoms_pw_2, + ICL_PW_2_POWER_DOMAINS, + POWER_DOMAIN_INIT); + /* + * - KVMR (HW control) + */ + +I915_DECL_PW_DOMAINS(icl_pwdoms_dc_off, + ICL_PW_2_POWER_DOMAINS, + POWER_DOMAIN_AUX_A, + POWER_DOMAIN_MODESET, + POWER_DOMAIN_DC_OFF, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(icl_pwdoms_ddi_io_d, POWER_DOMAIN_PORT_DDI_IO_D); +I915_DECL_PW_DOMAINS(icl_pwdoms_ddi_io_e, POWER_DOMAIN_PORT_DDI_IO_E); +I915_DECL_PW_DOMAINS(icl_pwdoms_ddi_io_f, POWER_DOMAIN_PORT_DDI_IO_F); + +I915_DECL_PW_DOMAINS(icl_pwdoms_aux_a, + POWER_DOMAIN_AUX_A, + POWER_DOMAIN_AUX_IO_A); +I915_DECL_PW_DOMAINS(icl_pwdoms_aux_b, POWER_DOMAIN_AUX_B); +I915_DECL_PW_DOMAINS(icl_pwdoms_aux_c, POWER_DOMAIN_AUX_C); +I915_DECL_PW_DOMAINS(icl_pwdoms_aux_d, POWER_DOMAIN_AUX_D); +I915_DECL_PW_DOMAINS(icl_pwdoms_aux_e, POWER_DOMAIN_AUX_E); +I915_DECL_PW_DOMAINS(icl_pwdoms_aux_f, POWER_DOMAIN_AUX_F); +I915_DECL_PW_DOMAINS(icl_pwdoms_aux_tbt1, POWER_DOMAIN_AUX_TBT1); +I915_DECL_PW_DOMAINS(icl_pwdoms_aux_tbt2, POWER_DOMAIN_AUX_TBT2); +I915_DECL_PW_DOMAINS(icl_pwdoms_aux_tbt3, POWER_DOMAIN_AUX_TBT3); +I915_DECL_PW_DOMAINS(icl_pwdoms_aux_tbt4, POWER_DOMAIN_AUX_TBT4); + +static const struct i915_power_well_desc icl_power_wells_pw_1[] = { + { + /* Handled by the DMC firmware */ + .instances = &I915_PW_INSTANCES( + I915_PW("PW_1", I915_PW_DOMAINS_NONE, + .hsw.idx = ICL_PW_CTL_IDX_PW_1, + .id = SKL_DISP_PW_1), + ), + .ops = &hsw_power_well_ops, + .always_on = true, + .has_fuses = true, + }, +}; + +static const struct i915_power_well_desc icl_power_wells_main[] = { + { + .instances = &I915_PW_INSTANCES( + I915_PW("DC_off", &icl_pwdoms_dc_off, + .id = SKL_DISP_DC_OFF), + ), + .ops = &gen9_dc_off_power_well_ops, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_2", &icl_pwdoms_pw_2, + .hsw.idx = ICL_PW_CTL_IDX_PW_2, + .id = SKL_DISP_PW_2), + ), + .ops = &hsw_power_well_ops, + .has_fuses = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_3", &icl_pwdoms_pw_3, + .hsw.idx = ICL_PW_CTL_IDX_PW_3, + .id = ICL_DISP_PW_3), + ), + .ops = &hsw_power_well_ops, + .has_vga = true, + .irq_pipe_mask = BIT(PIPE_B), + .has_fuses = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("DDI_IO_A", &glk_pwdoms_ddi_io_a, .hsw.idx = ICL_PW_CTL_IDX_DDI_A), + I915_PW("DDI_IO_B", &glk_pwdoms_ddi_io_b, .hsw.idx = ICL_PW_CTL_IDX_DDI_B), + I915_PW("DDI_IO_C", &glk_pwdoms_ddi_io_c, .hsw.idx = ICL_PW_CTL_IDX_DDI_C), + I915_PW("DDI_IO_D", &icl_pwdoms_ddi_io_d, .hsw.idx = ICL_PW_CTL_IDX_DDI_D), + I915_PW("DDI_IO_E", &icl_pwdoms_ddi_io_e, .hsw.idx = ICL_PW_CTL_IDX_DDI_E), + I915_PW("DDI_IO_F", &icl_pwdoms_ddi_io_f, .hsw.idx = ICL_PW_CTL_IDX_DDI_F), + ), + .ops = &icl_ddi_power_well_ops, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("AUX_A", &icl_pwdoms_aux_a, .hsw.idx = ICL_PW_CTL_IDX_AUX_A), + I915_PW("AUX_B", &icl_pwdoms_aux_b, .hsw.idx = ICL_PW_CTL_IDX_AUX_B), + I915_PW("AUX_C", &icl_pwdoms_aux_c, .hsw.idx = ICL_PW_CTL_IDX_AUX_C), + I915_PW("AUX_D", &icl_pwdoms_aux_d, .hsw.idx = ICL_PW_CTL_IDX_AUX_D), + I915_PW("AUX_E", &icl_pwdoms_aux_e, .hsw.idx = ICL_PW_CTL_IDX_AUX_E), + I915_PW("AUX_F", &icl_pwdoms_aux_f, .hsw.idx = ICL_PW_CTL_IDX_AUX_F), + ), + .ops = &icl_aux_power_well_ops, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("AUX_TBT1", &icl_pwdoms_aux_tbt1, .hsw.idx = ICL_PW_CTL_IDX_AUX_TBT1), + I915_PW("AUX_TBT2", &icl_pwdoms_aux_tbt2, .hsw.idx = ICL_PW_CTL_IDX_AUX_TBT2), + I915_PW("AUX_TBT3", &icl_pwdoms_aux_tbt3, .hsw.idx = ICL_PW_CTL_IDX_AUX_TBT3), + I915_PW("AUX_TBT4", &icl_pwdoms_aux_tbt4, .hsw.idx = ICL_PW_CTL_IDX_AUX_TBT4), + ), + .ops = &icl_aux_power_well_ops, + .is_tc_tbt = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_4", &icl_pwdoms_pw_4, + .hsw.idx = ICL_PW_CTL_IDX_PW_4), + ), + .ops = &hsw_power_well_ops, + .irq_pipe_mask = BIT(PIPE_C), + .has_fuses = true, + }, +}; + +static const struct i915_power_well_desc_list icl_power_wells[] = { + I915_PW_DESCRIPTORS(i9xx_power_wells_always_on), + I915_PW_DESCRIPTORS(icl_power_wells_pw_1), + I915_PW_DESCRIPTORS(icl_power_wells_main), +}; + +#define TGL_PW_5_POWER_DOMAINS \ + POWER_DOMAIN_PIPE_D, \ + POWER_DOMAIN_PIPE_PANEL_FITTER_D, \ + POWER_DOMAIN_TRANSCODER_D + +I915_DECL_PW_DOMAINS(tgl_pwdoms_pw_5, + TGL_PW_5_POWER_DOMAINS, + POWER_DOMAIN_INIT); + +#define TGL_PW_4_POWER_DOMAINS \ + TGL_PW_5_POWER_DOMAINS, \ + POWER_DOMAIN_PIPE_C, \ + POWER_DOMAIN_PIPE_PANEL_FITTER_C, \ + POWER_DOMAIN_TRANSCODER_C + +I915_DECL_PW_DOMAINS(tgl_pwdoms_pw_4, + TGL_PW_4_POWER_DOMAINS, + POWER_DOMAIN_INIT); + +#define TGL_PW_3_POWER_DOMAINS \ + TGL_PW_4_POWER_DOMAINS, \ + POWER_DOMAIN_PIPE_B, \ + POWER_DOMAIN_PIPE_PANEL_FITTER_B, \ + POWER_DOMAIN_TRANSCODER_B, \ + POWER_DOMAIN_PORT_DDI_LANES_TC1, \ + POWER_DOMAIN_PORT_DDI_LANES_TC2, \ + POWER_DOMAIN_PORT_DDI_LANES_TC3, \ + POWER_DOMAIN_PORT_DDI_LANES_TC4, \ + POWER_DOMAIN_PORT_DDI_LANES_TC5, \ + POWER_DOMAIN_PORT_DDI_LANES_TC6, \ + POWER_DOMAIN_VGA, \ + POWER_DOMAIN_AUDIO_MMIO, \ + POWER_DOMAIN_AUDIO_PLAYBACK, \ + POWER_DOMAIN_AUX_USBC1, \ + POWER_DOMAIN_AUX_USBC2, \ + POWER_DOMAIN_AUX_USBC3, \ + POWER_DOMAIN_AUX_USBC4, \ + POWER_DOMAIN_AUX_USBC5, \ + POWER_DOMAIN_AUX_USBC6, \ + POWER_DOMAIN_AUX_TBT1, \ + POWER_DOMAIN_AUX_TBT2, \ + POWER_DOMAIN_AUX_TBT3, \ + POWER_DOMAIN_AUX_TBT4, \ + POWER_DOMAIN_AUX_TBT5, \ + POWER_DOMAIN_AUX_TBT6 + +I915_DECL_PW_DOMAINS(tgl_pwdoms_pw_3, + TGL_PW_3_POWER_DOMAINS, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(tgl_pwdoms_pw_2, + TGL_PW_3_POWER_DOMAINS, + POWER_DOMAIN_TRANSCODER_VDSC_PW2, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(tgl_pwdoms_dc_off, + TGL_PW_3_POWER_DOMAINS, + POWER_DOMAIN_AUX_A, + POWER_DOMAIN_AUX_B, + POWER_DOMAIN_AUX_C, + POWER_DOMAIN_MODESET, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(tgl_pwdoms_ddi_io_tc1, POWER_DOMAIN_PORT_DDI_IO_TC1); +I915_DECL_PW_DOMAINS(tgl_pwdoms_ddi_io_tc2, POWER_DOMAIN_PORT_DDI_IO_TC2); +I915_DECL_PW_DOMAINS(tgl_pwdoms_ddi_io_tc3, POWER_DOMAIN_PORT_DDI_IO_TC3); +I915_DECL_PW_DOMAINS(tgl_pwdoms_ddi_io_tc4, POWER_DOMAIN_PORT_DDI_IO_TC4); +I915_DECL_PW_DOMAINS(tgl_pwdoms_ddi_io_tc5, POWER_DOMAIN_PORT_DDI_IO_TC5); +I915_DECL_PW_DOMAINS(tgl_pwdoms_ddi_io_tc6, POWER_DOMAIN_PORT_DDI_IO_TC6); + +I915_DECL_PW_DOMAINS(tgl_pwdoms_aux_usbc1, POWER_DOMAIN_AUX_USBC1); +I915_DECL_PW_DOMAINS(tgl_pwdoms_aux_usbc2, POWER_DOMAIN_AUX_USBC2); +I915_DECL_PW_DOMAINS(tgl_pwdoms_aux_usbc3, POWER_DOMAIN_AUX_USBC3); +I915_DECL_PW_DOMAINS(tgl_pwdoms_aux_usbc4, POWER_DOMAIN_AUX_USBC4); +I915_DECL_PW_DOMAINS(tgl_pwdoms_aux_usbc5, POWER_DOMAIN_AUX_USBC5); +I915_DECL_PW_DOMAINS(tgl_pwdoms_aux_usbc6, POWER_DOMAIN_AUX_USBC6); + +I915_DECL_PW_DOMAINS(tgl_pwdoms_aux_tbt5, POWER_DOMAIN_AUX_TBT5); +I915_DECL_PW_DOMAINS(tgl_pwdoms_aux_tbt6, POWER_DOMAIN_AUX_TBT6); + +I915_DECL_PW_DOMAINS(tgl_pwdoms_tc_cold_off, + POWER_DOMAIN_AUX_USBC1, + POWER_DOMAIN_AUX_USBC2, + POWER_DOMAIN_AUX_USBC3, + POWER_DOMAIN_AUX_USBC4, + POWER_DOMAIN_AUX_USBC5, + POWER_DOMAIN_AUX_USBC6, + POWER_DOMAIN_AUX_TBT1, + POWER_DOMAIN_AUX_TBT2, + POWER_DOMAIN_AUX_TBT3, + POWER_DOMAIN_AUX_TBT4, + POWER_DOMAIN_AUX_TBT5, + POWER_DOMAIN_AUX_TBT6, + POWER_DOMAIN_TC_COLD_OFF); + +static const struct i915_power_well_desc tgl_power_wells_main[] = { + { + .instances = &I915_PW_INSTANCES( + I915_PW("DC_off", &tgl_pwdoms_dc_off, + .id = SKL_DISP_DC_OFF), + ), + .ops = &gen9_dc_off_power_well_ops, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_2", &tgl_pwdoms_pw_2, + .hsw.idx = ICL_PW_CTL_IDX_PW_2, + .id = SKL_DISP_PW_2), + ), + .ops = &hsw_power_well_ops, + .has_fuses = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_3", &tgl_pwdoms_pw_3, + .hsw.idx = ICL_PW_CTL_IDX_PW_3, + .id = ICL_DISP_PW_3), + ), + .ops = &hsw_power_well_ops, + .has_vga = true, + .irq_pipe_mask = BIT(PIPE_B), + .has_fuses = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("DDI_IO_A", &glk_pwdoms_ddi_io_a, .hsw.idx = ICL_PW_CTL_IDX_DDI_A), + I915_PW("DDI_IO_B", &glk_pwdoms_ddi_io_b, .hsw.idx = ICL_PW_CTL_IDX_DDI_B), + I915_PW("DDI_IO_C", &glk_pwdoms_ddi_io_c, .hsw.idx = ICL_PW_CTL_IDX_DDI_C), + I915_PW("DDI_IO_TC1", &tgl_pwdoms_ddi_io_tc1, .hsw.idx = TGL_PW_CTL_IDX_DDI_TC1), + I915_PW("DDI_IO_TC2", &tgl_pwdoms_ddi_io_tc2, .hsw.idx = TGL_PW_CTL_IDX_DDI_TC2), + I915_PW("DDI_IO_TC3", &tgl_pwdoms_ddi_io_tc3, .hsw.idx = TGL_PW_CTL_IDX_DDI_TC3), + I915_PW("DDI_IO_TC4", &tgl_pwdoms_ddi_io_tc4, .hsw.idx = TGL_PW_CTL_IDX_DDI_TC4), + I915_PW("DDI_IO_TC5", &tgl_pwdoms_ddi_io_tc5, .hsw.idx = TGL_PW_CTL_IDX_DDI_TC5), + I915_PW("DDI_IO_TC6", &tgl_pwdoms_ddi_io_tc6, .hsw.idx = TGL_PW_CTL_IDX_DDI_TC6), + ), + .ops = &icl_ddi_power_well_ops, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_4", &tgl_pwdoms_pw_4, + .hsw.idx = ICL_PW_CTL_IDX_PW_4), + ), + .ops = &hsw_power_well_ops, + .has_fuses = true, + .irq_pipe_mask = BIT(PIPE_C), + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_5", &tgl_pwdoms_pw_5, + .hsw.idx = TGL_PW_CTL_IDX_PW_5), + ), + .ops = &hsw_power_well_ops, + .has_fuses = true, + .irq_pipe_mask = BIT(PIPE_D), + }, +}; + +static const struct i915_power_well_desc tgl_power_wells_tc_cold_off[] = { + { + .instances = &I915_PW_INSTANCES( + I915_PW("TC_cold_off", &tgl_pwdoms_tc_cold_off, + .id = TGL_DISP_PW_TC_COLD_OFF), + ), + .ops = &tgl_tc_cold_off_ops, + }, +}; + +static const struct i915_power_well_desc tgl_power_wells_aux[] = { + { + .instances = &I915_PW_INSTANCES( + I915_PW("AUX_A", &icl_pwdoms_aux_a, .hsw.idx = ICL_PW_CTL_IDX_AUX_A), + I915_PW("AUX_B", &icl_pwdoms_aux_b, .hsw.idx = ICL_PW_CTL_IDX_AUX_B), + I915_PW("AUX_C", &icl_pwdoms_aux_c, .hsw.idx = ICL_PW_CTL_IDX_AUX_C), + I915_PW("AUX_USBC1", &tgl_pwdoms_aux_usbc1, .hsw.idx = TGL_PW_CTL_IDX_AUX_TC1), + I915_PW("AUX_USBC2", &tgl_pwdoms_aux_usbc2, .hsw.idx = TGL_PW_CTL_IDX_AUX_TC2), + I915_PW("AUX_USBC3", &tgl_pwdoms_aux_usbc3, .hsw.idx = TGL_PW_CTL_IDX_AUX_TC3), + I915_PW("AUX_USBC4", &tgl_pwdoms_aux_usbc4, .hsw.idx = TGL_PW_CTL_IDX_AUX_TC4), + I915_PW("AUX_USBC5", &tgl_pwdoms_aux_usbc5, .hsw.idx = TGL_PW_CTL_IDX_AUX_TC5), + I915_PW("AUX_USBC6", &tgl_pwdoms_aux_usbc6, .hsw.idx = TGL_PW_CTL_IDX_AUX_TC6), + ), + .ops = &icl_aux_power_well_ops, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("AUX_TBT1", &icl_pwdoms_aux_tbt1, .hsw.idx = TGL_PW_CTL_IDX_AUX_TBT1), + I915_PW("AUX_TBT2", &icl_pwdoms_aux_tbt2, .hsw.idx = TGL_PW_CTL_IDX_AUX_TBT2), + I915_PW("AUX_TBT3", &icl_pwdoms_aux_tbt3, .hsw.idx = TGL_PW_CTL_IDX_AUX_TBT3), + I915_PW("AUX_TBT4", &icl_pwdoms_aux_tbt4, .hsw.idx = TGL_PW_CTL_IDX_AUX_TBT4), + I915_PW("AUX_TBT5", &tgl_pwdoms_aux_tbt5, .hsw.idx = TGL_PW_CTL_IDX_AUX_TBT5), + I915_PW("AUX_TBT6", &tgl_pwdoms_aux_tbt6, .hsw.idx = TGL_PW_CTL_IDX_AUX_TBT6), + ), + .ops = &icl_aux_power_well_ops, + .is_tc_tbt = true, + }, +}; + +static const struct i915_power_well_desc_list tgl_power_wells[] = { + I915_PW_DESCRIPTORS(i9xx_power_wells_always_on), + I915_PW_DESCRIPTORS(icl_power_wells_pw_1), + I915_PW_DESCRIPTORS(tgl_power_wells_main), + I915_PW_DESCRIPTORS(tgl_power_wells_tc_cold_off), + I915_PW_DESCRIPTORS(tgl_power_wells_aux), +}; + +static const struct i915_power_well_desc_list adls_power_wells[] = { + I915_PW_DESCRIPTORS(i9xx_power_wells_always_on), + I915_PW_DESCRIPTORS(icl_power_wells_pw_1), + I915_PW_DESCRIPTORS(tgl_power_wells_main), + I915_PW_DESCRIPTORS(tgl_power_wells_aux), +}; + +#define RKL_PW_4_POWER_DOMAINS \ + POWER_DOMAIN_PIPE_C, \ + POWER_DOMAIN_PIPE_PANEL_FITTER_C, \ + POWER_DOMAIN_TRANSCODER_C + +I915_DECL_PW_DOMAINS(rkl_pwdoms_pw_4, + RKL_PW_4_POWER_DOMAINS, + POWER_DOMAIN_INIT); + +#define RKL_PW_3_POWER_DOMAINS \ + RKL_PW_4_POWER_DOMAINS, \ + POWER_DOMAIN_PIPE_B, \ + POWER_DOMAIN_PIPE_PANEL_FITTER_B, \ + POWER_DOMAIN_TRANSCODER_B, \ + POWER_DOMAIN_PORT_DDI_LANES_TC1, \ + POWER_DOMAIN_PORT_DDI_LANES_TC2, \ + POWER_DOMAIN_VGA, \ + POWER_DOMAIN_AUDIO_MMIO, \ + POWER_DOMAIN_AUDIO_PLAYBACK, \ + POWER_DOMAIN_AUX_USBC1, \ + POWER_DOMAIN_AUX_USBC2 + +I915_DECL_PW_DOMAINS(rkl_pwdoms_pw_3, + RKL_PW_3_POWER_DOMAINS, + POWER_DOMAIN_INIT); + +/* + * There is no PW_2/PG_2 on RKL. + * + * RKL PW_1/PG_1 domains (under HW/DMC control): + * - DBUF function (note: registers are in PW0) + * - PIPE_A and its planes and VDSC/joining, except VGA + * - transcoder A + * - DDI_A and DDI_B + * - FBC + * + * RKL PW_0/PG_0 domains (under HW/DMC control): + * - PCI + * - clocks except port PLL + * - shared functions: + * * interrupts except pipe interrupts + * * MBus except PIPE_MBUS_DBOX_CTL + * * DBUF registers + * - central power except FBC + * - top-level GTC (DDI-level GTC is in the well associated with the DDI) + */ + +I915_DECL_PW_DOMAINS(rkl_pwdoms_dc_off, + RKL_PW_3_POWER_DOMAINS, + POWER_DOMAIN_AUX_A, + POWER_DOMAIN_AUX_B, + POWER_DOMAIN_MODESET, + POWER_DOMAIN_INIT); + +static const struct i915_power_well_desc rkl_power_wells_main[] = { + { + .instances = &I915_PW_INSTANCES( + I915_PW("DC_off", &rkl_pwdoms_dc_off, + .id = SKL_DISP_DC_OFF), + ), + .ops = &gen9_dc_off_power_well_ops, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_3", &rkl_pwdoms_pw_3, + .hsw.idx = ICL_PW_CTL_IDX_PW_3, + .id = ICL_DISP_PW_3), + ), + .ops = &hsw_power_well_ops, + .irq_pipe_mask = BIT(PIPE_B), + .has_vga = true, + .has_fuses = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_4", &rkl_pwdoms_pw_4, + .hsw.idx = ICL_PW_CTL_IDX_PW_4), + ), + .ops = &hsw_power_well_ops, + .has_fuses = true, + .irq_pipe_mask = BIT(PIPE_C), + }, +}; + +static const struct i915_power_well_desc rkl_power_wells_ddi_aux[] = { + { + .instances = &I915_PW_INSTANCES( + I915_PW("DDI_IO_A", &glk_pwdoms_ddi_io_a, .hsw.idx = ICL_PW_CTL_IDX_DDI_A), + I915_PW("DDI_IO_B", &glk_pwdoms_ddi_io_b, .hsw.idx = ICL_PW_CTL_IDX_DDI_B), + I915_PW("DDI_IO_TC1", &tgl_pwdoms_ddi_io_tc1, .hsw.idx = TGL_PW_CTL_IDX_DDI_TC1), + I915_PW("DDI_IO_TC2", &tgl_pwdoms_ddi_io_tc2, .hsw.idx = TGL_PW_CTL_IDX_DDI_TC2), + ), + .ops = &icl_ddi_power_well_ops, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("AUX_A", &icl_pwdoms_aux_a, .hsw.idx = ICL_PW_CTL_IDX_AUX_A), + I915_PW("AUX_B", &icl_pwdoms_aux_b, .hsw.idx = ICL_PW_CTL_IDX_AUX_B), + I915_PW("AUX_USBC1", &tgl_pwdoms_aux_usbc1, .hsw.idx = TGL_PW_CTL_IDX_AUX_TC1), + I915_PW("AUX_USBC2", &tgl_pwdoms_aux_usbc2, .hsw.idx = TGL_PW_CTL_IDX_AUX_TC2), + ), + .ops = &icl_aux_power_well_ops, + }, +}; + +static const struct i915_power_well_desc_list rkl_power_wells[] = { + I915_PW_DESCRIPTORS(i9xx_power_wells_always_on), + I915_PW_DESCRIPTORS(icl_power_wells_pw_1), + I915_PW_DESCRIPTORS(rkl_power_wells_main), + I915_PW_DESCRIPTORS(rkl_power_wells_ddi_aux), +}; + +/* + * DG1 onwards Audio MMIO/VERBS lies in PG0 power well. + */ +#define DG1_PW_3_POWER_DOMAINS \ + TGL_PW_4_POWER_DOMAINS, \ + POWER_DOMAIN_PIPE_B, \ + POWER_DOMAIN_PIPE_PANEL_FITTER_B, \ + POWER_DOMAIN_TRANSCODER_B, \ + POWER_DOMAIN_PORT_DDI_LANES_TC1, \ + POWER_DOMAIN_PORT_DDI_LANES_TC2, \ + POWER_DOMAIN_VGA, \ + POWER_DOMAIN_AUDIO_PLAYBACK, \ + POWER_DOMAIN_AUX_USBC1, \ + POWER_DOMAIN_AUX_USBC2 + +I915_DECL_PW_DOMAINS(dg1_pwdoms_pw_3, + DG1_PW_3_POWER_DOMAINS, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(dg1_pwdoms_dc_off, + DG1_PW_3_POWER_DOMAINS, + POWER_DOMAIN_AUDIO_MMIO, + POWER_DOMAIN_AUX_A, + POWER_DOMAIN_AUX_B, + POWER_DOMAIN_MODESET, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(dg1_pwdoms_pw_2, + DG1_PW_3_POWER_DOMAINS, + POWER_DOMAIN_TRANSCODER_VDSC_PW2, + POWER_DOMAIN_INIT); + +static const struct i915_power_well_desc dg1_power_wells_main[] = { + { + .instances = &I915_PW_INSTANCES( + I915_PW("DC_off", &dg1_pwdoms_dc_off, + .id = SKL_DISP_DC_OFF), + ), + .ops = &gen9_dc_off_power_well_ops, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_2", &dg1_pwdoms_pw_2, + .hsw.idx = ICL_PW_CTL_IDX_PW_2, + .id = SKL_DISP_PW_2), + ), + .ops = &hsw_power_well_ops, + .has_fuses = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_3", &dg1_pwdoms_pw_3, + .hsw.idx = ICL_PW_CTL_IDX_PW_3, + .id = ICL_DISP_PW_3), + ), + .ops = &hsw_power_well_ops, + .irq_pipe_mask = BIT(PIPE_B), + .has_vga = true, + .has_fuses = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_4", &tgl_pwdoms_pw_4, + .hsw.idx = ICL_PW_CTL_IDX_PW_4), + ), + .ops = &hsw_power_well_ops, + .has_fuses = true, + .irq_pipe_mask = BIT(PIPE_C), + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_5", &tgl_pwdoms_pw_5, + .hsw.idx = TGL_PW_CTL_IDX_PW_5), + ), + .ops = &hsw_power_well_ops, + .has_fuses = true, + .irq_pipe_mask = BIT(PIPE_D), + }, +}; + +static const struct i915_power_well_desc_list dg1_power_wells[] = { + I915_PW_DESCRIPTORS(i9xx_power_wells_always_on), + I915_PW_DESCRIPTORS(icl_power_wells_pw_1), + I915_PW_DESCRIPTORS(dg1_power_wells_main), + I915_PW_DESCRIPTORS(rkl_power_wells_ddi_aux), +}; + +/* + * XE_LPD Power Domains + * + * Previous platforms required that PG(n-1) be enabled before PG(n). That + * dependency chain turns into a dependency tree on XE_LPD: + * + * PG0 + * | + * --PG1-- + * / \ + * PGA --PG2-- + * / | \ + * PGB PGC PGD + * + * Power wells must be enabled from top to bottom and disabled from bottom + * to top. This allows pipes to be power gated independently. + */ + +#define XELPD_PW_D_POWER_DOMAINS \ + POWER_DOMAIN_PIPE_D, \ + POWER_DOMAIN_PIPE_PANEL_FITTER_D, \ + POWER_DOMAIN_TRANSCODER_D + +I915_DECL_PW_DOMAINS(xelpd_pwdoms_pw_d, + XELPD_PW_D_POWER_DOMAINS, + POWER_DOMAIN_INIT); + +#define XELPD_PW_C_POWER_DOMAINS \ + POWER_DOMAIN_PIPE_C, \ + POWER_DOMAIN_PIPE_PANEL_FITTER_C, \ + POWER_DOMAIN_TRANSCODER_C + +I915_DECL_PW_DOMAINS(xelpd_pwdoms_pw_c, + XELPD_PW_C_POWER_DOMAINS, + POWER_DOMAIN_INIT); + +#define XELPD_PW_B_POWER_DOMAINS \ + POWER_DOMAIN_PIPE_B, \ + POWER_DOMAIN_PIPE_PANEL_FITTER_B, \ + POWER_DOMAIN_TRANSCODER_B + +I915_DECL_PW_DOMAINS(xelpd_pwdoms_pw_b, + XELPD_PW_B_POWER_DOMAINS, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(xelpd_pwdoms_pw_a, + POWER_DOMAIN_PIPE_A, + POWER_DOMAIN_PIPE_PANEL_FITTER_A, + POWER_DOMAIN_INIT); + +#define XELPD_PW_2_POWER_DOMAINS \ + XELPD_PW_B_POWER_DOMAINS, \ + XELPD_PW_C_POWER_DOMAINS, \ + XELPD_PW_D_POWER_DOMAINS, \ + POWER_DOMAIN_PORT_DDI_LANES_C, \ + POWER_DOMAIN_PORT_DDI_LANES_D, \ + POWER_DOMAIN_PORT_DDI_LANES_E, \ + POWER_DOMAIN_PORT_DDI_LANES_TC1, \ + POWER_DOMAIN_PORT_DDI_LANES_TC2, \ + POWER_DOMAIN_PORT_DDI_LANES_TC3, \ + POWER_DOMAIN_PORT_DDI_LANES_TC4, \ + POWER_DOMAIN_VGA, \ + POWER_DOMAIN_AUDIO_PLAYBACK, \ + POWER_DOMAIN_AUX_C, \ + POWER_DOMAIN_AUX_D, \ + POWER_DOMAIN_AUX_E, \ + POWER_DOMAIN_AUX_USBC1, \ + POWER_DOMAIN_AUX_USBC2, \ + POWER_DOMAIN_AUX_USBC3, \ + POWER_DOMAIN_AUX_USBC4, \ + POWER_DOMAIN_AUX_TBT1, \ + POWER_DOMAIN_AUX_TBT2, \ + POWER_DOMAIN_AUX_TBT3, \ + POWER_DOMAIN_AUX_TBT4 + +I915_DECL_PW_DOMAINS(xelpd_pwdoms_pw_2, + XELPD_PW_2_POWER_DOMAINS, + POWER_DOMAIN_INIT); + +/* + * XELPD PW_1/PG_1 domains (under HW/DMC control): + * - DBUF function (registers are in PW0) + * - Transcoder A + * - DDI_A and DDI_B + * + * XELPD PW_0/PW_1 domains (under HW/DMC control): + * - PCI + * - Clocks except port PLL + * - Shared functions: + * * interrupts except pipe interrupts + * * MBus except PIPE_MBUS_DBOX_CTL + * * DBUF registers + * - Central power except FBC + * - Top-level GTC (DDI-level GTC is in the well associated with the DDI) + */ + +I915_DECL_PW_DOMAINS(xelpd_pwdoms_dc_off, + XELPD_PW_2_POWER_DOMAINS, + POWER_DOMAIN_PORT_DSI, + POWER_DOMAIN_AUDIO_MMIO, + POWER_DOMAIN_AUX_A, + POWER_DOMAIN_AUX_B, + POWER_DOMAIN_MODESET, + POWER_DOMAIN_INIT); + +static const struct i915_power_well_desc xelpd_power_wells_main[] = { + { + .instances = &I915_PW_INSTANCES( + I915_PW("DC_off", &xelpd_pwdoms_dc_off, + .id = SKL_DISP_DC_OFF), + ), + .ops = &gen9_dc_off_power_well_ops, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_2", &xelpd_pwdoms_pw_2, + .hsw.idx = ICL_PW_CTL_IDX_PW_2, + .id = SKL_DISP_PW_2), + ), + .ops = &hsw_power_well_ops, + .has_vga = true, + .has_fuses = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_A", &xelpd_pwdoms_pw_a, + .hsw.idx = XELPD_PW_CTL_IDX_PW_A), + ), + .ops = &hsw_power_well_ops, + .irq_pipe_mask = BIT(PIPE_A), + .has_fuses = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_B", &xelpd_pwdoms_pw_b, + .hsw.idx = XELPD_PW_CTL_IDX_PW_B), + ), + .ops = &hsw_power_well_ops, + .irq_pipe_mask = BIT(PIPE_B), + .has_fuses = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_C", &xelpd_pwdoms_pw_c, + .hsw.idx = XELPD_PW_CTL_IDX_PW_C), + ), + .ops = &hsw_power_well_ops, + .irq_pipe_mask = BIT(PIPE_C), + .has_fuses = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_D", &xelpd_pwdoms_pw_d, + .hsw.idx = XELPD_PW_CTL_IDX_PW_D), + ), + .ops = &hsw_power_well_ops, + .irq_pipe_mask = BIT(PIPE_D), + .has_fuses = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("DDI_IO_A", &glk_pwdoms_ddi_io_a, .hsw.idx = ICL_PW_CTL_IDX_DDI_A), + I915_PW("DDI_IO_B", &glk_pwdoms_ddi_io_b, .hsw.idx = ICL_PW_CTL_IDX_DDI_B), + I915_PW("DDI_IO_C", &glk_pwdoms_ddi_io_c, .hsw.idx = ICL_PW_CTL_IDX_DDI_C), + I915_PW("DDI_IO_D", &icl_pwdoms_ddi_io_d, .hsw.idx = XELPD_PW_CTL_IDX_DDI_D), + I915_PW("DDI_IO_E", &icl_pwdoms_ddi_io_e, .hsw.idx = XELPD_PW_CTL_IDX_DDI_E), + I915_PW("DDI_IO_TC1", &tgl_pwdoms_ddi_io_tc1, .hsw.idx = TGL_PW_CTL_IDX_DDI_TC1), + I915_PW("DDI_IO_TC2", &tgl_pwdoms_ddi_io_tc2, .hsw.idx = TGL_PW_CTL_IDX_DDI_TC2), + I915_PW("DDI_IO_TC3", &tgl_pwdoms_ddi_io_tc3, .hsw.idx = TGL_PW_CTL_IDX_DDI_TC3), + I915_PW("DDI_IO_TC4", &tgl_pwdoms_ddi_io_tc4, .hsw.idx = TGL_PW_CTL_IDX_DDI_TC4), + ), + .ops = &icl_ddi_power_well_ops, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("AUX_A", &icl_pwdoms_aux_a, .hsw.idx = ICL_PW_CTL_IDX_AUX_A), + I915_PW("AUX_B", &icl_pwdoms_aux_b, .hsw.idx = ICL_PW_CTL_IDX_AUX_B), + I915_PW("AUX_C", &icl_pwdoms_aux_c, .hsw.idx = ICL_PW_CTL_IDX_AUX_C), + I915_PW("AUX_D", &icl_pwdoms_aux_d, .hsw.idx = XELPD_PW_CTL_IDX_AUX_D), + I915_PW("AUX_E", &icl_pwdoms_aux_e, .hsw.idx = XELPD_PW_CTL_IDX_AUX_E), + I915_PW("AUX_USBC1", &tgl_pwdoms_aux_usbc1, .hsw.idx = TGL_PW_CTL_IDX_AUX_TC1), + I915_PW("AUX_USBC2", &tgl_pwdoms_aux_usbc2, .hsw.idx = TGL_PW_CTL_IDX_AUX_TC2), + I915_PW("AUX_USBC3", &tgl_pwdoms_aux_usbc3, .hsw.idx = TGL_PW_CTL_IDX_AUX_TC3), + I915_PW("AUX_USBC4", &tgl_pwdoms_aux_usbc4, .hsw.idx = TGL_PW_CTL_IDX_AUX_TC4), + ), + .ops = &icl_aux_power_well_ops, + .fixed_enable_delay = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("AUX_TBT1", &icl_pwdoms_aux_tbt1, .hsw.idx = TGL_PW_CTL_IDX_AUX_TBT1), + I915_PW("AUX_TBT2", &icl_pwdoms_aux_tbt2, .hsw.idx = TGL_PW_CTL_IDX_AUX_TBT2), + I915_PW("AUX_TBT3", &icl_pwdoms_aux_tbt3, .hsw.idx = TGL_PW_CTL_IDX_AUX_TBT3), + I915_PW("AUX_TBT4", &icl_pwdoms_aux_tbt4, .hsw.idx = TGL_PW_CTL_IDX_AUX_TBT4), + ), + .ops = &icl_aux_power_well_ops, + .is_tc_tbt = true, + }, +}; + +static const struct i915_power_well_desc_list xelpd_power_wells[] = { + I915_PW_DESCRIPTORS(i9xx_power_wells_always_on), + I915_PW_DESCRIPTORS(icl_power_wells_pw_1), + I915_PW_DESCRIPTORS(xelpd_power_wells_main), +}; + +/* + * MTL is based on XELPD power domains with the exception of power gating for: + * - DDI_IO (moved to PLL logic) + * - AUX and AUX_IO functionality and register access for USBC1-4 (PICA always-on) + */ +#define XELPDP_PW_2_POWER_DOMAINS \ + XELPD_PW_B_POWER_DOMAINS, \ + XELPD_PW_C_POWER_DOMAINS, \ + XELPD_PW_D_POWER_DOMAINS, \ + POWER_DOMAIN_AUDIO_PLAYBACK, \ + POWER_DOMAIN_VGA, \ + POWER_DOMAIN_PORT_DDI_LANES_TC1, \ + POWER_DOMAIN_PORT_DDI_LANES_TC2, \ + POWER_DOMAIN_PORT_DDI_LANES_TC3, \ + POWER_DOMAIN_PORT_DDI_LANES_TC4 + +I915_DECL_PW_DOMAINS(xelpdp_pwdoms_pw_2, + XELPDP_PW_2_POWER_DOMAINS, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(xelpdp_pwdoms_dc_off, + XELPDP_PW_2_POWER_DOMAINS, + POWER_DOMAIN_AUDIO_MMIO, + POWER_DOMAIN_MODESET, + POWER_DOMAIN_AUX_A, + POWER_DOMAIN_AUX_B, + POWER_DOMAIN_INIT); + +I915_DECL_PW_DOMAINS(xelpdp_pwdoms_aux_tc1, + POWER_DOMAIN_AUX_USBC1, + POWER_DOMAIN_AUX_TBT1); + +I915_DECL_PW_DOMAINS(xelpdp_pwdoms_aux_tc2, + POWER_DOMAIN_AUX_USBC2, + POWER_DOMAIN_AUX_TBT2); + +I915_DECL_PW_DOMAINS(xelpdp_pwdoms_aux_tc3, + POWER_DOMAIN_AUX_USBC3, + POWER_DOMAIN_AUX_TBT3); + +I915_DECL_PW_DOMAINS(xelpdp_pwdoms_aux_tc4, + POWER_DOMAIN_AUX_USBC4, + POWER_DOMAIN_AUX_TBT4); + +static const struct i915_power_well_desc xelpdp_power_wells_main[] = { + { + .instances = &I915_PW_INSTANCES( + I915_PW("DC_off", &xelpdp_pwdoms_dc_off, + .id = SKL_DISP_DC_OFF), + ), + .ops = &gen9_dc_off_power_well_ops, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_2", &xelpdp_pwdoms_pw_2, + .hsw.idx = ICL_PW_CTL_IDX_PW_2, + .id = SKL_DISP_PW_2), + ), + .ops = &hsw_power_well_ops, + .has_vga = true, + .has_fuses = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_A", &xelpd_pwdoms_pw_a, + .hsw.idx = XELPD_PW_CTL_IDX_PW_A), + ), + .ops = &hsw_power_well_ops, + .irq_pipe_mask = BIT(PIPE_A), + .has_fuses = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_B", &xelpd_pwdoms_pw_b, + .hsw.idx = XELPD_PW_CTL_IDX_PW_B), + ), + .ops = &hsw_power_well_ops, + .irq_pipe_mask = BIT(PIPE_B), + .has_fuses = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_C", &xelpd_pwdoms_pw_c, + .hsw.idx = XELPD_PW_CTL_IDX_PW_C), + ), + .ops = &hsw_power_well_ops, + .irq_pipe_mask = BIT(PIPE_C), + .has_fuses = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("PW_D", &xelpd_pwdoms_pw_d, + .hsw.idx = XELPD_PW_CTL_IDX_PW_D), + ), + .ops = &hsw_power_well_ops, + .irq_pipe_mask = BIT(PIPE_D), + .has_fuses = true, + }, { + .instances = &I915_PW_INSTANCES( + I915_PW("AUX_A", &icl_pwdoms_aux_a, .xelpdp.aux_ch = AUX_CH_A), + I915_PW("AUX_B", &icl_pwdoms_aux_b, .xelpdp.aux_ch = AUX_CH_B), + I915_PW("AUX_TC1", &xelpdp_pwdoms_aux_tc1, .xelpdp.aux_ch = AUX_CH_USBC1), + I915_PW("AUX_TC2", &xelpdp_pwdoms_aux_tc2, .xelpdp.aux_ch = AUX_CH_USBC2), + I915_PW("AUX_TC3", &xelpdp_pwdoms_aux_tc3, .xelpdp.aux_ch = AUX_CH_USBC3), + I915_PW("AUX_TC4", &xelpdp_pwdoms_aux_tc4, .xelpdp.aux_ch = AUX_CH_USBC4), + ), + .ops = &xelpdp_aux_power_well_ops, + }, +}; + +static const struct i915_power_well_desc_list xelpdp_power_wells[] = { + I915_PW_DESCRIPTORS(i9xx_power_wells_always_on), + I915_PW_DESCRIPTORS(icl_power_wells_pw_1), + I915_PW_DESCRIPTORS(xelpdp_power_wells_main), +}; + +static void init_power_well_domains(const struct i915_power_well_instance *inst, + struct i915_power_well *power_well) +{ + int j; + + if (!inst->domain_list) + return; + + if (inst->domain_list->count == 0) { + bitmap_fill(power_well->domains.bits, POWER_DOMAIN_NUM); + + return; + } + + for (j = 0; j < inst->domain_list->count; j++) + set_bit(inst->domain_list->list[j], power_well->domains.bits); +} + +#define for_each_power_well_instance_in_desc_list(_desc_list, _desc_count, _desc, _inst) \ + for ((_desc) = (_desc_list); (_desc) - (_desc_list) < (_desc_count); (_desc)++) \ + for ((_inst) = (_desc)->instances->list; \ + (_inst) - (_desc)->instances->list < (_desc)->instances->count; \ + (_inst)++) + +#define for_each_power_well_instance(_desc_list, _desc_count, _descs, _desc, _inst) \ + for ((_descs) = (_desc_list); \ + (_descs) - (_desc_list) < (_desc_count); \ + (_descs)++) \ + for_each_power_well_instance_in_desc_list((_descs)->list, (_descs)->count, \ + (_desc), (_inst)) + +static int +__set_power_wells(struct i915_power_domains *power_domains, + const struct i915_power_well_desc_list *power_well_descs, + int power_well_descs_sz) +{ + struct drm_i915_private *i915 = container_of(power_domains, + struct drm_i915_private, + display.power.domains); + u64 power_well_ids = 0; + const struct i915_power_well_desc_list *desc_list; + const struct i915_power_well_desc *desc; + const struct i915_power_well_instance *inst; + int power_well_count = 0; + int plt_idx = 0; + + for_each_power_well_instance(power_well_descs, power_well_descs_sz, desc_list, desc, inst) + power_well_count++; + + power_domains->power_well_count = power_well_count; + power_domains->power_wells = + kcalloc(power_well_count, + sizeof(*power_domains->power_wells), + GFP_KERNEL); + if (!power_domains->power_wells) + return -ENOMEM; + + for_each_power_well_instance(power_well_descs, power_well_descs_sz, desc_list, desc, inst) { + struct i915_power_well *pw = &power_domains->power_wells[plt_idx]; + enum i915_power_well_id id = inst->id; + + pw->desc = desc; + drm_WARN_ON(&i915->drm, + overflows_type(inst - desc->instances->list, pw->instance_idx)); + pw->instance_idx = inst - desc->instances->list; + + init_power_well_domains(inst, pw); + + plt_idx++; + + if (id == DISP_PW_ID_NONE) + continue; + + drm_WARN_ON(&i915->drm, id >= sizeof(power_well_ids) * 8); + drm_WARN_ON(&i915->drm, power_well_ids & BIT_ULL(id)); + power_well_ids |= BIT_ULL(id); + } + + return 0; +} + +#define set_power_wells(power_domains, __power_well_descs) \ + __set_power_wells(power_domains, __power_well_descs, \ + ARRAY_SIZE(__power_well_descs)) + +/** + * intel_display_power_map_init - initialize power domain -> power well mappings + * @power_domains: power domain state + * + * Creates all the power wells for the current platform, initializes the + * dynamic state for them and initializes the mapping of each power well to + * all the power domains the power well belongs to. + */ +int intel_display_power_map_init(struct i915_power_domains *power_domains) +{ + struct drm_i915_private *i915 = container_of(power_domains, + struct drm_i915_private, + display.power.domains); + /* + * The enabling order will be from lower to higher indexed wells, + * the disabling order is reversed. + */ + if (!HAS_DISPLAY(i915)) { + power_domains->power_well_count = 0; + return 0; + } + + if (DISPLAY_VER(i915) >= 14) + return set_power_wells(power_domains, xelpdp_power_wells); + else if (DISPLAY_VER(i915) >= 13) + return set_power_wells(power_domains, xelpd_power_wells); + else if (IS_DG1(i915)) + return set_power_wells(power_domains, dg1_power_wells); + else if (IS_ALDERLAKE_S(i915)) + return set_power_wells(power_domains, adls_power_wells); + else if (IS_ROCKETLAKE(i915)) + return set_power_wells(power_domains, rkl_power_wells); + else if (DISPLAY_VER(i915) == 12) + return set_power_wells(power_domains, tgl_power_wells); + else if (DISPLAY_VER(i915) == 11) + return set_power_wells(power_domains, icl_power_wells); + else if (IS_GEMINILAKE(i915)) + return set_power_wells(power_domains, glk_power_wells); + else if (IS_BROXTON(i915)) + return set_power_wells(power_domains, bxt_power_wells); + else if (DISPLAY_VER(i915) == 9) + return set_power_wells(power_domains, skl_power_wells); + else if (IS_CHERRYVIEW(i915)) + return set_power_wells(power_domains, chv_power_wells); + else if (IS_BROADWELL(i915)) + return set_power_wells(power_domains, bdw_power_wells); + else if (IS_HASWELL(i915)) + return set_power_wells(power_domains, hsw_power_wells); + else if (IS_VALLEYVIEW(i915)) + return set_power_wells(power_domains, vlv_power_wells); + else if (IS_I830(i915)) + return set_power_wells(power_domains, i830_power_wells); + else + return set_power_wells(power_domains, i9xx_power_wells); +} + +/** + * intel_display_power_map_cleanup - clean up power domain -> power well mappings + * @power_domains: power domain state + * + * Cleans up all the state that was initialized by intel_display_power_map_init(). + */ +void intel_display_power_map_cleanup(struct i915_power_domains *power_domains) +{ + kfree(power_domains->power_wells); +} diff --git a/drivers/gpu/drm/i915/display/intel_display_power_map.h b/drivers/gpu/drm/i915/display/intel_display_power_map.h new file mode 100644 index 000000000..da8f7055a --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_display_power_map.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2022 Intel Corporation + */ + +#ifndef __INTEL_DISPLAY_POWER_MAP_H__ +#define __INTEL_DISPLAY_POWER_MAP_H__ + +struct i915_power_domains; + +int intel_display_power_map_init(struct i915_power_domains *power_domains); +void intel_display_power_map_cleanup(struct i915_power_domains *power_domains); + +#endif diff --git a/drivers/gpu/drm/i915/display/intel_display_power_well.c b/drivers/gpu/drm/i915/display/intel_display_power_well.c new file mode 100644 index 000000000..1d18eee56 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_display_power_well.c @@ -0,0 +1,1956 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2022 Intel Corporation + */ + +#include "i915_drv.h" +#include "i915_irq.h" +#include "intel_backlight_regs.h" +#include "intel_combo_phy.h" +#include "intel_combo_phy_regs.h" +#include "intel_crt.h" +#include "intel_de.h" +#include "intel_display_power_well.h" +#include "intel_display_types.h" +#include "intel_dkl_phy.h" +#include "intel_dmc.h" +#include "intel_dpio_phy.h" +#include "intel_dpll.h" +#include "intel_hotplug.h" +#include "intel_pcode.h" +#include "intel_pps.h" +#include "intel_tc.h" +#include "intel_vga.h" +#include "skl_watermark.h" +#include "vlv_sideband.h" +#include "vlv_sideband_reg.h" + +struct i915_power_well_regs { + i915_reg_t bios; + i915_reg_t driver; + i915_reg_t kvmr; + i915_reg_t debug; +}; + +struct i915_power_well_ops { + const struct i915_power_well_regs *regs; + /* + * Synchronize the well's hw state to match the current sw state, for + * example enable/disable it based on the current refcount. Called + * during driver init and resume time, possibly after first calling + * the enable/disable handlers. + */ + void (*sync_hw)(struct drm_i915_private *i915, + struct i915_power_well *power_well); + /* + * Enable the well and resources that depend on it (for example + * interrupts located on the well). Called after the 0->1 refcount + * transition. + */ + void (*enable)(struct drm_i915_private *i915, + struct i915_power_well *power_well); + /* + * Disable the well and resources that depend on it. Called after + * the 1->0 refcount transition. + */ + void (*disable)(struct drm_i915_private *i915, + struct i915_power_well *power_well); + /* Returns the hw enabled state. */ + bool (*is_enabled)(struct drm_i915_private *i915, + struct i915_power_well *power_well); +}; + +static const struct i915_power_well_instance * +i915_power_well_instance(const struct i915_power_well *power_well) +{ + return &power_well->desc->instances->list[power_well->instance_idx]; +} + +struct i915_power_well * +lookup_power_well(struct drm_i915_private *i915, + enum i915_power_well_id power_well_id) +{ + struct i915_power_well *power_well; + + for_each_power_well(i915, power_well) + if (i915_power_well_instance(power_well)->id == power_well_id) + return power_well; + + /* + * It's not feasible to add error checking code to the callers since + * this condition really shouldn't happen and it doesn't even make sense + * to abort things like display initialization sequences. Just return + * the first power well and hope the WARN gets reported so we can fix + * our driver. + */ + drm_WARN(&i915->drm, 1, + "Power well %d not defined for this platform\n", + power_well_id); + return &i915->display.power.domains.power_wells[0]; +} + +void intel_power_well_enable(struct drm_i915_private *i915, + struct i915_power_well *power_well) +{ + drm_dbg_kms(&i915->drm, "enabling %s\n", intel_power_well_name(power_well)); + power_well->desc->ops->enable(i915, power_well); + power_well->hw_enabled = true; +} + +void intel_power_well_disable(struct drm_i915_private *i915, + struct i915_power_well *power_well) +{ + drm_dbg_kms(&i915->drm, "disabling %s\n", intel_power_well_name(power_well)); + power_well->hw_enabled = false; + power_well->desc->ops->disable(i915, power_well); +} + +void intel_power_well_sync_hw(struct drm_i915_private *i915, + struct i915_power_well *power_well) +{ + power_well->desc->ops->sync_hw(i915, power_well); + power_well->hw_enabled = + power_well->desc->ops->is_enabled(i915, power_well); +} + +void intel_power_well_get(struct drm_i915_private *i915, + struct i915_power_well *power_well) +{ + if (!power_well->count++) + intel_power_well_enable(i915, power_well); +} + +void intel_power_well_put(struct drm_i915_private *i915, + struct i915_power_well *power_well) +{ + drm_WARN(&i915->drm, !power_well->count, + "Use count on power well %s is already zero", + i915_power_well_instance(power_well)->name); + + if (!--power_well->count) + intel_power_well_disable(i915, power_well); +} + +bool intel_power_well_is_enabled(struct drm_i915_private *i915, + struct i915_power_well *power_well) +{ + return power_well->desc->ops->is_enabled(i915, power_well); +} + +bool intel_power_well_is_enabled_cached(struct i915_power_well *power_well) +{ + return power_well->hw_enabled; +} + +bool intel_display_power_well_is_enabled(struct drm_i915_private *dev_priv, + enum i915_power_well_id power_well_id) +{ + struct i915_power_well *power_well; + + power_well = lookup_power_well(dev_priv, power_well_id); + + return intel_power_well_is_enabled(dev_priv, power_well); +} + +bool intel_power_well_is_always_on(struct i915_power_well *power_well) +{ + return power_well->desc->always_on; +} + +const char *intel_power_well_name(struct i915_power_well *power_well) +{ + return i915_power_well_instance(power_well)->name; +} + +struct intel_power_domain_mask *intel_power_well_domains(struct i915_power_well *power_well) +{ + return &power_well->domains; +} + +int intel_power_well_refcount(struct i915_power_well *power_well) +{ + return power_well->count; +} + +/* + * Starting with Haswell, we have a "Power Down Well" that can be turned off + * when not needed anymore. We have 4 registers that can request the power well + * to be enabled, and it will only be disabled if none of the registers is + * requesting it to be enabled. + */ +static void hsw_power_well_post_enable(struct drm_i915_private *dev_priv, + u8 irq_pipe_mask, bool has_vga) +{ + if (has_vga) + intel_vga_reset_io_mem(dev_priv); + + if (irq_pipe_mask) + gen8_irq_power_well_post_enable(dev_priv, irq_pipe_mask); +} + +static void hsw_power_well_pre_disable(struct drm_i915_private *dev_priv, + u8 irq_pipe_mask) +{ + if (irq_pipe_mask) + gen8_irq_power_well_pre_disable(dev_priv, irq_pipe_mask); +} + +#define ICL_AUX_PW_TO_CH(pw_idx) \ + ((pw_idx) - ICL_PW_CTL_IDX_AUX_A + AUX_CH_A) + +#define ICL_TBT_AUX_PW_TO_CH(pw_idx) \ + ((pw_idx) - ICL_PW_CTL_IDX_AUX_TBT1 + AUX_CH_C) + +static enum aux_ch icl_aux_pw_to_ch(const struct i915_power_well *power_well) +{ + int pw_idx = i915_power_well_instance(power_well)->hsw.idx; + + return power_well->desc->is_tc_tbt ? ICL_TBT_AUX_PW_TO_CH(pw_idx) : + ICL_AUX_PW_TO_CH(pw_idx); +} + +static struct intel_digital_port * +aux_ch_to_digital_port(struct drm_i915_private *dev_priv, + enum aux_ch aux_ch) +{ + struct intel_digital_port *dig_port = NULL; + struct intel_encoder *encoder; + + for_each_intel_encoder(&dev_priv->drm, encoder) { + /* We'll check the MST primary port */ + if (encoder->type == INTEL_OUTPUT_DP_MST) + continue; + + dig_port = enc_to_dig_port(encoder); + if (!dig_port) + continue; + + if (dig_port->aux_ch != aux_ch) { + dig_port = NULL; + continue; + } + + break; + } + + return dig_port; +} + +static enum phy icl_aux_pw_to_phy(struct drm_i915_private *i915, + const struct i915_power_well *power_well) +{ + enum aux_ch aux_ch = icl_aux_pw_to_ch(power_well); + struct intel_digital_port *dig_port = aux_ch_to_digital_port(i915, aux_ch); + + return intel_port_to_phy(i915, dig_port->base.port); +} + +static void hsw_wait_for_power_well_enable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well, + bool timeout_expected) +{ + const struct i915_power_well_regs *regs = power_well->desc->ops->regs; + int pw_idx = i915_power_well_instance(power_well)->hsw.idx; + + /* + * For some power wells we're not supposed to watch the status bit for + * an ack, but rather just wait a fixed amount of time and then + * proceed. This is only used on DG2. + */ + if (IS_DG2(dev_priv) && power_well->desc->fixed_enable_delay) { + usleep_range(600, 1200); + return; + } + + /* Timeout for PW1:10 us, AUX:not specified, other PWs:20 us. */ + if (intel_de_wait_for_set(dev_priv, regs->driver, + HSW_PWR_WELL_CTL_STATE(pw_idx), 1)) { + drm_dbg_kms(&dev_priv->drm, "%s power well enable timeout\n", + intel_power_well_name(power_well)); + + drm_WARN_ON(&dev_priv->drm, !timeout_expected); + + } +} + +static u32 hsw_power_well_requesters(struct drm_i915_private *dev_priv, + const struct i915_power_well_regs *regs, + int pw_idx) +{ + u32 req_mask = HSW_PWR_WELL_CTL_REQ(pw_idx); + u32 ret; + + ret = intel_de_read(dev_priv, regs->bios) & req_mask ? 1 : 0; + ret |= intel_de_read(dev_priv, regs->driver) & req_mask ? 2 : 0; + if (regs->kvmr.reg) + ret |= intel_de_read(dev_priv, regs->kvmr) & req_mask ? 4 : 0; + ret |= intel_de_read(dev_priv, regs->debug) & req_mask ? 8 : 0; + + return ret; +} + +static void hsw_wait_for_power_well_disable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + const struct i915_power_well_regs *regs = power_well->desc->ops->regs; + int pw_idx = i915_power_well_instance(power_well)->hsw.idx; + bool disabled; + u32 reqs; + + /* + * Bspec doesn't require waiting for PWs to get disabled, but still do + * this for paranoia. The known cases where a PW will be forced on: + * - a KVMR request on any power well via the KVMR request register + * - a DMC request on PW1 and MISC_IO power wells via the BIOS and + * DEBUG request registers + * Skip the wait in case any of the request bits are set and print a + * diagnostic message. + */ + wait_for((disabled = !(intel_de_read(dev_priv, regs->driver) & + HSW_PWR_WELL_CTL_STATE(pw_idx))) || + (reqs = hsw_power_well_requesters(dev_priv, regs, pw_idx)), 1); + if (disabled) + return; + + drm_dbg_kms(&dev_priv->drm, + "%s forced on (bios:%d driver:%d kvmr:%d debug:%d)\n", + intel_power_well_name(power_well), + !!(reqs & 1), !!(reqs & 2), !!(reqs & 4), !!(reqs & 8)); +} + +static void gen9_wait_for_power_well_fuses(struct drm_i915_private *dev_priv, + enum skl_power_gate pg) +{ + /* Timeout 5us for PG#0, for other PGs 1us */ + drm_WARN_ON(&dev_priv->drm, + intel_de_wait_for_set(dev_priv, SKL_FUSE_STATUS, + SKL_FUSE_PG_DIST_STATUS(pg), 1)); +} + +static void hsw_power_well_enable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + const struct i915_power_well_regs *regs = power_well->desc->ops->regs; + int pw_idx = i915_power_well_instance(power_well)->hsw.idx; + u32 val; + + if (power_well->desc->has_fuses) { + enum skl_power_gate pg; + + pg = DISPLAY_VER(dev_priv) >= 11 ? ICL_PW_CTL_IDX_TO_PG(pw_idx) : + SKL_PW_CTL_IDX_TO_PG(pw_idx); + + /* Wa_16013190616:adlp */ + if (IS_ALDERLAKE_P(dev_priv) && pg == SKL_PG1) + intel_de_rmw(dev_priv, GEN8_CHICKEN_DCPR_1, 0, DISABLE_FLR_SRC); + + /* + * For PW1 we have to wait both for the PW0/PG0 fuse state + * before enabling the power well and PW1/PG1's own fuse + * state after the enabling. For all other power wells with + * fuses we only have to wait for that PW/PG's fuse state + * after the enabling. + */ + if (pg == SKL_PG1) + gen9_wait_for_power_well_fuses(dev_priv, SKL_PG0); + } + + val = intel_de_read(dev_priv, regs->driver); + intel_de_write(dev_priv, regs->driver, + val | HSW_PWR_WELL_CTL_REQ(pw_idx)); + + hsw_wait_for_power_well_enable(dev_priv, power_well, false); + + if (power_well->desc->has_fuses) { + enum skl_power_gate pg; + + pg = DISPLAY_VER(dev_priv) >= 11 ? ICL_PW_CTL_IDX_TO_PG(pw_idx) : + SKL_PW_CTL_IDX_TO_PG(pw_idx); + gen9_wait_for_power_well_fuses(dev_priv, pg); + } + + hsw_power_well_post_enable(dev_priv, + power_well->desc->irq_pipe_mask, + power_well->desc->has_vga); +} + +static void hsw_power_well_disable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + const struct i915_power_well_regs *regs = power_well->desc->ops->regs; + int pw_idx = i915_power_well_instance(power_well)->hsw.idx; + u32 val; + + hsw_power_well_pre_disable(dev_priv, + power_well->desc->irq_pipe_mask); + + val = intel_de_read(dev_priv, regs->driver); + intel_de_write(dev_priv, regs->driver, + val & ~HSW_PWR_WELL_CTL_REQ(pw_idx)); + hsw_wait_for_power_well_disable(dev_priv, power_well); +} + +static void +icl_combo_phy_aux_power_well_enable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + const struct i915_power_well_regs *regs = power_well->desc->ops->regs; + int pw_idx = i915_power_well_instance(power_well)->hsw.idx; + enum phy phy = icl_aux_pw_to_phy(dev_priv, power_well); + u32 val; + + drm_WARN_ON(&dev_priv->drm, !IS_ICELAKE(dev_priv)); + + val = intel_de_read(dev_priv, regs->driver); + intel_de_write(dev_priv, regs->driver, + val | HSW_PWR_WELL_CTL_REQ(pw_idx)); + + if (DISPLAY_VER(dev_priv) < 12) { + val = intel_de_read(dev_priv, ICL_PORT_CL_DW12(phy)); + intel_de_write(dev_priv, ICL_PORT_CL_DW12(phy), + val | ICL_LANE_ENABLE_AUX); + } + + hsw_wait_for_power_well_enable(dev_priv, power_well, false); + + /* Display WA #1178: icl */ + if (pw_idx >= ICL_PW_CTL_IDX_AUX_A && pw_idx <= ICL_PW_CTL_IDX_AUX_B && + !intel_bios_is_port_edp(dev_priv, (enum port)phy)) { + val = intel_de_read(dev_priv, ICL_AUX_ANAOVRD1(pw_idx)); + val |= ICL_AUX_ANAOVRD1_ENABLE | ICL_AUX_ANAOVRD1_LDO_BYPASS; + intel_de_write(dev_priv, ICL_AUX_ANAOVRD1(pw_idx), val); + } +} + +static void +icl_combo_phy_aux_power_well_disable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + const struct i915_power_well_regs *regs = power_well->desc->ops->regs; + int pw_idx = i915_power_well_instance(power_well)->hsw.idx; + enum phy phy = icl_aux_pw_to_phy(dev_priv, power_well); + u32 val; + + drm_WARN_ON(&dev_priv->drm, !IS_ICELAKE(dev_priv)); + + val = intel_de_read(dev_priv, ICL_PORT_CL_DW12(phy)); + intel_de_write(dev_priv, ICL_PORT_CL_DW12(phy), + val & ~ICL_LANE_ENABLE_AUX); + + val = intel_de_read(dev_priv, regs->driver); + intel_de_write(dev_priv, regs->driver, + val & ~HSW_PWR_WELL_CTL_REQ(pw_idx)); + + hsw_wait_for_power_well_disable(dev_priv, power_well); +} + +#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_RUNTIME_PM) + +static void icl_tc_port_assert_ref_held(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well, + struct intel_digital_port *dig_port) +{ + if (drm_WARN_ON(&dev_priv->drm, !dig_port)) + return; + + if (DISPLAY_VER(dev_priv) == 11 && intel_tc_cold_requires_aux_pw(dig_port)) + return; + + drm_WARN_ON(&dev_priv->drm, !intel_tc_port_ref_held(dig_port)); +} + +#else + +static void icl_tc_port_assert_ref_held(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well, + struct intel_digital_port *dig_port) +{ +} + +#endif + +#define TGL_AUX_PW_TO_TC_PORT(pw_idx) ((pw_idx) - TGL_PW_CTL_IDX_AUX_TC1) + +static void icl_tc_cold_exit(struct drm_i915_private *i915) +{ + int ret, tries = 0; + + while (1) { + ret = snb_pcode_write_timeout(&i915->uncore, ICL_PCODE_EXIT_TCCOLD, 0, + 250, 1); + if (ret != -EAGAIN || ++tries == 3) + break; + msleep(1); + } + + /* Spec states that TC cold exit can take up to 1ms to complete */ + if (!ret) + msleep(1); + + /* TODO: turn failure into a error as soon i915 CI updates ICL IFWI */ + drm_dbg_kms(&i915->drm, "TC cold block %s\n", ret ? "failed" : + "succeeded"); +} + +static void +icl_tc_phy_aux_power_well_enable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + enum aux_ch aux_ch = icl_aux_pw_to_ch(power_well); + struct intel_digital_port *dig_port = aux_ch_to_digital_port(dev_priv, aux_ch); + const struct i915_power_well_regs *regs = power_well->desc->ops->regs; + bool is_tbt = power_well->desc->is_tc_tbt; + bool timeout_expected; + u32 val; + + icl_tc_port_assert_ref_held(dev_priv, power_well, dig_port); + + val = intel_de_read(dev_priv, DP_AUX_CH_CTL(aux_ch)); + val &= ~DP_AUX_CH_CTL_TBT_IO; + if (is_tbt) + val |= DP_AUX_CH_CTL_TBT_IO; + intel_de_write(dev_priv, DP_AUX_CH_CTL(aux_ch), val); + + val = intel_de_read(dev_priv, regs->driver); + intel_de_write(dev_priv, regs->driver, + val | HSW_PWR_WELL_CTL_REQ(i915_power_well_instance(power_well)->hsw.idx)); + + /* + * An AUX timeout is expected if the TBT DP tunnel is down, + * or need to enable AUX on a legacy TypeC port as part of the TC-cold + * exit sequence. + */ + timeout_expected = is_tbt || intel_tc_cold_requires_aux_pw(dig_port); + if (DISPLAY_VER(dev_priv) == 11 && intel_tc_cold_requires_aux_pw(dig_port)) + icl_tc_cold_exit(dev_priv); + + hsw_wait_for_power_well_enable(dev_priv, power_well, timeout_expected); + + if (DISPLAY_VER(dev_priv) >= 12 && !is_tbt) { + enum tc_port tc_port; + + tc_port = TGL_AUX_PW_TO_TC_PORT(i915_power_well_instance(power_well)->hsw.idx); + + if (wait_for(intel_dkl_phy_read(dev_priv, DKL_CMN_UC_DW_27(tc_port), 2) & + DKL_CMN_UC_DW27_UC_HEALTH, 1)) + drm_warn(&dev_priv->drm, + "Timeout waiting TC uC health\n"); + } +} + +static void +icl_aux_power_well_enable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + enum phy phy = icl_aux_pw_to_phy(dev_priv, power_well); + + if (intel_phy_is_tc(dev_priv, phy)) + return icl_tc_phy_aux_power_well_enable(dev_priv, power_well); + else if (IS_ICELAKE(dev_priv)) + return icl_combo_phy_aux_power_well_enable(dev_priv, + power_well); + else + return hsw_power_well_enable(dev_priv, power_well); +} + +static void +icl_aux_power_well_disable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + enum phy phy = icl_aux_pw_to_phy(dev_priv, power_well); + + if (intel_phy_is_tc(dev_priv, phy)) + return hsw_power_well_disable(dev_priv, power_well); + else if (IS_ICELAKE(dev_priv)) + return icl_combo_phy_aux_power_well_disable(dev_priv, + power_well); + else + return hsw_power_well_disable(dev_priv, power_well); +} + +/* + * We should only use the power well if we explicitly asked the hardware to + * enable it, so check if it's enabled and also check if we've requested it to + * be enabled. + */ +static bool hsw_power_well_enabled(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + const struct i915_power_well_regs *regs = power_well->desc->ops->regs; + enum i915_power_well_id id = i915_power_well_instance(power_well)->id; + int pw_idx = i915_power_well_instance(power_well)->hsw.idx; + u32 mask = HSW_PWR_WELL_CTL_REQ(pw_idx) | + HSW_PWR_WELL_CTL_STATE(pw_idx); + u32 val; + + val = intel_de_read(dev_priv, regs->driver); + + /* + * On GEN9 big core due to a DMC bug the driver's request bits for PW1 + * and the MISC_IO PW will be not restored, so check instead for the + * BIOS's own request bits, which are forced-on for these power wells + * when exiting DC5/6. + */ + if (DISPLAY_VER(dev_priv) == 9 && !IS_BROXTON(dev_priv) && + (id == SKL_DISP_PW_1 || id == SKL_DISP_PW_MISC_IO)) + val |= intel_de_read(dev_priv, regs->bios); + + return (val & mask) == mask; +} + +static void assert_can_enable_dc9(struct drm_i915_private *dev_priv) +{ + drm_WARN_ONCE(&dev_priv->drm, + (intel_de_read(dev_priv, DC_STATE_EN) & DC_STATE_EN_DC9), + "DC9 already programmed to be enabled.\n"); + drm_WARN_ONCE(&dev_priv->drm, + intel_de_read(dev_priv, DC_STATE_EN) & + DC_STATE_EN_UPTO_DC5, + "DC5 still not disabled to enable DC9.\n"); + drm_WARN_ONCE(&dev_priv->drm, + intel_de_read(dev_priv, HSW_PWR_WELL_CTL2) & + HSW_PWR_WELL_CTL_REQ(SKL_PW_CTL_IDX_PW_2), + "Power well 2 on.\n"); + drm_WARN_ONCE(&dev_priv->drm, intel_irqs_enabled(dev_priv), + "Interrupts not disabled yet.\n"); + + /* + * TODO: check for the following to verify the conditions to enter DC9 + * state are satisfied: + * 1] Check relevant display engine registers to verify if mode set + * disable sequence was followed. + * 2] Check if display uninitialize sequence is initialized. + */ +} + +static void assert_can_disable_dc9(struct drm_i915_private *dev_priv) +{ + drm_WARN_ONCE(&dev_priv->drm, intel_irqs_enabled(dev_priv), + "Interrupts not disabled yet.\n"); + drm_WARN_ONCE(&dev_priv->drm, + intel_de_read(dev_priv, DC_STATE_EN) & + DC_STATE_EN_UPTO_DC5, + "DC5 still not disabled.\n"); + + /* + * TODO: check for the following to verify DC9 state was indeed + * entered before programming to disable it: + * 1] Check relevant display engine registers to verify if mode + * set disable sequence was followed. + * 2] Check if display uninitialize sequence is initialized. + */ +} + +static void gen9_write_dc_state(struct drm_i915_private *dev_priv, + u32 state) +{ + int rewrites = 0; + int rereads = 0; + u32 v; + + intel_de_write(dev_priv, DC_STATE_EN, state); + + /* It has been observed that disabling the dc6 state sometimes + * doesn't stick and dmc keeps returning old value. Make sure + * the write really sticks enough times and also force rewrite until + * we are confident that state is exactly what we want. + */ + do { + v = intel_de_read(dev_priv, DC_STATE_EN); + + if (v != state) { + intel_de_write(dev_priv, DC_STATE_EN, state); + rewrites++; + rereads = 0; + } else if (rereads++ > 5) { + break; + } + + } while (rewrites < 100); + + if (v != state) + drm_err(&dev_priv->drm, + "Writing dc state to 0x%x failed, now 0x%x\n", + state, v); + + /* Most of the times we need one retry, avoid spam */ + if (rewrites > 1) + drm_dbg_kms(&dev_priv->drm, + "Rewrote dc state to 0x%x %d times\n", + state, rewrites); +} + +static u32 gen9_dc_mask(struct drm_i915_private *dev_priv) +{ + u32 mask; + + mask = DC_STATE_EN_UPTO_DC5; + + if (DISPLAY_VER(dev_priv) >= 12) + mask |= DC_STATE_EN_DC3CO | DC_STATE_EN_UPTO_DC6 + | DC_STATE_EN_DC9; + else if (DISPLAY_VER(dev_priv) == 11) + mask |= DC_STATE_EN_UPTO_DC6 | DC_STATE_EN_DC9; + else if (IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv)) + mask |= DC_STATE_EN_DC9; + else + mask |= DC_STATE_EN_UPTO_DC6; + + return mask; +} + +void gen9_sanitize_dc_state(struct drm_i915_private *dev_priv) +{ + u32 val; + + if (!HAS_DISPLAY(dev_priv)) + return; + + val = intel_de_read(dev_priv, DC_STATE_EN) & gen9_dc_mask(dev_priv); + + drm_dbg_kms(&dev_priv->drm, + "Resetting DC state tracking from %02x to %02x\n", + dev_priv->display.dmc.dc_state, val); + dev_priv->display.dmc.dc_state = val; +} + +/** + * gen9_set_dc_state - set target display C power state + * @dev_priv: i915 device instance + * @state: target DC power state + * - DC_STATE_DISABLE + * - DC_STATE_EN_UPTO_DC5 + * - DC_STATE_EN_UPTO_DC6 + * - DC_STATE_EN_DC9 + * + * Signal to DMC firmware/HW the target DC power state passed in @state. + * DMC/HW can turn off individual display clocks and power rails when entering + * a deeper DC power state (higher in number) and turns these back when exiting + * that state to a shallower power state (lower in number). The HW will decide + * when to actually enter a given state on an on-demand basis, for instance + * depending on the active state of display pipes. The state of display + * registers backed by affected power rails are saved/restored as needed. + * + * Based on the above enabling a deeper DC power state is asynchronous wrt. + * enabling it. Disabling a deeper power state is synchronous: for instance + * setting %DC_STATE_DISABLE won't complete until all HW resources are turned + * back on and register state is restored. This is guaranteed by the MMIO write + * to DC_STATE_EN blocking until the state is restored. + */ +void gen9_set_dc_state(struct drm_i915_private *dev_priv, u32 state) +{ + u32 val; + u32 mask; + + if (!HAS_DISPLAY(dev_priv)) + return; + + if (drm_WARN_ON_ONCE(&dev_priv->drm, + state & ~dev_priv->display.dmc.allowed_dc_mask)) + state &= dev_priv->display.dmc.allowed_dc_mask; + + val = intel_de_read(dev_priv, DC_STATE_EN); + mask = gen9_dc_mask(dev_priv); + drm_dbg_kms(&dev_priv->drm, "Setting DC state from %02x to %02x\n", + val & mask, state); + + /* Check if DMC is ignoring our DC state requests */ + if ((val & mask) != dev_priv->display.dmc.dc_state) + drm_err(&dev_priv->drm, "DC state mismatch (0x%x -> 0x%x)\n", + dev_priv->display.dmc.dc_state, val & mask); + + val &= ~mask; + val |= state; + + gen9_write_dc_state(dev_priv, val); + + dev_priv->display.dmc.dc_state = val & mask; +} + +static void tgl_enable_dc3co(struct drm_i915_private *dev_priv) +{ + drm_dbg_kms(&dev_priv->drm, "Enabling DC3CO\n"); + gen9_set_dc_state(dev_priv, DC_STATE_EN_DC3CO); +} + +static void tgl_disable_dc3co(struct drm_i915_private *dev_priv) +{ + u32 val; + + drm_dbg_kms(&dev_priv->drm, "Disabling DC3CO\n"); + val = intel_de_read(dev_priv, DC_STATE_EN); + val &= ~DC_STATE_DC3CO_STATUS; + intel_de_write(dev_priv, DC_STATE_EN, val); + gen9_set_dc_state(dev_priv, DC_STATE_DISABLE); + /* + * Delay of 200us DC3CO Exit time B.Spec 49196 + */ + usleep_range(200, 210); +} + +static void assert_can_enable_dc5(struct drm_i915_private *dev_priv) +{ + enum i915_power_well_id high_pg; + + /* Power wells at this level and above must be disabled for DC5 entry */ + if (DISPLAY_VER(dev_priv) == 12) + high_pg = ICL_DISP_PW_3; + else + high_pg = SKL_DISP_PW_2; + + drm_WARN_ONCE(&dev_priv->drm, + intel_display_power_well_is_enabled(dev_priv, high_pg), + "Power wells above platform's DC5 limit still enabled.\n"); + + drm_WARN_ONCE(&dev_priv->drm, + (intel_de_read(dev_priv, DC_STATE_EN) & + DC_STATE_EN_UPTO_DC5), + "DC5 already programmed to be enabled.\n"); + assert_rpm_wakelock_held(&dev_priv->runtime_pm); + + assert_dmc_loaded(dev_priv); +} + +void gen9_enable_dc5(struct drm_i915_private *dev_priv) +{ + assert_can_enable_dc5(dev_priv); + + drm_dbg_kms(&dev_priv->drm, "Enabling DC5\n"); + + /* Wa Display #1183: skl,kbl,cfl */ + if (DISPLAY_VER(dev_priv) == 9 && !IS_BROXTON(dev_priv)) + intel_de_write(dev_priv, GEN8_CHICKEN_DCPR_1, + intel_de_read(dev_priv, GEN8_CHICKEN_DCPR_1) | SKL_SELECT_ALTERNATE_DC_EXIT); + + gen9_set_dc_state(dev_priv, DC_STATE_EN_UPTO_DC5); +} + +static void assert_can_enable_dc6(struct drm_i915_private *dev_priv) +{ + drm_WARN_ONCE(&dev_priv->drm, + intel_de_read(dev_priv, UTIL_PIN_CTL) & UTIL_PIN_ENABLE, + "Backlight is not disabled.\n"); + drm_WARN_ONCE(&dev_priv->drm, + (intel_de_read(dev_priv, DC_STATE_EN) & + DC_STATE_EN_UPTO_DC6), + "DC6 already programmed to be enabled.\n"); + + assert_dmc_loaded(dev_priv); +} + +void skl_enable_dc6(struct drm_i915_private *dev_priv) +{ + assert_can_enable_dc6(dev_priv); + + drm_dbg_kms(&dev_priv->drm, "Enabling DC6\n"); + + /* Wa Display #1183: skl,kbl,cfl */ + if (DISPLAY_VER(dev_priv) == 9 && !IS_BROXTON(dev_priv)) + intel_de_write(dev_priv, GEN8_CHICKEN_DCPR_1, + intel_de_read(dev_priv, GEN8_CHICKEN_DCPR_1) | SKL_SELECT_ALTERNATE_DC_EXIT); + + gen9_set_dc_state(dev_priv, DC_STATE_EN_UPTO_DC6); +} + +void bxt_enable_dc9(struct drm_i915_private *dev_priv) +{ + assert_can_enable_dc9(dev_priv); + + drm_dbg_kms(&dev_priv->drm, "Enabling DC9\n"); + /* + * Power sequencer reset is not needed on + * platforms with South Display Engine on PCH, + * because PPS registers are always on. + */ + if (!HAS_PCH_SPLIT(dev_priv)) + intel_pps_reset_all(dev_priv); + gen9_set_dc_state(dev_priv, DC_STATE_EN_DC9); +} + +void bxt_disable_dc9(struct drm_i915_private *dev_priv) +{ + assert_can_disable_dc9(dev_priv); + + drm_dbg_kms(&dev_priv->drm, "Disabling DC9\n"); + + gen9_set_dc_state(dev_priv, DC_STATE_DISABLE); + + intel_pps_unlock_regs_wa(dev_priv); +} + +static void hsw_power_well_sync_hw(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + const struct i915_power_well_regs *regs = power_well->desc->ops->regs; + int pw_idx = i915_power_well_instance(power_well)->hsw.idx; + u32 mask = HSW_PWR_WELL_CTL_REQ(pw_idx); + u32 bios_req = intel_de_read(dev_priv, regs->bios); + + /* Take over the request bit if set by BIOS. */ + if (bios_req & mask) { + u32 drv_req = intel_de_read(dev_priv, regs->driver); + + if (!(drv_req & mask)) + intel_de_write(dev_priv, regs->driver, drv_req | mask); + intel_de_write(dev_priv, regs->bios, bios_req & ~mask); + } +} + +static void bxt_dpio_cmn_power_well_enable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + bxt_ddi_phy_init(dev_priv, i915_power_well_instance(power_well)->bxt.phy); +} + +static void bxt_dpio_cmn_power_well_disable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + bxt_ddi_phy_uninit(dev_priv, i915_power_well_instance(power_well)->bxt.phy); +} + +static bool bxt_dpio_cmn_power_well_enabled(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + return bxt_ddi_phy_is_enabled(dev_priv, i915_power_well_instance(power_well)->bxt.phy); +} + +static void bxt_verify_ddi_phy_power_wells(struct drm_i915_private *dev_priv) +{ + struct i915_power_well *power_well; + + power_well = lookup_power_well(dev_priv, BXT_DISP_PW_DPIO_CMN_A); + if (intel_power_well_refcount(power_well) > 0) + bxt_ddi_phy_verify_state(dev_priv, i915_power_well_instance(power_well)->bxt.phy); + + power_well = lookup_power_well(dev_priv, VLV_DISP_PW_DPIO_CMN_BC); + if (intel_power_well_refcount(power_well) > 0) + bxt_ddi_phy_verify_state(dev_priv, i915_power_well_instance(power_well)->bxt.phy); + + if (IS_GEMINILAKE(dev_priv)) { + power_well = lookup_power_well(dev_priv, + GLK_DISP_PW_DPIO_CMN_C); + if (intel_power_well_refcount(power_well) > 0) + bxt_ddi_phy_verify_state(dev_priv, + i915_power_well_instance(power_well)->bxt.phy); + } +} + +static bool gen9_dc_off_power_well_enabled(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + return ((intel_de_read(dev_priv, DC_STATE_EN) & DC_STATE_EN_DC3CO) == 0 && + (intel_de_read(dev_priv, DC_STATE_EN) & DC_STATE_EN_UPTO_DC5_DC6_MASK) == 0); +} + +static void gen9_assert_dbuf_enabled(struct drm_i915_private *dev_priv) +{ + u8 hw_enabled_dbuf_slices = intel_enabled_dbuf_slices_mask(dev_priv); + u8 enabled_dbuf_slices = dev_priv->display.dbuf.enabled_slices; + + drm_WARN(&dev_priv->drm, + hw_enabled_dbuf_slices != enabled_dbuf_slices, + "Unexpected DBuf power power state (0x%08x, expected 0x%08x)\n", + hw_enabled_dbuf_slices, + enabled_dbuf_slices); +} + +void gen9_disable_dc_states(struct drm_i915_private *dev_priv) +{ + struct intel_cdclk_config cdclk_config = {}; + + if (dev_priv->display.dmc.target_dc_state == DC_STATE_EN_DC3CO) { + tgl_disable_dc3co(dev_priv); + return; + } + + gen9_set_dc_state(dev_priv, DC_STATE_DISABLE); + + if (!HAS_DISPLAY(dev_priv)) + return; + + intel_cdclk_get_cdclk(dev_priv, &cdclk_config); + /* Can't read out voltage_level so can't use intel_cdclk_changed() */ + drm_WARN_ON(&dev_priv->drm, + intel_cdclk_needs_modeset(&dev_priv->display.cdclk.hw, + &cdclk_config)); + + gen9_assert_dbuf_enabled(dev_priv); + + if (IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv)) + bxt_verify_ddi_phy_power_wells(dev_priv); + + if (DISPLAY_VER(dev_priv) >= 11) + /* + * DMC retains HW context only for port A, the other combo + * PHY's HW context for port B is lost after DC transitions, + * so we need to restore it manually. + */ + intel_combo_phy_init(dev_priv); +} + +static void gen9_dc_off_power_well_enable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + gen9_disable_dc_states(dev_priv); +} + +static void gen9_dc_off_power_well_disable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + if (!intel_dmc_has_payload(dev_priv)) + return; + + switch (dev_priv->display.dmc.target_dc_state) { + case DC_STATE_EN_DC3CO: + tgl_enable_dc3co(dev_priv); + break; + case DC_STATE_EN_UPTO_DC6: + skl_enable_dc6(dev_priv); + break; + case DC_STATE_EN_UPTO_DC5: + gen9_enable_dc5(dev_priv); + break; + } +} + +static void i9xx_power_well_sync_hw_noop(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ +} + +static void i9xx_always_on_power_well_noop(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ +} + +static bool i9xx_always_on_power_well_enabled(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + return true; +} + +static void i830_pipes_power_well_enable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + if ((intel_de_read(dev_priv, PIPECONF(PIPE_A)) & PIPECONF_ENABLE) == 0) + i830_enable_pipe(dev_priv, PIPE_A); + if ((intel_de_read(dev_priv, PIPECONF(PIPE_B)) & PIPECONF_ENABLE) == 0) + i830_enable_pipe(dev_priv, PIPE_B); +} + +static void i830_pipes_power_well_disable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + i830_disable_pipe(dev_priv, PIPE_B); + i830_disable_pipe(dev_priv, PIPE_A); +} + +static bool i830_pipes_power_well_enabled(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + return intel_de_read(dev_priv, PIPECONF(PIPE_A)) & PIPECONF_ENABLE && + intel_de_read(dev_priv, PIPECONF(PIPE_B)) & PIPECONF_ENABLE; +} + +static void i830_pipes_power_well_sync_hw(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + if (intel_power_well_refcount(power_well) > 0) + i830_pipes_power_well_enable(dev_priv, power_well); + else + i830_pipes_power_well_disable(dev_priv, power_well); +} + +static void vlv_set_power_well(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well, bool enable) +{ + int pw_idx = i915_power_well_instance(power_well)->vlv.idx; + u32 mask; + u32 state; + u32 ctrl; + + mask = PUNIT_PWRGT_MASK(pw_idx); + state = enable ? PUNIT_PWRGT_PWR_ON(pw_idx) : + PUNIT_PWRGT_PWR_GATE(pw_idx); + + vlv_punit_get(dev_priv); + +#define COND \ + ((vlv_punit_read(dev_priv, PUNIT_REG_PWRGT_STATUS) & mask) == state) + + if (COND) + goto out; + + ctrl = vlv_punit_read(dev_priv, PUNIT_REG_PWRGT_CTRL); + ctrl &= ~mask; + ctrl |= state; + vlv_punit_write(dev_priv, PUNIT_REG_PWRGT_CTRL, ctrl); + + if (wait_for(COND, 100)) + drm_err(&dev_priv->drm, + "timeout setting power well state %08x (%08x)\n", + state, + vlv_punit_read(dev_priv, PUNIT_REG_PWRGT_CTRL)); + +#undef COND + +out: + vlv_punit_put(dev_priv); +} + +static void vlv_power_well_enable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + vlv_set_power_well(dev_priv, power_well, true); +} + +static void vlv_power_well_disable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + vlv_set_power_well(dev_priv, power_well, false); +} + +static bool vlv_power_well_enabled(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + int pw_idx = i915_power_well_instance(power_well)->vlv.idx; + bool enabled = false; + u32 mask; + u32 state; + u32 ctrl; + + mask = PUNIT_PWRGT_MASK(pw_idx); + ctrl = PUNIT_PWRGT_PWR_ON(pw_idx); + + vlv_punit_get(dev_priv); + + state = vlv_punit_read(dev_priv, PUNIT_REG_PWRGT_STATUS) & mask; + /* + * We only ever set the power-on and power-gate states, anything + * else is unexpected. + */ + drm_WARN_ON(&dev_priv->drm, state != PUNIT_PWRGT_PWR_ON(pw_idx) && + state != PUNIT_PWRGT_PWR_GATE(pw_idx)); + if (state == ctrl) + enabled = true; + + /* + * A transient state at this point would mean some unexpected party + * is poking at the power controls too. + */ + ctrl = vlv_punit_read(dev_priv, PUNIT_REG_PWRGT_CTRL) & mask; + drm_WARN_ON(&dev_priv->drm, ctrl != state); + + vlv_punit_put(dev_priv); + + return enabled; +} + +static void vlv_init_display_clock_gating(struct drm_i915_private *dev_priv) +{ + u32 val; + + /* + * On driver load, a pipe may be active and driving a DSI display. + * Preserve DPOUNIT_CLOCK_GATE_DISABLE to avoid the pipe getting stuck + * (and never recovering) in this case. intel_dsi_post_disable() will + * clear it when we turn off the display. + */ + val = intel_de_read(dev_priv, DSPCLK_GATE_D(dev_priv)); + val &= DPOUNIT_CLOCK_GATE_DISABLE; + val |= VRHUNIT_CLOCK_GATE_DISABLE; + intel_de_write(dev_priv, DSPCLK_GATE_D(dev_priv), val); + + /* + * Disable trickle feed and enable pnd deadline calculation + */ + intel_de_write(dev_priv, MI_ARB_VLV, + MI_ARB_DISPLAY_TRICKLE_FEED_DISABLE); + intel_de_write(dev_priv, CBR1_VLV, 0); + + drm_WARN_ON(&dev_priv->drm, RUNTIME_INFO(dev_priv)->rawclk_freq == 0); + intel_de_write(dev_priv, RAWCLK_FREQ_VLV, + DIV_ROUND_CLOSEST(RUNTIME_INFO(dev_priv)->rawclk_freq, + 1000)); +} + +static void vlv_display_power_well_init(struct drm_i915_private *dev_priv) +{ + struct intel_encoder *encoder; + enum pipe pipe; + + /* + * Enable the CRI clock source so we can get at the + * display and the reference clock for VGA + * hotplug / manual detection. Supposedly DSI also + * needs the ref clock up and running. + * + * CHV DPLL B/C have some issues if VGA mode is enabled. + */ + for_each_pipe(dev_priv, pipe) { + u32 val = intel_de_read(dev_priv, DPLL(pipe)); + + val |= DPLL_REF_CLK_ENABLE_VLV | DPLL_VGA_MODE_DIS; + if (pipe != PIPE_A) + val |= DPLL_INTEGRATED_CRI_CLK_VLV; + + intel_de_write(dev_priv, DPLL(pipe), val); + } + + vlv_init_display_clock_gating(dev_priv); + + spin_lock_irq(&dev_priv->irq_lock); + valleyview_enable_display_irqs(dev_priv); + spin_unlock_irq(&dev_priv->irq_lock); + + /* + * During driver initialization/resume we can avoid restoring the + * part of the HW/SW state that will be inited anyway explicitly. + */ + if (dev_priv->display.power.domains.initializing) + return; + + intel_hpd_init(dev_priv); + intel_hpd_poll_disable(dev_priv); + + /* Re-enable the ADPA, if we have one */ + for_each_intel_encoder(&dev_priv->drm, encoder) { + if (encoder->type == INTEL_OUTPUT_ANALOG) + intel_crt_reset(&encoder->base); + } + + intel_vga_redisable_power_on(dev_priv); + + intel_pps_unlock_regs_wa(dev_priv); +} + +static void vlv_display_power_well_deinit(struct drm_i915_private *dev_priv) +{ + spin_lock_irq(&dev_priv->irq_lock); + valleyview_disable_display_irqs(dev_priv); + spin_unlock_irq(&dev_priv->irq_lock); + + /* make sure we're done processing display irqs */ + intel_synchronize_irq(dev_priv); + + intel_pps_reset_all(dev_priv); + + /* Prevent us from re-enabling polling on accident in late suspend */ + if (!dev_priv->drm.dev->power.is_suspended) + intel_hpd_poll_enable(dev_priv); +} + +static void vlv_display_power_well_enable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + vlv_set_power_well(dev_priv, power_well, true); + + vlv_display_power_well_init(dev_priv); +} + +static void vlv_display_power_well_disable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + vlv_display_power_well_deinit(dev_priv); + + vlv_set_power_well(dev_priv, power_well, false); +} + +static void vlv_dpio_cmn_power_well_enable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + /* since ref/cri clock was enabled */ + udelay(1); /* >10ns for cmnreset, >0ns for sidereset */ + + vlv_set_power_well(dev_priv, power_well, true); + + /* + * From VLV2A0_DP_eDP_DPIO_driver_vbios_notes_10.docx - + * 6. De-assert cmn_reset/side_reset. Same as VLV X0. + * a. GUnit 0x2110 bit[0] set to 1 (def 0) + * b. The other bits such as sfr settings / modesel may all + * be set to 0. + * + * This should only be done on init and resume from S3 with + * both PLLs disabled, or we risk losing DPIO and PLL + * synchronization. + */ + intel_de_write(dev_priv, DPIO_CTL, + intel_de_read(dev_priv, DPIO_CTL) | DPIO_CMNRST); +} + +static void vlv_dpio_cmn_power_well_disable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + enum pipe pipe; + + for_each_pipe(dev_priv, pipe) + assert_pll_disabled(dev_priv, pipe); + + /* Assert common reset */ + intel_de_write(dev_priv, DPIO_CTL, + intel_de_read(dev_priv, DPIO_CTL) & ~DPIO_CMNRST); + + vlv_set_power_well(dev_priv, power_well, false); +} + +#define BITS_SET(val, bits) (((val) & (bits)) == (bits)) + +static void assert_chv_phy_status(struct drm_i915_private *dev_priv) +{ + struct i915_power_well *cmn_bc = + lookup_power_well(dev_priv, VLV_DISP_PW_DPIO_CMN_BC); + struct i915_power_well *cmn_d = + lookup_power_well(dev_priv, CHV_DISP_PW_DPIO_CMN_D); + u32 phy_control = dev_priv->display.power.chv_phy_control; + u32 phy_status = 0; + u32 phy_status_mask = 0xffffffff; + + /* + * The BIOS can leave the PHY is some weird state + * where it doesn't fully power down some parts. + * Disable the asserts until the PHY has been fully + * reset (ie. the power well has been disabled at + * least once). + */ + if (!dev_priv->display.power.chv_phy_assert[DPIO_PHY0]) + phy_status_mask &= ~(PHY_STATUS_CMN_LDO(DPIO_PHY0, DPIO_CH0) | + PHY_STATUS_SPLINE_LDO(DPIO_PHY0, DPIO_CH0, 0) | + PHY_STATUS_SPLINE_LDO(DPIO_PHY0, DPIO_CH0, 1) | + PHY_STATUS_CMN_LDO(DPIO_PHY0, DPIO_CH1) | + PHY_STATUS_SPLINE_LDO(DPIO_PHY0, DPIO_CH1, 0) | + PHY_STATUS_SPLINE_LDO(DPIO_PHY0, DPIO_CH1, 1)); + + if (!dev_priv->display.power.chv_phy_assert[DPIO_PHY1]) + phy_status_mask &= ~(PHY_STATUS_CMN_LDO(DPIO_PHY1, DPIO_CH0) | + PHY_STATUS_SPLINE_LDO(DPIO_PHY1, DPIO_CH0, 0) | + PHY_STATUS_SPLINE_LDO(DPIO_PHY1, DPIO_CH0, 1)); + + if (intel_power_well_is_enabled(dev_priv, cmn_bc)) { + phy_status |= PHY_POWERGOOD(DPIO_PHY0); + + /* this assumes override is only used to enable lanes */ + if ((phy_control & PHY_CH_POWER_DOWN_OVRD_EN(DPIO_PHY0, DPIO_CH0)) == 0) + phy_control |= PHY_CH_POWER_DOWN_OVRD(0xf, DPIO_PHY0, DPIO_CH0); + + if ((phy_control & PHY_CH_POWER_DOWN_OVRD_EN(DPIO_PHY0, DPIO_CH1)) == 0) + phy_control |= PHY_CH_POWER_DOWN_OVRD(0xf, DPIO_PHY0, DPIO_CH1); + + /* CL1 is on whenever anything is on in either channel */ + if (BITS_SET(phy_control, + PHY_CH_POWER_DOWN_OVRD(0xf, DPIO_PHY0, DPIO_CH0) | + PHY_CH_POWER_DOWN_OVRD(0xf, DPIO_PHY0, DPIO_CH1))) + phy_status |= PHY_STATUS_CMN_LDO(DPIO_PHY0, DPIO_CH0); + + /* + * The DPLLB check accounts for the pipe B + port A usage + * with CL2 powered up but all the lanes in the second channel + * powered down. + */ + if (BITS_SET(phy_control, + PHY_CH_POWER_DOWN_OVRD(0xf, DPIO_PHY0, DPIO_CH1)) && + (intel_de_read(dev_priv, DPLL(PIPE_B)) & DPLL_VCO_ENABLE) == 0) + phy_status |= PHY_STATUS_CMN_LDO(DPIO_PHY0, DPIO_CH1); + + if (BITS_SET(phy_control, + PHY_CH_POWER_DOWN_OVRD(0x3, DPIO_PHY0, DPIO_CH0))) + phy_status |= PHY_STATUS_SPLINE_LDO(DPIO_PHY0, DPIO_CH0, 0); + if (BITS_SET(phy_control, + PHY_CH_POWER_DOWN_OVRD(0xc, DPIO_PHY0, DPIO_CH0))) + phy_status |= PHY_STATUS_SPLINE_LDO(DPIO_PHY0, DPIO_CH0, 1); + + if (BITS_SET(phy_control, + PHY_CH_POWER_DOWN_OVRD(0x3, DPIO_PHY0, DPIO_CH1))) + phy_status |= PHY_STATUS_SPLINE_LDO(DPIO_PHY0, DPIO_CH1, 0); + if (BITS_SET(phy_control, + PHY_CH_POWER_DOWN_OVRD(0xc, DPIO_PHY0, DPIO_CH1))) + phy_status |= PHY_STATUS_SPLINE_LDO(DPIO_PHY0, DPIO_CH1, 1); + } + + if (intel_power_well_is_enabled(dev_priv, cmn_d)) { + phy_status |= PHY_POWERGOOD(DPIO_PHY1); + + /* this assumes override is only used to enable lanes */ + if ((phy_control & PHY_CH_POWER_DOWN_OVRD_EN(DPIO_PHY1, DPIO_CH0)) == 0) + phy_control |= PHY_CH_POWER_DOWN_OVRD(0xf, DPIO_PHY1, DPIO_CH0); + + if (BITS_SET(phy_control, + PHY_CH_POWER_DOWN_OVRD(0xf, DPIO_PHY1, DPIO_CH0))) + phy_status |= PHY_STATUS_CMN_LDO(DPIO_PHY1, DPIO_CH0); + + if (BITS_SET(phy_control, + PHY_CH_POWER_DOWN_OVRD(0x3, DPIO_PHY1, DPIO_CH0))) + phy_status |= PHY_STATUS_SPLINE_LDO(DPIO_PHY1, DPIO_CH0, 0); + if (BITS_SET(phy_control, + PHY_CH_POWER_DOWN_OVRD(0xc, DPIO_PHY1, DPIO_CH0))) + phy_status |= PHY_STATUS_SPLINE_LDO(DPIO_PHY1, DPIO_CH0, 1); + } + + phy_status &= phy_status_mask; + + /* + * The PHY may be busy with some initial calibration and whatnot, + * so the power state can take a while to actually change. + */ + if (intel_de_wait_for_register(dev_priv, DISPLAY_PHY_STATUS, + phy_status_mask, phy_status, 10)) + drm_err(&dev_priv->drm, + "Unexpected PHY_STATUS 0x%08x, expected 0x%08x (PHY_CONTROL=0x%08x)\n", + intel_de_read(dev_priv, DISPLAY_PHY_STATUS) & phy_status_mask, + phy_status, dev_priv->display.power.chv_phy_control); +} + +#undef BITS_SET + +static void chv_dpio_cmn_power_well_enable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + enum i915_power_well_id id = i915_power_well_instance(power_well)->id; + enum dpio_phy phy; + enum pipe pipe; + u32 tmp; + + drm_WARN_ON_ONCE(&dev_priv->drm, + id != VLV_DISP_PW_DPIO_CMN_BC && + id != CHV_DISP_PW_DPIO_CMN_D); + + if (id == VLV_DISP_PW_DPIO_CMN_BC) { + pipe = PIPE_A; + phy = DPIO_PHY0; + } else { + pipe = PIPE_C; + phy = DPIO_PHY1; + } + + /* since ref/cri clock was enabled */ + udelay(1); /* >10ns for cmnreset, >0ns for sidereset */ + vlv_set_power_well(dev_priv, power_well, true); + + /* Poll for phypwrgood signal */ + if (intel_de_wait_for_set(dev_priv, DISPLAY_PHY_STATUS, + PHY_POWERGOOD(phy), 1)) + drm_err(&dev_priv->drm, "Display PHY %d is not power up\n", + phy); + + vlv_dpio_get(dev_priv); + + /* Enable dynamic power down */ + tmp = vlv_dpio_read(dev_priv, pipe, CHV_CMN_DW28); + tmp |= DPIO_DYNPWRDOWNEN_CH0 | DPIO_CL1POWERDOWNEN | + DPIO_SUS_CLK_CONFIG_GATE_CLKREQ; + vlv_dpio_write(dev_priv, pipe, CHV_CMN_DW28, tmp); + + if (id == VLV_DISP_PW_DPIO_CMN_BC) { + tmp = vlv_dpio_read(dev_priv, pipe, _CHV_CMN_DW6_CH1); + tmp |= DPIO_DYNPWRDOWNEN_CH1; + vlv_dpio_write(dev_priv, pipe, _CHV_CMN_DW6_CH1, tmp); + } else { + /* + * Force the non-existing CL2 off. BXT does this + * too, so maybe it saves some power even though + * CL2 doesn't exist? + */ + tmp = vlv_dpio_read(dev_priv, pipe, CHV_CMN_DW30); + tmp |= DPIO_CL2_LDOFUSE_PWRENB; + vlv_dpio_write(dev_priv, pipe, CHV_CMN_DW30, tmp); + } + + vlv_dpio_put(dev_priv); + + dev_priv->display.power.chv_phy_control |= PHY_COM_LANE_RESET_DEASSERT(phy); + intel_de_write(dev_priv, DISPLAY_PHY_CONTROL, + dev_priv->display.power.chv_phy_control); + + drm_dbg_kms(&dev_priv->drm, + "Enabled DPIO PHY%d (PHY_CONTROL=0x%08x)\n", + phy, dev_priv->display.power.chv_phy_control); + + assert_chv_phy_status(dev_priv); +} + +static void chv_dpio_cmn_power_well_disable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + enum i915_power_well_id id = i915_power_well_instance(power_well)->id; + enum dpio_phy phy; + + drm_WARN_ON_ONCE(&dev_priv->drm, + id != VLV_DISP_PW_DPIO_CMN_BC && + id != CHV_DISP_PW_DPIO_CMN_D); + + if (id == VLV_DISP_PW_DPIO_CMN_BC) { + phy = DPIO_PHY0; + assert_pll_disabled(dev_priv, PIPE_A); + assert_pll_disabled(dev_priv, PIPE_B); + } else { + phy = DPIO_PHY1; + assert_pll_disabled(dev_priv, PIPE_C); + } + + dev_priv->display.power.chv_phy_control &= ~PHY_COM_LANE_RESET_DEASSERT(phy); + intel_de_write(dev_priv, DISPLAY_PHY_CONTROL, + dev_priv->display.power.chv_phy_control); + + vlv_set_power_well(dev_priv, power_well, false); + + drm_dbg_kms(&dev_priv->drm, + "Disabled DPIO PHY%d (PHY_CONTROL=0x%08x)\n", + phy, dev_priv->display.power.chv_phy_control); + + /* PHY is fully reset now, so we can enable the PHY state asserts */ + dev_priv->display.power.chv_phy_assert[phy] = true; + + assert_chv_phy_status(dev_priv); +} + +static void assert_chv_phy_powergate(struct drm_i915_private *dev_priv, enum dpio_phy phy, + enum dpio_channel ch, bool override, unsigned int mask) +{ + enum pipe pipe = phy == DPIO_PHY0 ? PIPE_A : PIPE_C; + u32 reg, val, expected, actual; + + /* + * The BIOS can leave the PHY is some weird state + * where it doesn't fully power down some parts. + * Disable the asserts until the PHY has been fully + * reset (ie. the power well has been disabled at + * least once). + */ + if (!dev_priv->display.power.chv_phy_assert[phy]) + return; + + if (ch == DPIO_CH0) + reg = _CHV_CMN_DW0_CH0; + else + reg = _CHV_CMN_DW6_CH1; + + vlv_dpio_get(dev_priv); + val = vlv_dpio_read(dev_priv, pipe, reg); + vlv_dpio_put(dev_priv); + + /* + * This assumes !override is only used when the port is disabled. + * All lanes should power down even without the override when + * the port is disabled. + */ + if (!override || mask == 0xf) { + expected = DPIO_ALLDL_POWERDOWN | DPIO_ANYDL_POWERDOWN; + /* + * If CH1 common lane is not active anymore + * (eg. for pipe B DPLL) the entire channel will + * shut down, which causes the common lane registers + * to read as 0. That means we can't actually check + * the lane power down status bits, but as the entire + * register reads as 0 it's a good indication that the + * channel is indeed entirely powered down. + */ + if (ch == DPIO_CH1 && val == 0) + expected = 0; + } else if (mask != 0x0) { + expected = DPIO_ANYDL_POWERDOWN; + } else { + expected = 0; + } + + if (ch == DPIO_CH0) + actual = val >> DPIO_ANYDL_POWERDOWN_SHIFT_CH0; + else + actual = val >> DPIO_ANYDL_POWERDOWN_SHIFT_CH1; + actual &= DPIO_ALLDL_POWERDOWN | DPIO_ANYDL_POWERDOWN; + + drm_WARN(&dev_priv->drm, actual != expected, + "Unexpected DPIO lane power down: all %d, any %d. Expected: all %d, any %d. (0x%x = 0x%08x)\n", + !!(actual & DPIO_ALLDL_POWERDOWN), + !!(actual & DPIO_ANYDL_POWERDOWN), + !!(expected & DPIO_ALLDL_POWERDOWN), + !!(expected & DPIO_ANYDL_POWERDOWN), + reg, val); +} + +bool chv_phy_powergate_ch(struct drm_i915_private *dev_priv, enum dpio_phy phy, + enum dpio_channel ch, bool override) +{ + struct i915_power_domains *power_domains = &dev_priv->display.power.domains; + bool was_override; + + mutex_lock(&power_domains->lock); + + was_override = dev_priv->display.power.chv_phy_control & PHY_CH_POWER_DOWN_OVRD_EN(phy, ch); + + if (override == was_override) + goto out; + + if (override) + dev_priv->display.power.chv_phy_control |= PHY_CH_POWER_DOWN_OVRD_EN(phy, ch); + else + dev_priv->display.power.chv_phy_control &= ~PHY_CH_POWER_DOWN_OVRD_EN(phy, ch); + + intel_de_write(dev_priv, DISPLAY_PHY_CONTROL, + dev_priv->display.power.chv_phy_control); + + drm_dbg_kms(&dev_priv->drm, + "Power gating DPIO PHY%d CH%d (DPIO_PHY_CONTROL=0x%08x)\n", + phy, ch, dev_priv->display.power.chv_phy_control); + + assert_chv_phy_status(dev_priv); + +out: + mutex_unlock(&power_domains->lock); + + return was_override; +} + +void chv_phy_powergate_lanes(struct intel_encoder *encoder, + bool override, unsigned int mask) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct i915_power_domains *power_domains = &dev_priv->display.power.domains; + enum dpio_phy phy = vlv_dig_port_to_phy(enc_to_dig_port(encoder)); + enum dpio_channel ch = vlv_dig_port_to_channel(enc_to_dig_port(encoder)); + + mutex_lock(&power_domains->lock); + + dev_priv->display.power.chv_phy_control &= ~PHY_CH_POWER_DOWN_OVRD(0xf, phy, ch); + dev_priv->display.power.chv_phy_control |= PHY_CH_POWER_DOWN_OVRD(mask, phy, ch); + + if (override) + dev_priv->display.power.chv_phy_control |= PHY_CH_POWER_DOWN_OVRD_EN(phy, ch); + else + dev_priv->display.power.chv_phy_control &= ~PHY_CH_POWER_DOWN_OVRD_EN(phy, ch); + + intel_de_write(dev_priv, DISPLAY_PHY_CONTROL, + dev_priv->display.power.chv_phy_control); + + drm_dbg_kms(&dev_priv->drm, + "Power gating DPIO PHY%d CH%d lanes 0x%x (PHY_CONTROL=0x%08x)\n", + phy, ch, mask, dev_priv->display.power.chv_phy_control); + + assert_chv_phy_status(dev_priv); + + assert_chv_phy_powergate(dev_priv, phy, ch, override, mask); + + mutex_unlock(&power_domains->lock); +} + +static bool chv_pipe_power_well_enabled(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + enum pipe pipe = PIPE_A; + bool enabled; + u32 state, ctrl; + + vlv_punit_get(dev_priv); + + state = vlv_punit_read(dev_priv, PUNIT_REG_DSPSSPM) & DP_SSS_MASK(pipe); + /* + * We only ever set the power-on and power-gate states, anything + * else is unexpected. + */ + drm_WARN_ON(&dev_priv->drm, state != DP_SSS_PWR_ON(pipe) && + state != DP_SSS_PWR_GATE(pipe)); + enabled = state == DP_SSS_PWR_ON(pipe); + + /* + * A transient state at this point would mean some unexpected party + * is poking at the power controls too. + */ + ctrl = vlv_punit_read(dev_priv, PUNIT_REG_DSPSSPM) & DP_SSC_MASK(pipe); + drm_WARN_ON(&dev_priv->drm, ctrl << 16 != state); + + vlv_punit_put(dev_priv); + + return enabled; +} + +static void chv_set_pipe_power_well(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well, + bool enable) +{ + enum pipe pipe = PIPE_A; + u32 state; + u32 ctrl; + + state = enable ? DP_SSS_PWR_ON(pipe) : DP_SSS_PWR_GATE(pipe); + + vlv_punit_get(dev_priv); + +#define COND \ + ((vlv_punit_read(dev_priv, PUNIT_REG_DSPSSPM) & DP_SSS_MASK(pipe)) == state) + + if (COND) + goto out; + + ctrl = vlv_punit_read(dev_priv, PUNIT_REG_DSPSSPM); + ctrl &= ~DP_SSC_MASK(pipe); + ctrl |= enable ? DP_SSC_PWR_ON(pipe) : DP_SSC_PWR_GATE(pipe); + vlv_punit_write(dev_priv, PUNIT_REG_DSPSSPM, ctrl); + + if (wait_for(COND, 100)) + drm_err(&dev_priv->drm, + "timeout setting power well state %08x (%08x)\n", + state, + vlv_punit_read(dev_priv, PUNIT_REG_DSPSSPM)); + +#undef COND + +out: + vlv_punit_put(dev_priv); +} + +static void chv_pipe_power_well_sync_hw(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + intel_de_write(dev_priv, DISPLAY_PHY_CONTROL, + dev_priv->display.power.chv_phy_control); +} + +static void chv_pipe_power_well_enable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + chv_set_pipe_power_well(dev_priv, power_well, true); + + vlv_display_power_well_init(dev_priv); +} + +static void chv_pipe_power_well_disable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + vlv_display_power_well_deinit(dev_priv); + + chv_set_pipe_power_well(dev_priv, power_well, false); +} + +static void +tgl_tc_cold_request(struct drm_i915_private *i915, bool block) +{ + u8 tries = 0; + int ret; + + while (1) { + u32 low_val; + u32 high_val = 0; + + if (block) + low_val = TGL_PCODE_EXIT_TCCOLD_DATA_L_BLOCK_REQ; + else + low_val = TGL_PCODE_EXIT_TCCOLD_DATA_L_UNBLOCK_REQ; + + /* + * Spec states that we should timeout the request after 200us + * but the function below will timeout after 500us + */ + ret = snb_pcode_read(&i915->uncore, TGL_PCODE_TCCOLD, &low_val, &high_val); + if (ret == 0) { + if (block && + (low_val & TGL_PCODE_EXIT_TCCOLD_DATA_L_EXIT_FAILED)) + ret = -EIO; + else + break; + } + + if (++tries == 3) + break; + + msleep(1); + } + + if (ret) + drm_err(&i915->drm, "TC cold %sblock failed\n", + block ? "" : "un"); + else + drm_dbg_kms(&i915->drm, "TC cold %sblock succeeded\n", + block ? "" : "un"); +} + +static void +tgl_tc_cold_off_power_well_enable(struct drm_i915_private *i915, + struct i915_power_well *power_well) +{ + tgl_tc_cold_request(i915, true); +} + +static void +tgl_tc_cold_off_power_well_disable(struct drm_i915_private *i915, + struct i915_power_well *power_well) +{ + tgl_tc_cold_request(i915, false); +} + +static void +tgl_tc_cold_off_power_well_sync_hw(struct drm_i915_private *i915, + struct i915_power_well *power_well) +{ + if (intel_power_well_refcount(power_well) > 0) + tgl_tc_cold_off_power_well_enable(i915, power_well); + else + tgl_tc_cold_off_power_well_disable(i915, power_well); +} + +static bool +tgl_tc_cold_off_power_well_is_enabled(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + /* + * Not the correctly implementation but there is no way to just read it + * from PCODE, so returning count to avoid state mismatch errors + */ + return intel_power_well_refcount(power_well); +} + +static void xelpdp_aux_power_well_enable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + enum aux_ch aux_ch = i915_power_well_instance(power_well)->xelpdp.aux_ch; + + intel_de_rmw(dev_priv, XELPDP_DP_AUX_CH_CTL(aux_ch), + XELPDP_DP_AUX_CH_CTL_POWER_REQUEST, + XELPDP_DP_AUX_CH_CTL_POWER_REQUEST); + + /* + * The power status flag cannot be used to determine whether aux + * power wells have finished powering up. Instead we're + * expected to just wait a fixed 600us after raising the request + * bit. + */ + usleep_range(600, 1200); +} + +static void xelpdp_aux_power_well_disable(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + enum aux_ch aux_ch = i915_power_well_instance(power_well)->xelpdp.aux_ch; + + intel_de_rmw(dev_priv, XELPDP_DP_AUX_CH_CTL(aux_ch), + XELPDP_DP_AUX_CH_CTL_POWER_REQUEST, + 0); + usleep_range(10, 30); +} + +static bool xelpdp_aux_power_well_enabled(struct drm_i915_private *dev_priv, + struct i915_power_well *power_well) +{ + enum aux_ch aux_ch = i915_power_well_instance(power_well)->xelpdp.aux_ch; + + return intel_de_read(dev_priv, XELPDP_DP_AUX_CH_CTL(aux_ch)) & + XELPDP_DP_AUX_CH_CTL_POWER_STATUS; +} + +const struct i915_power_well_ops i9xx_always_on_power_well_ops = { + .sync_hw = i9xx_power_well_sync_hw_noop, + .enable = i9xx_always_on_power_well_noop, + .disable = i9xx_always_on_power_well_noop, + .is_enabled = i9xx_always_on_power_well_enabled, +}; + +const struct i915_power_well_ops chv_pipe_power_well_ops = { + .sync_hw = chv_pipe_power_well_sync_hw, + .enable = chv_pipe_power_well_enable, + .disable = chv_pipe_power_well_disable, + .is_enabled = chv_pipe_power_well_enabled, +}; + +const struct i915_power_well_ops chv_dpio_cmn_power_well_ops = { + .sync_hw = i9xx_power_well_sync_hw_noop, + .enable = chv_dpio_cmn_power_well_enable, + .disable = chv_dpio_cmn_power_well_disable, + .is_enabled = vlv_power_well_enabled, +}; + +const struct i915_power_well_ops i830_pipes_power_well_ops = { + .sync_hw = i830_pipes_power_well_sync_hw, + .enable = i830_pipes_power_well_enable, + .disable = i830_pipes_power_well_disable, + .is_enabled = i830_pipes_power_well_enabled, +}; + +static const struct i915_power_well_regs hsw_power_well_regs = { + .bios = HSW_PWR_WELL_CTL1, + .driver = HSW_PWR_WELL_CTL2, + .kvmr = HSW_PWR_WELL_CTL3, + .debug = HSW_PWR_WELL_CTL4, +}; + +const struct i915_power_well_ops hsw_power_well_ops = { + .regs = &hsw_power_well_regs, + .sync_hw = hsw_power_well_sync_hw, + .enable = hsw_power_well_enable, + .disable = hsw_power_well_disable, + .is_enabled = hsw_power_well_enabled, +}; + +const struct i915_power_well_ops gen9_dc_off_power_well_ops = { + .sync_hw = i9xx_power_well_sync_hw_noop, + .enable = gen9_dc_off_power_well_enable, + .disable = gen9_dc_off_power_well_disable, + .is_enabled = gen9_dc_off_power_well_enabled, +}; + +const struct i915_power_well_ops bxt_dpio_cmn_power_well_ops = { + .sync_hw = i9xx_power_well_sync_hw_noop, + .enable = bxt_dpio_cmn_power_well_enable, + .disable = bxt_dpio_cmn_power_well_disable, + .is_enabled = bxt_dpio_cmn_power_well_enabled, +}; + +const struct i915_power_well_ops vlv_display_power_well_ops = { + .sync_hw = i9xx_power_well_sync_hw_noop, + .enable = vlv_display_power_well_enable, + .disable = vlv_display_power_well_disable, + .is_enabled = vlv_power_well_enabled, +}; + +const struct i915_power_well_ops vlv_dpio_cmn_power_well_ops = { + .sync_hw = i9xx_power_well_sync_hw_noop, + .enable = vlv_dpio_cmn_power_well_enable, + .disable = vlv_dpio_cmn_power_well_disable, + .is_enabled = vlv_power_well_enabled, +}; + +const struct i915_power_well_ops vlv_dpio_power_well_ops = { + .sync_hw = i9xx_power_well_sync_hw_noop, + .enable = vlv_power_well_enable, + .disable = vlv_power_well_disable, + .is_enabled = vlv_power_well_enabled, +}; + +static const struct i915_power_well_regs icl_aux_power_well_regs = { + .bios = ICL_PWR_WELL_CTL_AUX1, + .driver = ICL_PWR_WELL_CTL_AUX2, + .debug = ICL_PWR_WELL_CTL_AUX4, +}; + +const struct i915_power_well_ops icl_aux_power_well_ops = { + .regs = &icl_aux_power_well_regs, + .sync_hw = hsw_power_well_sync_hw, + .enable = icl_aux_power_well_enable, + .disable = icl_aux_power_well_disable, + .is_enabled = hsw_power_well_enabled, +}; + +static const struct i915_power_well_regs icl_ddi_power_well_regs = { + .bios = ICL_PWR_WELL_CTL_DDI1, + .driver = ICL_PWR_WELL_CTL_DDI2, + .debug = ICL_PWR_WELL_CTL_DDI4, +}; + +const struct i915_power_well_ops icl_ddi_power_well_ops = { + .regs = &icl_ddi_power_well_regs, + .sync_hw = hsw_power_well_sync_hw, + .enable = hsw_power_well_enable, + .disable = hsw_power_well_disable, + .is_enabled = hsw_power_well_enabled, +}; + +const struct i915_power_well_ops tgl_tc_cold_off_ops = { + .sync_hw = tgl_tc_cold_off_power_well_sync_hw, + .enable = tgl_tc_cold_off_power_well_enable, + .disable = tgl_tc_cold_off_power_well_disable, + .is_enabled = tgl_tc_cold_off_power_well_is_enabled, +}; + +const struct i915_power_well_ops xelpdp_aux_power_well_ops = { + .sync_hw = i9xx_power_well_sync_hw_noop, + .enable = xelpdp_aux_power_well_enable, + .disable = xelpdp_aux_power_well_disable, + .is_enabled = xelpdp_aux_power_well_enabled, +}; diff --git a/drivers/gpu/drm/i915/display/intel_display_power_well.h b/drivers/gpu/drm/i915/display/intel_display_power_well.h new file mode 100644 index 000000000..e13b521e3 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_display_power_well.h @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2022 Intel Corporation + */ +#ifndef __INTEL_DISPLAY_POWER_WELL_H__ +#define __INTEL_DISPLAY_POWER_WELL_H__ + +#include + +#include "intel_display.h" +#include "intel_display_power.h" + +struct drm_i915_private; +struct i915_power_well; + +#define for_each_power_well(__dev_priv, __power_well) \ + for ((__power_well) = (__dev_priv)->display.power.domains.power_wells; \ + (__power_well) - (__dev_priv)->display.power.domains.power_wells < \ + (__dev_priv)->display.power.domains.power_well_count; \ + (__power_well)++) + +#define for_each_power_well_reverse(__dev_priv, __power_well) \ + for ((__power_well) = (__dev_priv)->display.power.domains.power_wells + \ + (__dev_priv)->display.power.domains.power_well_count - 1; \ + (__power_well) - (__dev_priv)->display.power.domains.power_wells >= 0; \ + (__power_well)--) + +/* + * i915_power_well_id: + * + * IDs used to look up power wells. Power wells accessed directly bypassing + * the power domains framework must be assigned a unique ID. The rest of power + * wells must be assigned DISP_PW_ID_NONE. + */ +enum i915_power_well_id { + DISP_PW_ID_NONE = 0, /* must be kept zero */ + + VLV_DISP_PW_DISP2D, + BXT_DISP_PW_DPIO_CMN_A, + VLV_DISP_PW_DPIO_CMN_BC, + GLK_DISP_PW_DPIO_CMN_C, + CHV_DISP_PW_DPIO_CMN_D, + HSW_DISP_PW_GLOBAL, + SKL_DISP_PW_MISC_IO, + SKL_DISP_PW_1, + SKL_DISP_PW_2, + ICL_DISP_PW_3, + SKL_DISP_DC_OFF, + TGL_DISP_PW_TC_COLD_OFF, +}; + +struct i915_power_well_instance { + const char *name; + const struct i915_power_domain_list { + const enum intel_display_power_domain *list; + u8 count; + } *domain_list; + + /* unique identifier for this power well */ + enum i915_power_well_id id; + /* + * Arbitraty data associated with this power well. Platform and power + * well specific. + */ + union { + struct { + /* + * request/status flag index in the PUNIT power well + * control/status registers. + */ + u8 idx; + } vlv; + struct { + enum dpio_phy phy; + } bxt; + struct { + /* + * request/status flag index in the power well + * constrol/status registers. + */ + u8 idx; + } hsw; + struct { + u8 aux_ch; + } xelpdp; + }; +}; + +struct i915_power_well_desc { + const struct i915_power_well_ops *ops; + const struct i915_power_well_instance_list { + const struct i915_power_well_instance *list; + u8 count; + } *instances; + + /* Mask of pipes whose IRQ logic is backed by the pw */ + u16 irq_pipe_mask:4; + u16 always_on:1; + /* + * Instead of waiting for the status bit to ack enables, + * just wait a specific amount of time and then consider + * the well enabled. + */ + u16 fixed_enable_delay:1; + /* The pw is backing the VGA functionality */ + u16 has_vga:1; + u16 has_fuses:1; + /* + * The pw is for an ICL+ TypeC PHY port in + * Thunderbolt mode. + */ + u16 is_tc_tbt:1; +}; + +struct i915_power_well { + const struct i915_power_well_desc *desc; + struct intel_power_domain_mask domains; + /* power well enable/disable usage count */ + int count; + /* cached hw enabled state */ + bool hw_enabled; + /* index into desc->instances->list */ + u8 instance_idx; +}; + +struct i915_power_well *lookup_power_well(struct drm_i915_private *i915, + enum i915_power_well_id id); + +void intel_power_well_enable(struct drm_i915_private *i915, + struct i915_power_well *power_well); +void intel_power_well_disable(struct drm_i915_private *i915, + struct i915_power_well *power_well); +void intel_power_well_sync_hw(struct drm_i915_private *i915, + struct i915_power_well *power_well); +void intel_power_well_get(struct drm_i915_private *i915, + struct i915_power_well *power_well); +void intel_power_well_put(struct drm_i915_private *i915, + struct i915_power_well *power_well); +bool intel_power_well_is_enabled(struct drm_i915_private *i915, + struct i915_power_well *power_well); +bool intel_power_well_is_enabled_cached(struct i915_power_well *power_well); +bool intel_display_power_well_is_enabled(struct drm_i915_private *dev_priv, + enum i915_power_well_id power_well_id); +bool intel_power_well_is_always_on(struct i915_power_well *power_well); +const char *intel_power_well_name(struct i915_power_well *power_well); +struct intel_power_domain_mask *intel_power_well_domains(struct i915_power_well *power_well); +int intel_power_well_refcount(struct i915_power_well *power_well); + +void chv_phy_powergate_lanes(struct intel_encoder *encoder, + bool override, unsigned int mask); +bool chv_phy_powergate_ch(struct drm_i915_private *dev_priv, enum dpio_phy phy, + enum dpio_channel ch, bool override); + +void gen9_enable_dc5(struct drm_i915_private *dev_priv); +void skl_enable_dc6(struct drm_i915_private *dev_priv); +void gen9_sanitize_dc_state(struct drm_i915_private *dev_priv); +void gen9_set_dc_state(struct drm_i915_private *dev_priv, u32 state); +void gen9_disable_dc_states(struct drm_i915_private *dev_priv); +void bxt_enable_dc9(struct drm_i915_private *dev_priv); +void bxt_disable_dc9(struct drm_i915_private *dev_priv); + +extern const struct i915_power_well_ops i9xx_always_on_power_well_ops; +extern const struct i915_power_well_ops chv_pipe_power_well_ops; +extern const struct i915_power_well_ops chv_dpio_cmn_power_well_ops; +extern const struct i915_power_well_ops i830_pipes_power_well_ops; +extern const struct i915_power_well_ops hsw_power_well_ops; +extern const struct i915_power_well_ops gen9_dc_off_power_well_ops; +extern const struct i915_power_well_ops bxt_dpio_cmn_power_well_ops; +extern const struct i915_power_well_ops vlv_display_power_well_ops; +extern const struct i915_power_well_ops vlv_dpio_cmn_power_well_ops; +extern const struct i915_power_well_ops vlv_dpio_power_well_ops; +extern const struct i915_power_well_ops icl_aux_power_well_ops; +extern const struct i915_power_well_ops icl_ddi_power_well_ops; +extern const struct i915_power_well_ops tgl_tc_cold_off_ops; +extern const struct i915_power_well_ops xelpdp_aux_power_well_ops; + +#endif diff --git a/drivers/gpu/drm/i915/display/intel_display_trace.c b/drivers/gpu/drm/i915/display/intel_display_trace.c new file mode 100644 index 000000000..737979ada --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_display_trace.c @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright © 2021 Intel Corporation + */ + +#ifndef __CHECKER__ +#define CREATE_TRACE_POINTS +#include "intel_display_trace.h" +#endif diff --git a/drivers/gpu/drm/i915/display/intel_display_trace.h b/drivers/gpu/drm/i915/display/intel_display_trace.h new file mode 100644 index 000000000..2dd5a4b7f --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_display_trace.h @@ -0,0 +1,589 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright © 2021 Intel Corporation + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM i915 + +#if !defined(__INTEL_DISPLAY_TRACE_H__) || defined(TRACE_HEADER_MULTI_READ) +#define __INTEL_DISPLAY_TRACE_H__ + +#include +#include +#include + +#include "i915_drv.h" +#include "i915_irq.h" +#include "intel_crtc.h" +#include "intel_display_types.h" + +TRACE_EVENT(intel_pipe_enable, + TP_PROTO(struct intel_crtc *crtc), + TP_ARGS(crtc), + + TP_STRUCT__entry( + __array(u32, frame, 3) + __array(u32, scanline, 3) + __field(enum pipe, pipe) + ), + TP_fast_assign( + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_crtc *it__; + for_each_intel_crtc(&dev_priv->drm, it__) { + __entry->frame[it__->pipe] = intel_crtc_get_vblank_counter(it__); + __entry->scanline[it__->pipe] = intel_get_crtc_scanline(it__); + } + __entry->pipe = crtc->pipe; + ), + + TP_printk("pipe %c enable, pipe A: frame=%u, scanline=%u, pipe B: frame=%u, scanline=%u, pipe C: frame=%u, scanline=%u", + pipe_name(__entry->pipe), + __entry->frame[PIPE_A], __entry->scanline[PIPE_A], + __entry->frame[PIPE_B], __entry->scanline[PIPE_B], + __entry->frame[PIPE_C], __entry->scanline[PIPE_C]) +); + +TRACE_EVENT(intel_pipe_disable, + TP_PROTO(struct intel_crtc *crtc), + TP_ARGS(crtc), + + TP_STRUCT__entry( + __array(u32, frame, 3) + __array(u32, scanline, 3) + __field(enum pipe, pipe) + ), + + TP_fast_assign( + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_crtc *it__; + for_each_intel_crtc(&dev_priv->drm, it__) { + __entry->frame[it__->pipe] = intel_crtc_get_vblank_counter(it__); + __entry->scanline[it__->pipe] = intel_get_crtc_scanline(it__); + } + __entry->pipe = crtc->pipe; + ), + + TP_printk("pipe %c disable, pipe A: frame=%u, scanline=%u, pipe B: frame=%u, scanline=%u, pipe C: frame=%u, scanline=%u", + pipe_name(__entry->pipe), + __entry->frame[PIPE_A], __entry->scanline[PIPE_A], + __entry->frame[PIPE_B], __entry->scanline[PIPE_B], + __entry->frame[PIPE_C], __entry->scanline[PIPE_C]) +); + +TRACE_EVENT(intel_pipe_crc, + TP_PROTO(struct intel_crtc *crtc, const u32 *crcs), + TP_ARGS(crtc, crcs), + + TP_STRUCT__entry( + __field(enum pipe, pipe) + __field(u32, frame) + __field(u32, scanline) + __array(u32, crcs, 5) + ), + + TP_fast_assign( + __entry->pipe = crtc->pipe; + __entry->frame = intel_crtc_get_vblank_counter(crtc); + __entry->scanline = intel_get_crtc_scanline(crtc); + memcpy(__entry->crcs, crcs, sizeof(__entry->crcs)); + ), + + TP_printk("pipe %c, frame=%u, scanline=%u crc=%08x %08x %08x %08x %08x", + pipe_name(__entry->pipe), __entry->frame, __entry->scanline, + __entry->crcs[0], __entry->crcs[1], __entry->crcs[2], + __entry->crcs[3], __entry->crcs[4]) +); + +TRACE_EVENT(intel_cpu_fifo_underrun, + TP_PROTO(struct drm_i915_private *dev_priv, enum pipe pipe), + TP_ARGS(dev_priv, pipe), + + TP_STRUCT__entry( + __field(enum pipe, pipe) + __field(u32, frame) + __field(u32, scanline) + ), + + TP_fast_assign( + struct intel_crtc *crtc = intel_crtc_for_pipe(dev_priv, pipe); + __entry->pipe = pipe; + __entry->frame = intel_crtc_get_vblank_counter(crtc); + __entry->scanline = intel_get_crtc_scanline(crtc); + ), + + TP_printk("pipe %c, frame=%u, scanline=%u", + pipe_name(__entry->pipe), + __entry->frame, __entry->scanline) +); + +TRACE_EVENT(intel_pch_fifo_underrun, + TP_PROTO(struct drm_i915_private *dev_priv, enum pipe pch_transcoder), + TP_ARGS(dev_priv, pch_transcoder), + + TP_STRUCT__entry( + __field(enum pipe, pipe) + __field(u32, frame) + __field(u32, scanline) + ), + + TP_fast_assign( + enum pipe pipe = pch_transcoder; + struct intel_crtc *crtc = intel_crtc_for_pipe(dev_priv, pipe); + __entry->pipe = pipe; + __entry->frame = intel_crtc_get_vblank_counter(crtc); + __entry->scanline = intel_get_crtc_scanline(crtc); + ), + + TP_printk("pch transcoder %c, frame=%u, scanline=%u", + pipe_name(__entry->pipe), + __entry->frame, __entry->scanline) +); + +TRACE_EVENT(intel_memory_cxsr, + TP_PROTO(struct drm_i915_private *dev_priv, bool old, bool new), + TP_ARGS(dev_priv, old, new), + + TP_STRUCT__entry( + __array(u32, frame, 3) + __array(u32, scanline, 3) + __field(bool, old) + __field(bool, new) + ), + + TP_fast_assign( + struct intel_crtc *crtc; + for_each_intel_crtc(&dev_priv->drm, crtc) { + __entry->frame[crtc->pipe] = intel_crtc_get_vblank_counter(crtc); + __entry->scanline[crtc->pipe] = intel_get_crtc_scanline(crtc); + } + __entry->old = old; + __entry->new = new; + ), + + TP_printk("%s->%s, pipe A: frame=%u, scanline=%u, pipe B: frame=%u, scanline=%u, pipe C: frame=%u, scanline=%u", + str_on_off(__entry->old), str_on_off(__entry->new), + __entry->frame[PIPE_A], __entry->scanline[PIPE_A], + __entry->frame[PIPE_B], __entry->scanline[PIPE_B], + __entry->frame[PIPE_C], __entry->scanline[PIPE_C]) +); + +TRACE_EVENT(g4x_wm, + TP_PROTO(struct intel_crtc *crtc, const struct g4x_wm_values *wm), + TP_ARGS(crtc, wm), + + TP_STRUCT__entry( + __field(enum pipe, pipe) + __field(u32, frame) + __field(u32, scanline) + __field(u16, primary) + __field(u16, sprite) + __field(u16, cursor) + __field(u16, sr_plane) + __field(u16, sr_cursor) + __field(u16, sr_fbc) + __field(u16, hpll_plane) + __field(u16, hpll_cursor) + __field(u16, hpll_fbc) + __field(bool, cxsr) + __field(bool, hpll) + __field(bool, fbc) + ), + + TP_fast_assign( + __entry->pipe = crtc->pipe; + __entry->frame = intel_crtc_get_vblank_counter(crtc); + __entry->scanline = intel_get_crtc_scanline(crtc); + __entry->primary = wm->pipe[crtc->pipe].plane[PLANE_PRIMARY]; + __entry->sprite = wm->pipe[crtc->pipe].plane[PLANE_SPRITE0]; + __entry->cursor = wm->pipe[crtc->pipe].plane[PLANE_CURSOR]; + __entry->sr_plane = wm->sr.plane; + __entry->sr_cursor = wm->sr.cursor; + __entry->sr_fbc = wm->sr.fbc; + __entry->hpll_plane = wm->hpll.plane; + __entry->hpll_cursor = wm->hpll.cursor; + __entry->hpll_fbc = wm->hpll.fbc; + __entry->cxsr = wm->cxsr; + __entry->hpll = wm->hpll_en; + __entry->fbc = wm->fbc_en; + ), + + TP_printk("pipe %c, frame=%u, scanline=%u, wm %d/%d/%d, sr %s/%d/%d/%d, hpll %s/%d/%d/%d, fbc %s", + pipe_name(__entry->pipe), __entry->frame, __entry->scanline, + __entry->primary, __entry->sprite, __entry->cursor, + str_yes_no(__entry->cxsr), __entry->sr_plane, __entry->sr_cursor, __entry->sr_fbc, + str_yes_no(__entry->hpll), __entry->hpll_plane, __entry->hpll_cursor, __entry->hpll_fbc, + str_yes_no(__entry->fbc)) +); + +TRACE_EVENT(vlv_wm, + TP_PROTO(struct intel_crtc *crtc, const struct vlv_wm_values *wm), + TP_ARGS(crtc, wm), + + TP_STRUCT__entry( + __field(enum pipe, pipe) + __field(u32, frame) + __field(u32, scanline) + __field(u32, level) + __field(u32, cxsr) + __field(u32, primary) + __field(u32, sprite0) + __field(u32, sprite1) + __field(u32, cursor) + __field(u32, sr_plane) + __field(u32, sr_cursor) + ), + + TP_fast_assign( + __entry->pipe = crtc->pipe; + __entry->frame = intel_crtc_get_vblank_counter(crtc); + __entry->scanline = intel_get_crtc_scanline(crtc); + __entry->level = wm->level; + __entry->cxsr = wm->cxsr; + __entry->primary = wm->pipe[crtc->pipe].plane[PLANE_PRIMARY]; + __entry->sprite0 = wm->pipe[crtc->pipe].plane[PLANE_SPRITE0]; + __entry->sprite1 = wm->pipe[crtc->pipe].plane[PLANE_SPRITE1]; + __entry->cursor = wm->pipe[crtc->pipe].plane[PLANE_CURSOR]; + __entry->sr_plane = wm->sr.plane; + __entry->sr_cursor = wm->sr.cursor; + ), + + TP_printk("pipe %c, frame=%u, scanline=%u, level=%d, cxsr=%d, wm %d/%d/%d/%d, sr %d/%d", + pipe_name(__entry->pipe), __entry->frame, + __entry->scanline, __entry->level, __entry->cxsr, + __entry->primary, __entry->sprite0, __entry->sprite1, __entry->cursor, + __entry->sr_plane, __entry->sr_cursor) +); + +TRACE_EVENT(vlv_fifo_size, + TP_PROTO(struct intel_crtc *crtc, u32 sprite0_start, u32 sprite1_start, u32 fifo_size), + TP_ARGS(crtc, sprite0_start, sprite1_start, fifo_size), + + TP_STRUCT__entry( + __field(enum pipe, pipe) + __field(u32, frame) + __field(u32, scanline) + __field(u32, sprite0_start) + __field(u32, sprite1_start) + __field(u32, fifo_size) + ), + + TP_fast_assign( + __entry->pipe = crtc->pipe; + __entry->frame = intel_crtc_get_vblank_counter(crtc); + __entry->scanline = intel_get_crtc_scanline(crtc); + __entry->sprite0_start = sprite0_start; + __entry->sprite1_start = sprite1_start; + __entry->fifo_size = fifo_size; + ), + + TP_printk("pipe %c, frame=%u, scanline=%u, %d/%d/%d", + pipe_name(__entry->pipe), __entry->frame, + __entry->scanline, __entry->sprite0_start, + __entry->sprite1_start, __entry->fifo_size) +); + +TRACE_EVENT(intel_plane_update_noarm, + TP_PROTO(struct drm_plane *plane, struct intel_crtc *crtc), + TP_ARGS(plane, crtc), + + TP_STRUCT__entry( + __field(enum pipe, pipe) + __field(u32, frame) + __field(u32, scanline) + __array(int, src, 4) + __array(int, dst, 4) + __string(name, plane->name) + ), + + TP_fast_assign( + __assign_str(name, plane->name); + __entry->pipe = crtc->pipe; + __entry->frame = intel_crtc_get_vblank_counter(crtc); + __entry->scanline = intel_get_crtc_scanline(crtc); + memcpy(__entry->src, &plane->state->src, sizeof(__entry->src)); + memcpy(__entry->dst, &plane->state->dst, sizeof(__entry->dst)); + ), + + TP_printk("pipe %c, plane %s, frame=%u, scanline=%u, " DRM_RECT_FP_FMT " -> " DRM_RECT_FMT, + pipe_name(__entry->pipe), __get_str(name), + __entry->frame, __entry->scanline, + DRM_RECT_FP_ARG((const struct drm_rect *)__entry->src), + DRM_RECT_ARG((const struct drm_rect *)__entry->dst)) +); + +TRACE_EVENT(intel_plane_update_arm, + TP_PROTO(struct drm_plane *plane, struct intel_crtc *crtc), + TP_ARGS(plane, crtc), + + TP_STRUCT__entry( + __field(enum pipe, pipe) + __field(u32, frame) + __field(u32, scanline) + __array(int, src, 4) + __array(int, dst, 4) + __string(name, plane->name) + ), + + TP_fast_assign( + __assign_str(name, plane->name); + __entry->pipe = crtc->pipe; + __entry->frame = intel_crtc_get_vblank_counter(crtc); + __entry->scanline = intel_get_crtc_scanline(crtc); + memcpy(__entry->src, &plane->state->src, sizeof(__entry->src)); + memcpy(__entry->dst, &plane->state->dst, sizeof(__entry->dst)); + ), + + TP_printk("pipe %c, plane %s, frame=%u, scanline=%u, " DRM_RECT_FP_FMT " -> " DRM_RECT_FMT, + pipe_name(__entry->pipe), __get_str(name), + __entry->frame, __entry->scanline, + DRM_RECT_FP_ARG((const struct drm_rect *)__entry->src), + DRM_RECT_ARG((const struct drm_rect *)__entry->dst)) +); + +TRACE_EVENT(intel_plane_disable_arm, + TP_PROTO(struct drm_plane *plane, struct intel_crtc *crtc), + TP_ARGS(plane, crtc), + + TP_STRUCT__entry( + __field(enum pipe, pipe) + __field(u32, frame) + __field(u32, scanline) + __string(name, plane->name) + ), + + TP_fast_assign( + __assign_str(name, plane->name); + __entry->pipe = crtc->pipe; + __entry->frame = intel_crtc_get_vblank_counter(crtc); + __entry->scanline = intel_get_crtc_scanline(crtc); + ), + + TP_printk("pipe %c, plane %s, frame=%u, scanline=%u", + pipe_name(__entry->pipe), __get_str(name), + __entry->frame, __entry->scanline) +); + +TRACE_EVENT(intel_fbc_activate, + TP_PROTO(struct intel_plane *plane), + TP_ARGS(plane), + + TP_STRUCT__entry( + __field(enum pipe, pipe) + __field(u32, frame) + __field(u32, scanline) + ), + + TP_fast_assign( + struct intel_crtc *crtc = intel_crtc_for_pipe(to_i915(plane->base.dev), + plane->pipe); + __entry->pipe = crtc->pipe; + __entry->frame = intel_crtc_get_vblank_counter(crtc); + __entry->scanline = intel_get_crtc_scanline(crtc); + ), + + TP_printk("pipe %c, frame=%u, scanline=%u", + pipe_name(__entry->pipe), __entry->frame, __entry->scanline) +); + +TRACE_EVENT(intel_fbc_deactivate, + TP_PROTO(struct intel_plane *plane), + TP_ARGS(plane), + + TP_STRUCT__entry( + __field(enum pipe, pipe) + __field(u32, frame) + __field(u32, scanline) + ), + + TP_fast_assign( + struct intel_crtc *crtc = intel_crtc_for_pipe(to_i915(plane->base.dev), + plane->pipe); + __entry->pipe = crtc->pipe; + __entry->frame = intel_crtc_get_vblank_counter(crtc); + __entry->scanline = intel_get_crtc_scanline(crtc); + ), + + TP_printk("pipe %c, frame=%u, scanline=%u", + pipe_name(__entry->pipe), __entry->frame, __entry->scanline) +); + +TRACE_EVENT(intel_fbc_nuke, + TP_PROTO(struct intel_plane *plane), + TP_ARGS(plane), + + TP_STRUCT__entry( + __field(enum pipe, pipe) + __field(u32, frame) + __field(u32, scanline) + ), + + TP_fast_assign( + struct intel_crtc *crtc = intel_crtc_for_pipe(to_i915(plane->base.dev), + plane->pipe); + __entry->pipe = crtc->pipe; + __entry->frame = intel_crtc_get_vblank_counter(crtc); + __entry->scanline = intel_get_crtc_scanline(crtc); + ), + + TP_printk("pipe %c, frame=%u, scanline=%u", + pipe_name(__entry->pipe), __entry->frame, __entry->scanline) +); + +TRACE_EVENT(intel_crtc_vblank_work_start, + TP_PROTO(struct intel_crtc *crtc), + TP_ARGS(crtc), + + TP_STRUCT__entry( + __field(enum pipe, pipe) + __field(u32, frame) + __field(u32, scanline) + ), + + TP_fast_assign( + __entry->pipe = crtc->pipe; + __entry->frame = intel_crtc_get_vblank_counter(crtc); + __entry->scanline = intel_get_crtc_scanline(crtc); + ), + + TP_printk("pipe %c, frame=%u, scanline=%u", + pipe_name(__entry->pipe), __entry->frame, + __entry->scanline) +); + +TRACE_EVENT(intel_crtc_vblank_work_end, + TP_PROTO(struct intel_crtc *crtc), + TP_ARGS(crtc), + + TP_STRUCT__entry( + __field(enum pipe, pipe) + __field(u32, frame) + __field(u32, scanline) + ), + + TP_fast_assign( + __entry->pipe = crtc->pipe; + __entry->frame = intel_crtc_get_vblank_counter(crtc); + __entry->scanline = intel_get_crtc_scanline(crtc); + ), + + TP_printk("pipe %c, frame=%u, scanline=%u", + pipe_name(__entry->pipe), __entry->frame, + __entry->scanline) +); + +TRACE_EVENT(intel_pipe_update_start, + TP_PROTO(struct intel_crtc *crtc), + TP_ARGS(crtc), + + TP_STRUCT__entry( + __field(enum pipe, pipe) + __field(u32, frame) + __field(u32, scanline) + __field(u32, min) + __field(u32, max) + ), + + TP_fast_assign( + __entry->pipe = crtc->pipe; + __entry->frame = intel_crtc_get_vblank_counter(crtc); + __entry->scanline = intel_get_crtc_scanline(crtc); + __entry->min = crtc->debug.min_vbl; + __entry->max = crtc->debug.max_vbl; + ), + + TP_printk("pipe %c, frame=%u, scanline=%u, min=%u, max=%u", + pipe_name(__entry->pipe), __entry->frame, + __entry->scanline, __entry->min, __entry->max) +); + +TRACE_EVENT(intel_pipe_update_vblank_evaded, + TP_PROTO(struct intel_crtc *crtc), + TP_ARGS(crtc), + + TP_STRUCT__entry( + __field(enum pipe, pipe) + __field(u32, frame) + __field(u32, scanline) + __field(u32, min) + __field(u32, max) + ), + + TP_fast_assign( + __entry->pipe = crtc->pipe; + __entry->frame = crtc->debug.start_vbl_count; + __entry->scanline = crtc->debug.scanline_start; + __entry->min = crtc->debug.min_vbl; + __entry->max = crtc->debug.max_vbl; + ), + + TP_printk("pipe %c, frame=%u, scanline=%u, min=%u, max=%u", + pipe_name(__entry->pipe), __entry->frame, + __entry->scanline, __entry->min, __entry->max) +); + +TRACE_EVENT(intel_pipe_update_end, + TP_PROTO(struct intel_crtc *crtc, u32 frame, int scanline_end), + TP_ARGS(crtc, frame, scanline_end), + + TP_STRUCT__entry( + __field(enum pipe, pipe) + __field(u32, frame) + __field(u32, scanline) + ), + + TP_fast_assign( + __entry->pipe = crtc->pipe; + __entry->frame = frame; + __entry->scanline = scanline_end; + ), + + TP_printk("pipe %c, frame=%u, scanline=%u", + pipe_name(__entry->pipe), __entry->frame, + __entry->scanline) +); + +TRACE_EVENT(intel_frontbuffer_invalidate, + TP_PROTO(unsigned int frontbuffer_bits, unsigned int origin), + TP_ARGS(frontbuffer_bits, origin), + + TP_STRUCT__entry( + __field(unsigned int, frontbuffer_bits) + __field(unsigned int, origin) + ), + + TP_fast_assign( + __entry->frontbuffer_bits = frontbuffer_bits; + __entry->origin = origin; + ), + + TP_printk("frontbuffer_bits=0x%08x, origin=%u", + __entry->frontbuffer_bits, __entry->origin) +); + +TRACE_EVENT(intel_frontbuffer_flush, + TP_PROTO(unsigned int frontbuffer_bits, unsigned int origin), + TP_ARGS(frontbuffer_bits, origin), + + TP_STRUCT__entry( + __field(unsigned int, frontbuffer_bits) + __field(unsigned int, origin) + ), + + TP_fast_assign( + __entry->frontbuffer_bits = frontbuffer_bits; + __entry->origin = origin; + ), + + TP_printk("frontbuffer_bits=0x%08x, origin=%u", + __entry->frontbuffer_bits, __entry->origin) +); + +#endif /* __INTEL_DISPLAY_TRACE_H__ */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH ../../drivers/gpu/drm/i915/display +#define TRACE_INCLUDE_FILE intel_display_trace +#include diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h new file mode 100644 index 000000000..a8bf91a21 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_display_types.h @@ -0,0 +1,2074 @@ +/* + * Copyright (c) 2006 Dave Airlie + * Copyright (c) 2007-2008 Intel Corporation + * Jesse Barnes + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef __INTEL_DISPLAY_TYPES_H__ +#define __INTEL_DISPLAY_TYPES_H__ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "i915_vma.h" +#include "i915_vma_types.h" +#include "intel_bios.h" +#include "intel_display.h" +#include "intel_display_power.h" +#include "intel_dpll_mgr.h" +#include "intel_pm_types.h" + +struct drm_printer; +struct __intel_global_objs_state; +struct intel_ddi_buf_trans; +struct intel_fbc; +struct intel_connector; + +/* + * Display related stuff + */ + +/* these are outputs from the chip - integrated only + external chips are via DVO or SDVO output */ +enum intel_output_type { + INTEL_OUTPUT_UNUSED = 0, + INTEL_OUTPUT_ANALOG = 1, + INTEL_OUTPUT_DVO = 2, + INTEL_OUTPUT_SDVO = 3, + INTEL_OUTPUT_LVDS = 4, + INTEL_OUTPUT_TVOUT = 5, + INTEL_OUTPUT_HDMI = 6, + INTEL_OUTPUT_DP = 7, + INTEL_OUTPUT_EDP = 8, + INTEL_OUTPUT_DSI = 9, + INTEL_OUTPUT_DDI = 10, + INTEL_OUTPUT_DP_MST = 11, +}; + +enum hdmi_force_audio { + HDMI_AUDIO_OFF_DVI = -2, /* no aux data for HDMI-DVI converter */ + HDMI_AUDIO_OFF, /* force turn off HDMI audio */ + HDMI_AUDIO_AUTO, /* trust EDID */ + HDMI_AUDIO_ON, /* force turn on HDMI audio */ +}; + +/* "Broadcast RGB" property */ +enum intel_broadcast_rgb { + INTEL_BROADCAST_RGB_AUTO, + INTEL_BROADCAST_RGB_FULL, + INTEL_BROADCAST_RGB_LIMITED, +}; + +struct intel_fb_view { + /* + * The remap information used in the remapped and rotated views to + * create the DMA scatter-gather list for each FB color plane. This sg + * list is created along with the view type (gtt.type) specific + * i915_vma object and contains the list of FB object pages (reordered + * in the rotated view) that are visible in the view. + * In the normal view the FB object's backing store sg list is used + * directly and hence the remap information here is not used. + */ + struct i915_gtt_view gtt; + + /* + * The GTT view (gtt.type) specific information for each FB color + * plane. In the normal GTT view all formats (up to 4 color planes), + * in the rotated and remapped GTT view all no-CCS formats (up to 2 + * color planes) are supported. + * + * The view information shared by all FB color planes in the FB, + * like dst x/y and src/dst width, is stored separately in + * intel_plane_state. + */ + struct i915_color_plane_view { + u32 offset; + unsigned int x, y; + /* + * Plane stride in: + * bytes for 0/180 degree rotation + * pixels for 90/270 degree rotation + */ + unsigned int mapping_stride; + unsigned int scanout_stride; + } color_plane[4]; +}; + +struct intel_framebuffer { + struct drm_framebuffer base; + struct intel_frontbuffer *frontbuffer; + + /* Params to remap the FB pages and program the plane registers in each view. */ + struct intel_fb_view normal_view; + union { + struct intel_fb_view rotated_view; + struct intel_fb_view remapped_view; + }; + + struct i915_address_space *dpt_vm; +}; + +enum intel_hotplug_state { + INTEL_HOTPLUG_UNCHANGED, + INTEL_HOTPLUG_CHANGED, + INTEL_HOTPLUG_RETRY, +}; + +struct intel_encoder { + struct drm_encoder base; + + enum intel_output_type type; + enum port port; + u16 cloneable; + u8 pipe_mask; + enum intel_hotplug_state (*hotplug)(struct intel_encoder *encoder, + struct intel_connector *connector); + enum intel_output_type (*compute_output_type)(struct intel_encoder *, + struct intel_crtc_state *, + struct drm_connector_state *); + int (*compute_config)(struct intel_encoder *, + struct intel_crtc_state *, + struct drm_connector_state *); + int (*compute_config_late)(struct intel_encoder *, + struct intel_crtc_state *, + struct drm_connector_state *); + void (*update_prepare)(struct intel_atomic_state *, + struct intel_encoder *, + struct intel_crtc *); + void (*pre_pll_enable)(struct intel_atomic_state *, + struct intel_encoder *, + const struct intel_crtc_state *, + const struct drm_connector_state *); + void (*pre_enable)(struct intel_atomic_state *, + struct intel_encoder *, + const struct intel_crtc_state *, + const struct drm_connector_state *); + void (*enable)(struct intel_atomic_state *, + struct intel_encoder *, + const struct intel_crtc_state *, + const struct drm_connector_state *); + void (*update_complete)(struct intel_atomic_state *, + struct intel_encoder *, + struct intel_crtc *); + void (*disable)(struct intel_atomic_state *, + struct intel_encoder *, + const struct intel_crtc_state *, + const struct drm_connector_state *); + void (*post_disable)(struct intel_atomic_state *, + struct intel_encoder *, + const struct intel_crtc_state *, + const struct drm_connector_state *); + void (*post_pll_disable)(struct intel_atomic_state *, + struct intel_encoder *, + const struct intel_crtc_state *, + const struct drm_connector_state *); + void (*update_pipe)(struct intel_atomic_state *, + struct intel_encoder *, + const struct intel_crtc_state *, + const struct drm_connector_state *); + /* Read out the current hw state of this connector, returning true if + * the encoder is active. If the encoder is enabled it also set the pipe + * it is connected to in the pipe parameter. */ + bool (*get_hw_state)(struct intel_encoder *, enum pipe *pipe); + /* Reconstructs the equivalent mode flags for the current hardware + * state. This must be called _after_ display->get_pipe_config has + * pre-filled the pipe config. Note that intel_encoder->base.crtc must + * be set correctly before calling this function. */ + void (*get_config)(struct intel_encoder *, + struct intel_crtc_state *pipe_config); + + /* + * Optional hook called during init/resume to sync any state + * stored in the encoder (eg. DP link parameters) wrt. the HW state. + */ + void (*sync_state)(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state); + + /* + * Optional hook, returning true if this encoder allows a fastset + * during the initial commit, false otherwise. + */ + bool (*initial_fastset_check)(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state); + + /* + * Acquires the power domains needed for an active encoder during + * hardware state readout. + */ + void (*get_power_domains)(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state); + /* + * Called during system suspend after all pending requests for the + * encoder are flushed (for example for DP AUX transactions) and + * device interrupts are disabled. + */ + void (*suspend)(struct intel_encoder *); + /* + * Called during system reboot/shutdown after all the + * encoders have been disabled and suspended. + */ + void (*shutdown)(struct intel_encoder *encoder); + /* + * Enable/disable the clock to the port. + */ + void (*enable_clock)(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state); + void (*disable_clock)(struct intel_encoder *encoder); + /* + * Returns whether the port clock is enabled or not. + */ + bool (*is_clock_enabled)(struct intel_encoder *encoder); + const struct intel_ddi_buf_trans *(*get_buf_trans)(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + int *n_entries); + void (*set_signal_levels)(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state); + + enum hpd_pin hpd_pin; + enum intel_display_power_domain power_domain; + /* for communication with audio component; protected by av_mutex */ + const struct drm_connector *audio_connector; + + /* VBT information for this encoder (may be NULL for older platforms) */ + const struct intel_bios_encoder_data *devdata; +}; + +struct intel_panel_bl_funcs { + /* Connector and platform specific backlight functions */ + int (*setup)(struct intel_connector *connector, enum pipe pipe); + u32 (*get)(struct intel_connector *connector, enum pipe pipe); + void (*set)(const struct drm_connector_state *conn_state, u32 level); + void (*disable)(const struct drm_connector_state *conn_state, u32 level); + void (*enable)(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state, u32 level); + u32 (*hz_to_pwm)(struct intel_connector *connector, u32 hz); +}; + +enum drrs_type { + DRRS_TYPE_NONE, + DRRS_TYPE_STATIC, + DRRS_TYPE_SEAMLESS, +}; + +struct intel_vbt_panel_data { + struct drm_display_mode *lfp_lvds_vbt_mode; /* if any */ + struct drm_display_mode *sdvo_lvds_vbt_mode; /* if any */ + + /* Feature bits */ + int panel_type; + unsigned int lvds_dither:1; + unsigned int bios_lvds_val; /* initial [PCH_]LVDS reg val in VBIOS */ + + bool vrr; + + u8 seamless_drrs_min_refresh_rate; + enum drrs_type drrs_type; + + struct { + int max_link_rate; + int rate; + int lanes; + int preemphasis; + int vswing; + int bpp; + struct edp_power_seq pps; + u8 drrs_msa_timing_delay; + bool low_vswing; + bool initialized; + bool hobl; + } edp; + + struct { + bool enable; + bool full_link; + bool require_aux_wakeup; + int idle_frames; + int tp1_wakeup_time_us; + int tp2_tp3_wakeup_time_us; + int psr2_tp2_tp3_wakeup_time_us; + } psr; + + struct { + u16 pwm_freq_hz; + u16 brightness_precision_bits; + bool present; + bool active_low_pwm; + u8 min_brightness; /* min_brightness/255 of max */ + u8 controller; /* brightness controller number */ + enum intel_backlight_type type; + } backlight; + + /* MIPI DSI */ + struct { + u16 panel_id; + struct mipi_config *config; + struct mipi_pps_data *pps; + u16 bl_ports; + u16 cabc_ports; + u8 seq_version; + u32 size; + u8 *data; + const u8 *sequence[MIPI_SEQ_MAX]; + u8 *deassert_seq; /* Used by fixup_mipi_sequences() */ + enum drm_panel_orientation orientation; + } dsi; +}; + +struct intel_panel { + struct list_head fixed_modes; + + /* backlight */ + struct { + bool present; + u32 level; + u32 min; + u32 max; + bool enabled; + bool combination_mode; /* gen 2/4 only */ + bool active_low_pwm; + bool alternate_pwm_increment; /* lpt+ */ + + /* PWM chip */ + u32 pwm_level_min; + u32 pwm_level_max; + bool pwm_enabled; + bool util_pin_active_low; /* bxt+ */ + u8 controller; /* bxt+ only */ + struct pwm_device *pwm; + struct pwm_state pwm_state; + + /* DPCD backlight */ + union { + struct { + struct drm_edp_backlight_info info; + } vesa; + struct { + bool sdr_uses_aux; + } intel; + } edp; + + struct backlight_device *device; + + const struct intel_panel_bl_funcs *funcs; + const struct intel_panel_bl_funcs *pwm_funcs; + void (*power)(struct intel_connector *, bool enable); + } backlight; + + struct intel_vbt_panel_data vbt; +}; + +struct intel_digital_port; + +enum check_link_response { + HDCP_LINK_PROTECTED = 0, + HDCP_TOPOLOGY_CHANGE, + HDCP_LINK_INTEGRITY_FAILURE, + HDCP_REAUTH_REQUEST +}; + +/* + * This structure serves as a translation layer between the generic HDCP code + * and the bus-specific code. What that means is that HDCP over HDMI differs + * from HDCP over DP, so to account for these differences, we need to + * communicate with the receiver through this shim. + * + * For completeness, the 2 buses differ in the following ways: + * - DP AUX vs. DDC + * HDCP registers on the receiver are set via DP AUX for DP, and + * they are set via DDC for HDMI. + * - Receiver register offsets + * The offsets of the registers are different for DP vs. HDMI + * - Receiver register masks/offsets + * For instance, the ready bit for the KSV fifo is in a different + * place on DP vs HDMI + * - Receiver register names + * Seriously. In the DP spec, the 16-bit register containing + * downstream information is called BINFO, on HDMI it's called + * BSTATUS. To confuse matters further, DP has a BSTATUS register + * with a completely different definition. + * - KSV FIFO + * On HDMI, the ksv fifo is read all at once, whereas on DP it must + * be read 3 keys at a time + * - Aksv output + * Since Aksv is hidden in hardware, there's different procedures + * to send it over DP AUX vs DDC + */ +struct intel_hdcp_shim { + /* Outputs the transmitter's An and Aksv values to the receiver. */ + int (*write_an_aksv)(struct intel_digital_port *dig_port, u8 *an); + + /* Reads the receiver's key selection vector */ + int (*read_bksv)(struct intel_digital_port *dig_port, u8 *bksv); + + /* + * Reads BINFO from DP receivers and BSTATUS from HDMI receivers. The + * definitions are the same in the respective specs, but the names are + * different. Call it BSTATUS since that's the name the HDMI spec + * uses and it was there first. + */ + int (*read_bstatus)(struct intel_digital_port *dig_port, + u8 *bstatus); + + /* Determines whether a repeater is present downstream */ + int (*repeater_present)(struct intel_digital_port *dig_port, + bool *repeater_present); + + /* Reads the receiver's Ri' value */ + int (*read_ri_prime)(struct intel_digital_port *dig_port, u8 *ri); + + /* Determines if the receiver's KSV FIFO is ready for consumption */ + int (*read_ksv_ready)(struct intel_digital_port *dig_port, + bool *ksv_ready); + + /* Reads the ksv fifo for num_downstream devices */ + int (*read_ksv_fifo)(struct intel_digital_port *dig_port, + int num_downstream, u8 *ksv_fifo); + + /* Reads a 32-bit part of V' from the receiver */ + int (*read_v_prime_part)(struct intel_digital_port *dig_port, + int i, u32 *part); + + /* Enables HDCP signalling on the port */ + int (*toggle_signalling)(struct intel_digital_port *dig_port, + enum transcoder cpu_transcoder, + bool enable); + + /* Enable/Disable stream encryption on DP MST Transport Link */ + int (*stream_encryption)(struct intel_connector *connector, + bool enable); + + /* Ensures the link is still protected */ + bool (*check_link)(struct intel_digital_port *dig_port, + struct intel_connector *connector); + + /* Detects panel's hdcp capability. This is optional for HDMI. */ + int (*hdcp_capable)(struct intel_digital_port *dig_port, + bool *hdcp_capable); + + /* HDCP adaptation(DP/HDMI) required on the port */ + enum hdcp_wired_protocol protocol; + + /* Detects whether sink is HDCP2.2 capable */ + int (*hdcp_2_2_capable)(struct intel_digital_port *dig_port, + bool *capable); + + /* Write HDCP2.2 messages */ + int (*write_2_2_msg)(struct intel_digital_port *dig_port, + void *buf, size_t size); + + /* Read HDCP2.2 messages */ + int (*read_2_2_msg)(struct intel_digital_port *dig_port, + u8 msg_id, void *buf, size_t size); + + /* + * Implementation of DP HDCP2.2 Errata for the communication of stream + * type to Receivers. In DP HDCP2.2 Stream type is one of the input to + * the HDCP2.2 Cipher for En/De-Cryption. Not applicable for HDMI. + */ + int (*config_stream_type)(struct intel_digital_port *dig_port, + bool is_repeater, u8 type); + + /* Enable/Disable HDCP 2.2 stream encryption on DP MST Transport Link */ + int (*stream_2_2_encryption)(struct intel_connector *connector, + bool enable); + + /* HDCP2.2 Link Integrity Check */ + int (*check_2_2_link)(struct intel_digital_port *dig_port, + struct intel_connector *connector); +}; + +struct intel_hdcp { + const struct intel_hdcp_shim *shim; + /* Mutex for hdcp state of the connector */ + struct mutex mutex; + u64 value; + struct delayed_work check_work; + struct work_struct prop_work; + + /* HDCP1.4 Encryption status */ + bool hdcp_encrypted; + + /* HDCP2.2 related definitions */ + /* Flag indicates whether this connector supports HDCP2.2 or not. */ + bool hdcp2_supported; + + /* HDCP2.2 Encryption status */ + bool hdcp2_encrypted; + + /* + * Content Stream Type defined by content owner. TYPE0(0x0) content can + * flow in the link protected by HDCP2.2 or HDCP1.4, where as TYPE1(0x1) + * content can flow only through a link protected by HDCP2.2. + */ + u8 content_type; + + bool is_paired; + bool is_repeater; + + /* + * Count of ReceiverID_List received. Initialized to 0 at AKE_INIT. + * Incremented after processing the RepeaterAuth_Send_ReceiverID_List. + * When it rolls over re-auth has to be triggered. + */ + u32 seq_num_v; + + /* + * Count of RepeaterAuth_Stream_Manage msg propagated. + * Initialized to 0 on AKE_INIT. Incremented after every successful + * transmission of RepeaterAuth_Stream_Manage message. When it rolls + * over re-Auth has to be triggered. + */ + u32 seq_num_m; + + /* + * Work queue to signal the CP_IRQ. Used for the waiters to read the + * available information from HDCP DP sink. + */ + wait_queue_head_t cp_irq_queue; + atomic_t cp_irq_count; + int cp_irq_count_cached; + + /* + * HDCP register access for gen12+ need the transcoder associated. + * Transcoder attached to the connector could be changed at modeset. + * Hence caching the transcoder here. + */ + enum transcoder cpu_transcoder; + /* Only used for DP MST stream encryption */ + enum transcoder stream_transcoder; +}; + +struct intel_connector { + struct drm_connector base; + /* + * The fixed encoder this connector is connected to. + */ + struct intel_encoder *encoder; + + /* ACPI device id for ACPI and driver cooperation */ + u32 acpi_device_id; + + /* Reads out the current hw, returning true if the connector is enabled + * and active (i.e. dpms ON state). */ + bool (*get_hw_state)(struct intel_connector *); + + /* Panel info for eDP and LVDS */ + struct intel_panel panel; + + /* Cached EDID for eDP and LVDS. May hold ERR_PTR for invalid EDID. */ + struct edid *edid; + struct edid *detect_edid; + + /* Number of times hotplug detection was tried after an HPD interrupt */ + int hotplug_retries; + + /* since POLL and HPD connectors may use the same HPD line keep the native + state of connector->polled in case hotplug storm detection changes it */ + u8 polled; + + struct drm_dp_mst_port *port; + + struct intel_dp *mst_port; + + /* Work struct to schedule a uevent on link train failure */ + struct work_struct modeset_retry_work; + + struct intel_hdcp hdcp; +}; + +struct intel_digital_connector_state { + struct drm_connector_state base; + + enum hdmi_force_audio force_audio; + int broadcast_rgb; +}; + +#define to_intel_digital_connector_state(x) container_of(x, struct intel_digital_connector_state, base) + +struct dpll { + /* given values */ + int n; + int m1, m2; + int p1, p2; + /* derived values */ + int dot; + int vco; + int m; + int p; +}; + +struct intel_atomic_state { + struct drm_atomic_state base; + + intel_wakeref_t wakeref; + + struct __intel_global_objs_state *global_objs; + int num_global_objs; + + bool dpll_set, modeset; + + struct intel_shared_dpll_state shared_dpll[I915_NUM_PLLS]; + + /* + * Current watermarks can't be trusted during hardware readout, so + * don't bother calculating intermediate watermarks. + */ + bool skip_intermediate_wm; + + bool rps_interactive; + + struct i915_sw_fence commit_ready; + + struct llist_node freed; +}; + +struct intel_plane_state { + struct drm_plane_state uapi; + + /* + * actual hardware state, the state we program to the hardware. + * The following members are used to verify the hardware state: + * During initial hw readout, they need to be copied from uapi. + */ + struct { + struct drm_crtc *crtc; + struct drm_framebuffer *fb; + + u16 alpha; + u16 pixel_blend_mode; + unsigned int rotation; + enum drm_color_encoding color_encoding; + enum drm_color_range color_range; + enum drm_scaling_filter scaling_filter; + } hw; + + struct i915_vma *ggtt_vma; + struct i915_vma *dpt_vma; + unsigned long flags; +#define PLANE_HAS_FENCE BIT(0) + + struct intel_fb_view view; + + /* Plane pxp decryption state */ + bool decrypt; + + /* Plane state to display black pixels when pxp is borked */ + bool force_black; + + /* plane control register */ + u32 ctl; + + /* plane color control register */ + u32 color_ctl; + + /* chroma upsampler control register */ + u32 cus_ctl; + + /* + * scaler_id + * = -1 : not using a scaler + * >= 0 : using a scalers + * + * plane requiring a scaler: + * - During check_plane, its bit is set in + * crtc_state->scaler_state.scaler_users by calling helper function + * update_scaler_plane. + * - scaler_id indicates the scaler it got assigned. + * + * plane doesn't require a scaler: + * - this can happen when scaling is no more required or plane simply + * got disabled. + * - During check_plane, corresponding bit is reset in + * crtc_state->scaler_state.scaler_users by calling helper function + * update_scaler_plane. + */ + int scaler_id; + + /* + * planar_linked_plane: + * + * ICL planar formats require 2 planes that are updated as pairs. + * This member is used to make sure the other plane is also updated + * when required, and for update_slave() to find the correct + * plane_state to pass as argument. + */ + struct intel_plane *planar_linked_plane; + + /* + * planar_slave: + * If set don't update use the linked plane's state for updating + * this plane during atomic commit with the update_slave() callback. + * + * It's also used by the watermark code to ignore wm calculations on + * this plane. They're calculated by the linked plane's wm code. + */ + u32 planar_slave; + + struct drm_intel_sprite_colorkey ckey; + + struct drm_rect psr2_sel_fetch_area; + + /* Clear Color Value */ + u64 ccval; + + const char *no_fbc_reason; +}; + +struct intel_initial_plane_config { + struct intel_framebuffer *fb; + struct i915_vma *vma; + unsigned int tiling; + int size; + u32 base; + u8 rotation; +}; + +struct intel_scaler { + int in_use; + u32 mode; +}; + +struct intel_crtc_scaler_state { +#define SKL_NUM_SCALERS 2 + struct intel_scaler scalers[SKL_NUM_SCALERS]; + + /* + * scaler_users: keeps track of users requesting scalers on this crtc. + * + * If a bit is set, a user is using a scaler. + * Here user can be a plane or crtc as defined below: + * bits 0-30 - plane (bit position is index from drm_plane_index) + * bit 31 - crtc + * + * Instead of creating a new index to cover planes and crtc, using + * existing drm_plane_index for planes which is well less than 31 + * planes and bit 31 for crtc. This should be fine to cover all + * our platforms. + * + * intel_atomic_setup_scalers will setup available scalers to users + * requesting scalers. It will gracefully fail if request exceeds + * avilability. + */ +#define SKL_CRTC_INDEX 31 + unsigned scaler_users; + + /* scaler used by crtc for panel fitting purpose */ + int scaler_id; +}; + +/* {crtc,crtc_state}->mode_flags */ +/* Flag to get scanline using frame time stamps */ +#define I915_MODE_FLAG_GET_SCANLINE_FROM_TIMESTAMP (1<<1) +/* Flag to use the scanline counter instead of the pixel counter */ +#define I915_MODE_FLAG_USE_SCANLINE_COUNTER (1<<2) +/* + * TE0 or TE1 flag is set if the crtc has a DSI encoder which + * is operating in command mode. + * Flag to use TE from DSI0 instead of VBI in command mode + */ +#define I915_MODE_FLAG_DSI_USE_TE0 (1<<3) +/* Flag to use TE from DSI1 instead of VBI in command mode */ +#define I915_MODE_FLAG_DSI_USE_TE1 (1<<4) +/* Flag to indicate mipi dsi periodic command mode where we do not get TE */ +#define I915_MODE_FLAG_DSI_PERIODIC_CMD_MODE (1<<5) +/* Do tricks to make vblank timestamps sane with VRR? */ +#define I915_MODE_FLAG_VRR (1<<6) + +struct intel_wm_level { + bool enable; + u32 pri_val; + u32 spr_val; + u32 cur_val; + u32 fbc_val; +}; + +struct intel_pipe_wm { + struct intel_wm_level wm[5]; + bool fbc_wm_enabled; + bool pipe_enabled; + bool sprites_enabled; + bool sprites_scaled; +}; + +struct skl_wm_level { + u16 min_ddb_alloc; + u16 blocks; + u8 lines; + bool enable; + bool ignore_lines; + bool can_sagv; +}; + +struct skl_plane_wm { + struct skl_wm_level wm[8]; + struct skl_wm_level uv_wm[8]; + struct skl_wm_level trans_wm; + struct { + struct skl_wm_level wm0; + struct skl_wm_level trans_wm; + } sagv; + bool is_planar; +}; + +struct skl_pipe_wm { + struct skl_plane_wm planes[I915_MAX_PLANES]; + bool use_sagv_wm; +}; + +enum vlv_wm_level { + VLV_WM_LEVEL_PM2, + VLV_WM_LEVEL_PM5, + VLV_WM_LEVEL_DDR_DVFS, + NUM_VLV_WM_LEVELS, +}; + +struct vlv_wm_state { + struct g4x_pipe_wm wm[NUM_VLV_WM_LEVELS]; + struct g4x_sr_wm sr[NUM_VLV_WM_LEVELS]; + u8 num_levels; + bool cxsr; +}; + +struct vlv_fifo_state { + u16 plane[I915_MAX_PLANES]; +}; + +enum g4x_wm_level { + G4X_WM_LEVEL_NORMAL, + G4X_WM_LEVEL_SR, + G4X_WM_LEVEL_HPLL, + NUM_G4X_WM_LEVELS, +}; + +struct g4x_wm_state { + struct g4x_pipe_wm wm; + struct g4x_sr_wm sr; + struct g4x_sr_wm hpll; + bool cxsr; + bool hpll_en; + bool fbc_en; +}; + +struct intel_crtc_wm_state { + union { + /* + * raw: + * The "raw" watermark values produced by the formula + * given the plane's current state. They do not consider + * how much FIFO is actually allocated for each plane. + * + * optimal: + * The "optimal" watermark values given the current + * state of the planes and the amount of FIFO + * allocated to each, ignoring any previous state + * of the planes. + * + * intermediate: + * The "intermediate" watermark values when transitioning + * between the old and new "optimal" values. Used when + * the watermark registers are single buffered and hence + * their state changes asynchronously with regards to the + * actual plane registers. These are essentially the + * worst case combination of the old and new "optimal" + * watermarks, which are therefore safe to use when the + * plane is in either its old or new state. + */ + struct { + struct intel_pipe_wm intermediate; + struct intel_pipe_wm optimal; + } ilk; + + struct { + struct skl_pipe_wm raw; + /* gen9+ only needs 1-step wm programming */ + struct skl_pipe_wm optimal; + struct skl_ddb_entry ddb; + /* + * pre-icl: for packed/planar CbCr + * icl+: for everything + */ + struct skl_ddb_entry plane_ddb[I915_MAX_PLANES]; + /* pre-icl: for planar Y */ + struct skl_ddb_entry plane_ddb_y[I915_MAX_PLANES]; + } skl; + + struct { + struct g4x_pipe_wm raw[NUM_VLV_WM_LEVELS]; /* not inverted */ + struct vlv_wm_state intermediate; /* inverted */ + struct vlv_wm_state optimal; /* inverted */ + struct vlv_fifo_state fifo_state; + } vlv; + + struct { + struct g4x_pipe_wm raw[NUM_G4X_WM_LEVELS]; + struct g4x_wm_state intermediate; + struct g4x_wm_state optimal; + } g4x; + }; + + /* + * Platforms with two-step watermark programming will need to + * update watermark programming post-vblank to switch from the + * safe intermediate watermarks to the optimal final + * watermarks. + */ + bool need_postvbl_update; +}; + +enum intel_output_format { + INTEL_OUTPUT_FORMAT_RGB, + INTEL_OUTPUT_FORMAT_YCBCR420, + INTEL_OUTPUT_FORMAT_YCBCR444, +}; + +struct intel_mpllb_state { + u32 clock; /* in KHz */ + u32 ref_control; + u32 mpllb_cp; + u32 mpllb_div; + u32 mpllb_div2; + u32 mpllb_fracn1; + u32 mpllb_fracn2; + u32 mpllb_sscen; + u32 mpllb_sscstep; +}; + +struct intel_crtc_state { + /* + * uapi (drm) state. This is the software state shown to userspace. + * In particular, the following members are used for bookkeeping: + * - crtc + * - state + * - *_changed + * - event + * - commit + * - mode_blob + */ + struct drm_crtc_state uapi; + + /* + * actual hardware state, the state we program to the hardware. + * The following members are used to verify the hardware state: + * - enable + * - active + * - mode / pipe_mode / adjusted_mode + * - color property blobs. + * + * During initial hw readout, they need to be copied to uapi. + * + * Bigjoiner will allow a transcoder mode that spans 2 pipes; + * Use the pipe_mode for calculations like watermarks, pipe + * scaler, and bandwidth. + * + * Use adjusted_mode for things that need to know the full + * mode on the transcoder, which spans all pipes. + */ + struct { + bool active, enable; + struct drm_property_blob *degamma_lut, *gamma_lut, *ctm; + struct drm_display_mode mode, pipe_mode, adjusted_mode; + enum drm_scaling_filter scaling_filter; + } hw; + + /** + * quirks - bitfield with hw state readout quirks + * + * For various reasons the hw state readout code might not be able to + * completely faithfully read out the current state. These cases are + * tracked with quirk flags so that fastboot and state checker can act + * accordingly. + */ +#define PIPE_CONFIG_QUIRK_MODE_SYNC_FLAGS (1<<0) /* unreliable sync mode.flags */ + unsigned long quirks; + + unsigned fb_bits; /* framebuffers to flip */ + bool update_pipe; /* can a fast modeset be performed? */ + bool disable_cxsr; + bool update_wm_pre, update_wm_post; /* watermarks are updated */ + bool fifo_changed; /* FIFO split is changed */ + bool preload_luts; + bool inherited; /* state inherited from BIOS? */ + + /* Ask the hardware to actually async flip? */ + bool do_async_flip; + + /* Pipe source size (ie. panel fitter input size) + * All planes will be positioned inside this space, + * and get clipped at the edges. */ + struct drm_rect pipe_src; + + /* + * Pipe pixel rate, adjusted for + * panel fitter/pipe scaler downscaling. + */ + unsigned int pixel_rate; + + /* Whether to set up the PCH/FDI. Note that we never allow sharing + * between pch encoders and cpu encoders. */ + bool has_pch_encoder; + + /* Are we sending infoframes on the attached port */ + bool has_infoframe; + + /* CPU Transcoder for the pipe. Currently this can only differ from the + * pipe on Haswell and later (where we have a special eDP transcoder) + * and Broxton (where we have special DSI transcoders). */ + enum transcoder cpu_transcoder; + + /* + * Use reduced/limited/broadcast rbg range, compressing from the full + * range fed into the crtcs. + */ + bool limited_color_range; + + /* Bitmask of encoder types (enum intel_output_type) + * driven by the pipe. + */ + unsigned int output_types; + + /* Whether we should send NULL infoframes. Required for audio. */ + bool has_hdmi_sink; + + /* Audio enabled on this pipe. Only valid if either has_hdmi_sink or + * has_dp_encoder is set. */ + bool has_audio; + + /* + * Enable dithering, used when the selected pipe bpp doesn't match the + * plane bpp. + */ + bool dither; + + /* + * Dither gets enabled for 18bpp which causes CRC mismatch errors for + * compliance video pattern tests. + * Disable dither only if it is a compliance test request for + * 18bpp. + */ + bool dither_force_disable; + + /* Controls for the clock computation, to override various stages. */ + bool clock_set; + + /* SDVO TV has a bunch of special case. To make multifunction encoders + * work correctly, we need to track this at runtime.*/ + bool sdvo_tv_clock; + + /* + * crtc bandwidth limit, don't increase pipe bpp or clock if not really + * required. This is set in the 2nd loop of calling encoder's + * ->compute_config if the first pick doesn't work out. + */ + bool bw_constrained; + + /* Settings for the intel dpll used on pretty much everything but + * haswell. */ + struct dpll dpll; + + /* Selected dpll when shared or NULL. */ + struct intel_shared_dpll *shared_dpll; + + /* Actual register state of the dpll, for shared dpll cross-checking. */ + union { + struct intel_dpll_hw_state dpll_hw_state; + struct intel_mpllb_state mpllb_state; + }; + + /* + * ICL reserved DPLLs for the CRTC/port. The active PLL is selected by + * setting shared_dpll and dpll_hw_state to one of these reserved ones. + */ + struct icl_port_dpll { + struct intel_shared_dpll *pll; + struct intel_dpll_hw_state hw_state; + } icl_port_dplls[ICL_PORT_DPLL_COUNT]; + + /* DSI PLL registers */ + struct { + u32 ctrl, div; + } dsi_pll; + + int pipe_bpp; + struct intel_link_m_n dp_m_n; + + /* m2_n2 for eDP downclock */ + struct intel_link_m_n dp_m2_n2; + bool has_drrs; + bool seamless_m_n; + + /* PSR is supported but might not be enabled due the lack of enabled planes */ + bool has_psr; + bool has_psr2; + bool enable_psr2_sel_fetch; + bool req_psr2_sdp_prior_scanline; + u32 dc3co_exitline; + u16 su_y_granularity; + struct drm_dp_vsc_sdp psr_vsc; + + /* + * Frequence the dpll for the port should run at. Differs from the + * adjusted dotclock e.g. for DP or 10/12bpc hdmi mode. This is also + * already multiplied by pixel_multiplier. + */ + int port_clock; + + /* Used by SDVO (and if we ever fix it, HDMI). */ + unsigned pixel_multiplier; + + /* I915_MODE_FLAG_* */ + u8 mode_flags; + + u8 lane_count; + + /* + * Used by platforms having DP/HDMI PHY with programmable lane + * latency optimization. + */ + u8 lane_lat_optim_mask; + + /* minimum acceptable voltage level */ + u8 min_voltage_level; + + /* Panel fitter controls for gen2-gen4 + VLV */ + struct { + u32 control; + u32 pgm_ratios; + u32 lvds_border_bits; + } gmch_pfit; + + /* Panel fitter placement and size for Ironlake+ */ + struct { + struct drm_rect dst; + bool enabled; + bool force_thru; + } pch_pfit; + + /* FDI configuration, only valid if has_pch_encoder is set. */ + int fdi_lanes; + struct intel_link_m_n fdi_m_n; + + bool ips_enabled; + + bool crc_enabled; + + bool double_wide; + + int pbn; + + struct intel_crtc_scaler_state scaler_state; + + /* w/a for waiting 2 vblanks during crtc enable */ + enum pipe hsw_workaround_pipe; + + /* IVB sprite scaling w/a (WaCxSRDisabledForSpriteScaling:ivb) */ + bool disable_lp_wm; + + struct intel_crtc_wm_state wm; + + int min_cdclk[I915_MAX_PLANES]; + + /* for packed/planar CbCr */ + u32 data_rate[I915_MAX_PLANES]; + /* for planar Y */ + u32 data_rate_y[I915_MAX_PLANES]; + + /* FIXME unify with data_rate[]? */ + u64 rel_data_rate[I915_MAX_PLANES]; + u64 rel_data_rate_y[I915_MAX_PLANES]; + + /* Gamma mode programmed on the pipe */ + u32 gamma_mode; + + union { + /* CSC mode programmed on the pipe */ + u32 csc_mode; + + /* CHV CGM mode */ + u32 cgm_mode; + }; + + /* bitmask of logically enabled planes (enum plane_id) */ + u8 enabled_planes; + + /* bitmask of actually visible planes (enum plane_id) */ + u8 active_planes; + u8 scaled_planes; + u8 nv12_planes; + u8 c8_planes; + + /* bitmask of planes that will be updated during the commit */ + u8 update_planes; + + u8 framestart_delay; /* 1-4 */ + u8 msa_timing_delay; /* 0-3 */ + + struct { + u32 enable; + u32 gcp; + union hdmi_infoframe avi; + union hdmi_infoframe spd; + union hdmi_infoframe hdmi; + union hdmi_infoframe drm; + struct drm_dp_vsc_sdp vsc; + } infoframes; + + /* HDMI scrambling status */ + bool hdmi_scrambling; + + /* HDMI High TMDS char rate ratio */ + bool hdmi_high_tmds_clock_ratio; + + /* Output format RGB/YCBCR etc */ + enum intel_output_format output_format; + + /* enable pipe gamma? */ + bool gamma_enable; + + /* enable pipe csc? */ + bool csc_enable; + + /* big joiner pipe bitmask */ + u8 bigjoiner_pipes; + + /* Display Stream compression state */ + struct { + bool compression_enable; + bool dsc_split; + u16 compressed_bpp; + u8 slice_count; + struct drm_dsc_config config; + } dsc; + + /* HSW+ linetime watermarks */ + u16 linetime; + u16 ips_linetime; + + /* Forward Error correction State */ + bool fec_enable; + + /* Pointer to master transcoder in case of tiled displays */ + enum transcoder master_transcoder; + + /* Bitmask to indicate slaves attached */ + u8 sync_mode_slaves_mask; + + /* Only valid on TGL+ */ + enum transcoder mst_master_transcoder; + + /* For DSB related info */ + struct intel_dsb *dsb; + + u32 psr2_man_track_ctl; + + /* Variable Refresh Rate state */ + struct { + bool enable; + u8 pipeline_full; + u16 flipline, vmin, vmax, guardband; + } vrr; + + /* Stream Splitter for eDP MSO */ + struct { + bool enable; + u8 link_count; + u8 pixel_overlap; + } splitter; + + /* for loading single buffered registers during vblank */ + struct drm_vblank_work vblank_work; +}; + +enum intel_pipe_crc_source { + INTEL_PIPE_CRC_SOURCE_NONE, + INTEL_PIPE_CRC_SOURCE_PLANE1, + INTEL_PIPE_CRC_SOURCE_PLANE2, + INTEL_PIPE_CRC_SOURCE_PLANE3, + INTEL_PIPE_CRC_SOURCE_PLANE4, + INTEL_PIPE_CRC_SOURCE_PLANE5, + INTEL_PIPE_CRC_SOURCE_PLANE6, + INTEL_PIPE_CRC_SOURCE_PLANE7, + INTEL_PIPE_CRC_SOURCE_PIPE, + /* TV/DP on pre-gen5/vlv can't use the pipe source. */ + INTEL_PIPE_CRC_SOURCE_TV, + INTEL_PIPE_CRC_SOURCE_DP_B, + INTEL_PIPE_CRC_SOURCE_DP_C, + INTEL_PIPE_CRC_SOURCE_DP_D, + INTEL_PIPE_CRC_SOURCE_AUTO, + INTEL_PIPE_CRC_SOURCE_MAX, +}; + +enum drrs_refresh_rate { + DRRS_REFRESH_RATE_HIGH, + DRRS_REFRESH_RATE_LOW, +}; + +#define INTEL_PIPE_CRC_ENTRIES_NR 128 +struct intel_pipe_crc { + spinlock_t lock; + int skipped; + enum intel_pipe_crc_source source; +}; + +struct intel_crtc { + struct drm_crtc base; + enum pipe pipe; + /* + * Whether the crtc and the connected output pipeline is active. Implies + * that crtc->enabled is set, i.e. the current mode configuration has + * some outputs connected to this crtc. + */ + bool active; + u8 plane_ids_mask; + + /* I915_MODE_FLAG_* */ + u8 mode_flags; + + u16 vmax_vblank_start; + + struct intel_display_power_domain_set enabled_power_domains; + struct intel_overlay *overlay; + + struct intel_crtc_state *config; + + /* Access to these should be protected by dev_priv->irq_lock. */ + bool cpu_fifo_underrun_disabled; + bool pch_fifo_underrun_disabled; + + /* per-pipe watermark state */ + struct { + /* watermarks currently being used */ + union { + struct intel_pipe_wm ilk; + struct vlv_wm_state vlv; + struct g4x_wm_state g4x; + } active; + } wm; + + struct { + struct mutex mutex; + struct delayed_work work; + enum drrs_refresh_rate refresh_rate; + unsigned int frontbuffer_bits; + unsigned int busy_frontbuffer_bits; + enum transcoder cpu_transcoder; + struct intel_link_m_n m_n, m2_n2; + } drrs; + + int scanline_offset; + + struct { + unsigned start_vbl_count; + ktime_t start_vbl_time; + int min_vbl, max_vbl; + int scanline_start; +#ifdef CONFIG_DRM_I915_DEBUG_VBLANK_EVADE + struct { + u64 min; + u64 max; + u64 sum; + unsigned int over; + unsigned int times[17]; /* [1us, 16ms] */ + } vbl; +#endif + } debug; + + /* scalers available on this crtc */ + int num_scalers; + + /* for loading single buffered registers during vblank */ + struct pm_qos_request vblank_pm_qos; + +#ifdef CONFIG_DEBUG_FS + struct intel_pipe_crc pipe_crc; +#endif +}; + +struct intel_plane { + struct drm_plane base; + enum i9xx_plane_id i9xx_plane; + enum plane_id id; + enum pipe pipe; + bool need_async_flip_disable_wa; + u32 frontbuffer_bit; + + struct { + u32 base, cntl, size; + } cursor; + + struct intel_fbc *fbc; + + /* + * NOTE: Do not place new plane state fields here (e.g., when adding + * new plane properties). New runtime state should now be placed in + * the intel_plane_state structure and accessed via plane_state. + */ + + int (*min_width)(const struct drm_framebuffer *fb, + int color_plane, + unsigned int rotation); + int (*max_width)(const struct drm_framebuffer *fb, + int color_plane, + unsigned int rotation); + int (*max_height)(const struct drm_framebuffer *fb, + int color_plane, + unsigned int rotation); + unsigned int (*max_stride)(struct intel_plane *plane, + u32 pixel_format, u64 modifier, + unsigned int rotation); + /* Write all non-self arming plane registers */ + void (*update_noarm)(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state); + /* Write all self-arming plane registers */ + void (*update_arm)(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state); + /* Disable the plane, must arm */ + void (*disable_arm)(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state); + bool (*get_hw_state)(struct intel_plane *plane, enum pipe *pipe); + int (*check_plane)(struct intel_crtc_state *crtc_state, + struct intel_plane_state *plane_state); + int (*min_cdclk)(const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state); + void (*async_flip)(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state, + bool async_flip); + void (*enable_flip_done)(struct intel_plane *plane); + void (*disable_flip_done)(struct intel_plane *plane); +}; + +struct intel_watermark_params { + u16 fifo_size; + u16 max_wm; + u8 default_wm; + u8 guard_size; + u8 cacheline_size; +}; + +struct cxsr_latency { + bool is_desktop : 1; + bool is_ddr3 : 1; + u16 fsb_freq; + u16 mem_freq; + u16 display_sr; + u16 display_hpll_disable; + u16 cursor_sr; + u16 cursor_hpll_disable; +}; + +#define to_intel_atomic_state(x) container_of(x, struct intel_atomic_state, base) +#define to_intel_crtc(x) container_of(x, struct intel_crtc, base) +#define to_intel_crtc_state(x) container_of(x, struct intel_crtc_state, uapi) +#define to_intel_connector(x) container_of(x, struct intel_connector, base) +#define to_intel_encoder(x) container_of(x, struct intel_encoder, base) +#define to_intel_framebuffer(x) container_of(x, struct intel_framebuffer, base) +#define to_intel_plane(x) container_of(x, struct intel_plane, base) +#define to_intel_plane_state(x) container_of(x, struct intel_plane_state, uapi) +#define intel_fb_obj(x) ((x) ? to_intel_bo((x)->obj[0]) : NULL) + +struct intel_hdmi { + i915_reg_t hdmi_reg; + int ddc_bus; + struct { + enum drm_dp_dual_mode_type type; + int max_tmds_clock; + } dp_dual_mode; + bool has_hdmi_sink; + bool has_audio; + struct intel_connector *attached_connector; + struct cec_notifier *cec_notifier; +}; + +struct intel_dp_mst_encoder; + +struct intel_dp_compliance_data { + unsigned long edid; + u8 video_pattern; + u16 hdisplay, vdisplay; + u8 bpc; + struct drm_dp_phy_test_params phytest; +}; + +struct intel_dp_compliance { + unsigned long test_type; + struct intel_dp_compliance_data test_data; + bool test_active; + int test_link_rate; + u8 test_lane_count; +}; + +struct intel_dp_pcon_frl { + bool is_trained; + int trained_rate_gbps; +}; + +struct intel_pps { + int panel_power_up_delay; + int panel_power_down_delay; + int panel_power_cycle_delay; + int backlight_on_delay; + int backlight_off_delay; + struct delayed_work panel_vdd_work; + bool want_panel_vdd; + bool initializing; + unsigned long last_power_on; + unsigned long last_backlight_off; + ktime_t panel_power_off_time; + intel_wakeref_t vdd_wakeref; + + /* + * Pipe whose power sequencer is currently locked into + * this port. Only relevant on VLV/CHV. + */ + enum pipe pps_pipe; + /* + * Pipe currently driving the port. Used for preventing + * the use of the PPS for any pipe currentrly driving + * external DP as that will mess things up on VLV. + */ + enum pipe active_pipe; + /* + * Set if the sequencer may be reset due to a power transition, + * requiring a reinitialization. Only relevant on BXT. + */ + bool pps_reset; + struct edp_power_seq pps_delays; + struct edp_power_seq bios_pps_delays; +}; + +struct intel_psr { + /* Mutex for PSR state of the transcoder */ + struct mutex lock; + +#define I915_PSR_DEBUG_MODE_MASK 0x0f +#define I915_PSR_DEBUG_DEFAULT 0x00 +#define I915_PSR_DEBUG_DISABLE 0x01 +#define I915_PSR_DEBUG_ENABLE 0x02 +#define I915_PSR_DEBUG_FORCE_PSR1 0x03 +#define I915_PSR_DEBUG_ENABLE_SEL_FETCH 0x4 +#define I915_PSR_DEBUG_IRQ 0x10 + + u32 debug; + bool sink_support; + bool source_support; + bool enabled; + bool paused; + enum pipe pipe; + enum transcoder transcoder; + bool active; + struct work_struct work; + unsigned int busy_frontbuffer_bits; + bool sink_psr2_support; + bool link_standby; + bool colorimetry_support; + bool psr2_enabled; + bool psr2_sel_fetch_enabled; + bool psr2_sel_fetch_cff_enabled; + bool req_psr2_sdp_prior_scanline; + u8 sink_sync_latency; + u8 io_wake_lines; + u8 fast_wake_lines; + ktime_t last_entry_attempt; + ktime_t last_exit; + bool sink_not_reliable; + bool irq_aux_error; + u16 su_w_granularity; + u16 su_y_granularity; + u32 dc3co_exitline; + u32 dc3co_exit_delay; + struct delayed_work dc3co_work; +}; + +struct intel_dp { + i915_reg_t output_reg; + u32 DP; + int link_rate; + u8 lane_count; + u8 sink_count; + bool link_trained; + bool has_hdmi_sink; + bool has_audio; + bool reset_link_params; + bool use_max_params; + u8 dpcd[DP_RECEIVER_CAP_SIZE]; + u8 psr_dpcd[EDP_PSR_RECEIVER_CAP_SIZE]; + u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS]; + u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE]; + u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE]; + u8 lttpr_common_caps[DP_LTTPR_COMMON_CAP_SIZE]; + u8 lttpr_phy_caps[DP_MAX_LTTPR_COUNT][DP_LTTPR_PHY_CAP_SIZE]; + u8 fec_capable; + u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]; + /* source rates */ + int num_source_rates; + const int *source_rates; + /* sink rates as reported by DP_MAX_LINK_RATE/DP_SUPPORTED_LINK_RATES */ + int num_sink_rates; + int sink_rates[DP_MAX_SUPPORTED_RATES]; + bool use_rate_select; + /* Max sink lane count as reported by DP_MAX_LANE_COUNT */ + int max_sink_lane_count; + /* intersection of source and sink rates */ + int num_common_rates; + int common_rates[DP_MAX_SUPPORTED_RATES]; + /* Max lane count for the current link */ + int max_link_lane_count; + /* Max rate for the current link */ + int max_link_rate; + int mso_link_count; + int mso_pixel_overlap; + /* sink or branch descriptor */ + struct drm_dp_desc desc; + struct drm_dp_aux aux; + u32 aux_busy_last_status; + u8 train_set[4]; + + struct intel_pps pps; + + bool is_mst; + int active_mst_links; + + /* connector directly attached - won't be use for modeset in mst world */ + struct intel_connector *attached_connector; + + /* mst connector list */ + struct intel_dp_mst_encoder *mst_encoders[I915_MAX_PIPES]; + struct drm_dp_mst_topology_mgr mst_mgr; + + u32 (*get_aux_clock_divider)(struct intel_dp *dp, int index); + /* + * This function returns the value we have to program the AUX_CTL + * register with to kick off an AUX transaction. + */ + u32 (*get_aux_send_ctl)(struct intel_dp *dp, int send_bytes, + u32 aux_clock_divider); + + i915_reg_t (*aux_ch_ctl_reg)(struct intel_dp *dp); + i915_reg_t (*aux_ch_data_reg)(struct intel_dp *dp, int index); + + /* This is called before a link training is starterd */ + void (*prepare_link_retrain)(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state); + void (*set_link_train)(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + u8 dp_train_pat); + void (*set_idle_link_train)(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state); + + u8 (*preemph_max)(struct intel_dp *intel_dp); + u8 (*voltage_max)(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state); + + /* Displayport compliance testing */ + struct intel_dp_compliance compliance; + + /* Downstream facing port caps */ + struct { + int min_tmds_clock, max_tmds_clock; + int max_dotclock; + int pcon_max_frl_bw; + u8 max_bpc; + bool ycbcr_444_to_420; + bool rgb_to_ycbcr; + } dfp; + + /* To control wakeup latency, e.g. for irq-driven dp aux transfers. */ + struct pm_qos_request pm_qos; + + /* Display stream compression testing */ + bool force_dsc_en; + int force_dsc_bpc; + + bool hobl_failed; + bool hobl_active; + + struct intel_dp_pcon_frl frl; + + struct intel_psr psr; + + /* When we last wrote the OUI for eDP */ + unsigned long last_oui_write; +}; + +enum lspcon_vendor { + LSPCON_VENDOR_MCA, + LSPCON_VENDOR_PARADE +}; + +struct intel_lspcon { + bool active; + bool hdr_supported; + enum drm_lspcon_mode mode; + enum lspcon_vendor vendor; +}; + +struct intel_digital_port { + struct intel_encoder base; + u32 saved_port_bits; + struct intel_dp dp; + struct intel_hdmi hdmi; + struct intel_lspcon lspcon; + enum irqreturn (*hpd_pulse)(struct intel_digital_port *, bool); + bool release_cl2_override; + u8 max_lanes; + /* Used for DP and ICL+ TypeC/DP and TypeC/HDMI ports. */ + enum aux_ch aux_ch; + enum intel_display_power_domain ddi_io_power_domain; + intel_wakeref_t ddi_io_wakeref; + intel_wakeref_t aux_wakeref; + + struct mutex tc_lock; /* protects the TypeC port mode */ + intel_wakeref_t tc_lock_wakeref; + enum intel_display_power_domain tc_lock_power_domain; + struct delayed_work tc_disconnect_phy_work; + int tc_link_refcount; + bool tc_legacy_port:1; + char tc_port_name[8]; + enum tc_port_mode tc_mode; + enum tc_port_mode tc_init_mode; + enum phy_fia tc_phy_fia; + u8 tc_phy_fia_idx; + + /* protects num_hdcp_streams reference count, hdcp_port_data and hdcp_auth_status */ + struct mutex hdcp_mutex; + /* the number of pipes using HDCP signalling out of this port */ + unsigned int num_hdcp_streams; + /* port HDCP auth status */ + bool hdcp_auth_status; + /* HDCP port data need to pass to security f/w */ + struct hdcp_port_data hdcp_port_data; + /* Whether the MST topology supports HDCP Type 1 Content */ + bool hdcp_mst_type1_capable; + + void (*write_infoframe)(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + unsigned int type, + const void *frame, ssize_t len); + void (*read_infoframe)(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + unsigned int type, + void *frame, ssize_t len); + void (*set_infoframes)(struct intel_encoder *encoder, + bool enable, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state); + u32 (*infoframes_enabled)(struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config); + bool (*connected)(struct intel_encoder *encoder); +}; + +struct intel_dp_mst_encoder { + struct intel_encoder base; + enum pipe pipe; + struct intel_digital_port *primary; + struct intel_connector *connector; +}; + +static inline enum dpio_channel +vlv_dig_port_to_channel(struct intel_digital_port *dig_port) +{ + switch (dig_port->base.port) { + default: + MISSING_CASE(dig_port->base.port); + fallthrough; + case PORT_B: + case PORT_D: + return DPIO_CH0; + case PORT_C: + return DPIO_CH1; + } +} + +static inline enum dpio_phy +vlv_dig_port_to_phy(struct intel_digital_port *dig_port) +{ + switch (dig_port->base.port) { + default: + MISSING_CASE(dig_port->base.port); + fallthrough; + case PORT_B: + case PORT_C: + return DPIO_PHY0; + case PORT_D: + return DPIO_PHY1; + } +} + +static inline enum dpio_channel +vlv_pipe_to_channel(enum pipe pipe) +{ + switch (pipe) { + default: + MISSING_CASE(pipe); + fallthrough; + case PIPE_A: + case PIPE_C: + return DPIO_CH0; + case PIPE_B: + return DPIO_CH1; + } +} + +struct intel_load_detect_pipe { + struct drm_atomic_state *restore_state; +}; + +static inline struct intel_encoder * +intel_attached_encoder(struct intel_connector *connector) +{ + return connector->encoder; +} + +static inline bool intel_encoder_is_dig_port(struct intel_encoder *encoder) +{ + switch (encoder->type) { + case INTEL_OUTPUT_DDI: + case INTEL_OUTPUT_DP: + case INTEL_OUTPUT_EDP: + case INTEL_OUTPUT_HDMI: + return true; + default: + return false; + } +} + +static inline bool intel_encoder_is_mst(struct intel_encoder *encoder) +{ + return encoder->type == INTEL_OUTPUT_DP_MST; +} + +static inline struct intel_dp_mst_encoder * +enc_to_mst(struct intel_encoder *encoder) +{ + return container_of(&encoder->base, struct intel_dp_mst_encoder, + base.base); +} + +static inline struct intel_digital_port * +enc_to_dig_port(struct intel_encoder *encoder) +{ + struct intel_encoder *intel_encoder = encoder; + + if (intel_encoder_is_dig_port(intel_encoder)) + return container_of(&encoder->base, struct intel_digital_port, + base.base); + else if (intel_encoder_is_mst(intel_encoder)) + return enc_to_mst(encoder)->primary; + else + return NULL; +} + +static inline struct intel_digital_port * +intel_attached_dig_port(struct intel_connector *connector) +{ + return enc_to_dig_port(intel_attached_encoder(connector)); +} + +static inline struct intel_hdmi * +enc_to_intel_hdmi(struct intel_encoder *encoder) +{ + return &enc_to_dig_port(encoder)->hdmi; +} + +static inline struct intel_hdmi * +intel_attached_hdmi(struct intel_connector *connector) +{ + return enc_to_intel_hdmi(intel_attached_encoder(connector)); +} + +static inline struct intel_dp *enc_to_intel_dp(struct intel_encoder *encoder) +{ + return &enc_to_dig_port(encoder)->dp; +} + +static inline struct intel_dp *intel_attached_dp(struct intel_connector *connector) +{ + return enc_to_intel_dp(intel_attached_encoder(connector)); +} + +static inline bool intel_encoder_is_dp(struct intel_encoder *encoder) +{ + switch (encoder->type) { + case INTEL_OUTPUT_DP: + case INTEL_OUTPUT_EDP: + return true; + case INTEL_OUTPUT_DDI: + /* Skip pure HDMI/DVI DDI encoders */ + return i915_mmio_reg_valid(enc_to_intel_dp(encoder)->output_reg); + default: + return false; + } +} + +static inline struct intel_lspcon * +enc_to_intel_lspcon(struct intel_encoder *encoder) +{ + return &enc_to_dig_port(encoder)->lspcon; +} + +static inline struct intel_digital_port * +dp_to_dig_port(struct intel_dp *intel_dp) +{ + return container_of(intel_dp, struct intel_digital_port, dp); +} + +static inline struct intel_lspcon * +dp_to_lspcon(struct intel_dp *intel_dp) +{ + return &dp_to_dig_port(intel_dp)->lspcon; +} + +#define dp_to_i915(__intel_dp) to_i915(dp_to_dig_port(__intel_dp)->base.base.dev) + +#define CAN_PSR(intel_dp) ((intel_dp)->psr.sink_support && \ + (intel_dp)->psr.source_support) + +static inline bool intel_encoder_can_psr(struct intel_encoder *encoder) +{ + if (!intel_encoder_is_dp(encoder)) + return false; + + return CAN_PSR(enc_to_intel_dp(encoder)); +} + +static inline struct intel_digital_port * +hdmi_to_dig_port(struct intel_hdmi *intel_hdmi) +{ + return container_of(intel_hdmi, struct intel_digital_port, hdmi); +} + +static inline struct intel_plane_state * +intel_atomic_get_plane_state(struct intel_atomic_state *state, + struct intel_plane *plane) +{ + struct drm_plane_state *ret = + drm_atomic_get_plane_state(&state->base, &plane->base); + + if (IS_ERR(ret)) + return ERR_CAST(ret); + + return to_intel_plane_state(ret); +} + +static inline struct intel_plane_state * +intel_atomic_get_old_plane_state(struct intel_atomic_state *state, + struct intel_plane *plane) +{ + return to_intel_plane_state(drm_atomic_get_old_plane_state(&state->base, + &plane->base)); +} + +static inline struct intel_plane_state * +intel_atomic_get_new_plane_state(struct intel_atomic_state *state, + struct intel_plane *plane) +{ + return to_intel_plane_state(drm_atomic_get_new_plane_state(&state->base, + &plane->base)); +} + +static inline struct intel_crtc_state * +intel_atomic_get_old_crtc_state(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + return to_intel_crtc_state(drm_atomic_get_old_crtc_state(&state->base, + &crtc->base)); +} + +static inline struct intel_crtc_state * +intel_atomic_get_new_crtc_state(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + return to_intel_crtc_state(drm_atomic_get_new_crtc_state(&state->base, + &crtc->base)); +} + +static inline struct intel_digital_connector_state * +intel_atomic_get_new_connector_state(struct intel_atomic_state *state, + struct intel_connector *connector) +{ + return to_intel_digital_connector_state( + drm_atomic_get_new_connector_state(&state->base, + &connector->base)); +} + +static inline struct intel_digital_connector_state * +intel_atomic_get_old_connector_state(struct intel_atomic_state *state, + struct intel_connector *connector) +{ + return to_intel_digital_connector_state( + drm_atomic_get_old_connector_state(&state->base, + &connector->base)); +} + +/* intel_display.c */ +static inline bool +intel_crtc_has_type(const struct intel_crtc_state *crtc_state, + enum intel_output_type type) +{ + return crtc_state->output_types & (1 << type); +} +static inline bool +intel_crtc_has_dp_encoder(const struct intel_crtc_state *crtc_state) +{ + return crtc_state->output_types & + ((1 << INTEL_OUTPUT_DP) | + (1 << INTEL_OUTPUT_DP_MST) | + (1 << INTEL_OUTPUT_EDP)); +} + +static inline bool +intel_crtc_needs_modeset(const struct intel_crtc_state *crtc_state) +{ + return drm_atomic_crtc_needs_modeset(&crtc_state->uapi); +} + +static inline u32 intel_plane_ggtt_offset(const struct intel_plane_state *plane_state) +{ + return i915_ggtt_offset(plane_state->ggtt_vma); +} + +static inline struct intel_frontbuffer * +to_intel_frontbuffer(struct drm_framebuffer *fb) +{ + return fb ? to_intel_framebuffer(fb)->frontbuffer : NULL; +} + +#endif /* __INTEL_DISPLAY_TYPES_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_dkl_phy.c b/drivers/gpu/drm/i915/display/intel_dkl_phy.c new file mode 100644 index 000000000..710b030c7 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dkl_phy.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2022 Intel Corporation + */ + +#include "i915_drv.h" +#include "i915_reg.h" + +#include "intel_de.h" +#include "intel_display.h" +#include "intel_dkl_phy.h" + +static void +dkl_phy_set_hip_idx(struct drm_i915_private *i915, i915_reg_t reg, int idx) +{ + enum tc_port tc_port = DKL_REG_TC_PORT(reg); + + drm_WARN_ON(&i915->drm, tc_port < TC_PORT_1 || tc_port >= I915_MAX_TC_PORTS); + + intel_de_write(i915, + HIP_INDEX_REG(tc_port), + HIP_INDEX_VAL(tc_port, idx)); +} + +/** + * intel_dkl_phy_read - read a Dekel PHY register + * @i915: i915 device instance + * @reg: Dekel PHY register + * @ln: lane instance of @reg + * + * Read the @reg Dekel PHY register. + * + * Returns the read value. + */ +u32 +intel_dkl_phy_read(struct drm_i915_private *i915, i915_reg_t reg, int ln) +{ + u32 val; + + spin_lock(&i915->display.dkl.phy_lock); + + dkl_phy_set_hip_idx(i915, reg, ln); + val = intel_de_read(i915, reg); + + spin_unlock(&i915->display.dkl.phy_lock); + + return val; +} + +/** + * intel_dkl_phy_write - write a Dekel PHY register + * @i915: i915 device instance + * @reg: Dekel PHY register + * @ln: lane instance of @reg + * @val: value to write + * + * Write @val to the @reg Dekel PHY register. + */ +void +intel_dkl_phy_write(struct drm_i915_private *i915, i915_reg_t reg, int ln, u32 val) +{ + spin_lock(&i915->display.dkl.phy_lock); + + dkl_phy_set_hip_idx(i915, reg, ln); + intel_de_write(i915, reg, val); + + spin_unlock(&i915->display.dkl.phy_lock); +} + +/** + * intel_dkl_phy_rmw - read-modify-write a Dekel PHY register + * @i915: i915 device instance + * @reg: Dekel PHY register + * @ln: lane instance of @reg + * @clear: mask to clear + * @set: mask to set + * + * Read the @reg Dekel PHY register, clearing then setting the @clear/@set bits in it, and writing + * this value back to the register if the value differs from the read one. + */ +void +intel_dkl_phy_rmw(struct drm_i915_private *i915, i915_reg_t reg, int ln, u32 clear, u32 set) +{ + spin_lock(&i915->display.dkl.phy_lock); + + dkl_phy_set_hip_idx(i915, reg, ln); + intel_de_rmw(i915, reg, clear, set); + + spin_unlock(&i915->display.dkl.phy_lock); +} + +/** + * intel_dkl_phy_posting_read - do a posting read from a Dekel PHY register + * @i915: i915 device instance + * @reg: Dekel PHY register + * @ln: lane instance of @reg + * + * Read the @reg Dekel PHY register without returning the read value. + */ +void +intel_dkl_phy_posting_read(struct drm_i915_private *i915, i915_reg_t reg, int ln) +{ + spin_lock(&i915->display.dkl.phy_lock); + + dkl_phy_set_hip_idx(i915, reg, ln); + intel_de_posting_read(i915, reg); + + spin_unlock(&i915->display.dkl.phy_lock); +} diff --git a/drivers/gpu/drm/i915/display/intel_dkl_phy.h b/drivers/gpu/drm/i915/display/intel_dkl_phy.h new file mode 100644 index 000000000..260ad121a --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dkl_phy.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2022 Intel Corporation + */ + +#ifndef __INTEL_DKL_PHY_H__ +#define __INTEL_DKL_PHY_H__ + +#include + +#include "i915_reg_defs.h" + +struct drm_i915_private; + +u32 +intel_dkl_phy_read(struct drm_i915_private *i915, i915_reg_t reg, int ln); +void +intel_dkl_phy_write(struct drm_i915_private *i915, i915_reg_t reg, int ln, u32 val); +void +intel_dkl_phy_rmw(struct drm_i915_private *i915, i915_reg_t reg, int ln, u32 clear, u32 set); +void +intel_dkl_phy_posting_read(struct drm_i915_private *i915, i915_reg_t reg, int ln); + +#endif /* __INTEL_DKL_PHY_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_dmc.c b/drivers/gpu/drm/i915/display/intel_dmc.c new file mode 100644 index 000000000..e52ecc073 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dmc.c @@ -0,0 +1,1132 @@ +/* + * Copyright © 2014 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +#include + +#include "i915_drv.h" +#include "i915_reg.h" +#include "intel_de.h" +#include "intel_dmc.h" +#include "intel_dmc_regs.h" + +/** + * DOC: DMC Firmware Support + * + * From gen9 onwards we have newly added DMC (Display microcontroller) in display + * engine to save and restore the state of display engine when it enter into + * low-power state and comes back to normal. + */ + +#define DMC_VERSION(major, minor) ((major) << 16 | (minor)) +#define DMC_VERSION_MAJOR(version) ((version) >> 16) +#define DMC_VERSION_MINOR(version) ((version) & 0xffff) + +#define DMC_PATH(platform, major, minor) \ + "i915/" \ + __stringify(platform) "_dmc_ver" \ + __stringify(major) "_" \ + __stringify(minor) ".bin" + +#define DISPLAY_VER13_DMC_MAX_FW_SIZE 0x20000 + +#define DISPLAY_VER12_DMC_MAX_FW_SIZE ICL_DMC_MAX_FW_SIZE + +#define DG2_DMC_PATH DMC_PATH(dg2, 2, 07) +#define DG2_DMC_VERSION_REQUIRED DMC_VERSION(2, 07) +MODULE_FIRMWARE(DG2_DMC_PATH); + +#define ADLP_DMC_PATH DMC_PATH(adlp, 2, 16) +#define ADLP_DMC_VERSION_REQUIRED DMC_VERSION(2, 16) +MODULE_FIRMWARE(ADLP_DMC_PATH); + +#define ADLS_DMC_PATH DMC_PATH(adls, 2, 01) +#define ADLS_DMC_VERSION_REQUIRED DMC_VERSION(2, 1) +MODULE_FIRMWARE(ADLS_DMC_PATH); + +#define DG1_DMC_PATH DMC_PATH(dg1, 2, 02) +#define DG1_DMC_VERSION_REQUIRED DMC_VERSION(2, 2) +MODULE_FIRMWARE(DG1_DMC_PATH); + +#define RKL_DMC_PATH DMC_PATH(rkl, 2, 03) +#define RKL_DMC_VERSION_REQUIRED DMC_VERSION(2, 3) +MODULE_FIRMWARE(RKL_DMC_PATH); + +#define TGL_DMC_PATH DMC_PATH(tgl, 2, 12) +#define TGL_DMC_VERSION_REQUIRED DMC_VERSION(2, 12) +MODULE_FIRMWARE(TGL_DMC_PATH); + +#define ICL_DMC_PATH DMC_PATH(icl, 1, 09) +#define ICL_DMC_VERSION_REQUIRED DMC_VERSION(1, 9) +#define ICL_DMC_MAX_FW_SIZE 0x6000 +MODULE_FIRMWARE(ICL_DMC_PATH); + +#define GLK_DMC_PATH DMC_PATH(glk, 1, 04) +#define GLK_DMC_VERSION_REQUIRED DMC_VERSION(1, 4) +#define GLK_DMC_MAX_FW_SIZE 0x4000 +MODULE_FIRMWARE(GLK_DMC_PATH); + +#define KBL_DMC_PATH DMC_PATH(kbl, 1, 04) +#define KBL_DMC_VERSION_REQUIRED DMC_VERSION(1, 4) +#define KBL_DMC_MAX_FW_SIZE BXT_DMC_MAX_FW_SIZE +MODULE_FIRMWARE(KBL_DMC_PATH); + +#define SKL_DMC_PATH DMC_PATH(skl, 1, 27) +#define SKL_DMC_VERSION_REQUIRED DMC_VERSION(1, 27) +#define SKL_DMC_MAX_FW_SIZE BXT_DMC_MAX_FW_SIZE +MODULE_FIRMWARE(SKL_DMC_PATH); + +#define BXT_DMC_PATH DMC_PATH(bxt, 1, 07) +#define BXT_DMC_VERSION_REQUIRED DMC_VERSION(1, 7) +#define BXT_DMC_MAX_FW_SIZE 0x3000 +MODULE_FIRMWARE(BXT_DMC_PATH); + +#define DMC_DEFAULT_FW_OFFSET 0xFFFFFFFF +#define PACKAGE_MAX_FW_INFO_ENTRIES 20 +#define PACKAGE_V2_MAX_FW_INFO_ENTRIES 32 +#define DMC_V1_MAX_MMIO_COUNT 8 +#define DMC_V3_MAX_MMIO_COUNT 20 +#define DMC_V1_MMIO_START_RANGE 0x80000 + +struct intel_css_header { + /* 0x09 for DMC */ + u32 module_type; + + /* Includes the DMC specific header in dwords */ + u32 header_len; + + /* always value would be 0x10000 */ + u32 header_ver; + + /* Not used */ + u32 module_id; + + /* Not used */ + u32 module_vendor; + + /* in YYYYMMDD format */ + u32 date; + + /* Size in dwords (CSS_Headerlen + PackageHeaderLen + dmc FWsLen)/4 */ + u32 size; + + /* Not used */ + u32 key_size; + + /* Not used */ + u32 modulus_size; + + /* Not used */ + u32 exponent_size; + + /* Not used */ + u32 reserved1[12]; + + /* Major Minor */ + u32 version; + + /* Not used */ + u32 reserved2[8]; + + /* Not used */ + u32 kernel_header_info; +} __packed; + +struct intel_fw_info { + u8 reserved1; + + /* reserved on package_header version 1, must be 0 on version 2 */ + u8 dmc_id; + + /* Stepping (A, B, C, ..., *). * is a wildcard */ + char stepping; + + /* Sub-stepping (0, 1, ..., *). * is a wildcard */ + char substepping; + + u32 offset; + u32 reserved2; +} __packed; + +struct intel_package_header { + /* DMC container header length in dwords */ + u8 header_len; + + /* 0x01, 0x02 */ + u8 header_ver; + + u8 reserved[10]; + + /* Number of valid entries in the FWInfo array below */ + u32 num_entries; +} __packed; + +struct intel_dmc_header_base { + /* always value would be 0x40403E3E */ + u32 signature; + + /* DMC binary header length */ + u8 header_len; + + /* 0x01 */ + u8 header_ver; + + /* Reserved */ + u16 dmcc_ver; + + /* Major, Minor */ + u32 project; + + /* Firmware program size (excluding header) in dwords */ + u32 fw_size; + + /* Major Minor version */ + u32 fw_version; +} __packed; + +struct intel_dmc_header_v1 { + struct intel_dmc_header_base base; + + /* Number of valid MMIO cycles present. */ + u32 mmio_count; + + /* MMIO address */ + u32 mmioaddr[DMC_V1_MAX_MMIO_COUNT]; + + /* MMIO data */ + u32 mmiodata[DMC_V1_MAX_MMIO_COUNT]; + + /* FW filename */ + char dfile[32]; + + u32 reserved1[2]; +} __packed; + +struct intel_dmc_header_v3 { + struct intel_dmc_header_base base; + + /* DMC RAM start MMIO address */ + u32 start_mmioaddr; + + u32 reserved[9]; + + /* FW filename */ + char dfile[32]; + + /* Number of valid MMIO cycles present. */ + u32 mmio_count; + + /* MMIO address */ + u32 mmioaddr[DMC_V3_MAX_MMIO_COUNT]; + + /* MMIO data */ + u32 mmiodata[DMC_V3_MAX_MMIO_COUNT]; +} __packed; + +struct stepping_info { + char stepping; + char substepping; +}; + +static bool has_dmc_id_fw(struct drm_i915_private *i915, int dmc_id) +{ + return i915->display.dmc.dmc_info[dmc_id].payload; +} + +bool intel_dmc_has_payload(struct drm_i915_private *i915) +{ + return has_dmc_id_fw(i915, DMC_FW_MAIN); +} + +static const struct stepping_info * +intel_get_stepping_info(struct drm_i915_private *i915, + struct stepping_info *si) +{ + const char *step_name = intel_step_name(RUNTIME_INFO(i915)->step.display_step); + + si->stepping = step_name[0]; + si->substepping = step_name[1]; + return si; +} + +static void gen9_set_dc_state_debugmask(struct drm_i915_private *dev_priv) +{ + /* The below bit doesn't need to be cleared ever afterwards */ + intel_de_rmw(dev_priv, DC_STATE_DEBUG, 0, + DC_STATE_DEBUG_MASK_CORES | DC_STATE_DEBUG_MASK_MEMORY_UP); + intel_de_posting_read(dev_priv, DC_STATE_DEBUG); +} + +static void disable_event_handler(struct drm_i915_private *i915, + i915_reg_t ctl_reg, i915_reg_t htp_reg) +{ + intel_de_write(i915, ctl_reg, + REG_FIELD_PREP(DMC_EVT_CTL_TYPE_MASK, + DMC_EVT_CTL_TYPE_EDGE_0_1) | + REG_FIELD_PREP(DMC_EVT_CTL_EVENT_ID_MASK, + DMC_EVT_CTL_EVENT_ID_FALSE)); + intel_de_write(i915, htp_reg, 0); +} + +static void +disable_flip_queue_event(struct drm_i915_private *i915, + i915_reg_t ctl_reg, i915_reg_t htp_reg) +{ + u32 event_ctl; + u32 event_htp; + + event_ctl = intel_de_read(i915, ctl_reg); + event_htp = intel_de_read(i915, htp_reg); + if (event_ctl != (DMC_EVT_CTL_ENABLE | + DMC_EVT_CTL_RECURRING | + REG_FIELD_PREP(DMC_EVT_CTL_TYPE_MASK, + DMC_EVT_CTL_TYPE_EDGE_0_1) | + REG_FIELD_PREP(DMC_EVT_CTL_EVENT_ID_MASK, + DMC_EVT_CTL_EVENT_ID_CLK_MSEC)) || + !event_htp) { + drm_dbg_kms(&i915->drm, + "Unexpected DMC event configuration (control %08x htp %08x)\n", + event_ctl, event_htp); + return; + } + + disable_event_handler(i915, ctl_reg, htp_reg); +} + +static bool +get_flip_queue_event_regs(struct drm_i915_private *i915, int dmc_id, + i915_reg_t *ctl_reg, i915_reg_t *htp_reg) +{ + switch (dmc_id) { + case DMC_FW_MAIN: + if (DISPLAY_VER(i915) == 12) { + *ctl_reg = DMC_EVT_CTL(i915, dmc_id, 3); + *htp_reg = DMC_EVT_HTP(i915, dmc_id, 3); + + return true; + } + break; + case DMC_FW_PIPEA ... DMC_FW_PIPED: + if (IS_DG2(i915)) { + *ctl_reg = DMC_EVT_CTL(i915, dmc_id, 2); + *htp_reg = DMC_EVT_HTP(i915, dmc_id, 2); + + return true; + } + break; + } + + return false; +} + +static void +disable_all_flip_queue_events(struct drm_i915_private *i915) +{ + int dmc_id; + + /* TODO: check if the following applies to all D13+ platforms. */ + if (!IS_DG2(i915) && !IS_TIGERLAKE(i915)) + return; + + for (dmc_id = 0; dmc_id < DMC_FW_MAX; dmc_id++) { + i915_reg_t ctl_reg; + i915_reg_t htp_reg; + + if (!has_dmc_id_fw(i915, dmc_id)) + continue; + + if (!get_flip_queue_event_regs(i915, dmc_id, &ctl_reg, &htp_reg)) + continue; + + disable_flip_queue_event(i915, ctl_reg, htp_reg); + } +} + +static void disable_all_event_handlers(struct drm_i915_private *i915) +{ + int id; + + /* TODO: disable the event handlers on pre-GEN12 platforms as well */ + if (DISPLAY_VER(i915) < 12) + return; + + for (id = DMC_FW_MAIN; id < DMC_FW_MAX; id++) { + int handler; + + if (!has_dmc_id_fw(i915, id)) + continue; + + for (handler = 0; handler < DMC_EVENT_HANDLER_COUNT_GEN12; handler++) + disable_event_handler(i915, + DMC_EVT_CTL(i915, id, handler), + DMC_EVT_HTP(i915, id, handler)); + } +} + +static void pipedmc_clock_gating_wa(struct drm_i915_private *i915, bool enable) +{ + enum pipe pipe; + + if (DISPLAY_VER(i915) != 13) + return; + + /* + * Wa_16015201720:adl-p,dg2 + * The WA requires clock gating to be disabled all the time + * for pipe A and B. + * For pipe C and D clock gating needs to be disabled only + * during initializing the firmware. + */ + if (enable) + for (pipe = PIPE_A; pipe <= PIPE_D; pipe++) + intel_de_rmw(i915, CLKGATE_DIS_PSL_EXT(pipe), + 0, PIPEDMC_GATING_DIS); + else + for (pipe = PIPE_C; pipe <= PIPE_D; pipe++) + intel_de_rmw(i915, CLKGATE_DIS_PSL_EXT(pipe), + PIPEDMC_GATING_DIS, 0); +} + +/** + * intel_dmc_load_program() - write the firmware from memory to register. + * @dev_priv: i915 drm device. + * + * DMC firmware is read from a .bin file and kept in internal memory one time. + * Everytime display comes back from low power state this function is called to + * copy the firmware from internal memory to registers. + */ +void intel_dmc_load_program(struct drm_i915_private *dev_priv) +{ + struct intel_dmc *dmc = &dev_priv->display.dmc; + u32 id, i; + + if (!intel_dmc_has_payload(dev_priv)) + return; + + pipedmc_clock_gating_wa(dev_priv, true); + + disable_all_event_handlers(dev_priv); + + assert_rpm_wakelock_held(&dev_priv->runtime_pm); + + preempt_disable(); + + for (id = 0; id < DMC_FW_MAX; id++) { + for (i = 0; i < dmc->dmc_info[id].dmc_fw_size; i++) { + intel_uncore_write_fw(&dev_priv->uncore, + DMC_PROGRAM(dmc->dmc_info[id].start_mmioaddr, i), + dmc->dmc_info[id].payload[i]); + } + } + + preempt_enable(); + + for (id = 0; id < DMC_FW_MAX; id++) { + for (i = 0; i < dmc->dmc_info[id].mmio_count; i++) { + intel_de_write(dev_priv, dmc->dmc_info[id].mmioaddr[i], + dmc->dmc_info[id].mmiodata[i]); + } + } + + dev_priv->display.dmc.dc_state = 0; + + gen9_set_dc_state_debugmask(dev_priv); + + /* + * Flip queue events need to be disabled before enabling DC5/6. + * i915 doesn't use the flip queue feature, so disable it already + * here. + */ + disable_all_flip_queue_events(dev_priv); + + pipedmc_clock_gating_wa(dev_priv, false); +} + +/** + * intel_dmc_disable_program() - disable the firmware + * @i915: i915 drm device + * + * Disable all event handlers in the firmware, making sure the firmware is + * inactive after the display is uninitialized. + */ +void intel_dmc_disable_program(struct drm_i915_private *i915) +{ + if (!intel_dmc_has_payload(i915)) + return; + + pipedmc_clock_gating_wa(i915, true); + disable_all_event_handlers(i915); + pipedmc_clock_gating_wa(i915, false); +} + +void assert_dmc_loaded(struct drm_i915_private *i915) +{ + drm_WARN_ONCE(&i915->drm, + !intel_de_read(i915, DMC_PROGRAM(i915->display.dmc.dmc_info[DMC_FW_MAIN].start_mmioaddr, 0)), + "DMC program storage start is NULL\n"); + drm_WARN_ONCE(&i915->drm, !intel_de_read(i915, DMC_SSP_BASE), + "DMC SSP Base Not fine\n"); + drm_WARN_ONCE(&i915->drm, !intel_de_read(i915, DMC_HTP_SKL), + "DMC HTP Not fine\n"); +} + +static bool fw_info_matches_stepping(const struct intel_fw_info *fw_info, + const struct stepping_info *si) +{ + if ((fw_info->substepping == '*' && si->stepping == fw_info->stepping) || + (si->stepping == fw_info->stepping && si->substepping == fw_info->substepping) || + /* + * If we don't find a more specific one from above two checks, we + * then check for the generic one to be sure to work even with + * "broken firmware" + */ + (si->stepping == '*' && si->substepping == fw_info->substepping) || + (fw_info->stepping == '*' && fw_info->substepping == '*')) + return true; + + return false; +} + +/* + * Search fw_info table for dmc_offset to find firmware binary: num_entries is + * already sanitized. + */ +static void dmc_set_fw_offset(struct intel_dmc *dmc, + const struct intel_fw_info *fw_info, + unsigned int num_entries, + const struct stepping_info *si, + u8 package_ver) +{ + unsigned int i, id; + + struct drm_i915_private *i915 = container_of(dmc, typeof(*i915), display.dmc); + + for (i = 0; i < num_entries; i++) { + id = package_ver <= 1 ? DMC_FW_MAIN : fw_info[i].dmc_id; + + if (id >= DMC_FW_MAX) { + drm_dbg(&i915->drm, "Unsupported firmware id: %u\n", id); + continue; + } + + /* More specific versions come first, so we don't even have to + * check for the stepping since we already found a previous FW + * for this id. + */ + if (dmc->dmc_info[id].present) + continue; + + if (fw_info_matches_stepping(&fw_info[i], si)) { + dmc->dmc_info[id].present = true; + dmc->dmc_info[id].dmc_offset = fw_info[i].offset; + } + } +} + +static bool dmc_mmio_addr_sanity_check(struct intel_dmc *dmc, + const u32 *mmioaddr, u32 mmio_count, + int header_ver, u8 dmc_id) +{ + struct drm_i915_private *i915 = container_of(dmc, typeof(*i915), display.dmc); + u32 start_range, end_range; + int i; + + if (dmc_id >= DMC_FW_MAX) { + drm_warn(&i915->drm, "Unsupported firmware id %u\n", dmc_id); + return false; + } + + if (header_ver == 1) { + start_range = DMC_MMIO_START_RANGE; + end_range = DMC_MMIO_END_RANGE; + } else if (dmc_id == DMC_FW_MAIN) { + start_range = TGL_MAIN_MMIO_START; + end_range = TGL_MAIN_MMIO_END; + } else if (DISPLAY_VER(i915) >= 13) { + start_range = ADLP_PIPE_MMIO_START; + end_range = ADLP_PIPE_MMIO_END; + } else if (DISPLAY_VER(i915) >= 12) { + start_range = TGL_PIPE_MMIO_START(dmc_id); + end_range = TGL_PIPE_MMIO_END(dmc_id); + } else { + drm_warn(&i915->drm, "Unknown mmio range for sanity check"); + return false; + } + + for (i = 0; i < mmio_count; i++) { + if (mmioaddr[i] < start_range || mmioaddr[i] > end_range) + return false; + } + + return true; +} + +static u32 parse_dmc_fw_header(struct intel_dmc *dmc, + const struct intel_dmc_header_base *dmc_header, + size_t rem_size, u8 dmc_id) +{ + struct drm_i915_private *i915 = container_of(dmc, typeof(*i915), display.dmc); + struct dmc_fw_info *dmc_info = &dmc->dmc_info[dmc_id]; + unsigned int header_len_bytes, dmc_header_size, payload_size, i; + const u32 *mmioaddr, *mmiodata; + u32 mmio_count, mmio_count_max, start_mmioaddr; + u8 *payload; + + BUILD_BUG_ON(ARRAY_SIZE(dmc_info->mmioaddr) < DMC_V3_MAX_MMIO_COUNT || + ARRAY_SIZE(dmc_info->mmioaddr) < DMC_V1_MAX_MMIO_COUNT); + + /* + * Check if we can access common fields, we will checkc again below + * after we have read the version + */ + if (rem_size < sizeof(struct intel_dmc_header_base)) + goto error_truncated; + + /* Cope with small differences between v1 and v3 */ + if (dmc_header->header_ver == 3) { + const struct intel_dmc_header_v3 *v3 = + (const struct intel_dmc_header_v3 *)dmc_header; + + if (rem_size < sizeof(struct intel_dmc_header_v3)) + goto error_truncated; + + mmioaddr = v3->mmioaddr; + mmiodata = v3->mmiodata; + mmio_count = v3->mmio_count; + mmio_count_max = DMC_V3_MAX_MMIO_COUNT; + /* header_len is in dwords */ + header_len_bytes = dmc_header->header_len * 4; + start_mmioaddr = v3->start_mmioaddr; + dmc_header_size = sizeof(*v3); + } else if (dmc_header->header_ver == 1) { + const struct intel_dmc_header_v1 *v1 = + (const struct intel_dmc_header_v1 *)dmc_header; + + if (rem_size < sizeof(struct intel_dmc_header_v1)) + goto error_truncated; + + mmioaddr = v1->mmioaddr; + mmiodata = v1->mmiodata; + mmio_count = v1->mmio_count; + mmio_count_max = DMC_V1_MAX_MMIO_COUNT; + header_len_bytes = dmc_header->header_len; + start_mmioaddr = DMC_V1_MMIO_START_RANGE; + dmc_header_size = sizeof(*v1); + } else { + drm_err(&i915->drm, "Unknown DMC fw header version: %u\n", + dmc_header->header_ver); + return 0; + } + + if (header_len_bytes != dmc_header_size) { + drm_err(&i915->drm, "DMC firmware has wrong dmc header length " + "(%u bytes)\n", header_len_bytes); + return 0; + } + + /* Cache the dmc header info. */ + if (mmio_count > mmio_count_max) { + drm_err(&i915->drm, "DMC firmware has wrong mmio count %u\n", mmio_count); + return 0; + } + + if (!dmc_mmio_addr_sanity_check(dmc, mmioaddr, mmio_count, + dmc_header->header_ver, dmc_id)) { + drm_err(&i915->drm, "DMC firmware has Wrong MMIO Addresses\n"); + return 0; + } + + for (i = 0; i < mmio_count; i++) { + dmc_info->mmioaddr[i] = _MMIO(mmioaddr[i]); + dmc_info->mmiodata[i] = mmiodata[i]; + } + dmc_info->mmio_count = mmio_count; + dmc_info->start_mmioaddr = start_mmioaddr; + + rem_size -= header_len_bytes; + + /* fw_size is in dwords, so multiplied by 4 to convert into bytes. */ + payload_size = dmc_header->fw_size * 4; + if (rem_size < payload_size) + goto error_truncated; + + if (payload_size > dmc->max_fw_size) { + drm_err(&i915->drm, "DMC FW too big (%u bytes)\n", payload_size); + return 0; + } + dmc_info->dmc_fw_size = dmc_header->fw_size; + + dmc_info->payload = kmalloc(payload_size, GFP_KERNEL); + if (!dmc_info->payload) + return 0; + + payload = (u8 *)(dmc_header) + header_len_bytes; + memcpy(dmc_info->payload, payload, payload_size); + + return header_len_bytes + payload_size; + +error_truncated: + drm_err(&i915->drm, "Truncated DMC firmware, refusing.\n"); + return 0; +} + +static u32 +parse_dmc_fw_package(struct intel_dmc *dmc, + const struct intel_package_header *package_header, + const struct stepping_info *si, + size_t rem_size) +{ + struct drm_i915_private *i915 = container_of(dmc, typeof(*i915), display.dmc); + u32 package_size = sizeof(struct intel_package_header); + u32 num_entries, max_entries; + const struct intel_fw_info *fw_info; + + if (rem_size < package_size) + goto error_truncated; + + if (package_header->header_ver == 1) { + max_entries = PACKAGE_MAX_FW_INFO_ENTRIES; + } else if (package_header->header_ver == 2) { + max_entries = PACKAGE_V2_MAX_FW_INFO_ENTRIES; + } else { + drm_err(&i915->drm, "DMC firmware has unknown header version %u\n", + package_header->header_ver); + return 0; + } + + /* + * We should always have space for max_entries, + * even if not all are used + */ + package_size += max_entries * sizeof(struct intel_fw_info); + if (rem_size < package_size) + goto error_truncated; + + if (package_header->header_len * 4 != package_size) { + drm_err(&i915->drm, "DMC firmware has wrong package header length " + "(%u bytes)\n", package_size); + return 0; + } + + num_entries = package_header->num_entries; + if (WARN_ON(package_header->num_entries > max_entries)) + num_entries = max_entries; + + fw_info = (const struct intel_fw_info *) + ((u8 *)package_header + sizeof(*package_header)); + dmc_set_fw_offset(dmc, fw_info, num_entries, si, + package_header->header_ver); + + /* dmc_offset is in dwords */ + return package_size; + +error_truncated: + drm_err(&i915->drm, "Truncated DMC firmware, refusing.\n"); + return 0; +} + +/* Return number of bytes parsed or 0 on error */ +static u32 parse_dmc_fw_css(struct intel_dmc *dmc, + struct intel_css_header *css_header, + size_t rem_size) +{ + struct drm_i915_private *i915 = container_of(dmc, typeof(*i915), display.dmc); + + if (rem_size < sizeof(struct intel_css_header)) { + drm_err(&i915->drm, "Truncated DMC firmware, refusing.\n"); + return 0; + } + + if (sizeof(struct intel_css_header) != + (css_header->header_len * 4)) { + drm_err(&i915->drm, "DMC firmware has wrong CSS header length " + "(%u bytes)\n", + (css_header->header_len * 4)); + return 0; + } + + if (dmc->required_version && + css_header->version != dmc->required_version) { + drm_info(&i915->drm, "Refusing to load DMC firmware v%u.%u," + " please use v%u.%u\n", + DMC_VERSION_MAJOR(css_header->version), + DMC_VERSION_MINOR(css_header->version), + DMC_VERSION_MAJOR(dmc->required_version), + DMC_VERSION_MINOR(dmc->required_version)); + return 0; + } + + dmc->version = css_header->version; + + return sizeof(struct intel_css_header); +} + +static void parse_dmc_fw(struct drm_i915_private *dev_priv, + const struct firmware *fw) +{ + struct intel_css_header *css_header; + struct intel_package_header *package_header; + struct intel_dmc_header_base *dmc_header; + struct intel_dmc *dmc = &dev_priv->display.dmc; + struct stepping_info display_info = { '*', '*'}; + const struct stepping_info *si = intel_get_stepping_info(dev_priv, &display_info); + u32 readcount = 0; + u32 r, offset; + int id; + + if (!fw) + return; + + /* Extract CSS Header information */ + css_header = (struct intel_css_header *)fw->data; + r = parse_dmc_fw_css(dmc, css_header, fw->size); + if (!r) + return; + + readcount += r; + + /* Extract Package Header information */ + package_header = (struct intel_package_header *)&fw->data[readcount]; + r = parse_dmc_fw_package(dmc, package_header, si, fw->size - readcount); + if (!r) + return; + + readcount += r; + + for (id = 0; id < DMC_FW_MAX; id++) { + if (!dev_priv->display.dmc.dmc_info[id].present) + continue; + + offset = readcount + dmc->dmc_info[id].dmc_offset * 4; + if (offset > fw->size) { + drm_err(&dev_priv->drm, "Reading beyond the fw_size\n"); + continue; + } + + dmc_header = (struct intel_dmc_header_base *)&fw->data[offset]; + parse_dmc_fw_header(dmc, dmc_header, fw->size - offset, id); + } +} + +static void intel_dmc_runtime_pm_get(struct drm_i915_private *dev_priv) +{ + drm_WARN_ON(&dev_priv->drm, dev_priv->display.dmc.wakeref); + dev_priv->display.dmc.wakeref = + intel_display_power_get(dev_priv, POWER_DOMAIN_INIT); +} + +static void intel_dmc_runtime_pm_put(struct drm_i915_private *dev_priv) +{ + intel_wakeref_t wakeref __maybe_unused = + fetch_and_zero(&dev_priv->display.dmc.wakeref); + + intel_display_power_put(dev_priv, POWER_DOMAIN_INIT, wakeref); +} + +static void dmc_load_work_fn(struct work_struct *work) +{ + struct drm_i915_private *dev_priv; + struct intel_dmc *dmc; + const struct firmware *fw = NULL; + + dev_priv = container_of(work, typeof(*dev_priv), display.dmc.work); + dmc = &dev_priv->display.dmc; + + request_firmware(&fw, dev_priv->display.dmc.fw_path, dev_priv->drm.dev); + parse_dmc_fw(dev_priv, fw); + + if (intel_dmc_has_payload(dev_priv)) { + intel_dmc_load_program(dev_priv); + intel_dmc_runtime_pm_put(dev_priv); + + drm_info(&dev_priv->drm, + "Finished loading DMC firmware %s (v%u.%u)\n", + dev_priv->display.dmc.fw_path, DMC_VERSION_MAJOR(dmc->version), + DMC_VERSION_MINOR(dmc->version)); + } else { + drm_notice(&dev_priv->drm, + "Failed to load DMC firmware %s." + " Disabling runtime power management.\n", + dmc->fw_path); + drm_notice(&dev_priv->drm, "DMC firmware homepage: %s", + INTEL_UC_FIRMWARE_URL); + } + + release_firmware(fw); +} + +/** + * intel_dmc_ucode_init() - initialize the firmware loading. + * @dev_priv: i915 drm device. + * + * This function is called at the time of loading the display driver to read + * firmware from a .bin file and copied into a internal memory. + */ +void intel_dmc_ucode_init(struct drm_i915_private *dev_priv) +{ + struct intel_dmc *dmc = &dev_priv->display.dmc; + + INIT_WORK(&dev_priv->display.dmc.work, dmc_load_work_fn); + + if (!HAS_DMC(dev_priv)) + return; + + /* + * Obtain a runtime pm reference, until DMC is loaded, to avoid entering + * runtime-suspend. + * + * On error, we return with the rpm wakeref held to prevent runtime + * suspend as runtime suspend *requires* a working DMC for whatever + * reason. + */ + intel_dmc_runtime_pm_get(dev_priv); + + if (IS_DG2(dev_priv)) { + dmc->fw_path = DG2_DMC_PATH; + dmc->required_version = DG2_DMC_VERSION_REQUIRED; + dmc->max_fw_size = DISPLAY_VER13_DMC_MAX_FW_SIZE; + } else if (IS_ALDERLAKE_P(dev_priv)) { + dmc->fw_path = ADLP_DMC_PATH; + dmc->required_version = ADLP_DMC_VERSION_REQUIRED; + dmc->max_fw_size = DISPLAY_VER13_DMC_MAX_FW_SIZE; + } else if (IS_ALDERLAKE_S(dev_priv)) { + dmc->fw_path = ADLS_DMC_PATH; + dmc->required_version = ADLS_DMC_VERSION_REQUIRED; + dmc->max_fw_size = DISPLAY_VER12_DMC_MAX_FW_SIZE; + } else if (IS_DG1(dev_priv)) { + dmc->fw_path = DG1_DMC_PATH; + dmc->required_version = DG1_DMC_VERSION_REQUIRED; + dmc->max_fw_size = DISPLAY_VER12_DMC_MAX_FW_SIZE; + } else if (IS_ROCKETLAKE(dev_priv)) { + dmc->fw_path = RKL_DMC_PATH; + dmc->required_version = RKL_DMC_VERSION_REQUIRED; + dmc->max_fw_size = DISPLAY_VER12_DMC_MAX_FW_SIZE; + } else if (IS_TIGERLAKE(dev_priv)) { + dmc->fw_path = TGL_DMC_PATH; + dmc->required_version = TGL_DMC_VERSION_REQUIRED; + dmc->max_fw_size = DISPLAY_VER12_DMC_MAX_FW_SIZE; + } else if (DISPLAY_VER(dev_priv) == 11) { + dmc->fw_path = ICL_DMC_PATH; + dmc->required_version = ICL_DMC_VERSION_REQUIRED; + dmc->max_fw_size = ICL_DMC_MAX_FW_SIZE; + } else if (IS_GEMINILAKE(dev_priv)) { + dmc->fw_path = GLK_DMC_PATH; + dmc->required_version = GLK_DMC_VERSION_REQUIRED; + dmc->max_fw_size = GLK_DMC_MAX_FW_SIZE; + } else if (IS_KABYLAKE(dev_priv) || + IS_COFFEELAKE(dev_priv) || + IS_COMETLAKE(dev_priv)) { + dmc->fw_path = KBL_DMC_PATH; + dmc->required_version = KBL_DMC_VERSION_REQUIRED; + dmc->max_fw_size = KBL_DMC_MAX_FW_SIZE; + } else if (IS_SKYLAKE(dev_priv)) { + dmc->fw_path = SKL_DMC_PATH; + dmc->required_version = SKL_DMC_VERSION_REQUIRED; + dmc->max_fw_size = SKL_DMC_MAX_FW_SIZE; + } else if (IS_BROXTON(dev_priv)) { + dmc->fw_path = BXT_DMC_PATH; + dmc->required_version = BXT_DMC_VERSION_REQUIRED; + dmc->max_fw_size = BXT_DMC_MAX_FW_SIZE; + } + + if (dev_priv->params.dmc_firmware_path) { + if (strlen(dev_priv->params.dmc_firmware_path) == 0) { + dmc->fw_path = NULL; + drm_info(&dev_priv->drm, + "Disabling DMC firmware and runtime PM\n"); + return; + } + + dmc->fw_path = dev_priv->params.dmc_firmware_path; + /* Bypass version check for firmware override. */ + dmc->required_version = 0; + } + + if (!dmc->fw_path) { + drm_dbg_kms(&dev_priv->drm, + "No known DMC firmware for platform, disabling runtime PM\n"); + return; + } + + drm_dbg_kms(&dev_priv->drm, "Loading %s\n", dmc->fw_path); + schedule_work(&dev_priv->display.dmc.work); +} + +/** + * intel_dmc_ucode_suspend() - prepare DMC firmware before system suspend + * @dev_priv: i915 drm device + * + * Prepare the DMC firmware before entering system suspend. This includes + * flushing pending work items and releasing any resources acquired during + * init. + */ +void intel_dmc_ucode_suspend(struct drm_i915_private *dev_priv) +{ + if (!HAS_DMC(dev_priv)) + return; + + flush_work(&dev_priv->display.dmc.work); + + /* Drop the reference held in case DMC isn't loaded. */ + if (!intel_dmc_has_payload(dev_priv)) + intel_dmc_runtime_pm_put(dev_priv); +} + +/** + * intel_dmc_ucode_resume() - init DMC firmware during system resume + * @dev_priv: i915 drm device + * + * Reinitialize the DMC firmware during system resume, reacquiring any + * resources released in intel_dmc_ucode_suspend(). + */ +void intel_dmc_ucode_resume(struct drm_i915_private *dev_priv) +{ + if (!HAS_DMC(dev_priv)) + return; + + /* + * Reacquire the reference to keep RPM disabled in case DMC isn't + * loaded. + */ + if (!intel_dmc_has_payload(dev_priv)) + intel_dmc_runtime_pm_get(dev_priv); +} + +/** + * intel_dmc_ucode_fini() - unload the DMC firmware. + * @dev_priv: i915 drm device. + * + * Firmmware unloading includes freeing the internal memory and reset the + * firmware loading status. + */ +void intel_dmc_ucode_fini(struct drm_i915_private *dev_priv) +{ + int id; + + if (!HAS_DMC(dev_priv)) + return; + + intel_dmc_ucode_suspend(dev_priv); + drm_WARN_ON(&dev_priv->drm, dev_priv->display.dmc.wakeref); + + for (id = 0; id < DMC_FW_MAX; id++) + kfree(dev_priv->display.dmc.dmc_info[id].payload); +} + +void intel_dmc_print_error_state(struct drm_i915_error_state_buf *m, + struct drm_i915_private *i915) +{ + struct intel_dmc *dmc = &i915->display.dmc; + + if (!HAS_DMC(i915)) + return; + + i915_error_printf(m, "DMC loaded: %s\n", + str_yes_no(intel_dmc_has_payload(i915))); + i915_error_printf(m, "DMC fw version: %d.%d\n", + DMC_VERSION_MAJOR(dmc->version), + DMC_VERSION_MINOR(dmc->version)); +} + +static int intel_dmc_debugfs_status_show(struct seq_file *m, void *unused) +{ + struct drm_i915_private *i915 = m->private; + intel_wakeref_t wakeref; + struct intel_dmc *dmc; + i915_reg_t dc5_reg, dc6_reg = INVALID_MMIO_REG; + + if (!HAS_DMC(i915)) + return -ENODEV; + + dmc = &i915->display.dmc; + + wakeref = intel_runtime_pm_get(&i915->runtime_pm); + + seq_printf(m, "fw loaded: %s\n", + str_yes_no(intel_dmc_has_payload(i915))); + seq_printf(m, "path: %s\n", dmc->fw_path); + seq_printf(m, "Pipe A fw support: %s\n", + str_yes_no(GRAPHICS_VER(i915) >= 12)); + seq_printf(m, "Pipe A fw loaded: %s\n", + str_yes_no(dmc->dmc_info[DMC_FW_PIPEA].payload)); + seq_printf(m, "Pipe B fw support: %s\n", + str_yes_no(IS_ALDERLAKE_P(i915))); + seq_printf(m, "Pipe B fw loaded: %s\n", + str_yes_no(dmc->dmc_info[DMC_FW_PIPEB].payload)); + + if (!intel_dmc_has_payload(i915)) + goto out; + + seq_printf(m, "version: %d.%d\n", DMC_VERSION_MAJOR(dmc->version), + DMC_VERSION_MINOR(dmc->version)); + + if (DISPLAY_VER(i915) >= 12) { + if (IS_DGFX(i915)) { + dc5_reg = DG1_DMC_DEBUG_DC5_COUNT; + } else { + dc5_reg = TGL_DMC_DEBUG_DC5_COUNT; + dc6_reg = TGL_DMC_DEBUG_DC6_COUNT; + } + + /* + * NOTE: DMC_DEBUG3 is a general purpose reg. + * According to B.Specs:49196 DMC f/w reuses DC5/6 counter + * reg for DC3CO debugging and validation, + * but TGL DMC f/w is using DMC_DEBUG3 reg for DC3CO counter. + */ + seq_printf(m, "DC3CO count: %d\n", + intel_de_read(i915, IS_DGFX(i915) ? + DG1_DMC_DEBUG3 : TGL_DMC_DEBUG3)); + } else { + dc5_reg = IS_BROXTON(i915) ? BXT_DMC_DC3_DC5_COUNT : + SKL_DMC_DC3_DC5_COUNT; + if (!IS_GEMINILAKE(i915) && !IS_BROXTON(i915)) + dc6_reg = SKL_DMC_DC5_DC6_COUNT; + } + + seq_printf(m, "DC3 -> DC5 count: %d\n", intel_de_read(i915, dc5_reg)); + if (i915_mmio_reg_valid(dc6_reg)) + seq_printf(m, "DC5 -> DC6 count: %d\n", + intel_de_read(i915, dc6_reg)); + +out: + seq_printf(m, "program base: 0x%08x\n", + intel_de_read(i915, DMC_PROGRAM(dmc->dmc_info[DMC_FW_MAIN].start_mmioaddr, 0))); + seq_printf(m, "ssp base: 0x%08x\n", + intel_de_read(i915, DMC_SSP_BASE)); + seq_printf(m, "htp: 0x%08x\n", intel_de_read(i915, DMC_HTP_SKL)); + + intel_runtime_pm_put(&i915->runtime_pm, wakeref); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(intel_dmc_debugfs_status); + +void intel_dmc_debugfs_register(struct drm_i915_private *i915) +{ + struct drm_minor *minor = i915->drm.primary; + + debugfs_create_file("i915_dmc_info", 0444, minor->debugfs_root, + i915, &intel_dmc_debugfs_status_fops); +} diff --git a/drivers/gpu/drm/i915/display/intel_dmc.h b/drivers/gpu/drm/i915/display/intel_dmc.h new file mode 100644 index 000000000..67e03315e --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dmc.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_DMC_H__ +#define __INTEL_DMC_H__ + +#include "i915_reg_defs.h" +#include "intel_wakeref.h" +#include + +struct drm_i915_error_state_buf; +struct drm_i915_private; + +enum { + DMC_FW_MAIN = 0, + DMC_FW_PIPEA, + DMC_FW_PIPEB, + DMC_FW_PIPEC, + DMC_FW_PIPED, + DMC_FW_MAX +}; + +struct intel_dmc { + struct work_struct work; + const char *fw_path; + u32 required_version; + u32 max_fw_size; /* bytes */ + u32 version; + struct dmc_fw_info { + u32 mmio_count; + i915_reg_t mmioaddr[20]; + u32 mmiodata[20]; + u32 dmc_offset; + u32 start_mmioaddr; + u32 dmc_fw_size; /*dwords */ + u32 *payload; + bool present; + } dmc_info[DMC_FW_MAX]; + + u32 dc_state; + u32 target_dc_state; + u32 allowed_dc_mask; + intel_wakeref_t wakeref; +}; + +void intel_dmc_ucode_init(struct drm_i915_private *i915); +void intel_dmc_load_program(struct drm_i915_private *i915); +void intel_dmc_disable_program(struct drm_i915_private *i915); +void intel_dmc_ucode_fini(struct drm_i915_private *i915); +void intel_dmc_ucode_suspend(struct drm_i915_private *i915); +void intel_dmc_ucode_resume(struct drm_i915_private *i915); +bool intel_dmc_has_payload(struct drm_i915_private *i915); +void intel_dmc_debugfs_register(struct drm_i915_private *i915); +void intel_dmc_print_error_state(struct drm_i915_error_state_buf *m, + struct drm_i915_private *i915); + +void assert_dmc_loaded(struct drm_i915_private *i915); + +#endif /* __INTEL_DMC_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_dmc_regs.h b/drivers/gpu/drm/i915/display/intel_dmc_regs.h new file mode 100644 index 000000000..5e5e41644 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dmc_regs.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2022 Intel Corporation + */ + +#ifndef __INTEL_DMC_REGS_H__ +#define __INTEL_DMC_REGS_H__ + +#include "i915_reg_defs.h" + +#define DMC_PROGRAM(addr, i) _MMIO((addr) + (i) * 4) +#define DMC_SSP_BASE_ADDR_GEN9 0x00002FC0 + +#define _ADLP_PIPEDMC_REG_MMIO_BASE_A 0x5f000 +#define _TGL_PIPEDMC_REG_MMIO_BASE_A 0x92000 + +#define __PIPEDMC_REG_MMIO_BASE(i915, dmc_id) \ + ((DISPLAY_VER(i915) >= 13 ? _ADLP_PIPEDMC_REG_MMIO_BASE_A : \ + _TGL_PIPEDMC_REG_MMIO_BASE_A) + \ + 0x400 * ((dmc_id) - 1)) + +#define __DMC_REG_MMIO_BASE 0x8f000 + +#define _DMC_REG_MMIO_BASE(i915, dmc_id) \ + ((dmc_id) == DMC_FW_MAIN ? __DMC_REG_MMIO_BASE : \ + __PIPEDMC_REG_MMIO_BASE(i915, dmc_id)) + +#define _DMC_REG(i915, dmc_id, reg) \ + ((reg) - __DMC_REG_MMIO_BASE + _DMC_REG_MMIO_BASE(i915, dmc_id)) + +#define DMC_EVENT_HANDLER_COUNT_GEN12 8 + +#define _DMC_EVT_HTP_0 0x8f004 + +#define DMC_EVT_HTP(i915, dmc_id, handler) \ + _MMIO(_DMC_REG(i915, dmc_id, _DMC_EVT_HTP_0) + 4 * (handler)) + +#define _DMC_EVT_CTL_0 0x8f034 + +#define DMC_EVT_CTL(i915, dmc_id, handler) \ + _MMIO(_DMC_REG(i915, dmc_id, _DMC_EVT_CTL_0) + 4 * (handler)) + +#define DMC_EVT_CTL_ENABLE REG_BIT(31) +#define DMC_EVT_CTL_RECURRING REG_BIT(30) +#define DMC_EVT_CTL_TYPE_MASK REG_GENMASK(17, 16) +#define DMC_EVT_CTL_TYPE_LEVEL_0 0 +#define DMC_EVT_CTL_TYPE_LEVEL_1 1 +#define DMC_EVT_CTL_TYPE_EDGE_1_0 2 +#define DMC_EVT_CTL_TYPE_EDGE_0_1 3 + +#define DMC_EVT_CTL_EVENT_ID_MASK REG_GENMASK(15, 8) +#define DMC_EVT_CTL_EVENT_ID_FALSE 0x01 +/* An event handler scheduled to run at a 1 kHz frequency. */ +#define DMC_EVT_CTL_EVENT_ID_CLK_MSEC 0xbf + +#define DMC_HTP_ADDR_SKL 0x00500034 +#define DMC_SSP_BASE _MMIO(0x8F074) +#define DMC_HTP_SKL _MMIO(0x8F004) +#define DMC_LAST_WRITE _MMIO(0x8F034) +#define DMC_LAST_WRITE_VALUE 0xc003b400 +#define DMC_MMIO_START_RANGE 0x80000 +#define DMC_MMIO_END_RANGE 0x8FFFF +#define DMC_V1_MMIO_START_RANGE 0x80000 +#define TGL_MAIN_MMIO_START 0x8F000 +#define TGL_MAIN_MMIO_END 0x8FFFF +#define _TGL_PIPEA_MMIO_START 0x92000 +#define _TGL_PIPEA_MMIO_END 0x93FFF +#define _TGL_PIPEB_MMIO_START 0x96000 +#define _TGL_PIPEB_MMIO_END 0x97FFF +#define ADLP_PIPE_MMIO_START 0x5F000 +#define ADLP_PIPE_MMIO_END 0x5FFFF + +#define TGL_PIPE_MMIO_START(dmc_id) _PICK_EVEN(((dmc_id) - 1), _TGL_PIPEA_MMIO_START,\ + _TGL_PIPEB_MMIO_START) + +#define TGL_PIPE_MMIO_END(dmc_id) _PICK_EVEN(((dmc_id) - 1), _TGL_PIPEA_MMIO_END,\ + _TGL_PIPEB_MMIO_END) + +#define SKL_DMC_DC3_DC5_COUNT _MMIO(0x80030) +#define SKL_DMC_DC5_DC6_COUNT _MMIO(0x8002C) +#define BXT_DMC_DC3_DC5_COUNT _MMIO(0x80038) +#define TGL_DMC_DEBUG_DC5_COUNT _MMIO(0x101084) +#define TGL_DMC_DEBUG_DC6_COUNT _MMIO(0x101088) +#define DG1_DMC_DEBUG_DC5_COUNT _MMIO(0x134154) + +#define TGL_DMC_DEBUG3 _MMIO(0x101090) +#define DG1_DMC_DEBUG3 _MMIO(0x13415c) + +#endif /* __INTEL_DMC_REGS_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c new file mode 100644 index 000000000..4699c2110 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -0,0 +1,5475 @@ +/* + * Copyright © 2008 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Authors: + * Keith Packard + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "g4x_dp.h" +#include "i915_debugfs.h" +#include "i915_drv.h" +#include "intel_atomic.h" +#include "intel_audio.h" +#include "intel_backlight.h" +#include "intel_combo_phy_regs.h" +#include "intel_connector.h" +#include "intel_crtc.h" +#include "intel_ddi.h" +#include "intel_de.h" +#include "intel_display_types.h" +#include "intel_dp.h" +#include "intel_dp_aux.h" +#include "intel_dp_hdcp.h" +#include "intel_dp_link_training.h" +#include "intel_dp_mst.h" +#include "intel_dpio_phy.h" +#include "intel_dpll.h" +#include "intel_fifo_underrun.h" +#include "intel_hdcp.h" +#include "intel_hdmi.h" +#include "intel_hotplug.h" +#include "intel_lspcon.h" +#include "intel_lvds.h" +#include "intel_panel.h" +#include "intel_pch_display.h" +#include "intel_pps.h" +#include "intel_psr.h" +#include "intel_tc.h" +#include "intel_vdsc.h" +#include "intel_vrr.h" + +/* DP DSC throughput values used for slice count calculations KPixels/s */ +#define DP_DSC_PEAK_PIXEL_RATE 2720000 +#define DP_DSC_MAX_ENC_THROUGHPUT_0 340000 +#define DP_DSC_MAX_ENC_THROUGHPUT_1 400000 + +/* DP DSC FEC Overhead factor = 1/(0.972261) */ +#define DP_DSC_FEC_OVERHEAD_FACTOR 972261 + +/* Compliance test status bits */ +#define INTEL_DP_RESOLUTION_SHIFT_MASK 0 +#define INTEL_DP_RESOLUTION_PREFERRED (1 << INTEL_DP_RESOLUTION_SHIFT_MASK) +#define INTEL_DP_RESOLUTION_STANDARD (2 << INTEL_DP_RESOLUTION_SHIFT_MASK) +#define INTEL_DP_RESOLUTION_FAILSAFE (3 << INTEL_DP_RESOLUTION_SHIFT_MASK) + + +/* Constants for DP DSC configurations */ +static const u8 valid_dsc_bpp[] = {6, 8, 10, 12, 15}; + +/* With Single pipe configuration, HW is capable of supporting maximum + * of 4 slices per line. + */ +static const u8 valid_dsc_slicecount[] = {1, 2, 4}; + +/** + * intel_dp_is_edp - is the given port attached to an eDP panel (either CPU or PCH) + * @intel_dp: DP struct + * + * If a CPU or PCH DP output is attached to an eDP panel, this function + * will return true, and false otherwise. + * + * This function is not safe to use prior to encoder type being set. + */ +bool intel_dp_is_edp(struct intel_dp *intel_dp) +{ + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + + return dig_port->base.type == INTEL_OUTPUT_EDP; +} + +static void intel_dp_unset_edid(struct intel_dp *intel_dp); +static int intel_dp_dsc_compute_bpp(struct intel_dp *intel_dp, u8 dsc_max_bpc); + +/* Is link rate UHBR and thus 128b/132b? */ +bool intel_dp_is_uhbr(const struct intel_crtc_state *crtc_state) +{ + return crtc_state->port_clock >= 1000000; +} + +static void intel_dp_set_default_sink_rates(struct intel_dp *intel_dp) +{ + intel_dp->sink_rates[0] = 162000; + intel_dp->num_sink_rates = 1; +} + +/* update sink rates from dpcd */ +static void intel_dp_set_dpcd_sink_rates(struct intel_dp *intel_dp) +{ + static const int dp_rates[] = { + 162000, 270000, 540000, 810000 + }; + int i, max_rate; + int max_lttpr_rate; + + if (drm_dp_has_quirk(&intel_dp->desc, DP_DPCD_QUIRK_CAN_DO_MAX_LINK_RATE_3_24_GBPS)) { + /* Needed, e.g., for Apple MBP 2017, 15 inch eDP Retina panel */ + static const int quirk_rates[] = { 162000, 270000, 324000 }; + + memcpy(intel_dp->sink_rates, quirk_rates, sizeof(quirk_rates)); + intel_dp->num_sink_rates = ARRAY_SIZE(quirk_rates); + + return; + } + + /* + * Sink rates for 8b/10b. + */ + max_rate = drm_dp_bw_code_to_link_rate(intel_dp->dpcd[DP_MAX_LINK_RATE]); + max_lttpr_rate = drm_dp_lttpr_max_link_rate(intel_dp->lttpr_common_caps); + if (max_lttpr_rate) + max_rate = min(max_rate, max_lttpr_rate); + + for (i = 0; i < ARRAY_SIZE(dp_rates); i++) { + if (dp_rates[i] > max_rate) + break; + intel_dp->sink_rates[i] = dp_rates[i]; + } + + /* + * Sink rates for 128b/132b. If set, sink should support all 8b/10b + * rates and 10 Gbps. + */ + if (intel_dp->dpcd[DP_MAIN_LINK_CHANNEL_CODING] & DP_CAP_ANSI_128B132B) { + u8 uhbr_rates = 0; + + BUILD_BUG_ON(ARRAY_SIZE(intel_dp->sink_rates) < ARRAY_SIZE(dp_rates) + 3); + + drm_dp_dpcd_readb(&intel_dp->aux, + DP_128B132B_SUPPORTED_LINK_RATES, &uhbr_rates); + + if (drm_dp_lttpr_count(intel_dp->lttpr_common_caps)) { + /* We have a repeater */ + if (intel_dp->lttpr_common_caps[0] >= 0x20 && + intel_dp->lttpr_common_caps[DP_MAIN_LINK_CHANNEL_CODING_PHY_REPEATER - + DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV] & + DP_PHY_REPEATER_128B132B_SUPPORTED) { + /* Repeater supports 128b/132b, valid UHBR rates */ + uhbr_rates &= intel_dp->lttpr_common_caps[DP_PHY_REPEATER_128B132B_RATES - + DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV]; + } else { + /* Does not support 128b/132b */ + uhbr_rates = 0; + } + } + + if (uhbr_rates & DP_UHBR10) + intel_dp->sink_rates[i++] = 1000000; + if (uhbr_rates & DP_UHBR13_5) + intel_dp->sink_rates[i++] = 1350000; + if (uhbr_rates & DP_UHBR20) + intel_dp->sink_rates[i++] = 2000000; + } + + intel_dp->num_sink_rates = i; +} + +static void intel_dp_set_sink_rates(struct intel_dp *intel_dp) +{ + struct intel_connector *connector = intel_dp->attached_connector; + struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); + struct intel_encoder *encoder = &intel_dig_port->base; + + intel_dp_set_dpcd_sink_rates(intel_dp); + + if (intel_dp->num_sink_rates) + return; + + drm_err(&dp_to_i915(intel_dp)->drm, + "[CONNECTOR:%d:%s][ENCODER:%d:%s] Invalid DPCD with no link rates, using defaults\n", + connector->base.base.id, connector->base.name, + encoder->base.base.id, encoder->base.name); + + intel_dp_set_default_sink_rates(intel_dp); +} + +static void intel_dp_set_default_max_sink_lane_count(struct intel_dp *intel_dp) +{ + intel_dp->max_sink_lane_count = 1; +} + +static void intel_dp_set_max_sink_lane_count(struct intel_dp *intel_dp) +{ + struct intel_connector *connector = intel_dp->attached_connector; + struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); + struct intel_encoder *encoder = &intel_dig_port->base; + + intel_dp->max_sink_lane_count = drm_dp_max_lane_count(intel_dp->dpcd); + + switch (intel_dp->max_sink_lane_count) { + case 1: + case 2: + case 4: + return; + } + + drm_err(&dp_to_i915(intel_dp)->drm, + "[CONNECTOR:%d:%s][ENCODER:%d:%s] Invalid DPCD max lane count (%d), using default\n", + connector->base.base.id, connector->base.name, + encoder->base.base.id, encoder->base.name, + intel_dp->max_sink_lane_count); + + intel_dp_set_default_max_sink_lane_count(intel_dp); +} + +/* Get length of rates array potentially limited by max_rate. */ +static int intel_dp_rate_limit_len(const int *rates, int len, int max_rate) +{ + int i; + + /* Limit results by potentially reduced max rate */ + for (i = 0; i < len; i++) { + if (rates[len - i - 1] <= max_rate) + return len - i; + } + + return 0; +} + +/* Get length of common rates array potentially limited by max_rate. */ +static int intel_dp_common_len_rate_limit(const struct intel_dp *intel_dp, + int max_rate) +{ + return intel_dp_rate_limit_len(intel_dp->common_rates, + intel_dp->num_common_rates, max_rate); +} + +static int intel_dp_common_rate(struct intel_dp *intel_dp, int index) +{ + if (drm_WARN_ON(&dp_to_i915(intel_dp)->drm, + index < 0 || index >= intel_dp->num_common_rates)) + return 162000; + + return intel_dp->common_rates[index]; +} + +/* Theoretical max between source and sink */ +static int intel_dp_max_common_rate(struct intel_dp *intel_dp) +{ + return intel_dp_common_rate(intel_dp, intel_dp->num_common_rates - 1); +} + +static int intel_dp_max_source_lane_count(struct intel_digital_port *dig_port) +{ + int vbt_max_lanes = intel_bios_dp_max_lane_count(&dig_port->base); + int max_lanes = dig_port->max_lanes; + + if (vbt_max_lanes) + max_lanes = min(max_lanes, vbt_max_lanes); + + return max_lanes; +} + +/* Theoretical max between source and sink */ +static int intel_dp_max_common_lane_count(struct intel_dp *intel_dp) +{ + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + int source_max = intel_dp_max_source_lane_count(dig_port); + int sink_max = intel_dp->max_sink_lane_count; + int fia_max = intel_tc_port_fia_max_lane_count(dig_port); + int lttpr_max = drm_dp_lttpr_max_lane_count(intel_dp->lttpr_common_caps); + + if (lttpr_max) + sink_max = min(sink_max, lttpr_max); + + return min3(source_max, sink_max, fia_max); +} + +int intel_dp_max_lane_count(struct intel_dp *intel_dp) +{ + switch (intel_dp->max_link_lane_count) { + case 1: + case 2: + case 4: + return intel_dp->max_link_lane_count; + default: + MISSING_CASE(intel_dp->max_link_lane_count); + return 1; + } +} + +/* + * The required data bandwidth for a mode with given pixel clock and bpp. This + * is the required net bandwidth independent of the data bandwidth efficiency. + */ +int +intel_dp_link_required(int pixel_clock, int bpp) +{ + /* pixel_clock is in kHz, divide bpp by 8 for bit to Byte conversion */ + return DIV_ROUND_UP(pixel_clock * bpp, 8); +} + +/* + * Given a link rate and lanes, get the data bandwidth. + * + * Data bandwidth is the actual payload rate, which depends on the data + * bandwidth efficiency and the link rate. + * + * For 8b/10b channel encoding, SST and non-FEC, the data bandwidth efficiency + * is 80%. For example, for a 1.62 Gbps link, 1.62*10^9 bps * 0.80 * (1/8) = + * 162000 kBps. With 8-bit symbols, we have 162000 kHz symbol clock. Just by + * coincidence, the port clock in kHz matches the data bandwidth in kBps, and + * they equal the link bit rate in Gbps multiplied by 100000. (Note that this no + * longer holds for data bandwidth as soon as FEC or MST is taken into account!) + * + * For 128b/132b channel encoding, the data bandwidth efficiency is 96.71%. For + * example, for a 10 Gbps link, 10*10^9 bps * 0.9671 * (1/8) = 1208875 + * kBps. With 32-bit symbols, we have 312500 kHz symbol clock. The value 1000000 + * does not match the symbol clock, the port clock (not even if you think in + * terms of a byte clock), nor the data bandwidth. It only matches the link bit + * rate in units of 10000 bps. + */ +int +intel_dp_max_data_rate(int max_link_rate, int max_lanes) +{ + if (max_link_rate >= 1000000) { + /* + * UHBR rates always use 128b/132b channel encoding, and have + * 97.71% data bandwidth efficiency. Consider max_link_rate the + * link bit rate in units of 10000 bps. + */ + int max_link_rate_kbps = max_link_rate * 10; + + max_link_rate_kbps = DIV_ROUND_CLOSEST_ULL(mul_u32_u32(max_link_rate_kbps, 9671), 10000); + max_link_rate = max_link_rate_kbps / 8; + } + + /* + * Lower than UHBR rates always use 8b/10b channel encoding, and have + * 80% data bandwidth efficiency for SST non-FEC. However, this turns + * out to be a nop by coincidence, and can be skipped: + * + * int max_link_rate_kbps = max_link_rate * 10; + * max_link_rate_kbps = DIV_ROUND_CLOSEST_ULL(max_link_rate_kbps * 8, 10); + * max_link_rate = max_link_rate_kbps / 8; + */ + + return max_link_rate * max_lanes; +} + +bool intel_dp_can_bigjoiner(struct intel_dp *intel_dp) +{ + struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); + struct intel_encoder *encoder = &intel_dig_port->base; + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + + return DISPLAY_VER(dev_priv) >= 12 || + (DISPLAY_VER(dev_priv) == 11 && + encoder->port != PORT_A); +} + +static int dg2_max_source_rate(struct intel_dp *intel_dp) +{ + return intel_dp_is_edp(intel_dp) ? 810000 : 1350000; +} + +static int icl_max_source_rate(struct intel_dp *intel_dp) +{ + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev); + enum phy phy = intel_port_to_phy(dev_priv, dig_port->base.port); + + if (intel_phy_is_combo(dev_priv, phy) && !intel_dp_is_edp(intel_dp)) + return 540000; + + return 810000; +} + +static int ehl_max_source_rate(struct intel_dp *intel_dp) +{ + if (intel_dp_is_edp(intel_dp)) + return 540000; + + return 810000; +} + +static int vbt_max_link_rate(struct intel_dp *intel_dp) +{ + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + int max_rate; + + max_rate = intel_bios_dp_max_link_rate(encoder); + + if (intel_dp_is_edp(intel_dp)) { + struct intel_connector *connector = intel_dp->attached_connector; + int edp_max_rate = connector->panel.vbt.edp.max_link_rate; + + if (max_rate && edp_max_rate) + max_rate = min(max_rate, edp_max_rate); + else if (edp_max_rate) + max_rate = edp_max_rate; + } + + return max_rate; +} + +static void +intel_dp_set_source_rates(struct intel_dp *intel_dp) +{ + /* The values must be in increasing order */ + static const int icl_rates[] = { + 162000, 216000, 270000, 324000, 432000, 540000, 648000, 810000, + 1000000, 1350000, + }; + static const int bxt_rates[] = { + 162000, 216000, 243000, 270000, 324000, 432000, 540000 + }; + static const int skl_rates[] = { + 162000, 216000, 270000, 324000, 432000, 540000 + }; + static const int hsw_rates[] = { + 162000, 270000, 540000 + }; + static const int g4x_rates[] = { + 162000, 270000 + }; + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev); + const int *source_rates; + int size, max_rate = 0, vbt_max_rate; + + /* This should only be done once */ + drm_WARN_ON(&dev_priv->drm, + intel_dp->source_rates || intel_dp->num_source_rates); + + if (DISPLAY_VER(dev_priv) >= 11) { + source_rates = icl_rates; + size = ARRAY_SIZE(icl_rates); + if (IS_DG2(dev_priv)) + max_rate = dg2_max_source_rate(intel_dp); + else if (IS_ALDERLAKE_P(dev_priv) || IS_ALDERLAKE_S(dev_priv) || + IS_DG1(dev_priv) || IS_ROCKETLAKE(dev_priv)) + max_rate = 810000; + else if (IS_JSL_EHL(dev_priv)) + max_rate = ehl_max_source_rate(intel_dp); + else + max_rate = icl_max_source_rate(intel_dp); + } else if (IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv)) { + source_rates = bxt_rates; + size = ARRAY_SIZE(bxt_rates); + } else if (DISPLAY_VER(dev_priv) == 9) { + source_rates = skl_rates; + size = ARRAY_SIZE(skl_rates); + } else if ((IS_HASWELL(dev_priv) && !IS_HSW_ULX(dev_priv)) || + IS_BROADWELL(dev_priv)) { + source_rates = hsw_rates; + size = ARRAY_SIZE(hsw_rates); + } else { + source_rates = g4x_rates; + size = ARRAY_SIZE(g4x_rates); + } + + vbt_max_rate = vbt_max_link_rate(intel_dp); + if (max_rate && vbt_max_rate) + max_rate = min(max_rate, vbt_max_rate); + else if (vbt_max_rate) + max_rate = vbt_max_rate; + + if (max_rate) + size = intel_dp_rate_limit_len(source_rates, size, max_rate); + + intel_dp->source_rates = source_rates; + intel_dp->num_source_rates = size; +} + +static int intersect_rates(const int *source_rates, int source_len, + const int *sink_rates, int sink_len, + int *common_rates) +{ + int i = 0, j = 0, k = 0; + + while (i < source_len && j < sink_len) { + if (source_rates[i] == sink_rates[j]) { + if (WARN_ON(k >= DP_MAX_SUPPORTED_RATES)) + return k; + common_rates[k] = source_rates[i]; + ++k; + ++i; + ++j; + } else if (source_rates[i] < sink_rates[j]) { + ++i; + } else { + ++j; + } + } + return k; +} + +/* return index of rate in rates array, or -1 if not found */ +static int intel_dp_rate_index(const int *rates, int len, int rate) +{ + int i; + + for (i = 0; i < len; i++) + if (rate == rates[i]) + return i; + + return -1; +} + +static void intel_dp_set_common_rates(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + + drm_WARN_ON(&i915->drm, + !intel_dp->num_source_rates || !intel_dp->num_sink_rates); + + intel_dp->num_common_rates = intersect_rates(intel_dp->source_rates, + intel_dp->num_source_rates, + intel_dp->sink_rates, + intel_dp->num_sink_rates, + intel_dp->common_rates); + + /* Paranoia, there should always be something in common. */ + if (drm_WARN_ON(&i915->drm, intel_dp->num_common_rates == 0)) { + intel_dp->common_rates[0] = 162000; + intel_dp->num_common_rates = 1; + } +} + +static bool intel_dp_link_params_valid(struct intel_dp *intel_dp, int link_rate, + u8 lane_count) +{ + /* + * FIXME: we need to synchronize the current link parameters with + * hardware readout. Currently fast link training doesn't work on + * boot-up. + */ + if (link_rate == 0 || + link_rate > intel_dp->max_link_rate) + return false; + + if (lane_count == 0 || + lane_count > intel_dp_max_lane_count(intel_dp)) + return false; + + return true; +} + +static bool intel_dp_can_link_train_fallback_for_edp(struct intel_dp *intel_dp, + int link_rate, + u8 lane_count) +{ + /* FIXME figure out what we actually want here */ + const struct drm_display_mode *fixed_mode = + intel_panel_preferred_fixed_mode(intel_dp->attached_connector); + int mode_rate, max_rate; + + mode_rate = intel_dp_link_required(fixed_mode->clock, 18); + max_rate = intel_dp_max_data_rate(link_rate, lane_count); + if (mode_rate > max_rate) + return false; + + return true; +} + +int intel_dp_get_link_train_fallback_values(struct intel_dp *intel_dp, + int link_rate, u8 lane_count) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + int index; + + /* + * TODO: Enable fallback on MST links once MST link compute can handle + * the fallback params. + */ + if (intel_dp->is_mst) { + drm_err(&i915->drm, "Link Training Unsuccessful\n"); + return -1; + } + + if (intel_dp_is_edp(intel_dp) && !intel_dp->use_max_params) { + drm_dbg_kms(&i915->drm, + "Retrying Link training for eDP with max parameters\n"); + intel_dp->use_max_params = true; + return 0; + } + + index = intel_dp_rate_index(intel_dp->common_rates, + intel_dp->num_common_rates, + link_rate); + if (index > 0) { + if (intel_dp_is_edp(intel_dp) && + !intel_dp_can_link_train_fallback_for_edp(intel_dp, + intel_dp_common_rate(intel_dp, index - 1), + lane_count)) { + drm_dbg_kms(&i915->drm, + "Retrying Link training for eDP with same parameters\n"); + return 0; + } + intel_dp->max_link_rate = intel_dp_common_rate(intel_dp, index - 1); + intel_dp->max_link_lane_count = lane_count; + } else if (lane_count > 1) { + if (intel_dp_is_edp(intel_dp) && + !intel_dp_can_link_train_fallback_for_edp(intel_dp, + intel_dp_max_common_rate(intel_dp), + lane_count >> 1)) { + drm_dbg_kms(&i915->drm, + "Retrying Link training for eDP with same parameters\n"); + return 0; + } + intel_dp->max_link_rate = intel_dp_max_common_rate(intel_dp); + intel_dp->max_link_lane_count = lane_count >> 1; + } else { + drm_err(&i915->drm, "Link Training Unsuccessful\n"); + return -1; + } + + return 0; +} + +u32 intel_dp_mode_to_fec_clock(u32 mode_clock) +{ + return div_u64(mul_u32_u32(mode_clock, 1000000U), + DP_DSC_FEC_OVERHEAD_FACTOR); +} + +static int +small_joiner_ram_size_bits(struct drm_i915_private *i915) +{ + if (DISPLAY_VER(i915) >= 13) + return 17280 * 8; + else if (DISPLAY_VER(i915) >= 11) + return 7680 * 8; + else + return 6144 * 8; +} + +static u16 intel_dp_dsc_get_output_bpp(struct drm_i915_private *i915, + u32 link_clock, u32 lane_count, + u32 mode_clock, u32 mode_hdisplay, + bool bigjoiner, + u32 pipe_bpp) +{ + u32 bits_per_pixel, max_bpp_small_joiner_ram; + int i; + + /* + * Available Link Bandwidth(Kbits/sec) = (NumberOfLanes)* + * (LinkSymbolClock)* 8 * (TimeSlotsPerMTP) + * for SST -> TimeSlotsPerMTP is 1, + * for MST -> TimeSlotsPerMTP has to be calculated + */ + bits_per_pixel = (link_clock * lane_count * 8) / + intel_dp_mode_to_fec_clock(mode_clock); + + /* Small Joiner Check: output bpp <= joiner RAM (bits) / Horiz. width */ + max_bpp_small_joiner_ram = small_joiner_ram_size_bits(i915) / + mode_hdisplay; + + if (bigjoiner) + max_bpp_small_joiner_ram *= 2; + + /* + * Greatest allowed DSC BPP = MIN (output BPP from available Link BW + * check, output bpp from small joiner RAM check) + */ + bits_per_pixel = min(bits_per_pixel, max_bpp_small_joiner_ram); + + if (bigjoiner) { + u32 max_bpp_bigjoiner = + i915->display.cdclk.max_cdclk_freq * 48 / + intel_dp_mode_to_fec_clock(mode_clock); + + bits_per_pixel = min(bits_per_pixel, max_bpp_bigjoiner); + } + + /* Error out if the max bpp is less than smallest allowed valid bpp */ + if (bits_per_pixel < valid_dsc_bpp[0]) { + drm_dbg_kms(&i915->drm, "Unsupported BPP %u, min %u\n", + bits_per_pixel, valid_dsc_bpp[0]); + return 0; + } + + /* From XE_LPD onwards we support from bpc upto uncompressed bpp-1 BPPs */ + if (DISPLAY_VER(i915) >= 13) { + bits_per_pixel = min(bits_per_pixel, pipe_bpp - 1); + } else { + /* Find the nearest match in the array of known BPPs from VESA */ + for (i = 0; i < ARRAY_SIZE(valid_dsc_bpp) - 1; i++) { + if (bits_per_pixel < valid_dsc_bpp[i + 1]) + break; + } + bits_per_pixel = valid_dsc_bpp[i]; + } + + /* + * Compressed BPP in U6.4 format so multiply by 16, for Gen 11, + * fractional part is 0 + */ + return bits_per_pixel << 4; +} + +static u8 intel_dp_dsc_get_slice_count(struct intel_dp *intel_dp, + int mode_clock, int mode_hdisplay, + bool bigjoiner) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + u8 min_slice_count, i; + int max_slice_width; + + if (mode_clock <= DP_DSC_PEAK_PIXEL_RATE) + min_slice_count = DIV_ROUND_UP(mode_clock, + DP_DSC_MAX_ENC_THROUGHPUT_0); + else + min_slice_count = DIV_ROUND_UP(mode_clock, + DP_DSC_MAX_ENC_THROUGHPUT_1); + + max_slice_width = drm_dp_dsc_sink_max_slice_width(intel_dp->dsc_dpcd); + if (max_slice_width < DP_DSC_MIN_SLICE_WIDTH_VALUE) { + drm_dbg_kms(&i915->drm, + "Unsupported slice width %d by DP DSC Sink device\n", + max_slice_width); + return 0; + } + /* Also take into account max slice width */ + min_slice_count = max_t(u8, min_slice_count, + DIV_ROUND_UP(mode_hdisplay, + max_slice_width)); + + /* Find the closest match to the valid slice count values */ + for (i = 0; i < ARRAY_SIZE(valid_dsc_slicecount); i++) { + u8 test_slice_count = valid_dsc_slicecount[i] << bigjoiner; + + if (test_slice_count > + drm_dp_dsc_sink_max_slice_count(intel_dp->dsc_dpcd, false)) + break; + + /* big joiner needs small joiner to be enabled */ + if (bigjoiner && test_slice_count < 4) + continue; + + if (min_slice_count <= test_slice_count) + return test_slice_count; + } + + drm_dbg_kms(&i915->drm, "Unsupported Slice Count %d\n", + min_slice_count); + return 0; +} + +static enum intel_output_format +intel_dp_output_format(struct intel_connector *connector, + bool ycbcr_420_output) +{ + struct intel_dp *intel_dp = intel_attached_dp(connector); + + if (!connector->base.ycbcr_420_allowed || !ycbcr_420_output) + return INTEL_OUTPUT_FORMAT_RGB; + + if (intel_dp->dfp.rgb_to_ycbcr && + intel_dp->dfp.ycbcr_444_to_420) + return INTEL_OUTPUT_FORMAT_RGB; + + if (intel_dp->dfp.ycbcr_444_to_420) + return INTEL_OUTPUT_FORMAT_YCBCR444; + else + return INTEL_OUTPUT_FORMAT_YCBCR420; +} + +int intel_dp_min_bpp(enum intel_output_format output_format) +{ + if (output_format == INTEL_OUTPUT_FORMAT_RGB) + return 6 * 3; + else + return 8 * 3; +} + +static int intel_dp_output_bpp(enum intel_output_format output_format, int bpp) +{ + /* + * bpp value was assumed to RGB format. And YCbCr 4:2:0 output + * format of the number of bytes per pixel will be half the number + * of bytes of RGB pixel. + */ + if (output_format == INTEL_OUTPUT_FORMAT_YCBCR420) + bpp /= 2; + + return bpp; +} + +static int +intel_dp_mode_min_output_bpp(struct intel_connector *connector, + const struct drm_display_mode *mode) +{ + const struct drm_display_info *info = &connector->base.display_info; + enum intel_output_format output_format = + intel_dp_output_format(connector, drm_mode_is_420_only(info, mode)); + + return intel_dp_output_bpp(output_format, intel_dp_min_bpp(output_format)); +} + +static bool intel_dp_hdisplay_bad(struct drm_i915_private *dev_priv, + int hdisplay) +{ + /* + * Older platforms don't like hdisplay==4096 with DP. + * + * On ILK/SNB/IVB the pipe seems to be somewhat running (scanline + * and frame counter increment), but we don't get vblank interrupts, + * and the pipe underruns immediately. The link also doesn't seem + * to get trained properly. + * + * On CHV the vblank interrupts don't seem to disappear but + * otherwise the symptoms are similar. + * + * TODO: confirm the behaviour on HSW+ + */ + return hdisplay == 4096 && !HAS_DDI(dev_priv); +} + +static int intel_dp_max_tmds_clock(struct intel_dp *intel_dp) +{ + struct intel_connector *connector = intel_dp->attached_connector; + const struct drm_display_info *info = &connector->base.display_info; + int max_tmds_clock = intel_dp->dfp.max_tmds_clock; + + /* Only consider the sink's max TMDS clock if we know this is a HDMI DFP */ + if (max_tmds_clock && info->max_tmds_clock) + max_tmds_clock = min(max_tmds_clock, info->max_tmds_clock); + + return max_tmds_clock; +} + +static enum drm_mode_status +intel_dp_tmds_clock_valid(struct intel_dp *intel_dp, + int clock, int bpc, bool ycbcr420_output, + bool respect_downstream_limits) +{ + int tmds_clock, min_tmds_clock, max_tmds_clock; + + if (!respect_downstream_limits) + return MODE_OK; + + tmds_clock = intel_hdmi_tmds_clock(clock, bpc, ycbcr420_output); + + min_tmds_clock = intel_dp->dfp.min_tmds_clock; + max_tmds_clock = intel_dp_max_tmds_clock(intel_dp); + + if (min_tmds_clock && tmds_clock < min_tmds_clock) + return MODE_CLOCK_LOW; + + if (max_tmds_clock && tmds_clock > max_tmds_clock) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static enum drm_mode_status +intel_dp_mode_valid_downstream(struct intel_connector *connector, + const struct drm_display_mode *mode, + int target_clock) +{ + struct intel_dp *intel_dp = intel_attached_dp(connector); + const struct drm_display_info *info = &connector->base.display_info; + enum drm_mode_status status; + bool ycbcr_420_only; + + /* If PCON supports FRL MODE, check FRL bandwidth constraints */ + if (intel_dp->dfp.pcon_max_frl_bw) { + int target_bw; + int max_frl_bw; + int bpp = intel_dp_mode_min_output_bpp(connector, mode); + + target_bw = bpp * target_clock; + + max_frl_bw = intel_dp->dfp.pcon_max_frl_bw; + + /* converting bw from Gbps to Kbps*/ + max_frl_bw = max_frl_bw * 1000000; + + if (target_bw > max_frl_bw) + return MODE_CLOCK_HIGH; + + return MODE_OK; + } + + if (intel_dp->dfp.max_dotclock && + target_clock > intel_dp->dfp.max_dotclock) + return MODE_CLOCK_HIGH; + + ycbcr_420_only = drm_mode_is_420_only(info, mode); + + /* Assume 8bpc for the DP++/HDMI/DVI TMDS clock check */ + status = intel_dp_tmds_clock_valid(intel_dp, target_clock, + 8, ycbcr_420_only, true); + + if (status != MODE_OK) { + if (ycbcr_420_only || + !connector->base.ycbcr_420_allowed || + !drm_mode_is_420_also(info, mode)) + return status; + + status = intel_dp_tmds_clock_valid(intel_dp, target_clock, + 8, true, true); + if (status != MODE_OK) + return status; + } + + return MODE_OK; +} + +static bool intel_dp_need_bigjoiner(struct intel_dp *intel_dp, + int hdisplay, int clock) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + + if (!intel_dp_can_bigjoiner(intel_dp)) + return false; + + return clock > i915->max_dotclk_freq || hdisplay > 5120; +} + +static enum drm_mode_status +intel_dp_mode_valid(struct drm_connector *_connector, + struct drm_display_mode *mode) +{ + struct intel_connector *connector = to_intel_connector(_connector); + struct intel_dp *intel_dp = intel_attached_dp(connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + const struct drm_display_mode *fixed_mode; + int target_clock = mode->clock; + int max_rate, mode_rate, max_lanes, max_link_clock; + int max_dotclk = dev_priv->max_dotclk_freq; + u16 dsc_max_output_bpp = 0; + u8 dsc_slice_count = 0; + enum drm_mode_status status; + bool dsc = false, bigjoiner = false; + + status = intel_cpu_transcoder_mode_valid(dev_priv, mode); + if (status != MODE_OK) + return status; + + if (mode->flags & DRM_MODE_FLAG_DBLCLK) + return MODE_H_ILLEGAL; + + fixed_mode = intel_panel_fixed_mode(connector, mode); + if (intel_dp_is_edp(intel_dp) && fixed_mode) { + status = intel_panel_mode_valid(connector, mode); + if (status != MODE_OK) + return status; + + target_clock = fixed_mode->clock; + } + + if (mode->clock < 10000) + return MODE_CLOCK_LOW; + + if (intel_dp_need_bigjoiner(intel_dp, mode->hdisplay, target_clock)) { + bigjoiner = true; + max_dotclk *= 2; + } + if (target_clock > max_dotclk) + return MODE_CLOCK_HIGH; + + max_link_clock = intel_dp_max_link_rate(intel_dp); + max_lanes = intel_dp_max_lane_count(intel_dp); + + max_rate = intel_dp_max_data_rate(max_link_clock, max_lanes); + mode_rate = intel_dp_link_required(target_clock, + intel_dp_mode_min_output_bpp(connector, mode)); + + if (intel_dp_hdisplay_bad(dev_priv, mode->hdisplay)) + return MODE_H_ILLEGAL; + + /* + * Output bpp is stored in 6.4 format so right shift by 4 to get the + * integer value since we support only integer values of bpp. + */ + if (DISPLAY_VER(dev_priv) >= 10 && + drm_dp_sink_supports_dsc(intel_dp->dsc_dpcd)) { + /* + * TBD pass the connector BPC, + * for now U8_MAX so that max BPC on that platform would be picked + */ + int pipe_bpp = intel_dp_dsc_compute_bpp(intel_dp, U8_MAX); + + if (intel_dp_is_edp(intel_dp)) { + dsc_max_output_bpp = + drm_edp_dsc_sink_output_bpp(intel_dp->dsc_dpcd) >> 4; + dsc_slice_count = + drm_dp_dsc_sink_max_slice_count(intel_dp->dsc_dpcd, + true); + } else if (drm_dp_sink_supports_fec(intel_dp->fec_capable)) { + dsc_max_output_bpp = + intel_dp_dsc_get_output_bpp(dev_priv, + max_link_clock, + max_lanes, + target_clock, + mode->hdisplay, + bigjoiner, + pipe_bpp) >> 4; + dsc_slice_count = + intel_dp_dsc_get_slice_count(intel_dp, + target_clock, + mode->hdisplay, + bigjoiner); + } + + dsc = dsc_max_output_bpp && dsc_slice_count; + } + + /* + * Big joiner configuration needs DSC for TGL which is not true for + * XE_LPD where uncompressed joiner is supported. + */ + if (DISPLAY_VER(dev_priv) < 13 && bigjoiner && !dsc) + return MODE_CLOCK_HIGH; + + if (mode_rate > max_rate && !dsc) + return MODE_CLOCK_HIGH; + + status = intel_dp_mode_valid_downstream(connector, mode, target_clock); + if (status != MODE_OK) + return status; + + return intel_mode_valid_max_plane_size(dev_priv, mode, bigjoiner); +} + +bool intel_dp_source_supports_tps3(struct drm_i915_private *i915) +{ + return DISPLAY_VER(i915) >= 9 || IS_BROADWELL(i915) || IS_HASWELL(i915); +} + +bool intel_dp_source_supports_tps4(struct drm_i915_private *i915) +{ + return DISPLAY_VER(i915) >= 10; +} + +static void snprintf_int_array(char *str, size_t len, + const int *array, int nelem) +{ + int i; + + str[0] = '\0'; + + for (i = 0; i < nelem; i++) { + int r = snprintf(str, len, "%s%d", i ? ", " : "", array[i]); + if (r >= len) + return; + str += r; + len -= r; + } +} + +static void intel_dp_print_rates(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + char str[128]; /* FIXME: too big for stack? */ + + if (!drm_debug_enabled(DRM_UT_KMS)) + return; + + snprintf_int_array(str, sizeof(str), + intel_dp->source_rates, intel_dp->num_source_rates); + drm_dbg_kms(&i915->drm, "source rates: %s\n", str); + + snprintf_int_array(str, sizeof(str), + intel_dp->sink_rates, intel_dp->num_sink_rates); + drm_dbg_kms(&i915->drm, "sink rates: %s\n", str); + + snprintf_int_array(str, sizeof(str), + intel_dp->common_rates, intel_dp->num_common_rates); + drm_dbg_kms(&i915->drm, "common rates: %s\n", str); +} + +int +intel_dp_max_link_rate(struct intel_dp *intel_dp) +{ + int len; + + len = intel_dp_common_len_rate_limit(intel_dp, intel_dp->max_link_rate); + + return intel_dp_common_rate(intel_dp, len - 1); +} + +int intel_dp_rate_select(struct intel_dp *intel_dp, int rate) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + int i = intel_dp_rate_index(intel_dp->sink_rates, + intel_dp->num_sink_rates, rate); + + if (drm_WARN_ON(&i915->drm, i < 0)) + i = 0; + + return i; +} + +void intel_dp_compute_rate(struct intel_dp *intel_dp, int port_clock, + u8 *link_bw, u8 *rate_select) +{ + /* eDP 1.4 rate select method. */ + if (intel_dp->use_rate_select) { + *link_bw = 0; + *rate_select = + intel_dp_rate_select(intel_dp, port_clock); + } else { + *link_bw = drm_dp_link_rate_to_bw_code(port_clock); + *rate_select = 0; + } +} + +static bool intel_dp_source_supports_fec(struct intel_dp *intel_dp, + const struct intel_crtc_state *pipe_config) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + + /* On TGL, FEC is supported on all Pipes */ + if (DISPLAY_VER(dev_priv) >= 12) + return true; + + if (DISPLAY_VER(dev_priv) == 11 && pipe_config->cpu_transcoder != TRANSCODER_A) + return true; + + return false; +} + +static bool intel_dp_supports_fec(struct intel_dp *intel_dp, + const struct intel_crtc_state *pipe_config) +{ + return intel_dp_source_supports_fec(intel_dp, pipe_config) && + drm_dp_sink_supports_fec(intel_dp->fec_capable); +} + +static bool intel_dp_supports_dsc(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP) && !crtc_state->fec_enable) + return false; + + return intel_dsc_source_support(crtc_state) && + drm_dp_sink_supports_dsc(intel_dp->dsc_dpcd); +} + +static bool intel_dp_is_ycbcr420(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + return crtc_state->output_format == INTEL_OUTPUT_FORMAT_YCBCR420 || + (crtc_state->output_format == INTEL_OUTPUT_FORMAT_YCBCR444 && + intel_dp->dfp.ycbcr_444_to_420); +} + +static int intel_dp_hdmi_compute_bpc(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + int bpc, bool respect_downstream_limits) +{ + bool ycbcr420_output = intel_dp_is_ycbcr420(intel_dp, crtc_state); + int clock = crtc_state->hw.adjusted_mode.crtc_clock; + + /* + * Current bpc could already be below 8bpc due to + * FDI bandwidth constraints or other limits. + * HDMI minimum is 8bpc however. + */ + bpc = max(bpc, 8); + + /* + * We will never exceed downstream TMDS clock limits while + * attempting deep color. If the user insists on forcing an + * out of spec mode they will have to be satisfied with 8bpc. + */ + if (!respect_downstream_limits) + bpc = 8; + + for (; bpc >= 8; bpc -= 2) { + if (intel_hdmi_bpc_possible(crtc_state, bpc, + intel_dp->has_hdmi_sink, ycbcr420_output) && + intel_dp_tmds_clock_valid(intel_dp, clock, bpc, ycbcr420_output, + respect_downstream_limits) == MODE_OK) + return bpc; + } + + return -EINVAL; +} + +static int intel_dp_max_bpp(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + bool respect_downstream_limits) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + struct intel_connector *intel_connector = intel_dp->attached_connector; + int bpp, bpc; + + bpc = crtc_state->pipe_bpp / 3; + + if (intel_dp->dfp.max_bpc) + bpc = min_t(int, bpc, intel_dp->dfp.max_bpc); + + if (intel_dp->dfp.min_tmds_clock) { + int max_hdmi_bpc; + + max_hdmi_bpc = intel_dp_hdmi_compute_bpc(intel_dp, crtc_state, bpc, + respect_downstream_limits); + if (max_hdmi_bpc < 0) + return 0; + + bpc = min(bpc, max_hdmi_bpc); + } + + bpp = bpc * 3; + if (intel_dp_is_edp(intel_dp)) { + /* Get bpp from vbt only for panels that dont have bpp in edid */ + if (intel_connector->base.display_info.bpc == 0 && + intel_connector->panel.vbt.edp.bpp && + intel_connector->panel.vbt.edp.bpp < bpp) { + drm_dbg_kms(&dev_priv->drm, + "clamping bpp for eDP panel to BIOS-provided %i\n", + intel_connector->panel.vbt.edp.bpp); + bpp = intel_connector->panel.vbt.edp.bpp; + } + } + + return bpp; +} + +/* Adjust link config limits based on compliance test requests. */ +void +intel_dp_adjust_compliance_config(struct intel_dp *intel_dp, + struct intel_crtc_state *pipe_config, + struct link_config_limits *limits) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + + /* For DP Compliance we override the computed bpp for the pipe */ + if (intel_dp->compliance.test_data.bpc != 0) { + int bpp = 3 * intel_dp->compliance.test_data.bpc; + + limits->min_bpp = limits->max_bpp = bpp; + pipe_config->dither_force_disable = bpp == 6 * 3; + + drm_dbg_kms(&i915->drm, "Setting pipe_bpp to %d\n", bpp); + } + + /* Use values requested by Compliance Test Request */ + if (intel_dp->compliance.test_type == DP_TEST_LINK_TRAINING) { + int index; + + /* Validate the compliance test data since max values + * might have changed due to link train fallback. + */ + if (intel_dp_link_params_valid(intel_dp, intel_dp->compliance.test_link_rate, + intel_dp->compliance.test_lane_count)) { + index = intel_dp_rate_index(intel_dp->common_rates, + intel_dp->num_common_rates, + intel_dp->compliance.test_link_rate); + if (index >= 0) + limits->min_rate = limits->max_rate = + intel_dp->compliance.test_link_rate; + limits->min_lane_count = limits->max_lane_count = + intel_dp->compliance.test_lane_count; + } + } +} + +static bool has_seamless_m_n(struct intel_connector *connector) +{ + struct drm_i915_private *i915 = to_i915(connector->base.dev); + + /* + * Seamless M/N reprogramming only implemented + * for BDW+ double buffered M/N registers so far. + */ + return HAS_DOUBLE_BUFFERED_M_N(i915) && + intel_panel_drrs_type(connector) == DRRS_TYPE_SEAMLESS; +} + +static int intel_dp_mode_clock(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + const struct drm_display_mode *adjusted_mode = &crtc_state->hw.adjusted_mode; + + /* FIXME a bit of a mess wrt clock vs. crtc_clock */ + if (has_seamless_m_n(connector)) + return intel_panel_highest_mode(connector, adjusted_mode)->clock; + else + return adjusted_mode->crtc_clock; +} + +/* Optimize link config in order: max bpp, min clock, min lanes */ +static int +intel_dp_compute_link_config_wide(struct intel_dp *intel_dp, + struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state, + const struct link_config_limits *limits) +{ + int bpp, i, lane_count, clock = intel_dp_mode_clock(pipe_config, conn_state); + int mode_rate, link_rate, link_avail; + + for (bpp = limits->max_bpp; bpp >= limits->min_bpp; bpp -= 2 * 3) { + int output_bpp = intel_dp_output_bpp(pipe_config->output_format, bpp); + + mode_rate = intel_dp_link_required(clock, output_bpp); + + for (i = 0; i < intel_dp->num_common_rates; i++) { + link_rate = intel_dp_common_rate(intel_dp, i); + if (link_rate < limits->min_rate || + link_rate > limits->max_rate) + continue; + + for (lane_count = limits->min_lane_count; + lane_count <= limits->max_lane_count; + lane_count <<= 1) { + link_avail = intel_dp_max_data_rate(link_rate, + lane_count); + + if (mode_rate <= link_avail) { + pipe_config->lane_count = lane_count; + pipe_config->pipe_bpp = bpp; + pipe_config->port_clock = link_rate; + + return 0; + } + } + } + } + + return -EINVAL; +} + +static int intel_dp_dsc_compute_bpp(struct intel_dp *intel_dp, u8 max_req_bpc) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + int i, num_bpc; + u8 dsc_bpc[3] = {0}; + u8 dsc_max_bpc; + + /* Max DSC Input BPC for ICL is 10 and for TGL+ is 12 */ + if (DISPLAY_VER(i915) >= 12) + dsc_max_bpc = min_t(u8, 12, max_req_bpc); + else + dsc_max_bpc = min_t(u8, 10, max_req_bpc); + + num_bpc = drm_dp_dsc_sink_supported_input_bpcs(intel_dp->dsc_dpcd, + dsc_bpc); + for (i = 0; i < num_bpc; i++) { + if (dsc_max_bpc >= dsc_bpc[i]) + return dsc_bpc[i] * 3; + } + + return 0; +} + +static int intel_dp_source_dsc_version_minor(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + + return DISPLAY_VER(i915) >= 14 ? 2 : 1; +} + +static int intel_dp_sink_dsc_version_minor(struct intel_dp *intel_dp) +{ + return (intel_dp->dsc_dpcd[DP_DSC_REV - DP_DSC_SUPPORT] & DP_DSC_MINOR_MASK) >> + DP_DSC_MINOR_SHIFT; +} + +static int intel_dp_dsc_compute_params(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + struct drm_dsc_config *vdsc_cfg = &crtc_state->dsc.config; + u8 line_buf_depth; + int ret; + + /* + * RC_MODEL_SIZE is currently a constant across all configurations. + * + * FIXME: Look into using sink defined DPCD DP_DSC_RC_BUF_BLK_SIZE and + * DP_DSC_RC_BUF_SIZE for this. + */ + vdsc_cfg->rc_model_size = DSC_RC_MODEL_SIZE_CONST; + vdsc_cfg->pic_height = crtc_state->hw.adjusted_mode.crtc_vdisplay; + + /* + * Slice Height of 8 works for all currently available panels. So start + * with that if pic_height is an integral multiple of 8. Eventually add + * logic to try multiple slice heights. + */ + if (vdsc_cfg->pic_height % 8 == 0) + vdsc_cfg->slice_height = 8; + else if (vdsc_cfg->pic_height % 4 == 0) + vdsc_cfg->slice_height = 4; + else + vdsc_cfg->slice_height = 2; + + ret = intel_dsc_compute_params(crtc_state); + if (ret) + return ret; + + vdsc_cfg->dsc_version_major = + (intel_dp->dsc_dpcd[DP_DSC_REV - DP_DSC_SUPPORT] & + DP_DSC_MAJOR_MASK) >> DP_DSC_MAJOR_SHIFT; + vdsc_cfg->dsc_version_minor = + min(intel_dp_source_dsc_version_minor(intel_dp), + intel_dp_sink_dsc_version_minor(intel_dp)); + + vdsc_cfg->convert_rgb = intel_dp->dsc_dpcd[DP_DSC_DEC_COLOR_FORMAT_CAP - DP_DSC_SUPPORT] & + DP_DSC_RGB; + + line_buf_depth = drm_dp_dsc_sink_line_buf_depth(intel_dp->dsc_dpcd); + if (!line_buf_depth) { + drm_dbg_kms(&i915->drm, + "DSC Sink Line Buffer Depth invalid\n"); + return -EINVAL; + } + + if (vdsc_cfg->dsc_version_minor == 2) + vdsc_cfg->line_buf_depth = (line_buf_depth == DSC_1_2_MAX_LINEBUF_DEPTH_BITS) ? + DSC_1_2_MAX_LINEBUF_DEPTH_VAL : line_buf_depth; + else + vdsc_cfg->line_buf_depth = (line_buf_depth > DSC_1_1_MAX_LINEBUF_DEPTH_BITS) ? + DSC_1_1_MAX_LINEBUF_DEPTH_BITS : line_buf_depth; + + vdsc_cfg->block_pred_enable = + intel_dp->dsc_dpcd[DP_DSC_BLK_PREDICTION_SUPPORT - DP_DSC_SUPPORT] & + DP_DSC_BLK_PREDICTION_IS_SUPPORTED; + + return drm_dsc_compute_rc_parameters(vdsc_cfg); +} + +static int intel_dp_dsc_compute_config(struct intel_dp *intel_dp, + struct intel_crtc_state *pipe_config, + struct drm_connector_state *conn_state, + struct link_config_limits *limits) +{ + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev); + const struct drm_display_mode *adjusted_mode = + &pipe_config->hw.adjusted_mode; + int pipe_bpp; + int ret; + + pipe_config->fec_enable = !intel_dp_is_edp(intel_dp) && + intel_dp_supports_fec(intel_dp, pipe_config); + + if (!intel_dp_supports_dsc(intel_dp, pipe_config)) + return -EINVAL; + + pipe_bpp = intel_dp_dsc_compute_bpp(intel_dp, conn_state->max_requested_bpc); + + if (intel_dp->force_dsc_bpc) { + pipe_bpp = intel_dp->force_dsc_bpc * 3; + drm_dbg_kms(&dev_priv->drm, "Input DSC BPP forced to %d", pipe_bpp); + } + + /* Min Input BPC for ICL+ is 8 */ + if (pipe_bpp < 8 * 3) { + drm_dbg_kms(&dev_priv->drm, + "No DSC support for less than 8bpc\n"); + return -EINVAL; + } + + /* + * For now enable DSC for max bpp, max link rate, max lane count. + * Optimize this later for the minimum possible link rate/lane count + * with DSC enabled for the requested mode. + */ + pipe_config->pipe_bpp = pipe_bpp; + pipe_config->port_clock = limits->max_rate; + pipe_config->lane_count = limits->max_lane_count; + + if (intel_dp_is_edp(intel_dp)) { + pipe_config->dsc.compressed_bpp = + min_t(u16, drm_edp_dsc_sink_output_bpp(intel_dp->dsc_dpcd) >> 4, + pipe_config->pipe_bpp); + pipe_config->dsc.slice_count = + drm_dp_dsc_sink_max_slice_count(intel_dp->dsc_dpcd, + true); + if (!pipe_config->dsc.slice_count) { + drm_dbg_kms(&dev_priv->drm, "Unsupported Slice Count %d\n", + pipe_config->dsc.slice_count); + return -EINVAL; + } + } else { + u16 dsc_max_output_bpp; + u8 dsc_dp_slice_count; + + dsc_max_output_bpp = + intel_dp_dsc_get_output_bpp(dev_priv, + pipe_config->port_clock, + pipe_config->lane_count, + adjusted_mode->crtc_clock, + adjusted_mode->crtc_hdisplay, + pipe_config->bigjoiner_pipes, + pipe_bpp); + dsc_dp_slice_count = + intel_dp_dsc_get_slice_count(intel_dp, + adjusted_mode->crtc_clock, + adjusted_mode->crtc_hdisplay, + pipe_config->bigjoiner_pipes); + if (!dsc_max_output_bpp || !dsc_dp_slice_count) { + drm_dbg_kms(&dev_priv->drm, + "Compressed BPP/Slice Count not supported\n"); + return -EINVAL; + } + pipe_config->dsc.compressed_bpp = min_t(u16, + dsc_max_output_bpp >> 4, + pipe_config->pipe_bpp); + pipe_config->dsc.slice_count = dsc_dp_slice_count; + } + + /* + * VDSC engine operates at 1 Pixel per clock, so if peak pixel rate + * is greater than the maximum Cdclock and if slice count is even + * then we need to use 2 VDSC instances. + */ + if (adjusted_mode->crtc_clock > dev_priv->display.cdclk.max_cdclk_freq || + pipe_config->bigjoiner_pipes) { + if (pipe_config->dsc.slice_count < 2) { + drm_dbg_kms(&dev_priv->drm, + "Cannot split stream to use 2 VDSC instances\n"); + return -EINVAL; + } + + pipe_config->dsc.dsc_split = true; + } + + ret = intel_dp_dsc_compute_params(&dig_port->base, pipe_config); + if (ret < 0) { + drm_dbg_kms(&dev_priv->drm, + "Cannot compute valid DSC parameters for Input Bpp = %d " + "Compressed BPP = %d\n", + pipe_config->pipe_bpp, + pipe_config->dsc.compressed_bpp); + return ret; + } + + pipe_config->dsc.compression_enable = true; + drm_dbg_kms(&dev_priv->drm, "DP DSC computed with Input Bpp = %d " + "Compressed Bpp = %d Slice Count = %d\n", + pipe_config->pipe_bpp, + pipe_config->dsc.compressed_bpp, + pipe_config->dsc.slice_count); + + return 0; +} + +static int +intel_dp_compute_link_config(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config, + struct drm_connector_state *conn_state, + bool respect_downstream_limits) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + struct intel_crtc *crtc = to_intel_crtc(pipe_config->uapi.crtc); + const struct drm_display_mode *adjusted_mode = + &pipe_config->hw.adjusted_mode; + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + struct link_config_limits limits; + bool joiner_needs_dsc = false; + int ret; + + limits.min_rate = intel_dp_common_rate(intel_dp, 0); + limits.max_rate = intel_dp_max_link_rate(intel_dp); + + limits.min_lane_count = 1; + limits.max_lane_count = intel_dp_max_lane_count(intel_dp); + + limits.min_bpp = intel_dp_min_bpp(pipe_config->output_format); + limits.max_bpp = intel_dp_max_bpp(intel_dp, pipe_config, respect_downstream_limits); + + if (intel_dp->use_max_params) { + /* + * Use the maximum clock and number of lanes the eDP panel + * advertizes being capable of in case the initial fast + * optimal params failed us. The panels are generally + * designed to support only a single clock and lane + * configuration, and typically on older panels these + * values correspond to the native resolution of the panel. + */ + limits.min_lane_count = limits.max_lane_count; + limits.min_rate = limits.max_rate; + } + + intel_dp_adjust_compliance_config(intel_dp, pipe_config, &limits); + + drm_dbg_kms(&i915->drm, "DP link computation with max lane count %i " + "max rate %d max bpp %d pixel clock %iKHz\n", + limits.max_lane_count, limits.max_rate, + limits.max_bpp, adjusted_mode->crtc_clock); + + if (intel_dp_need_bigjoiner(intel_dp, adjusted_mode->crtc_hdisplay, + adjusted_mode->crtc_clock)) + pipe_config->bigjoiner_pipes = GENMASK(crtc->pipe + 1, crtc->pipe); + + /* + * Pipe joiner needs compression up to display 12 due to bandwidth + * limitation. DG2 onwards pipe joiner can be enabled without + * compression. + */ + joiner_needs_dsc = DISPLAY_VER(i915) < 13 && pipe_config->bigjoiner_pipes; + + /* + * Optimize for slow and wide for everything, because there are some + * eDP 1.3 and 1.4 panels don't work well with fast and narrow. + */ + ret = intel_dp_compute_link_config_wide(intel_dp, pipe_config, conn_state, &limits); + + if (ret || joiner_needs_dsc || intel_dp->force_dsc_en) { + drm_dbg_kms(&i915->drm, "Try DSC (fallback=%s, joiner=%s, force=%s)\n", + str_yes_no(ret), str_yes_no(joiner_needs_dsc), + str_yes_no(intel_dp->force_dsc_en)); + ret = intel_dp_dsc_compute_config(intel_dp, pipe_config, + conn_state, &limits); + if (ret < 0) + return ret; + } + + if (pipe_config->dsc.compression_enable) { + drm_dbg_kms(&i915->drm, + "DP lane count %d clock %d Input bpp %d Compressed bpp %d\n", + pipe_config->lane_count, pipe_config->port_clock, + pipe_config->pipe_bpp, + pipe_config->dsc.compressed_bpp); + + drm_dbg_kms(&i915->drm, + "DP link rate required %i available %i\n", + intel_dp_link_required(adjusted_mode->crtc_clock, + pipe_config->dsc.compressed_bpp), + intel_dp_max_data_rate(pipe_config->port_clock, + pipe_config->lane_count)); + } else { + drm_dbg_kms(&i915->drm, "DP lane count %d clock %d bpp %d\n", + pipe_config->lane_count, pipe_config->port_clock, + pipe_config->pipe_bpp); + + drm_dbg_kms(&i915->drm, + "DP link rate required %i available %i\n", + intel_dp_link_required(adjusted_mode->crtc_clock, + pipe_config->pipe_bpp), + intel_dp_max_data_rate(pipe_config->port_clock, + pipe_config->lane_count)); + } + return 0; +} + +bool intel_dp_limited_color_range(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + const struct intel_digital_connector_state *intel_conn_state = + to_intel_digital_connector_state(conn_state); + const struct drm_display_mode *adjusted_mode = + &crtc_state->hw.adjusted_mode; + + /* + * Our YCbCr output is always limited range. + * crtc_state->limited_color_range only applies to RGB, + * and it must never be set for YCbCr or we risk setting + * some conflicting bits in PIPECONF which will mess up + * the colors on the monitor. + */ + if (crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB) + return false; + + if (intel_conn_state->broadcast_rgb == INTEL_BROADCAST_RGB_AUTO) { + /* + * See: + * CEA-861-E - 5.1 Default Encoding Parameters + * VESA DisplayPort Ver.1.2a - 5.1.1.1 Video Colorimetry + */ + return crtc_state->pipe_bpp != 18 && + drm_default_rgb_quant_range(adjusted_mode) == + HDMI_QUANTIZATION_RANGE_LIMITED; + } else { + return intel_conn_state->broadcast_rgb == + INTEL_BROADCAST_RGB_LIMITED; + } +} + +static bool intel_dp_port_has_audio(struct drm_i915_private *dev_priv, + enum port port) +{ + if (IS_G4X(dev_priv)) + return false; + if (DISPLAY_VER(dev_priv) < 12 && port == PORT_A) + return false; + + return true; +} + +static void intel_dp_compute_vsc_colorimetry(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state, + struct drm_dp_vsc_sdp *vsc) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + + /* + * Prepare VSC Header for SU as per DP 1.4 spec, Table 2-118 + * VSC SDP supporting 3D stereo, PSR2, and Pixel Encoding/ + * Colorimetry Format indication. + */ + vsc->revision = 0x5; + vsc->length = 0x13; + + /* DP 1.4a spec, Table 2-120 */ + switch (crtc_state->output_format) { + case INTEL_OUTPUT_FORMAT_YCBCR444: + vsc->pixelformat = DP_PIXELFORMAT_YUV444; + break; + case INTEL_OUTPUT_FORMAT_YCBCR420: + vsc->pixelformat = DP_PIXELFORMAT_YUV420; + break; + case INTEL_OUTPUT_FORMAT_RGB: + default: + vsc->pixelformat = DP_PIXELFORMAT_RGB; + } + + switch (conn_state->colorspace) { + case DRM_MODE_COLORIMETRY_BT709_YCC: + vsc->colorimetry = DP_COLORIMETRY_BT709_YCC; + break; + case DRM_MODE_COLORIMETRY_XVYCC_601: + vsc->colorimetry = DP_COLORIMETRY_XVYCC_601; + break; + case DRM_MODE_COLORIMETRY_XVYCC_709: + vsc->colorimetry = DP_COLORIMETRY_XVYCC_709; + break; + case DRM_MODE_COLORIMETRY_SYCC_601: + vsc->colorimetry = DP_COLORIMETRY_SYCC_601; + break; + case DRM_MODE_COLORIMETRY_OPYCC_601: + vsc->colorimetry = DP_COLORIMETRY_OPYCC_601; + break; + case DRM_MODE_COLORIMETRY_BT2020_CYCC: + vsc->colorimetry = DP_COLORIMETRY_BT2020_CYCC; + break; + case DRM_MODE_COLORIMETRY_BT2020_RGB: + vsc->colorimetry = DP_COLORIMETRY_BT2020_RGB; + break; + case DRM_MODE_COLORIMETRY_BT2020_YCC: + vsc->colorimetry = DP_COLORIMETRY_BT2020_YCC; + break; + case DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65: + case DRM_MODE_COLORIMETRY_DCI_P3_RGB_THEATER: + vsc->colorimetry = DP_COLORIMETRY_DCI_P3_RGB; + break; + default: + /* + * RGB->YCBCR color conversion uses the BT.709 + * color space. + */ + if (crtc_state->output_format == INTEL_OUTPUT_FORMAT_YCBCR420) + vsc->colorimetry = DP_COLORIMETRY_BT709_YCC; + else + vsc->colorimetry = DP_COLORIMETRY_DEFAULT; + break; + } + + vsc->bpc = crtc_state->pipe_bpp / 3; + + /* only RGB pixelformat supports 6 bpc */ + drm_WARN_ON(&dev_priv->drm, + vsc->bpc == 6 && vsc->pixelformat != DP_PIXELFORMAT_RGB); + + /* all YCbCr are always limited range */ + vsc->dynamic_range = DP_DYNAMIC_RANGE_CTA; + vsc->content_type = DP_CONTENT_TYPE_NOT_DEFINED; +} + +static void intel_dp_compute_vsc_sdp(struct intel_dp *intel_dp, + struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct drm_dp_vsc_sdp *vsc = &crtc_state->infoframes.vsc; + + /* When a crtc state has PSR, VSC SDP will be handled by PSR routine */ + if (crtc_state->has_psr) + return; + + if (!intel_dp_needs_vsc_sdp(crtc_state, conn_state)) + return; + + crtc_state->infoframes.enable |= intel_hdmi_infoframe_enable(DP_SDP_VSC); + vsc->sdp_type = DP_SDP_VSC; + intel_dp_compute_vsc_colorimetry(crtc_state, conn_state, + &crtc_state->infoframes.vsc); +} + +void intel_dp_compute_psr_vsc_sdp(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state, + struct drm_dp_vsc_sdp *vsc) +{ + vsc->sdp_type = DP_SDP_VSC; + + if (crtc_state->has_psr2) { + if (intel_dp->psr.colorimetry_support && + intel_dp_needs_vsc_sdp(crtc_state, conn_state)) { + /* [PSR2, +Colorimetry] */ + intel_dp_compute_vsc_colorimetry(crtc_state, conn_state, + vsc); + } else { + /* + * [PSR2, -Colorimetry] + * Prepare VSC Header for SU as per eDP 1.4 spec, Table 6-11 + * 3D stereo + PSR/PSR2 + Y-coordinate. + */ + vsc->revision = 0x4; + vsc->length = 0xe; + } + } else { + /* + * [PSR1] + * Prepare VSC Header for SU as per DP 1.4 spec, Table 2-118 + * VSC SDP supporting 3D stereo + PSR (applies to eDP v1.3 or + * higher). + */ + vsc->revision = 0x2; + vsc->length = 0x8; + } +} + +static void +intel_dp_compute_hdr_metadata_infoframe_sdp(struct intel_dp *intel_dp, + struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + int ret; + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + struct hdmi_drm_infoframe *drm_infoframe = &crtc_state->infoframes.drm.drm; + + if (!conn_state->hdr_output_metadata) + return; + + ret = drm_hdmi_infoframe_set_hdr_metadata(drm_infoframe, conn_state); + + if (ret) { + drm_dbg_kms(&dev_priv->drm, "couldn't set HDR metadata in infoframe\n"); + return; + } + + crtc_state->infoframes.enable |= + intel_hdmi_infoframe_enable(HDMI_PACKET_TYPE_GAMUT_METADATA); +} + +static bool cpu_transcoder_has_drrs(struct drm_i915_private *i915, + enum transcoder cpu_transcoder) +{ + if (HAS_DOUBLE_BUFFERED_M_N(i915)) + return true; + + return intel_cpu_transcoder_has_m2_n2(i915, cpu_transcoder); +} + +static bool can_enable_drrs(struct intel_connector *connector, + const struct intel_crtc_state *pipe_config, + const struct drm_display_mode *downclock_mode) +{ + struct drm_i915_private *i915 = to_i915(connector->base.dev); + + if (pipe_config->vrr.enable) + return false; + + /* + * DRRS and PSR can't be enable together, so giving preference to PSR + * as it allows more power-savings by complete shutting down display, + * so to guarantee this, intel_drrs_compute_config() must be called + * after intel_psr_compute_config(). + */ + if (pipe_config->has_psr) + return false; + + /* FIXME missing FDI M2/N2 etc. */ + if (pipe_config->has_pch_encoder) + return false; + + if (!cpu_transcoder_has_drrs(i915, pipe_config->cpu_transcoder)) + return false; + + return downclock_mode && + intel_panel_drrs_type(connector) == DRRS_TYPE_SEAMLESS; +} + +static void +intel_dp_drrs_compute_config(struct intel_connector *connector, + struct intel_crtc_state *pipe_config, + int output_bpp) +{ + struct drm_i915_private *i915 = to_i915(connector->base.dev); + const struct drm_display_mode *downclock_mode = + intel_panel_downclock_mode(connector, &pipe_config->hw.adjusted_mode); + int pixel_clock; + + if (has_seamless_m_n(connector)) + pipe_config->seamless_m_n = true; + + if (!can_enable_drrs(connector, pipe_config, downclock_mode)) { + if (intel_cpu_transcoder_has_m2_n2(i915, pipe_config->cpu_transcoder)) + intel_zero_m_n(&pipe_config->dp_m2_n2); + return; + } + + if (IS_IRONLAKE(i915) || IS_SANDYBRIDGE(i915) || IS_IVYBRIDGE(i915)) + pipe_config->msa_timing_delay = connector->panel.vbt.edp.drrs_msa_timing_delay; + + pipe_config->has_drrs = true; + + pixel_clock = downclock_mode->clock; + if (pipe_config->splitter.enable) + pixel_clock /= pipe_config->splitter.link_count; + + intel_link_compute_m_n(output_bpp, pipe_config->lane_count, pixel_clock, + pipe_config->port_clock, &pipe_config->dp_m2_n2, + pipe_config->fec_enable); + + /* FIXME: abstract this better */ + if (pipe_config->splitter.enable) + pipe_config->dp_m2_n2.data_m *= pipe_config->splitter.link_count; +} + +static bool intel_dp_has_audio(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + const struct intel_digital_connector_state *intel_conn_state = + to_intel_digital_connector_state(conn_state); + + if (!intel_dp_port_has_audio(i915, encoder->port)) + return false; + + if (intel_conn_state->force_audio == HDMI_AUDIO_AUTO) + return intel_dp->has_audio; + else + return intel_conn_state->force_audio == HDMI_AUDIO_ON; +} + +static int +intel_dp_compute_output_format(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + bool respect_downstream_limits) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + struct intel_connector *connector = intel_dp->attached_connector; + const struct drm_display_info *info = &connector->base.display_info; + const struct drm_display_mode *adjusted_mode = &crtc_state->hw.adjusted_mode; + bool ycbcr_420_only; + int ret; + + ycbcr_420_only = drm_mode_is_420_only(info, adjusted_mode); + + crtc_state->output_format = intel_dp_output_format(connector, ycbcr_420_only); + + if (ycbcr_420_only && !intel_dp_is_ycbcr420(intel_dp, crtc_state)) { + drm_dbg_kms(&i915->drm, + "YCbCr 4:2:0 mode but YCbCr 4:2:0 output not possible. Falling back to RGB.\n"); + crtc_state->output_format = INTEL_OUTPUT_FORMAT_RGB; + } + + ret = intel_dp_compute_link_config(encoder, crtc_state, conn_state, + respect_downstream_limits); + if (ret) { + if (intel_dp_is_ycbcr420(intel_dp, crtc_state) || + !connector->base.ycbcr_420_allowed || + !drm_mode_is_420_also(info, adjusted_mode)) + return ret; + + crtc_state->output_format = intel_dp_output_format(connector, true); + ret = intel_dp_compute_link_config(encoder, crtc_state, conn_state, + respect_downstream_limits); + } + + return ret; +} + +int +intel_dp_compute_config(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config, + struct drm_connector_state *conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct drm_display_mode *adjusted_mode = &pipe_config->hw.adjusted_mode; + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + const struct drm_display_mode *fixed_mode; + struct intel_connector *connector = intel_dp->attached_connector; + int ret = 0, output_bpp; + + if (HAS_PCH_SPLIT(dev_priv) && !HAS_DDI(dev_priv) && encoder->port != PORT_A) + pipe_config->has_pch_encoder = true; + + pipe_config->has_audio = intel_dp_has_audio(encoder, pipe_config, conn_state); + + fixed_mode = intel_panel_fixed_mode(connector, adjusted_mode); + if (intel_dp_is_edp(intel_dp) && fixed_mode) { + ret = intel_panel_compute_config(connector, adjusted_mode); + if (ret) + return ret; + } + + if (adjusted_mode->flags & DRM_MODE_FLAG_DBLSCAN) + return -EINVAL; + + if (HAS_GMCH(dev_priv) && + adjusted_mode->flags & DRM_MODE_FLAG_INTERLACE) + return -EINVAL; + + if (adjusted_mode->flags & DRM_MODE_FLAG_DBLCLK) + return -EINVAL; + + if (intel_dp_hdisplay_bad(dev_priv, adjusted_mode->crtc_hdisplay)) + return -EINVAL; + + /* + * Try to respect downstream TMDS clock limits first, if + * that fails assume the user might know something we don't. + */ + ret = intel_dp_compute_output_format(encoder, pipe_config, conn_state, true); + if (ret) + ret = intel_dp_compute_output_format(encoder, pipe_config, conn_state, false); + if (ret) + return ret; + + if ((intel_dp_is_edp(intel_dp) && fixed_mode) || + pipe_config->output_format == INTEL_OUTPUT_FORMAT_YCBCR420) { + ret = intel_panel_fitting(pipe_config, conn_state); + if (ret) + return ret; + } + + pipe_config->limited_color_range = + intel_dp_limited_color_range(pipe_config, conn_state); + + if (pipe_config->dsc.compression_enable) + output_bpp = pipe_config->dsc.compressed_bpp; + else + output_bpp = intel_dp_output_bpp(pipe_config->output_format, + pipe_config->pipe_bpp); + + if (intel_dp->mso_link_count) { + int n = intel_dp->mso_link_count; + int overlap = intel_dp->mso_pixel_overlap; + + pipe_config->splitter.enable = true; + pipe_config->splitter.link_count = n; + pipe_config->splitter.pixel_overlap = overlap; + + drm_dbg_kms(&dev_priv->drm, "MSO link count %d, pixel overlap %d\n", + n, overlap); + + adjusted_mode->crtc_hdisplay = adjusted_mode->crtc_hdisplay / n + overlap; + adjusted_mode->crtc_hblank_start = adjusted_mode->crtc_hblank_start / n + overlap; + adjusted_mode->crtc_hblank_end = adjusted_mode->crtc_hblank_end / n + overlap; + adjusted_mode->crtc_hsync_start = adjusted_mode->crtc_hsync_start / n + overlap; + adjusted_mode->crtc_hsync_end = adjusted_mode->crtc_hsync_end / n + overlap; + adjusted_mode->crtc_htotal = adjusted_mode->crtc_htotal / n + overlap; + adjusted_mode->crtc_clock /= n; + } + + intel_link_compute_m_n(output_bpp, + pipe_config->lane_count, + adjusted_mode->crtc_clock, + pipe_config->port_clock, + &pipe_config->dp_m_n, + pipe_config->fec_enable); + + /* FIXME: abstract this better */ + if (pipe_config->splitter.enable) + pipe_config->dp_m_n.data_m *= pipe_config->splitter.link_count; + + if (!HAS_DDI(dev_priv)) + g4x_dp_set_clock(encoder, pipe_config); + + intel_vrr_compute_config(pipe_config, conn_state); + intel_psr_compute_config(intel_dp, pipe_config, conn_state); + intel_dp_drrs_compute_config(connector, pipe_config, output_bpp); + intel_dp_compute_vsc_sdp(intel_dp, pipe_config, conn_state); + intel_dp_compute_hdr_metadata_infoframe_sdp(intel_dp, pipe_config, conn_state); + + return 0; +} + +void intel_dp_set_link_params(struct intel_dp *intel_dp, + int link_rate, int lane_count) +{ + memset(intel_dp->train_set, 0, sizeof(intel_dp->train_set)); + intel_dp->link_trained = false; + intel_dp->link_rate = link_rate; + intel_dp->lane_count = lane_count; +} + +static void intel_dp_reset_max_link_params(struct intel_dp *intel_dp) +{ + intel_dp->max_link_lane_count = intel_dp_max_common_lane_count(intel_dp); + intel_dp->max_link_rate = intel_dp_max_common_rate(intel_dp); +} + +/* Enable backlight PWM and backlight PP control. */ +void intel_edp_backlight_on(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct intel_dp *intel_dp = enc_to_intel_dp(to_intel_encoder(conn_state->best_encoder)); + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + + if (!intel_dp_is_edp(intel_dp)) + return; + + drm_dbg_kms(&i915->drm, "\n"); + + intel_backlight_enable(crtc_state, conn_state); + intel_pps_backlight_on(intel_dp); +} + +/* Disable backlight PP control and backlight PWM. */ +void intel_edp_backlight_off(const struct drm_connector_state *old_conn_state) +{ + struct intel_dp *intel_dp = enc_to_intel_dp(to_intel_encoder(old_conn_state->best_encoder)); + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + + if (!intel_dp_is_edp(intel_dp)) + return; + + drm_dbg_kms(&i915->drm, "\n"); + + intel_pps_backlight_off(intel_dp); + intel_backlight_disable(old_conn_state); +} + +static bool downstream_hpd_needs_d0(struct intel_dp *intel_dp) +{ + /* + * DPCD 1.2+ should support BRANCH_DEVICE_CTRL, and thus + * be capable of signalling downstream hpd with a long pulse. + * Whether or not that means D3 is safe to use is not clear, + * but let's assume so until proven otherwise. + * + * FIXME should really check all downstream ports... + */ + return intel_dp->dpcd[DP_DPCD_REV] == 0x11 && + drm_dp_is_branch(intel_dp->dpcd) && + intel_dp->downstream_ports[0] & DP_DS_PORT_HPD; +} + +void intel_dp_sink_set_decompression_state(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + bool enable) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + int ret; + + if (!crtc_state->dsc.compression_enable) + return; + + ret = drm_dp_dpcd_writeb(&intel_dp->aux, DP_DSC_ENABLE, + enable ? DP_DECOMPRESSION_EN : 0); + if (ret < 0) + drm_dbg_kms(&i915->drm, + "Failed to %s sink decompression state\n", + str_enable_disable(enable)); +} + +static void +intel_edp_init_source_oui(struct intel_dp *intel_dp, bool careful) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + u8 oui[] = { 0x00, 0xaa, 0x01 }; + u8 buf[3] = { 0 }; + + /* + * During driver init, we want to be careful and avoid changing the source OUI if it's + * already set to what we want, so as to avoid clearing any state by accident + */ + if (careful) { + if (drm_dp_dpcd_read(&intel_dp->aux, DP_SOURCE_OUI, buf, sizeof(buf)) < 0) + drm_err(&i915->drm, "Failed to read source OUI\n"); + + if (memcmp(oui, buf, sizeof(oui)) == 0) + return; + } + + if (drm_dp_dpcd_write(&intel_dp->aux, DP_SOURCE_OUI, oui, sizeof(oui)) < 0) + drm_err(&i915->drm, "Failed to write source OUI\n"); + + intel_dp->last_oui_write = jiffies; +} + +void intel_dp_wait_source_oui(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + + drm_dbg_kms(&i915->drm, "Performing OUI wait\n"); + wait_remaining_ms_from_jiffies(intel_dp->last_oui_write, 30); +} + +/* If the device supports it, try to set the power state appropriately */ +void intel_dp_set_power(struct intel_dp *intel_dp, u8 mode) +{ + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + int ret, i; + + /* Should have a valid DPCD by this point */ + if (intel_dp->dpcd[DP_DPCD_REV] < 0x11) + return; + + if (mode != DP_SET_POWER_D0) { + if (downstream_hpd_needs_d0(intel_dp)) + return; + + ret = drm_dp_dpcd_writeb(&intel_dp->aux, DP_SET_POWER, mode); + } else { + struct intel_lspcon *lspcon = dp_to_lspcon(intel_dp); + + lspcon_resume(dp_to_dig_port(intel_dp)); + + /* Write the source OUI as early as possible */ + if (intel_dp_is_edp(intel_dp)) + intel_edp_init_source_oui(intel_dp, false); + + /* + * When turning on, we need to retry for 1ms to give the sink + * time to wake up. + */ + for (i = 0; i < 3; i++) { + ret = drm_dp_dpcd_writeb(&intel_dp->aux, DP_SET_POWER, mode); + if (ret == 1) + break; + msleep(1); + } + + if (ret == 1 && lspcon->active) + lspcon_wait_pcon_mode(lspcon); + } + + if (ret != 1) + drm_dbg_kms(&i915->drm, "[ENCODER:%d:%s] Set power to %s failed\n", + encoder->base.base.id, encoder->base.name, + mode == DP_SET_POWER_D0 ? "D0" : "D3"); +} + +static bool +intel_dp_get_dpcd(struct intel_dp *intel_dp); + +/** + * intel_dp_sync_state - sync the encoder state during init/resume + * @encoder: intel encoder to sync + * @crtc_state: state for the CRTC connected to the encoder + * + * Sync any state stored in the encoder wrt. HW state during driver init + * and system resume. + */ +void intel_dp_sync_state(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + + if (!crtc_state) + return; + + /* + * Don't clobber DPCD if it's been already read out during output + * setup (eDP) or detect. + */ + if (intel_dp->dpcd[DP_DPCD_REV] == 0) + intel_dp_get_dpcd(intel_dp); + + intel_dp_reset_max_link_params(intel_dp); +} + +bool intel_dp_initial_fastset_check(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + + /* + * If BIOS has set an unsupported or non-standard link rate for some + * reason force an encoder recompute and full modeset. + */ + if (intel_dp_rate_index(intel_dp->source_rates, intel_dp->num_source_rates, + crtc_state->port_clock) < 0) { + drm_dbg_kms(&i915->drm, "Forcing full modeset due to unsupported link rate\n"); + crtc_state->uapi.connectors_changed = true; + return false; + } + + /* + * FIXME hack to force full modeset when DSC is being used. + * + * As long as we do not have full state readout and config comparison + * of crtc_state->dsc, we have no way to ensure reliable fastset. + * Remove once we have readout for DSC. + */ + if (crtc_state->dsc.compression_enable) { + drm_dbg_kms(&i915->drm, "Forcing full modeset due to DSC being enabled\n"); + crtc_state->uapi.mode_changed = true; + return false; + } + + if (CAN_PSR(intel_dp)) { + drm_dbg_kms(&i915->drm, "Forcing full modeset to compute PSR state\n"); + crtc_state->uapi.mode_changed = true; + return false; + } + + return true; +} + +static void intel_dp_get_pcon_dsc_cap(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + + /* Clear the cached register set to avoid using stale values */ + + memset(intel_dp->pcon_dsc_dpcd, 0, sizeof(intel_dp->pcon_dsc_dpcd)); + + if (drm_dp_dpcd_read(&intel_dp->aux, DP_PCON_DSC_ENCODER, + intel_dp->pcon_dsc_dpcd, + sizeof(intel_dp->pcon_dsc_dpcd)) < 0) + drm_err(&i915->drm, "Failed to read DPCD register 0x%x\n", + DP_PCON_DSC_ENCODER); + + drm_dbg_kms(&i915->drm, "PCON ENCODER DSC DPCD: %*ph\n", + (int)sizeof(intel_dp->pcon_dsc_dpcd), intel_dp->pcon_dsc_dpcd); +} + +static int intel_dp_pcon_get_frl_mask(u8 frl_bw_mask) +{ + int bw_gbps[] = {9, 18, 24, 32, 40, 48}; + int i; + + for (i = ARRAY_SIZE(bw_gbps) - 1; i >= 0; i--) { + if (frl_bw_mask & (1 << i)) + return bw_gbps[i]; + } + return 0; +} + +static int intel_dp_pcon_set_frl_mask(int max_frl) +{ + switch (max_frl) { + case 48: + return DP_PCON_FRL_BW_MASK_48GBPS; + case 40: + return DP_PCON_FRL_BW_MASK_40GBPS; + case 32: + return DP_PCON_FRL_BW_MASK_32GBPS; + case 24: + return DP_PCON_FRL_BW_MASK_24GBPS; + case 18: + return DP_PCON_FRL_BW_MASK_18GBPS; + case 9: + return DP_PCON_FRL_BW_MASK_9GBPS; + } + + return 0; +} + +static int intel_dp_hdmi_sink_max_frl(struct intel_dp *intel_dp) +{ + struct intel_connector *intel_connector = intel_dp->attached_connector; + struct drm_connector *connector = &intel_connector->base; + int max_frl_rate; + int max_lanes, rate_per_lane; + int max_dsc_lanes, dsc_rate_per_lane; + + max_lanes = connector->display_info.hdmi.max_lanes; + rate_per_lane = connector->display_info.hdmi.max_frl_rate_per_lane; + max_frl_rate = max_lanes * rate_per_lane; + + if (connector->display_info.hdmi.dsc_cap.v_1p2) { + max_dsc_lanes = connector->display_info.hdmi.dsc_cap.max_lanes; + dsc_rate_per_lane = connector->display_info.hdmi.dsc_cap.max_frl_rate_per_lane; + if (max_dsc_lanes && dsc_rate_per_lane) + max_frl_rate = min(max_frl_rate, max_dsc_lanes * dsc_rate_per_lane); + } + + return max_frl_rate; +} + +static bool +intel_dp_pcon_is_frl_trained(struct intel_dp *intel_dp, + u8 max_frl_bw_mask, u8 *frl_trained_mask) +{ + if (drm_dp_pcon_hdmi_link_active(&intel_dp->aux) && + drm_dp_pcon_hdmi_link_mode(&intel_dp->aux, frl_trained_mask) == DP_PCON_HDMI_MODE_FRL && + *frl_trained_mask >= max_frl_bw_mask) + return true; + + return false; +} + +static int intel_dp_pcon_start_frl_training(struct intel_dp *intel_dp) +{ +#define TIMEOUT_FRL_READY_MS 500 +#define TIMEOUT_HDMI_LINK_ACTIVE_MS 1000 + + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + int max_frl_bw, max_pcon_frl_bw, max_edid_frl_bw, ret; + u8 max_frl_bw_mask = 0, frl_trained_mask; + bool is_active; + + max_pcon_frl_bw = intel_dp->dfp.pcon_max_frl_bw; + drm_dbg(&i915->drm, "PCON max rate = %d Gbps\n", max_pcon_frl_bw); + + max_edid_frl_bw = intel_dp_hdmi_sink_max_frl(intel_dp); + drm_dbg(&i915->drm, "Sink max rate from EDID = %d Gbps\n", max_edid_frl_bw); + + max_frl_bw = min(max_edid_frl_bw, max_pcon_frl_bw); + + if (max_frl_bw <= 0) + return -EINVAL; + + max_frl_bw_mask = intel_dp_pcon_set_frl_mask(max_frl_bw); + drm_dbg(&i915->drm, "MAX_FRL_BW_MASK = %u\n", max_frl_bw_mask); + + if (intel_dp_pcon_is_frl_trained(intel_dp, max_frl_bw_mask, &frl_trained_mask)) + goto frl_trained; + + ret = drm_dp_pcon_frl_prepare(&intel_dp->aux, false); + if (ret < 0) + return ret; + /* Wait for PCON to be FRL Ready */ + wait_for(is_active = drm_dp_pcon_is_frl_ready(&intel_dp->aux) == true, TIMEOUT_FRL_READY_MS); + + if (!is_active) + return -ETIMEDOUT; + + ret = drm_dp_pcon_frl_configure_1(&intel_dp->aux, max_frl_bw, + DP_PCON_ENABLE_SEQUENTIAL_LINK); + if (ret < 0) + return ret; + ret = drm_dp_pcon_frl_configure_2(&intel_dp->aux, max_frl_bw_mask, + DP_PCON_FRL_LINK_TRAIN_NORMAL); + if (ret < 0) + return ret; + ret = drm_dp_pcon_frl_enable(&intel_dp->aux); + if (ret < 0) + return ret; + /* + * Wait for FRL to be completed + * Check if the HDMI Link is up and active. + */ + wait_for(is_active = + intel_dp_pcon_is_frl_trained(intel_dp, max_frl_bw_mask, &frl_trained_mask), + TIMEOUT_HDMI_LINK_ACTIVE_MS); + + if (!is_active) + return -ETIMEDOUT; + +frl_trained: + drm_dbg(&i915->drm, "FRL_TRAINED_MASK = %u\n", frl_trained_mask); + intel_dp->frl.trained_rate_gbps = intel_dp_pcon_get_frl_mask(frl_trained_mask); + intel_dp->frl.is_trained = true; + drm_dbg(&i915->drm, "FRL trained with : %d Gbps\n", intel_dp->frl.trained_rate_gbps); + + return 0; +} + +static bool intel_dp_is_hdmi_2_1_sink(struct intel_dp *intel_dp) +{ + if (drm_dp_is_branch(intel_dp->dpcd) && + intel_dp->has_hdmi_sink && + intel_dp_hdmi_sink_max_frl(intel_dp) > 0) + return true; + + return false; +} + +static +int intel_dp_pcon_set_tmds_mode(struct intel_dp *intel_dp) +{ + int ret; + u8 buf = 0; + + /* Set PCON source control mode */ + buf |= DP_PCON_ENABLE_SOURCE_CTL_MODE; + + ret = drm_dp_dpcd_writeb(&intel_dp->aux, DP_PCON_HDMI_LINK_CONFIG_1, buf); + if (ret < 0) + return ret; + + /* Set HDMI LINK ENABLE */ + buf |= DP_PCON_ENABLE_HDMI_LINK; + ret = drm_dp_dpcd_writeb(&intel_dp->aux, DP_PCON_HDMI_LINK_CONFIG_1, buf); + if (ret < 0) + return ret; + + return 0; +} + +void intel_dp_check_frl_training(struct intel_dp *intel_dp) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + + /* + * Always go for FRL training if: + * -PCON supports SRC_CTL_MODE (VESA DP2.0-HDMI2.1 PCON Spec Draft-1 Sec-7) + * -sink is HDMI2.1 + */ + if (!(intel_dp->downstream_ports[2] & DP_PCON_SOURCE_CTL_MODE) || + !intel_dp_is_hdmi_2_1_sink(intel_dp) || + intel_dp->frl.is_trained) + return; + + if (intel_dp_pcon_start_frl_training(intel_dp) < 0) { + int ret, mode; + + drm_dbg(&dev_priv->drm, "Couldn't set FRL mode, continuing with TMDS mode\n"); + ret = intel_dp_pcon_set_tmds_mode(intel_dp); + mode = drm_dp_pcon_hdmi_link_mode(&intel_dp->aux, NULL); + + if (ret < 0 || mode != DP_PCON_HDMI_MODE_TMDS) + drm_dbg(&dev_priv->drm, "Issue with PCON, cannot set TMDS mode\n"); + } else { + drm_dbg(&dev_priv->drm, "FRL training Completed\n"); + } +} + +static int +intel_dp_pcon_dsc_enc_slice_height(const struct intel_crtc_state *crtc_state) +{ + int vactive = crtc_state->hw.adjusted_mode.vdisplay; + + return intel_hdmi_dsc_get_slice_height(vactive); +} + +static int +intel_dp_pcon_dsc_enc_slices(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + struct intel_connector *intel_connector = intel_dp->attached_connector; + struct drm_connector *connector = &intel_connector->base; + int hdmi_throughput = connector->display_info.hdmi.dsc_cap.clk_per_slice; + int hdmi_max_slices = connector->display_info.hdmi.dsc_cap.max_slices; + int pcon_max_slices = drm_dp_pcon_dsc_max_slices(intel_dp->pcon_dsc_dpcd); + int pcon_max_slice_width = drm_dp_pcon_dsc_max_slice_width(intel_dp->pcon_dsc_dpcd); + + return intel_hdmi_dsc_get_num_slices(crtc_state, pcon_max_slices, + pcon_max_slice_width, + hdmi_max_slices, hdmi_throughput); +} + +static int +intel_dp_pcon_dsc_enc_bpp(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + int num_slices, int slice_width) +{ + struct intel_connector *intel_connector = intel_dp->attached_connector; + struct drm_connector *connector = &intel_connector->base; + int output_format = crtc_state->output_format; + bool hdmi_all_bpp = connector->display_info.hdmi.dsc_cap.all_bpp; + int pcon_fractional_bpp = drm_dp_pcon_dsc_bpp_incr(intel_dp->pcon_dsc_dpcd); + int hdmi_max_chunk_bytes = + connector->display_info.hdmi.dsc_cap.total_chunk_kbytes * 1024; + + return intel_hdmi_dsc_get_bpp(pcon_fractional_bpp, slice_width, + num_slices, output_format, hdmi_all_bpp, + hdmi_max_chunk_bytes); +} + +void +intel_dp_pcon_dsc_configure(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + u8 pps_param[6]; + int slice_height; + int slice_width; + int num_slices; + int bits_per_pixel; + int ret; + struct intel_connector *intel_connector = intel_dp->attached_connector; + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + struct drm_connector *connector; + bool hdmi_is_dsc_1_2; + + if (!intel_dp_is_hdmi_2_1_sink(intel_dp)) + return; + + if (!intel_connector) + return; + connector = &intel_connector->base; + hdmi_is_dsc_1_2 = connector->display_info.hdmi.dsc_cap.v_1p2; + + if (!drm_dp_pcon_enc_is_dsc_1_2(intel_dp->pcon_dsc_dpcd) || + !hdmi_is_dsc_1_2) + return; + + slice_height = intel_dp_pcon_dsc_enc_slice_height(crtc_state); + if (!slice_height) + return; + + num_slices = intel_dp_pcon_dsc_enc_slices(intel_dp, crtc_state); + if (!num_slices) + return; + + slice_width = DIV_ROUND_UP(crtc_state->hw.adjusted_mode.hdisplay, + num_slices); + + bits_per_pixel = intel_dp_pcon_dsc_enc_bpp(intel_dp, crtc_state, + num_slices, slice_width); + if (!bits_per_pixel) + return; + + pps_param[0] = slice_height & 0xFF; + pps_param[1] = slice_height >> 8; + pps_param[2] = slice_width & 0xFF; + pps_param[3] = slice_width >> 8; + pps_param[4] = bits_per_pixel & 0xFF; + pps_param[5] = (bits_per_pixel >> 8) & 0x3; + + ret = drm_dp_pcon_pps_override_param(&intel_dp->aux, pps_param); + if (ret < 0) + drm_dbg_kms(&i915->drm, "Failed to set pcon DSC\n"); +} + +void intel_dp_configure_protocol_converter(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + u8 tmp; + + if (intel_dp->dpcd[DP_DPCD_REV] < 0x13) + return; + + if (!drm_dp_is_branch(intel_dp->dpcd)) + return; + + tmp = intel_dp->has_hdmi_sink ? + DP_HDMI_DVI_OUTPUT_CONFIG : 0; + + if (drm_dp_dpcd_writeb(&intel_dp->aux, + DP_PROTOCOL_CONVERTER_CONTROL_0, tmp) != 1) + drm_dbg_kms(&i915->drm, "Failed to %s protocol converter HDMI mode\n", + str_enable_disable(intel_dp->has_hdmi_sink)); + + tmp = crtc_state->output_format == INTEL_OUTPUT_FORMAT_YCBCR444 && + intel_dp->dfp.ycbcr_444_to_420 ? DP_CONVERSION_TO_YCBCR420_ENABLE : 0; + + if (drm_dp_dpcd_writeb(&intel_dp->aux, + DP_PROTOCOL_CONVERTER_CONTROL_1, tmp) != 1) + drm_dbg_kms(&i915->drm, + "Failed to %s protocol converter YCbCr 4:2:0 conversion mode\n", + str_enable_disable(intel_dp->dfp.ycbcr_444_to_420)); + + tmp = intel_dp->dfp.rgb_to_ycbcr ? + DP_CONVERSION_BT709_RGB_YCBCR_ENABLE : 0; + + if (drm_dp_pcon_convert_rgb_to_ycbcr(&intel_dp->aux, tmp) < 0) + drm_dbg_kms(&i915->drm, + "Failed to %s protocol converter RGB->YCbCr conversion mode\n", + str_enable_disable(tmp)); +} + + +bool intel_dp_get_colorimetry_status(struct intel_dp *intel_dp) +{ + u8 dprx = 0; + + if (drm_dp_dpcd_readb(&intel_dp->aux, DP_DPRX_FEATURE_ENUMERATION_LIST, + &dprx) != 1) + return false; + return dprx & DP_VSC_SDP_EXT_FOR_COLORIMETRY_SUPPORTED; +} + +static void intel_dp_get_dsc_sink_cap(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + + /* + * Clear the cached register set to avoid using stale values + * for the sinks that do not support DSC. + */ + memset(intel_dp->dsc_dpcd, 0, sizeof(intel_dp->dsc_dpcd)); + + /* Clear fec_capable to avoid using stale values */ + intel_dp->fec_capable = 0; + + /* Cache the DSC DPCD if eDP or DP rev >= 1.4 */ + if (intel_dp->dpcd[DP_DPCD_REV] >= 0x14 || + intel_dp->edp_dpcd[0] >= DP_EDP_14) { + if (drm_dp_dpcd_read(&intel_dp->aux, DP_DSC_SUPPORT, + intel_dp->dsc_dpcd, + sizeof(intel_dp->dsc_dpcd)) < 0) + drm_err(&i915->drm, + "Failed to read DPCD register 0x%x\n", + DP_DSC_SUPPORT); + + drm_dbg_kms(&i915->drm, "DSC DPCD: %*ph\n", + (int)sizeof(intel_dp->dsc_dpcd), + intel_dp->dsc_dpcd); + + /* FEC is supported only on DP 1.4 */ + if (!intel_dp_is_edp(intel_dp) && + drm_dp_dpcd_readb(&intel_dp->aux, DP_FEC_CAPABILITY, + &intel_dp->fec_capable) < 0) + drm_err(&i915->drm, + "Failed to read FEC DPCD register\n"); + + drm_dbg_kms(&i915->drm, "FEC CAPABILITY: %x\n", + intel_dp->fec_capable); + } +} + +static void intel_edp_mso_mode_fixup(struct intel_connector *connector, + struct drm_display_mode *mode) +{ + struct intel_dp *intel_dp = intel_attached_dp(connector); + struct drm_i915_private *i915 = to_i915(connector->base.dev); + int n = intel_dp->mso_link_count; + int overlap = intel_dp->mso_pixel_overlap; + + if (!mode || !n) + return; + + mode->hdisplay = (mode->hdisplay - overlap) * n; + mode->hsync_start = (mode->hsync_start - overlap) * n; + mode->hsync_end = (mode->hsync_end - overlap) * n; + mode->htotal = (mode->htotal - overlap) * n; + mode->clock *= n; + + drm_mode_set_name(mode); + + drm_dbg_kms(&i915->drm, + "[CONNECTOR:%d:%s] using generated MSO mode: " DRM_MODE_FMT "\n", + connector->base.base.id, connector->base.name, + DRM_MODE_ARG(mode)); +} + +void intel_edp_fixup_vbt_bpp(struct intel_encoder *encoder, int pipe_bpp) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + struct intel_connector *connector = intel_dp->attached_connector; + + if (connector->panel.vbt.edp.bpp && pipe_bpp > connector->panel.vbt.edp.bpp) { + /* + * This is a big fat ugly hack. + * + * Some machines in UEFI boot mode provide us a VBT that has 18 + * bpp and 1.62 GHz link bandwidth for eDP, which for reasons + * unknown we fail to light up. Yet the same BIOS boots up with + * 24 bpp and 2.7 GHz link. Use the same bpp as the BIOS uses as + * max, not what it tells us to use. + * + * Note: This will still be broken if the eDP panel is not lit + * up by the BIOS, and thus we can't get the mode at module + * load. + */ + drm_dbg_kms(&dev_priv->drm, + "pipe has %d bpp for eDP panel, overriding BIOS-provided max %d bpp\n", + pipe_bpp, connector->panel.vbt.edp.bpp); + connector->panel.vbt.edp.bpp = pipe_bpp; + } +} + +static void intel_edp_mso_init(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + struct intel_connector *connector = intel_dp->attached_connector; + struct drm_display_info *info = &connector->base.display_info; + u8 mso; + + if (intel_dp->edp_dpcd[0] < DP_EDP_14) + return; + + if (drm_dp_dpcd_readb(&intel_dp->aux, DP_EDP_MSO_LINK_CAPABILITIES, &mso) != 1) { + drm_err(&i915->drm, "Failed to read MSO cap\n"); + return; + } + + /* Valid configurations are SST or MSO 2x1, 2x2, 4x1 */ + mso &= DP_EDP_MSO_NUMBER_OF_LINKS_MASK; + if (mso % 2 || mso > drm_dp_max_lane_count(intel_dp->dpcd)) { + drm_err(&i915->drm, "Invalid MSO link count cap %u\n", mso); + mso = 0; + } + + if (mso) { + drm_dbg_kms(&i915->drm, "Sink MSO %ux%u configuration, pixel overlap %u\n", + mso, drm_dp_max_lane_count(intel_dp->dpcd) / mso, + info->mso_pixel_overlap); + if (!HAS_MSO(i915)) { + drm_err(&i915->drm, "No source MSO support, disabling\n"); + mso = 0; + } + } + + intel_dp->mso_link_count = mso; + intel_dp->mso_pixel_overlap = mso ? info->mso_pixel_overlap : 0; +} + +static bool +intel_edp_init_dpcd(struct intel_dp *intel_dp) +{ + struct drm_i915_private *dev_priv = + to_i915(dp_to_dig_port(intel_dp)->base.base.dev); + + /* this function is meant to be called only once */ + drm_WARN_ON(&dev_priv->drm, intel_dp->dpcd[DP_DPCD_REV] != 0); + + if (drm_dp_read_dpcd_caps(&intel_dp->aux, intel_dp->dpcd) != 0) + return false; + + drm_dp_read_desc(&intel_dp->aux, &intel_dp->desc, + drm_dp_is_branch(intel_dp->dpcd)); + + /* + * Read the eDP display control registers. + * + * Do this independent of DP_DPCD_DISPLAY_CONTROL_CAPABLE bit in + * DP_EDP_CONFIGURATION_CAP, because some buggy displays do not have it + * set, but require eDP 1.4+ detection (e.g. for supported link rates + * method). The display control registers should read zero if they're + * not supported anyway. + */ + if (drm_dp_dpcd_read(&intel_dp->aux, DP_EDP_DPCD_REV, + intel_dp->edp_dpcd, sizeof(intel_dp->edp_dpcd)) == + sizeof(intel_dp->edp_dpcd)) { + drm_dbg_kms(&dev_priv->drm, "eDP DPCD: %*ph\n", + (int)sizeof(intel_dp->edp_dpcd), + intel_dp->edp_dpcd); + + intel_dp->use_max_params = intel_dp->edp_dpcd[0] < DP_EDP_14; + } + + /* + * This has to be called after intel_dp->edp_dpcd is filled, PSR checks + * for SET_POWER_CAPABLE bit in intel_dp->edp_dpcd[1] + */ + intel_psr_init_dpcd(intel_dp); + + /* Clear the default sink rates */ + intel_dp->num_sink_rates = 0; + + /* Read the eDP 1.4+ supported link rates. */ + if (intel_dp->edp_dpcd[0] >= DP_EDP_14) { + __le16 sink_rates[DP_MAX_SUPPORTED_RATES]; + int i; + + drm_dp_dpcd_read(&intel_dp->aux, DP_SUPPORTED_LINK_RATES, + sink_rates, sizeof(sink_rates)); + + for (i = 0; i < ARRAY_SIZE(sink_rates); i++) { + int val = le16_to_cpu(sink_rates[i]); + + if (val == 0) + break; + + /* Value read multiplied by 200kHz gives the per-lane + * link rate in kHz. The source rates are, however, + * stored in terms of LS_Clk kHz. The full conversion + * back to symbols is + * (val * 200kHz)*(8/10 ch. encoding)*(1/8 bit to Byte) + */ + intel_dp->sink_rates[i] = (val * 200) / 10; + } + intel_dp->num_sink_rates = i; + } + + /* + * Use DP_LINK_RATE_SET if DP_SUPPORTED_LINK_RATES are available, + * default to DP_MAX_LINK_RATE and DP_LINK_BW_SET otherwise. + */ + if (intel_dp->num_sink_rates) + intel_dp->use_rate_select = true; + else + intel_dp_set_sink_rates(intel_dp); + intel_dp_set_max_sink_lane_count(intel_dp); + + /* Read the eDP DSC DPCD registers */ + if (DISPLAY_VER(dev_priv) >= 10) + intel_dp_get_dsc_sink_cap(intel_dp); + + /* + * If needed, program our source OUI so we can make various Intel-specific AUX services + * available (such as HDR backlight controls) + */ + intel_edp_init_source_oui(intel_dp, true); + + return true; +} + +static bool +intel_dp_has_sink_count(struct intel_dp *intel_dp) +{ + if (!intel_dp->attached_connector) + return false; + + return drm_dp_read_sink_count_cap(&intel_dp->attached_connector->base, + intel_dp->dpcd, + &intel_dp->desc); +} + +static bool +intel_dp_get_dpcd(struct intel_dp *intel_dp) +{ + int ret; + + if (intel_dp_init_lttpr_and_dprx_caps(intel_dp) < 0) + return false; + + /* + * Don't clobber cached eDP rates. Also skip re-reading + * the OUI/ID since we know it won't change. + */ + if (!intel_dp_is_edp(intel_dp)) { + drm_dp_read_desc(&intel_dp->aux, &intel_dp->desc, + drm_dp_is_branch(intel_dp->dpcd)); + + intel_dp_set_sink_rates(intel_dp); + intel_dp_set_max_sink_lane_count(intel_dp); + intel_dp_set_common_rates(intel_dp); + } + + if (intel_dp_has_sink_count(intel_dp)) { + ret = drm_dp_read_sink_count(&intel_dp->aux); + if (ret < 0) + return false; + + /* + * Sink count can change between short pulse hpd hence + * a member variable in intel_dp will track any changes + * between short pulse interrupts. + */ + intel_dp->sink_count = ret; + + /* + * SINK_COUNT == 0 and DOWNSTREAM_PORT_PRESENT == 1 implies that + * a dongle is present but no display. Unless we require to know + * if a dongle is present or not, we don't need to update + * downstream port information. So, an early return here saves + * time from performing other operations which are not required. + */ + if (!intel_dp->sink_count) + return false; + } + + return drm_dp_read_downstream_info(&intel_dp->aux, intel_dp->dpcd, + intel_dp->downstream_ports) == 0; +} + +static bool +intel_dp_can_mst(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + + return i915->params.enable_dp_mst && + intel_dp_mst_source_support(intel_dp) && + drm_dp_read_mst_cap(&intel_dp->aux, intel_dp->dpcd); +} + +static void +intel_dp_configure_mst(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + struct intel_encoder *encoder = + &dp_to_dig_port(intel_dp)->base; + bool sink_can_mst = drm_dp_read_mst_cap(&intel_dp->aux, intel_dp->dpcd); + + drm_dbg_kms(&i915->drm, + "[ENCODER:%d:%s] MST support: port: %s, sink: %s, modparam: %s\n", + encoder->base.base.id, encoder->base.name, + str_yes_no(intel_dp_mst_source_support(intel_dp)), + str_yes_no(sink_can_mst), + str_yes_no(i915->params.enable_dp_mst)); + + if (!intel_dp_mst_source_support(intel_dp)) + return; + + intel_dp->is_mst = sink_can_mst && + i915->params.enable_dp_mst; + + drm_dp_mst_topology_mgr_set_mst(&intel_dp->mst_mgr, + intel_dp->is_mst); +} + +static bool +intel_dp_get_sink_irq_esi(struct intel_dp *intel_dp, u8 *esi) +{ + return drm_dp_dpcd_read(&intel_dp->aux, DP_SINK_COUNT_ESI, esi, 4) == 4; +} + +static bool intel_dp_ack_sink_irq_esi(struct intel_dp *intel_dp, u8 esi[4]) +{ + int retry; + + for (retry = 0; retry < 3; retry++) { + if (drm_dp_dpcd_write(&intel_dp->aux, DP_SINK_COUNT_ESI + 1, + &esi[1], 3) == 3) + return true; + } + + return false; +} + +bool +intel_dp_needs_vsc_sdp(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + /* + * As per DP 1.4a spec section 2.2.4.3 [MSA Field for Indication + * of Color Encoding Format and Content Color Gamut], in order to + * sending YCBCR 420 or HDR BT.2020 signals we should use DP VSC SDP. + */ + if (crtc_state->output_format == INTEL_OUTPUT_FORMAT_YCBCR420) + return true; + + switch (conn_state->colorspace) { + case DRM_MODE_COLORIMETRY_SYCC_601: + case DRM_MODE_COLORIMETRY_OPYCC_601: + case DRM_MODE_COLORIMETRY_BT2020_YCC: + case DRM_MODE_COLORIMETRY_BT2020_RGB: + case DRM_MODE_COLORIMETRY_BT2020_CYCC: + return true; + default: + break; + } + + return false; +} + +static ssize_t intel_dp_vsc_sdp_pack(const struct drm_dp_vsc_sdp *vsc, + struct dp_sdp *sdp, size_t size) +{ + size_t length = sizeof(struct dp_sdp); + + if (size < length) + return -ENOSPC; + + memset(sdp, 0, size); + + /* + * Prepare VSC Header for SU as per DP 1.4a spec, Table 2-119 + * VSC SDP Header Bytes + */ + sdp->sdp_header.HB0 = 0; /* Secondary-Data Packet ID = 0 */ + sdp->sdp_header.HB1 = vsc->sdp_type; /* Secondary-data Packet Type */ + sdp->sdp_header.HB2 = vsc->revision; /* Revision Number */ + sdp->sdp_header.HB3 = vsc->length; /* Number of Valid Data Bytes */ + + /* + * Only revision 0x5 supports Pixel Encoding/Colorimetry Format as + * per DP 1.4a spec. + */ + if (vsc->revision != 0x5) + goto out; + + /* VSC SDP Payload for DB16 through DB18 */ + /* Pixel Encoding and Colorimetry Formats */ + sdp->db[16] = (vsc->pixelformat & 0xf) << 4; /* DB16[7:4] */ + sdp->db[16] |= vsc->colorimetry & 0xf; /* DB16[3:0] */ + + switch (vsc->bpc) { + case 6: + /* 6bpc: 0x0 */ + break; + case 8: + sdp->db[17] = 0x1; /* DB17[3:0] */ + break; + case 10: + sdp->db[17] = 0x2; + break; + case 12: + sdp->db[17] = 0x3; + break; + case 16: + sdp->db[17] = 0x4; + break; + default: + MISSING_CASE(vsc->bpc); + break; + } + /* Dynamic Range and Component Bit Depth */ + if (vsc->dynamic_range == DP_DYNAMIC_RANGE_CTA) + sdp->db[17] |= 0x80; /* DB17[7] */ + + /* Content Type */ + sdp->db[18] = vsc->content_type & 0x7; + +out: + return length; +} + +static ssize_t +intel_dp_hdr_metadata_infoframe_sdp_pack(struct drm_i915_private *i915, + const struct hdmi_drm_infoframe *drm_infoframe, + struct dp_sdp *sdp, + size_t size) +{ + size_t length = sizeof(struct dp_sdp); + const int infoframe_size = HDMI_INFOFRAME_HEADER_SIZE + HDMI_DRM_INFOFRAME_SIZE; + unsigned char buf[HDMI_INFOFRAME_HEADER_SIZE + HDMI_DRM_INFOFRAME_SIZE]; + ssize_t len; + + if (size < length) + return -ENOSPC; + + memset(sdp, 0, size); + + len = hdmi_drm_infoframe_pack_only(drm_infoframe, buf, sizeof(buf)); + if (len < 0) { + drm_dbg_kms(&i915->drm, "buffer size is smaller than hdr metadata infoframe\n"); + return -ENOSPC; + } + + if (len != infoframe_size) { + drm_dbg_kms(&i915->drm, "wrong static hdr metadata size\n"); + return -ENOSPC; + } + + /* + * Set up the infoframe sdp packet for HDR static metadata. + * Prepare VSC Header for SU as per DP 1.4a spec, + * Table 2-100 and Table 2-101 + */ + + /* Secondary-Data Packet ID, 00h for non-Audio INFOFRAME */ + sdp->sdp_header.HB0 = 0; + /* + * Packet Type 80h + Non-audio INFOFRAME Type value + * HDMI_INFOFRAME_TYPE_DRM: 0x87 + * - 80h + Non-audio INFOFRAME Type value + * - InfoFrame Type: 0x07 + * [CTA-861-G Table-42 Dynamic Range and Mastering InfoFrame] + */ + sdp->sdp_header.HB1 = drm_infoframe->type; + /* + * Least Significant Eight Bits of (Data Byte Count – 1) + * infoframe_size - 1 + */ + sdp->sdp_header.HB2 = 0x1D; + /* INFOFRAME SDP Version Number */ + sdp->sdp_header.HB3 = (0x13 << 2); + /* CTA Header Byte 2 (INFOFRAME Version Number) */ + sdp->db[0] = drm_infoframe->version; + /* CTA Header Byte 3 (Length of INFOFRAME): HDMI_DRM_INFOFRAME_SIZE */ + sdp->db[1] = drm_infoframe->length; + /* + * Copy HDMI_DRM_INFOFRAME_SIZE size from a buffer after + * HDMI_INFOFRAME_HEADER_SIZE + */ + BUILD_BUG_ON(sizeof(sdp->db) < HDMI_DRM_INFOFRAME_SIZE + 2); + memcpy(&sdp->db[2], &buf[HDMI_INFOFRAME_HEADER_SIZE], + HDMI_DRM_INFOFRAME_SIZE); + + /* + * Size of DP infoframe sdp packet for HDR static metadata consists of + * - DP SDP Header(struct dp_sdp_header): 4 bytes + * - Two Data Blocks: 2 bytes + * CTA Header Byte2 (INFOFRAME Version Number) + * CTA Header Byte3 (Length of INFOFRAME) + * - HDMI_DRM_INFOFRAME_SIZE: 26 bytes + * + * Prior to GEN11's GMP register size is identical to DP HDR static metadata + * infoframe size. But GEN11+ has larger than that size, write_infoframe + * will pad rest of the size. + */ + return sizeof(struct dp_sdp_header) + 2 + HDMI_DRM_INFOFRAME_SIZE; +} + +static void intel_write_dp_sdp(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + unsigned int type) +{ + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct dp_sdp sdp = {}; + ssize_t len; + + if ((crtc_state->infoframes.enable & + intel_hdmi_infoframe_enable(type)) == 0) + return; + + switch (type) { + case DP_SDP_VSC: + len = intel_dp_vsc_sdp_pack(&crtc_state->infoframes.vsc, &sdp, + sizeof(sdp)); + break; + case HDMI_PACKET_TYPE_GAMUT_METADATA: + len = intel_dp_hdr_metadata_infoframe_sdp_pack(dev_priv, + &crtc_state->infoframes.drm.drm, + &sdp, sizeof(sdp)); + break; + default: + MISSING_CASE(type); + return; + } + + if (drm_WARN_ON(&dev_priv->drm, len < 0)) + return; + + dig_port->write_infoframe(encoder, crtc_state, type, &sdp, len); +} + +void intel_write_dp_vsc_sdp(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_dp_vsc_sdp *vsc) +{ + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct dp_sdp sdp = {}; + ssize_t len; + + len = intel_dp_vsc_sdp_pack(vsc, &sdp, sizeof(sdp)); + + if (drm_WARN_ON(&dev_priv->drm, len < 0)) + return; + + dig_port->write_infoframe(encoder, crtc_state, DP_SDP_VSC, + &sdp, len); +} + +void intel_dp_set_infoframes(struct intel_encoder *encoder, + bool enable, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + i915_reg_t reg = HSW_TVIDEO_DIP_CTL(crtc_state->cpu_transcoder); + u32 dip_enable = VIDEO_DIP_ENABLE_AVI_HSW | VIDEO_DIP_ENABLE_GCP_HSW | + VIDEO_DIP_ENABLE_VS_HSW | VIDEO_DIP_ENABLE_GMP_HSW | + VIDEO_DIP_ENABLE_SPD_HSW | VIDEO_DIP_ENABLE_DRM_GLK; + u32 val = intel_de_read(dev_priv, reg) & ~dip_enable; + + /* TODO: Add DSC case (DIP_ENABLE_PPS) */ + /* When PSR is enabled, this routine doesn't disable VSC DIP */ + if (!crtc_state->has_psr) + val &= ~VIDEO_DIP_ENABLE_VSC_HSW; + + intel_de_write(dev_priv, reg, val); + intel_de_posting_read(dev_priv, reg); + + if (!enable) + return; + + /* When PSR is enabled, VSC SDP is handled by PSR routine */ + if (!crtc_state->has_psr) + intel_write_dp_sdp(encoder, crtc_state, DP_SDP_VSC); + + intel_write_dp_sdp(encoder, crtc_state, HDMI_PACKET_TYPE_GAMUT_METADATA); +} + +static int intel_dp_vsc_sdp_unpack(struct drm_dp_vsc_sdp *vsc, + const void *buffer, size_t size) +{ + const struct dp_sdp *sdp = buffer; + + if (size < sizeof(struct dp_sdp)) + return -EINVAL; + + memset(vsc, 0, sizeof(*vsc)); + + if (sdp->sdp_header.HB0 != 0) + return -EINVAL; + + if (sdp->sdp_header.HB1 != DP_SDP_VSC) + return -EINVAL; + + vsc->sdp_type = sdp->sdp_header.HB1; + vsc->revision = sdp->sdp_header.HB2; + vsc->length = sdp->sdp_header.HB3; + + if ((sdp->sdp_header.HB2 == 0x2 && sdp->sdp_header.HB3 == 0x8) || + (sdp->sdp_header.HB2 == 0x4 && sdp->sdp_header.HB3 == 0xe)) { + /* + * - HB2 = 0x2, HB3 = 0x8 + * VSC SDP supporting 3D stereo + PSR + * - HB2 = 0x4, HB3 = 0xe + * VSC SDP supporting 3D stereo + PSR2 with Y-coordinate of + * first scan line of the SU region (applies to eDP v1.4b + * and higher). + */ + return 0; + } else if (sdp->sdp_header.HB2 == 0x5 && sdp->sdp_header.HB3 == 0x13) { + /* + * - HB2 = 0x5, HB3 = 0x13 + * VSC SDP supporting 3D stereo + PSR2 + Pixel Encoding/Colorimetry + * Format. + */ + vsc->pixelformat = (sdp->db[16] >> 4) & 0xf; + vsc->colorimetry = sdp->db[16] & 0xf; + vsc->dynamic_range = (sdp->db[17] >> 7) & 0x1; + + switch (sdp->db[17] & 0x7) { + case 0x0: + vsc->bpc = 6; + break; + case 0x1: + vsc->bpc = 8; + break; + case 0x2: + vsc->bpc = 10; + break; + case 0x3: + vsc->bpc = 12; + break; + case 0x4: + vsc->bpc = 16; + break; + default: + MISSING_CASE(sdp->db[17] & 0x7); + return -EINVAL; + } + + vsc->content_type = sdp->db[18] & 0x7; + } else { + return -EINVAL; + } + + return 0; +} + +static int +intel_dp_hdr_metadata_infoframe_sdp_unpack(struct hdmi_drm_infoframe *drm_infoframe, + const void *buffer, size_t size) +{ + int ret; + + const struct dp_sdp *sdp = buffer; + + if (size < sizeof(struct dp_sdp)) + return -EINVAL; + + if (sdp->sdp_header.HB0 != 0) + return -EINVAL; + + if (sdp->sdp_header.HB1 != HDMI_INFOFRAME_TYPE_DRM) + return -EINVAL; + + /* + * Least Significant Eight Bits of (Data Byte Count – 1) + * 1Dh (i.e., Data Byte Count = 30 bytes). + */ + if (sdp->sdp_header.HB2 != 0x1D) + return -EINVAL; + + /* Most Significant Two Bits of (Data Byte Count – 1), Clear to 00b. */ + if ((sdp->sdp_header.HB3 & 0x3) != 0) + return -EINVAL; + + /* INFOFRAME SDP Version Number */ + if (((sdp->sdp_header.HB3 >> 2) & 0x3f) != 0x13) + return -EINVAL; + + /* CTA Header Byte 2 (INFOFRAME Version Number) */ + if (sdp->db[0] != 1) + return -EINVAL; + + /* CTA Header Byte 3 (Length of INFOFRAME): HDMI_DRM_INFOFRAME_SIZE */ + if (sdp->db[1] != HDMI_DRM_INFOFRAME_SIZE) + return -EINVAL; + + ret = hdmi_drm_infoframe_unpack_only(drm_infoframe, &sdp->db[2], + HDMI_DRM_INFOFRAME_SIZE); + + return ret; +} + +static void intel_read_dp_vsc_sdp(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state, + struct drm_dp_vsc_sdp *vsc) +{ + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + unsigned int type = DP_SDP_VSC; + struct dp_sdp sdp = {}; + int ret; + + /* When PSR is enabled, VSC SDP is handled by PSR routine */ + if (crtc_state->has_psr) + return; + + if ((crtc_state->infoframes.enable & + intel_hdmi_infoframe_enable(type)) == 0) + return; + + dig_port->read_infoframe(encoder, crtc_state, type, &sdp, sizeof(sdp)); + + ret = intel_dp_vsc_sdp_unpack(vsc, &sdp, sizeof(sdp)); + + if (ret) + drm_dbg_kms(&dev_priv->drm, "Failed to unpack DP VSC SDP\n"); +} + +static void intel_read_dp_hdr_metadata_infoframe_sdp(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state, + struct hdmi_drm_infoframe *drm_infoframe) +{ + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + unsigned int type = HDMI_PACKET_TYPE_GAMUT_METADATA; + struct dp_sdp sdp = {}; + int ret; + + if ((crtc_state->infoframes.enable & + intel_hdmi_infoframe_enable(type)) == 0) + return; + + dig_port->read_infoframe(encoder, crtc_state, type, &sdp, + sizeof(sdp)); + + ret = intel_dp_hdr_metadata_infoframe_sdp_unpack(drm_infoframe, &sdp, + sizeof(sdp)); + + if (ret) + drm_dbg_kms(&dev_priv->drm, + "Failed to unpack DP HDR Metadata Infoframe SDP\n"); +} + +void intel_read_dp_sdp(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state, + unsigned int type) +{ + switch (type) { + case DP_SDP_VSC: + intel_read_dp_vsc_sdp(encoder, crtc_state, + &crtc_state->infoframes.vsc); + break; + case HDMI_PACKET_TYPE_GAMUT_METADATA: + intel_read_dp_hdr_metadata_infoframe_sdp(encoder, crtc_state, + &crtc_state->infoframes.drm.drm); + break; + default: + MISSING_CASE(type); + break; + } +} + +static u8 intel_dp_autotest_link_training(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + int status = 0; + int test_link_rate; + u8 test_lane_count, test_link_bw; + /* (DP CTS 1.2) + * 4.3.1.11 + */ + /* Read the TEST_LANE_COUNT and TEST_LINK_RTAE fields (DP CTS 3.1.4) */ + status = drm_dp_dpcd_readb(&intel_dp->aux, DP_TEST_LANE_COUNT, + &test_lane_count); + + if (status <= 0) { + drm_dbg_kms(&i915->drm, "Lane count read failed\n"); + return DP_TEST_NAK; + } + test_lane_count &= DP_MAX_LANE_COUNT_MASK; + + status = drm_dp_dpcd_readb(&intel_dp->aux, DP_TEST_LINK_RATE, + &test_link_bw); + if (status <= 0) { + drm_dbg_kms(&i915->drm, "Link Rate read failed\n"); + return DP_TEST_NAK; + } + test_link_rate = drm_dp_bw_code_to_link_rate(test_link_bw); + + /* Validate the requested link rate and lane count */ + if (!intel_dp_link_params_valid(intel_dp, test_link_rate, + test_lane_count)) + return DP_TEST_NAK; + + intel_dp->compliance.test_lane_count = test_lane_count; + intel_dp->compliance.test_link_rate = test_link_rate; + + return DP_TEST_ACK; +} + +static u8 intel_dp_autotest_video_pattern(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + u8 test_pattern; + u8 test_misc; + __be16 h_width, v_height; + int status = 0; + + /* Read the TEST_PATTERN (DP CTS 3.1.5) */ + status = drm_dp_dpcd_readb(&intel_dp->aux, DP_TEST_PATTERN, + &test_pattern); + if (status <= 0) { + drm_dbg_kms(&i915->drm, "Test pattern read failed\n"); + return DP_TEST_NAK; + } + if (test_pattern != DP_COLOR_RAMP) + return DP_TEST_NAK; + + status = drm_dp_dpcd_read(&intel_dp->aux, DP_TEST_H_WIDTH_HI, + &h_width, 2); + if (status <= 0) { + drm_dbg_kms(&i915->drm, "H Width read failed\n"); + return DP_TEST_NAK; + } + + status = drm_dp_dpcd_read(&intel_dp->aux, DP_TEST_V_HEIGHT_HI, + &v_height, 2); + if (status <= 0) { + drm_dbg_kms(&i915->drm, "V Height read failed\n"); + return DP_TEST_NAK; + } + + status = drm_dp_dpcd_readb(&intel_dp->aux, DP_TEST_MISC0, + &test_misc); + if (status <= 0) { + drm_dbg_kms(&i915->drm, "TEST MISC read failed\n"); + return DP_TEST_NAK; + } + if ((test_misc & DP_TEST_COLOR_FORMAT_MASK) != DP_COLOR_FORMAT_RGB) + return DP_TEST_NAK; + if (test_misc & DP_TEST_DYNAMIC_RANGE_CEA) + return DP_TEST_NAK; + switch (test_misc & DP_TEST_BIT_DEPTH_MASK) { + case DP_TEST_BIT_DEPTH_6: + intel_dp->compliance.test_data.bpc = 6; + break; + case DP_TEST_BIT_DEPTH_8: + intel_dp->compliance.test_data.bpc = 8; + break; + default: + return DP_TEST_NAK; + } + + intel_dp->compliance.test_data.video_pattern = test_pattern; + intel_dp->compliance.test_data.hdisplay = be16_to_cpu(h_width); + intel_dp->compliance.test_data.vdisplay = be16_to_cpu(v_height); + /* Set test active flag here so userspace doesn't interrupt things */ + intel_dp->compliance.test_active = true; + + return DP_TEST_ACK; +} + +static u8 intel_dp_autotest_edid(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + u8 test_result = DP_TEST_ACK; + struct intel_connector *intel_connector = intel_dp->attached_connector; + struct drm_connector *connector = &intel_connector->base; + + if (intel_connector->detect_edid == NULL || + connector->edid_corrupt || + intel_dp->aux.i2c_defer_count > 6) { + /* Check EDID read for NACKs, DEFERs and corruption + * (DP CTS 1.2 Core r1.1) + * 4.2.2.4 : Failed EDID read, I2C_NAK + * 4.2.2.5 : Failed EDID read, I2C_DEFER + * 4.2.2.6 : EDID corruption detected + * Use failsafe mode for all cases + */ + if (intel_dp->aux.i2c_nack_count > 0 || + intel_dp->aux.i2c_defer_count > 0) + drm_dbg_kms(&i915->drm, + "EDID read had %d NACKs, %d DEFERs\n", + intel_dp->aux.i2c_nack_count, + intel_dp->aux.i2c_defer_count); + intel_dp->compliance.test_data.edid = INTEL_DP_RESOLUTION_FAILSAFE; + } else { + struct edid *block = intel_connector->detect_edid; + + /* We have to write the checksum + * of the last block read + */ + block += intel_connector->detect_edid->extensions; + + if (drm_dp_dpcd_writeb(&intel_dp->aux, DP_TEST_EDID_CHECKSUM, + block->checksum) <= 0) + drm_dbg_kms(&i915->drm, + "Failed to write EDID checksum\n"); + + test_result = DP_TEST_ACK | DP_TEST_EDID_CHECKSUM_WRITE; + intel_dp->compliance.test_data.edid = INTEL_DP_RESOLUTION_PREFERRED; + } + + /* Set test active flag here so userspace doesn't interrupt things */ + intel_dp->compliance.test_active = true; + + return test_result; +} + +static void intel_dp_phy_pattern_update(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = + to_i915(dp_to_dig_port(intel_dp)->base.base.dev); + struct drm_dp_phy_test_params *data = + &intel_dp->compliance.test_data.phytest; + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + enum pipe pipe = crtc->pipe; + u32 pattern_val; + + switch (data->phy_pattern) { + case DP_PHY_TEST_PATTERN_NONE: + drm_dbg_kms(&dev_priv->drm, "Disable Phy Test Pattern\n"); + intel_de_write(dev_priv, DDI_DP_COMP_CTL(pipe), 0x0); + break; + case DP_PHY_TEST_PATTERN_D10_2: + drm_dbg_kms(&dev_priv->drm, "Set D10.2 Phy Test Pattern\n"); + intel_de_write(dev_priv, DDI_DP_COMP_CTL(pipe), + DDI_DP_COMP_CTL_ENABLE | DDI_DP_COMP_CTL_D10_2); + break; + case DP_PHY_TEST_PATTERN_ERROR_COUNT: + drm_dbg_kms(&dev_priv->drm, "Set Error Count Phy Test Pattern\n"); + intel_de_write(dev_priv, DDI_DP_COMP_CTL(pipe), + DDI_DP_COMP_CTL_ENABLE | + DDI_DP_COMP_CTL_SCRAMBLED_0); + break; + case DP_PHY_TEST_PATTERN_PRBS7: + drm_dbg_kms(&dev_priv->drm, "Set PRBS7 Phy Test Pattern\n"); + intel_de_write(dev_priv, DDI_DP_COMP_CTL(pipe), + DDI_DP_COMP_CTL_ENABLE | DDI_DP_COMP_CTL_PRBS7); + break; + case DP_PHY_TEST_PATTERN_80BIT_CUSTOM: + /* + * FIXME: Ideally pattern should come from DPCD 0x250. As + * current firmware of DPR-100 could not set it, so hardcoding + * now for complaince test. + */ + drm_dbg_kms(&dev_priv->drm, + "Set 80Bit Custom Phy Test Pattern 0x3e0f83e0 0x0f83e0f8 0x0000f83e\n"); + pattern_val = 0x3e0f83e0; + intel_de_write(dev_priv, DDI_DP_COMP_PAT(pipe, 0), pattern_val); + pattern_val = 0x0f83e0f8; + intel_de_write(dev_priv, DDI_DP_COMP_PAT(pipe, 1), pattern_val); + pattern_val = 0x0000f83e; + intel_de_write(dev_priv, DDI_DP_COMP_PAT(pipe, 2), pattern_val); + intel_de_write(dev_priv, DDI_DP_COMP_CTL(pipe), + DDI_DP_COMP_CTL_ENABLE | + DDI_DP_COMP_CTL_CUSTOM80); + break; + case DP_PHY_TEST_PATTERN_CP2520: + /* + * FIXME: Ideally pattern should come from DPCD 0x24A. As + * current firmware of DPR-100 could not set it, so hardcoding + * now for complaince test. + */ + drm_dbg_kms(&dev_priv->drm, "Set HBR2 compliance Phy Test Pattern\n"); + pattern_val = 0xFB; + intel_de_write(dev_priv, DDI_DP_COMP_CTL(pipe), + DDI_DP_COMP_CTL_ENABLE | DDI_DP_COMP_CTL_HBR2 | + pattern_val); + break; + default: + WARN(1, "Invalid Phy Test Pattern\n"); + } +} + +static void intel_dp_process_phy_request(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + struct drm_dp_phy_test_params *data = + &intel_dp->compliance.test_data.phytest; + u8 link_status[DP_LINK_STATUS_SIZE]; + + if (drm_dp_dpcd_read_phy_link_status(&intel_dp->aux, DP_PHY_DPRX, + link_status) < 0) { + drm_dbg_kms(&i915->drm, "failed to get link status\n"); + return; + } + + /* retrieve vswing & pre-emphasis setting */ + intel_dp_get_adjust_train(intel_dp, crtc_state, DP_PHY_DPRX, + link_status); + + intel_dp_set_signal_levels(intel_dp, crtc_state, DP_PHY_DPRX); + + intel_dp_phy_pattern_update(intel_dp, crtc_state); + + drm_dp_dpcd_write(&intel_dp->aux, DP_TRAINING_LANE0_SET, + intel_dp->train_set, crtc_state->lane_count); + + drm_dp_set_phy_test_pattern(&intel_dp->aux, data, + intel_dp->dpcd[DP_DPCD_REV]); +} + +static u8 intel_dp_autotest_phy_pattern(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + struct drm_dp_phy_test_params *data = + &intel_dp->compliance.test_data.phytest; + + if (drm_dp_get_phy_test_pattern(&intel_dp->aux, data)) { + drm_dbg_kms(&i915->drm, "DP Phy Test pattern AUX read failure\n"); + return DP_TEST_NAK; + } + + /* Set test active flag here so userspace doesn't interrupt things */ + intel_dp->compliance.test_active = true; + + return DP_TEST_ACK; +} + +static void intel_dp_handle_test_request(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + u8 response = DP_TEST_NAK; + u8 request = 0; + int status; + + status = drm_dp_dpcd_readb(&intel_dp->aux, DP_TEST_REQUEST, &request); + if (status <= 0) { + drm_dbg_kms(&i915->drm, + "Could not read test request from sink\n"); + goto update_status; + } + + switch (request) { + case DP_TEST_LINK_TRAINING: + drm_dbg_kms(&i915->drm, "LINK_TRAINING test requested\n"); + response = intel_dp_autotest_link_training(intel_dp); + break; + case DP_TEST_LINK_VIDEO_PATTERN: + drm_dbg_kms(&i915->drm, "TEST_PATTERN test requested\n"); + response = intel_dp_autotest_video_pattern(intel_dp); + break; + case DP_TEST_LINK_EDID_READ: + drm_dbg_kms(&i915->drm, "EDID test requested\n"); + response = intel_dp_autotest_edid(intel_dp); + break; + case DP_TEST_LINK_PHY_TEST_PATTERN: + drm_dbg_kms(&i915->drm, "PHY_PATTERN test requested\n"); + response = intel_dp_autotest_phy_pattern(intel_dp); + break; + default: + drm_dbg_kms(&i915->drm, "Invalid test request '%02x'\n", + request); + break; + } + + if (response & DP_TEST_ACK) + intel_dp->compliance.test_type = request; + +update_status: + status = drm_dp_dpcd_writeb(&intel_dp->aux, DP_TEST_RESPONSE, response); + if (status <= 0) + drm_dbg_kms(&i915->drm, + "Could not write test response to sink\n"); +} + +static bool intel_dp_link_ok(struct intel_dp *intel_dp, + u8 link_status[DP_LINK_STATUS_SIZE]) +{ + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + bool uhbr = intel_dp->link_rate >= 1000000; + bool ok; + + if (uhbr) + ok = drm_dp_128b132b_lane_channel_eq_done(link_status, + intel_dp->lane_count); + else + ok = drm_dp_channel_eq_ok(link_status, intel_dp->lane_count); + + if (ok) + return true; + + intel_dp_dump_link_status(intel_dp, DP_PHY_DPRX, link_status); + drm_dbg_kms(&i915->drm, + "[ENCODER:%d:%s] %s link not ok, retraining\n", + encoder->base.base.id, encoder->base.name, + uhbr ? "128b/132b" : "8b/10b"); + + return false; +} + +static void +intel_dp_mst_hpd_irq(struct intel_dp *intel_dp, u8 *esi, u8 *ack) +{ + bool handled = false; + + drm_dp_mst_hpd_irq_handle_event(&intel_dp->mst_mgr, esi, ack, &handled); + + if (esi[1] & DP_CP_IRQ) { + intel_hdcp_handle_cp_irq(intel_dp->attached_connector); + ack[1] |= DP_CP_IRQ; + } +} + +static bool intel_dp_mst_link_status(struct intel_dp *intel_dp) +{ + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + u8 link_status[DP_LINK_STATUS_SIZE] = {}; + const size_t esi_link_status_size = DP_LINK_STATUS_SIZE - 2; + + if (drm_dp_dpcd_read(&intel_dp->aux, DP_LANE0_1_STATUS_ESI, link_status, + esi_link_status_size) != esi_link_status_size) { + drm_err(&i915->drm, + "[ENCODER:%d:%s] Failed to read link status\n", + encoder->base.base.id, encoder->base.name); + return false; + } + + return intel_dp_link_ok(intel_dp, link_status); +} + +/** + * intel_dp_check_mst_status - service any pending MST interrupts, check link status + * @intel_dp: Intel DP struct + * + * Read any pending MST interrupts, call MST core to handle these and ack the + * interrupts. Check if the main and AUX link state is ok. + * + * Returns: + * - %true if pending interrupts were serviced (or no interrupts were + * pending) w/o detecting an error condition. + * - %false if an error condition - like AUX failure or a loss of link - is + * detected, which needs servicing from the hotplug work. + */ +static bool +intel_dp_check_mst_status(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + bool link_ok = true; + + drm_WARN_ON_ONCE(&i915->drm, intel_dp->active_mst_links < 0); + + for (;;) { + u8 esi[4] = {}; + u8 ack[4] = {}; + + if (!intel_dp_get_sink_irq_esi(intel_dp, esi)) { + drm_dbg_kms(&i915->drm, + "failed to get ESI - device may have failed\n"); + link_ok = false; + + break; + } + + drm_dbg_kms(&i915->drm, "DPRX ESI: %4ph\n", esi); + + if (intel_dp->active_mst_links > 0 && link_ok && + esi[3] & LINK_STATUS_CHANGED) { + if (!intel_dp_mst_link_status(intel_dp)) + link_ok = false; + ack[3] |= LINK_STATUS_CHANGED; + } + + intel_dp_mst_hpd_irq(intel_dp, esi, ack); + + if (!memchr_inv(ack, 0, sizeof(ack))) + break; + + if (!intel_dp_ack_sink_irq_esi(intel_dp, ack)) + drm_dbg_kms(&i915->drm, "Failed to ack ESI\n"); + + if (ack[1] & (DP_DOWN_REP_MSG_RDY | DP_UP_REQ_MSG_RDY)) + drm_dp_mst_hpd_irq_send_new_request(&intel_dp->mst_mgr); + } + + return link_ok; +} + +static void +intel_dp_handle_hdmi_link_status_change(struct intel_dp *intel_dp) +{ + bool is_active; + u8 buf = 0; + + is_active = drm_dp_pcon_hdmi_link_active(&intel_dp->aux); + if (intel_dp->frl.is_trained && !is_active) { + if (drm_dp_dpcd_readb(&intel_dp->aux, DP_PCON_HDMI_LINK_CONFIG_1, &buf) < 0) + return; + + buf &= ~DP_PCON_ENABLE_HDMI_LINK; + if (drm_dp_dpcd_writeb(&intel_dp->aux, DP_PCON_HDMI_LINK_CONFIG_1, buf) < 0) + return; + + drm_dp_pcon_hdmi_frl_link_error_count(&intel_dp->aux, &intel_dp->attached_connector->base); + + intel_dp->frl.is_trained = false; + + /* Restart FRL training or fall back to TMDS mode */ + intel_dp_check_frl_training(intel_dp); + } +} + +static bool +intel_dp_needs_link_retrain(struct intel_dp *intel_dp) +{ + u8 link_status[DP_LINK_STATUS_SIZE]; + + if (!intel_dp->link_trained) + return false; + + /* + * While PSR source HW is enabled, it will control main-link sending + * frames, enabling and disabling it so trying to do a retrain will fail + * as the link would or not be on or it could mix training patterns + * and frame data at the same time causing retrain to fail. + * Also when exiting PSR, HW will retrain the link anyways fixing + * any link status error. + */ + if (intel_psr_enabled(intel_dp)) + return false; + + if (drm_dp_dpcd_read_phy_link_status(&intel_dp->aux, DP_PHY_DPRX, + link_status) < 0) + return false; + + /* + * Validate the cached values of intel_dp->link_rate and + * intel_dp->lane_count before attempting to retrain. + * + * FIXME would be nice to user the crtc state here, but since + * we need to call this from the short HPD handler that seems + * a bit hard. + */ + if (!intel_dp_link_params_valid(intel_dp, intel_dp->link_rate, + intel_dp->lane_count)) + return false; + + /* Retrain if link not ok */ + return !intel_dp_link_ok(intel_dp, link_status); +} + +static bool intel_dp_has_connector(struct intel_dp *intel_dp, + const struct drm_connector_state *conn_state) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + struct intel_encoder *encoder; + enum pipe pipe; + + if (!conn_state->best_encoder) + return false; + + /* SST */ + encoder = &dp_to_dig_port(intel_dp)->base; + if (conn_state->best_encoder == &encoder->base) + return true; + + /* MST */ + for_each_pipe(i915, pipe) { + encoder = &intel_dp->mst_encoders[pipe]->base; + if (conn_state->best_encoder == &encoder->base) + return true; + } + + return false; +} + +static int intel_dp_prep_link_retrain(struct intel_dp *intel_dp, + struct drm_modeset_acquire_ctx *ctx, + u8 *pipe_mask) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + struct drm_connector_list_iter conn_iter; + struct intel_connector *connector; + int ret = 0; + + *pipe_mask = 0; + + if (!intel_dp_needs_link_retrain(intel_dp)) + return 0; + + drm_connector_list_iter_begin(&i915->drm, &conn_iter); + for_each_intel_connector_iter(connector, &conn_iter) { + struct drm_connector_state *conn_state = + connector->base.state; + struct intel_crtc_state *crtc_state; + struct intel_crtc *crtc; + + if (!intel_dp_has_connector(intel_dp, conn_state)) + continue; + + crtc = to_intel_crtc(conn_state->crtc); + if (!crtc) + continue; + + ret = drm_modeset_lock(&crtc->base.mutex, ctx); + if (ret) + break; + + crtc_state = to_intel_crtc_state(crtc->base.state); + + drm_WARN_ON(&i915->drm, !intel_crtc_has_dp_encoder(crtc_state)); + + if (!crtc_state->hw.active) + continue; + + if (conn_state->commit && + !try_wait_for_completion(&conn_state->commit->hw_done)) + continue; + + *pipe_mask |= BIT(crtc->pipe); + } + drm_connector_list_iter_end(&conn_iter); + + if (!intel_dp_needs_link_retrain(intel_dp)) + *pipe_mask = 0; + + return ret; +} + +static bool intel_dp_is_connected(struct intel_dp *intel_dp) +{ + struct intel_connector *connector = intel_dp->attached_connector; + + return connector->base.status == connector_status_connected || + intel_dp->is_mst; +} + +int intel_dp_retrain_link(struct intel_encoder *encoder, + struct drm_modeset_acquire_ctx *ctx) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + struct intel_crtc *crtc; + u8 pipe_mask; + int ret; + + if (!intel_dp_is_connected(intel_dp)) + return 0; + + ret = drm_modeset_lock(&dev_priv->drm.mode_config.connection_mutex, + ctx); + if (ret) + return ret; + + ret = intel_dp_prep_link_retrain(intel_dp, ctx, &pipe_mask); + if (ret) + return ret; + + if (pipe_mask == 0) + return 0; + + drm_dbg_kms(&dev_priv->drm, "[ENCODER:%d:%s] retraining link\n", + encoder->base.base.id, encoder->base.name); + + for_each_intel_crtc_in_pipe_mask(&dev_priv->drm, crtc, pipe_mask) { + const struct intel_crtc_state *crtc_state = + to_intel_crtc_state(crtc->base.state); + + /* Suppress underruns caused by re-training */ + intel_set_cpu_fifo_underrun_reporting(dev_priv, crtc->pipe, false); + if (crtc_state->has_pch_encoder) + intel_set_pch_fifo_underrun_reporting(dev_priv, + intel_crtc_pch_transcoder(crtc), false); + } + + for_each_intel_crtc_in_pipe_mask(&dev_priv->drm, crtc, pipe_mask) { + const struct intel_crtc_state *crtc_state = + to_intel_crtc_state(crtc->base.state); + + /* retrain on the MST master transcoder */ + if (DISPLAY_VER(dev_priv) >= 12 && + intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST) && + !intel_dp_mst_is_master_trans(crtc_state)) + continue; + + intel_dp_check_frl_training(intel_dp); + intel_dp_pcon_dsc_configure(intel_dp, crtc_state); + intel_dp_start_link_train(intel_dp, crtc_state); + intel_dp_stop_link_train(intel_dp, crtc_state); + break; + } + + for_each_intel_crtc_in_pipe_mask(&dev_priv->drm, crtc, pipe_mask) { + const struct intel_crtc_state *crtc_state = + to_intel_crtc_state(crtc->base.state); + + /* Keep underrun reporting disabled until things are stable */ + intel_crtc_wait_for_next_vblank(crtc); + + intel_set_cpu_fifo_underrun_reporting(dev_priv, crtc->pipe, true); + if (crtc_state->has_pch_encoder) + intel_set_pch_fifo_underrun_reporting(dev_priv, + intel_crtc_pch_transcoder(crtc), true); + } + + return 0; +} + +static int intel_dp_prep_phy_test(struct intel_dp *intel_dp, + struct drm_modeset_acquire_ctx *ctx, + u8 *pipe_mask) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + struct drm_connector_list_iter conn_iter; + struct intel_connector *connector; + int ret = 0; + + *pipe_mask = 0; + + drm_connector_list_iter_begin(&i915->drm, &conn_iter); + for_each_intel_connector_iter(connector, &conn_iter) { + struct drm_connector_state *conn_state = + connector->base.state; + struct intel_crtc_state *crtc_state; + struct intel_crtc *crtc; + + if (!intel_dp_has_connector(intel_dp, conn_state)) + continue; + + crtc = to_intel_crtc(conn_state->crtc); + if (!crtc) + continue; + + ret = drm_modeset_lock(&crtc->base.mutex, ctx); + if (ret) + break; + + crtc_state = to_intel_crtc_state(crtc->base.state); + + drm_WARN_ON(&i915->drm, !intel_crtc_has_dp_encoder(crtc_state)); + + if (!crtc_state->hw.active) + continue; + + if (conn_state->commit && + !try_wait_for_completion(&conn_state->commit->hw_done)) + continue; + + *pipe_mask |= BIT(crtc->pipe); + } + drm_connector_list_iter_end(&conn_iter); + + return ret; +} + +static int intel_dp_do_phy_test(struct intel_encoder *encoder, + struct drm_modeset_acquire_ctx *ctx) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + struct intel_crtc *crtc; + u8 pipe_mask; + int ret; + + ret = drm_modeset_lock(&dev_priv->drm.mode_config.connection_mutex, + ctx); + if (ret) + return ret; + + ret = intel_dp_prep_phy_test(intel_dp, ctx, &pipe_mask); + if (ret) + return ret; + + if (pipe_mask == 0) + return 0; + + drm_dbg_kms(&dev_priv->drm, "[ENCODER:%d:%s] PHY test\n", + encoder->base.base.id, encoder->base.name); + + for_each_intel_crtc_in_pipe_mask(&dev_priv->drm, crtc, pipe_mask) { + const struct intel_crtc_state *crtc_state = + to_intel_crtc_state(crtc->base.state); + + /* test on the MST master transcoder */ + if (DISPLAY_VER(dev_priv) >= 12 && + intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST) && + !intel_dp_mst_is_master_trans(crtc_state)) + continue; + + intel_dp_process_phy_request(intel_dp, crtc_state); + break; + } + + return 0; +} + +void intel_dp_phy_test(struct intel_encoder *encoder) +{ + struct drm_modeset_acquire_ctx ctx; + int ret; + + drm_modeset_acquire_init(&ctx, 0); + + for (;;) { + ret = intel_dp_do_phy_test(encoder, &ctx); + + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + continue; + } + + break; + } + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); + drm_WARN(encoder->base.dev, ret, + "Acquiring modeset locks failed with %i\n", ret); +} + +static void intel_dp_check_device_service_irq(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + u8 val; + + if (intel_dp->dpcd[DP_DPCD_REV] < 0x11) + return; + + if (drm_dp_dpcd_readb(&intel_dp->aux, + DP_DEVICE_SERVICE_IRQ_VECTOR, &val) != 1 || !val) + return; + + drm_dp_dpcd_writeb(&intel_dp->aux, DP_DEVICE_SERVICE_IRQ_VECTOR, val); + + if (val & DP_AUTOMATED_TEST_REQUEST) + intel_dp_handle_test_request(intel_dp); + + if (val & DP_CP_IRQ) + intel_hdcp_handle_cp_irq(intel_dp->attached_connector); + + if (val & DP_SINK_SPECIFIC_IRQ) + drm_dbg_kms(&i915->drm, "Sink specific irq unhandled\n"); +} + +static void intel_dp_check_link_service_irq(struct intel_dp *intel_dp) +{ + u8 val; + + if (intel_dp->dpcd[DP_DPCD_REV] < 0x11) + return; + + if (drm_dp_dpcd_readb(&intel_dp->aux, + DP_LINK_SERVICE_IRQ_VECTOR_ESI0, &val) != 1 || !val) + return; + + if (drm_dp_dpcd_writeb(&intel_dp->aux, + DP_LINK_SERVICE_IRQ_VECTOR_ESI0, val) != 1) + return; + + if (val & HDMI_LINK_STATUS_CHANGED) + intel_dp_handle_hdmi_link_status_change(intel_dp); +} + +/* + * According to DP spec + * 5.1.2: + * 1. Read DPCD + * 2. Configure link according to Receiver Capabilities + * 3. Use Link Training from 2.5.3.3 and 3.5.1.3 + * 4. Check link status on receipt of hot-plug interrupt + * + * intel_dp_short_pulse - handles short pulse interrupts + * when full detection is not required. + * Returns %true if short pulse is handled and full detection + * is NOT required and %false otherwise. + */ +static bool +intel_dp_short_pulse(struct intel_dp *intel_dp) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + u8 old_sink_count = intel_dp->sink_count; + bool ret; + + /* + * Clearing compliance test variables to allow capturing + * of values for next automated test request. + */ + memset(&intel_dp->compliance, 0, sizeof(intel_dp->compliance)); + + /* + * Now read the DPCD to see if it's actually running + * If the current value of sink count doesn't match with + * the value that was stored earlier or dpcd read failed + * we need to do full detection + */ + ret = intel_dp_get_dpcd(intel_dp); + + if ((old_sink_count != intel_dp->sink_count) || !ret) { + /* No need to proceed if we are going to do full detect */ + return false; + } + + intel_dp_check_device_service_irq(intel_dp); + intel_dp_check_link_service_irq(intel_dp); + + /* Handle CEC interrupts, if any */ + drm_dp_cec_irq(&intel_dp->aux); + + /* defer to the hotplug work for link retraining if needed */ + if (intel_dp_needs_link_retrain(intel_dp)) + return false; + + intel_psr_short_pulse(intel_dp); + + switch (intel_dp->compliance.test_type) { + case DP_TEST_LINK_TRAINING: + drm_dbg_kms(&dev_priv->drm, + "Link Training Compliance Test requested\n"); + /* Send a Hotplug Uevent to userspace to start modeset */ + drm_kms_helper_hotplug_event(&dev_priv->drm); + break; + case DP_TEST_LINK_PHY_TEST_PATTERN: + drm_dbg_kms(&dev_priv->drm, + "PHY test pattern Compliance Test requested\n"); + /* + * Schedule long hpd to do the test + * + * FIXME get rid of the ad-hoc phy test modeset code + * and properly incorporate it into the normal modeset. + */ + return false; + } + + return true; +} + +/* XXX this is probably wrong for multiple downstream ports */ +static enum drm_connector_status +intel_dp_detect_dpcd(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + u8 *dpcd = intel_dp->dpcd; + u8 type; + + if (drm_WARN_ON(&i915->drm, intel_dp_is_edp(intel_dp))) + return connector_status_connected; + + lspcon_resume(dig_port); + + if (!intel_dp_get_dpcd(intel_dp)) + return connector_status_disconnected; + + /* if there's no downstream port, we're done */ + if (!drm_dp_is_branch(dpcd)) + return connector_status_connected; + + /* If we're HPD-aware, SINK_COUNT changes dynamically */ + if (intel_dp_has_sink_count(intel_dp) && + intel_dp->downstream_ports[0] & DP_DS_PORT_HPD) { + return intel_dp->sink_count ? + connector_status_connected : connector_status_disconnected; + } + + if (intel_dp_can_mst(intel_dp)) + return connector_status_connected; + + /* If no HPD, poke DDC gently */ + if (drm_probe_ddc(&intel_dp->aux.ddc)) + return connector_status_connected; + + /* Well we tried, say unknown for unreliable port types */ + if (intel_dp->dpcd[DP_DPCD_REV] >= 0x11) { + type = intel_dp->downstream_ports[0] & DP_DS_PORT_TYPE_MASK; + if (type == DP_DS_PORT_TYPE_VGA || + type == DP_DS_PORT_TYPE_NON_EDID) + return connector_status_unknown; + } else { + type = intel_dp->dpcd[DP_DOWNSTREAMPORT_PRESENT] & + DP_DWN_STRM_PORT_TYPE_MASK; + if (type == DP_DWN_STRM_PORT_TYPE_ANALOG || + type == DP_DWN_STRM_PORT_TYPE_OTHER) + return connector_status_unknown; + } + + /* Anything else is out of spec, warn and ignore */ + drm_dbg_kms(&i915->drm, "Broken DP branch device, ignoring\n"); + return connector_status_disconnected; +} + +static enum drm_connector_status +edp_detect(struct intel_dp *intel_dp) +{ + return connector_status_connected; +} + +/* + * intel_digital_port_connected - is the specified port connected? + * @encoder: intel_encoder + * + * In cases where there's a connector physically connected but it can't be used + * by our hardware we also return false, since the rest of the driver should + * pretty much treat the port as disconnected. This is relevant for type-C + * (starting on ICL) where there's ownership involved. + * + * Return %true if port is connected, %false otherwise. + */ +bool intel_digital_port_connected(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + bool is_connected = false; + intel_wakeref_t wakeref; + + with_intel_display_power(dev_priv, POWER_DOMAIN_DISPLAY_CORE, wakeref) + is_connected = dig_port->connected(encoder); + + return is_connected; +} + +static struct edid * +intel_dp_get_edid(struct intel_dp *intel_dp) +{ + struct intel_connector *intel_connector = intel_dp->attached_connector; + + /* use cached edid if we have one */ + if (intel_connector->edid) { + /* invalid edid */ + if (IS_ERR(intel_connector->edid)) + return NULL; + + return drm_edid_duplicate(intel_connector->edid); + } else + return drm_get_edid(&intel_connector->base, + &intel_dp->aux.ddc); +} + +static void +intel_dp_update_dfp(struct intel_dp *intel_dp, + const struct edid *edid) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + struct intel_connector *connector = intel_dp->attached_connector; + + intel_dp->dfp.max_bpc = + drm_dp_downstream_max_bpc(intel_dp->dpcd, + intel_dp->downstream_ports, edid); + + intel_dp->dfp.max_dotclock = + drm_dp_downstream_max_dotclock(intel_dp->dpcd, + intel_dp->downstream_ports); + + intel_dp->dfp.min_tmds_clock = + drm_dp_downstream_min_tmds_clock(intel_dp->dpcd, + intel_dp->downstream_ports, + edid); + intel_dp->dfp.max_tmds_clock = + drm_dp_downstream_max_tmds_clock(intel_dp->dpcd, + intel_dp->downstream_ports, + edid); + + intel_dp->dfp.pcon_max_frl_bw = + drm_dp_get_pcon_max_frl_bw(intel_dp->dpcd, + intel_dp->downstream_ports); + + drm_dbg_kms(&i915->drm, + "[CONNECTOR:%d:%s] DFP max bpc %d, max dotclock %d, TMDS clock %d-%d, PCON Max FRL BW %dGbps\n", + connector->base.base.id, connector->base.name, + intel_dp->dfp.max_bpc, + intel_dp->dfp.max_dotclock, + intel_dp->dfp.min_tmds_clock, + intel_dp->dfp.max_tmds_clock, + intel_dp->dfp.pcon_max_frl_bw); + + intel_dp_get_pcon_dsc_cap(intel_dp); +} + +static void +intel_dp_update_420(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + struct intel_connector *connector = intel_dp->attached_connector; + bool is_branch, ycbcr_420_passthrough, ycbcr_444_to_420, rgb_to_ycbcr; + + /* No YCbCr output support on gmch platforms */ + if (HAS_GMCH(i915)) + return; + + /* + * ILK doesn't seem capable of DP YCbCr output. The + * displayed image is severly corrupted. SNB+ is fine. + */ + if (IS_IRONLAKE(i915)) + return; + + is_branch = drm_dp_is_branch(intel_dp->dpcd); + ycbcr_420_passthrough = + drm_dp_downstream_420_passthrough(intel_dp->dpcd, + intel_dp->downstream_ports); + /* on-board LSPCON always assumed to support 4:4:4->4:2:0 conversion */ + ycbcr_444_to_420 = + dp_to_dig_port(intel_dp)->lspcon.active || + drm_dp_downstream_444_to_420_conversion(intel_dp->dpcd, + intel_dp->downstream_ports); + rgb_to_ycbcr = drm_dp_downstream_rgb_to_ycbcr_conversion(intel_dp->dpcd, + intel_dp->downstream_ports, + DP_DS_HDMI_BT709_RGB_YCBCR_CONV); + + if (DISPLAY_VER(i915) >= 11) { + /* Let PCON convert from RGB->YCbCr if possible */ + if (is_branch && rgb_to_ycbcr && ycbcr_444_to_420) { + intel_dp->dfp.rgb_to_ycbcr = true; + intel_dp->dfp.ycbcr_444_to_420 = true; + connector->base.ycbcr_420_allowed = true; + } else { + /* Prefer 4:2:0 passthrough over 4:4:4->4:2:0 conversion */ + intel_dp->dfp.ycbcr_444_to_420 = + ycbcr_444_to_420 && !ycbcr_420_passthrough; + + connector->base.ycbcr_420_allowed = + !is_branch || ycbcr_444_to_420 || ycbcr_420_passthrough; + } + } else { + /* 4:4:4->4:2:0 conversion is the only way */ + intel_dp->dfp.ycbcr_444_to_420 = ycbcr_444_to_420; + + connector->base.ycbcr_420_allowed = ycbcr_444_to_420; + } + + drm_dbg_kms(&i915->drm, + "[CONNECTOR:%d:%s] RGB->YcbCr conversion? %s, YCbCr 4:2:0 allowed? %s, YCbCr 4:4:4->4:2:0 conversion? %s\n", + connector->base.base.id, connector->base.name, + str_yes_no(intel_dp->dfp.rgb_to_ycbcr), + str_yes_no(connector->base.ycbcr_420_allowed), + str_yes_no(intel_dp->dfp.ycbcr_444_to_420)); +} + +static void +intel_dp_set_edid(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + struct intel_connector *connector = intel_dp->attached_connector; + struct edid *edid; + bool vrr_capable; + + intel_dp_unset_edid(intel_dp); + edid = intel_dp_get_edid(intel_dp); + connector->detect_edid = edid; + + vrr_capable = intel_vrr_is_capable(connector); + drm_dbg_kms(&i915->drm, "[CONNECTOR:%d:%s] VRR capable: %s\n", + connector->base.base.id, connector->base.name, str_yes_no(vrr_capable)); + drm_connector_set_vrr_capable_property(&connector->base, vrr_capable); + + intel_dp_update_dfp(intel_dp, edid); + intel_dp_update_420(intel_dp); + + if (edid && edid->input & DRM_EDID_INPUT_DIGITAL) { + intel_dp->has_hdmi_sink = drm_detect_hdmi_monitor(edid); + intel_dp->has_audio = drm_detect_monitor_audio(edid); + } + + drm_dp_cec_set_edid(&intel_dp->aux, edid); +} + +static void +intel_dp_unset_edid(struct intel_dp *intel_dp) +{ + struct intel_connector *connector = intel_dp->attached_connector; + + drm_dp_cec_unset_edid(&intel_dp->aux); + kfree(connector->detect_edid); + connector->detect_edid = NULL; + + intel_dp->has_hdmi_sink = false; + intel_dp->has_audio = false; + + intel_dp->dfp.max_bpc = 0; + intel_dp->dfp.max_dotclock = 0; + intel_dp->dfp.min_tmds_clock = 0; + intel_dp->dfp.max_tmds_clock = 0; + + intel_dp->dfp.pcon_max_frl_bw = 0; + + intel_dp->dfp.ycbcr_444_to_420 = false; + connector->base.ycbcr_420_allowed = false; + + drm_connector_set_vrr_capable_property(&connector->base, + false); +} + +static int +intel_dp_detect(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + bool force) +{ + struct drm_i915_private *dev_priv = to_i915(connector->dev); + struct intel_dp *intel_dp = intel_attached_dp(to_intel_connector(connector)); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + struct intel_encoder *encoder = &dig_port->base; + enum drm_connector_status status; + + drm_dbg_kms(&dev_priv->drm, "[CONNECTOR:%d:%s]\n", + connector->base.id, connector->name); + drm_WARN_ON(&dev_priv->drm, + !drm_modeset_is_locked(&dev_priv->drm.mode_config.connection_mutex)); + + if (!INTEL_DISPLAY_ENABLED(dev_priv)) + return connector_status_disconnected; + + /* Can't disconnect eDP */ + if (intel_dp_is_edp(intel_dp)) + status = edp_detect(intel_dp); + else if (intel_digital_port_connected(encoder)) + status = intel_dp_detect_dpcd(intel_dp); + else + status = connector_status_disconnected; + + if (status == connector_status_disconnected) { + memset(&intel_dp->compliance, 0, sizeof(intel_dp->compliance)); + memset(intel_dp->dsc_dpcd, 0, sizeof(intel_dp->dsc_dpcd)); + + if (intel_dp->is_mst) { + drm_dbg_kms(&dev_priv->drm, + "MST device may have disappeared %d vs %d\n", + intel_dp->is_mst, + intel_dp->mst_mgr.mst_state); + intel_dp->is_mst = false; + drm_dp_mst_topology_mgr_set_mst(&intel_dp->mst_mgr, + intel_dp->is_mst); + } + + goto out; + } + + /* Read DP Sink DSC Cap DPCD regs for DP v1.4 */ + if (DISPLAY_VER(dev_priv) >= 11) + intel_dp_get_dsc_sink_cap(intel_dp); + + intel_dp_configure_mst(intel_dp); + + /* + * TODO: Reset link params when switching to MST mode, until MST + * supports link training fallback params. + */ + if (intel_dp->reset_link_params || intel_dp->is_mst) { + intel_dp_reset_max_link_params(intel_dp); + intel_dp->reset_link_params = false; + } + + intel_dp_print_rates(intel_dp); + + if (intel_dp->is_mst) { + /* + * If we are in MST mode then this connector + * won't appear connected or have anything + * with EDID on it + */ + status = connector_status_disconnected; + goto out; + } + + /* + * Some external monitors do not signal loss of link synchronization + * with an IRQ_HPD, so force a link status check. + */ + if (!intel_dp_is_edp(intel_dp)) { + int ret; + + ret = intel_dp_retrain_link(encoder, ctx); + if (ret) + return ret; + } + + /* + * Clearing NACK and defer counts to get their exact values + * while reading EDID which are required by Compliance tests + * 4.2.2.4 and 4.2.2.5 + */ + intel_dp->aux.i2c_nack_count = 0; + intel_dp->aux.i2c_defer_count = 0; + + intel_dp_set_edid(intel_dp); + if (intel_dp_is_edp(intel_dp) || + to_intel_connector(connector)->detect_edid) + status = connector_status_connected; + + intel_dp_check_device_service_irq(intel_dp); + +out: + if (status != connector_status_connected && !intel_dp->is_mst) + intel_dp_unset_edid(intel_dp); + + /* + * Make sure the refs for power wells enabled during detect are + * dropped to avoid a new detect cycle triggered by HPD polling. + */ + intel_display_power_flush_work(dev_priv); + + if (!intel_dp_is_edp(intel_dp)) + drm_dp_set_subconnector_property(connector, + status, + intel_dp->dpcd, + intel_dp->downstream_ports); + return status; +} + +static void +intel_dp_force(struct drm_connector *connector) +{ + struct intel_dp *intel_dp = intel_attached_dp(to_intel_connector(connector)); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + struct intel_encoder *intel_encoder = &dig_port->base; + struct drm_i915_private *dev_priv = to_i915(intel_encoder->base.dev); + enum intel_display_power_domain aux_domain = + intel_aux_power_domain(dig_port); + intel_wakeref_t wakeref; + + drm_dbg_kms(&dev_priv->drm, "[CONNECTOR:%d:%s]\n", + connector->base.id, connector->name); + intel_dp_unset_edid(intel_dp); + + if (connector->status != connector_status_connected) + return; + + wakeref = intel_display_power_get(dev_priv, aux_domain); + + intel_dp_set_edid(intel_dp); + + intel_display_power_put(dev_priv, aux_domain, wakeref); +} + +static int intel_dp_get_modes(struct drm_connector *connector) +{ + struct intel_connector *intel_connector = to_intel_connector(connector); + struct edid *edid; + int num_modes = 0; + + edid = intel_connector->detect_edid; + if (edid) + num_modes = intel_connector_update_modes(connector, edid); + + /* Also add fixed mode, which may or may not be present in EDID */ + if (intel_dp_is_edp(intel_attached_dp(intel_connector))) + num_modes += intel_panel_get_modes(intel_connector); + + if (num_modes) + return num_modes; + + if (!edid) { + struct intel_dp *intel_dp = intel_attached_dp(intel_connector); + struct drm_display_mode *mode; + + mode = drm_dp_downstream_mode(connector->dev, + intel_dp->dpcd, + intel_dp->downstream_ports); + if (mode) { + drm_mode_probed_add(connector, mode); + num_modes++; + } + } + + return num_modes; +} + +static int +intel_dp_connector_register(struct drm_connector *connector) +{ + struct drm_i915_private *i915 = to_i915(connector->dev); + struct intel_dp *intel_dp = intel_attached_dp(to_intel_connector(connector)); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + struct intel_lspcon *lspcon = &dig_port->lspcon; + int ret; + + ret = intel_connector_register(connector); + if (ret) + return ret; + + drm_dbg_kms(&i915->drm, "registering %s bus for %s\n", + intel_dp->aux.name, connector->kdev->kobj.name); + + intel_dp->aux.dev = connector->kdev; + ret = drm_dp_aux_register(&intel_dp->aux); + if (!ret) + drm_dp_cec_register_connector(&intel_dp->aux, connector); + + if (!intel_bios_is_lspcon_present(i915, dig_port->base.port)) + return ret; + + /* + * ToDo: Clean this up to handle lspcon init and resume more + * efficiently and streamlined. + */ + if (lspcon_init(dig_port)) { + lspcon_detect_hdr_capability(lspcon); + if (lspcon->hdr_supported) + drm_connector_attach_hdr_output_metadata_property(connector); + } + + return ret; +} + +static void +intel_dp_connector_unregister(struct drm_connector *connector) +{ + struct intel_dp *intel_dp = intel_attached_dp(to_intel_connector(connector)); + + drm_dp_cec_unregister_connector(&intel_dp->aux); + drm_dp_aux_unregister(&intel_dp->aux); + intel_connector_unregister(connector); +} + +void intel_dp_encoder_flush_work(struct drm_encoder *encoder) +{ + struct intel_digital_port *dig_port = enc_to_dig_port(to_intel_encoder(encoder)); + struct intel_dp *intel_dp = &dig_port->dp; + + intel_dp_mst_encoder_cleanup(dig_port); + + intel_pps_vdd_off_sync(intel_dp); + + intel_dp_aux_fini(intel_dp); +} + +void intel_dp_encoder_suspend(struct intel_encoder *intel_encoder) +{ + struct intel_dp *intel_dp = enc_to_intel_dp(intel_encoder); + + intel_pps_vdd_off_sync(intel_dp); +} + +void intel_dp_encoder_shutdown(struct intel_encoder *intel_encoder) +{ + struct intel_dp *intel_dp = enc_to_intel_dp(intel_encoder); + + intel_pps_wait_power_cycle(intel_dp); +} + +static int intel_modeset_tile_group(struct intel_atomic_state *state, + int tile_group_id) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct drm_connector_list_iter conn_iter; + struct drm_connector *connector; + int ret = 0; + + drm_connector_list_iter_begin(&dev_priv->drm, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + struct drm_connector_state *conn_state; + struct intel_crtc_state *crtc_state; + struct intel_crtc *crtc; + + if (!connector->has_tile || + connector->tile_group->id != tile_group_id) + continue; + + conn_state = drm_atomic_get_connector_state(&state->base, + connector); + if (IS_ERR(conn_state)) { + ret = PTR_ERR(conn_state); + break; + } + + crtc = to_intel_crtc(conn_state->crtc); + + if (!crtc) + continue; + + crtc_state = intel_atomic_get_new_crtc_state(state, crtc); + crtc_state->uapi.mode_changed = true; + + ret = drm_atomic_add_affected_planes(&state->base, &crtc->base); + if (ret) + break; + } + drm_connector_list_iter_end(&conn_iter); + + return ret; +} + +static int intel_modeset_affected_transcoders(struct intel_atomic_state *state, u8 transcoders) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_crtc *crtc; + + if (transcoders == 0) + return 0; + + for_each_intel_crtc(&dev_priv->drm, crtc) { + struct intel_crtc_state *crtc_state; + int ret; + + crtc_state = intel_atomic_get_crtc_state(&state->base, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + if (!crtc_state->hw.enable) + continue; + + if (!(transcoders & BIT(crtc_state->cpu_transcoder))) + continue; + + crtc_state->uapi.mode_changed = true; + + ret = drm_atomic_add_affected_connectors(&state->base, &crtc->base); + if (ret) + return ret; + + ret = drm_atomic_add_affected_planes(&state->base, &crtc->base); + if (ret) + return ret; + + transcoders &= ~BIT(crtc_state->cpu_transcoder); + } + + drm_WARN_ON(&dev_priv->drm, transcoders != 0); + + return 0; +} + +static int intel_modeset_synced_crtcs(struct intel_atomic_state *state, + struct drm_connector *connector) +{ + const struct drm_connector_state *old_conn_state = + drm_atomic_get_old_connector_state(&state->base, connector); + const struct intel_crtc_state *old_crtc_state; + struct intel_crtc *crtc; + u8 transcoders; + + crtc = to_intel_crtc(old_conn_state->crtc); + if (!crtc) + return 0; + + old_crtc_state = intel_atomic_get_old_crtc_state(state, crtc); + + if (!old_crtc_state->hw.active) + return 0; + + transcoders = old_crtc_state->sync_mode_slaves_mask; + if (old_crtc_state->master_transcoder != INVALID_TRANSCODER) + transcoders |= BIT(old_crtc_state->master_transcoder); + + return intel_modeset_affected_transcoders(state, + transcoders); +} + +static int intel_dp_connector_atomic_check(struct drm_connector *conn, + struct drm_atomic_state *_state) +{ + struct drm_i915_private *dev_priv = to_i915(conn->dev); + struct intel_atomic_state *state = to_intel_atomic_state(_state); + struct drm_connector_state *conn_state = drm_atomic_get_new_connector_state(_state, conn); + struct intel_connector *intel_conn = to_intel_connector(conn); + struct intel_dp *intel_dp = enc_to_intel_dp(intel_conn->encoder); + int ret; + + ret = intel_digital_connector_atomic_check(conn, &state->base); + if (ret) + return ret; + + if (intel_dp_mst_source_support(intel_dp)) { + ret = drm_dp_mst_root_conn_atomic_check(conn_state, &intel_dp->mst_mgr); + if (ret) + return ret; + } + + /* + * We don't enable port sync on BDW due to missing w/as and + * due to not having adjusted the modeset sequence appropriately. + */ + if (DISPLAY_VER(dev_priv) < 9) + return 0; + + if (!intel_connector_needs_modeset(state, conn)) + return 0; + + if (conn->has_tile) { + ret = intel_modeset_tile_group(state, conn->tile_group->id); + if (ret) + return ret; + } + + return intel_modeset_synced_crtcs(state, conn); +} + +static void intel_dp_oob_hotplug_event(struct drm_connector *connector) +{ + struct intel_encoder *encoder = intel_attached_encoder(to_intel_connector(connector)); + struct drm_i915_private *i915 = to_i915(connector->dev); + + spin_lock_irq(&i915->irq_lock); + i915->display.hotplug.event_bits |= BIT(encoder->hpd_pin); + spin_unlock_irq(&i915->irq_lock); + queue_delayed_work(system_wq, &i915->display.hotplug.hotplug_work, 0); +} + +static const struct drm_connector_funcs intel_dp_connector_funcs = { + .force = intel_dp_force, + .fill_modes = drm_helper_probe_single_connector_modes, + .atomic_get_property = intel_digital_connector_atomic_get_property, + .atomic_set_property = intel_digital_connector_atomic_set_property, + .late_register = intel_dp_connector_register, + .early_unregister = intel_dp_connector_unregister, + .destroy = intel_connector_destroy, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .atomic_duplicate_state = intel_digital_connector_duplicate_state, + .oob_hotplug_event = intel_dp_oob_hotplug_event, +}; + +static const struct drm_connector_helper_funcs intel_dp_connector_helper_funcs = { + .detect_ctx = intel_dp_detect, + .get_modes = intel_dp_get_modes, + .mode_valid = intel_dp_mode_valid, + .atomic_check = intel_dp_connector_atomic_check, +}; + +enum irqreturn +intel_dp_hpd_pulse(struct intel_digital_port *dig_port, bool long_hpd) +{ + struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev); + struct intel_dp *intel_dp = &dig_port->dp; + + if (dig_port->base.type == INTEL_OUTPUT_EDP && + (long_hpd || !intel_pps_have_panel_power_or_vdd(intel_dp))) { + /* + * vdd off can generate a long/short pulse on eDP which + * would require vdd on to handle it, and thus we + * would end up in an endless cycle of + * "vdd off -> long/short hpd -> vdd on -> detect -> vdd off -> ..." + */ + drm_dbg_kms(&i915->drm, + "ignoring %s hpd on eDP [ENCODER:%d:%s]\n", + long_hpd ? "long" : "short", + dig_port->base.base.base.id, + dig_port->base.base.name); + return IRQ_HANDLED; + } + + drm_dbg_kms(&i915->drm, "got hpd irq on [ENCODER:%d:%s] - %s\n", + dig_port->base.base.base.id, + dig_port->base.base.name, + long_hpd ? "long" : "short"); + + if (long_hpd) { + intel_dp->reset_link_params = true; + return IRQ_NONE; + } + + if (intel_dp->is_mst) { + if (!intel_dp_check_mst_status(intel_dp)) + return IRQ_NONE; + } else if (!intel_dp_short_pulse(intel_dp)) { + return IRQ_NONE; + } + + return IRQ_HANDLED; +} + +/* check the VBT to see whether the eDP is on another port */ +bool intel_dp_is_port_edp(struct drm_i915_private *dev_priv, enum port port) +{ + /* + * eDP not supported on g4x. so bail out early just + * for a bit extra safety in case the VBT is bonkers. + */ + if (DISPLAY_VER(dev_priv) < 5) + return false; + + if (DISPLAY_VER(dev_priv) < 9 && port == PORT_A) + return true; + + return intel_bios_is_port_edp(dev_priv, port); +} + +static bool +has_gamut_metadata_dip(struct drm_i915_private *i915, enum port port) +{ + if (intel_bios_is_lspcon_present(i915, port)) + return false; + + if (DISPLAY_VER(i915) >= 11) + return true; + + if (port == PORT_A) + return false; + + if (IS_HASWELL(i915) || IS_BROADWELL(i915) || + DISPLAY_VER(i915) >= 9) + return true; + + return false; +} + +static void +intel_dp_add_properties(struct intel_dp *intel_dp, struct drm_connector *connector) +{ + struct drm_i915_private *dev_priv = to_i915(connector->dev); + enum port port = dp_to_dig_port(intel_dp)->base.port; + + if (!intel_dp_is_edp(intel_dp)) + drm_connector_attach_dp_subconnector_property(connector); + + if (!IS_G4X(dev_priv) && port != PORT_A) + intel_attach_force_audio_property(connector); + + intel_attach_broadcast_rgb_property(connector); + if (HAS_GMCH(dev_priv)) + drm_connector_attach_max_bpc_property(connector, 6, 10); + else if (DISPLAY_VER(dev_priv) >= 5) + drm_connector_attach_max_bpc_property(connector, 6, 12); + + /* Register HDMI colorspace for case of lspcon */ + if (intel_bios_is_lspcon_present(dev_priv, port)) { + drm_connector_attach_content_type_property(connector); + intel_attach_hdmi_colorspace_property(connector); + } else { + intel_attach_dp_colorspace_property(connector); + } + + if (has_gamut_metadata_dip(dev_priv, port)) + drm_connector_attach_hdr_output_metadata_property(connector); + + if (intel_dp_is_edp(intel_dp)) { + u32 allowed_scalers; + + allowed_scalers = BIT(DRM_MODE_SCALE_ASPECT) | BIT(DRM_MODE_SCALE_FULLSCREEN); + if (!HAS_GMCH(dev_priv)) + allowed_scalers |= BIT(DRM_MODE_SCALE_CENTER); + + drm_connector_attach_scaling_mode_property(connector, allowed_scalers); + + connector->state->scaling_mode = DRM_MODE_SCALE_ASPECT; + + } + + if (HAS_VRR(dev_priv)) + drm_connector_attach_vrr_capable_property(connector); +} + +static void +intel_edp_add_properties(struct intel_dp *intel_dp) +{ + struct intel_connector *connector = intel_dp->attached_connector; + struct drm_i915_private *i915 = to_i915(connector->base.dev); + const struct drm_display_mode *fixed_mode = + intel_panel_preferred_fixed_mode(connector); + + if (!fixed_mode) + return; + + drm_connector_set_panel_orientation_with_quirk(&connector->base, + i915->display.vbt.orientation, + fixed_mode->hdisplay, + fixed_mode->vdisplay); +} + +static bool intel_edp_init_connector(struct intel_dp *intel_dp, + struct intel_connector *intel_connector) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + struct drm_device *dev = &dev_priv->drm; + struct drm_connector *connector = &intel_connector->base; + struct drm_display_mode *fixed_mode; + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + bool has_dpcd; + enum pipe pipe = INVALID_PIPE; + struct edid *edid; + + if (!intel_dp_is_edp(intel_dp)) + return true; + + /* + * On IBX/CPT we may get here with LVDS already registered. Since the + * driver uses the only internal power sequencer available for both + * eDP and LVDS bail out early in this case to prevent interfering + * with an already powered-on LVDS power sequencer. + */ + if (intel_get_lvds_encoder(dev_priv)) { + drm_WARN_ON(dev, + !(HAS_PCH_IBX(dev_priv) || HAS_PCH_CPT(dev_priv))); + drm_info(&dev_priv->drm, + "LVDS was detected, not registering eDP\n"); + + return false; + } + + intel_bios_init_panel_early(dev_priv, &intel_connector->panel, + encoder->devdata); + + intel_pps_init(intel_dp); + + /* Cache DPCD and EDID for edp. */ + has_dpcd = intel_edp_init_dpcd(intel_dp); + + if (!has_dpcd) { + /* if this fails, presume the device is a ghost */ + drm_info(&dev_priv->drm, + "failed to retrieve link info, disabling eDP\n"); + goto out_vdd_off; + } + + mutex_lock(&dev->mode_config.mutex); + edid = drm_get_edid(connector, &intel_dp->aux.ddc); + if (!edid) { + /* Fallback to EDID from ACPI OpRegion, if any */ + edid = intel_opregion_get_edid(intel_connector); + if (edid) + drm_dbg_kms(&dev_priv->drm, + "[CONNECTOR:%d:%s] Using OpRegion EDID\n", + connector->base.id, connector->name); + } + if (edid) { + if (drm_add_edid_modes(connector, edid)) { + drm_connector_update_edid_property(connector, edid); + } else { + kfree(edid); + edid = ERR_PTR(-EINVAL); + } + } else { + edid = ERR_PTR(-ENOENT); + } + intel_connector->edid = edid; + + intel_bios_init_panel_late(dev_priv, &intel_connector->panel, + encoder->devdata, IS_ERR(edid) ? NULL : edid); + + intel_panel_add_edid_fixed_modes(intel_connector, true); + + /* MSO requires information from the EDID */ + intel_edp_mso_init(intel_dp); + + /* multiply the mode clock and horizontal timings for MSO */ + list_for_each_entry(fixed_mode, &intel_connector->panel.fixed_modes, head) + intel_edp_mso_mode_fixup(intel_connector, fixed_mode); + + /* fallback to VBT if available for eDP */ + if (!intel_panel_preferred_fixed_mode(intel_connector)) + intel_panel_add_vbt_lfp_fixed_mode(intel_connector); + + mutex_unlock(&dev->mode_config.mutex); + + if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { + /* + * Figure out the current pipe for the initial backlight setup. + * If the current pipe isn't valid, try the PPS pipe, and if that + * fails just assume pipe A. + */ + pipe = vlv_active_pipe(intel_dp); + + if (pipe != PIPE_A && pipe != PIPE_B) + pipe = intel_dp->pps.pps_pipe; + + if (pipe != PIPE_A && pipe != PIPE_B) + pipe = PIPE_A; + + drm_dbg_kms(&dev_priv->drm, + "using pipe %c for initial backlight setup\n", + pipe_name(pipe)); + } + + intel_panel_init(intel_connector); + + intel_backlight_setup(intel_connector, pipe); + + intel_edp_add_properties(intel_dp); + + intel_pps_init_late(intel_dp); + + return true; + +out_vdd_off: + intel_pps_vdd_off_sync(intel_dp); + + return false; +} + +static void intel_dp_modeset_retry_work_fn(struct work_struct *work) +{ + struct intel_connector *intel_connector; + struct drm_connector *connector; + + intel_connector = container_of(work, typeof(*intel_connector), + modeset_retry_work); + connector = &intel_connector->base; + drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s]\n", connector->base.id, + connector->name); + + /* Grab the locks before changing connector property*/ + mutex_lock(&connector->dev->mode_config.mutex); + /* Set connector link status to BAD and send a Uevent to notify + * userspace to do a modeset. + */ + drm_connector_set_link_status_property(connector, + DRM_MODE_LINK_STATUS_BAD); + mutex_unlock(&connector->dev->mode_config.mutex); + /* Send Hotplug uevent so userspace can reprobe */ + drm_kms_helper_connector_hotplug_event(connector); +} + +bool +intel_dp_init_connector(struct intel_digital_port *dig_port, + struct intel_connector *intel_connector) +{ + struct drm_connector *connector = &intel_connector->base; + struct intel_dp *intel_dp = &dig_port->dp; + struct intel_encoder *intel_encoder = &dig_port->base; + struct drm_device *dev = intel_encoder->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + enum port port = intel_encoder->port; + enum phy phy = intel_port_to_phy(dev_priv, port); + int type; + + /* Initialize the work for modeset in case of link train failure */ + INIT_WORK(&intel_connector->modeset_retry_work, + intel_dp_modeset_retry_work_fn); + + if (drm_WARN(dev, dig_port->max_lanes < 1, + "Not enough lanes (%d) for DP on [ENCODER:%d:%s]\n", + dig_port->max_lanes, intel_encoder->base.base.id, + intel_encoder->base.name)) + return false; + + intel_dp->reset_link_params = true; + intel_dp->pps.pps_pipe = INVALID_PIPE; + intel_dp->pps.active_pipe = INVALID_PIPE; + + /* Preserve the current hw state. */ + intel_dp->DP = intel_de_read(dev_priv, intel_dp->output_reg); + intel_dp->attached_connector = intel_connector; + + if (intel_dp_is_port_edp(dev_priv, port)) { + /* + * Currently we don't support eDP on TypeC ports, although in + * theory it could work on TypeC legacy ports. + */ + drm_WARN_ON(dev, intel_phy_is_tc(dev_priv, phy)); + type = DRM_MODE_CONNECTOR_eDP; + intel_encoder->type = INTEL_OUTPUT_EDP; + + /* eDP only on port B and/or C on vlv/chv */ + if (drm_WARN_ON(dev, (IS_VALLEYVIEW(dev_priv) || + IS_CHERRYVIEW(dev_priv)) && + port != PORT_B && port != PORT_C)) + return false; + } else { + type = DRM_MODE_CONNECTOR_DisplayPort; + } + + intel_dp_set_default_sink_rates(intel_dp); + intel_dp_set_default_max_sink_lane_count(intel_dp); + + if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) + intel_dp->pps.active_pipe = vlv_active_pipe(intel_dp); + + drm_dbg_kms(&dev_priv->drm, + "Adding %s connector on [ENCODER:%d:%s]\n", + type == DRM_MODE_CONNECTOR_eDP ? "eDP" : "DP", + intel_encoder->base.base.id, intel_encoder->base.name); + + drm_connector_init(dev, connector, &intel_dp_connector_funcs, type); + drm_connector_helper_add(connector, &intel_dp_connector_helper_funcs); + + if (!HAS_GMCH(dev_priv)) + connector->interlace_allowed = true; + connector->doublescan_allowed = 0; + + intel_connector->polled = DRM_CONNECTOR_POLL_HPD; + + intel_dp_aux_init(intel_dp); + + intel_connector_attach_encoder(intel_connector, intel_encoder); + + if (HAS_DDI(dev_priv)) + intel_connector->get_hw_state = intel_ddi_connector_get_hw_state; + else + intel_connector->get_hw_state = intel_connector_get_hw_state; + + if (!intel_edp_init_connector(intel_dp, intel_connector)) { + intel_dp_aux_fini(intel_dp); + goto fail; + } + + intel_dp_set_source_rates(intel_dp); + intel_dp_set_common_rates(intel_dp); + intel_dp_reset_max_link_params(intel_dp); + + /* init MST on ports that can support it */ + intel_dp_mst_encoder_init(dig_port, + intel_connector->base.base.id); + + intel_dp_add_properties(intel_dp, connector); + + if (is_hdcp_supported(dev_priv, port) && !intel_dp_is_edp(intel_dp)) { + int ret = intel_dp_hdcp_init(dig_port, intel_connector); + if (ret) + drm_dbg_kms(&dev_priv->drm, + "HDCP init failed, skipping.\n"); + } + + /* For G4X desktop chip, PEG_BAND_GAP_DATA 3:0 must first be written + * 0xd. Failure to do so will result in spurious interrupts being + * generated on the port when a cable is not attached. + */ + if (IS_G45(dev_priv)) { + u32 temp = intel_de_read(dev_priv, PEG_BAND_GAP_DATA); + intel_de_write(dev_priv, PEG_BAND_GAP_DATA, + (temp & ~0xf) | 0xd); + } + + intel_dp->frl.is_trained = false; + intel_dp->frl.trained_rate_gbps = 0; + + intel_psr_init(intel_dp); + + return true; + +fail: + drm_connector_cleanup(connector); + + return false; +} + +void intel_dp_mst_suspend(struct drm_i915_private *dev_priv) +{ + struct intel_encoder *encoder; + + if (!HAS_DISPLAY(dev_priv)) + return; + + for_each_intel_encoder(&dev_priv->drm, encoder) { + struct intel_dp *intel_dp; + + if (encoder->type != INTEL_OUTPUT_DDI) + continue; + + intel_dp = enc_to_intel_dp(encoder); + + if (!intel_dp_mst_source_support(intel_dp)) + continue; + + if (intel_dp->is_mst) + drm_dp_mst_topology_mgr_suspend(&intel_dp->mst_mgr); + } +} + +void intel_dp_mst_resume(struct drm_i915_private *dev_priv) +{ + struct intel_encoder *encoder; + + if (!HAS_DISPLAY(dev_priv)) + return; + + for_each_intel_encoder(&dev_priv->drm, encoder) { + struct intel_dp *intel_dp; + int ret; + + if (encoder->type != INTEL_OUTPUT_DDI) + continue; + + intel_dp = enc_to_intel_dp(encoder); + + if (!intel_dp_mst_source_support(intel_dp)) + continue; + + ret = drm_dp_mst_topology_mgr_resume(&intel_dp->mst_mgr, + true); + if (ret) { + intel_dp->is_mst = false; + drm_dp_mst_topology_mgr_set_mst(&intel_dp->mst_mgr, + false); + } + } +} diff --git a/drivers/gpu/drm/i915/display/intel_dp.h b/drivers/gpu/drm/i915/display/intel_dp.h new file mode 100644 index 000000000..a54902c71 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dp.h @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_DP_H__ +#define __INTEL_DP_H__ + +#include + +enum intel_output_format; +enum pipe; +enum port; +struct drm_connector_state; +struct drm_encoder; +struct drm_i915_private; +struct drm_modeset_acquire_ctx; +struct drm_dp_vsc_sdp; +struct intel_atomic_state; +struct intel_connector; +struct intel_crtc_state; +struct intel_digital_port; +struct intel_dp; +struct intel_encoder; + +struct link_config_limits { + int min_rate, max_rate; + int min_lane_count, max_lane_count; + int min_bpp, max_bpp; +}; + +void intel_edp_fixup_vbt_bpp(struct intel_encoder *encoder, int pipe_bpp); +void intel_dp_adjust_compliance_config(struct intel_dp *intel_dp, + struct intel_crtc_state *pipe_config, + struct link_config_limits *limits); +bool intel_dp_limited_color_range(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state); +int intel_dp_min_bpp(enum intel_output_format output_format); +bool intel_dp_init_connector(struct intel_digital_port *dig_port, + struct intel_connector *intel_connector); +void intel_dp_set_link_params(struct intel_dp *intel_dp, + int link_rate, int lane_count); +int intel_dp_get_link_train_fallback_values(struct intel_dp *intel_dp, + int link_rate, u8 lane_count); +int intel_dp_retrain_link(struct intel_encoder *encoder, + struct drm_modeset_acquire_ctx *ctx); +void intel_dp_set_power(struct intel_dp *intel_dp, u8 mode); +void intel_dp_configure_protocol_converter(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state); +void intel_dp_sink_set_decompression_state(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + bool enable); +void intel_dp_encoder_suspend(struct intel_encoder *intel_encoder); +void intel_dp_encoder_shutdown(struct intel_encoder *intel_encoder); +void intel_dp_encoder_flush_work(struct drm_encoder *encoder); +int intel_dp_compute_config(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config, + struct drm_connector_state *conn_state); +bool intel_dp_is_edp(struct intel_dp *intel_dp); +bool intel_dp_is_uhbr(const struct intel_crtc_state *crtc_state); +bool intel_dp_is_port_edp(struct drm_i915_private *dev_priv, enum port port); +enum irqreturn intel_dp_hpd_pulse(struct intel_digital_port *dig_port, + bool long_hpd); +void intel_edp_backlight_on(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state); +void intel_edp_backlight_off(const struct drm_connector_state *conn_state); +void intel_edp_fixup_vbt_bpp(struct intel_encoder *encoder, int pipe_bpp); +void intel_dp_mst_suspend(struct drm_i915_private *dev_priv); +void intel_dp_mst_resume(struct drm_i915_private *dev_priv); +int intel_dp_max_link_rate(struct intel_dp *intel_dp); +int intel_dp_max_lane_count(struct intel_dp *intel_dp); +int intel_dp_rate_select(struct intel_dp *intel_dp, int rate); + +void intel_dp_compute_rate(struct intel_dp *intel_dp, int port_clock, + u8 *link_bw, u8 *rate_select); +bool intel_dp_source_supports_tps3(struct drm_i915_private *i915); +bool intel_dp_source_supports_tps4(struct drm_i915_private *i915); + +bool intel_dp_get_colorimetry_status(struct intel_dp *intel_dp); +int intel_dp_link_required(int pixel_clock, int bpp); +int intel_dp_max_data_rate(int max_link_rate, int max_lanes); +bool intel_dp_can_bigjoiner(struct intel_dp *intel_dp); +bool intel_dp_needs_vsc_sdp(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state); +void intel_dp_compute_psr_vsc_sdp(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state, + struct drm_dp_vsc_sdp *vsc); +void intel_write_dp_vsc_sdp(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_dp_vsc_sdp *vsc); +void intel_dp_set_infoframes(struct intel_encoder *encoder, bool enable, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state); +void intel_read_dp_sdp(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state, + unsigned int type); +bool intel_digital_port_connected(struct intel_encoder *encoder); + +static inline unsigned int intel_dp_unused_lane_mask(int lane_count) +{ + return ~((1 << lane_count) - 1) & 0xf; +} + +u32 intel_dp_mode_to_fec_clock(u32 mode_clock); + +void intel_ddi_update_pipe(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state); + +bool intel_dp_initial_fastset_check(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state); +void intel_dp_sync_state(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state); + +void intel_dp_check_frl_training(struct intel_dp *intel_dp); +void intel_dp_pcon_dsc_configure(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state); +void intel_dp_phy_test(struct intel_encoder *encoder); + +void intel_dp_wait_source_oui(struct intel_dp *intel_dp); + +#endif /* __INTEL_DP_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_dp_aux.c b/drivers/gpu/drm/i915/display/intel_dp_aux.c new file mode 100644 index 000000000..ab357161c --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dp_aux.c @@ -0,0 +1,771 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2020-2021 Intel Corporation + */ + +#include "i915_drv.h" +#include "i915_trace.h" +#include "intel_display_types.h" +#include "intel_dp_aux.h" +#include "intel_pps.h" +#include "intel_tc.h" + +static u32 intel_dp_aux_pack(const u8 *src, int src_bytes) +{ + int i; + u32 v = 0; + + if (src_bytes > 4) + src_bytes = 4; + for (i = 0; i < src_bytes; i++) + v |= ((u32)src[i]) << ((3 - i) * 8); + return v; +} + +static void intel_dp_aux_unpack(u32 src, u8 *dst, int dst_bytes) +{ + int i; + + if (dst_bytes > 4) + dst_bytes = 4; + for (i = 0; i < dst_bytes; i++) + dst[i] = src >> ((3 - i) * 8); +} + +static u32 +intel_dp_aux_wait_done(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + i915_reg_t ch_ctl = intel_dp->aux_ch_ctl_reg(intel_dp); + const unsigned int timeout_ms = 10; + u32 status; + bool done; + +#define C (((status = intel_uncore_read_notrace(&i915->uncore, ch_ctl)) & DP_AUX_CH_CTL_SEND_BUSY) == 0) + done = wait_event_timeout(i915->display.gmbus.wait_queue, C, + msecs_to_jiffies_timeout(timeout_ms)); + + /* just trace the final value */ + trace_i915_reg_rw(false, ch_ctl, status, sizeof(status), true); + + if (!done) + drm_err(&i915->drm, + "%s: did not complete or timeout within %ums (status 0x%08x)\n", + intel_dp->aux.name, timeout_ms, status); +#undef C + + return status; +} + +static u32 g4x_get_aux_clock_divider(struct intel_dp *intel_dp, int index) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + + if (index) + return 0; + + /* + * The clock divider is based off the hrawclk, and would like to run at + * 2MHz. So, take the hrawclk value and divide by 2000 and use that + */ + return DIV_ROUND_CLOSEST(RUNTIME_INFO(dev_priv)->rawclk_freq, 2000); +} + +static u32 ilk_get_aux_clock_divider(struct intel_dp *intel_dp, int index) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + u32 freq; + + if (index) + return 0; + + /* + * The clock divider is based off the cdclk or PCH rawclk, and would + * like to run at 2MHz. So, take the cdclk or PCH rawclk value and + * divide by 2000 and use that + */ + if (dig_port->aux_ch == AUX_CH_A) + freq = dev_priv->display.cdclk.hw.cdclk; + else + freq = RUNTIME_INFO(dev_priv)->rawclk_freq; + return DIV_ROUND_CLOSEST(freq, 2000); +} + +static u32 hsw_get_aux_clock_divider(struct intel_dp *intel_dp, int index) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + + if (dig_port->aux_ch != AUX_CH_A && HAS_PCH_LPT_H(dev_priv)) { + /* Workaround for non-ULT HSW */ + switch (index) { + case 0: return 63; + case 1: return 72; + default: return 0; + } + } + + return ilk_get_aux_clock_divider(intel_dp, index); +} + +static u32 skl_get_aux_clock_divider(struct intel_dp *intel_dp, int index) +{ + /* + * SKL doesn't need us to program the AUX clock divider (Hardware will + * derive the clock from CDCLK automatically). We still implement the + * get_aux_clock_divider vfunc to plug-in into the existing code. + */ + return index ? 0 : 1; +} + +static int intel_dp_aux_sync_len(void) +{ + int precharge = 16; /* 10-16 */ + int preamble = 16; + + return precharge + preamble; +} + +static int intel_dp_aux_fw_sync_len(void) +{ + int precharge = 10; /* 10-16 */ + int preamble = 8; + + return precharge + preamble; +} + +static int g4x_dp_aux_precharge_len(void) +{ + int precharge_min = 10; + int preamble = 16; + + /* HW wants the length of the extra precharge in 2us units */ + return (intel_dp_aux_sync_len() - + precharge_min - preamble) / 2; +} + +static u32 g4x_get_aux_send_ctl(struct intel_dp *intel_dp, + int send_bytes, + u32 aux_clock_divider) +{ + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + struct drm_i915_private *dev_priv = + to_i915(dig_port->base.base.dev); + u32 timeout; + + /* Max timeout value on G4x-BDW: 1.6ms */ + if (IS_BROADWELL(dev_priv)) + timeout = DP_AUX_CH_CTL_TIME_OUT_600us; + else + timeout = DP_AUX_CH_CTL_TIME_OUT_400us; + + return DP_AUX_CH_CTL_SEND_BUSY | + DP_AUX_CH_CTL_DONE | + DP_AUX_CH_CTL_INTERRUPT | + DP_AUX_CH_CTL_TIME_OUT_ERROR | + timeout | + DP_AUX_CH_CTL_RECEIVE_ERROR | + (send_bytes << DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT) | + (g4x_dp_aux_precharge_len() << DP_AUX_CH_CTL_PRECHARGE_2US_SHIFT) | + (aux_clock_divider << DP_AUX_CH_CTL_BIT_CLOCK_2X_SHIFT); +} + +static u32 skl_get_aux_send_ctl(struct intel_dp *intel_dp, + int send_bytes, + u32 unused) +{ + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev); + u32 ret; + + /* + * Max timeout values: + * SKL-GLK: 1.6ms + * ICL+: 4ms + */ + ret = DP_AUX_CH_CTL_SEND_BUSY | + DP_AUX_CH_CTL_DONE | + DP_AUX_CH_CTL_INTERRUPT | + DP_AUX_CH_CTL_TIME_OUT_ERROR | + DP_AUX_CH_CTL_TIME_OUT_MAX | + DP_AUX_CH_CTL_RECEIVE_ERROR | + (send_bytes << DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT) | + DP_AUX_CH_CTL_FW_SYNC_PULSE_SKL(intel_dp_aux_fw_sync_len()) | + DP_AUX_CH_CTL_SYNC_PULSE_SKL(intel_dp_aux_sync_len()); + + if (intel_tc_port_in_tbt_alt_mode(dig_port)) + ret |= DP_AUX_CH_CTL_TBT_IO; + + /* + * Power request bit is already set during aux power well enable. + * Preserve the bit across aux transactions. + */ + if (DISPLAY_VER(i915) >= 14) + ret |= XELPDP_DP_AUX_CH_CTL_POWER_REQUEST; + + return ret; +} + +static int +intel_dp_aux_xfer(struct intel_dp *intel_dp, + const u8 *send, int send_bytes, + u8 *recv, int recv_size, + u32 aux_send_ctl_flags) +{ + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + struct drm_i915_private *i915 = + to_i915(dig_port->base.base.dev); + struct intel_uncore *uncore = &i915->uncore; + enum phy phy = intel_port_to_phy(i915, dig_port->base.port); + bool is_tc_port = intel_phy_is_tc(i915, phy); + i915_reg_t ch_ctl, ch_data[5]; + u32 aux_clock_divider; + enum intel_display_power_domain aux_domain; + intel_wakeref_t aux_wakeref; + intel_wakeref_t pps_wakeref; + int i, ret, recv_bytes; + int try, clock = 0; + u32 status; + bool vdd; + + ch_ctl = intel_dp->aux_ch_ctl_reg(intel_dp); + for (i = 0; i < ARRAY_SIZE(ch_data); i++) + ch_data[i] = intel_dp->aux_ch_data_reg(intel_dp, i); + + if (is_tc_port) + intel_tc_port_lock(dig_port); + + aux_domain = intel_aux_power_domain(dig_port); + + aux_wakeref = intel_display_power_get(i915, aux_domain); + pps_wakeref = intel_pps_lock(intel_dp); + + /* + * We will be called with VDD already enabled for dpcd/edid/oui reads. + * In such cases we want to leave VDD enabled and it's up to upper layers + * to turn it off. But for eg. i2c-dev access we need to turn it on/off + * ourselves. + */ + vdd = intel_pps_vdd_on_unlocked(intel_dp); + + /* + * dp aux is extremely sensitive to irq latency, hence request the + * lowest possible wakeup latency and so prevent the cpu from going into + * deep sleep states. + */ + cpu_latency_qos_update_request(&intel_dp->pm_qos, 0); + + intel_pps_check_power_unlocked(intel_dp); + + /* Try to wait for any previous AUX channel activity */ + for (try = 0; try < 3; try++) { + status = intel_uncore_read_notrace(uncore, ch_ctl); + if ((status & DP_AUX_CH_CTL_SEND_BUSY) == 0) + break; + msleep(1); + } + /* just trace the final value */ + trace_i915_reg_rw(false, ch_ctl, status, sizeof(status), true); + + if (try == 3) { + const u32 status = intel_uncore_read(uncore, ch_ctl); + + if (status != intel_dp->aux_busy_last_status) { + drm_WARN(&i915->drm, 1, + "%s: not started (status 0x%08x)\n", + intel_dp->aux.name, status); + intel_dp->aux_busy_last_status = status; + } + + ret = -EBUSY; + goto out; + } + + /* Only 5 data registers! */ + if (drm_WARN_ON(&i915->drm, send_bytes > 20 || recv_size > 20)) { + ret = -E2BIG; + goto out; + } + + while ((aux_clock_divider = intel_dp->get_aux_clock_divider(intel_dp, clock++))) { + u32 send_ctl = intel_dp->get_aux_send_ctl(intel_dp, + send_bytes, + aux_clock_divider); + + send_ctl |= aux_send_ctl_flags; + + /* Must try at least 3 times according to DP spec */ + for (try = 0; try < 5; try++) { + /* Load the send data into the aux channel data registers */ + for (i = 0; i < send_bytes; i += 4) + intel_uncore_write(uncore, + ch_data[i >> 2], + intel_dp_aux_pack(send + i, + send_bytes - i)); + + /* Send the command and wait for it to complete */ + intel_uncore_write(uncore, ch_ctl, send_ctl); + + status = intel_dp_aux_wait_done(intel_dp); + + /* Clear done status and any errors */ + intel_uncore_write(uncore, + ch_ctl, + status | + DP_AUX_CH_CTL_DONE | + DP_AUX_CH_CTL_TIME_OUT_ERROR | + DP_AUX_CH_CTL_RECEIVE_ERROR); + + /* + * DP CTS 1.2 Core Rev 1.1, 4.2.1.1 & 4.2.1.2 + * 400us delay required for errors and timeouts + * Timeout errors from the HW already meet this + * requirement so skip to next iteration + */ + if (status & DP_AUX_CH_CTL_TIME_OUT_ERROR) + continue; + + if (status & DP_AUX_CH_CTL_RECEIVE_ERROR) { + usleep_range(400, 500); + continue; + } + if (status & DP_AUX_CH_CTL_DONE) + goto done; + } + } + + if ((status & DP_AUX_CH_CTL_DONE) == 0) { + drm_err(&i915->drm, "%s: not done (status 0x%08x)\n", + intel_dp->aux.name, status); + ret = -EBUSY; + goto out; + } + +done: + /* + * Check for timeout or receive error. Timeouts occur when the sink is + * not connected. + */ + if (status & DP_AUX_CH_CTL_RECEIVE_ERROR) { + drm_err(&i915->drm, "%s: receive error (status 0x%08x)\n", + intel_dp->aux.name, status); + ret = -EIO; + goto out; + } + + /* + * Timeouts occur when the device isn't connected, so they're "normal" + * -- don't fill the kernel log with these + */ + if (status & DP_AUX_CH_CTL_TIME_OUT_ERROR) { + drm_dbg_kms(&i915->drm, "%s: timeout (status 0x%08x)\n", + intel_dp->aux.name, status); + ret = -ETIMEDOUT; + goto out; + } + + /* Unload any bytes sent back from the other side */ + recv_bytes = ((status & DP_AUX_CH_CTL_MESSAGE_SIZE_MASK) >> + DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT); + + /* + * By BSpec: "Message sizes of 0 or >20 are not allowed." + * We have no idea of what happened so we return -EBUSY so + * drm layer takes care for the necessary retries. + */ + if (recv_bytes == 0 || recv_bytes > 20) { + drm_dbg_kms(&i915->drm, + "%s: Forbidden recv_bytes = %d on aux transaction\n", + intel_dp->aux.name, recv_bytes); + ret = -EBUSY; + goto out; + } + + if (recv_bytes > recv_size) + recv_bytes = recv_size; + + for (i = 0; i < recv_bytes; i += 4) + intel_dp_aux_unpack(intel_uncore_read(uncore, ch_data[i >> 2]), + recv + i, recv_bytes - i); + + ret = recv_bytes; +out: + cpu_latency_qos_update_request(&intel_dp->pm_qos, PM_QOS_DEFAULT_VALUE); + + if (vdd) + intel_pps_vdd_off_unlocked(intel_dp, false); + + intel_pps_unlock(intel_dp, pps_wakeref); + intel_display_power_put_async(i915, aux_domain, aux_wakeref); + + if (is_tc_port) + intel_tc_port_unlock(dig_port); + + return ret; +} + +#define BARE_ADDRESS_SIZE 3 +#define HEADER_SIZE (BARE_ADDRESS_SIZE + 1) + +static void +intel_dp_aux_header(u8 txbuf[HEADER_SIZE], + const struct drm_dp_aux_msg *msg) +{ + txbuf[0] = (msg->request << 4) | ((msg->address >> 16) & 0xf); + txbuf[1] = (msg->address >> 8) & 0xff; + txbuf[2] = msg->address & 0xff; + txbuf[3] = msg->size - 1; +} + +static u32 intel_dp_aux_xfer_flags(const struct drm_dp_aux_msg *msg) +{ + /* + * If we're trying to send the HDCP Aksv, we need to set a the Aksv + * select bit to inform the hardware to send the Aksv after our header + * since we can't access that data from software. + */ + if ((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_NATIVE_WRITE && + msg->address == DP_AUX_HDCP_AKSV) + return DP_AUX_CH_CTL_AUX_AKSV_SELECT; + + return 0; +} + +static ssize_t +intel_dp_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) +{ + struct intel_dp *intel_dp = container_of(aux, struct intel_dp, aux); + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + u8 txbuf[20], rxbuf[20]; + size_t txsize, rxsize; + u32 flags = intel_dp_aux_xfer_flags(msg); + int ret; + + intel_dp_aux_header(txbuf, msg); + + switch (msg->request & ~DP_AUX_I2C_MOT) { + case DP_AUX_NATIVE_WRITE: + case DP_AUX_I2C_WRITE: + case DP_AUX_I2C_WRITE_STATUS_UPDATE: + txsize = msg->size ? HEADER_SIZE + msg->size : BARE_ADDRESS_SIZE; + rxsize = 2; /* 0 or 1 data bytes */ + + if (drm_WARN_ON(&i915->drm, txsize > 20)) + return -E2BIG; + + drm_WARN_ON(&i915->drm, !msg->buffer != !msg->size); + + if (msg->buffer) + memcpy(txbuf + HEADER_SIZE, msg->buffer, msg->size); + + ret = intel_dp_aux_xfer(intel_dp, txbuf, txsize, + rxbuf, rxsize, flags); + if (ret > 0) { + msg->reply = rxbuf[0] >> 4; + + if (ret > 1) { + /* Number of bytes written in a short write. */ + ret = clamp_t(int, rxbuf[1], 0, msg->size); + } else { + /* Return payload size. */ + ret = msg->size; + } + } + break; + + case DP_AUX_NATIVE_READ: + case DP_AUX_I2C_READ: + txsize = msg->size ? HEADER_SIZE : BARE_ADDRESS_SIZE; + rxsize = msg->size + 1; + + if (drm_WARN_ON(&i915->drm, rxsize > 20)) + return -E2BIG; + + ret = intel_dp_aux_xfer(intel_dp, txbuf, txsize, + rxbuf, rxsize, flags); + if (ret > 0) { + msg->reply = rxbuf[0] >> 4; + /* + * Assume happy day, and copy the data. The caller is + * expected to check msg->reply before touching it. + * + * Return payload size. + */ + ret--; + memcpy(msg->buffer, rxbuf + 1, ret); + } + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static i915_reg_t g4x_aux_ctl_reg(struct intel_dp *intel_dp) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + enum aux_ch aux_ch = dig_port->aux_ch; + + switch (aux_ch) { + case AUX_CH_B: + case AUX_CH_C: + case AUX_CH_D: + return DP_AUX_CH_CTL(aux_ch); + default: + MISSING_CASE(aux_ch); + return DP_AUX_CH_CTL(AUX_CH_B); + } +} + +static i915_reg_t g4x_aux_data_reg(struct intel_dp *intel_dp, int index) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + enum aux_ch aux_ch = dig_port->aux_ch; + + switch (aux_ch) { + case AUX_CH_B: + case AUX_CH_C: + case AUX_CH_D: + return DP_AUX_CH_DATA(aux_ch, index); + default: + MISSING_CASE(aux_ch); + return DP_AUX_CH_DATA(AUX_CH_B, index); + } +} + +static i915_reg_t ilk_aux_ctl_reg(struct intel_dp *intel_dp) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + enum aux_ch aux_ch = dig_port->aux_ch; + + switch (aux_ch) { + case AUX_CH_A: + return DP_AUX_CH_CTL(aux_ch); + case AUX_CH_B: + case AUX_CH_C: + case AUX_CH_D: + return PCH_DP_AUX_CH_CTL(aux_ch); + default: + MISSING_CASE(aux_ch); + return DP_AUX_CH_CTL(AUX_CH_A); + } +} + +static i915_reg_t ilk_aux_data_reg(struct intel_dp *intel_dp, int index) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + enum aux_ch aux_ch = dig_port->aux_ch; + + switch (aux_ch) { + case AUX_CH_A: + return DP_AUX_CH_DATA(aux_ch, index); + case AUX_CH_B: + case AUX_CH_C: + case AUX_CH_D: + return PCH_DP_AUX_CH_DATA(aux_ch, index); + default: + MISSING_CASE(aux_ch); + return DP_AUX_CH_DATA(AUX_CH_A, index); + } +} + +static i915_reg_t skl_aux_ctl_reg(struct intel_dp *intel_dp) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + enum aux_ch aux_ch = dig_port->aux_ch; + + switch (aux_ch) { + case AUX_CH_A: + case AUX_CH_B: + case AUX_CH_C: + case AUX_CH_D: + case AUX_CH_E: + case AUX_CH_F: + return DP_AUX_CH_CTL(aux_ch); + default: + MISSING_CASE(aux_ch); + return DP_AUX_CH_CTL(AUX_CH_A); + } +} + +static i915_reg_t skl_aux_data_reg(struct intel_dp *intel_dp, int index) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + enum aux_ch aux_ch = dig_port->aux_ch; + + switch (aux_ch) { + case AUX_CH_A: + case AUX_CH_B: + case AUX_CH_C: + case AUX_CH_D: + case AUX_CH_E: + case AUX_CH_F: + return DP_AUX_CH_DATA(aux_ch, index); + default: + MISSING_CASE(aux_ch); + return DP_AUX_CH_DATA(AUX_CH_A, index); + } +} + +static i915_reg_t tgl_aux_ctl_reg(struct intel_dp *intel_dp) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + enum aux_ch aux_ch = dig_port->aux_ch; + + switch (aux_ch) { + case AUX_CH_A: + case AUX_CH_B: + case AUX_CH_C: + case AUX_CH_USBC1: + case AUX_CH_USBC2: + case AUX_CH_USBC3: + case AUX_CH_USBC4: + case AUX_CH_USBC5: /* aka AUX_CH_D_XELPD */ + case AUX_CH_USBC6: /* aka AUX_CH_E_XELPD */ + return DP_AUX_CH_CTL(aux_ch); + default: + MISSING_CASE(aux_ch); + return DP_AUX_CH_CTL(AUX_CH_A); + } +} + +static i915_reg_t tgl_aux_data_reg(struct intel_dp *intel_dp, int index) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + enum aux_ch aux_ch = dig_port->aux_ch; + + switch (aux_ch) { + case AUX_CH_A: + case AUX_CH_B: + case AUX_CH_C: + case AUX_CH_USBC1: + case AUX_CH_USBC2: + case AUX_CH_USBC3: + case AUX_CH_USBC4: + case AUX_CH_USBC5: /* aka AUX_CH_D_XELPD */ + case AUX_CH_USBC6: /* aka AUX_CH_E_XELPD */ + return DP_AUX_CH_DATA(aux_ch, index); + default: + MISSING_CASE(aux_ch); + return DP_AUX_CH_DATA(AUX_CH_A, index); + } +} + +static i915_reg_t xelpdp_aux_ctl_reg(struct intel_dp *intel_dp) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + enum aux_ch aux_ch = dig_port->aux_ch; + + switch (aux_ch) { + case AUX_CH_A: + case AUX_CH_B: + case AUX_CH_USBC1: + case AUX_CH_USBC2: + case AUX_CH_USBC3: + case AUX_CH_USBC4: + return XELPDP_DP_AUX_CH_CTL(aux_ch); + default: + MISSING_CASE(aux_ch); + return XELPDP_DP_AUX_CH_CTL(AUX_CH_A); + } +} + +static i915_reg_t xelpdp_aux_data_reg(struct intel_dp *intel_dp, int index) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + enum aux_ch aux_ch = dig_port->aux_ch; + + switch (aux_ch) { + case AUX_CH_A: + case AUX_CH_B: + case AUX_CH_USBC1: + case AUX_CH_USBC2: + case AUX_CH_USBC3: + case AUX_CH_USBC4: + return XELPDP_DP_AUX_CH_DATA(aux_ch, index); + default: + MISSING_CASE(aux_ch); + return XELPDP_DP_AUX_CH_DATA(AUX_CH_A, index); + } +} + +void intel_dp_aux_fini(struct intel_dp *intel_dp) +{ + if (cpu_latency_qos_request_active(&intel_dp->pm_qos)) + cpu_latency_qos_remove_request(&intel_dp->pm_qos); + + kfree(intel_dp->aux.name); +} + +void intel_dp_aux_init(struct intel_dp *intel_dp) +{ + struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + struct intel_encoder *encoder = &dig_port->base; + enum aux_ch aux_ch = dig_port->aux_ch; + + if (DISPLAY_VER(dev_priv) >= 14) { + intel_dp->aux_ch_ctl_reg = xelpdp_aux_ctl_reg; + intel_dp->aux_ch_data_reg = xelpdp_aux_data_reg; + } else if (DISPLAY_VER(dev_priv) >= 12) { + intel_dp->aux_ch_ctl_reg = tgl_aux_ctl_reg; + intel_dp->aux_ch_data_reg = tgl_aux_data_reg; + } else if (DISPLAY_VER(dev_priv) >= 9) { + intel_dp->aux_ch_ctl_reg = skl_aux_ctl_reg; + intel_dp->aux_ch_data_reg = skl_aux_data_reg; + } else if (HAS_PCH_SPLIT(dev_priv)) { + intel_dp->aux_ch_ctl_reg = ilk_aux_ctl_reg; + intel_dp->aux_ch_data_reg = ilk_aux_data_reg; + } else { + intel_dp->aux_ch_ctl_reg = g4x_aux_ctl_reg; + intel_dp->aux_ch_data_reg = g4x_aux_data_reg; + } + + if (DISPLAY_VER(dev_priv) >= 9) + intel_dp->get_aux_clock_divider = skl_get_aux_clock_divider; + else if (IS_BROADWELL(dev_priv) || IS_HASWELL(dev_priv)) + intel_dp->get_aux_clock_divider = hsw_get_aux_clock_divider; + else if (HAS_PCH_SPLIT(dev_priv)) + intel_dp->get_aux_clock_divider = ilk_get_aux_clock_divider; + else + intel_dp->get_aux_clock_divider = g4x_get_aux_clock_divider; + + if (DISPLAY_VER(dev_priv) >= 9) + intel_dp->get_aux_send_ctl = skl_get_aux_send_ctl; + else + intel_dp->get_aux_send_ctl = g4x_get_aux_send_ctl; + + intel_dp->aux.drm_dev = &dev_priv->drm; + drm_dp_aux_init(&intel_dp->aux); + + /* Failure to allocate our preferred name is not critical */ + if (DISPLAY_VER(dev_priv) >= 13 && aux_ch >= AUX_CH_D_XELPD) + intel_dp->aux.name = kasprintf(GFP_KERNEL, "AUX %c/%s", + aux_ch_name(aux_ch - AUX_CH_D_XELPD + AUX_CH_D), + encoder->base.name); + else if (DISPLAY_VER(dev_priv) >= 12 && aux_ch >= AUX_CH_USBC1) + intel_dp->aux.name = kasprintf(GFP_KERNEL, "AUX USBC%c/%s", + aux_ch - AUX_CH_USBC1 + '1', + encoder->base.name); + else + intel_dp->aux.name = kasprintf(GFP_KERNEL, "AUX %c/%s", + aux_ch_name(aux_ch), + encoder->base.name); + + intel_dp->aux.transfer = intel_dp_aux_transfer; + cpu_latency_qos_add_request(&intel_dp->pm_qos, PM_QOS_DEFAULT_VALUE); +} diff --git a/drivers/gpu/drm/i915/display/intel_dp_aux.h b/drivers/gpu/drm/i915/display/intel_dp_aux.h new file mode 100644 index 000000000..738577537 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dp_aux.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2020-2021 Intel Corporation + */ + +#ifndef __INTEL_DP_AUX_H__ +#define __INTEL_DP_AUX_H__ + +struct intel_dp; + +void intel_dp_aux_fini(struct intel_dp *intel_dp); +void intel_dp_aux_init(struct intel_dp *intel_dp); + +#endif /* __INTEL_DP_AUX_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c b/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c new file mode 100644 index 000000000..83af95bce --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c @@ -0,0 +1,519 @@ +/* + * Copyright © 2015 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +/* + * Laptops with Intel GPUs which have panels that support controlling the + * backlight through DP AUX can actually use two different interfaces: Intel's + * proprietary DP AUX backlight interface, and the standard VESA backlight + * interface. Unfortunately, at the time of writing this a lot of laptops will + * advertise support for the standard VESA backlight interface when they + * don't properly support it. However, on these systems the Intel backlight + * interface generally does work properly. Additionally, these systems will + * usually just indicate that they use PWM backlight controls in their VBIOS + * for some reason. + */ + +#include "i915_drv.h" +#include "intel_backlight.h" +#include "intel_display_types.h" +#include "intel_dp.h" +#include "intel_dp_aux_backlight.h" + +/* TODO: + * Implement HDR, right now we just implement the bare minimum to bring us back into SDR mode so we + * can make people's backlights work in the mean time + */ + +/* + * DP AUX registers for Intel's proprietary HDR backlight interface. We define + * them here since we'll likely be the only driver to ever use these. + */ +#define INTEL_EDP_HDR_TCON_CAP0 0x340 + +#define INTEL_EDP_HDR_TCON_CAP1 0x341 +# define INTEL_EDP_HDR_TCON_2084_DECODE_CAP BIT(0) +# define INTEL_EDP_HDR_TCON_2020_GAMUT_CAP BIT(1) +# define INTEL_EDP_HDR_TCON_TONE_MAPPING_CAP BIT(2) +# define INTEL_EDP_HDR_TCON_SEGMENTED_BACKLIGHT_CAP BIT(3) +# define INTEL_EDP_HDR_TCON_BRIGHTNESS_NITS_CAP BIT(4) +# define INTEL_EDP_HDR_TCON_OPTIMIZATION_CAP BIT(5) +# define INTEL_EDP_HDR_TCON_SDP_COLORIMETRY_CAP BIT(6) +# define INTEL_EDP_HDR_TCON_SRGB_TO_PANEL_GAMUT_CONVERSION_CAP BIT(7) + +#define INTEL_EDP_HDR_TCON_CAP2 0x342 +# define INTEL_EDP_SDR_TCON_BRIGHTNESS_AUX_CAP BIT(0) + +#define INTEL_EDP_HDR_TCON_CAP3 0x343 + +#define INTEL_EDP_HDR_GETSET_CTRL_PARAMS 0x344 +# define INTEL_EDP_HDR_TCON_2084_DECODE_ENABLE BIT(0) +# define INTEL_EDP_HDR_TCON_2020_GAMUT_ENABLE BIT(1) +# define INTEL_EDP_HDR_TCON_TONE_MAPPING_ENABLE BIT(2) /* Pre-TGL+ */ +# define INTEL_EDP_HDR_TCON_SEGMENTED_BACKLIGHT_ENABLE BIT(3) +# define INTEL_EDP_HDR_TCON_BRIGHTNESS_AUX_ENABLE BIT(4) +# define INTEL_EDP_HDR_TCON_SRGB_TO_PANEL_GAMUT_ENABLE BIT(5) +/* Bit 6 is reserved */ +# define INTEL_EDP_HDR_TCON_SDP_COLORIMETRY_ENABLE BIT(7) + +#define INTEL_EDP_HDR_CONTENT_LUMINANCE 0x346 /* Pre-TGL+ */ +#define INTEL_EDP_HDR_PANEL_LUMINANCE_OVERRIDE 0x34A +#define INTEL_EDP_SDR_LUMINANCE_LEVEL 0x352 +#define INTEL_EDP_BRIGHTNESS_NITS_LSB 0x354 +#define INTEL_EDP_BRIGHTNESS_NITS_MSB 0x355 +#define INTEL_EDP_BRIGHTNESS_DELAY_FRAMES 0x356 +#define INTEL_EDP_BRIGHTNESS_PER_FRAME_STEPS 0x357 + +#define INTEL_EDP_BRIGHTNESS_OPTIMIZATION_0 0x358 +# define INTEL_EDP_TCON_USAGE_MASK GENMASK(0, 3) +# define INTEL_EDP_TCON_USAGE_UNKNOWN 0x0 +# define INTEL_EDP_TCON_USAGE_DESKTOP 0x1 +# define INTEL_EDP_TCON_USAGE_FULL_SCREEN_MEDIA 0x2 +# define INTEL_EDP_TCON_USAGE_FULL_SCREEN_GAMING 0x3 +# define INTEL_EDP_TCON_POWER_MASK BIT(4) +# define INTEL_EDP_TCON_POWER_DC (0 << 4) +# define INTEL_EDP_TCON_POWER_AC (1 << 4) +# define INTEL_EDP_TCON_OPTIMIZATION_STRENGTH_MASK GENMASK(5, 7) + +#define INTEL_EDP_BRIGHTNESS_OPTIMIZATION_1 0x359 + +enum intel_dp_aux_backlight_modparam { + INTEL_DP_AUX_BACKLIGHT_AUTO = -1, + INTEL_DP_AUX_BACKLIGHT_OFF = 0, + INTEL_DP_AUX_BACKLIGHT_ON = 1, + INTEL_DP_AUX_BACKLIGHT_FORCE_VESA = 2, + INTEL_DP_AUX_BACKLIGHT_FORCE_INTEL = 3, +}; + +/* Intel EDP backlight callbacks */ +static bool +intel_dp_aux_supports_hdr_backlight(struct intel_connector *connector) +{ + struct drm_i915_private *i915 = to_i915(connector->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); + struct drm_dp_aux *aux = &intel_dp->aux; + struct intel_panel *panel = &connector->panel; + int ret; + u8 tcon_cap[4]; + + intel_dp_wait_source_oui(intel_dp); + + ret = drm_dp_dpcd_read(aux, INTEL_EDP_HDR_TCON_CAP0, tcon_cap, sizeof(tcon_cap)); + if (ret != sizeof(tcon_cap)) + return false; + + if (!(tcon_cap[1] & INTEL_EDP_HDR_TCON_BRIGHTNESS_NITS_CAP)) + return false; + + if (tcon_cap[0] >= 1) { + drm_dbg_kms(&i915->drm, "Detected Intel HDR backlight interface version %d\n", + tcon_cap[0]); + } else { + drm_dbg_kms(&i915->drm, "Detected unsupported HDR backlight interface version %d\n", + tcon_cap[0]); + return false; + } + + /* + * If we don't have HDR static metadata there is no way to + * runtime detect used range for nits based control. For now + * do not use Intel proprietary eDP backlight control if we + * don't have this data in panel EDID. In case we find panel + * which supports only nits based control, but doesn't provide + * HDR static metadata we need to start maintaining table of + * ranges for such panels. + */ + if (i915->params.enable_dpcd_backlight != INTEL_DP_AUX_BACKLIGHT_FORCE_INTEL && + !(connector->base.hdr_sink_metadata.hdmi_type1.metadata_type & + BIT(HDMI_STATIC_METADATA_TYPE1))) { + drm_info(&i915->drm, + "Panel is missing HDR static metadata. Possible support for Intel HDR backlight interface is not used. If your backlight controls don't work try booting with i915.enable_dpcd_backlight=%d. needs this, please file a _new_ bug report on drm/i915, see " FDO_BUG_URL " for details.\n", + INTEL_DP_AUX_BACKLIGHT_FORCE_INTEL); + return false; + } + + panel->backlight.edp.intel.sdr_uses_aux = + tcon_cap[2] & INTEL_EDP_SDR_TCON_BRIGHTNESS_AUX_CAP; + + return true; +} + +static u32 +intel_dp_aux_hdr_get_backlight(struct intel_connector *connector, enum pipe pipe) +{ + struct drm_i915_private *i915 = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); + u8 tmp; + u8 buf[2] = { 0 }; + + if (drm_dp_dpcd_readb(&intel_dp->aux, INTEL_EDP_HDR_GETSET_CTRL_PARAMS, &tmp) != 1) { + drm_err(&i915->drm, "Failed to read current backlight mode from DPCD\n"); + return 0; + } + + if (!(tmp & INTEL_EDP_HDR_TCON_BRIGHTNESS_AUX_ENABLE)) { + if (!panel->backlight.edp.intel.sdr_uses_aux) { + u32 pwm_level = panel->backlight.pwm_funcs->get(connector, pipe); + + return intel_backlight_level_from_pwm(connector, pwm_level); + } + + /* Assume 100% brightness if backlight controls aren't enabled yet */ + return panel->backlight.max; + } + + if (drm_dp_dpcd_read(&intel_dp->aux, INTEL_EDP_BRIGHTNESS_NITS_LSB, buf, + sizeof(buf)) != sizeof(buf)) { + drm_err(&i915->drm, "Failed to read brightness from DPCD\n"); + return 0; + } + + return (buf[1] << 8 | buf[0]); +} + +static void +intel_dp_aux_hdr_set_aux_backlight(const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct drm_device *dev = connector->base.dev; + struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); + u8 buf[4] = { 0 }; + + buf[0] = level & 0xFF; + buf[1] = (level & 0xFF00) >> 8; + + if (drm_dp_dpcd_write(&intel_dp->aux, INTEL_EDP_BRIGHTNESS_NITS_LSB, buf, + sizeof(buf)) != sizeof(buf)) + drm_err(dev, "Failed to write brightness level to DPCD\n"); +} + +static void +intel_dp_aux_hdr_set_backlight(const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct intel_panel *panel = &connector->panel; + + if (panel->backlight.edp.intel.sdr_uses_aux) { + intel_dp_aux_hdr_set_aux_backlight(conn_state, level); + } else { + const u32 pwm_level = intel_backlight_level_to_pwm(connector, level); + + intel_backlight_set_pwm_level(conn_state, pwm_level); + } +} + +static void +intel_dp_aux_hdr_enable_backlight(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct intel_panel *panel = &connector->panel; + struct drm_i915_private *i915 = to_i915(connector->base.dev); + struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); + int ret; + u8 old_ctrl, ctrl; + + intel_dp_wait_source_oui(intel_dp); + + ret = drm_dp_dpcd_readb(&intel_dp->aux, INTEL_EDP_HDR_GETSET_CTRL_PARAMS, &old_ctrl); + if (ret != 1) { + drm_err(&i915->drm, "Failed to read current backlight control mode: %d\n", ret); + return; + } + + ctrl = old_ctrl; + if (panel->backlight.edp.intel.sdr_uses_aux) { + ctrl |= INTEL_EDP_HDR_TCON_BRIGHTNESS_AUX_ENABLE; + intel_dp_aux_hdr_set_aux_backlight(conn_state, level); + } else { + u32 pwm_level = intel_backlight_level_to_pwm(connector, level); + + panel->backlight.pwm_funcs->enable(crtc_state, conn_state, pwm_level); + + ctrl &= ~INTEL_EDP_HDR_TCON_BRIGHTNESS_AUX_ENABLE; + } + + if (ctrl != old_ctrl) + if (drm_dp_dpcd_writeb(&intel_dp->aux, INTEL_EDP_HDR_GETSET_CTRL_PARAMS, ctrl) != 1) + drm_err(&i915->drm, "Failed to configure DPCD brightness controls\n"); +} + +static void +intel_dp_aux_hdr_disable_backlight(const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct intel_panel *panel = &connector->panel; + + /* Nothing to do for AUX based backlight controls */ + if (panel->backlight.edp.intel.sdr_uses_aux) + return; + + /* Note we want the actual pwm_level to be 0, regardless of pwm_min */ + panel->backlight.pwm_funcs->disable(conn_state, intel_backlight_invert_pwm_level(connector, 0)); +} + +static int +intel_dp_aux_hdr_setup_backlight(struct intel_connector *connector, enum pipe pipe) +{ + struct drm_i915_private *i915 = to_i915(connector->base.dev); + struct intel_panel *panel = &connector->panel; + struct drm_luminance_range_info *luminance_range = + &connector->base.display_info.luminance_range; + int ret; + + if (panel->backlight.edp.intel.sdr_uses_aux) { + drm_dbg_kms(&i915->drm, "SDR backlight is controlled through DPCD\n"); + } else { + drm_dbg_kms(&i915->drm, "SDR backlight is controlled through PWM\n"); + + ret = panel->backlight.pwm_funcs->setup(connector, pipe); + if (ret < 0) { + drm_err(&i915->drm, + "Failed to setup SDR backlight controls through PWM: %d\n", ret); + return ret; + } + } + + if (luminance_range->max_luminance) { + panel->backlight.max = luminance_range->max_luminance; + panel->backlight.min = luminance_range->min_luminance; + } else { + panel->backlight.max = 512; + panel->backlight.min = 0; + } + + drm_dbg_kms(&i915->drm, "Using backlight range %d..%d\n", panel->backlight.min, + panel->backlight.max); + + panel->backlight.level = intel_dp_aux_hdr_get_backlight(connector, pipe); + panel->backlight.enabled = panel->backlight.level != 0; + + return 0; +} + +/* VESA backlight callbacks */ +static u32 intel_dp_aux_vesa_get_backlight(struct intel_connector *connector, enum pipe unused) +{ + return connector->panel.backlight.level; +} + +static void +intel_dp_aux_vesa_set_backlight(const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct intel_panel *panel = &connector->panel; + struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); + + if (!panel->backlight.edp.vesa.info.aux_set) { + const u32 pwm_level = intel_backlight_level_to_pwm(connector, level); + + intel_backlight_set_pwm_level(conn_state, pwm_level); + } + + drm_edp_backlight_set_level(&intel_dp->aux, &panel->backlight.edp.vesa.info, level); +} + +static void +intel_dp_aux_vesa_enable_backlight(const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state, u32 level) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct intel_panel *panel = &connector->panel; + struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); + + if (!panel->backlight.edp.vesa.info.aux_enable) { + u32 pwm_level; + + if (!panel->backlight.edp.vesa.info.aux_set) + pwm_level = intel_backlight_level_to_pwm(connector, level); + else + pwm_level = intel_backlight_invert_pwm_level(connector, + panel->backlight.pwm_level_max); + + panel->backlight.pwm_funcs->enable(crtc_state, conn_state, pwm_level); + } + + drm_edp_backlight_enable(&intel_dp->aux, &panel->backlight.edp.vesa.info, level); +} + +static void intel_dp_aux_vesa_disable_backlight(const struct drm_connector_state *old_conn_state, + u32 level) +{ + struct intel_connector *connector = to_intel_connector(old_conn_state->connector); + struct intel_panel *panel = &connector->panel; + struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); + + drm_edp_backlight_disable(&intel_dp->aux, &panel->backlight.edp.vesa.info); + + if (!panel->backlight.edp.vesa.info.aux_enable) + panel->backlight.pwm_funcs->disable(old_conn_state, + intel_backlight_invert_pwm_level(connector, 0)); +} + +static int intel_dp_aux_vesa_setup_backlight(struct intel_connector *connector, enum pipe pipe) +{ + struct intel_dp *intel_dp = intel_attached_dp(connector); + struct intel_panel *panel = &connector->panel; + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + u16 current_level; + u8 current_mode; + int ret; + + ret = drm_edp_backlight_init(&intel_dp->aux, &panel->backlight.edp.vesa.info, + panel->vbt.backlight.pwm_freq_hz, intel_dp->edp_dpcd, + ¤t_level, ¤t_mode); + if (ret < 0) + return ret; + + if (!panel->backlight.edp.vesa.info.aux_set || !panel->backlight.edp.vesa.info.aux_enable) { + ret = panel->backlight.pwm_funcs->setup(connector, pipe); + if (ret < 0) { + drm_err(&i915->drm, + "Failed to setup PWM backlight controls for eDP backlight: %d\n", + ret); + return ret; + } + } + + if (panel->backlight.edp.vesa.info.aux_set) { + panel->backlight.max = panel->backlight.edp.vesa.info.max; + panel->backlight.min = 0; + if (current_mode == DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD) { + panel->backlight.level = current_level; + panel->backlight.enabled = panel->backlight.level != 0; + } else { + panel->backlight.level = panel->backlight.max; + panel->backlight.enabled = false; + } + } else { + panel->backlight.max = panel->backlight.pwm_level_max; + panel->backlight.min = panel->backlight.pwm_level_min; + if (current_mode == DP_EDP_BACKLIGHT_CONTROL_MODE_PWM) { + panel->backlight.level = panel->backlight.pwm_funcs->get(connector, pipe); + panel->backlight.enabled = panel->backlight.pwm_enabled; + } else { + panel->backlight.level = panel->backlight.max; + panel->backlight.enabled = false; + } + } + + return 0; +} + +static bool +intel_dp_aux_supports_vesa_backlight(struct intel_connector *connector) +{ + struct intel_dp *intel_dp = intel_attached_dp(connector); + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + + if (drm_edp_backlight_supported(intel_dp->edp_dpcd)) { + drm_dbg_kms(&i915->drm, "AUX Backlight Control Supported!\n"); + return true; + } + return false; +} + +static const struct intel_panel_bl_funcs intel_dp_hdr_bl_funcs = { + .setup = intel_dp_aux_hdr_setup_backlight, + .enable = intel_dp_aux_hdr_enable_backlight, + .disable = intel_dp_aux_hdr_disable_backlight, + .set = intel_dp_aux_hdr_set_backlight, + .get = intel_dp_aux_hdr_get_backlight, +}; + +static const struct intel_panel_bl_funcs intel_dp_vesa_bl_funcs = { + .setup = intel_dp_aux_vesa_setup_backlight, + .enable = intel_dp_aux_vesa_enable_backlight, + .disable = intel_dp_aux_vesa_disable_backlight, + .set = intel_dp_aux_vesa_set_backlight, + .get = intel_dp_aux_vesa_get_backlight, +}; + +int intel_dp_aux_init_backlight_funcs(struct intel_connector *connector) +{ + struct drm_device *dev = connector->base.dev; + struct intel_panel *panel = &connector->panel; + struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + bool try_intel_interface = false, try_vesa_interface = false; + + /* Check the VBT and user's module parameters to figure out which + * interfaces to probe + */ + switch (i915->params.enable_dpcd_backlight) { + case INTEL_DP_AUX_BACKLIGHT_OFF: + return -ENODEV; + case INTEL_DP_AUX_BACKLIGHT_AUTO: + switch (panel->vbt.backlight.type) { + case INTEL_BACKLIGHT_VESA_EDP_AUX_INTERFACE: + try_vesa_interface = true; + break; + case INTEL_BACKLIGHT_DISPLAY_DDI: + try_intel_interface = true; + break; + default: + return -ENODEV; + } + break; + case INTEL_DP_AUX_BACKLIGHT_ON: + if (panel->vbt.backlight.type != INTEL_BACKLIGHT_VESA_EDP_AUX_INTERFACE) + try_intel_interface = true; + + try_vesa_interface = true; + break; + case INTEL_DP_AUX_BACKLIGHT_FORCE_VESA: + try_vesa_interface = true; + break; + case INTEL_DP_AUX_BACKLIGHT_FORCE_INTEL: + try_intel_interface = true; + break; + } + + /* + * Since Intel has their own backlight control interface, the majority of machines out there + * using DPCD backlight controls with Intel GPUs will be using this interface as opposed to + * the VESA interface. However, other GPUs (such as Nvidia's) will always use the VESA + * interface. This means that there's quite a number of panels out there that will advertise + * support for both interfaces, primarily systems with Intel/Nvidia hybrid GPU setups. + * + * There's a catch to this though: on many panels that advertise support for both + * interfaces, the VESA backlight interface will stop working once we've programmed the + * panel with Intel's OUI - which is also required for us to be able to detect Intel's + * backlight interface at all. This means that the only sensible way for us to detect both + * interfaces is to probe for Intel's first, and VESA's second. + */ + if (try_intel_interface && intel_dp_aux_supports_hdr_backlight(connector)) { + drm_dbg_kms(dev, "Using Intel proprietary eDP backlight controls\n"); + panel->backlight.funcs = &intel_dp_hdr_bl_funcs; + return 0; + } + + if (try_vesa_interface && intel_dp_aux_supports_vesa_backlight(connector)) { + drm_dbg_kms(dev, "Using VESA eDP backlight controls\n"); + panel->backlight.funcs = &intel_dp_vesa_bl_funcs; + return 0; + } + + return -ENODEV; +} diff --git a/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.h b/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.h new file mode 100644 index 000000000..ed60c2858 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_DP_AUX_BACKLIGHT_H__ +#define __INTEL_DP_AUX_BACKLIGHT_H__ + +struct intel_connector; + +int intel_dp_aux_init_backlight_funcs(struct intel_connector *intel_connector); + +#endif /* __INTEL_DP_AUX_BACKLIGHT_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_dp_hdcp.c b/drivers/gpu/drm/i915/display/intel_dp_hdcp.c new file mode 100644 index 000000000..88689124c --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dp_hdcp.c @@ -0,0 +1,823 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (C) 2020 Google, Inc. + * + * Authors: + * Sean Paul + */ + +#include +#include +#include +#include + +#include "intel_ddi.h" +#include "intel_de.h" +#include "intel_display_types.h" +#include "intel_dp.h" +#include "intel_dp_hdcp.h" +#include "intel_hdcp.h" +#include "intel_hdcp_regs.h" + +static unsigned int transcoder_to_stream_enc_status(enum transcoder cpu_transcoder) +{ + u32 stream_enc_mask; + + switch (cpu_transcoder) { + case TRANSCODER_A: + stream_enc_mask = HDCP_STATUS_STREAM_A_ENC; + break; + case TRANSCODER_B: + stream_enc_mask = HDCP_STATUS_STREAM_B_ENC; + break; + case TRANSCODER_C: + stream_enc_mask = HDCP_STATUS_STREAM_C_ENC; + break; + case TRANSCODER_D: + stream_enc_mask = HDCP_STATUS_STREAM_D_ENC; + break; + default: + stream_enc_mask = 0; + } + + return stream_enc_mask; +} + +static void intel_dp_hdcp_wait_for_cp_irq(struct intel_hdcp *hdcp, int timeout) +{ + long ret; + +#define C (hdcp->cp_irq_count_cached != atomic_read(&hdcp->cp_irq_count)) + ret = wait_event_interruptible_timeout(hdcp->cp_irq_queue, C, + msecs_to_jiffies(timeout)); + + if (!ret) + DRM_DEBUG_KMS("Timedout at waiting for CP_IRQ\n"); +} + +static +int intel_dp_hdcp_write_an_aksv(struct intel_digital_port *dig_port, + u8 *an) +{ + struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev); + u8 aksv[DRM_HDCP_KSV_LEN] = {}; + ssize_t dpcd_ret; + + /* Output An first, that's easy */ + dpcd_ret = drm_dp_dpcd_write(&dig_port->dp.aux, DP_AUX_HDCP_AN, + an, DRM_HDCP_AN_LEN); + if (dpcd_ret != DRM_HDCP_AN_LEN) { + drm_dbg_kms(&i915->drm, + "Failed to write An over DP/AUX (%zd)\n", + dpcd_ret); + return dpcd_ret >= 0 ? -EIO : dpcd_ret; + } + + /* + * Since Aksv is Oh-So-Secret, we can't access it in software. So we + * send an empty buffer of the correct length through the DP helpers. On + * the other side, in the transfer hook, we'll generate a flag based on + * the destination address which will tickle the hardware to output the + * Aksv on our behalf after the header is sent. + */ + dpcd_ret = drm_dp_dpcd_write(&dig_port->dp.aux, DP_AUX_HDCP_AKSV, + aksv, DRM_HDCP_KSV_LEN); + if (dpcd_ret != DRM_HDCP_KSV_LEN) { + drm_dbg_kms(&i915->drm, + "Failed to write Aksv over DP/AUX (%zd)\n", + dpcd_ret); + return dpcd_ret >= 0 ? -EIO : dpcd_ret; + } + return 0; +} + +static int intel_dp_hdcp_read_bksv(struct intel_digital_port *dig_port, + u8 *bksv) +{ + struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev); + ssize_t ret; + + ret = drm_dp_dpcd_read(&dig_port->dp.aux, DP_AUX_HDCP_BKSV, bksv, + DRM_HDCP_KSV_LEN); + if (ret != DRM_HDCP_KSV_LEN) { + drm_dbg_kms(&i915->drm, + "Read Bksv from DP/AUX failed (%zd)\n", ret); + return ret >= 0 ? -EIO : ret; + } + return 0; +} + +static int intel_dp_hdcp_read_bstatus(struct intel_digital_port *dig_port, + u8 *bstatus) +{ + struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev); + ssize_t ret; + + /* + * For some reason the HDMI and DP HDCP specs call this register + * definition by different names. In the HDMI spec, it's called BSTATUS, + * but in DP it's called BINFO. + */ + ret = drm_dp_dpcd_read(&dig_port->dp.aux, DP_AUX_HDCP_BINFO, + bstatus, DRM_HDCP_BSTATUS_LEN); + if (ret != DRM_HDCP_BSTATUS_LEN) { + drm_dbg_kms(&i915->drm, + "Read bstatus from DP/AUX failed (%zd)\n", ret); + return ret >= 0 ? -EIO : ret; + } + return 0; +} + +static +int intel_dp_hdcp_read_bcaps(struct intel_digital_port *dig_port, + u8 *bcaps) +{ + struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev); + ssize_t ret; + + ret = drm_dp_dpcd_read(&dig_port->dp.aux, DP_AUX_HDCP_BCAPS, + bcaps, 1); + if (ret != 1) { + drm_dbg_kms(&i915->drm, + "Read bcaps from DP/AUX failed (%zd)\n", ret); + return ret >= 0 ? -EIO : ret; + } + + return 0; +} + +static +int intel_dp_hdcp_repeater_present(struct intel_digital_port *dig_port, + bool *repeater_present) +{ + ssize_t ret; + u8 bcaps; + + ret = intel_dp_hdcp_read_bcaps(dig_port, &bcaps); + if (ret) + return ret; + + *repeater_present = bcaps & DP_BCAPS_REPEATER_PRESENT; + return 0; +} + +static +int intel_dp_hdcp_read_ri_prime(struct intel_digital_port *dig_port, + u8 *ri_prime) +{ + struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev); + ssize_t ret; + + ret = drm_dp_dpcd_read(&dig_port->dp.aux, DP_AUX_HDCP_RI_PRIME, + ri_prime, DRM_HDCP_RI_LEN); + if (ret != DRM_HDCP_RI_LEN) { + drm_dbg_kms(&i915->drm, "Read Ri' from DP/AUX failed (%zd)\n", + ret); + return ret >= 0 ? -EIO : ret; + } + return 0; +} + +static +int intel_dp_hdcp_read_ksv_ready(struct intel_digital_port *dig_port, + bool *ksv_ready) +{ + struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev); + ssize_t ret; + u8 bstatus; + + ret = drm_dp_dpcd_read(&dig_port->dp.aux, DP_AUX_HDCP_BSTATUS, + &bstatus, 1); + if (ret != 1) { + drm_dbg_kms(&i915->drm, + "Read bstatus from DP/AUX failed (%zd)\n", ret); + return ret >= 0 ? -EIO : ret; + } + *ksv_ready = bstatus & DP_BSTATUS_READY; + return 0; +} + +static +int intel_dp_hdcp_read_ksv_fifo(struct intel_digital_port *dig_port, + int num_downstream, u8 *ksv_fifo) +{ + struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev); + ssize_t ret; + int i; + + /* KSV list is read via 15 byte window (3 entries @ 5 bytes each) */ + for (i = 0; i < num_downstream; i += 3) { + size_t len = min(num_downstream - i, 3) * DRM_HDCP_KSV_LEN; + ret = drm_dp_dpcd_read(&dig_port->dp.aux, + DP_AUX_HDCP_KSV_FIFO, + ksv_fifo + i * DRM_HDCP_KSV_LEN, + len); + if (ret != len) { + drm_dbg_kms(&i915->drm, + "Read ksv[%d] from DP/AUX failed (%zd)\n", + i, ret); + return ret >= 0 ? -EIO : ret; + } + } + return 0; +} + +static +int intel_dp_hdcp_read_v_prime_part(struct intel_digital_port *dig_port, + int i, u32 *part) +{ + struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev); + ssize_t ret; + + if (i >= DRM_HDCP_V_PRIME_NUM_PARTS) + return -EINVAL; + + ret = drm_dp_dpcd_read(&dig_port->dp.aux, + DP_AUX_HDCP_V_PRIME(i), part, + DRM_HDCP_V_PRIME_PART_LEN); + if (ret != DRM_HDCP_V_PRIME_PART_LEN) { + drm_dbg_kms(&i915->drm, + "Read v'[%d] from DP/AUX failed (%zd)\n", i, ret); + return ret >= 0 ? -EIO : ret; + } + return 0; +} + +static +int intel_dp_hdcp_toggle_signalling(struct intel_digital_port *dig_port, + enum transcoder cpu_transcoder, + bool enable) +{ + /* Not used for single stream DisplayPort setups */ + return 0; +} + +static +bool intel_dp_hdcp_check_link(struct intel_digital_port *dig_port, + struct intel_connector *connector) +{ + struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev); + ssize_t ret; + u8 bstatus; + + ret = drm_dp_dpcd_read(&dig_port->dp.aux, DP_AUX_HDCP_BSTATUS, + &bstatus, 1); + if (ret != 1) { + drm_dbg_kms(&i915->drm, + "Read bstatus from DP/AUX failed (%zd)\n", ret); + return false; + } + + return !(bstatus & (DP_BSTATUS_LINK_FAILURE | DP_BSTATUS_REAUTH_REQ)); +} + +static +int intel_dp_hdcp_capable(struct intel_digital_port *dig_port, + bool *hdcp_capable) +{ + ssize_t ret; + u8 bcaps; + + ret = intel_dp_hdcp_read_bcaps(dig_port, &bcaps); + if (ret) + return ret; + + *hdcp_capable = bcaps & DP_BCAPS_HDCP_CAPABLE; + return 0; +} + +struct hdcp2_dp_errata_stream_type { + u8 msg_id; + u8 stream_type; +} __packed; + +struct hdcp2_dp_msg_data { + u8 msg_id; + u32 offset; + bool msg_detectable; + u32 timeout; + u32 timeout2; /* Added for non_paired situation */ + /* Timeout to read entire msg */ + u32 msg_read_timeout; +}; + +static const struct hdcp2_dp_msg_data hdcp2_dp_msg_data[] = { + { HDCP_2_2_AKE_INIT, DP_HDCP_2_2_AKE_INIT_OFFSET, false, 0, 0, 0}, + { HDCP_2_2_AKE_SEND_CERT, DP_HDCP_2_2_AKE_SEND_CERT_OFFSET, + false, HDCP_2_2_CERT_TIMEOUT_MS, 0, HDCP_2_2_DP_CERT_READ_TIMEOUT_MS}, + { HDCP_2_2_AKE_NO_STORED_KM, DP_HDCP_2_2_AKE_NO_STORED_KM_OFFSET, + false, 0, 0, 0 }, + { HDCP_2_2_AKE_STORED_KM, DP_HDCP_2_2_AKE_STORED_KM_OFFSET, + false, 0, 0, 0 }, + { HDCP_2_2_AKE_SEND_HPRIME, DP_HDCP_2_2_AKE_SEND_HPRIME_OFFSET, + true, HDCP_2_2_HPRIME_PAIRED_TIMEOUT_MS, + HDCP_2_2_HPRIME_NO_PAIRED_TIMEOUT_MS, HDCP_2_2_DP_HPRIME_READ_TIMEOUT_MS}, + { HDCP_2_2_AKE_SEND_PAIRING_INFO, + DP_HDCP_2_2_AKE_SEND_PAIRING_INFO_OFFSET, true, + HDCP_2_2_PAIRING_TIMEOUT_MS, 0, HDCP_2_2_DP_PAIRING_READ_TIMEOUT_MS }, + { HDCP_2_2_LC_INIT, DP_HDCP_2_2_LC_INIT_OFFSET, false, 0, 0, 0 }, + { HDCP_2_2_LC_SEND_LPRIME, DP_HDCP_2_2_LC_SEND_LPRIME_OFFSET, + false, HDCP_2_2_DP_LPRIME_TIMEOUT_MS, 0, 0 }, + { HDCP_2_2_SKE_SEND_EKS, DP_HDCP_2_2_SKE_SEND_EKS_OFFSET, false, + 0, 0, 0 }, + { HDCP_2_2_REP_SEND_RECVID_LIST, + DP_HDCP_2_2_REP_SEND_RECVID_LIST_OFFSET, true, + HDCP_2_2_RECVID_LIST_TIMEOUT_MS, 0, 0 }, + { HDCP_2_2_REP_SEND_ACK, DP_HDCP_2_2_REP_SEND_ACK_OFFSET, false, + 0, 0, 0 }, + { HDCP_2_2_REP_STREAM_MANAGE, + DP_HDCP_2_2_REP_STREAM_MANAGE_OFFSET, false, + 0, 0, 0}, + { HDCP_2_2_REP_STREAM_READY, DP_HDCP_2_2_REP_STREAM_READY_OFFSET, + false, HDCP_2_2_STREAM_READY_TIMEOUT_MS, 0, 0 }, +/* local define to shovel this through the write_2_2 interface */ +#define HDCP_2_2_ERRATA_DP_STREAM_TYPE 50 + { HDCP_2_2_ERRATA_DP_STREAM_TYPE, + DP_HDCP_2_2_REG_STREAM_TYPE_OFFSET, false, + 0, 0 }, +}; + +static int +intel_dp_hdcp2_read_rx_status(struct intel_digital_port *dig_port, + u8 *rx_status) +{ + struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev); + ssize_t ret; + + ret = drm_dp_dpcd_read(&dig_port->dp.aux, + DP_HDCP_2_2_REG_RXSTATUS_OFFSET, rx_status, + HDCP_2_2_DP_RXSTATUS_LEN); + if (ret != HDCP_2_2_DP_RXSTATUS_LEN) { + drm_dbg_kms(&i915->drm, + "Read bstatus from DP/AUX failed (%zd)\n", ret); + return ret >= 0 ? -EIO : ret; + } + + return 0; +} + +static +int hdcp2_detect_msg_availability(struct intel_digital_port *dig_port, + u8 msg_id, bool *msg_ready) +{ + u8 rx_status; + int ret; + + *msg_ready = false; + ret = intel_dp_hdcp2_read_rx_status(dig_port, &rx_status); + if (ret < 0) + return ret; + + switch (msg_id) { + case HDCP_2_2_AKE_SEND_HPRIME: + if (HDCP_2_2_DP_RXSTATUS_H_PRIME(rx_status)) + *msg_ready = true; + break; + case HDCP_2_2_AKE_SEND_PAIRING_INFO: + if (HDCP_2_2_DP_RXSTATUS_PAIRING(rx_status)) + *msg_ready = true; + break; + case HDCP_2_2_REP_SEND_RECVID_LIST: + if (HDCP_2_2_DP_RXSTATUS_READY(rx_status)) + *msg_ready = true; + break; + default: + DRM_ERROR("Unidentified msg_id: %d\n", msg_id); + return -EINVAL; + } + + return 0; +} + +static ssize_t +intel_dp_hdcp2_wait_for_msg(struct intel_digital_port *dig_port, + const struct hdcp2_dp_msg_data *hdcp2_msg_data) +{ + struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev); + struct intel_dp *dp = &dig_port->dp; + struct intel_hdcp *hdcp = &dp->attached_connector->hdcp; + u8 msg_id = hdcp2_msg_data->msg_id; + int ret, timeout; + bool msg_ready = false; + + if (msg_id == HDCP_2_2_AKE_SEND_HPRIME && !hdcp->is_paired) + timeout = hdcp2_msg_data->timeout2; + else + timeout = hdcp2_msg_data->timeout; + + /* + * There is no way to detect the CERT, LPRIME and STREAM_READY + * availability. So Wait for timeout and read the msg. + */ + if (!hdcp2_msg_data->msg_detectable) { + mdelay(timeout); + ret = 0; + } else { + /* + * As we want to check the msg availability at timeout, Ignoring + * the timeout at wait for CP_IRQ. + */ + intel_dp_hdcp_wait_for_cp_irq(hdcp, timeout); + ret = hdcp2_detect_msg_availability(dig_port, + msg_id, &msg_ready); + if (!msg_ready) + ret = -ETIMEDOUT; + } + + if (ret) + drm_dbg_kms(&i915->drm, + "msg_id %d, ret %d, timeout(mSec): %d\n", + hdcp2_msg_data->msg_id, ret, timeout); + + return ret; +} + +static const struct hdcp2_dp_msg_data *get_hdcp2_dp_msg_data(u8 msg_id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(hdcp2_dp_msg_data); i++) + if (hdcp2_dp_msg_data[i].msg_id == msg_id) + return &hdcp2_dp_msg_data[i]; + + return NULL; +} + +static +int intel_dp_hdcp2_write_msg(struct intel_digital_port *dig_port, + void *buf, size_t size) +{ + unsigned int offset; + u8 *byte = buf; + ssize_t ret, bytes_to_write, len; + const struct hdcp2_dp_msg_data *hdcp2_msg_data; + + hdcp2_msg_data = get_hdcp2_dp_msg_data(*byte); + if (!hdcp2_msg_data) + return -EINVAL; + + offset = hdcp2_msg_data->offset; + + /* No msg_id in DP HDCP2.2 msgs */ + bytes_to_write = size - 1; + byte++; + + while (bytes_to_write) { + len = bytes_to_write > DP_AUX_MAX_PAYLOAD_BYTES ? + DP_AUX_MAX_PAYLOAD_BYTES : bytes_to_write; + + ret = drm_dp_dpcd_write(&dig_port->dp.aux, + offset, (void *)byte, len); + if (ret < 0) + return ret; + + bytes_to_write -= ret; + byte += ret; + offset += ret; + } + + return size; +} + +static +ssize_t get_receiver_id_list_rx_info(struct intel_digital_port *dig_port, u32 *dev_cnt, u8 *byte) +{ + ssize_t ret; + u8 *rx_info = byte; + + ret = drm_dp_dpcd_read(&dig_port->dp.aux, + DP_HDCP_2_2_REG_RXINFO_OFFSET, + (void *)rx_info, HDCP_2_2_RXINFO_LEN); + if (ret != HDCP_2_2_RXINFO_LEN) + return ret >= 0 ? -EIO : ret; + + *dev_cnt = (HDCP_2_2_DEV_COUNT_HI(rx_info[0]) << 4 | + HDCP_2_2_DEV_COUNT_LO(rx_info[1])); + + if (*dev_cnt > HDCP_2_2_MAX_DEVICE_COUNT) + *dev_cnt = HDCP_2_2_MAX_DEVICE_COUNT; + + return ret; +} + +static +int intel_dp_hdcp2_read_msg(struct intel_digital_port *dig_port, + u8 msg_id, void *buf, size_t size) +{ + struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev); + struct intel_dp *dp = &dig_port->dp; + struct intel_hdcp *hdcp = &dp->attached_connector->hdcp; + unsigned int offset; + u8 *byte = buf; + ssize_t ret, bytes_to_recv, len; + const struct hdcp2_dp_msg_data *hdcp2_msg_data; + ktime_t msg_end = ktime_set(0, 0); + bool msg_expired; + u32 dev_cnt; + + hdcp2_msg_data = get_hdcp2_dp_msg_data(msg_id); + if (!hdcp2_msg_data) + return -EINVAL; + offset = hdcp2_msg_data->offset; + + ret = intel_dp_hdcp2_wait_for_msg(dig_port, hdcp2_msg_data); + if (ret < 0) + return ret; + + hdcp->cp_irq_count_cached = atomic_read(&hdcp->cp_irq_count); + + /* DP adaptation msgs has no msg_id */ + byte++; + + if (msg_id == HDCP_2_2_REP_SEND_RECVID_LIST) { + ret = get_receiver_id_list_rx_info(dig_port, &dev_cnt, byte); + if (ret < 0) + return ret; + + byte += ret; + size = sizeof(struct hdcp2_rep_send_receiverid_list) - + HDCP_2_2_RXINFO_LEN - HDCP_2_2_RECEIVER_IDS_MAX_LEN + + (dev_cnt * HDCP_2_2_RECEIVER_ID_LEN); + offset += HDCP_2_2_RXINFO_LEN; + } + + bytes_to_recv = size - 1; + + while (bytes_to_recv) { + len = bytes_to_recv > DP_AUX_MAX_PAYLOAD_BYTES ? + DP_AUX_MAX_PAYLOAD_BYTES : bytes_to_recv; + + /* Entire msg read timeout since initiate of msg read */ + if (bytes_to_recv == size - 1 && hdcp2_msg_data->msg_read_timeout > 0) + msg_end = ktime_add_ms(ktime_get_raw(), + hdcp2_msg_data->msg_read_timeout); + + ret = drm_dp_dpcd_read(&dig_port->dp.aux, offset, + (void *)byte, len); + if (ret < 0) { + drm_dbg_kms(&i915->drm, "msg_id %d, ret %zd\n", + msg_id, ret); + return ret; + } + + bytes_to_recv -= ret; + byte += ret; + offset += ret; + } + + if (hdcp2_msg_data->msg_read_timeout > 0) { + msg_expired = ktime_after(ktime_get_raw(), msg_end); + if (msg_expired) { + drm_dbg_kms(&i915->drm, "msg_id %d, entire msg read timeout(mSec): %d\n", + msg_id, hdcp2_msg_data->msg_read_timeout); + return -ETIMEDOUT; + } + } + + byte = buf; + *byte = msg_id; + + return size; +} + +static +int intel_dp_hdcp2_config_stream_type(struct intel_digital_port *dig_port, + bool is_repeater, u8 content_type) +{ + int ret; + struct hdcp2_dp_errata_stream_type stream_type_msg; + + if (is_repeater) + return 0; + + /* + * Errata for DP: As Stream type is used for encryption, Receiver + * should be communicated with stream type for the decryption of the + * content. + * Repeater will be communicated with stream type as a part of it's + * auth later in time. + */ + stream_type_msg.msg_id = HDCP_2_2_ERRATA_DP_STREAM_TYPE; + stream_type_msg.stream_type = content_type; + + ret = intel_dp_hdcp2_write_msg(dig_port, &stream_type_msg, + sizeof(stream_type_msg)); + + return ret < 0 ? ret : 0; + +} + +static +int intel_dp_hdcp2_check_link(struct intel_digital_port *dig_port, + struct intel_connector *connector) +{ + u8 rx_status; + int ret; + + ret = intel_dp_hdcp2_read_rx_status(dig_port, &rx_status); + if (ret) + return ret; + + if (HDCP_2_2_DP_RXSTATUS_REAUTH_REQ(rx_status)) + ret = HDCP_REAUTH_REQUEST; + else if (HDCP_2_2_DP_RXSTATUS_LINK_FAILED(rx_status)) + ret = HDCP_LINK_INTEGRITY_FAILURE; + else if (HDCP_2_2_DP_RXSTATUS_READY(rx_status)) + ret = HDCP_TOPOLOGY_CHANGE; + + return ret; +} + +static +int intel_dp_hdcp2_capable(struct intel_digital_port *dig_port, + bool *capable) +{ + u8 rx_caps[3]; + int ret; + + *capable = false; + ret = drm_dp_dpcd_read(&dig_port->dp.aux, + DP_HDCP_2_2_REG_RX_CAPS_OFFSET, + rx_caps, HDCP_2_2_RXCAPS_LEN); + if (ret != HDCP_2_2_RXCAPS_LEN) + return ret >= 0 ? -EIO : ret; + + if (rx_caps[0] == HDCP_2_2_RX_CAPS_VERSION_VAL && + HDCP_2_2_DP_HDCP_CAPABLE(rx_caps[2])) + *capable = true; + + return 0; +} + +static const struct intel_hdcp_shim intel_dp_hdcp_shim = { + .write_an_aksv = intel_dp_hdcp_write_an_aksv, + .read_bksv = intel_dp_hdcp_read_bksv, + .read_bstatus = intel_dp_hdcp_read_bstatus, + .repeater_present = intel_dp_hdcp_repeater_present, + .read_ri_prime = intel_dp_hdcp_read_ri_prime, + .read_ksv_ready = intel_dp_hdcp_read_ksv_ready, + .read_ksv_fifo = intel_dp_hdcp_read_ksv_fifo, + .read_v_prime_part = intel_dp_hdcp_read_v_prime_part, + .toggle_signalling = intel_dp_hdcp_toggle_signalling, + .check_link = intel_dp_hdcp_check_link, + .hdcp_capable = intel_dp_hdcp_capable, + .write_2_2_msg = intel_dp_hdcp2_write_msg, + .read_2_2_msg = intel_dp_hdcp2_read_msg, + .config_stream_type = intel_dp_hdcp2_config_stream_type, + .check_2_2_link = intel_dp_hdcp2_check_link, + .hdcp_2_2_capable = intel_dp_hdcp2_capable, + .protocol = HDCP_PROTOCOL_DP, +}; + +static int +intel_dp_mst_toggle_hdcp_stream_select(struct intel_connector *connector, + bool enable) +{ + struct intel_digital_port *dig_port = intel_attached_dig_port(connector); + struct drm_i915_private *i915 = to_i915(connector->base.dev); + struct intel_hdcp *hdcp = &connector->hdcp; + int ret; + + ret = intel_ddi_toggle_hdcp_bits(&dig_port->base, + hdcp->stream_transcoder, enable, + TRANS_DDI_HDCP_SELECT); + if (ret) + drm_err(&i915->drm, "%s HDCP stream select failed (%d)\n", + enable ? "Enable" : "Disable", ret); + return ret; +} + +static int +intel_dp_mst_hdcp_stream_encryption(struct intel_connector *connector, + bool enable) +{ + struct intel_digital_port *dig_port = intel_attached_dig_port(connector); + struct drm_i915_private *i915 = to_i915(connector->base.dev); + struct intel_hdcp *hdcp = &connector->hdcp; + enum port port = dig_port->base.port; + enum transcoder cpu_transcoder = hdcp->stream_transcoder; + u32 stream_enc_status; + int ret; + + ret = intel_dp_mst_toggle_hdcp_stream_select(connector, enable); + if (ret) + return ret; + + stream_enc_status = transcoder_to_stream_enc_status(cpu_transcoder); + if (!stream_enc_status) + return -EINVAL; + + /* Wait for encryption confirmation */ + if (intel_de_wait_for_register(i915, + HDCP_STATUS(i915, cpu_transcoder, port), + stream_enc_status, + enable ? stream_enc_status : 0, + HDCP_ENCRYPT_STATUS_CHANGE_TIMEOUT_MS)) { + drm_err(&i915->drm, "Timed out waiting for transcoder: %s stream encryption %s\n", + transcoder_name(cpu_transcoder), enable ? "enabled" : "disabled"); + return -ETIMEDOUT; + } + + return 0; +} + +static int +intel_dp_mst_hdcp2_stream_encryption(struct intel_connector *connector, + bool enable) +{ + struct intel_digital_port *dig_port = intel_attached_dig_port(connector); + struct drm_i915_private *i915 = to_i915(connector->base.dev); + struct hdcp_port_data *data = &dig_port->hdcp_port_data; + struct intel_hdcp *hdcp = &connector->hdcp; + enum transcoder cpu_transcoder = hdcp->stream_transcoder; + enum pipe pipe = (enum pipe)cpu_transcoder; + enum port port = dig_port->base.port; + int ret; + + drm_WARN_ON(&i915->drm, enable && + !!(intel_de_read(i915, HDCP2_AUTH_STREAM(i915, cpu_transcoder, port)) + & AUTH_STREAM_TYPE) != data->streams[0].stream_type); + + ret = intel_dp_mst_toggle_hdcp_stream_select(connector, enable); + if (ret) + return ret; + + /* Wait for encryption confirmation */ + if (intel_de_wait_for_register(i915, + HDCP2_STREAM_STATUS(i915, cpu_transcoder, pipe), + STREAM_ENCRYPTION_STATUS, + enable ? STREAM_ENCRYPTION_STATUS : 0, + HDCP_ENCRYPT_STATUS_CHANGE_TIMEOUT_MS)) { + drm_err(&i915->drm, "Timed out waiting for transcoder: %s stream encryption %s\n", + transcoder_name(cpu_transcoder), enable ? "enabled" : "disabled"); + return -ETIMEDOUT; + } + + return 0; +} + +static +int intel_dp_mst_hdcp2_check_link(struct intel_digital_port *dig_port, + struct intel_connector *connector) +{ + struct intel_hdcp *hdcp = &connector->hdcp; + int ret; + + /* + * We do need to do the Link Check only for the connector involved with + * HDCP port authentication and encryption. + * We can re-use the hdcp->is_repeater flag to know that the connector + * involved with HDCP port authentication and encryption. + */ + if (hdcp->is_repeater) { + ret = intel_dp_hdcp2_check_link(dig_port, connector); + if (ret) + return ret; + } + + return 0; +} + +static const struct intel_hdcp_shim intel_dp_mst_hdcp_shim = { + .write_an_aksv = intel_dp_hdcp_write_an_aksv, + .read_bksv = intel_dp_hdcp_read_bksv, + .read_bstatus = intel_dp_hdcp_read_bstatus, + .repeater_present = intel_dp_hdcp_repeater_present, + .read_ri_prime = intel_dp_hdcp_read_ri_prime, + .read_ksv_ready = intel_dp_hdcp_read_ksv_ready, + .read_ksv_fifo = intel_dp_hdcp_read_ksv_fifo, + .read_v_prime_part = intel_dp_hdcp_read_v_prime_part, + .toggle_signalling = intel_dp_hdcp_toggle_signalling, + .stream_encryption = intel_dp_mst_hdcp_stream_encryption, + .check_link = intel_dp_hdcp_check_link, + .hdcp_capable = intel_dp_hdcp_capable, + .write_2_2_msg = intel_dp_hdcp2_write_msg, + .read_2_2_msg = intel_dp_hdcp2_read_msg, + .config_stream_type = intel_dp_hdcp2_config_stream_type, + .stream_2_2_encryption = intel_dp_mst_hdcp2_stream_encryption, + .check_2_2_link = intel_dp_mst_hdcp2_check_link, + .hdcp_2_2_capable = intel_dp_hdcp2_capable, + .protocol = HDCP_PROTOCOL_DP, +}; + +int intel_dp_hdcp_init(struct intel_digital_port *dig_port, + struct intel_connector *intel_connector) +{ + struct drm_device *dev = intel_connector->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + struct intel_encoder *intel_encoder = &dig_port->base; + enum port port = intel_encoder->port; + struct intel_dp *intel_dp = &dig_port->dp; + + if (!is_hdcp_supported(dev_priv, port)) + return 0; + + if (intel_connector->mst_port) + return intel_hdcp_init(intel_connector, dig_port, + &intel_dp_mst_hdcp_shim); + else if (!intel_dp_is_edp(intel_dp)) + return intel_hdcp_init(intel_connector, dig_port, + &intel_dp_hdcp_shim); + + return 0; +} diff --git a/drivers/gpu/drm/i915/display/intel_dp_hdcp.h b/drivers/gpu/drm/i915/display/intel_dp_hdcp.h new file mode 100644 index 000000000..eff5ec5c5 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dp_hdcp.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2021 Intel Corporation + */ + +#ifndef __INTEL_DP_HDCP___ +#define __INTEL_DP_HDCP___ + +struct intel_connector; +struct intel_digital_port; + +int intel_dp_hdcp_init(struct intel_digital_port *dig_port, + struct intel_connector *intel_connector); + +#endif /* __INTEL_DP_HDCP___ */ diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_training.c b/drivers/gpu/drm/i915/display/intel_dp_link_training.c new file mode 100644 index 000000000..3d3efcf02 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dp_link_training.c @@ -0,0 +1,1456 @@ +/* + * Copyright © 2008-2015 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "i915_drv.h" +#include "intel_display_types.h" +#include "intel_dp.h" +#include "intel_dp_link_training.h" + +static void intel_dp_reset_lttpr_common_caps(struct intel_dp *intel_dp) +{ + memset(intel_dp->lttpr_common_caps, 0, sizeof(intel_dp->lttpr_common_caps)); +} + +static void intel_dp_reset_lttpr_count(struct intel_dp *intel_dp) +{ + intel_dp->lttpr_common_caps[DP_PHY_REPEATER_CNT - + DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV] = 0; +} + +static u8 *intel_dp_lttpr_phy_caps(struct intel_dp *intel_dp, + enum drm_dp_phy dp_phy) +{ + return intel_dp->lttpr_phy_caps[dp_phy - DP_PHY_LTTPR1]; +} + +static void intel_dp_read_lttpr_phy_caps(struct intel_dp *intel_dp, + const u8 dpcd[DP_RECEIVER_CAP_SIZE], + enum drm_dp_phy dp_phy) +{ + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + u8 *phy_caps = intel_dp_lttpr_phy_caps(intel_dp, dp_phy); + + if (drm_dp_read_lttpr_phy_caps(&intel_dp->aux, dpcd, dp_phy, phy_caps) < 0) { + drm_dbg_kms(&dp_to_i915(intel_dp)->drm, + "[ENCODER:%d:%s][%s] failed to read the PHY caps\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy)); + return; + } + + drm_dbg_kms(&dp_to_i915(intel_dp)->drm, + "[ENCODER:%d:%s][%s] PHY capabilities: %*ph\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy), + (int)sizeof(intel_dp->lttpr_phy_caps[0]), + phy_caps); +} + +static bool intel_dp_read_lttpr_common_caps(struct intel_dp *intel_dp, + const u8 dpcd[DP_RECEIVER_CAP_SIZE]) +{ + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + int ret; + + ret = drm_dp_read_lttpr_common_caps(&intel_dp->aux, dpcd, + intel_dp->lttpr_common_caps); + if (ret < 0) + goto reset_caps; + + drm_dbg_kms(&dp_to_i915(intel_dp)->drm, + "[ENCODER:%d:%s] LTTPR common capabilities: %*ph\n", + encoder->base.base.id, encoder->base.name, + (int)sizeof(intel_dp->lttpr_common_caps), + intel_dp->lttpr_common_caps); + + /* The minimum value of LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV is 1.4 */ + if (intel_dp->lttpr_common_caps[0] < 0x14) + goto reset_caps; + + return true; + +reset_caps: + intel_dp_reset_lttpr_common_caps(intel_dp); + return false; +} + +static bool +intel_dp_set_lttpr_transparent_mode(struct intel_dp *intel_dp, bool enable) +{ + u8 val = enable ? DP_PHY_REPEATER_MODE_TRANSPARENT : + DP_PHY_REPEATER_MODE_NON_TRANSPARENT; + + return drm_dp_dpcd_write(&intel_dp->aux, DP_PHY_REPEATER_MODE, &val, 1) == 1; +} + +static int intel_dp_init_lttpr(struct intel_dp *intel_dp, const u8 dpcd[DP_RECEIVER_CAP_SIZE]) +{ + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + int lttpr_count; + int i; + + if (!intel_dp_read_lttpr_common_caps(intel_dp, dpcd)) + return 0; + + lttpr_count = drm_dp_lttpr_count(intel_dp->lttpr_common_caps); + /* + * Prevent setting LTTPR transparent mode explicitly if no LTTPRs are + * detected as this breaks link training at least on the Dell WD19TB + * dock. + */ + if (lttpr_count == 0) + return 0; + + /* + * See DP Standard v2.0 3.6.6.1. about the explicit disabling of + * non-transparent mode and the disable->enable non-transparent mode + * sequence. + */ + intel_dp_set_lttpr_transparent_mode(intel_dp, true); + + /* + * In case of unsupported number of LTTPRs or failing to switch to + * non-transparent mode fall-back to transparent link training mode, + * still taking into account any LTTPR common lane- rate/count limits. + */ + if (lttpr_count < 0) + return 0; + + if (!intel_dp_set_lttpr_transparent_mode(intel_dp, false)) { + drm_dbg_kms(&i915->drm, + "[ENCODER:%d:%s] Switching to LTTPR non-transparent LT mode failed, fall-back to transparent mode\n", + encoder->base.base.id, encoder->base.name); + + intel_dp_set_lttpr_transparent_mode(intel_dp, true); + intel_dp_reset_lttpr_count(intel_dp); + + return 0; + } + + for (i = 0; i < lttpr_count; i++) + intel_dp_read_lttpr_phy_caps(intel_dp, dpcd, DP_PHY_LTTPR(i)); + + return lttpr_count; +} + +/** + * intel_dp_init_lttpr_and_dprx_caps - detect LTTPR and DPRX caps, init the LTTPR link training mode + * @intel_dp: Intel DP struct + * + * Read the LTTPR common and DPRX capabilities and switch to non-transparent + * link training mode if any is detected and read the PHY capabilities for all + * detected LTTPRs. In case of an LTTPR detection error or if the number of + * LTTPRs is more than is supported (8), fall back to the no-LTTPR, + * transparent mode link training mode. + * + * Returns: + * >0 if LTTPRs were detected and the non-transparent LT mode was set. The + * DPRX capabilities are read out. + * 0 if no LTTPRs or more than 8 LTTPRs were detected or in case of a + * detection failure and the transparent LT mode was set. The DPRX + * capabilities are read out. + * <0 Reading out the DPRX capabilities failed. + */ +int intel_dp_init_lttpr_and_dprx_caps(struct intel_dp *intel_dp) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + int lttpr_count = 0; + + /* + * Detecting LTTPRs must be avoided on platforms with an AUX timeout + * period < 3.2ms. (see DP Standard v2.0, 2.11.2, 3.6.6.1). + */ + if (!intel_dp_is_edp(intel_dp) && + (DISPLAY_VER(i915) >= 10 && !IS_GEMINILAKE(i915))) { + u8 dpcd[DP_RECEIVER_CAP_SIZE]; + + if (drm_dp_dpcd_probe(&intel_dp->aux, DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV)) + return -EIO; + + if (drm_dp_read_dpcd_caps(&intel_dp->aux, dpcd)) + return -EIO; + + lttpr_count = intel_dp_init_lttpr(intel_dp, dpcd); + } + + /* + * The DPTX shall read the DPRX caps after LTTPR detection, so re-read + * it here. + */ + if (drm_dp_read_dpcd_caps(&intel_dp->aux, intel_dp->dpcd)) { + intel_dp_reset_lttpr_common_caps(intel_dp); + return -EIO; + } + + return lttpr_count; +} + +static u8 dp_voltage_max(u8 preemph) +{ + switch (preemph & DP_TRAIN_PRE_EMPHASIS_MASK) { + case DP_TRAIN_PRE_EMPH_LEVEL_0: + return DP_TRAIN_VOLTAGE_SWING_LEVEL_3; + case DP_TRAIN_PRE_EMPH_LEVEL_1: + return DP_TRAIN_VOLTAGE_SWING_LEVEL_2; + case DP_TRAIN_PRE_EMPH_LEVEL_2: + return DP_TRAIN_VOLTAGE_SWING_LEVEL_1; + case DP_TRAIN_PRE_EMPH_LEVEL_3: + default: + return DP_TRAIN_VOLTAGE_SWING_LEVEL_0; + } +} + +static u8 intel_dp_lttpr_voltage_max(struct intel_dp *intel_dp, + enum drm_dp_phy dp_phy) +{ + const u8 *phy_caps = intel_dp_lttpr_phy_caps(intel_dp, dp_phy); + + if (drm_dp_lttpr_voltage_swing_level_3_supported(phy_caps)) + return DP_TRAIN_VOLTAGE_SWING_LEVEL_3; + else + return DP_TRAIN_VOLTAGE_SWING_LEVEL_2; +} + +static u8 intel_dp_lttpr_preemph_max(struct intel_dp *intel_dp, + enum drm_dp_phy dp_phy) +{ + const u8 *phy_caps = intel_dp_lttpr_phy_caps(intel_dp, dp_phy); + + if (drm_dp_lttpr_pre_emphasis_level_3_supported(phy_caps)) + return DP_TRAIN_PRE_EMPH_LEVEL_3; + else + return DP_TRAIN_PRE_EMPH_LEVEL_2; +} + +static bool +intel_dp_phy_is_downstream_of_source(struct intel_dp *intel_dp, + enum drm_dp_phy dp_phy) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + int lttpr_count = drm_dp_lttpr_count(intel_dp->lttpr_common_caps); + + drm_WARN_ON_ONCE(&i915->drm, lttpr_count <= 0 && dp_phy != DP_PHY_DPRX); + + return lttpr_count <= 0 || dp_phy == DP_PHY_LTTPR(lttpr_count - 1); +} + +static u8 intel_dp_phy_voltage_max(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + enum drm_dp_phy dp_phy) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + u8 voltage_max; + + /* + * Get voltage_max from the DPTX_PHY (source or LTTPR) upstream from + * the DPRX_PHY we train. + */ + if (intel_dp_phy_is_downstream_of_source(intel_dp, dp_phy)) + voltage_max = intel_dp->voltage_max(intel_dp, crtc_state); + else + voltage_max = intel_dp_lttpr_voltage_max(intel_dp, dp_phy + 1); + + drm_WARN_ON_ONCE(&i915->drm, + voltage_max != DP_TRAIN_VOLTAGE_SWING_LEVEL_2 && + voltage_max != DP_TRAIN_VOLTAGE_SWING_LEVEL_3); + + return voltage_max; +} + +static u8 intel_dp_phy_preemph_max(struct intel_dp *intel_dp, + enum drm_dp_phy dp_phy) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + u8 preemph_max; + + /* + * Get preemph_max from the DPTX_PHY (source or LTTPR) upstream from + * the DPRX_PHY we train. + */ + if (intel_dp_phy_is_downstream_of_source(intel_dp, dp_phy)) + preemph_max = intel_dp->preemph_max(intel_dp); + else + preemph_max = intel_dp_lttpr_preemph_max(intel_dp, dp_phy + 1); + + drm_WARN_ON_ONCE(&i915->drm, + preemph_max != DP_TRAIN_PRE_EMPH_LEVEL_2 && + preemph_max != DP_TRAIN_PRE_EMPH_LEVEL_3); + + return preemph_max; +} + +static bool has_per_lane_signal_levels(struct intel_dp *intel_dp, + enum drm_dp_phy dp_phy) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + + return !intel_dp_phy_is_downstream_of_source(intel_dp, dp_phy) || + DISPLAY_VER(i915) >= 11; +} + +/* 128b/132b */ +static u8 intel_dp_get_lane_adjust_tx_ffe_preset(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + enum drm_dp_phy dp_phy, + const u8 link_status[DP_LINK_STATUS_SIZE], + int lane) +{ + u8 tx_ffe = 0; + + if (has_per_lane_signal_levels(intel_dp, dp_phy)) { + lane = min(lane, crtc_state->lane_count - 1); + tx_ffe = drm_dp_get_adjust_tx_ffe_preset(link_status, lane); + } else { + for (lane = 0; lane < crtc_state->lane_count; lane++) + tx_ffe = max(tx_ffe, drm_dp_get_adjust_tx_ffe_preset(link_status, lane)); + } + + return tx_ffe; +} + +/* 8b/10b */ +static u8 intel_dp_get_lane_adjust_vswing_preemph(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + enum drm_dp_phy dp_phy, + const u8 link_status[DP_LINK_STATUS_SIZE], + int lane) +{ + u8 v = 0; + u8 p = 0; + u8 voltage_max; + u8 preemph_max; + + if (has_per_lane_signal_levels(intel_dp, dp_phy)) { + lane = min(lane, crtc_state->lane_count - 1); + + v = drm_dp_get_adjust_request_voltage(link_status, lane); + p = drm_dp_get_adjust_request_pre_emphasis(link_status, lane); + } else { + for (lane = 0; lane < crtc_state->lane_count; lane++) { + v = max(v, drm_dp_get_adjust_request_voltage(link_status, lane)); + p = max(p, drm_dp_get_adjust_request_pre_emphasis(link_status, lane)); + } + } + + preemph_max = intel_dp_phy_preemph_max(intel_dp, dp_phy); + if (p >= preemph_max) + p = preemph_max | DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; + + v = min(v, dp_voltage_max(p)); + + voltage_max = intel_dp_phy_voltage_max(intel_dp, crtc_state, dp_phy); + if (v >= voltage_max) + v = voltage_max | DP_TRAIN_MAX_SWING_REACHED; + + return v | p; +} + +static u8 intel_dp_get_lane_adjust_train(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + enum drm_dp_phy dp_phy, + const u8 link_status[DP_LINK_STATUS_SIZE], + int lane) +{ + if (intel_dp_is_uhbr(crtc_state)) + return intel_dp_get_lane_adjust_tx_ffe_preset(intel_dp, crtc_state, + dp_phy, link_status, lane); + else + return intel_dp_get_lane_adjust_vswing_preemph(intel_dp, crtc_state, + dp_phy, link_status, lane); +} + +#define TRAIN_REQ_FMT "%d/%d/%d/%d" +#define _TRAIN_REQ_VSWING_ARGS(link_status, lane) \ + (drm_dp_get_adjust_request_voltage((link_status), (lane)) >> DP_TRAIN_VOLTAGE_SWING_SHIFT) +#define TRAIN_REQ_VSWING_ARGS(link_status) \ + _TRAIN_REQ_VSWING_ARGS(link_status, 0), \ + _TRAIN_REQ_VSWING_ARGS(link_status, 1), \ + _TRAIN_REQ_VSWING_ARGS(link_status, 2), \ + _TRAIN_REQ_VSWING_ARGS(link_status, 3) +#define _TRAIN_REQ_PREEMPH_ARGS(link_status, lane) \ + (drm_dp_get_adjust_request_pre_emphasis((link_status), (lane)) >> DP_TRAIN_PRE_EMPHASIS_SHIFT) +#define TRAIN_REQ_PREEMPH_ARGS(link_status) \ + _TRAIN_REQ_PREEMPH_ARGS(link_status, 0), \ + _TRAIN_REQ_PREEMPH_ARGS(link_status, 1), \ + _TRAIN_REQ_PREEMPH_ARGS(link_status, 2), \ + _TRAIN_REQ_PREEMPH_ARGS(link_status, 3) +#define _TRAIN_REQ_TX_FFE_ARGS(link_status, lane) \ + drm_dp_get_adjust_tx_ffe_preset((link_status), (lane)) +#define TRAIN_REQ_TX_FFE_ARGS(link_status) \ + _TRAIN_REQ_TX_FFE_ARGS(link_status, 0), \ + _TRAIN_REQ_TX_FFE_ARGS(link_status, 1), \ + _TRAIN_REQ_TX_FFE_ARGS(link_status, 2), \ + _TRAIN_REQ_TX_FFE_ARGS(link_status, 3) + +void +intel_dp_get_adjust_train(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + enum drm_dp_phy dp_phy, + const u8 link_status[DP_LINK_STATUS_SIZE]) +{ + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + int lane; + + if (intel_dp_is_uhbr(crtc_state)) { + drm_dbg_kms(&i915->drm, "[ENCODER:%d:%s][%s] 128b/132b, lanes: %d, " + "TX FFE request: " TRAIN_REQ_FMT "\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy), + crtc_state->lane_count, + TRAIN_REQ_TX_FFE_ARGS(link_status)); + } else { + drm_dbg_kms(&i915->drm, "[ENCODER:%d:%s][%s] 8b/10b, lanes: %d, " + "vswing request: " TRAIN_REQ_FMT ", " + "pre-emphasis request: " TRAIN_REQ_FMT "\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy), + crtc_state->lane_count, + TRAIN_REQ_VSWING_ARGS(link_status), + TRAIN_REQ_PREEMPH_ARGS(link_status)); + } + + for (lane = 0; lane < 4; lane++) + intel_dp->train_set[lane] = + intel_dp_get_lane_adjust_train(intel_dp, crtc_state, + dp_phy, link_status, lane); +} + +static int intel_dp_training_pattern_set_reg(struct intel_dp *intel_dp, + enum drm_dp_phy dp_phy) +{ + return dp_phy == DP_PHY_DPRX ? + DP_TRAINING_PATTERN_SET : + DP_TRAINING_PATTERN_SET_PHY_REPEATER(dp_phy); +} + +static bool +intel_dp_set_link_train(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + enum drm_dp_phy dp_phy, + u8 dp_train_pat) +{ + int reg = intel_dp_training_pattern_set_reg(intel_dp, dp_phy); + u8 buf[sizeof(intel_dp->train_set) + 1]; + int len; + + intel_dp_program_link_training_pattern(intel_dp, crtc_state, + dp_phy, dp_train_pat); + + buf[0] = dp_train_pat; + /* DP_TRAINING_LANEx_SET follow DP_TRAINING_PATTERN_SET */ + memcpy(buf + 1, intel_dp->train_set, crtc_state->lane_count); + len = crtc_state->lane_count + 1; + + return drm_dp_dpcd_write(&intel_dp->aux, reg, buf, len) == len; +} + +static char dp_training_pattern_name(u8 train_pat) +{ + switch (train_pat) { + case DP_TRAINING_PATTERN_1: + case DP_TRAINING_PATTERN_2: + case DP_TRAINING_PATTERN_3: + return '0' + train_pat; + case DP_TRAINING_PATTERN_4: + return '4'; + default: + MISSING_CASE(train_pat); + return '?'; + } +} + +void +intel_dp_program_link_training_pattern(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + enum drm_dp_phy dp_phy, + u8 dp_train_pat) +{ + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + u8 train_pat = intel_dp_training_pattern_symbol(dp_train_pat); + + if (train_pat != DP_TRAINING_PATTERN_DISABLE) + drm_dbg_kms(&i915->drm, + "[ENCODER:%d:%s][%s] Using DP training pattern TPS%c\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy), + dp_training_pattern_name(train_pat)); + + intel_dp->set_link_train(intel_dp, crtc_state, dp_train_pat); +} + +#define TRAIN_SET_FMT "%d%s/%d%s/%d%s/%d%s" +#define _TRAIN_SET_VSWING_ARGS(train_set) \ + ((train_set) & DP_TRAIN_VOLTAGE_SWING_MASK) >> DP_TRAIN_VOLTAGE_SWING_SHIFT, \ + (train_set) & DP_TRAIN_MAX_SWING_REACHED ? "(max)" : "" +#define TRAIN_SET_VSWING_ARGS(train_set) \ + _TRAIN_SET_VSWING_ARGS((train_set)[0]), \ + _TRAIN_SET_VSWING_ARGS((train_set)[1]), \ + _TRAIN_SET_VSWING_ARGS((train_set)[2]), \ + _TRAIN_SET_VSWING_ARGS((train_set)[3]) +#define _TRAIN_SET_PREEMPH_ARGS(train_set) \ + ((train_set) & DP_TRAIN_PRE_EMPHASIS_MASK) >> DP_TRAIN_PRE_EMPHASIS_SHIFT, \ + (train_set) & DP_TRAIN_MAX_PRE_EMPHASIS_REACHED ? "(max)" : "" +#define TRAIN_SET_PREEMPH_ARGS(train_set) \ + _TRAIN_SET_PREEMPH_ARGS((train_set)[0]), \ + _TRAIN_SET_PREEMPH_ARGS((train_set)[1]), \ + _TRAIN_SET_PREEMPH_ARGS((train_set)[2]), \ + _TRAIN_SET_PREEMPH_ARGS((train_set)[3]) +#define _TRAIN_SET_TX_FFE_ARGS(train_set) \ + ((train_set) & DP_TX_FFE_PRESET_VALUE_MASK), "" +#define TRAIN_SET_TX_FFE_ARGS(train_set) \ + _TRAIN_SET_TX_FFE_ARGS((train_set)[0]), \ + _TRAIN_SET_TX_FFE_ARGS((train_set)[1]), \ + _TRAIN_SET_TX_FFE_ARGS((train_set)[2]), \ + _TRAIN_SET_TX_FFE_ARGS((train_set)[3]) + +void intel_dp_set_signal_levels(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + enum drm_dp_phy dp_phy) +{ + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + + if (intel_dp_is_uhbr(crtc_state)) { + drm_dbg_kms(&i915->drm, "[ENCODER:%d:%s][%s] 128b/132b, lanes: %d, " + "TX FFE presets: " TRAIN_SET_FMT "\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy), + crtc_state->lane_count, + TRAIN_SET_TX_FFE_ARGS(intel_dp->train_set)); + } else { + drm_dbg_kms(&i915->drm, "[ENCODER:%d:%s][%s] 8b/10b, lanes: %d, " + "vswing levels: " TRAIN_SET_FMT ", " + "pre-emphasis levels: " TRAIN_SET_FMT "\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy), + crtc_state->lane_count, + TRAIN_SET_VSWING_ARGS(intel_dp->train_set), + TRAIN_SET_PREEMPH_ARGS(intel_dp->train_set)); + } + + if (intel_dp_phy_is_downstream_of_source(intel_dp, dp_phy)) + encoder->set_signal_levels(encoder, crtc_state); +} + +static bool +intel_dp_reset_link_train(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + enum drm_dp_phy dp_phy, + u8 dp_train_pat) +{ + memset(intel_dp->train_set, 0, sizeof(intel_dp->train_set)); + intel_dp_set_signal_levels(intel_dp, crtc_state, dp_phy); + return intel_dp_set_link_train(intel_dp, crtc_state, dp_phy, dp_train_pat); +} + +static bool +intel_dp_update_link_train(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + enum drm_dp_phy dp_phy) +{ + int reg = dp_phy == DP_PHY_DPRX ? + DP_TRAINING_LANE0_SET : + DP_TRAINING_LANE0_SET_PHY_REPEATER(dp_phy); + int ret; + + intel_dp_set_signal_levels(intel_dp, crtc_state, dp_phy); + + ret = drm_dp_dpcd_write(&intel_dp->aux, reg, + intel_dp->train_set, crtc_state->lane_count); + + return ret == crtc_state->lane_count; +} + +/* 128b/132b */ +static bool intel_dp_lane_max_tx_ffe_reached(u8 train_set_lane) +{ + return (train_set_lane & DP_TX_FFE_PRESET_VALUE_MASK) == + DP_TX_FFE_PRESET_VALUE_MASK; +} + +/* + * 8b/10b + * + * FIXME: The DP spec is very confusing here, also the Link CTS spec seems to + * have self contradicting tests around this area. + * + * In lieu of better ideas let's just stop when we've reached the max supported + * vswing with its max pre-emphasis, which is either 2+1 or 3+0 depending on + * whether vswing level 3 is supported or not. + */ +static bool intel_dp_lane_max_vswing_reached(u8 train_set_lane) +{ + u8 v = (train_set_lane & DP_TRAIN_VOLTAGE_SWING_MASK) >> + DP_TRAIN_VOLTAGE_SWING_SHIFT; + u8 p = (train_set_lane & DP_TRAIN_PRE_EMPHASIS_MASK) >> + DP_TRAIN_PRE_EMPHASIS_SHIFT; + + if ((train_set_lane & DP_TRAIN_MAX_SWING_REACHED) == 0) + return false; + + if (v + p != 3) + return false; + + return true; +} + +static bool intel_dp_link_max_vswing_reached(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + int lane; + + for (lane = 0; lane < crtc_state->lane_count; lane++) { + u8 train_set_lane = intel_dp->train_set[lane]; + + if (intel_dp_is_uhbr(crtc_state)) { + if (!intel_dp_lane_max_tx_ffe_reached(train_set_lane)) + return false; + } else { + if (!intel_dp_lane_max_vswing_reached(train_set_lane)) + return false; + } + } + + return true; +} + +/* + * Prepare link training by configuring the link parameters. On DDI platforms + * also enable the port here. + */ +static bool +intel_dp_prepare_link_train(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + u8 link_config[2]; + u8 link_bw, rate_select; + + if (intel_dp->prepare_link_retrain) + intel_dp->prepare_link_retrain(intel_dp, crtc_state); + + intel_dp_compute_rate(intel_dp, crtc_state->port_clock, + &link_bw, &rate_select); + + /* + * WaEdpLinkRateDataReload + * + * Parade PS8461E MUX (used on varius TGL+ laptops) needs + * to snoop the link rates reported by the sink when we + * use LINK_RATE_SET in order to operate in jitter cleaning + * mode (as opposed to redriver mode). Unfortunately it + * loses track of the snooped link rates when powered down, + * so we need to make it re-snoop often. Without this high + * link rates are not stable. + */ + if (!link_bw) { + struct intel_connector *connector = intel_dp->attached_connector; + __le16 sink_rates[DP_MAX_SUPPORTED_RATES]; + + drm_dbg_kms(&i915->drm, "[CONNECTOR:%d:%s] Reloading eDP link rates\n", + connector->base.base.id, connector->base.name); + + drm_dp_dpcd_read(&intel_dp->aux, DP_SUPPORTED_LINK_RATES, + sink_rates, sizeof(sink_rates)); + } + + if (link_bw) + drm_dbg_kms(&i915->drm, + "[ENCODER:%d:%s] Using LINK_BW_SET value %02x\n", + encoder->base.base.id, encoder->base.name, link_bw); + else + drm_dbg_kms(&i915->drm, + "[ENCODER:%d:%s] Using LINK_RATE_SET value %02x\n", + encoder->base.base.id, encoder->base.name, rate_select); + + /* Write the link configuration data */ + link_config[0] = link_bw; + link_config[1] = crtc_state->lane_count; + if (drm_dp_enhanced_frame_cap(intel_dp->dpcd)) + link_config[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; + drm_dp_dpcd_write(&intel_dp->aux, DP_LINK_BW_SET, link_config, 2); + + /* eDP 1.4 rate select method. */ + if (!link_bw) + drm_dp_dpcd_write(&intel_dp->aux, DP_LINK_RATE_SET, + &rate_select, 1); + + link_config[0] = crtc_state->vrr.enable ? DP_MSA_TIMING_PAR_IGNORE_EN : 0; + link_config[1] = intel_dp_is_uhbr(crtc_state) ? + DP_SET_ANSI_128B132B : DP_SET_ANSI_8B10B; + drm_dp_dpcd_write(&intel_dp->aux, DP_DOWNSPREAD_CTRL, link_config, 2); + + return true; +} + +static bool intel_dp_adjust_request_changed(const struct intel_crtc_state *crtc_state, + const u8 old_link_status[DP_LINK_STATUS_SIZE], + const u8 new_link_status[DP_LINK_STATUS_SIZE]) +{ + int lane; + + for (lane = 0; lane < crtc_state->lane_count; lane++) { + u8 old, new; + + if (intel_dp_is_uhbr(crtc_state)) { + old = drm_dp_get_adjust_tx_ffe_preset(old_link_status, lane); + new = drm_dp_get_adjust_tx_ffe_preset(new_link_status, lane); + } else { + old = drm_dp_get_adjust_request_voltage(old_link_status, lane) | + drm_dp_get_adjust_request_pre_emphasis(old_link_status, lane); + new = drm_dp_get_adjust_request_voltage(new_link_status, lane) | + drm_dp_get_adjust_request_pre_emphasis(new_link_status, lane); + } + + if (old != new) + return true; + } + + return false; +} + +void +intel_dp_dump_link_status(struct intel_dp *intel_dp, enum drm_dp_phy dp_phy, + const u8 link_status[DP_LINK_STATUS_SIZE]) +{ + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + + drm_dbg_kms(&i915->drm, + "[ENCODER:%d:%s][%s] ln0_1:0x%x ln2_3:0x%x align:0x%x sink:0x%x adj_req0_1:0x%x adj_req2_3:0x%x\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy), + link_status[0], link_status[1], link_status[2], + link_status[3], link_status[4], link_status[5]); +} + +/* + * Perform the link training clock recovery phase on the given DP PHY using + * training pattern 1. + */ +static bool +intel_dp_link_training_clock_recovery(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + enum drm_dp_phy dp_phy) +{ + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + u8 old_link_status[DP_LINK_STATUS_SIZE] = {}; + int voltage_tries, cr_tries, max_cr_tries; + u8 link_status[DP_LINK_STATUS_SIZE]; + bool max_vswing_reached = false; + int delay_us; + + delay_us = drm_dp_read_clock_recovery_delay(&intel_dp->aux, + intel_dp->dpcd, dp_phy, + intel_dp_is_uhbr(crtc_state)); + + /* clock recovery */ + if (!intel_dp_reset_link_train(intel_dp, crtc_state, dp_phy, + DP_TRAINING_PATTERN_1 | + DP_LINK_SCRAMBLING_DISABLE)) { + drm_err(&i915->drm, "[ENCODER:%d:%s][%s] Failed to enable link training\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy)); + return false; + } + + /* + * The DP 1.4 spec defines the max clock recovery retries value + * as 10 but for pre-DP 1.4 devices we set a very tolerant + * retry limit of 80 (4 voltage levels x 4 preemphasis levels x + * x 5 identical voltage retries). Since the previous specs didn't + * define a limit and created the possibility of an infinite loop + * we want to prevent any sync from triggering that corner case. + */ + if (intel_dp->dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14) + max_cr_tries = 10; + else + max_cr_tries = 80; + + voltage_tries = 1; + for (cr_tries = 0; cr_tries < max_cr_tries; ++cr_tries) { + usleep_range(delay_us, 2 * delay_us); + + if (drm_dp_dpcd_read_phy_link_status(&intel_dp->aux, dp_phy, + link_status) < 0) { + drm_err(&i915->drm, "[ENCODER:%d:%s][%s] Failed to get link status\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy)); + return false; + } + + if (drm_dp_clock_recovery_ok(link_status, crtc_state->lane_count)) { + drm_dbg_kms(&i915->drm, + "[ENCODER:%d:%s][%s] Clock recovery OK\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy)); + return true; + } + + if (voltage_tries == 5) { + intel_dp_dump_link_status(intel_dp, dp_phy, link_status); + drm_dbg_kms(&i915->drm, + "[ENCODER:%d:%s][%s] Same voltage tried 5 times\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy)); + return false; + } + + if (max_vswing_reached) { + intel_dp_dump_link_status(intel_dp, dp_phy, link_status); + drm_dbg_kms(&i915->drm, + "[ENCODER:%d:%s][%s] Max Voltage Swing reached\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy)); + return false; + } + + /* Update training set as requested by target */ + intel_dp_get_adjust_train(intel_dp, crtc_state, dp_phy, + link_status); + if (!intel_dp_update_link_train(intel_dp, crtc_state, dp_phy)) { + drm_err(&i915->drm, + "[ENCODER:%d:%s][%s] Failed to update link training\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy)); + return false; + } + + if (!intel_dp_adjust_request_changed(crtc_state, old_link_status, link_status)) + ++voltage_tries; + else + voltage_tries = 1; + + memcpy(old_link_status, link_status, sizeof(link_status)); + + if (intel_dp_link_max_vswing_reached(intel_dp, crtc_state)) + max_vswing_reached = true; + } + + intel_dp_dump_link_status(intel_dp, dp_phy, link_status); + drm_err(&i915->drm, + "[ENCODER:%d:%s][%s] Failed clock recovery %d times, giving up!\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy), max_cr_tries); + + return false; +} + +/* + * Pick Training Pattern Sequence (TPS) for channel equalization. 128b/132b TPS2 + * for UHBR+, TPS4 for HBR3 or for 1.4 devices that support it, TPS3 for HBR2 or + * 1.2 devices that support it, TPS2 otherwise. + */ +static u32 intel_dp_training_pattern(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + enum drm_dp_phy dp_phy) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + bool source_tps3, sink_tps3, source_tps4, sink_tps4; + + /* UHBR+ use separate 128b/132b TPS2 */ + if (intel_dp_is_uhbr(crtc_state)) + return DP_TRAINING_PATTERN_2; + + /* + * TPS4 support is mandatory for all downstream devices that + * support HBR3. There are no known eDP panels that support + * TPS4 as of Feb 2018 as per VESA eDP_v1.4b_E1 specification. + * LTTPRs must support TPS4. + */ + source_tps4 = intel_dp_source_supports_tps4(i915); + sink_tps4 = dp_phy != DP_PHY_DPRX || + drm_dp_tps4_supported(intel_dp->dpcd); + if (source_tps4 && sink_tps4) { + return DP_TRAINING_PATTERN_4; + } else if (crtc_state->port_clock == 810000) { + if (!source_tps4) + drm_dbg_kms(&i915->drm, + "8.1 Gbps link rate without source TPS4 support\n"); + if (!sink_tps4) + drm_dbg_kms(&i915->drm, + "8.1 Gbps link rate without sink TPS4 support\n"); + } + + /* + * TPS3 support is mandatory for downstream devices that + * support HBR2. However, not all sinks follow the spec. + */ + source_tps3 = intel_dp_source_supports_tps3(i915); + sink_tps3 = dp_phy != DP_PHY_DPRX || + drm_dp_tps3_supported(intel_dp->dpcd); + if (source_tps3 && sink_tps3) { + return DP_TRAINING_PATTERN_3; + } else if (crtc_state->port_clock >= 540000) { + if (!source_tps3) + drm_dbg_kms(&i915->drm, + ">=5.4/6.48 Gbps link rate without source TPS3 support\n"); + if (!sink_tps3) + drm_dbg_kms(&i915->drm, + ">=5.4/6.48 Gbps link rate without sink TPS3 support\n"); + } + + return DP_TRAINING_PATTERN_2; +} + +/* + * Perform the link training channel equalization phase on the given DP PHY + * using one of training pattern 2, 3 or 4 depending on the source and + * sink capabilities. + */ +static bool +intel_dp_link_training_channel_equalization(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + enum drm_dp_phy dp_phy) +{ + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + int tries; + u32 training_pattern; + u8 link_status[DP_LINK_STATUS_SIZE]; + bool channel_eq = false; + int delay_us; + + delay_us = drm_dp_read_channel_eq_delay(&intel_dp->aux, + intel_dp->dpcd, dp_phy, + intel_dp_is_uhbr(crtc_state)); + + training_pattern = intel_dp_training_pattern(intel_dp, crtc_state, dp_phy); + /* Scrambling is disabled for TPS2/3 and enabled for TPS4 */ + if (training_pattern != DP_TRAINING_PATTERN_4) + training_pattern |= DP_LINK_SCRAMBLING_DISABLE; + + /* channel equalization */ + if (!intel_dp_set_link_train(intel_dp, crtc_state, dp_phy, + training_pattern)) { + drm_err(&i915->drm, + "[ENCODER:%d:%s][%s] Failed to start channel equalization\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy)); + return false; + } + + for (tries = 0; tries < 5; tries++) { + usleep_range(delay_us, 2 * delay_us); + + if (drm_dp_dpcd_read_phy_link_status(&intel_dp->aux, dp_phy, + link_status) < 0) { + drm_err(&i915->drm, + "[ENCODER:%d:%s][%s] Failed to get link status\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy)); + break; + } + + /* Make sure clock is still ok */ + if (!drm_dp_clock_recovery_ok(link_status, + crtc_state->lane_count)) { + intel_dp_dump_link_status(intel_dp, dp_phy, link_status); + drm_dbg_kms(&i915->drm, + "[ENCODER:%d:%s][%s] Clock recovery check failed, cannot " + "continue channel equalization\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy)); + break; + } + + if (drm_dp_channel_eq_ok(link_status, + crtc_state->lane_count)) { + channel_eq = true; + drm_dbg_kms(&i915->drm, + "[ENCODER:%d:%s][%s] Channel EQ done. DP Training successful\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy)); + break; + } + + /* Update training set as requested by target */ + intel_dp_get_adjust_train(intel_dp, crtc_state, dp_phy, + link_status); + if (!intel_dp_update_link_train(intel_dp, crtc_state, dp_phy)) { + drm_err(&i915->drm, + "[ENCODER:%d:%s][%s] Failed to update link training\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy)); + break; + } + } + + /* Try 5 times, else fail and try at lower BW */ + if (tries == 5) { + intel_dp_dump_link_status(intel_dp, dp_phy, link_status); + drm_dbg_kms(&i915->drm, + "[ENCODER:%d:%s][%s] Channel equalization failed 5 times\n", + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy)); + } + + return channel_eq; +} + +static bool intel_dp_disable_dpcd_training_pattern(struct intel_dp *intel_dp, + enum drm_dp_phy dp_phy) +{ + int reg = intel_dp_training_pattern_set_reg(intel_dp, dp_phy); + u8 val = DP_TRAINING_PATTERN_DISABLE; + + return drm_dp_dpcd_write(&intel_dp->aux, reg, &val, 1) == 1; +} + +static int +intel_dp_128b132b_intra_hop(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + u8 sink_status; + int ret; + + ret = drm_dp_dpcd_readb(&intel_dp->aux, DP_SINK_STATUS, &sink_status); + if (ret != 1) { + drm_dbg_kms(&i915->drm, "Failed to read sink status\n"); + return ret < 0 ? ret : -EIO; + } + + return sink_status & DP_INTRA_HOP_AUX_REPLY_INDICATION ? 1 : 0; +} + +/** + * intel_dp_stop_link_train - stop link training + * @intel_dp: DP struct + * @crtc_state: state for CRTC attached to the encoder + * + * Stop the link training of the @intel_dp port, disabling the training + * pattern in the sink's DPCD, and disabling the test pattern symbol + * generation on the port. + * + * What symbols are output on the port after this point is + * platform specific: On DDI/VLV/CHV platforms it will be the idle pattern + * with the pipe being disabled, on older platforms it's HW specific if/how an + * idle pattern is generated, as the pipe is already enabled here for those. + * + * This function must be called after intel_dp_start_link_train(). + */ +void intel_dp_stop_link_train(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + + intel_dp->link_trained = true; + + intel_dp_disable_dpcd_training_pattern(intel_dp, DP_PHY_DPRX); + intel_dp_program_link_training_pattern(intel_dp, crtc_state, DP_PHY_DPRX, + DP_TRAINING_PATTERN_DISABLE); + + if (intel_dp_is_uhbr(crtc_state) && + wait_for(intel_dp_128b132b_intra_hop(intel_dp, crtc_state) == 0, 500)) { + drm_dbg_kms(&i915->drm, + "[ENCODER:%d:%s] 128b/132b intra-hop not clearing\n", + encoder->base.base.id, encoder->base.name); + } +} + +static bool +intel_dp_link_train_phy(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + enum drm_dp_phy dp_phy) +{ + struct intel_connector *connector = intel_dp->attached_connector; + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + bool ret = false; + + if (!intel_dp_link_training_clock_recovery(intel_dp, crtc_state, dp_phy)) + goto out; + + if (!intel_dp_link_training_channel_equalization(intel_dp, crtc_state, dp_phy)) + goto out; + + ret = true; + +out: + drm_dbg_kms(&dp_to_i915(intel_dp)->drm, + "[CONNECTOR:%d:%s][ENCODER:%d:%s][%s] Link Training %s at link rate = %d, lane count = %d\n", + connector->base.base.id, connector->base.name, + encoder->base.base.id, encoder->base.name, + drm_dp_phy_name(dp_phy), + ret ? "passed" : "failed", + crtc_state->port_clock, crtc_state->lane_count); + + return ret; +} + +static void intel_dp_schedule_fallback_link_training(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + struct intel_connector *intel_connector = intel_dp->attached_connector; + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + + if (intel_dp->hobl_active) { + drm_dbg_kms(&dp_to_i915(intel_dp)->drm, + "[ENCODER:%d:%s] Link Training failed with HOBL active, " + "not enabling it from now on", + encoder->base.base.id, encoder->base.name); + intel_dp->hobl_failed = true; + } else if (intel_dp_get_link_train_fallback_values(intel_dp, + crtc_state->port_clock, + crtc_state->lane_count)) { + return; + } + + /* Schedule a Hotplug Uevent to userspace to start modeset */ + schedule_work(&intel_connector->modeset_retry_work); +} + +/* Perform the link training on all LTTPRs and the DPRX on a link. */ +static bool +intel_dp_link_train_all_phys(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + int lttpr_count) +{ + bool ret = true; + int i; + + for (i = lttpr_count - 1; i >= 0; i--) { + enum drm_dp_phy dp_phy = DP_PHY_LTTPR(i); + + ret = intel_dp_link_train_phy(intel_dp, crtc_state, dp_phy); + intel_dp_disable_dpcd_training_pattern(intel_dp, dp_phy); + + if (!ret) + break; + } + + if (ret) + ret = intel_dp_link_train_phy(intel_dp, crtc_state, DP_PHY_DPRX); + + if (intel_dp->set_idle_link_train) + intel_dp->set_idle_link_train(intel_dp, crtc_state); + + return ret; +} + +/* + * 128b/132b DP LANEx_EQ_DONE Sequence (DP 2.0 E11 3.5.2.16.1) + */ +static bool +intel_dp_128b132b_lane_eq(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + u8 link_status[DP_LINK_STATUS_SIZE]; + int delay_us; + int try, max_tries = 20; + unsigned long deadline; + bool timeout = false; + + /* + * Reset signal levels. Start transmitting 128b/132b TPS1. + * + * Put DPRX and LTTPRs (if any) into intra-hop AUX mode by writing TPS1 + * in DP_TRAINING_PATTERN_SET. + */ + if (!intel_dp_reset_link_train(intel_dp, crtc_state, DP_PHY_DPRX, + DP_TRAINING_PATTERN_1)) { + drm_err(&i915->drm, + "[ENCODER:%d:%s] Failed to start 128b/132b TPS1\n", + encoder->base.base.id, encoder->base.name); + return false; + } + + delay_us = drm_dp_128b132b_read_aux_rd_interval(&intel_dp->aux); + + /* Read the initial TX FFE settings. */ + if (drm_dp_dpcd_read_link_status(&intel_dp->aux, link_status) < 0) { + drm_err(&i915->drm, + "[ENCODER:%d:%s] Failed to read TX FFE presets\n", + encoder->base.base.id, encoder->base.name); + return false; + } + + /* Update signal levels and training set as requested. */ + intel_dp_get_adjust_train(intel_dp, crtc_state, DP_PHY_DPRX, link_status); + if (!intel_dp_update_link_train(intel_dp, crtc_state, DP_PHY_DPRX)) { + drm_err(&i915->drm, + "[ENCODER:%d:%s] Failed to set initial TX FFE settings\n", + encoder->base.base.id, encoder->base.name); + return false; + } + + /* Start transmitting 128b/132b TPS2. */ + if (!intel_dp_set_link_train(intel_dp, crtc_state, DP_PHY_DPRX, + DP_TRAINING_PATTERN_2)) { + drm_err(&i915->drm, + "[ENCODER:%d:%s] Failed to start 128b/132b TPS2\n", + encoder->base.base.id, encoder->base.name); + return false; + } + + /* Time budget for the LANEx_EQ_DONE Sequence */ + deadline = jiffies + msecs_to_jiffies_timeout(400); + + for (try = 0; try < max_tries; try++) { + usleep_range(delay_us, 2 * delay_us); + + /* + * The delay may get updated. The transmitter shall read the + * delay before link status during link training. + */ + delay_us = drm_dp_128b132b_read_aux_rd_interval(&intel_dp->aux); + + if (drm_dp_dpcd_read_link_status(&intel_dp->aux, link_status) < 0) { + drm_err(&i915->drm, + "[ENCODER:%d:%s] Failed to read link status\n", + encoder->base.base.id, encoder->base.name); + return false; + } + + if (drm_dp_128b132b_link_training_failed(link_status)) { + intel_dp_dump_link_status(intel_dp, DP_PHY_DPRX, link_status); + drm_err(&i915->drm, + "[ENCODER:%d:%s] Downstream link training failure\n", + encoder->base.base.id, encoder->base.name); + return false; + } + + if (drm_dp_128b132b_lane_channel_eq_done(link_status, crtc_state->lane_count)) { + drm_dbg_kms(&i915->drm, + "[ENCODER:%d:%s] Lane channel eq done\n", + encoder->base.base.id, encoder->base.name); + break; + } + + if (timeout) { + intel_dp_dump_link_status(intel_dp, DP_PHY_DPRX, link_status); + drm_err(&i915->drm, + "[ENCODER:%d:%s] Lane channel eq timeout\n", + encoder->base.base.id, encoder->base.name); + return false; + } + + if (time_after(jiffies, deadline)) + timeout = true; /* try one last time after deadline */ + + /* Update signal levels and training set as requested. */ + intel_dp_get_adjust_train(intel_dp, crtc_state, DP_PHY_DPRX, link_status); + if (!intel_dp_update_link_train(intel_dp, crtc_state, DP_PHY_DPRX)) { + drm_err(&i915->drm, + "[ENCODER:%d:%s] Failed to update TX FFE settings\n", + encoder->base.base.id, encoder->base.name); + return false; + } + } + + if (try == max_tries) { + intel_dp_dump_link_status(intel_dp, DP_PHY_DPRX, link_status); + drm_err(&i915->drm, + "[ENCODER:%d:%s] Max loop count reached\n", + encoder->base.base.id, encoder->base.name); + return false; + } + + for (;;) { + if (time_after(jiffies, deadline)) + timeout = true; /* try one last time after deadline */ + + if (drm_dp_dpcd_read_link_status(&intel_dp->aux, link_status) < 0) { + drm_err(&i915->drm, + "[ENCODER:%d:%s] Failed to read link status\n", + encoder->base.base.id, encoder->base.name); + return false; + } + + if (drm_dp_128b132b_link_training_failed(link_status)) { + intel_dp_dump_link_status(intel_dp, DP_PHY_DPRX, link_status); + drm_err(&i915->drm, + "[ENCODER:%d:%s] Downstream link training failure\n", + encoder->base.base.id, encoder->base.name); + return false; + } + + if (drm_dp_128b132b_eq_interlane_align_done(link_status)) { + drm_dbg_kms(&i915->drm, + "[ENCODER:%d:%s] Interlane align done\n", + encoder->base.base.id, encoder->base.name); + break; + } + + if (timeout) { + intel_dp_dump_link_status(intel_dp, DP_PHY_DPRX, link_status); + drm_err(&i915->drm, + "[ENCODER:%d:%s] Interlane align timeout\n", + encoder->base.base.id, encoder->base.name); + return false; + } + + usleep_range(2000, 3000); + } + + return true; +} + +/* + * 128b/132b DP LANEx_CDS_DONE Sequence (DP 2.0 E11 3.5.2.16.2) + */ +static bool +intel_dp_128b132b_lane_cds(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + int lttpr_count) +{ + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + u8 link_status[DP_LINK_STATUS_SIZE]; + unsigned long deadline; + + if (drm_dp_dpcd_writeb(&intel_dp->aux, DP_TRAINING_PATTERN_SET, + DP_TRAINING_PATTERN_2_CDS) != 1) { + drm_err(&i915->drm, + "[ENCODER:%d:%s] Failed to start 128b/132b TPS2 CDS\n", + encoder->base.base.id, encoder->base.name); + return false; + } + + /* Time budget for the LANEx_CDS_DONE Sequence */ + deadline = jiffies + msecs_to_jiffies_timeout((lttpr_count + 1) * 20); + + for (;;) { + bool timeout = false; + + if (time_after(jiffies, deadline)) + timeout = true; /* try one last time after deadline */ + + usleep_range(2000, 3000); + + if (drm_dp_dpcd_read_link_status(&intel_dp->aux, link_status) < 0) { + drm_err(&i915->drm, + "[ENCODER:%d:%s] Failed to read link status\n", + encoder->base.base.id, encoder->base.name); + return false; + } + + if (drm_dp_128b132b_eq_interlane_align_done(link_status) && + drm_dp_128b132b_cds_interlane_align_done(link_status) && + drm_dp_128b132b_lane_symbol_locked(link_status, crtc_state->lane_count)) { + drm_dbg_kms(&i915->drm, + "[ENCODER:%d:%s] CDS interlane align done\n", + encoder->base.base.id, encoder->base.name); + break; + } + + if (drm_dp_128b132b_link_training_failed(link_status)) { + intel_dp_dump_link_status(intel_dp, DP_PHY_DPRX, link_status); + drm_err(&i915->drm, + "[ENCODER:%d:%s] Downstream link training failure\n", + encoder->base.base.id, encoder->base.name); + return false; + } + + if (timeout) { + intel_dp_dump_link_status(intel_dp, DP_PHY_DPRX, link_status); + drm_err(&i915->drm, + "[ENCODER:%d:%s] CDS timeout\n", + encoder->base.base.id, encoder->base.name); + return false; + } + } + + /* FIXME: Should DP_TRAINING_PATTERN_DISABLE be written first? */ + if (intel_dp->set_idle_link_train) + intel_dp->set_idle_link_train(intel_dp, crtc_state); + + return true; +} + +/* + * 128b/132b link training sequence. (DP 2.0 E11 SCR on link training.) + */ +static bool +intel_dp_128b132b_link_train(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + int lttpr_count) +{ + struct drm_i915_private *i915 = dp_to_i915(intel_dp); + struct intel_connector *connector = intel_dp->attached_connector; + struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; + bool passed = false; + + if (wait_for(intel_dp_128b132b_intra_hop(intel_dp, crtc_state) == 0, 500)) { + drm_err(&i915->drm, + "[ENCODER:%d:%s] 128b/132b intra-hop not clear\n", + encoder->base.base.id, encoder->base.name); + return false; + } + + if (intel_dp_128b132b_lane_eq(intel_dp, crtc_state) && + intel_dp_128b132b_lane_cds(intel_dp, crtc_state, lttpr_count)) + passed = true; + + drm_dbg_kms(&i915->drm, + "[CONNECTOR:%d:%s][ENCODER:%d:%s] 128b/132b Link Training %s at link rate = %d, lane count = %d\n", + connector->base.base.id, connector->base.name, + encoder->base.base.id, encoder->base.name, + passed ? "passed" : "failed", + crtc_state->port_clock, crtc_state->lane_count); + + return passed; +} + +/** + * intel_dp_start_link_train - start link training + * @intel_dp: DP struct + * @crtc_state: state for CRTC attached to the encoder + * + * Start the link training of the @intel_dp port, scheduling a fallback + * retraining with reduced link rate/lane parameters if the link training + * fails. + * After calling this function intel_dp_stop_link_train() must be called. + */ +void intel_dp_start_link_train(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + bool passed; + /* + * TODO: Reiniting LTTPRs here won't be needed once proper connector + * HW state readout is added. + */ + int lttpr_count = intel_dp_init_lttpr_and_dprx_caps(intel_dp); + + if (lttpr_count < 0) + /* Still continue with enabling the port and link training. */ + lttpr_count = 0; + + intel_dp_prepare_link_train(intel_dp, crtc_state); + + if (intel_dp_is_uhbr(crtc_state)) + passed = intel_dp_128b132b_link_train(intel_dp, crtc_state, lttpr_count); + else + passed = intel_dp_link_train_all_phys(intel_dp, crtc_state, lttpr_count); + + if (!passed) + intel_dp_schedule_fallback_link_training(intel_dp, crtc_state); +} diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_training.h b/drivers/gpu/drm/i915/display/intel_dp_link_training.h new file mode 100644 index 000000000..7fa1c0833 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dp_link_training.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_DP_LINK_TRAINING_H__ +#define __INTEL_DP_LINK_TRAINING_H__ + +#include + +struct intel_crtc_state; +struct intel_dp; + +int intel_dp_init_lttpr_and_dprx_caps(struct intel_dp *intel_dp); + +void intel_dp_get_adjust_train(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + enum drm_dp_phy dp_phy, + const u8 link_status[DP_LINK_STATUS_SIZE]); +void intel_dp_program_link_training_pattern(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + enum drm_dp_phy dp_phy, + u8 dp_train_pat); +void intel_dp_set_signal_levels(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + enum drm_dp_phy dp_phy); +void intel_dp_start_link_train(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state); +void intel_dp_stop_link_train(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state); + +void +intel_dp_dump_link_status(struct intel_dp *intel_dp, enum drm_dp_phy dp_phy, + const u8 link_status[DP_LINK_STATUS_SIZE]); + +/* Get the TPSx symbol type of the value programmed to DP_TRAINING_PATTERN_SET */ +static inline u8 intel_dp_training_pattern_symbol(u8 pattern) +{ + return pattern & ~DP_LINK_SCRAMBLING_DISABLE; +} + +#endif /* __INTEL_DP_LINK_TRAINING_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.c b/drivers/gpu/drm/i915/display/intel_dp_mst.c new file mode 100644 index 000000000..eec32f682 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dp_mst.c @@ -0,0 +1,1076 @@ +/* + * Copyright © 2008 Intel Corporation + * 2014 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +#include +#include +#include +#include + +#include "i915_drv.h" +#include "intel_atomic.h" +#include "intel_audio.h" +#include "intel_connector.h" +#include "intel_crtc.h" +#include "intel_ddi.h" +#include "intel_de.h" +#include "intel_display_types.h" +#include "intel_dp.h" +#include "intel_dp_hdcp.h" +#include "intel_dp_mst.h" +#include "intel_dpio_phy.h" +#include "intel_hdcp.h" +#include "intel_hotplug.h" +#include "skl_scaler.h" + +static int intel_dp_mst_compute_link_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + struct link_config_limits *limits) +{ + struct drm_atomic_state *state = crtc_state->uapi.state; + struct intel_dp_mst_encoder *intel_mst = enc_to_mst(encoder); + struct intel_dp *intel_dp = &intel_mst->primary->dp; + struct drm_dp_mst_topology_state *mst_state; + struct intel_connector *connector = + to_intel_connector(conn_state->connector); + struct drm_i915_private *i915 = to_i915(connector->base.dev); + const struct drm_display_mode *adjusted_mode = + &crtc_state->hw.adjusted_mode; + int bpp, slots = -EINVAL; + + mst_state = drm_atomic_get_mst_topology_state(state, &intel_dp->mst_mgr); + if (IS_ERR(mst_state)) + return PTR_ERR(mst_state); + + crtc_state->lane_count = limits->max_lane_count; + crtc_state->port_clock = limits->max_rate; + + // TODO: Handle pbn_div changes by adding a new MST helper + if (!mst_state->pbn_div) { + mst_state->pbn_div = drm_dp_get_vc_payload_bw(&intel_dp->mst_mgr, + limits->max_rate, + limits->max_lane_count); + } + + for (bpp = limits->max_bpp; bpp >= limits->min_bpp; bpp -= 2 * 3) { + crtc_state->pipe_bpp = bpp; + + crtc_state->pbn = drm_dp_calc_pbn_mode(adjusted_mode->crtc_clock, + crtc_state->pipe_bpp, + false); + slots = drm_dp_atomic_find_time_slots(state, &intel_dp->mst_mgr, + connector->port, crtc_state->pbn); + if (slots == -EDEADLK) + return slots; + if (slots >= 0) + break; + } + + if (slots < 0) { + drm_dbg_kms(&i915->drm, "failed finding vcpi slots:%d\n", + slots); + return slots; + } + + intel_link_compute_m_n(crtc_state->pipe_bpp, + crtc_state->lane_count, + adjusted_mode->crtc_clock, + crtc_state->port_clock, + &crtc_state->dp_m_n, + crtc_state->fec_enable); + crtc_state->dp_m_n.tu = slots; + + return 0; +} + +static int intel_dp_mst_update_slots(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + struct intel_dp_mst_encoder *intel_mst = enc_to_mst(encoder); + struct intel_dp *intel_dp = &intel_mst->primary->dp; + struct drm_dp_mst_topology_mgr *mgr = &intel_dp->mst_mgr; + struct drm_dp_mst_topology_state *topology_state; + u8 link_coding_cap = intel_dp_is_uhbr(crtc_state) ? + DP_CAP_ANSI_128B132B : DP_CAP_ANSI_8B10B; + + topology_state = drm_atomic_get_mst_topology_state(conn_state->state, mgr); + if (IS_ERR(topology_state)) { + drm_dbg_kms(&i915->drm, "slot update failed\n"); + return PTR_ERR(topology_state); + } + + drm_dp_mst_update_slots(topology_state, link_coding_cap); + + return 0; +} + +static int intel_dp_mst_compute_config(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config, + struct drm_connector_state *conn_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_dp_mst_encoder *intel_mst = enc_to_mst(encoder); + struct intel_dp *intel_dp = &intel_mst->primary->dp; + struct intel_connector *connector = + to_intel_connector(conn_state->connector); + struct intel_digital_connector_state *intel_conn_state = + to_intel_digital_connector_state(conn_state); + const struct drm_display_mode *adjusted_mode = + &pipe_config->hw.adjusted_mode; + struct link_config_limits limits; + int ret; + + if (adjusted_mode->flags & DRM_MODE_FLAG_DBLSCAN) + return -EINVAL; + + pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB; + pipe_config->has_pch_encoder = false; + + if (intel_conn_state->force_audio == HDMI_AUDIO_AUTO) + pipe_config->has_audio = connector->port->has_audio; + else + pipe_config->has_audio = + intel_conn_state->force_audio == HDMI_AUDIO_ON; + + /* + * for MST we always configure max link bw - the spec doesn't + * seem to suggest we should do otherwise. + */ + limits.min_rate = + limits.max_rate = intel_dp_max_link_rate(intel_dp); + + limits.min_lane_count = + limits.max_lane_count = intel_dp_max_lane_count(intel_dp); + + limits.min_bpp = intel_dp_min_bpp(pipe_config->output_format); + /* + * FIXME: If all the streams can't fit into the link with + * their current pipe_bpp we should reduce pipe_bpp across + * the board until things start to fit. Until then we + * limit to <= 8bpc since that's what was hardcoded for all + * MST streams previously. This hack should be removed once + * we have the proper retry logic in place. + */ + limits.max_bpp = min(pipe_config->pipe_bpp, 24); + + intel_dp_adjust_compliance_config(intel_dp, pipe_config, &limits); + + ret = intel_dp_mst_compute_link_config(encoder, pipe_config, + conn_state, &limits); + if (ret) + return ret; + + ret = intel_dp_mst_update_slots(encoder, pipe_config, conn_state); + if (ret) + return ret; + + pipe_config->limited_color_range = + intel_dp_limited_color_range(pipe_config, conn_state); + + if (IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv)) + pipe_config->lane_lat_optim_mask = + bxt_ddi_phy_calc_lane_lat_optim_mask(pipe_config->lane_count); + + intel_ddi_compute_min_voltage_level(dev_priv, pipe_config); + + return 0; +} + +/* + * Iterate over all connectors and return a mask of + * all CPU transcoders streaming over the same DP link. + */ +static unsigned int +intel_dp_mst_transcoder_mask(struct intel_atomic_state *state, + struct intel_dp *mst_port) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + const struct intel_digital_connector_state *conn_state; + struct intel_connector *connector; + u8 transcoders = 0; + int i; + + if (DISPLAY_VER(dev_priv) < 12) + return 0; + + for_each_new_intel_connector_in_state(state, connector, conn_state, i) { + const struct intel_crtc_state *crtc_state; + struct intel_crtc *crtc; + + if (connector->mst_port != mst_port || !conn_state->base.crtc) + continue; + + crtc = to_intel_crtc(conn_state->base.crtc); + crtc_state = intel_atomic_get_new_crtc_state(state, crtc); + + if (!crtc_state->hw.active) + continue; + + transcoders |= BIT(crtc_state->cpu_transcoder); + } + + return transcoders; +} + +static int intel_dp_mst_compute_config_late(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct intel_atomic_state *state = to_intel_atomic_state(conn_state->state); + struct intel_dp_mst_encoder *intel_mst = enc_to_mst(encoder); + struct intel_dp *intel_dp = &intel_mst->primary->dp; + + /* lowest numbered transcoder will be designated master */ + crtc_state->mst_master_transcoder = + ffs(intel_dp_mst_transcoder_mask(state, intel_dp)) - 1; + + return 0; +} + +/* + * If one of the connectors in a MST stream needs a modeset, mark all CRTCs + * that shares the same MST stream as mode changed, + * intel_modeset_pipe_config()+intel_crtc_check_fastset() will take care to do + * a fastset when possible. + */ +static int +intel_dp_mst_atomic_master_trans_check(struct intel_connector *connector, + struct intel_atomic_state *state) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct drm_connector_list_iter connector_list_iter; + struct intel_connector *connector_iter; + int ret = 0; + + if (DISPLAY_VER(dev_priv) < 12) + return 0; + + if (!intel_connector_needs_modeset(state, &connector->base)) + return 0; + + drm_connector_list_iter_begin(&dev_priv->drm, &connector_list_iter); + for_each_intel_connector_iter(connector_iter, &connector_list_iter) { + struct intel_digital_connector_state *conn_iter_state; + struct intel_crtc_state *crtc_state; + struct intel_crtc *crtc; + + if (connector_iter->mst_port != connector->mst_port || + connector_iter == connector) + continue; + + conn_iter_state = intel_atomic_get_digital_connector_state(state, + connector_iter); + if (IS_ERR(conn_iter_state)) { + ret = PTR_ERR(conn_iter_state); + break; + } + + if (!conn_iter_state->base.crtc) + continue; + + crtc = to_intel_crtc(conn_iter_state->base.crtc); + crtc_state = intel_atomic_get_crtc_state(&state->base, crtc); + if (IS_ERR(crtc_state)) { + ret = PTR_ERR(crtc_state); + break; + } + + ret = drm_atomic_add_affected_planes(&state->base, &crtc->base); + if (ret) + break; + crtc_state->uapi.mode_changed = true; + } + drm_connector_list_iter_end(&connector_list_iter); + + return ret; +} + +static int +intel_dp_mst_atomic_check(struct drm_connector *connector, + struct drm_atomic_state *_state) +{ + struct intel_atomic_state *state = to_intel_atomic_state(_state); + struct intel_connector *intel_connector = + to_intel_connector(connector); + int ret; + + ret = intel_digital_connector_atomic_check(connector, &state->base); + if (ret) + return ret; + + ret = intel_dp_mst_atomic_master_trans_check(intel_connector, state); + if (ret) + return ret; + + return drm_dp_atomic_release_time_slots(&state->base, + &intel_connector->mst_port->mst_mgr, + intel_connector->port); +} + +static void clear_act_sent(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + + intel_de_write(i915, dp_tp_status_reg(encoder, crtc_state), + DP_TP_STATUS_ACT_SENT); +} + +static void wait_for_act_sent(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(encoder->base.dev); + struct intel_dp_mst_encoder *intel_mst = enc_to_mst(encoder); + struct intel_dp *intel_dp = &intel_mst->primary->dp; + + if (intel_de_wait_for_set(i915, dp_tp_status_reg(encoder, crtc_state), + DP_TP_STATUS_ACT_SENT, 1)) + drm_err(&i915->drm, "Timed out waiting for ACT sent\n"); + + drm_dp_check_act_status(&intel_dp->mst_mgr); +} + +static void intel_mst_disable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct intel_dp_mst_encoder *intel_mst = enc_to_mst(encoder); + struct intel_digital_port *dig_port = intel_mst->primary; + struct intel_dp *intel_dp = &dig_port->dp; + struct intel_connector *connector = + to_intel_connector(old_conn_state->connector); + struct drm_dp_mst_topology_state *old_mst_state = + drm_atomic_get_old_mst_topology_state(&state->base, &intel_dp->mst_mgr); + struct drm_dp_mst_topology_state *new_mst_state = + drm_atomic_get_new_mst_topology_state(&state->base, &intel_dp->mst_mgr); + const struct drm_dp_mst_atomic_payload *old_payload = + drm_atomic_get_mst_payload_state(old_mst_state, connector->port); + struct drm_dp_mst_atomic_payload *new_payload = + drm_atomic_get_mst_payload_state(new_mst_state, connector->port); + struct drm_i915_private *i915 = to_i915(connector->base.dev); + + drm_dbg_kms(&i915->drm, "active links %d\n", + intel_dp->active_mst_links); + + intel_hdcp_disable(intel_mst->connector); + + drm_dp_remove_payload(&intel_dp->mst_mgr, new_mst_state, + old_payload, new_payload); + + intel_audio_codec_disable(encoder, old_crtc_state, old_conn_state); +} + +static void intel_mst_post_disable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct intel_dp_mst_encoder *intel_mst = enc_to_mst(encoder); + struct intel_digital_port *dig_port = intel_mst->primary; + struct intel_dp *intel_dp = &dig_port->dp; + struct intel_connector *connector = + to_intel_connector(old_conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + bool last_mst_stream; + + intel_dp->active_mst_links--; + last_mst_stream = intel_dp->active_mst_links == 0; + drm_WARN_ON(&dev_priv->drm, + DISPLAY_VER(dev_priv) >= 12 && last_mst_stream && + !intel_dp_mst_is_master_trans(old_crtc_state)); + + intel_crtc_vblank_off(old_crtc_state); + + intel_disable_transcoder(old_crtc_state); + + clear_act_sent(encoder, old_crtc_state); + + intel_de_rmw(dev_priv, TRANS_DDI_FUNC_CTL(old_crtc_state->cpu_transcoder), + TRANS_DDI_DP_VC_PAYLOAD_ALLOC, 0); + + wait_for_act_sent(encoder, old_crtc_state); + + intel_ddi_disable_transcoder_func(old_crtc_state); + + if (DISPLAY_VER(dev_priv) >= 9) + skl_scaler_disable(old_crtc_state); + else + ilk_pfit_disable(old_crtc_state); + + /* + * Power down mst path before disabling the port, otherwise we end + * up getting interrupts from the sink upon detecting link loss. + */ + drm_dp_send_power_updown_phy(&intel_dp->mst_mgr, connector->port, + false); + + /* + * BSpec 4287: disable DIP after the transcoder is disabled and before + * the transcoder clock select is set to none. + */ + if (last_mst_stream) + intel_dp_set_infoframes(&dig_port->base, false, + old_crtc_state, NULL); + /* + * From TGL spec: "If multi-stream slave transcoder: Configure + * Transcoder Clock Select to direct no clock to the transcoder" + * + * From older GENs spec: "Configure Transcoder Clock Select to direct + * no clock to the transcoder" + */ + if (DISPLAY_VER(dev_priv) < 12 || !last_mst_stream) + intel_ddi_disable_pipe_clock(old_crtc_state); + + + intel_mst->connector = NULL; + if (last_mst_stream) + dig_port->base.post_disable(state, &dig_port->base, + old_crtc_state, NULL); + + drm_dbg_kms(&dev_priv->drm, "active links %d\n", + intel_dp->active_mst_links); +} + +static void intel_mst_pre_pll_enable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ + struct intel_dp_mst_encoder *intel_mst = enc_to_mst(encoder); + struct intel_digital_port *dig_port = intel_mst->primary; + struct intel_dp *intel_dp = &dig_port->dp; + + if (intel_dp->active_mst_links == 0) + dig_port->base.pre_pll_enable(state, &dig_port->base, + pipe_config, NULL); +} + +static void intel_mst_pre_enable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ + struct intel_dp_mst_encoder *intel_mst = enc_to_mst(encoder); + struct intel_digital_port *dig_port = intel_mst->primary; + struct intel_dp *intel_dp = &dig_port->dp; + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_connector *connector = + to_intel_connector(conn_state->connector); + struct drm_dp_mst_topology_state *mst_state = + drm_atomic_get_new_mst_topology_state(&state->base, &intel_dp->mst_mgr); + int ret; + bool first_mst_stream; + + /* MST encoders are bound to a crtc, not to a connector, + * force the mapping here for get_hw_state. + */ + connector->encoder = encoder; + intel_mst->connector = connector; + first_mst_stream = intel_dp->active_mst_links == 0; + drm_WARN_ON(&dev_priv->drm, + DISPLAY_VER(dev_priv) >= 12 && first_mst_stream && + !intel_dp_mst_is_master_trans(pipe_config)); + + drm_dbg_kms(&dev_priv->drm, "active links %d\n", + intel_dp->active_mst_links); + + if (first_mst_stream) + intel_dp_set_power(intel_dp, DP_SET_POWER_D0); + + drm_dp_send_power_updown_phy(&intel_dp->mst_mgr, connector->port, true); + + if (first_mst_stream) + dig_port->base.pre_enable(state, &dig_port->base, + pipe_config, NULL); + + intel_dp->active_mst_links++; + + ret = drm_dp_add_payload_part1(&intel_dp->mst_mgr, mst_state, + drm_atomic_get_mst_payload_state(mst_state, connector->port)); + if (ret < 0) + drm_err(&dev_priv->drm, "Failed to create MST payload for %s: %d\n", + connector->base.name, ret); + + /* + * Before Gen 12 this is not done as part of + * dig_port->base.pre_enable() and should be done here. For + * Gen 12+ the step in which this should be done is different for the + * first MST stream, so it's done on the DDI for the first stream and + * here for the following ones. + */ + if (DISPLAY_VER(dev_priv) < 12 || !first_mst_stream) + intel_ddi_enable_pipe_clock(encoder, pipe_config); + + intel_ddi_set_dp_msa(pipe_config, conn_state); +} + +static void intel_mst_enable_dp(struct intel_atomic_state *state, + struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config, + const struct drm_connector_state *conn_state) +{ + struct intel_dp_mst_encoder *intel_mst = enc_to_mst(encoder); + struct intel_digital_port *dig_port = intel_mst->primary; + struct intel_dp *intel_dp = &dig_port->dp; + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct drm_dp_mst_topology_state *mst_state = + drm_atomic_get_new_mst_topology_state(&state->base, &intel_dp->mst_mgr); + enum transcoder trans = pipe_config->cpu_transcoder; + + drm_WARN_ON(&dev_priv->drm, pipe_config->has_pch_encoder); + + clear_act_sent(encoder, pipe_config); + + if (intel_dp_is_uhbr(pipe_config)) { + const struct drm_display_mode *adjusted_mode = + &pipe_config->hw.adjusted_mode; + u64 crtc_clock_hz = KHz(adjusted_mode->crtc_clock); + + intel_de_write(dev_priv, TRANS_DP2_VFREQHIGH(pipe_config->cpu_transcoder), + TRANS_DP2_VFREQ_PIXEL_CLOCK(crtc_clock_hz >> 24)); + intel_de_write(dev_priv, TRANS_DP2_VFREQLOW(pipe_config->cpu_transcoder), + TRANS_DP2_VFREQ_PIXEL_CLOCK(crtc_clock_hz & 0xffffff)); + } + + intel_ddi_enable_transcoder_func(encoder, pipe_config); + + intel_de_rmw(dev_priv, TRANS_DDI_FUNC_CTL(trans), 0, + TRANS_DDI_DP_VC_PAYLOAD_ALLOC); + + drm_dbg_kms(&dev_priv->drm, "active links %d\n", + intel_dp->active_mst_links); + + wait_for_act_sent(encoder, pipe_config); + + drm_dp_add_payload_part2(&intel_dp->mst_mgr, &state->base, + drm_atomic_get_mst_payload_state(mst_state, connector->port)); + + if (DISPLAY_VER(dev_priv) >= 14 && pipe_config->fec_enable) + intel_de_rmw(dev_priv, MTL_CHICKEN_TRANS(trans), 0, + FECSTALL_DIS_DPTSTREAM_DPTTG); + else if (DISPLAY_VER(dev_priv) >= 12 && pipe_config->fec_enable) + intel_de_rmw(dev_priv, CHICKEN_TRANS(trans), 0, + FECSTALL_DIS_DPTSTREAM_DPTTG); + + intel_enable_transcoder(pipe_config); + + intel_crtc_vblank_on(pipe_config); + + intel_audio_codec_enable(encoder, pipe_config, conn_state); + + /* Enable hdcp if it's desired */ + if (conn_state->content_protection == + DRM_MODE_CONTENT_PROTECTION_DESIRED) + intel_hdcp_enable(to_intel_connector(conn_state->connector), + pipe_config, + (u8)conn_state->hdcp_content_type); +} + +static bool intel_dp_mst_enc_get_hw_state(struct intel_encoder *encoder, + enum pipe *pipe) +{ + struct intel_dp_mst_encoder *intel_mst = enc_to_mst(encoder); + *pipe = intel_mst->pipe; + if (intel_mst->connector) + return true; + return false; +} + +static void intel_dp_mst_enc_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config) +{ + struct intel_dp_mst_encoder *intel_mst = enc_to_mst(encoder); + struct intel_digital_port *dig_port = intel_mst->primary; + + dig_port->base.get_config(&dig_port->base, pipe_config); +} + +static bool intel_dp_mst_initial_fastset_check(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + struct intel_dp_mst_encoder *intel_mst = enc_to_mst(encoder); + struct intel_digital_port *dig_port = intel_mst->primary; + + return intel_dp_initial_fastset_check(&dig_port->base, crtc_state); +} + +static int intel_dp_mst_get_ddc_modes(struct drm_connector *connector) +{ + struct intel_connector *intel_connector = to_intel_connector(connector); + struct intel_dp *intel_dp = intel_connector->mst_port; + struct edid *edid; + int ret; + + if (drm_connector_is_unregistered(connector)) + return intel_connector_update_modes(connector, NULL); + + edid = drm_dp_mst_get_edid(connector, &intel_dp->mst_mgr, intel_connector->port); + ret = intel_connector_update_modes(connector, edid); + kfree(edid); + + return ret; +} + +static int +intel_dp_mst_connector_late_register(struct drm_connector *connector) +{ + struct intel_connector *intel_connector = to_intel_connector(connector); + int ret; + + ret = drm_dp_mst_connector_late_register(connector, + intel_connector->port); + if (ret < 0) + return ret; + + ret = intel_connector_register(connector); + if (ret < 0) + drm_dp_mst_connector_early_unregister(connector, + intel_connector->port); + + return ret; +} + +static void +intel_dp_mst_connector_early_unregister(struct drm_connector *connector) +{ + struct intel_connector *intel_connector = to_intel_connector(connector); + + intel_connector_unregister(connector); + drm_dp_mst_connector_early_unregister(connector, + intel_connector->port); +} + +static const struct drm_connector_funcs intel_dp_mst_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .atomic_get_property = intel_digital_connector_atomic_get_property, + .atomic_set_property = intel_digital_connector_atomic_set_property, + .late_register = intel_dp_mst_connector_late_register, + .early_unregister = intel_dp_mst_connector_early_unregister, + .destroy = intel_connector_destroy, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .atomic_duplicate_state = intel_digital_connector_duplicate_state, +}; + +static int intel_dp_mst_get_modes(struct drm_connector *connector) +{ + return intel_dp_mst_get_ddc_modes(connector); +} + +static int +intel_dp_mst_mode_valid_ctx(struct drm_connector *connector, + struct drm_display_mode *mode, + struct drm_modeset_acquire_ctx *ctx, + enum drm_mode_status *status) +{ + struct drm_i915_private *dev_priv = to_i915(connector->dev); + struct intel_connector *intel_connector = to_intel_connector(connector); + struct intel_dp *intel_dp = intel_connector->mst_port; + struct drm_dp_mst_topology_mgr *mgr = &intel_dp->mst_mgr; + struct drm_dp_mst_port *port = intel_connector->port; + const int min_bpp = 18; + int max_dotclk = to_i915(connector->dev)->max_dotclk_freq; + int max_rate, mode_rate, max_lanes, max_link_clock; + int ret; + + if (drm_connector_is_unregistered(connector)) { + *status = MODE_ERROR; + return 0; + } + + *status = intel_cpu_transcoder_mode_valid(dev_priv, mode); + if (*status != MODE_OK) + return 0; + + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) { + *status = MODE_NO_DBLESCAN; + return 0; + } + + max_link_clock = intel_dp_max_link_rate(intel_dp); + max_lanes = intel_dp_max_lane_count(intel_dp); + + max_rate = intel_dp_max_data_rate(max_link_clock, max_lanes); + mode_rate = intel_dp_link_required(mode->clock, min_bpp); + + ret = drm_modeset_lock(&mgr->base.lock, ctx); + if (ret) + return ret; + + if (mode_rate > max_rate || mode->clock > max_dotclk || + drm_dp_calc_pbn_mode(mode->clock, min_bpp, false) > port->full_pbn) { + *status = MODE_CLOCK_HIGH; + return 0; + } + + if (mode->clock < 10000) { + *status = MODE_CLOCK_LOW; + return 0; + } + + if (mode->flags & DRM_MODE_FLAG_DBLCLK) { + *status = MODE_H_ILLEGAL; + return 0; + } + + *status = intel_mode_valid_max_plane_size(dev_priv, mode, false); + return 0; +} + +static struct drm_encoder *intel_mst_atomic_best_encoder(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + struct drm_connector_state *connector_state = drm_atomic_get_new_connector_state(state, + connector); + struct intel_connector *intel_connector = to_intel_connector(connector); + struct intel_dp *intel_dp = intel_connector->mst_port; + struct intel_crtc *crtc = to_intel_crtc(connector_state->crtc); + + return &intel_dp->mst_encoders[crtc->pipe]->base.base; +} + +static int +intel_dp_mst_detect(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, bool force) +{ + struct drm_i915_private *i915 = to_i915(connector->dev); + struct intel_connector *intel_connector = to_intel_connector(connector); + struct intel_dp *intel_dp = intel_connector->mst_port; + + if (!INTEL_DISPLAY_ENABLED(i915)) + return connector_status_disconnected; + + if (drm_connector_is_unregistered(connector)) + return connector_status_disconnected; + + return drm_dp_mst_detect_port(connector, ctx, &intel_dp->mst_mgr, + intel_connector->port); +} + +static const struct drm_connector_helper_funcs intel_dp_mst_connector_helper_funcs = { + .get_modes = intel_dp_mst_get_modes, + .mode_valid_ctx = intel_dp_mst_mode_valid_ctx, + .atomic_best_encoder = intel_mst_atomic_best_encoder, + .atomic_check = intel_dp_mst_atomic_check, + .detect_ctx = intel_dp_mst_detect, +}; + +static void intel_dp_mst_encoder_destroy(struct drm_encoder *encoder) +{ + struct intel_dp_mst_encoder *intel_mst = enc_to_mst(to_intel_encoder(encoder)); + + drm_encoder_cleanup(encoder); + kfree(intel_mst); +} + +static const struct drm_encoder_funcs intel_dp_mst_enc_funcs = { + .destroy = intel_dp_mst_encoder_destroy, +}; + +static bool intel_dp_mst_get_hw_state(struct intel_connector *connector) +{ + if (intel_attached_encoder(connector) && connector->base.state->crtc) { + enum pipe pipe; + if (!intel_attached_encoder(connector)->get_hw_state(intel_attached_encoder(connector), &pipe)) + return false; + return true; + } + return false; +} + +static struct drm_connector *intel_dp_add_mst_connector(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port, const char *pathprop) +{ + struct intel_dp *intel_dp = container_of(mgr, struct intel_dp, mst_mgr); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + struct drm_device *dev = dig_port->base.base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + struct intel_connector *intel_connector; + struct drm_connector *connector; + enum pipe pipe; + int ret; + + intel_connector = intel_connector_alloc(); + if (!intel_connector) + return NULL; + + intel_connector->get_hw_state = intel_dp_mst_get_hw_state; + intel_connector->mst_port = intel_dp; + intel_connector->port = port; + drm_dp_mst_get_port_malloc(port); + + connector = &intel_connector->base; + ret = drm_connector_init(dev, connector, &intel_dp_mst_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret) { + drm_dp_mst_put_port_malloc(port); + intel_connector_free(intel_connector); + return NULL; + } + + drm_connector_helper_add(connector, &intel_dp_mst_connector_helper_funcs); + + for_each_pipe(dev_priv, pipe) { + struct drm_encoder *enc = + &intel_dp->mst_encoders[pipe]->base.base; + + ret = drm_connector_attach_encoder(&intel_connector->base, enc); + if (ret) + goto err; + } + + drm_object_attach_property(&connector->base, dev->mode_config.path_property, 0); + drm_object_attach_property(&connector->base, dev->mode_config.tile_property, 0); + + ret = drm_connector_set_path_property(connector, pathprop); + if (ret) + goto err; + + intel_attach_force_audio_property(connector); + intel_attach_broadcast_rgb_property(connector); + + ret = intel_dp_hdcp_init(dig_port, intel_connector); + if (ret) + drm_dbg_kms(&dev_priv->drm, "[%s:%d] HDCP MST init failed, skipping.\n", + connector->name, connector->base.id); + /* + * Reuse the prop from the SST connector because we're + * not allowed to create new props after device registration. + */ + connector->max_bpc_property = + intel_dp->attached_connector->base.max_bpc_property; + if (connector->max_bpc_property) + drm_connector_attach_max_bpc_property(connector, 6, 12); + + return connector; + +err: + drm_connector_cleanup(connector); + return NULL; +} + +static void +intel_dp_mst_poll_hpd_irq(struct drm_dp_mst_topology_mgr *mgr) +{ + struct intel_dp *intel_dp = container_of(mgr, struct intel_dp, mst_mgr); + + intel_hpd_trigger_irq(dp_to_dig_port(intel_dp)); +} + +static const struct drm_dp_mst_topology_cbs mst_cbs = { + .add_connector = intel_dp_add_mst_connector, + .poll_hpd_irq = intel_dp_mst_poll_hpd_irq, +}; + +static struct intel_dp_mst_encoder * +intel_dp_create_fake_mst_encoder(struct intel_digital_port *dig_port, enum pipe pipe) +{ + struct intel_dp_mst_encoder *intel_mst; + struct intel_encoder *intel_encoder; + struct drm_device *dev = dig_port->base.base.dev; + + intel_mst = kzalloc(sizeof(*intel_mst), GFP_KERNEL); + + if (!intel_mst) + return NULL; + + intel_mst->pipe = pipe; + intel_encoder = &intel_mst->base; + intel_mst->primary = dig_port; + + drm_encoder_init(dev, &intel_encoder->base, &intel_dp_mst_enc_funcs, + DRM_MODE_ENCODER_DPMST, "DP-MST %c", pipe_name(pipe)); + + intel_encoder->type = INTEL_OUTPUT_DP_MST; + intel_encoder->power_domain = dig_port->base.power_domain; + intel_encoder->port = dig_port->base.port; + intel_encoder->cloneable = 0; + /* + * This is wrong, but broken userspace uses the intersection + * of possible_crtcs of all the encoders of a given connector + * to figure out which crtcs can drive said connector. What + * should be used instead is the union of possible_crtcs. + * To keep such userspace functioning we must misconfigure + * this to make sure the intersection is not empty :( + */ + intel_encoder->pipe_mask = ~0; + + intel_encoder->compute_config = intel_dp_mst_compute_config; + intel_encoder->compute_config_late = intel_dp_mst_compute_config_late; + intel_encoder->disable = intel_mst_disable_dp; + intel_encoder->post_disable = intel_mst_post_disable_dp; + intel_encoder->update_pipe = intel_ddi_update_pipe; + intel_encoder->pre_pll_enable = intel_mst_pre_pll_enable_dp; + intel_encoder->pre_enable = intel_mst_pre_enable_dp; + intel_encoder->enable = intel_mst_enable_dp; + intel_encoder->get_hw_state = intel_dp_mst_enc_get_hw_state; + intel_encoder->get_config = intel_dp_mst_enc_get_config; + intel_encoder->initial_fastset_check = intel_dp_mst_initial_fastset_check; + + return intel_mst; + +} + +static bool +intel_dp_create_fake_mst_encoders(struct intel_digital_port *dig_port) +{ + struct intel_dp *intel_dp = &dig_port->dp; + struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev); + enum pipe pipe; + + for_each_pipe(dev_priv, pipe) + intel_dp->mst_encoders[pipe] = intel_dp_create_fake_mst_encoder(dig_port, pipe); + return true; +} + +int +intel_dp_mst_encoder_active_links(struct intel_digital_port *dig_port) +{ + return dig_port->dp.active_mst_links; +} + +int +intel_dp_mst_encoder_init(struct intel_digital_port *dig_port, int conn_base_id) +{ + struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev); + struct intel_dp *intel_dp = &dig_port->dp; + enum port port = dig_port->base.port; + int ret; + + if (!HAS_DP_MST(i915) || intel_dp_is_edp(intel_dp)) + return 0; + + if (DISPLAY_VER(i915) < 12 && port == PORT_A) + return 0; + + if (DISPLAY_VER(i915) < 11 && port == PORT_E) + return 0; + + intel_dp->mst_mgr.cbs = &mst_cbs; + + /* create encoders */ + intel_dp_create_fake_mst_encoders(dig_port); + ret = drm_dp_mst_topology_mgr_init(&intel_dp->mst_mgr, &i915->drm, + &intel_dp->aux, 16, 3, conn_base_id); + if (ret) { + intel_dp->mst_mgr.cbs = NULL; + return ret; + } + + return 0; +} + +bool intel_dp_mst_source_support(struct intel_dp *intel_dp) +{ + return intel_dp->mst_mgr.cbs; +} + +void +intel_dp_mst_encoder_cleanup(struct intel_digital_port *dig_port) +{ + struct intel_dp *intel_dp = &dig_port->dp; + + if (!intel_dp_mst_source_support(intel_dp)) + return; + + drm_dp_mst_topology_mgr_destroy(&intel_dp->mst_mgr); + /* encoders will get killed by normal cleanup */ + + intel_dp->mst_mgr.cbs = NULL; +} + +bool intel_dp_mst_is_master_trans(const struct intel_crtc_state *crtc_state) +{ + return crtc_state->mst_master_transcoder == crtc_state->cpu_transcoder; +} + +bool intel_dp_mst_is_slave_trans(const struct intel_crtc_state *crtc_state) +{ + return crtc_state->mst_master_transcoder != INVALID_TRANSCODER && + crtc_state->mst_master_transcoder != crtc_state->cpu_transcoder; +} + +/** + * intel_dp_mst_add_topology_state_for_connector - add MST topology state for a connector + * @state: atomic state + * @connector: connector to add the state for + * @crtc: the CRTC @connector is attached to + * + * Add the MST topology state for @connector to @state. + * + * Returns 0 on success, negative error code on failure. + */ +static int +intel_dp_mst_add_topology_state_for_connector(struct intel_atomic_state *state, + struct intel_connector *connector, + struct intel_crtc *crtc) +{ + struct drm_dp_mst_topology_state *mst_state; + + if (!connector->mst_port) + return 0; + + mst_state = drm_atomic_get_mst_topology_state(&state->base, + &connector->mst_port->mst_mgr); + if (IS_ERR(mst_state)) + return PTR_ERR(mst_state); + + mst_state->pending_crtc_mask |= drm_crtc_mask(&crtc->base); + + return 0; +} + +/** + * intel_dp_mst_add_topology_state_for_crtc - add MST topology state for a CRTC + * @state: atomic state + * @crtc: CRTC to add the state for + * + * Add the MST topology state for @crtc to @state. + * + * Returns 0 on success, negative error code on failure. + */ +int intel_dp_mst_add_topology_state_for_crtc(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_connector *_connector; + struct drm_connector_state *conn_state; + int i; + + for_each_new_connector_in_state(&state->base, _connector, conn_state, i) { + struct intel_connector *connector = to_intel_connector(_connector); + int ret; + + if (conn_state->crtc != &crtc->base) + continue; + + ret = intel_dp_mst_add_topology_state_for_connector(state, connector, crtc); + if (ret) + return ret; + } + + return 0; +} diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.h b/drivers/gpu/drm/i915/display/intel_dp_mst.h new file mode 100644 index 000000000..f1815bb72 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dp_mst.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_DP_MST_H__ +#define __INTEL_DP_MST_H__ + +#include + +struct intel_atomic_state; +struct intel_crtc; +struct intel_crtc_state; +struct intel_digital_port; +struct intel_dp; + +int intel_dp_mst_encoder_init(struct intel_digital_port *dig_port, int conn_id); +void intel_dp_mst_encoder_cleanup(struct intel_digital_port *dig_port); +int intel_dp_mst_encoder_active_links(struct intel_digital_port *dig_port); +bool intel_dp_mst_is_master_trans(const struct intel_crtc_state *crtc_state); +bool intel_dp_mst_is_slave_trans(const struct intel_crtc_state *crtc_state); +bool intel_dp_mst_source_support(struct intel_dp *intel_dp); +int intel_dp_mst_add_topology_state_for_crtc(struct intel_atomic_state *state, + struct intel_crtc *crtc); + +#endif /* __INTEL_DP_MST_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_dpio_phy.c b/drivers/gpu/drm/i915/display/intel_dpio_phy.c new file mode 100644 index 000000000..8732b8722 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dpio_phy.c @@ -0,0 +1,1106 @@ +/* + * Copyright © 2014-2016 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "intel_ddi.h" +#include "intel_ddi_buf_trans.h" +#include "intel_de.h" +#include "intel_display_power_well.h" +#include "intel_display_types.h" +#include "intel_dp.h" +#include "intel_dpio_phy.h" +#include "vlv_sideband.h" + +/** + * DOC: DPIO + * + * VLV, CHV and BXT have slightly peculiar display PHYs for driving DP/HDMI + * ports. DPIO is the name given to such a display PHY. These PHYs + * don't follow the standard programming model using direct MMIO + * registers, and instead their registers must be accessed trough IOSF + * sideband. VLV has one such PHY for driving ports B and C, and CHV + * adds another PHY for driving port D. Each PHY responds to specific + * IOSF-SB port. + * + * Each display PHY is made up of one or two channels. Each channel + * houses a common lane part which contains the PLL and other common + * logic. CH0 common lane also contains the IOSF-SB logic for the + * Common Register Interface (CRI) ie. the DPIO registers. CRI clock + * must be running when any DPIO registers are accessed. + * + * In addition to having their own registers, the PHYs are also + * controlled through some dedicated signals from the display + * controller. These include PLL reference clock enable, PLL enable, + * and CRI clock selection, for example. + * + * Eeach channel also has two splines (also called data lanes), and + * each spline is made up of one Physical Access Coding Sub-Layer + * (PCS) block and two TX lanes. So each channel has two PCS blocks + * and four TX lanes. The TX lanes are used as DP lanes or TMDS + * data/clock pairs depending on the output type. + * + * Additionally the PHY also contains an AUX lane with AUX blocks + * for each channel. This is used for DP AUX communication, but + * this fact isn't really relevant for the driver since AUX is + * controlled from the display controller side. No DPIO registers + * need to be accessed during AUX communication, + * + * Generally on VLV/CHV the common lane corresponds to the pipe and + * the spline (PCS/TX) corresponds to the port. + * + * For dual channel PHY (VLV/CHV): + * + * pipe A == CMN/PLL/REF CH0 + * + * pipe B == CMN/PLL/REF CH1 + * + * port B == PCS/TX CH0 + * + * port C == PCS/TX CH1 + * + * This is especially important when we cross the streams + * ie. drive port B with pipe B, or port C with pipe A. + * + * For single channel PHY (CHV): + * + * pipe C == CMN/PLL/REF CH0 + * + * port D == PCS/TX CH0 + * + * On BXT the entire PHY channel corresponds to the port. That means + * the PLL is also now associated with the port rather than the pipe, + * and so the clock needs to be routed to the appropriate transcoder. + * Port A PLL is directly connected to transcoder EDP and port B/C + * PLLs can be routed to any transcoder A/B/C. + * + * Note: DDI0 is digital port B, DD1 is digital port C, and DDI2 is + * digital port D (CHV) or port A (BXT). :: + * + * + * Dual channel PHY (VLV/CHV/BXT) + * --------------------------------- + * | CH0 | CH1 | + * | CMN/PLL/REF | CMN/PLL/REF | + * |---------------|---------------| Display PHY + * | PCS01 | PCS23 | PCS01 | PCS23 | + * |-------|-------|-------|-------| + * |TX0|TX1|TX2|TX3|TX0|TX1|TX2|TX3| + * --------------------------------- + * | DDI0 | DDI1 | DP/HDMI ports + * --------------------------------- + * + * Single channel PHY (CHV/BXT) + * ----------------- + * | CH0 | + * | CMN/PLL/REF | + * |---------------| Display PHY + * | PCS01 | PCS23 | + * |-------|-------| + * |TX0|TX1|TX2|TX3| + * ----------------- + * | DDI2 | DP/HDMI port + * ----------------- + */ + +/** + * struct bxt_ddi_phy_info - Hold info for a broxton DDI phy + */ +struct bxt_ddi_phy_info { + /** + * @dual_channel: true if this phy has a second channel. + */ + bool dual_channel; + + /** + * @rcomp_phy: If -1, indicates this phy has its own rcomp resistor. + * Otherwise the GRC value will be copied from the phy indicated by + * this field. + */ + enum dpio_phy rcomp_phy; + + /** + * @reset_delay: delay in us to wait before setting the common reset + * bit in BXT_PHY_CTL_FAMILY, which effectively enables the phy. + */ + int reset_delay; + + /** + * @pwron_mask: Mask with the appropriate bit set that would cause the + * punit to power this phy if written to BXT_P_CR_GT_DISP_PWRON. + */ + u32 pwron_mask; + + /** + * @channel: struct containing per channel information. + */ + struct { + /** + * @channel.port: which port maps to this channel. + */ + enum port port; + } channel[2]; +}; + +static const struct bxt_ddi_phy_info bxt_ddi_phy_info[] = { + [DPIO_PHY0] = { + .dual_channel = true, + .rcomp_phy = DPIO_PHY1, + .pwron_mask = BIT(0), + + .channel = { + [DPIO_CH0] = { .port = PORT_B }, + [DPIO_CH1] = { .port = PORT_C }, + } + }, + [DPIO_PHY1] = { + .dual_channel = false, + .rcomp_phy = -1, + .pwron_mask = BIT(1), + + .channel = { + [DPIO_CH0] = { .port = PORT_A }, + } + }, +}; + +static const struct bxt_ddi_phy_info glk_ddi_phy_info[] = { + [DPIO_PHY0] = { + .dual_channel = false, + .rcomp_phy = DPIO_PHY1, + .pwron_mask = BIT(0), + .reset_delay = 20, + + .channel = { + [DPIO_CH0] = { .port = PORT_B }, + } + }, + [DPIO_PHY1] = { + .dual_channel = false, + .rcomp_phy = -1, + .pwron_mask = BIT(3), + .reset_delay = 20, + + .channel = { + [DPIO_CH0] = { .port = PORT_A }, + } + }, + [DPIO_PHY2] = { + .dual_channel = false, + .rcomp_phy = DPIO_PHY1, + .pwron_mask = BIT(1), + .reset_delay = 20, + + .channel = { + [DPIO_CH0] = { .port = PORT_C }, + } + }, +}; + +static const struct bxt_ddi_phy_info * +bxt_get_phy_list(struct drm_i915_private *dev_priv, int *count) +{ + if (IS_GEMINILAKE(dev_priv)) { + *count = ARRAY_SIZE(glk_ddi_phy_info); + return glk_ddi_phy_info; + } else { + *count = ARRAY_SIZE(bxt_ddi_phy_info); + return bxt_ddi_phy_info; + } +} + +static const struct bxt_ddi_phy_info * +bxt_get_phy_info(struct drm_i915_private *dev_priv, enum dpio_phy phy) +{ + int count; + const struct bxt_ddi_phy_info *phy_list = + bxt_get_phy_list(dev_priv, &count); + + return &phy_list[phy]; +} + +void bxt_port_to_phy_channel(struct drm_i915_private *dev_priv, enum port port, + enum dpio_phy *phy, enum dpio_channel *ch) +{ + const struct bxt_ddi_phy_info *phy_info, *phys; + int i, count; + + phys = bxt_get_phy_list(dev_priv, &count); + + for (i = 0; i < count; i++) { + phy_info = &phys[i]; + + if (port == phy_info->channel[DPIO_CH0].port) { + *phy = i; + *ch = DPIO_CH0; + return; + } + + if (phy_info->dual_channel && + port == phy_info->channel[DPIO_CH1].port) { + *phy = i; + *ch = DPIO_CH1; + return; + } + } + + drm_WARN(&dev_priv->drm, 1, "PHY not found for PORT %c", + port_name(port)); + *phy = DPIO_PHY0; + *ch = DPIO_CH0; +} + +void bxt_ddi_phy_set_signal_levels(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + int level = intel_ddi_level(encoder, crtc_state, 0); + const struct intel_ddi_buf_trans *trans; + enum dpio_channel ch; + enum dpio_phy phy; + int n_entries; + u32 val; + + trans = encoder->get_buf_trans(encoder, crtc_state, &n_entries); + if (drm_WARN_ON_ONCE(&dev_priv->drm, !trans)) + return; + + bxt_port_to_phy_channel(dev_priv, encoder->port, &phy, &ch); + + /* + * While we write to the group register to program all lanes at once we + * can read only lane registers and we pick lanes 0/1 for that. + */ + val = intel_de_read(dev_priv, BXT_PORT_PCS_DW10_LN01(phy, ch)); + val &= ~(TX2_SWING_CALC_INIT | TX1_SWING_CALC_INIT); + intel_de_write(dev_priv, BXT_PORT_PCS_DW10_GRP(phy, ch), val); + + val = intel_de_read(dev_priv, BXT_PORT_TX_DW2_LN0(phy, ch)); + val &= ~(MARGIN_000 | UNIQ_TRANS_SCALE); + val |= trans->entries[level].bxt.margin << MARGIN_000_SHIFT | + trans->entries[level].bxt.scale << UNIQ_TRANS_SCALE_SHIFT; + intel_de_write(dev_priv, BXT_PORT_TX_DW2_GRP(phy, ch), val); + + val = intel_de_read(dev_priv, BXT_PORT_TX_DW3_LN0(phy, ch)); + val &= ~SCALE_DCOMP_METHOD; + if (trans->entries[level].bxt.enable) + val |= SCALE_DCOMP_METHOD; + + if ((val & UNIQUE_TRANGE_EN_METHOD) && !(val & SCALE_DCOMP_METHOD)) + drm_err(&dev_priv->drm, + "Disabled scaling while ouniqetrangenmethod was set"); + + intel_de_write(dev_priv, BXT_PORT_TX_DW3_GRP(phy, ch), val); + + val = intel_de_read(dev_priv, BXT_PORT_TX_DW4_LN0(phy, ch)); + val &= ~DE_EMPHASIS; + val |= trans->entries[level].bxt.deemphasis << DEEMPH_SHIFT; + intel_de_write(dev_priv, BXT_PORT_TX_DW4_GRP(phy, ch), val); + + val = intel_de_read(dev_priv, BXT_PORT_PCS_DW10_LN01(phy, ch)); + val |= TX2_SWING_CALC_INIT | TX1_SWING_CALC_INIT; + intel_de_write(dev_priv, BXT_PORT_PCS_DW10_GRP(phy, ch), val); +} + +bool bxt_ddi_phy_is_enabled(struct drm_i915_private *dev_priv, + enum dpio_phy phy) +{ + const struct bxt_ddi_phy_info *phy_info; + + phy_info = bxt_get_phy_info(dev_priv, phy); + + if (!(intel_de_read(dev_priv, BXT_P_CR_GT_DISP_PWRON) & phy_info->pwron_mask)) + return false; + + if ((intel_de_read(dev_priv, BXT_PORT_CL1CM_DW0(phy)) & + (PHY_POWER_GOOD | PHY_RESERVED)) != PHY_POWER_GOOD) { + drm_dbg(&dev_priv->drm, + "DDI PHY %d powered, but power hasn't settled\n", phy); + + return false; + } + + if (!(intel_de_read(dev_priv, BXT_PHY_CTL_FAMILY(phy)) & COMMON_RESET_DIS)) { + drm_dbg(&dev_priv->drm, + "DDI PHY %d powered, but still in reset\n", phy); + + return false; + } + + return true; +} + +static u32 bxt_get_grc(struct drm_i915_private *dev_priv, enum dpio_phy phy) +{ + u32 val = intel_de_read(dev_priv, BXT_PORT_REF_DW6(phy)); + + return (val & GRC_CODE_MASK) >> GRC_CODE_SHIFT; +} + +static void bxt_phy_wait_grc_done(struct drm_i915_private *dev_priv, + enum dpio_phy phy) +{ + if (intel_de_wait_for_set(dev_priv, BXT_PORT_REF_DW3(phy), + GRC_DONE, 10)) + drm_err(&dev_priv->drm, "timeout waiting for PHY%d GRC\n", + phy); +} + +static void _bxt_ddi_phy_init(struct drm_i915_private *dev_priv, + enum dpio_phy phy) +{ + const struct bxt_ddi_phy_info *phy_info; + u32 val; + + phy_info = bxt_get_phy_info(dev_priv, phy); + + if (bxt_ddi_phy_is_enabled(dev_priv, phy)) { + /* Still read out the GRC value for state verification */ + if (phy_info->rcomp_phy != -1) + dev_priv->bxt_phy_grc = bxt_get_grc(dev_priv, phy); + + if (bxt_ddi_phy_verify_state(dev_priv, phy)) { + drm_dbg(&dev_priv->drm, "DDI PHY %d already enabled, " + "won't reprogram it\n", phy); + return; + } + + drm_dbg(&dev_priv->drm, + "DDI PHY %d enabled with invalid state, " + "force reprogramming it\n", phy); + } + + val = intel_de_read(dev_priv, BXT_P_CR_GT_DISP_PWRON); + val |= phy_info->pwron_mask; + intel_de_write(dev_priv, BXT_P_CR_GT_DISP_PWRON, val); + + /* + * The PHY registers start out inaccessible and respond to reads with + * all 1s. Eventually they become accessible as they power up, then + * the reserved bit will give the default 0. Poll on the reserved bit + * becoming 0 to find when the PHY is accessible. + * The flag should get set in 100us according to the HW team, but + * use 1ms due to occasional timeouts observed with that. + */ + if (intel_wait_for_register_fw(&dev_priv->uncore, + BXT_PORT_CL1CM_DW0(phy), + PHY_RESERVED | PHY_POWER_GOOD, + PHY_POWER_GOOD, + 1)) + drm_err(&dev_priv->drm, "timeout during PHY%d power on\n", + phy); + + /* Program PLL Rcomp code offset */ + val = intel_de_read(dev_priv, BXT_PORT_CL1CM_DW9(phy)); + val &= ~IREF0RC_OFFSET_MASK; + val |= 0xE4 << IREF0RC_OFFSET_SHIFT; + intel_de_write(dev_priv, BXT_PORT_CL1CM_DW9(phy), val); + + val = intel_de_read(dev_priv, BXT_PORT_CL1CM_DW10(phy)); + val &= ~IREF1RC_OFFSET_MASK; + val |= 0xE4 << IREF1RC_OFFSET_SHIFT; + intel_de_write(dev_priv, BXT_PORT_CL1CM_DW10(phy), val); + + /* Program power gating */ + val = intel_de_read(dev_priv, BXT_PORT_CL1CM_DW28(phy)); + val |= OCL1_POWER_DOWN_EN | DW28_OLDO_DYN_PWR_DOWN_EN | + SUS_CLK_CONFIG; + intel_de_write(dev_priv, BXT_PORT_CL1CM_DW28(phy), val); + + if (phy_info->dual_channel) { + val = intel_de_read(dev_priv, BXT_PORT_CL2CM_DW6(phy)); + val |= DW6_OLDO_DYN_PWR_DOWN_EN; + intel_de_write(dev_priv, BXT_PORT_CL2CM_DW6(phy), val); + } + + if (phy_info->rcomp_phy != -1) { + u32 grc_code; + + bxt_phy_wait_grc_done(dev_priv, phy_info->rcomp_phy); + + /* + * PHY0 isn't connected to an RCOMP resistor so copy over + * the corresponding calibrated value from PHY1, and disable + * the automatic calibration on PHY0. + */ + val = dev_priv->bxt_phy_grc = bxt_get_grc(dev_priv, + phy_info->rcomp_phy); + grc_code = val << GRC_CODE_FAST_SHIFT | + val << GRC_CODE_SLOW_SHIFT | + val; + intel_de_write(dev_priv, BXT_PORT_REF_DW6(phy), grc_code); + + val = intel_de_read(dev_priv, BXT_PORT_REF_DW8(phy)); + val |= GRC_DIS | GRC_RDY_OVRD; + intel_de_write(dev_priv, BXT_PORT_REF_DW8(phy), val); + } + + if (phy_info->reset_delay) + udelay(phy_info->reset_delay); + + val = intel_de_read(dev_priv, BXT_PHY_CTL_FAMILY(phy)); + val |= COMMON_RESET_DIS; + intel_de_write(dev_priv, BXT_PHY_CTL_FAMILY(phy), val); +} + +void bxt_ddi_phy_uninit(struct drm_i915_private *dev_priv, enum dpio_phy phy) +{ + const struct bxt_ddi_phy_info *phy_info; + u32 val; + + phy_info = bxt_get_phy_info(dev_priv, phy); + + val = intel_de_read(dev_priv, BXT_PHY_CTL_FAMILY(phy)); + val &= ~COMMON_RESET_DIS; + intel_de_write(dev_priv, BXT_PHY_CTL_FAMILY(phy), val); + + val = intel_de_read(dev_priv, BXT_P_CR_GT_DISP_PWRON); + val &= ~phy_info->pwron_mask; + intel_de_write(dev_priv, BXT_P_CR_GT_DISP_PWRON, val); +} + +void bxt_ddi_phy_init(struct drm_i915_private *dev_priv, enum dpio_phy phy) +{ + const struct bxt_ddi_phy_info *phy_info = + bxt_get_phy_info(dev_priv, phy); + enum dpio_phy rcomp_phy = phy_info->rcomp_phy; + bool was_enabled; + + lockdep_assert_held(&dev_priv->display.power.domains.lock); + + was_enabled = true; + if (rcomp_phy != -1) + was_enabled = bxt_ddi_phy_is_enabled(dev_priv, rcomp_phy); + + /* + * We need to copy the GRC calibration value from rcomp_phy, + * so make sure it's powered up. + */ + if (!was_enabled) + _bxt_ddi_phy_init(dev_priv, rcomp_phy); + + _bxt_ddi_phy_init(dev_priv, phy); + + if (!was_enabled) + bxt_ddi_phy_uninit(dev_priv, rcomp_phy); +} + +static bool __printf(6, 7) +__phy_reg_verify_state(struct drm_i915_private *dev_priv, enum dpio_phy phy, + i915_reg_t reg, u32 mask, u32 expected, + const char *reg_fmt, ...) +{ + struct va_format vaf; + va_list args; + u32 val; + + val = intel_de_read(dev_priv, reg); + if ((val & mask) == expected) + return true; + + va_start(args, reg_fmt); + vaf.fmt = reg_fmt; + vaf.va = &args; + + drm_dbg(&dev_priv->drm, "DDI PHY %d reg %pV [%08x] state mismatch: " + "current %08x, expected %08x (mask %08x)\n", + phy, &vaf, reg.reg, val, (val & ~mask) | expected, + mask); + + va_end(args); + + return false; +} + +bool bxt_ddi_phy_verify_state(struct drm_i915_private *dev_priv, + enum dpio_phy phy) +{ + const struct bxt_ddi_phy_info *phy_info; + u32 mask; + bool ok; + + phy_info = bxt_get_phy_info(dev_priv, phy); + +#define _CHK(reg, mask, exp, fmt, ...) \ + __phy_reg_verify_state(dev_priv, phy, reg, mask, exp, fmt, \ + ## __VA_ARGS__) + + if (!bxt_ddi_phy_is_enabled(dev_priv, phy)) + return false; + + ok = true; + + /* PLL Rcomp code offset */ + ok &= _CHK(BXT_PORT_CL1CM_DW9(phy), + IREF0RC_OFFSET_MASK, 0xe4 << IREF0RC_OFFSET_SHIFT, + "BXT_PORT_CL1CM_DW9(%d)", phy); + ok &= _CHK(BXT_PORT_CL1CM_DW10(phy), + IREF1RC_OFFSET_MASK, 0xe4 << IREF1RC_OFFSET_SHIFT, + "BXT_PORT_CL1CM_DW10(%d)", phy); + + /* Power gating */ + mask = OCL1_POWER_DOWN_EN | DW28_OLDO_DYN_PWR_DOWN_EN | SUS_CLK_CONFIG; + ok &= _CHK(BXT_PORT_CL1CM_DW28(phy), mask, mask, + "BXT_PORT_CL1CM_DW28(%d)", phy); + + if (phy_info->dual_channel) + ok &= _CHK(BXT_PORT_CL2CM_DW6(phy), + DW6_OLDO_DYN_PWR_DOWN_EN, DW6_OLDO_DYN_PWR_DOWN_EN, + "BXT_PORT_CL2CM_DW6(%d)", phy); + + if (phy_info->rcomp_phy != -1) { + u32 grc_code = dev_priv->bxt_phy_grc; + + grc_code = grc_code << GRC_CODE_FAST_SHIFT | + grc_code << GRC_CODE_SLOW_SHIFT | + grc_code; + mask = GRC_CODE_FAST_MASK | GRC_CODE_SLOW_MASK | + GRC_CODE_NOM_MASK; + ok &= _CHK(BXT_PORT_REF_DW6(phy), mask, grc_code, + "BXT_PORT_REF_DW6(%d)", phy); + + mask = GRC_DIS | GRC_RDY_OVRD; + ok &= _CHK(BXT_PORT_REF_DW8(phy), mask, mask, + "BXT_PORT_REF_DW8(%d)", phy); + } + + return ok; +#undef _CHK +} + +u8 +bxt_ddi_phy_calc_lane_lat_optim_mask(u8 lane_count) +{ + switch (lane_count) { + case 1: + return 0; + case 2: + return BIT(2) | BIT(0); + case 4: + return BIT(3) | BIT(2) | BIT(0); + default: + MISSING_CASE(lane_count); + + return 0; + } +} + +void bxt_ddi_phy_set_lane_optim_mask(struct intel_encoder *encoder, + u8 lane_lat_optim_mask) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + enum port port = encoder->port; + enum dpio_phy phy; + enum dpio_channel ch; + int lane; + + bxt_port_to_phy_channel(dev_priv, port, &phy, &ch); + + for (lane = 0; lane < 4; lane++) { + u32 val = intel_de_read(dev_priv, + BXT_PORT_TX_DW14_LN(phy, ch, lane)); + + /* + * Note that on CHV this flag is called UPAR, but has + * the same function. + */ + val &= ~LATENCY_OPTIM; + if (lane_lat_optim_mask & BIT(lane)) + val |= LATENCY_OPTIM; + + intel_de_write(dev_priv, BXT_PORT_TX_DW14_LN(phy, ch, lane), + val); + } +} + +u8 +bxt_ddi_phy_get_lane_lat_optim_mask(struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + enum port port = encoder->port; + enum dpio_phy phy; + enum dpio_channel ch; + int lane; + u8 mask; + + bxt_port_to_phy_channel(dev_priv, port, &phy, &ch); + + mask = 0; + for (lane = 0; lane < 4; lane++) { + u32 val = intel_de_read(dev_priv, + BXT_PORT_TX_DW14_LN(phy, ch, lane)); + + if (val & LATENCY_OPTIM) + mask |= BIT(lane); + } + + return mask; +} + +void chv_set_phy_signal_level(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + u32 deemph_reg_value, u32 margin_reg_value, + bool uniq_trans_scale) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + enum dpio_channel ch = vlv_dig_port_to_channel(dig_port); + enum pipe pipe = crtc->pipe; + u32 val; + int i; + + vlv_dpio_get(dev_priv); + + /* Clear calc init */ + val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW10(ch)); + val &= ~(DPIO_PCS_SWING_CALC_TX0_TX2 | DPIO_PCS_SWING_CALC_TX1_TX3); + val &= ~(DPIO_PCS_TX1DEEMP_MASK | DPIO_PCS_TX2DEEMP_MASK); + val |= DPIO_PCS_TX1DEEMP_9P5 | DPIO_PCS_TX2DEEMP_9P5; + vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW10(ch), val); + + if (crtc_state->lane_count > 2) { + val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW10(ch)); + val &= ~(DPIO_PCS_SWING_CALC_TX0_TX2 | DPIO_PCS_SWING_CALC_TX1_TX3); + val &= ~(DPIO_PCS_TX1DEEMP_MASK | DPIO_PCS_TX2DEEMP_MASK); + val |= DPIO_PCS_TX1DEEMP_9P5 | DPIO_PCS_TX2DEEMP_9P5; + vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW10(ch), val); + } + + val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW9(ch)); + val &= ~(DPIO_PCS_TX1MARGIN_MASK | DPIO_PCS_TX2MARGIN_MASK); + val |= DPIO_PCS_TX1MARGIN_000 | DPIO_PCS_TX2MARGIN_000; + vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW9(ch), val); + + if (crtc_state->lane_count > 2) { + val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW9(ch)); + val &= ~(DPIO_PCS_TX1MARGIN_MASK | DPIO_PCS_TX2MARGIN_MASK); + val |= DPIO_PCS_TX1MARGIN_000 | DPIO_PCS_TX2MARGIN_000; + vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW9(ch), val); + } + + /* Program swing deemph */ + for (i = 0; i < crtc_state->lane_count; i++) { + val = vlv_dpio_read(dev_priv, pipe, CHV_TX_DW4(ch, i)); + val &= ~DPIO_SWING_DEEMPH9P5_MASK; + val |= deemph_reg_value << DPIO_SWING_DEEMPH9P5_SHIFT; + vlv_dpio_write(dev_priv, pipe, CHV_TX_DW4(ch, i), val); + } + + /* Program swing margin */ + for (i = 0; i < crtc_state->lane_count; i++) { + val = vlv_dpio_read(dev_priv, pipe, CHV_TX_DW2(ch, i)); + + val &= ~DPIO_SWING_MARGIN000_MASK; + val |= margin_reg_value << DPIO_SWING_MARGIN000_SHIFT; + + /* + * Supposedly this value shouldn't matter when unique transition + * scale is disabled, but in fact it does matter. Let's just + * always program the same value and hope it's OK. + */ + val &= ~(0xff << DPIO_UNIQ_TRANS_SCALE_SHIFT); + val |= 0x9a << DPIO_UNIQ_TRANS_SCALE_SHIFT; + + vlv_dpio_write(dev_priv, pipe, CHV_TX_DW2(ch, i), val); + } + + /* + * The document said it needs to set bit 27 for ch0 and bit 26 + * for ch1. Might be a typo in the doc. + * For now, for this unique transition scale selection, set bit + * 27 for ch0 and ch1. + */ + for (i = 0; i < crtc_state->lane_count; i++) { + val = vlv_dpio_read(dev_priv, pipe, CHV_TX_DW3(ch, i)); + if (uniq_trans_scale) + val |= DPIO_TX_UNIQ_TRANS_SCALE_EN; + else + val &= ~DPIO_TX_UNIQ_TRANS_SCALE_EN; + vlv_dpio_write(dev_priv, pipe, CHV_TX_DW3(ch, i), val); + } + + /* Start swing calculation */ + val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW10(ch)); + val |= DPIO_PCS_SWING_CALC_TX0_TX2 | DPIO_PCS_SWING_CALC_TX1_TX3; + vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW10(ch), val); + + if (crtc_state->lane_count > 2) { + val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW10(ch)); + val |= DPIO_PCS_SWING_CALC_TX0_TX2 | DPIO_PCS_SWING_CALC_TX1_TX3; + vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW10(ch), val); + } + + vlv_dpio_put(dev_priv); +} + +void chv_data_lane_soft_reset(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + bool reset) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + enum dpio_channel ch = vlv_dig_port_to_channel(enc_to_dig_port(encoder)); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + enum pipe pipe = crtc->pipe; + u32 val; + + val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW0(ch)); + if (reset) + val &= ~(DPIO_PCS_TX_LANE2_RESET | DPIO_PCS_TX_LANE1_RESET); + else + val |= DPIO_PCS_TX_LANE2_RESET | DPIO_PCS_TX_LANE1_RESET; + vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW0(ch), val); + + if (crtc_state->lane_count > 2) { + val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW0(ch)); + if (reset) + val &= ~(DPIO_PCS_TX_LANE2_RESET | DPIO_PCS_TX_LANE1_RESET); + else + val |= DPIO_PCS_TX_LANE2_RESET | DPIO_PCS_TX_LANE1_RESET; + vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW0(ch), val); + } + + val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW1(ch)); + val |= CHV_PCS_REQ_SOFTRESET_EN; + if (reset) + val &= ~DPIO_PCS_CLK_SOFT_RESET; + else + val |= DPIO_PCS_CLK_SOFT_RESET; + vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW1(ch), val); + + if (crtc_state->lane_count > 2) { + val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW1(ch)); + val |= CHV_PCS_REQ_SOFTRESET_EN; + if (reset) + val &= ~DPIO_PCS_CLK_SOFT_RESET; + else + val |= DPIO_PCS_CLK_SOFT_RESET; + vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW1(ch), val); + } +} + +void chv_phy_pre_pll_enable(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + enum dpio_channel ch = vlv_dig_port_to_channel(dig_port); + enum pipe pipe = crtc->pipe; + unsigned int lane_mask = + intel_dp_unused_lane_mask(crtc_state->lane_count); + u32 val; + + /* + * Must trick the second common lane into life. + * Otherwise we can't even access the PLL. + */ + if (ch == DPIO_CH0 && pipe == PIPE_B) + dig_port->release_cl2_override = + !chv_phy_powergate_ch(dev_priv, DPIO_PHY0, DPIO_CH1, true); + + chv_phy_powergate_lanes(encoder, true, lane_mask); + + vlv_dpio_get(dev_priv); + + /* Assert data lane reset */ + chv_data_lane_soft_reset(encoder, crtc_state, true); + + /* program left/right clock distribution */ + if (pipe != PIPE_B) { + val = vlv_dpio_read(dev_priv, pipe, _CHV_CMN_DW5_CH0); + val &= ~(CHV_BUFLEFTENA1_MASK | CHV_BUFRIGHTENA1_MASK); + if (ch == DPIO_CH0) + val |= CHV_BUFLEFTENA1_FORCE; + if (ch == DPIO_CH1) + val |= CHV_BUFRIGHTENA1_FORCE; + vlv_dpio_write(dev_priv, pipe, _CHV_CMN_DW5_CH0, val); + } else { + val = vlv_dpio_read(dev_priv, pipe, _CHV_CMN_DW1_CH1); + val &= ~(CHV_BUFLEFTENA2_MASK | CHV_BUFRIGHTENA2_MASK); + if (ch == DPIO_CH0) + val |= CHV_BUFLEFTENA2_FORCE; + if (ch == DPIO_CH1) + val |= CHV_BUFRIGHTENA2_FORCE; + vlv_dpio_write(dev_priv, pipe, _CHV_CMN_DW1_CH1, val); + } + + /* program clock channel usage */ + val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW8(ch)); + val |= CHV_PCS_USEDCLKCHANNEL_OVRRIDE; + if (pipe != PIPE_B) + val &= ~CHV_PCS_USEDCLKCHANNEL; + else + val |= CHV_PCS_USEDCLKCHANNEL; + vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW8(ch), val); + + if (crtc_state->lane_count > 2) { + val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW8(ch)); + val |= CHV_PCS_USEDCLKCHANNEL_OVRRIDE; + if (pipe != PIPE_B) + val &= ~CHV_PCS_USEDCLKCHANNEL; + else + val |= CHV_PCS_USEDCLKCHANNEL; + vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW8(ch), val); + } + + /* + * This a a bit weird since generally CL + * matches the pipe, but here we need to + * pick the CL based on the port. + */ + val = vlv_dpio_read(dev_priv, pipe, CHV_CMN_DW19(ch)); + if (pipe != PIPE_B) + val &= ~CHV_CMN_USEDCLKCHANNEL; + else + val |= CHV_CMN_USEDCLKCHANNEL; + vlv_dpio_write(dev_priv, pipe, CHV_CMN_DW19(ch), val); + + vlv_dpio_put(dev_priv); +} + +void chv_phy_pre_encoder_enable(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + enum dpio_channel ch = vlv_dig_port_to_channel(dig_port); + enum pipe pipe = crtc->pipe; + int data, i, stagger; + u32 val; + + vlv_dpio_get(dev_priv); + + /* allow hardware to manage TX FIFO reset source */ + val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW11(ch)); + val &= ~DPIO_LANEDESKEW_STRAP_OVRD; + vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW11(ch), val); + + if (crtc_state->lane_count > 2) { + val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW11(ch)); + val &= ~DPIO_LANEDESKEW_STRAP_OVRD; + vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW11(ch), val); + } + + /* Program Tx lane latency optimal setting*/ + for (i = 0; i < crtc_state->lane_count; i++) { + /* Set the upar bit */ + if (crtc_state->lane_count == 1) + data = 0x0; + else + data = (i == 1) ? 0x0 : 0x1; + vlv_dpio_write(dev_priv, pipe, CHV_TX_DW14(ch, i), + data << DPIO_UPAR_SHIFT); + } + + /* Data lane stagger programming */ + if (crtc_state->port_clock > 270000) + stagger = 0x18; + else if (crtc_state->port_clock > 135000) + stagger = 0xd; + else if (crtc_state->port_clock > 67500) + stagger = 0x7; + else if (crtc_state->port_clock > 33750) + stagger = 0x4; + else + stagger = 0x2; + + val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW11(ch)); + val |= DPIO_TX2_STAGGER_MASK(0x1f); + vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW11(ch), val); + + if (crtc_state->lane_count > 2) { + val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW11(ch)); + val |= DPIO_TX2_STAGGER_MASK(0x1f); + vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW11(ch), val); + } + + vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW12(ch), + DPIO_LANESTAGGER_STRAP(stagger) | + DPIO_LANESTAGGER_STRAP_OVRD | + DPIO_TX1_STAGGER_MASK(0x1f) | + DPIO_TX1_STAGGER_MULT(6) | + DPIO_TX2_STAGGER_MULT(0)); + + if (crtc_state->lane_count > 2) { + vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW12(ch), + DPIO_LANESTAGGER_STRAP(stagger) | + DPIO_LANESTAGGER_STRAP_OVRD | + DPIO_TX1_STAGGER_MASK(0x1f) | + DPIO_TX1_STAGGER_MULT(7) | + DPIO_TX2_STAGGER_MULT(5)); + } + + /* Deassert data lane reset */ + chv_data_lane_soft_reset(encoder, crtc_state, false); + + vlv_dpio_put(dev_priv); +} + +void chv_phy_release_cl2_override(struct intel_encoder *encoder) +{ + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + + if (dig_port->release_cl2_override) { + chv_phy_powergate_ch(dev_priv, DPIO_PHY0, DPIO_CH1, false); + dig_port->release_cl2_override = false; + } +} + +void chv_phy_post_pll_disable(struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + enum pipe pipe = to_intel_crtc(old_crtc_state->uapi.crtc)->pipe; + u32 val; + + vlv_dpio_get(dev_priv); + + /* disable left/right clock distribution */ + if (pipe != PIPE_B) { + val = vlv_dpio_read(dev_priv, pipe, _CHV_CMN_DW5_CH0); + val &= ~(CHV_BUFLEFTENA1_MASK | CHV_BUFRIGHTENA1_MASK); + vlv_dpio_write(dev_priv, pipe, _CHV_CMN_DW5_CH0, val); + } else { + val = vlv_dpio_read(dev_priv, pipe, _CHV_CMN_DW1_CH1); + val &= ~(CHV_BUFLEFTENA2_MASK | CHV_BUFRIGHTENA2_MASK); + vlv_dpio_write(dev_priv, pipe, _CHV_CMN_DW1_CH1, val); + } + + vlv_dpio_put(dev_priv); + + /* + * Leave the power down bit cleared for at least one + * lane so that chv_powergate_phy_ch() will power + * on something when the channel is otherwise unused. + * When the port is off and the override is removed + * the lanes power down anyway, so otherwise it doesn't + * really matter what the state of power down bits is + * after this. + */ + chv_phy_powergate_lanes(encoder, false, 0x0); +} + +void vlv_set_phy_signal_level(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + u32 demph_reg_value, u32 preemph_reg_value, + u32 uniqtranscale_reg_value, u32 tx3_demph) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + enum dpio_channel port = vlv_dig_port_to_channel(dig_port); + enum pipe pipe = crtc->pipe; + + vlv_dpio_get(dev_priv); + + vlv_dpio_write(dev_priv, pipe, VLV_TX_DW5(port), 0x00000000); + vlv_dpio_write(dev_priv, pipe, VLV_TX_DW4(port), demph_reg_value); + vlv_dpio_write(dev_priv, pipe, VLV_TX_DW2(port), + uniqtranscale_reg_value); + vlv_dpio_write(dev_priv, pipe, VLV_TX_DW3(port), 0x0C782040); + + if (tx3_demph) + vlv_dpio_write(dev_priv, pipe, VLV_TX3_DW4(port), tx3_demph); + + vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW11(port), 0x00030000); + vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW9(port), preemph_reg_value); + vlv_dpio_write(dev_priv, pipe, VLV_TX_DW5(port), DPIO_TX_OCALINIT_EN); + + vlv_dpio_put(dev_priv); +} + +void vlv_phy_pre_pll_enable(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + enum dpio_channel port = vlv_dig_port_to_channel(dig_port); + enum pipe pipe = crtc->pipe; + + /* Program Tx lane resets to default */ + vlv_dpio_get(dev_priv); + + vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW0(port), + DPIO_PCS_TX_LANE2_RESET | + DPIO_PCS_TX_LANE1_RESET); + vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW1(port), + DPIO_PCS_CLK_CRI_RXEB_EIOS_EN | + DPIO_PCS_CLK_CRI_RXDIGFILTSG_EN | + (1<base.dev); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + enum dpio_channel port = vlv_dig_port_to_channel(dig_port); + enum pipe pipe = crtc->pipe; + u32 val; + + vlv_dpio_get(dev_priv); + + /* Enable clock channels for this port */ + val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW8(port)); + val = 0; + if (pipe) + val |= (1<<21); + else + val &= ~(1<<21); + val |= 0x001000c4; + vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW8(port), val); + + /* Program lane clock */ + vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW14(port), 0x00760018); + vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW23(port), 0x00400888); + + vlv_dpio_put(dev_priv); +} + +void vlv_phy_reset_lanes(struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state) +{ + struct intel_digital_port *dig_port = enc_to_dig_port(encoder); + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->uapi.crtc); + enum dpio_channel port = vlv_dig_port_to_channel(dig_port); + enum pipe pipe = crtc->pipe; + + vlv_dpio_get(dev_priv); + vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW0(port), 0x00000000); + vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW1(port), 0x00e00060); + vlv_dpio_put(dev_priv); +} diff --git a/drivers/gpu/drm/i915/display/intel_dpio_phy.h b/drivers/gpu/drm/i915/display/intel_dpio_phy.h new file mode 100644 index 000000000..9c3d008e8 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dpio_phy.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_DPIO_PHY_H__ +#define __INTEL_DPIO_PHY_H__ + +#include + +enum dpio_channel; +enum dpio_phy; +enum port; +struct drm_i915_private; +struct intel_crtc_state; +struct intel_encoder; + +void bxt_port_to_phy_channel(struct drm_i915_private *dev_priv, enum port port, + enum dpio_phy *phy, enum dpio_channel *ch); +void bxt_ddi_phy_set_signal_levels(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state); +void bxt_ddi_phy_init(struct drm_i915_private *dev_priv, enum dpio_phy phy); +void bxt_ddi_phy_uninit(struct drm_i915_private *dev_priv, enum dpio_phy phy); +bool bxt_ddi_phy_is_enabled(struct drm_i915_private *dev_priv, + enum dpio_phy phy); +bool bxt_ddi_phy_verify_state(struct drm_i915_private *dev_priv, + enum dpio_phy phy); +u8 bxt_ddi_phy_calc_lane_lat_optim_mask(u8 lane_count); +void bxt_ddi_phy_set_lane_optim_mask(struct intel_encoder *encoder, + u8 lane_lat_optim_mask); +u8 bxt_ddi_phy_get_lane_lat_optim_mask(struct intel_encoder *encoder); + +void chv_set_phy_signal_level(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + u32 deemph_reg_value, u32 margin_reg_value, + bool uniq_trans_scale); +void chv_data_lane_soft_reset(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + bool reset); +void chv_phy_pre_pll_enable(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state); +void chv_phy_pre_encoder_enable(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state); +void chv_phy_release_cl2_override(struct intel_encoder *encoder); +void chv_phy_post_pll_disable(struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state); + +void vlv_set_phy_signal_level(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + u32 demph_reg_value, u32 preemph_reg_value, + u32 uniqtranscale_reg_value, u32 tx3_demph); +void vlv_phy_pre_pll_enable(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state); +void vlv_phy_pre_encoder_enable(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state); +void vlv_phy_reset_lanes(struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state); + +#endif /* __INTEL_DPIO_PHY_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_dpll.c b/drivers/gpu/drm/i915/display/intel_dpll.c new file mode 100644 index 000000000..b15ba78d6 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dpll.c @@ -0,0 +1,2061 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2020 Intel Corporation + */ + +#include +#include + +#include "intel_crtc.h" +#include "intel_de.h" +#include "intel_display.h" +#include "intel_display_types.h" +#include "intel_dpll.h" +#include "intel_lvds.h" +#include "intel_panel.h" +#include "intel_pps.h" +#include "intel_snps_phy.h" +#include "vlv_sideband.h" + +struct intel_dpll_funcs { + int (*crtc_compute_clock)(struct intel_atomic_state *state, + struct intel_crtc *crtc); + int (*crtc_get_shared_dpll)(struct intel_atomic_state *state, + struct intel_crtc *crtc); +}; + +struct intel_limit { + struct { + int min, max; + } dot, vco, n, m, m1, m2, p, p1; + + struct { + int dot_limit; + int p2_slow, p2_fast; + } p2; +}; +static const struct intel_limit intel_limits_i8xx_dac = { + .dot = { .min = 25000, .max = 350000 }, + .vco = { .min = 908000, .max = 1512000 }, + .n = { .min = 2, .max = 16 }, + .m = { .min = 96, .max = 140 }, + .m1 = { .min = 18, .max = 26 }, + .m2 = { .min = 6, .max = 16 }, + .p = { .min = 4, .max = 128 }, + .p1 = { .min = 2, .max = 33 }, + .p2 = { .dot_limit = 165000, + .p2_slow = 4, .p2_fast = 2 }, +}; + +static const struct intel_limit intel_limits_i8xx_dvo = { + .dot = { .min = 25000, .max = 350000 }, + .vco = { .min = 908000, .max = 1512000 }, + .n = { .min = 2, .max = 16 }, + .m = { .min = 96, .max = 140 }, + .m1 = { .min = 18, .max = 26 }, + .m2 = { .min = 6, .max = 16 }, + .p = { .min = 4, .max = 128 }, + .p1 = { .min = 2, .max = 33 }, + .p2 = { .dot_limit = 165000, + .p2_slow = 4, .p2_fast = 4 }, +}; + +static const struct intel_limit intel_limits_i8xx_lvds = { + .dot = { .min = 25000, .max = 350000 }, + .vco = { .min = 908000, .max = 1512000 }, + .n = { .min = 2, .max = 16 }, + .m = { .min = 96, .max = 140 }, + .m1 = { .min = 18, .max = 26 }, + .m2 = { .min = 6, .max = 16 }, + .p = { .min = 4, .max = 128 }, + .p1 = { .min = 1, .max = 6 }, + .p2 = { .dot_limit = 165000, + .p2_slow = 14, .p2_fast = 7 }, +}; + +static const struct intel_limit intel_limits_i9xx_sdvo = { + .dot = { .min = 20000, .max = 400000 }, + .vco = { .min = 1400000, .max = 2800000 }, + .n = { .min = 1, .max = 6 }, + .m = { .min = 70, .max = 120 }, + .m1 = { .min = 8, .max = 18 }, + .m2 = { .min = 3, .max = 7 }, + .p = { .min = 5, .max = 80 }, + .p1 = { .min = 1, .max = 8 }, + .p2 = { .dot_limit = 200000, + .p2_slow = 10, .p2_fast = 5 }, +}; + +static const struct intel_limit intel_limits_i9xx_lvds = { + .dot = { .min = 20000, .max = 400000 }, + .vco = { .min = 1400000, .max = 2800000 }, + .n = { .min = 1, .max = 6 }, + .m = { .min = 70, .max = 120 }, + .m1 = { .min = 8, .max = 18 }, + .m2 = { .min = 3, .max = 7 }, + .p = { .min = 7, .max = 98 }, + .p1 = { .min = 1, .max = 8 }, + .p2 = { .dot_limit = 112000, + .p2_slow = 14, .p2_fast = 7 }, +}; + + +static const struct intel_limit intel_limits_g4x_sdvo = { + .dot = { .min = 25000, .max = 270000 }, + .vco = { .min = 1750000, .max = 3500000}, + .n = { .min = 1, .max = 4 }, + .m = { .min = 104, .max = 138 }, + .m1 = { .min = 17, .max = 23 }, + .m2 = { .min = 5, .max = 11 }, + .p = { .min = 10, .max = 30 }, + .p1 = { .min = 1, .max = 3}, + .p2 = { .dot_limit = 270000, + .p2_slow = 10, + .p2_fast = 10 + }, +}; + +static const struct intel_limit intel_limits_g4x_hdmi = { + .dot = { .min = 22000, .max = 400000 }, + .vco = { .min = 1750000, .max = 3500000}, + .n = { .min = 1, .max = 4 }, + .m = { .min = 104, .max = 138 }, + .m1 = { .min = 16, .max = 23 }, + .m2 = { .min = 5, .max = 11 }, + .p = { .min = 5, .max = 80 }, + .p1 = { .min = 1, .max = 8}, + .p2 = { .dot_limit = 165000, + .p2_slow = 10, .p2_fast = 5 }, +}; + +static const struct intel_limit intel_limits_g4x_single_channel_lvds = { + .dot = { .min = 20000, .max = 115000 }, + .vco = { .min = 1750000, .max = 3500000 }, + .n = { .min = 1, .max = 3 }, + .m = { .min = 104, .max = 138 }, + .m1 = { .min = 17, .max = 23 }, + .m2 = { .min = 5, .max = 11 }, + .p = { .min = 28, .max = 112 }, + .p1 = { .min = 2, .max = 8 }, + .p2 = { .dot_limit = 0, + .p2_slow = 14, .p2_fast = 14 + }, +}; + +static const struct intel_limit intel_limits_g4x_dual_channel_lvds = { + .dot = { .min = 80000, .max = 224000 }, + .vco = { .min = 1750000, .max = 3500000 }, + .n = { .min = 1, .max = 3 }, + .m = { .min = 104, .max = 138 }, + .m1 = { .min = 17, .max = 23 }, + .m2 = { .min = 5, .max = 11 }, + .p = { .min = 14, .max = 42 }, + .p1 = { .min = 2, .max = 6 }, + .p2 = { .dot_limit = 0, + .p2_slow = 7, .p2_fast = 7 + }, +}; + +static const struct intel_limit pnv_limits_sdvo = { + .dot = { .min = 20000, .max = 400000}, + .vco = { .min = 1700000, .max = 3500000 }, + /* Pineview's Ncounter is a ring counter */ + .n = { .min = 3, .max = 6 }, + .m = { .min = 2, .max = 256 }, + /* Pineview only has one combined m divider, which we treat as m2. */ + .m1 = { .min = 0, .max = 0 }, + .m2 = { .min = 0, .max = 254 }, + .p = { .min = 5, .max = 80 }, + .p1 = { .min = 1, .max = 8 }, + .p2 = { .dot_limit = 200000, + .p2_slow = 10, .p2_fast = 5 }, +}; + +static const struct intel_limit pnv_limits_lvds = { + .dot = { .min = 20000, .max = 400000 }, + .vco = { .min = 1700000, .max = 3500000 }, + .n = { .min = 3, .max = 6 }, + .m = { .min = 2, .max = 256 }, + .m1 = { .min = 0, .max = 0 }, + .m2 = { .min = 0, .max = 254 }, + .p = { .min = 7, .max = 112 }, + .p1 = { .min = 1, .max = 8 }, + .p2 = { .dot_limit = 112000, + .p2_slow = 14, .p2_fast = 14 }, +}; + +/* Ironlake / Sandybridge + * + * We calculate clock using (register_value + 2) for N/M1/M2, so here + * the range value for them is (actual_value - 2). + */ +static const struct intel_limit ilk_limits_dac = { + .dot = { .min = 25000, .max = 350000 }, + .vco = { .min = 1760000, .max = 3510000 }, + .n = { .min = 1, .max = 5 }, + .m = { .min = 79, .max = 127 }, + .m1 = { .min = 12, .max = 22 }, + .m2 = { .min = 5, .max = 9 }, + .p = { .min = 5, .max = 80 }, + .p1 = { .min = 1, .max = 8 }, + .p2 = { .dot_limit = 225000, + .p2_slow = 10, .p2_fast = 5 }, +}; + +static const struct intel_limit ilk_limits_single_lvds = { + .dot = { .min = 25000, .max = 350000 }, + .vco = { .min = 1760000, .max = 3510000 }, + .n = { .min = 1, .max = 3 }, + .m = { .min = 79, .max = 118 }, + .m1 = { .min = 12, .max = 22 }, + .m2 = { .min = 5, .max = 9 }, + .p = { .min = 28, .max = 112 }, + .p1 = { .min = 2, .max = 8 }, + .p2 = { .dot_limit = 225000, + .p2_slow = 14, .p2_fast = 14 }, +}; + +static const struct intel_limit ilk_limits_dual_lvds = { + .dot = { .min = 25000, .max = 350000 }, + .vco = { .min = 1760000, .max = 3510000 }, + .n = { .min = 1, .max = 3 }, + .m = { .min = 79, .max = 127 }, + .m1 = { .min = 12, .max = 22 }, + .m2 = { .min = 5, .max = 9 }, + .p = { .min = 14, .max = 56 }, + .p1 = { .min = 2, .max = 8 }, + .p2 = { .dot_limit = 225000, + .p2_slow = 7, .p2_fast = 7 }, +}; + +/* LVDS 100mhz refclk limits. */ +static const struct intel_limit ilk_limits_single_lvds_100m = { + .dot = { .min = 25000, .max = 350000 }, + .vco = { .min = 1760000, .max = 3510000 }, + .n = { .min = 1, .max = 2 }, + .m = { .min = 79, .max = 126 }, + .m1 = { .min = 12, .max = 22 }, + .m2 = { .min = 5, .max = 9 }, + .p = { .min = 28, .max = 112 }, + .p1 = { .min = 2, .max = 8 }, + .p2 = { .dot_limit = 225000, + .p2_slow = 14, .p2_fast = 14 }, +}; + +static const struct intel_limit ilk_limits_dual_lvds_100m = { + .dot = { .min = 25000, .max = 350000 }, + .vco = { .min = 1760000, .max = 3510000 }, + .n = { .min = 1, .max = 3 }, + .m = { .min = 79, .max = 126 }, + .m1 = { .min = 12, .max = 22 }, + .m2 = { .min = 5, .max = 9 }, + .p = { .min = 14, .max = 42 }, + .p1 = { .min = 2, .max = 6 }, + .p2 = { .dot_limit = 225000, + .p2_slow = 7, .p2_fast = 7 }, +}; + +static const struct intel_limit intel_limits_vlv = { + /* + * These are based on the data rate limits (measured in fast clocks) + * since those are the strictest limits we have. The fast + * clock and actual rate limits are more relaxed, so checking + * them would make no difference. + */ + .dot = { .min = 25000, .max = 270000 }, + .vco = { .min = 4000000, .max = 6000000 }, + .n = { .min = 1, .max = 7 }, + .m1 = { .min = 2, .max = 3 }, + .m2 = { .min = 11, .max = 156 }, + .p1 = { .min = 2, .max = 3 }, + .p2 = { .p2_slow = 2, .p2_fast = 20 }, /* slow=min, fast=max */ +}; + +static const struct intel_limit intel_limits_chv = { + /* + * These are based on the data rate limits (measured in fast clocks) + * since those are the strictest limits we have. The fast + * clock and actual rate limits are more relaxed, so checking + * them would make no difference. + */ + .dot = { .min = 25000, .max = 540000 }, + .vco = { .min = 4800000, .max = 6480000 }, + .n = { .min = 1, .max = 1 }, + .m1 = { .min = 2, .max = 2 }, + .m2 = { .min = 24 << 22, .max = 175 << 22 }, + .p1 = { .min = 2, .max = 4 }, + .p2 = { .p2_slow = 1, .p2_fast = 14 }, +}; + +static const struct intel_limit intel_limits_bxt = { + .dot = { .min = 25000, .max = 594000 }, + .vco = { .min = 4800000, .max = 6700000 }, + .n = { .min = 1, .max = 1 }, + .m1 = { .min = 2, .max = 2 }, + /* FIXME: find real m2 limits */ + .m2 = { .min = 2 << 22, .max = 255 << 22 }, + .p1 = { .min = 2, .max = 4 }, + .p2 = { .p2_slow = 1, .p2_fast = 20 }, +}; + +/* + * Platform specific helpers to calculate the port PLL loopback- (clock.m), + * and post-divider (clock.p) values, pre- (clock.vco) and post-divided fast + * (clock.dot) clock rates. This fast dot clock is fed to the port's IO logic. + * The helpers' return value is the rate of the clock that is fed to the + * display engine's pipe which can be the above fast dot clock rate or a + * divided-down version of it. + */ +/* m1 is reserved as 0 in Pineview, n is a ring counter */ +int pnv_calc_dpll_params(int refclk, struct dpll *clock) +{ + clock->m = clock->m2 + 2; + clock->p = clock->p1 * clock->p2; + if (WARN_ON(clock->n == 0 || clock->p == 0)) + return 0; + clock->vco = DIV_ROUND_CLOSEST(refclk * clock->m, clock->n); + clock->dot = DIV_ROUND_CLOSEST(clock->vco, clock->p); + + return clock->dot; +} + +static u32 i9xx_dpll_compute_m(const struct dpll *dpll) +{ + return 5 * (dpll->m1 + 2) + (dpll->m2 + 2); +} + +int i9xx_calc_dpll_params(int refclk, struct dpll *clock) +{ + clock->m = i9xx_dpll_compute_m(clock); + clock->p = clock->p1 * clock->p2; + if (WARN_ON(clock->n + 2 == 0 || clock->p == 0)) + return 0; + clock->vco = DIV_ROUND_CLOSEST(refclk * clock->m, clock->n + 2); + clock->dot = DIV_ROUND_CLOSEST(clock->vco, clock->p); + + return clock->dot; +} + +int vlv_calc_dpll_params(int refclk, struct dpll *clock) +{ + clock->m = clock->m1 * clock->m2; + clock->p = clock->p1 * clock->p2 * 5; + if (WARN_ON(clock->n == 0 || clock->p == 0)) + return 0; + clock->vco = DIV_ROUND_CLOSEST(refclk * clock->m, clock->n); + clock->dot = DIV_ROUND_CLOSEST(clock->vco, clock->p); + + return clock->dot; +} + +int chv_calc_dpll_params(int refclk, struct dpll *clock) +{ + clock->m = clock->m1 * clock->m2; + clock->p = clock->p1 * clock->p2 * 5; + if (WARN_ON(clock->n == 0 || clock->p == 0)) + return 0; + clock->vco = DIV_ROUND_CLOSEST_ULL(mul_u32_u32(refclk, clock->m), + clock->n << 22); + clock->dot = DIV_ROUND_CLOSEST(clock->vco, clock->p); + + return clock->dot; +} + +/* + * Returns whether the given set of divisors are valid for a given refclk with + * the given connectors. + */ +static bool intel_pll_is_valid(struct drm_i915_private *dev_priv, + const struct intel_limit *limit, + const struct dpll *clock) +{ + if (clock->n < limit->n.min || limit->n.max < clock->n) + return false; + if (clock->p1 < limit->p1.min || limit->p1.max < clock->p1) + return false; + if (clock->m2 < limit->m2.min || limit->m2.max < clock->m2) + return false; + if (clock->m1 < limit->m1.min || limit->m1.max < clock->m1) + return false; + + if (!IS_PINEVIEW(dev_priv) && !IS_LP(dev_priv)) + if (clock->m1 <= clock->m2) + return false; + + if (!IS_LP(dev_priv)) { + if (clock->p < limit->p.min || limit->p.max < clock->p) + return false; + if (clock->m < limit->m.min || limit->m.max < clock->m) + return false; + } + + if (clock->vco < limit->vco.min || limit->vco.max < clock->vco) + return false; + /* XXX: We may need to be checking "Dot clock" depending on the multiplier, + * connector, etc., rather than just a single range. + */ + if (clock->dot < limit->dot.min || limit->dot.max < clock->dot) + return false; + + return true; +} + +static int +i9xx_select_p2_div(const struct intel_limit *limit, + const struct intel_crtc_state *crtc_state, + int target) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS)) { + /* + * For LVDS just rely on its current settings for dual-channel. + * We haven't figured out how to reliably set up different + * single/dual channel state, if we even can. + */ + if (intel_is_dual_link_lvds(dev_priv)) + return limit->p2.p2_fast; + else + return limit->p2.p2_slow; + } else { + if (target < limit->p2.dot_limit) + return limit->p2.p2_slow; + else + return limit->p2.p2_fast; + } +} + +/* + * Returns a set of divisors for the desired target clock with the given + * refclk, or FALSE. + * + * Target and reference clocks are specified in kHz. + * + * If match_clock is provided, then best_clock P divider must match the P + * divider from @match_clock used for LVDS downclocking. + */ +static bool +i9xx_find_best_dpll(const struct intel_limit *limit, + struct intel_crtc_state *crtc_state, + int target, int refclk, + const struct dpll *match_clock, + struct dpll *best_clock) +{ + struct drm_device *dev = crtc_state->uapi.crtc->dev; + struct dpll clock; + int err = target; + + memset(best_clock, 0, sizeof(*best_clock)); + + clock.p2 = i9xx_select_p2_div(limit, crtc_state, target); + + for (clock.m1 = limit->m1.min; clock.m1 <= limit->m1.max; + clock.m1++) { + for (clock.m2 = limit->m2.min; + clock.m2 <= limit->m2.max; clock.m2++) { + if (clock.m2 >= clock.m1) + break; + for (clock.n = limit->n.min; + clock.n <= limit->n.max; clock.n++) { + for (clock.p1 = limit->p1.min; + clock.p1 <= limit->p1.max; clock.p1++) { + int this_err; + + i9xx_calc_dpll_params(refclk, &clock); + if (!intel_pll_is_valid(to_i915(dev), + limit, + &clock)) + continue; + if (match_clock && + clock.p != match_clock->p) + continue; + + this_err = abs(clock.dot - target); + if (this_err < err) { + *best_clock = clock; + err = this_err; + } + } + } + } + } + + return (err != target); +} + +/* + * Returns a set of divisors for the desired target clock with the given + * refclk, or FALSE. + * + * Target and reference clocks are specified in kHz. + * + * If match_clock is provided, then best_clock P divider must match the P + * divider from @match_clock used for LVDS downclocking. + */ +static bool +pnv_find_best_dpll(const struct intel_limit *limit, + struct intel_crtc_state *crtc_state, + int target, int refclk, + const struct dpll *match_clock, + struct dpll *best_clock) +{ + struct drm_device *dev = crtc_state->uapi.crtc->dev; + struct dpll clock; + int err = target; + + memset(best_clock, 0, sizeof(*best_clock)); + + clock.p2 = i9xx_select_p2_div(limit, crtc_state, target); + + for (clock.m1 = limit->m1.min; clock.m1 <= limit->m1.max; + clock.m1++) { + for (clock.m2 = limit->m2.min; + clock.m2 <= limit->m2.max; clock.m2++) { + for (clock.n = limit->n.min; + clock.n <= limit->n.max; clock.n++) { + for (clock.p1 = limit->p1.min; + clock.p1 <= limit->p1.max; clock.p1++) { + int this_err; + + pnv_calc_dpll_params(refclk, &clock); + if (!intel_pll_is_valid(to_i915(dev), + limit, + &clock)) + continue; + if (match_clock && + clock.p != match_clock->p) + continue; + + this_err = abs(clock.dot - target); + if (this_err < err) { + *best_clock = clock; + err = this_err; + } + } + } + } + } + + return (err != target); +} + +/* + * Returns a set of divisors for the desired target clock with the given + * refclk, or FALSE. + * + * Target and reference clocks are specified in kHz. + * + * If match_clock is provided, then best_clock P divider must match the P + * divider from @match_clock used for LVDS downclocking. + */ +static bool +g4x_find_best_dpll(const struct intel_limit *limit, + struct intel_crtc_state *crtc_state, + int target, int refclk, + const struct dpll *match_clock, + struct dpll *best_clock) +{ + struct drm_device *dev = crtc_state->uapi.crtc->dev; + struct dpll clock; + int max_n; + bool found = false; + /* approximately equals target * 0.00585 */ + int err_most = (target >> 8) + (target >> 9); + + memset(best_clock, 0, sizeof(*best_clock)); + + clock.p2 = i9xx_select_p2_div(limit, crtc_state, target); + + max_n = limit->n.max; + /* based on hardware requirement, prefer smaller n to precision */ + for (clock.n = limit->n.min; clock.n <= max_n; clock.n++) { + /* based on hardware requirement, prefere larger m1,m2 */ + for (clock.m1 = limit->m1.max; + clock.m1 >= limit->m1.min; clock.m1--) { + for (clock.m2 = limit->m2.max; + clock.m2 >= limit->m2.min; clock.m2--) { + for (clock.p1 = limit->p1.max; + clock.p1 >= limit->p1.min; clock.p1--) { + int this_err; + + i9xx_calc_dpll_params(refclk, &clock); + if (!intel_pll_is_valid(to_i915(dev), + limit, + &clock)) + continue; + + this_err = abs(clock.dot - target); + if (this_err < err_most) { + *best_clock = clock; + err_most = this_err; + max_n = clock.n; + found = true; + } + } + } + } + } + return found; +} + +/* + * Check if the calculated PLL configuration is more optimal compared to the + * best configuration and error found so far. Return the calculated error. + */ +static bool vlv_PLL_is_optimal(struct drm_device *dev, int target_freq, + const struct dpll *calculated_clock, + const struct dpll *best_clock, + unsigned int best_error_ppm, + unsigned int *error_ppm) +{ + /* + * For CHV ignore the error and consider only the P value. + * Prefer a bigger P value based on HW requirements. + */ + if (IS_CHERRYVIEW(to_i915(dev))) { + *error_ppm = 0; + + return calculated_clock->p > best_clock->p; + } + + if (drm_WARN_ON_ONCE(dev, !target_freq)) + return false; + + *error_ppm = div_u64(1000000ULL * + abs(target_freq - calculated_clock->dot), + target_freq); + /* + * Prefer a better P value over a better (smaller) error if the error + * is small. Ensure this preference for future configurations too by + * setting the error to 0. + */ + if (*error_ppm < 100 && calculated_clock->p > best_clock->p) { + *error_ppm = 0; + + return true; + } + + return *error_ppm + 10 < best_error_ppm; +} + +/* + * Returns a set of divisors for the desired target clock with the given + * refclk, or FALSE. + */ +static bool +vlv_find_best_dpll(const struct intel_limit *limit, + struct intel_crtc_state *crtc_state, + int target, int refclk, + const struct dpll *match_clock, + struct dpll *best_clock) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_device *dev = crtc->base.dev; + struct dpll clock; + unsigned int bestppm = 1000000; + /* min update 19.2 MHz */ + int max_n = min(limit->n.max, refclk / 19200); + bool found = false; + + memset(best_clock, 0, sizeof(*best_clock)); + + /* based on hardware requirement, prefer smaller n to precision */ + for (clock.n = limit->n.min; clock.n <= max_n; clock.n++) { + for (clock.p1 = limit->p1.max; clock.p1 >= limit->p1.min; clock.p1--) { + for (clock.p2 = limit->p2.p2_fast; clock.p2 >= limit->p2.p2_slow; + clock.p2 -= clock.p2 > 10 ? 2 : 1) { + clock.p = clock.p1 * clock.p2 * 5; + /* based on hardware requirement, prefer bigger m1,m2 values */ + for (clock.m1 = limit->m1.min; clock.m1 <= limit->m1.max; clock.m1++) { + unsigned int ppm; + + clock.m2 = DIV_ROUND_CLOSEST(target * clock.p * clock.n, + refclk * clock.m1); + + vlv_calc_dpll_params(refclk, &clock); + + if (!intel_pll_is_valid(to_i915(dev), + limit, + &clock)) + continue; + + if (!vlv_PLL_is_optimal(dev, target, + &clock, + best_clock, + bestppm, &ppm)) + continue; + + *best_clock = clock; + bestppm = ppm; + found = true; + } + } + } + } + + return found; +} + +/* + * Returns a set of divisors for the desired target clock with the given + * refclk, or FALSE. + */ +static bool +chv_find_best_dpll(const struct intel_limit *limit, + struct intel_crtc_state *crtc_state, + int target, int refclk, + const struct dpll *match_clock, + struct dpll *best_clock) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_device *dev = crtc->base.dev; + unsigned int best_error_ppm; + struct dpll clock; + u64 m2; + int found = false; + + memset(best_clock, 0, sizeof(*best_clock)); + best_error_ppm = 1000000; + + /* + * Based on hardware doc, the n always set to 1, and m1 always + * set to 2. If requires to support 200Mhz refclk, we need to + * revisit this because n may not 1 anymore. + */ + clock.n = 1; + clock.m1 = 2; + + for (clock.p1 = limit->p1.max; clock.p1 >= limit->p1.min; clock.p1--) { + for (clock.p2 = limit->p2.p2_fast; + clock.p2 >= limit->p2.p2_slow; + clock.p2 -= clock.p2 > 10 ? 2 : 1) { + unsigned int error_ppm; + + clock.p = clock.p1 * clock.p2 * 5; + + m2 = DIV_ROUND_CLOSEST_ULL(mul_u32_u32(target, clock.p * clock.n) << 22, + refclk * clock.m1); + + if (m2 > INT_MAX/clock.m1) + continue; + + clock.m2 = m2; + + chv_calc_dpll_params(refclk, &clock); + + if (!intel_pll_is_valid(to_i915(dev), limit, &clock)) + continue; + + if (!vlv_PLL_is_optimal(dev, target, &clock, best_clock, + best_error_ppm, &error_ppm)) + continue; + + *best_clock = clock; + best_error_ppm = error_ppm; + found = true; + } + } + + return found; +} + +bool bxt_find_best_dpll(struct intel_crtc_state *crtc_state, + struct dpll *best_clock) +{ + const struct intel_limit *limit = &intel_limits_bxt; + int refclk = 100000; + + return chv_find_best_dpll(limit, crtc_state, + crtc_state->port_clock, refclk, + NULL, best_clock); +} + +u32 i9xx_dpll_compute_fp(const struct dpll *dpll) +{ + return dpll->n << 16 | dpll->m1 << 8 | dpll->m2; +} + +static u32 pnv_dpll_compute_fp(const struct dpll *dpll) +{ + return (1 << dpll->n) << 16 | dpll->m2; +} + +static void i9xx_update_pll_dividers(struct intel_crtc_state *crtc_state, + const struct dpll *clock, + const struct dpll *reduced_clock) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + u32 fp, fp2; + + if (IS_PINEVIEW(dev_priv)) { + fp = pnv_dpll_compute_fp(clock); + fp2 = pnv_dpll_compute_fp(reduced_clock); + } else { + fp = i9xx_dpll_compute_fp(clock); + fp2 = i9xx_dpll_compute_fp(reduced_clock); + } + + crtc_state->dpll_hw_state.fp0 = fp; + crtc_state->dpll_hw_state.fp1 = fp2; +} + +static void i9xx_compute_dpll(struct intel_crtc_state *crtc_state, + const struct dpll *clock, + const struct dpll *reduced_clock) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + u32 dpll; + + i9xx_update_pll_dividers(crtc_state, clock, reduced_clock); + + dpll = DPLL_VGA_MODE_DIS; + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS)) + dpll |= DPLLB_MODE_LVDS; + else + dpll |= DPLLB_MODE_DAC_SERIAL; + + if (IS_I945G(dev_priv) || IS_I945GM(dev_priv) || + IS_G33(dev_priv) || IS_PINEVIEW(dev_priv)) { + dpll |= (crtc_state->pixel_multiplier - 1) + << SDVO_MULTIPLIER_SHIFT_HIRES; + } + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_SDVO) || + intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + dpll |= DPLL_SDVO_HIGH_SPEED; + + if (intel_crtc_has_dp_encoder(crtc_state)) + dpll |= DPLL_SDVO_HIGH_SPEED; + + /* compute bitmask from p1 value */ + if (IS_G4X(dev_priv)) { + dpll |= (1 << (clock->p1 - 1)) << DPLL_FPA01_P1_POST_DIV_SHIFT; + dpll |= (1 << (reduced_clock->p1 - 1)) << DPLL_FPA1_P1_POST_DIV_SHIFT; + } else if (IS_PINEVIEW(dev_priv)) { + dpll |= (1 << (clock->p1 - 1)) << DPLL_FPA01_P1_POST_DIV_SHIFT_PINEVIEW; + WARN_ON(reduced_clock->p1 != clock->p1); + } else { + dpll |= (1 << (clock->p1 - 1)) << DPLL_FPA01_P1_POST_DIV_SHIFT; + WARN_ON(reduced_clock->p1 != clock->p1); + } + + switch (clock->p2) { + case 5: + dpll |= DPLL_DAC_SERIAL_P2_CLOCK_DIV_5; + break; + case 7: + dpll |= DPLLB_LVDS_P2_CLOCK_DIV_7; + break; + case 10: + dpll |= DPLL_DAC_SERIAL_P2_CLOCK_DIV_10; + break; + case 14: + dpll |= DPLLB_LVDS_P2_CLOCK_DIV_14; + break; + } + WARN_ON(reduced_clock->p2 != clock->p2); + + if (DISPLAY_VER(dev_priv) >= 4) + dpll |= (6 << PLL_LOAD_PULSE_PHASE_SHIFT); + + if (crtc_state->sdvo_tv_clock) + dpll |= PLL_REF_INPUT_TVCLKINBC; + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS) && + intel_panel_use_ssc(dev_priv)) + dpll |= PLLB_REF_INPUT_SPREADSPECTRUMIN; + else + dpll |= PLL_REF_INPUT_DREFCLK; + + dpll |= DPLL_VCO_ENABLE; + crtc_state->dpll_hw_state.dpll = dpll; + + if (DISPLAY_VER(dev_priv) >= 4) { + u32 dpll_md = (crtc_state->pixel_multiplier - 1) + << DPLL_MD_UDI_MULTIPLIER_SHIFT; + crtc_state->dpll_hw_state.dpll_md = dpll_md; + } +} + +static void i8xx_compute_dpll(struct intel_crtc_state *crtc_state, + const struct dpll *clock, + const struct dpll *reduced_clock) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + u32 dpll; + + i9xx_update_pll_dividers(crtc_state, clock, reduced_clock); + + dpll = DPLL_VGA_MODE_DIS; + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS)) { + dpll |= (1 << (clock->p1 - 1)) << DPLL_FPA01_P1_POST_DIV_SHIFT; + } else { + if (clock->p1 == 2) + dpll |= PLL_P1_DIVIDE_BY_TWO; + else + dpll |= (clock->p1 - 2) << DPLL_FPA01_P1_POST_DIV_SHIFT; + if (clock->p2 == 4) + dpll |= PLL_P2_DIVIDE_BY_4; + } + WARN_ON(reduced_clock->p1 != clock->p1); + WARN_ON(reduced_clock->p2 != clock->p2); + + /* + * Bspec: + * "[Almador Errata}: For the correct operation of the muxed DVO pins + * (GDEVSELB/I2Cdata, GIRDBY/I2CClk) and (GFRAMEB/DVI_Data, + * GTRDYB/DVI_Clk): Bit 31 (DPLL VCO Enable) and Bit 30 (2X Clock + * Enable) must be set to “1” in both the DPLL A Control Register + * (06014h-06017h) and DPLL B Control Register (06018h-0601Bh)." + * + * For simplicity We simply keep both bits always enabled in + * both DPLLS. The spec says we should disable the DVO 2X clock + * when not needed, but this seems to work fine in practice. + */ + if (IS_I830(dev_priv) || + intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DVO)) + dpll |= DPLL_DVO_2X_MODE; + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS) && + intel_panel_use_ssc(dev_priv)) + dpll |= PLLB_REF_INPUT_SPREADSPECTRUMIN; + else + dpll |= PLL_REF_INPUT_DREFCLK; + + dpll |= DPLL_VCO_ENABLE; + crtc_state->dpll_hw_state.dpll = dpll; +} + +static int hsw_crtc_compute_clock(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct intel_encoder *encoder = + intel_get_crtc_new_encoder(state, crtc_state); + int ret; + + if (DISPLAY_VER(dev_priv) < 11 && + intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DSI)) + return 0; + + ret = intel_compute_shared_dplls(state, crtc, encoder); + if (ret) + return ret; + + /* FIXME this is a mess */ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DSI)) + return 0; + + /* CRT dotclock is determined via other means */ + if (!crtc_state->has_pch_encoder) + crtc_state->hw.adjusted_mode.crtc_clock = intel_crtc_dotclock(crtc_state); + + return 0; +} + +static int hsw_crtc_get_shared_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct intel_encoder *encoder = + intel_get_crtc_new_encoder(state, crtc_state); + + if (DISPLAY_VER(dev_priv) < 11 && + intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DSI)) + return 0; + + return intel_reserve_shared_dplls(state, crtc, encoder); +} + +static int dg2_crtc_compute_clock(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct intel_encoder *encoder = + intel_get_crtc_new_encoder(state, crtc_state); + int ret; + + ret = intel_mpllb_calc_state(crtc_state, encoder); + if (ret) + return ret; + + crtc_state->hw.adjusted_mode.crtc_clock = intel_crtc_dotclock(crtc_state); + + return 0; +} + +static bool ilk_needs_fb_cb_tune(const struct dpll *dpll, int factor) +{ + return dpll->m < factor * dpll->n; +} + +static void ilk_update_pll_dividers(struct intel_crtc_state *crtc_state, + const struct dpll *clock, + const struct dpll *reduced_clock) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + u32 fp, fp2; + int factor; + + /* Enable autotuning of the PLL clock (if permissible) */ + factor = 21; + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS)) { + if ((intel_panel_use_ssc(dev_priv) && + dev_priv->display.vbt.lvds_ssc_freq == 100000) || + (HAS_PCH_IBX(dev_priv) && + intel_is_dual_link_lvds(dev_priv))) + factor = 25; + } else if (crtc_state->sdvo_tv_clock) { + factor = 20; + } + + fp = i9xx_dpll_compute_fp(clock); + if (ilk_needs_fb_cb_tune(clock, factor)) + fp |= FP_CB_TUNE; + + fp2 = i9xx_dpll_compute_fp(reduced_clock); + if (ilk_needs_fb_cb_tune(reduced_clock, factor)) + fp2 |= FP_CB_TUNE; + + crtc_state->dpll_hw_state.fp0 = fp; + crtc_state->dpll_hw_state.fp1 = fp2; +} + +static void ilk_compute_dpll(struct intel_crtc_state *crtc_state, + const struct dpll *clock, + const struct dpll *reduced_clock) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + u32 dpll; + + ilk_update_pll_dividers(crtc_state, clock, reduced_clock); + + dpll = 0; + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS)) + dpll |= DPLLB_MODE_LVDS; + else + dpll |= DPLLB_MODE_DAC_SERIAL; + + dpll |= (crtc_state->pixel_multiplier - 1) + << PLL_REF_SDVO_HDMI_MULTIPLIER_SHIFT; + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_SDVO) || + intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + dpll |= DPLL_SDVO_HIGH_SPEED; + + if (intel_crtc_has_dp_encoder(crtc_state)) + dpll |= DPLL_SDVO_HIGH_SPEED; + + /* + * The high speed IO clock is only really required for + * SDVO/HDMI/DP, but we also enable it for CRT to make it + * possible to share the DPLL between CRT and HDMI. Enabling + * the clock needlessly does no real harm, except use up a + * bit of power potentially. + * + * We'll limit this to IVB with 3 pipes, since it has only two + * DPLLs and so DPLL sharing is the only way to get three pipes + * driving PCH ports at the same time. On SNB we could do this, + * and potentially avoid enabling the second DPLL, but it's not + * clear if it''s a win or loss power wise. No point in doing + * this on ILK at all since it has a fixed DPLL<->pipe mapping. + */ + if (INTEL_NUM_PIPES(dev_priv) == 3 && + intel_crtc_has_type(crtc_state, INTEL_OUTPUT_ANALOG)) + dpll |= DPLL_SDVO_HIGH_SPEED; + + /* compute bitmask from p1 value */ + dpll |= (1 << (clock->p1 - 1)) << DPLL_FPA01_P1_POST_DIV_SHIFT; + /* also FPA1 */ + dpll |= (1 << (reduced_clock->p1 - 1)) << DPLL_FPA1_P1_POST_DIV_SHIFT; + + switch (clock->p2) { + case 5: + dpll |= DPLL_DAC_SERIAL_P2_CLOCK_DIV_5; + break; + case 7: + dpll |= DPLLB_LVDS_P2_CLOCK_DIV_7; + break; + case 10: + dpll |= DPLL_DAC_SERIAL_P2_CLOCK_DIV_10; + break; + case 14: + dpll |= DPLLB_LVDS_P2_CLOCK_DIV_14; + break; + } + WARN_ON(reduced_clock->p2 != clock->p2); + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS) && + intel_panel_use_ssc(dev_priv)) + dpll |= PLLB_REF_INPUT_SPREADSPECTRUMIN; + else + dpll |= PLL_REF_INPUT_DREFCLK; + + dpll |= DPLL_VCO_ENABLE; + + crtc_state->dpll_hw_state.dpll = dpll; +} + +static int ilk_crtc_compute_clock(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + const struct intel_limit *limit; + int refclk = 120000; + int ret; + + /* CPU eDP is the only output that doesn't need a PCH PLL of its own. */ + if (!crtc_state->has_pch_encoder) + return 0; + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS)) { + if (intel_panel_use_ssc(dev_priv)) { + drm_dbg_kms(&dev_priv->drm, + "using SSC reference clock of %d kHz\n", + dev_priv->display.vbt.lvds_ssc_freq); + refclk = dev_priv->display.vbt.lvds_ssc_freq; + } + + if (intel_is_dual_link_lvds(dev_priv)) { + if (refclk == 100000) + limit = &ilk_limits_dual_lvds_100m; + else + limit = &ilk_limits_dual_lvds; + } else { + if (refclk == 100000) + limit = &ilk_limits_single_lvds_100m; + else + limit = &ilk_limits_single_lvds; + } + } else { + limit = &ilk_limits_dac; + } + + if (!crtc_state->clock_set && + !g4x_find_best_dpll(limit, crtc_state, crtc_state->port_clock, + refclk, NULL, &crtc_state->dpll)) + return -EINVAL; + + ilk_compute_dpll(crtc_state, &crtc_state->dpll, + &crtc_state->dpll); + + ret = intel_compute_shared_dplls(state, crtc, NULL); + if (ret) + return ret; + + crtc_state->port_clock = crtc_state->dpll.dot; + crtc_state->hw.adjusted_mode.crtc_clock = intel_crtc_dotclock(crtc_state); + + return ret; +} + +static int ilk_crtc_get_shared_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + /* CPU eDP is the only output that doesn't need a PCH PLL of its own. */ + if (!crtc_state->has_pch_encoder) + return 0; + + return intel_reserve_shared_dplls(state, crtc, NULL); +} + +void vlv_compute_dpll(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + crtc_state->dpll_hw_state.dpll = DPLL_INTEGRATED_REF_CLK_VLV | + DPLL_REF_CLK_ENABLE_VLV | DPLL_VGA_MODE_DIS; + if (crtc->pipe != PIPE_A) + crtc_state->dpll_hw_state.dpll |= DPLL_INTEGRATED_CRI_CLK_VLV; + + /* DPLL not used with DSI, but still need the rest set up */ + if (!intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DSI)) + crtc_state->dpll_hw_state.dpll |= DPLL_VCO_ENABLE | + DPLL_EXT_BUFFER_ENABLE_VLV; + + crtc_state->dpll_hw_state.dpll_md = + (crtc_state->pixel_multiplier - 1) << DPLL_MD_UDI_MULTIPLIER_SHIFT; +} + +void chv_compute_dpll(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + crtc_state->dpll_hw_state.dpll = DPLL_SSC_REF_CLK_CHV | + DPLL_REF_CLK_ENABLE_VLV | DPLL_VGA_MODE_DIS; + if (crtc->pipe != PIPE_A) + crtc_state->dpll_hw_state.dpll |= DPLL_INTEGRATED_CRI_CLK_VLV; + + /* DPLL not used with DSI, but still need the rest set up */ + if (!intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DSI)) + crtc_state->dpll_hw_state.dpll |= DPLL_VCO_ENABLE; + + crtc_state->dpll_hw_state.dpll_md = + (crtc_state->pixel_multiplier - 1) << DPLL_MD_UDI_MULTIPLIER_SHIFT; +} + +static int chv_crtc_compute_clock(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + const struct intel_limit *limit = &intel_limits_chv; + int refclk = 100000; + + if (!crtc_state->clock_set && + !chv_find_best_dpll(limit, crtc_state, crtc_state->port_clock, + refclk, NULL, &crtc_state->dpll)) + return -EINVAL; + + chv_compute_dpll(crtc_state); + + /* FIXME this is a mess */ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DSI)) + return 0; + + crtc_state->port_clock = crtc_state->dpll.dot; + crtc_state->hw.adjusted_mode.crtc_clock = intel_crtc_dotclock(crtc_state); + + return 0; +} + +static int vlv_crtc_compute_clock(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + const struct intel_limit *limit = &intel_limits_vlv; + int refclk = 100000; + + if (!crtc_state->clock_set && + !vlv_find_best_dpll(limit, crtc_state, crtc_state->port_clock, + refclk, NULL, &crtc_state->dpll)) { + return -EINVAL; + } + + vlv_compute_dpll(crtc_state); + + /* FIXME this is a mess */ + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DSI)) + return 0; + + crtc_state->port_clock = crtc_state->dpll.dot; + crtc_state->hw.adjusted_mode.crtc_clock = intel_crtc_dotclock(crtc_state); + + return 0; +} + +static int g4x_crtc_compute_clock(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + const struct intel_limit *limit; + int refclk = 96000; + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS)) { + if (intel_panel_use_ssc(dev_priv)) { + refclk = dev_priv->display.vbt.lvds_ssc_freq; + drm_dbg_kms(&dev_priv->drm, + "using SSC reference clock of %d kHz\n", + refclk); + } + + if (intel_is_dual_link_lvds(dev_priv)) + limit = &intel_limits_g4x_dual_channel_lvds; + else + limit = &intel_limits_g4x_single_channel_lvds; + } else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI) || + intel_crtc_has_type(crtc_state, INTEL_OUTPUT_ANALOG)) { + limit = &intel_limits_g4x_hdmi; + } else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_SDVO)) { + limit = &intel_limits_g4x_sdvo; + } else { + /* The option is for other outputs */ + limit = &intel_limits_i9xx_sdvo; + } + + if (!crtc_state->clock_set && + !g4x_find_best_dpll(limit, crtc_state, crtc_state->port_clock, + refclk, NULL, &crtc_state->dpll)) + return -EINVAL; + + i9xx_compute_dpll(crtc_state, &crtc_state->dpll, + &crtc_state->dpll); + + crtc_state->port_clock = crtc_state->dpll.dot; + /* FIXME this is a mess */ + if (!intel_crtc_has_type(crtc_state, INTEL_OUTPUT_TVOUT)) + crtc_state->hw.adjusted_mode.crtc_clock = intel_crtc_dotclock(crtc_state); + + return 0; +} + +static int pnv_crtc_compute_clock(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + const struct intel_limit *limit; + int refclk = 96000; + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS)) { + if (intel_panel_use_ssc(dev_priv)) { + refclk = dev_priv->display.vbt.lvds_ssc_freq; + drm_dbg_kms(&dev_priv->drm, + "using SSC reference clock of %d kHz\n", + refclk); + } + + limit = &pnv_limits_lvds; + } else { + limit = &pnv_limits_sdvo; + } + + if (!crtc_state->clock_set && + !pnv_find_best_dpll(limit, crtc_state, crtc_state->port_clock, + refclk, NULL, &crtc_state->dpll)) + return -EINVAL; + + i9xx_compute_dpll(crtc_state, &crtc_state->dpll, + &crtc_state->dpll); + + crtc_state->port_clock = crtc_state->dpll.dot; + crtc_state->hw.adjusted_mode.crtc_clock = intel_crtc_dotclock(crtc_state); + + return 0; +} + +static int i9xx_crtc_compute_clock(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + const struct intel_limit *limit; + int refclk = 96000; + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS)) { + if (intel_panel_use_ssc(dev_priv)) { + refclk = dev_priv->display.vbt.lvds_ssc_freq; + drm_dbg_kms(&dev_priv->drm, + "using SSC reference clock of %d kHz\n", + refclk); + } + + limit = &intel_limits_i9xx_lvds; + } else { + limit = &intel_limits_i9xx_sdvo; + } + + if (!crtc_state->clock_set && + !i9xx_find_best_dpll(limit, crtc_state, crtc_state->port_clock, + refclk, NULL, &crtc_state->dpll)) + return -EINVAL; + + i9xx_compute_dpll(crtc_state, &crtc_state->dpll, + &crtc_state->dpll); + + crtc_state->port_clock = crtc_state->dpll.dot; + /* FIXME this is a mess */ + if (!intel_crtc_has_type(crtc_state, INTEL_OUTPUT_TVOUT)) + crtc_state->hw.adjusted_mode.crtc_clock = intel_crtc_dotclock(crtc_state); + + return 0; +} + +static int i8xx_crtc_compute_clock(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + const struct intel_limit *limit; + int refclk = 48000; + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS)) { + if (intel_panel_use_ssc(dev_priv)) { + refclk = dev_priv->display.vbt.lvds_ssc_freq; + drm_dbg_kms(&dev_priv->drm, + "using SSC reference clock of %d kHz\n", + refclk); + } + + limit = &intel_limits_i8xx_lvds; + } else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DVO)) { + limit = &intel_limits_i8xx_dvo; + } else { + limit = &intel_limits_i8xx_dac; + } + + if (!crtc_state->clock_set && + !i9xx_find_best_dpll(limit, crtc_state, crtc_state->port_clock, + refclk, NULL, &crtc_state->dpll)) + return -EINVAL; + + i8xx_compute_dpll(crtc_state, &crtc_state->dpll, + &crtc_state->dpll); + + crtc_state->port_clock = crtc_state->dpll.dot; + crtc_state->hw.adjusted_mode.crtc_clock = intel_crtc_dotclock(crtc_state); + + return 0; +} + +static const struct intel_dpll_funcs dg2_dpll_funcs = { + .crtc_compute_clock = dg2_crtc_compute_clock, +}; + +static const struct intel_dpll_funcs hsw_dpll_funcs = { + .crtc_compute_clock = hsw_crtc_compute_clock, + .crtc_get_shared_dpll = hsw_crtc_get_shared_dpll, +}; + +static const struct intel_dpll_funcs ilk_dpll_funcs = { + .crtc_compute_clock = ilk_crtc_compute_clock, + .crtc_get_shared_dpll = ilk_crtc_get_shared_dpll, +}; + +static const struct intel_dpll_funcs chv_dpll_funcs = { + .crtc_compute_clock = chv_crtc_compute_clock, +}; + +static const struct intel_dpll_funcs vlv_dpll_funcs = { + .crtc_compute_clock = vlv_crtc_compute_clock, +}; + +static const struct intel_dpll_funcs g4x_dpll_funcs = { + .crtc_compute_clock = g4x_crtc_compute_clock, +}; + +static const struct intel_dpll_funcs pnv_dpll_funcs = { + .crtc_compute_clock = pnv_crtc_compute_clock, +}; + +static const struct intel_dpll_funcs i9xx_dpll_funcs = { + .crtc_compute_clock = i9xx_crtc_compute_clock, +}; + +static const struct intel_dpll_funcs i8xx_dpll_funcs = { + .crtc_compute_clock = i8xx_crtc_compute_clock, +}; + +int intel_dpll_crtc_compute_clock(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *i915 = to_i915(state->base.dev); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + int ret; + + drm_WARN_ON(&i915->drm, !intel_crtc_needs_modeset(crtc_state)); + + memset(&crtc_state->dpll_hw_state, 0, + sizeof(crtc_state->dpll_hw_state)); + + if (!crtc_state->hw.enable) + return 0; + + ret = i915->display.funcs.dpll->crtc_compute_clock(state, crtc); + if (ret) { + drm_dbg_kms(&i915->drm, "[CRTC:%d:%s] Couldn't calculate DPLL settings\n", + crtc->base.base.id, crtc->base.name); + return ret; + } + + return 0; +} + +int intel_dpll_crtc_get_shared_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *i915 = to_i915(state->base.dev); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + int ret; + + drm_WARN_ON(&i915->drm, !intel_crtc_needs_modeset(crtc_state)); + drm_WARN_ON(&i915->drm, !crtc_state->hw.enable && crtc_state->shared_dpll); + + if (!crtc_state->hw.enable || crtc_state->shared_dpll) + return 0; + + if (!i915->display.funcs.dpll->crtc_get_shared_dpll) + return 0; + + ret = i915->display.funcs.dpll->crtc_get_shared_dpll(state, crtc); + if (ret) { + drm_dbg_kms(&i915->drm, "[CRTC:%d:%s] Couldn't get a shared DPLL\n", + crtc->base.base.id, crtc->base.name); + return ret; + } + + return 0; +} + +void +intel_dpll_init_clock_hook(struct drm_i915_private *dev_priv) +{ + if (IS_DG2(dev_priv)) + dev_priv->display.funcs.dpll = &dg2_dpll_funcs; + else if (DISPLAY_VER(dev_priv) >= 9 || HAS_DDI(dev_priv)) + dev_priv->display.funcs.dpll = &hsw_dpll_funcs; + else if (HAS_PCH_SPLIT(dev_priv)) + dev_priv->display.funcs.dpll = &ilk_dpll_funcs; + else if (IS_CHERRYVIEW(dev_priv)) + dev_priv->display.funcs.dpll = &chv_dpll_funcs; + else if (IS_VALLEYVIEW(dev_priv)) + dev_priv->display.funcs.dpll = &vlv_dpll_funcs; + else if (IS_G4X(dev_priv)) + dev_priv->display.funcs.dpll = &g4x_dpll_funcs; + else if (IS_PINEVIEW(dev_priv)) + dev_priv->display.funcs.dpll = &pnv_dpll_funcs; + else if (DISPLAY_VER(dev_priv) != 2) + dev_priv->display.funcs.dpll = &i9xx_dpll_funcs; + else + dev_priv->display.funcs.dpll = &i8xx_dpll_funcs; +} + +static bool i9xx_has_pps(struct drm_i915_private *dev_priv) +{ + if (IS_I830(dev_priv)) + return false; + + return IS_PINEVIEW(dev_priv) || IS_MOBILE(dev_priv); +} + +void i9xx_enable_pll(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + u32 dpll = crtc_state->dpll_hw_state.dpll; + enum pipe pipe = crtc->pipe; + int i; + + assert_transcoder_disabled(dev_priv, crtc_state->cpu_transcoder); + + /* PLL is protected by panel, make sure we can write it */ + if (i9xx_has_pps(dev_priv)) + assert_pps_unlocked(dev_priv, pipe); + + intel_de_write(dev_priv, FP0(pipe), crtc_state->dpll_hw_state.fp0); + intel_de_write(dev_priv, FP1(pipe), crtc_state->dpll_hw_state.fp1); + + /* + * Apparently we need to have VGA mode enabled prior to changing + * the P1/P2 dividers. Otherwise the DPLL will keep using the old + * dividers, even though the register value does change. + */ + intel_de_write(dev_priv, DPLL(pipe), dpll & ~DPLL_VGA_MODE_DIS); + intel_de_write(dev_priv, DPLL(pipe), dpll); + + /* Wait for the clocks to stabilize. */ + intel_de_posting_read(dev_priv, DPLL(pipe)); + udelay(150); + + if (DISPLAY_VER(dev_priv) >= 4) { + intel_de_write(dev_priv, DPLL_MD(pipe), + crtc_state->dpll_hw_state.dpll_md); + } else { + /* The pixel multiplier can only be updated once the + * DPLL is enabled and the clocks are stable. + * + * So write it again. + */ + intel_de_write(dev_priv, DPLL(pipe), dpll); + } + + /* We do this three times for luck */ + for (i = 0; i < 3; i++) { + intel_de_write(dev_priv, DPLL(pipe), dpll); + intel_de_posting_read(dev_priv, DPLL(pipe)); + udelay(150); /* wait for warmup */ + } +} + +static void vlv_pllb_recal_opamp(struct drm_i915_private *dev_priv, + enum pipe pipe) +{ + u32 reg_val; + + /* + * PLLB opamp always calibrates to max value of 0x3f, force enable it + * and set it to a reasonable value instead. + */ + reg_val = vlv_dpio_read(dev_priv, pipe, VLV_PLL_DW9(1)); + reg_val &= 0xffffff00; + reg_val |= 0x00000030; + vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW9(1), reg_val); + + reg_val = vlv_dpio_read(dev_priv, pipe, VLV_REF_DW13); + reg_val &= 0x00ffffff; + reg_val |= 0x8c000000; + vlv_dpio_write(dev_priv, pipe, VLV_REF_DW13, reg_val); + + reg_val = vlv_dpio_read(dev_priv, pipe, VLV_PLL_DW9(1)); + reg_val &= 0xffffff00; + vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW9(1), reg_val); + + reg_val = vlv_dpio_read(dev_priv, pipe, VLV_REF_DW13); + reg_val &= 0x00ffffff; + reg_val |= 0xb0000000; + vlv_dpio_write(dev_priv, pipe, VLV_REF_DW13, reg_val); +} + +static void vlv_prepare_pll(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + u32 mdiv; + u32 bestn, bestm1, bestm2, bestp1, bestp2; + u32 coreclk, reg_val; + + vlv_dpio_get(dev_priv); + + bestn = crtc_state->dpll.n; + bestm1 = crtc_state->dpll.m1; + bestm2 = crtc_state->dpll.m2; + bestp1 = crtc_state->dpll.p1; + bestp2 = crtc_state->dpll.p2; + + /* See eDP HDMI DPIO driver vbios notes doc */ + + /* PLL B needs special handling */ + if (pipe == PIPE_B) + vlv_pllb_recal_opamp(dev_priv, pipe); + + /* Set up Tx target for periodic Rcomp update */ + vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW9_BCAST, 0x0100000f); + + /* Disable target IRef on PLL */ + reg_val = vlv_dpio_read(dev_priv, pipe, VLV_PLL_DW8(pipe)); + reg_val &= 0x00ffffff; + vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW8(pipe), reg_val); + + /* Disable fast lock */ + vlv_dpio_write(dev_priv, pipe, VLV_CMN_DW0, 0x610); + + /* Set idtafcrecal before PLL is enabled */ + mdiv = ((bestm1 << DPIO_M1DIV_SHIFT) | (bestm2 & DPIO_M2DIV_MASK)); + mdiv |= ((bestp1 << DPIO_P1_SHIFT) | (bestp2 << DPIO_P2_SHIFT)); + mdiv |= ((bestn << DPIO_N_SHIFT)); + mdiv |= (1 << DPIO_K_SHIFT); + + /* + * Post divider depends on pixel clock rate, DAC vs digital (and LVDS, + * but we don't support that). + * Note: don't use the DAC post divider as it seems unstable. + */ + mdiv |= (DPIO_POST_DIV_HDMIDP << DPIO_POST_DIV_SHIFT); + vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW3(pipe), mdiv); + + mdiv |= DPIO_ENABLE_CALIBRATION; + vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW3(pipe), mdiv); + + /* Set HBR and RBR LPF coefficients */ + if (crtc_state->port_clock == 162000 || + intel_crtc_has_type(crtc_state, INTEL_OUTPUT_ANALOG) || + intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW10(pipe), + 0x009f0003); + else + vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW10(pipe), + 0x00d0000f); + + if (intel_crtc_has_dp_encoder(crtc_state)) { + /* Use SSC source */ + if (pipe == PIPE_A) + vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW5(pipe), + 0x0df40000); + else + vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW5(pipe), + 0x0df70000); + } else { /* HDMI or VGA */ + /* Use bend source */ + if (pipe == PIPE_A) + vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW5(pipe), + 0x0df70000); + else + vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW5(pipe), + 0x0df40000); + } + + coreclk = vlv_dpio_read(dev_priv, pipe, VLV_PLL_DW7(pipe)); + coreclk = (coreclk & 0x0000ff00) | 0x01c00000; + if (intel_crtc_has_dp_encoder(crtc_state)) + coreclk |= 0x01000000; + vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW7(pipe), coreclk); + + vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW11(pipe), 0x87871000); + + vlv_dpio_put(dev_priv); +} + +static void _vlv_enable_pll(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + + intel_de_write(dev_priv, DPLL(pipe), crtc_state->dpll_hw_state.dpll); + intel_de_posting_read(dev_priv, DPLL(pipe)); + udelay(150); + + if (intel_de_wait_for_set(dev_priv, DPLL(pipe), DPLL_LOCK_VLV, 1)) + drm_err(&dev_priv->drm, "DPLL %d failed to lock\n", pipe); +} + +void vlv_enable_pll(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + + assert_transcoder_disabled(dev_priv, crtc_state->cpu_transcoder); + + /* PLL is protected by panel, make sure we can write it */ + assert_pps_unlocked(dev_priv, pipe); + + /* Enable Refclk */ + intel_de_write(dev_priv, DPLL(pipe), + crtc_state->dpll_hw_state.dpll & + ~(DPLL_VCO_ENABLE | DPLL_EXT_BUFFER_ENABLE_VLV)); + + if (crtc_state->dpll_hw_state.dpll & DPLL_VCO_ENABLE) { + vlv_prepare_pll(crtc_state); + _vlv_enable_pll(crtc_state); + } + + intel_de_write(dev_priv, DPLL_MD(pipe), + crtc_state->dpll_hw_state.dpll_md); + intel_de_posting_read(dev_priv, DPLL_MD(pipe)); +} + +static void chv_prepare_pll(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + enum dpio_channel port = vlv_pipe_to_channel(pipe); + u32 loopfilter, tribuf_calcntr; + u32 bestn, bestm1, bestm2, bestp1, bestp2, bestm2_frac; + u32 dpio_val; + int vco; + + bestn = crtc_state->dpll.n; + bestm2_frac = crtc_state->dpll.m2 & 0x3fffff; + bestm1 = crtc_state->dpll.m1; + bestm2 = crtc_state->dpll.m2 >> 22; + bestp1 = crtc_state->dpll.p1; + bestp2 = crtc_state->dpll.p2; + vco = crtc_state->dpll.vco; + dpio_val = 0; + loopfilter = 0; + + vlv_dpio_get(dev_priv); + + /* p1 and p2 divider */ + vlv_dpio_write(dev_priv, pipe, CHV_CMN_DW13(port), + 5 << DPIO_CHV_S1_DIV_SHIFT | + bestp1 << DPIO_CHV_P1_DIV_SHIFT | + bestp2 << DPIO_CHV_P2_DIV_SHIFT | + 1 << DPIO_CHV_K_DIV_SHIFT); + + /* Feedback post-divider - m2 */ + vlv_dpio_write(dev_priv, pipe, CHV_PLL_DW0(port), bestm2); + + /* Feedback refclk divider - n and m1 */ + vlv_dpio_write(dev_priv, pipe, CHV_PLL_DW1(port), + DPIO_CHV_M1_DIV_BY_2 | + 1 << DPIO_CHV_N_DIV_SHIFT); + + /* M2 fraction division */ + vlv_dpio_write(dev_priv, pipe, CHV_PLL_DW2(port), bestm2_frac); + + /* M2 fraction division enable */ + dpio_val = vlv_dpio_read(dev_priv, pipe, CHV_PLL_DW3(port)); + dpio_val &= ~(DPIO_CHV_FEEDFWD_GAIN_MASK | DPIO_CHV_FRAC_DIV_EN); + dpio_val |= (2 << DPIO_CHV_FEEDFWD_GAIN_SHIFT); + if (bestm2_frac) + dpio_val |= DPIO_CHV_FRAC_DIV_EN; + vlv_dpio_write(dev_priv, pipe, CHV_PLL_DW3(port), dpio_val); + + /* Program digital lock detect threshold */ + dpio_val = vlv_dpio_read(dev_priv, pipe, CHV_PLL_DW9(port)); + dpio_val &= ~(DPIO_CHV_INT_LOCK_THRESHOLD_MASK | + DPIO_CHV_INT_LOCK_THRESHOLD_SEL_COARSE); + dpio_val |= (0x5 << DPIO_CHV_INT_LOCK_THRESHOLD_SHIFT); + if (!bestm2_frac) + dpio_val |= DPIO_CHV_INT_LOCK_THRESHOLD_SEL_COARSE; + vlv_dpio_write(dev_priv, pipe, CHV_PLL_DW9(port), dpio_val); + + /* Loop filter */ + if (vco == 5400000) { + loopfilter |= (0x3 << DPIO_CHV_PROP_COEFF_SHIFT); + loopfilter |= (0x8 << DPIO_CHV_INT_COEFF_SHIFT); + loopfilter |= (0x1 << DPIO_CHV_GAIN_CTRL_SHIFT); + tribuf_calcntr = 0x9; + } else if (vco <= 6200000) { + loopfilter |= (0x5 << DPIO_CHV_PROP_COEFF_SHIFT); + loopfilter |= (0xB << DPIO_CHV_INT_COEFF_SHIFT); + loopfilter |= (0x3 << DPIO_CHV_GAIN_CTRL_SHIFT); + tribuf_calcntr = 0x9; + } else if (vco <= 6480000) { + loopfilter |= (0x4 << DPIO_CHV_PROP_COEFF_SHIFT); + loopfilter |= (0x9 << DPIO_CHV_INT_COEFF_SHIFT); + loopfilter |= (0x3 << DPIO_CHV_GAIN_CTRL_SHIFT); + tribuf_calcntr = 0x8; + } else { + /* Not supported. Apply the same limits as in the max case */ + loopfilter |= (0x4 << DPIO_CHV_PROP_COEFF_SHIFT); + loopfilter |= (0x9 << DPIO_CHV_INT_COEFF_SHIFT); + loopfilter |= (0x3 << DPIO_CHV_GAIN_CTRL_SHIFT); + tribuf_calcntr = 0; + } + vlv_dpio_write(dev_priv, pipe, CHV_PLL_DW6(port), loopfilter); + + dpio_val = vlv_dpio_read(dev_priv, pipe, CHV_PLL_DW8(port)); + dpio_val &= ~DPIO_CHV_TDC_TARGET_CNT_MASK; + dpio_val |= (tribuf_calcntr << DPIO_CHV_TDC_TARGET_CNT_SHIFT); + vlv_dpio_write(dev_priv, pipe, CHV_PLL_DW8(port), dpio_val); + + /* AFC Recal */ + vlv_dpio_write(dev_priv, pipe, CHV_CMN_DW14(port), + vlv_dpio_read(dev_priv, pipe, CHV_CMN_DW14(port)) | + DPIO_AFC_RECAL); + + vlv_dpio_put(dev_priv); +} + +static void _chv_enable_pll(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + enum dpio_channel port = vlv_pipe_to_channel(pipe); + u32 tmp; + + vlv_dpio_get(dev_priv); + + /* Enable back the 10bit clock to display controller */ + tmp = vlv_dpio_read(dev_priv, pipe, CHV_CMN_DW14(port)); + tmp |= DPIO_DCLKP_EN; + vlv_dpio_write(dev_priv, pipe, CHV_CMN_DW14(port), tmp); + + vlv_dpio_put(dev_priv); + + /* + * Need to wait > 100ns between dclkp clock enable bit and PLL enable. + */ + udelay(1); + + /* Enable PLL */ + intel_de_write(dev_priv, DPLL(pipe), crtc_state->dpll_hw_state.dpll); + + /* Check PLL is locked */ + if (intel_de_wait_for_set(dev_priv, DPLL(pipe), DPLL_LOCK_VLV, 1)) + drm_err(&dev_priv->drm, "PLL %d failed to lock\n", pipe); +} + +void chv_enable_pll(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + + assert_transcoder_disabled(dev_priv, crtc_state->cpu_transcoder); + + /* PLL is protected by panel, make sure we can write it */ + assert_pps_unlocked(dev_priv, pipe); + + /* Enable Refclk and SSC */ + intel_de_write(dev_priv, DPLL(pipe), + crtc_state->dpll_hw_state.dpll & ~DPLL_VCO_ENABLE); + + if (crtc_state->dpll_hw_state.dpll & DPLL_VCO_ENABLE) { + chv_prepare_pll(crtc_state); + _chv_enable_pll(crtc_state); + } + + if (pipe != PIPE_A) { + /* + * WaPixelRepeatModeFixForC0:chv + * + * DPLLCMD is AWOL. Use chicken bits to propagate + * the value from DPLLBMD to either pipe B or C. + */ + intel_de_write(dev_priv, CBR4_VLV, CBR_DPLLBMD_PIPE(pipe)); + intel_de_write(dev_priv, DPLL_MD(PIPE_B), + crtc_state->dpll_hw_state.dpll_md); + intel_de_write(dev_priv, CBR4_VLV, 0); + dev_priv->chv_dpll_md[pipe] = crtc_state->dpll_hw_state.dpll_md; + + /* + * DPLLB VGA mode also seems to cause problems. + * We should always have it disabled. + */ + drm_WARN_ON(&dev_priv->drm, + (intel_de_read(dev_priv, DPLL(PIPE_B)) & + DPLL_VGA_MODE_DIS) == 0); + } else { + intel_de_write(dev_priv, DPLL_MD(pipe), + crtc_state->dpll_hw_state.dpll_md); + intel_de_posting_read(dev_priv, DPLL_MD(pipe)); + } +} + +/** + * vlv_force_pll_on - forcibly enable just the PLL + * @dev_priv: i915 private structure + * @pipe: pipe PLL to enable + * @dpll: PLL configuration + * + * Enable the PLL for @pipe using the supplied @dpll config. To be used + * in cases where we need the PLL enabled even when @pipe is not going to + * be enabled. + */ +int vlv_force_pll_on(struct drm_i915_private *dev_priv, enum pipe pipe, + const struct dpll *dpll) +{ + struct intel_crtc *crtc = intel_crtc_for_pipe(dev_priv, pipe); + struct intel_crtc_state *crtc_state; + + crtc_state = intel_crtc_state_alloc(crtc); + if (!crtc_state) + return -ENOMEM; + + crtc_state->cpu_transcoder = (enum transcoder)pipe; + crtc_state->pixel_multiplier = 1; + crtc_state->dpll = *dpll; + crtc_state->output_types = BIT(INTEL_OUTPUT_EDP); + + if (IS_CHERRYVIEW(dev_priv)) { + chv_compute_dpll(crtc_state); + chv_enable_pll(crtc_state); + } else { + vlv_compute_dpll(crtc_state); + vlv_enable_pll(crtc_state); + } + + kfree(crtc_state); + + return 0; +} + +void vlv_disable_pll(struct drm_i915_private *dev_priv, enum pipe pipe) +{ + u32 val; + + /* Make sure the pipe isn't still relying on us */ + assert_transcoder_disabled(dev_priv, (enum transcoder)pipe); + + val = DPLL_INTEGRATED_REF_CLK_VLV | + DPLL_REF_CLK_ENABLE_VLV | DPLL_VGA_MODE_DIS; + if (pipe != PIPE_A) + val |= DPLL_INTEGRATED_CRI_CLK_VLV; + + intel_de_write(dev_priv, DPLL(pipe), val); + intel_de_posting_read(dev_priv, DPLL(pipe)); +} + +void chv_disable_pll(struct drm_i915_private *dev_priv, enum pipe pipe) +{ + enum dpio_channel port = vlv_pipe_to_channel(pipe); + u32 val; + + /* Make sure the pipe isn't still relying on us */ + assert_transcoder_disabled(dev_priv, (enum transcoder)pipe); + + val = DPLL_SSC_REF_CLK_CHV | + DPLL_REF_CLK_ENABLE_VLV | DPLL_VGA_MODE_DIS; + if (pipe != PIPE_A) + val |= DPLL_INTEGRATED_CRI_CLK_VLV; + + intel_de_write(dev_priv, DPLL(pipe), val); + intel_de_posting_read(dev_priv, DPLL(pipe)); + + vlv_dpio_get(dev_priv); + + /* Disable 10bit clock to display controller */ + val = vlv_dpio_read(dev_priv, pipe, CHV_CMN_DW14(port)); + val &= ~DPIO_DCLKP_EN; + vlv_dpio_write(dev_priv, pipe, CHV_CMN_DW14(port), val); + + vlv_dpio_put(dev_priv); +} + +void i9xx_disable_pll(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum pipe pipe = crtc->pipe; + + /* Don't disable pipe or pipe PLLs if needed */ + if (IS_I830(dev_priv)) + return; + + /* Make sure the pipe isn't still relying on us */ + assert_transcoder_disabled(dev_priv, crtc_state->cpu_transcoder); + + intel_de_write(dev_priv, DPLL(pipe), DPLL_VGA_MODE_DIS); + intel_de_posting_read(dev_priv, DPLL(pipe)); +} + + +/** + * vlv_force_pll_off - forcibly disable just the PLL + * @dev_priv: i915 private structure + * @pipe: pipe PLL to disable + * + * Disable the PLL for @pipe. To be used in cases where we need + * the PLL enabled even when @pipe is not going to be enabled. + */ +void vlv_force_pll_off(struct drm_i915_private *dev_priv, enum pipe pipe) +{ + if (IS_CHERRYVIEW(dev_priv)) + chv_disable_pll(dev_priv, pipe); + else + vlv_disable_pll(dev_priv, pipe); +} + +/* Only for pre-ILK configs */ +static void assert_pll(struct drm_i915_private *dev_priv, + enum pipe pipe, bool state) +{ + bool cur_state; + + cur_state = intel_de_read(dev_priv, DPLL(pipe)) & DPLL_VCO_ENABLE; + I915_STATE_WARN(cur_state != state, + "PLL state assertion failure (expected %s, current %s)\n", + str_on_off(state), str_on_off(cur_state)); +} + +void assert_pll_enabled(struct drm_i915_private *i915, enum pipe pipe) +{ + assert_pll(i915, pipe, true); +} + +void assert_pll_disabled(struct drm_i915_private *i915, enum pipe pipe) +{ + assert_pll(i915, pipe, false); +} diff --git a/drivers/gpu/drm/i915/display/intel_dpll.h b/drivers/gpu/drm/i915/display/intel_dpll.h new file mode 100644 index 000000000..bbc30542f --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dpll.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2020 Intel Corporation + */ + +#ifndef _INTEL_DPLL_H_ +#define _INTEL_DPLL_H_ + +#include + +struct dpll; +struct drm_i915_private; +struct intel_atomic_state; +struct intel_crtc; +struct intel_crtc_state; +enum pipe; + +void intel_dpll_init_clock_hook(struct drm_i915_private *dev_priv); +int intel_dpll_crtc_compute_clock(struct intel_atomic_state *state, + struct intel_crtc *crtc); +int intel_dpll_crtc_get_shared_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc); +int vlv_calc_dpll_params(int refclk, struct dpll *clock); +int pnv_calc_dpll_params(int refclk, struct dpll *clock); +int i9xx_calc_dpll_params(int refclk, struct dpll *clock); +u32 i9xx_dpll_compute_fp(const struct dpll *dpll); +void vlv_compute_dpll(struct intel_crtc_state *crtc_state); +void chv_compute_dpll(struct intel_crtc_state *crtc_state); + +int vlv_force_pll_on(struct drm_i915_private *dev_priv, enum pipe pipe, + const struct dpll *dpll); +void vlv_force_pll_off(struct drm_i915_private *dev_priv, enum pipe pipe); + +void chv_enable_pll(const struct intel_crtc_state *crtc_state); +void chv_disable_pll(struct drm_i915_private *dev_priv, enum pipe pipe); +void vlv_enable_pll(const struct intel_crtc_state *crtc_state); +void vlv_disable_pll(struct drm_i915_private *dev_priv, enum pipe pipe); +void i9xx_enable_pll(const struct intel_crtc_state *crtc_state); +void i9xx_disable_pll(const struct intel_crtc_state *crtc_state); +bool bxt_find_best_dpll(struct intel_crtc_state *crtc_state, + struct dpll *best_clock); +int chv_calc_dpll_params(int refclk, struct dpll *pll_clock); + +void assert_pll_enabled(struct drm_i915_private *i915, enum pipe pipe); +void assert_pll_disabled(struct drm_i915_private *i915, enum pipe pipe); + +#endif diff --git a/drivers/gpu/drm/i915/display/intel_dpll_mgr.c b/drivers/gpu/drm/i915/display/intel_dpll_mgr.c new file mode 100644 index 000000000..64dd603dc --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dpll_mgr.c @@ -0,0 +1,4564 @@ +/* + * Copyright © 2006-2016 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include + +#include "intel_de.h" +#include "intel_display_types.h" +#include "intel_dkl_phy.h" +#include "intel_dpio_phy.h" +#include "intel_dpll.h" +#include "intel_dpll_mgr.h" +#include "intel_pch_refclk.h" +#include "intel_tc.h" +#include "intel_tc_phy_regs.h" + +/** + * DOC: Display PLLs + * + * Display PLLs used for driving outputs vary by platform. While some have + * per-pipe or per-encoder dedicated PLLs, others allow the use of any PLL + * from a pool. In the latter scenario, it is possible that multiple pipes + * share a PLL if their configurations match. + * + * This file provides an abstraction over display PLLs. The function + * intel_shared_dpll_init() initializes the PLLs for the given platform. The + * users of a PLL are tracked and that tracking is integrated with the atomic + * modset interface. During an atomic operation, required PLLs can be reserved + * for a given CRTC and encoder configuration by calling + * intel_reserve_shared_dplls() and previously reserved PLLs can be released + * with intel_release_shared_dplls(). + * Changes to the users are first staged in the atomic state, and then made + * effective by calling intel_shared_dpll_swap_state() during the atomic + * commit phase. + */ + +/* platform specific hooks for managing DPLLs */ +struct intel_shared_dpll_funcs { + /* + * Hook for enabling the pll, called from intel_enable_shared_dpll() if + * the pll is not already enabled. + */ + void (*enable)(struct drm_i915_private *i915, + struct intel_shared_dpll *pll); + + /* + * Hook for disabling the pll, called from intel_disable_shared_dpll() + * only when it is safe to disable the pll, i.e., there are no more + * tracked users for it. + */ + void (*disable)(struct drm_i915_private *i915, + struct intel_shared_dpll *pll); + + /* + * Hook for reading the values currently programmed to the DPLL + * registers. This is used for initial hw state readout and state + * verification after a mode set. + */ + bool (*get_hw_state)(struct drm_i915_private *i915, + struct intel_shared_dpll *pll, + struct intel_dpll_hw_state *hw_state); + + /* + * Hook for calculating the pll's output frequency based on its passed + * in state. + */ + int (*get_freq)(struct drm_i915_private *i915, + const struct intel_shared_dpll *pll, + const struct intel_dpll_hw_state *pll_state); +}; + +struct intel_dpll_mgr { + const struct dpll_info *dpll_info; + + int (*compute_dplls)(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder); + int (*get_dplls)(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder); + void (*put_dplls)(struct intel_atomic_state *state, + struct intel_crtc *crtc); + void (*update_active_dpll)(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder); + void (*update_ref_clks)(struct drm_i915_private *i915); + void (*dump_hw_state)(struct drm_i915_private *dev_priv, + const struct intel_dpll_hw_state *hw_state); +}; + +static void +intel_atomic_duplicate_dpll_state(struct drm_i915_private *dev_priv, + struct intel_shared_dpll_state *shared_dpll) +{ + enum intel_dpll_id i; + + /* Copy shared dpll state */ + for (i = 0; i < dev_priv->display.dpll.num_shared_dpll; i++) { + struct intel_shared_dpll *pll = &dev_priv->display.dpll.shared_dplls[i]; + + shared_dpll[i] = pll->state; + } +} + +static struct intel_shared_dpll_state * +intel_atomic_get_shared_dpll_state(struct drm_atomic_state *s) +{ + struct intel_atomic_state *state = to_intel_atomic_state(s); + + drm_WARN_ON(s->dev, !drm_modeset_is_locked(&s->dev->mode_config.connection_mutex)); + + if (!state->dpll_set) { + state->dpll_set = true; + + intel_atomic_duplicate_dpll_state(to_i915(s->dev), + state->shared_dpll); + } + + return state->shared_dpll; +} + +/** + * intel_get_shared_dpll_by_id - get a DPLL given its id + * @dev_priv: i915 device instance + * @id: pll id + * + * Returns: + * A pointer to the DPLL with @id + */ +struct intel_shared_dpll * +intel_get_shared_dpll_by_id(struct drm_i915_private *dev_priv, + enum intel_dpll_id id) +{ + return &dev_priv->display.dpll.shared_dplls[id]; +} + +/** + * intel_get_shared_dpll_id - get the id of a DPLL + * @dev_priv: i915 device instance + * @pll: the DPLL + * + * Returns: + * The id of @pll + */ +enum intel_dpll_id +intel_get_shared_dpll_id(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + long pll_idx = pll - dev_priv->display.dpll.shared_dplls; + + if (drm_WARN_ON(&dev_priv->drm, + pll_idx < 0 || + pll_idx >= dev_priv->display.dpll.num_shared_dpll)) + return -1; + + return pll_idx; +} + +/* For ILK+ */ +void assert_shared_dpll(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll, + bool state) +{ + bool cur_state; + struct intel_dpll_hw_state hw_state; + + if (drm_WARN(&dev_priv->drm, !pll, + "asserting DPLL %s with no DPLL\n", str_on_off(state))) + return; + + cur_state = intel_dpll_get_hw_state(dev_priv, pll, &hw_state); + I915_STATE_WARN(cur_state != state, + "%s assertion failure (expected %s, current %s)\n", + pll->info->name, str_on_off(state), + str_on_off(cur_state)); +} + +static enum tc_port icl_pll_id_to_tc_port(enum intel_dpll_id id) +{ + return TC_PORT_1 + id - DPLL_ID_ICL_MGPLL1; +} + +enum intel_dpll_id icl_tc_port_to_pll_id(enum tc_port tc_port) +{ + return tc_port - TC_PORT_1 + DPLL_ID_ICL_MGPLL1; +} + +static i915_reg_t +intel_combo_pll_enable_reg(struct drm_i915_private *i915, + struct intel_shared_dpll *pll) +{ + if (IS_DG1(i915)) + return DG1_DPLL_ENABLE(pll->info->id); + else if (IS_JSL_EHL(i915) && (pll->info->id == DPLL_ID_EHL_DPLL4)) + return MG_PLL_ENABLE(0); + + return ICL_DPLL_ENABLE(pll->info->id); +} + +static i915_reg_t +intel_tc_pll_enable_reg(struct drm_i915_private *i915, + struct intel_shared_dpll *pll) +{ + const enum intel_dpll_id id = pll->info->id; + enum tc_port tc_port = icl_pll_id_to_tc_port(id); + + if (IS_ALDERLAKE_P(i915)) + return ADLP_PORTTC_PLL_ENABLE(tc_port); + + return MG_PLL_ENABLE(tc_port); +} + +/** + * intel_enable_shared_dpll - enable a CRTC's shared DPLL + * @crtc_state: CRTC, and its state, which has a shared DPLL + * + * Enable the shared DPLL used by @crtc. + */ +void intel_enable_shared_dpll(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_shared_dpll *pll = crtc_state->shared_dpll; + unsigned int pipe_mask = BIT(crtc->pipe); + unsigned int old_mask; + + if (drm_WARN_ON(&dev_priv->drm, pll == NULL)) + return; + + mutex_lock(&dev_priv->display.dpll.lock); + old_mask = pll->active_mask; + + if (drm_WARN_ON(&dev_priv->drm, !(pll->state.pipe_mask & pipe_mask)) || + drm_WARN_ON(&dev_priv->drm, pll->active_mask & pipe_mask)) + goto out; + + pll->active_mask |= pipe_mask; + + drm_dbg_kms(&dev_priv->drm, + "enable %s (active 0x%x, on? %d) for [CRTC:%d:%s]\n", + pll->info->name, pll->active_mask, pll->on, + crtc->base.base.id, crtc->base.name); + + if (old_mask) { + drm_WARN_ON(&dev_priv->drm, !pll->on); + assert_shared_dpll_enabled(dev_priv, pll); + goto out; + } + drm_WARN_ON(&dev_priv->drm, pll->on); + + drm_dbg_kms(&dev_priv->drm, "enabling %s\n", pll->info->name); + pll->info->funcs->enable(dev_priv, pll); + pll->on = true; + +out: + mutex_unlock(&dev_priv->display.dpll.lock); +} + +/** + * intel_disable_shared_dpll - disable a CRTC's shared DPLL + * @crtc_state: CRTC, and its state, which has a shared DPLL + * + * Disable the shared DPLL used by @crtc. + */ +void intel_disable_shared_dpll(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_shared_dpll *pll = crtc_state->shared_dpll; + unsigned int pipe_mask = BIT(crtc->pipe); + + /* PCH only available on ILK+ */ + if (DISPLAY_VER(dev_priv) < 5) + return; + + if (pll == NULL) + return; + + mutex_lock(&dev_priv->display.dpll.lock); + if (drm_WARN(&dev_priv->drm, !(pll->active_mask & pipe_mask), + "%s not used by [CRTC:%d:%s]\n", pll->info->name, + crtc->base.base.id, crtc->base.name)) + goto out; + + drm_dbg_kms(&dev_priv->drm, + "disable %s (active 0x%x, on? %d) for [CRTC:%d:%s]\n", + pll->info->name, pll->active_mask, pll->on, + crtc->base.base.id, crtc->base.name); + + assert_shared_dpll_enabled(dev_priv, pll); + drm_WARN_ON(&dev_priv->drm, !pll->on); + + pll->active_mask &= ~pipe_mask; + if (pll->active_mask) + goto out; + + drm_dbg_kms(&dev_priv->drm, "disabling %s\n", pll->info->name); + pll->info->funcs->disable(dev_priv, pll); + pll->on = false; + +out: + mutex_unlock(&dev_priv->display.dpll.lock); +} + +static struct intel_shared_dpll * +intel_find_shared_dpll(struct intel_atomic_state *state, + const struct intel_crtc *crtc, + const struct intel_dpll_hw_state *pll_state, + unsigned long dpll_mask) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_shared_dpll *pll, *unused_pll = NULL; + struct intel_shared_dpll_state *shared_dpll; + enum intel_dpll_id i; + + shared_dpll = intel_atomic_get_shared_dpll_state(&state->base); + + drm_WARN_ON(&dev_priv->drm, dpll_mask & ~(BIT(I915_NUM_PLLS) - 1)); + + for_each_set_bit(i, &dpll_mask, I915_NUM_PLLS) { + pll = &dev_priv->display.dpll.shared_dplls[i]; + + /* Only want to check enabled timings first */ + if (shared_dpll[i].pipe_mask == 0) { + if (!unused_pll) + unused_pll = pll; + continue; + } + + if (memcmp(pll_state, + &shared_dpll[i].hw_state, + sizeof(*pll_state)) == 0) { + drm_dbg_kms(&dev_priv->drm, + "[CRTC:%d:%s] sharing existing %s (pipe mask 0x%x, active 0x%x)\n", + crtc->base.base.id, crtc->base.name, + pll->info->name, + shared_dpll[i].pipe_mask, + pll->active_mask); + return pll; + } + } + + /* Ok no matching timings, maybe there's a free one? */ + if (unused_pll) { + drm_dbg_kms(&dev_priv->drm, "[CRTC:%d:%s] allocated %s\n", + crtc->base.base.id, crtc->base.name, + unused_pll->info->name); + return unused_pll; + } + + return NULL; +} + +static void +intel_reference_shared_dpll(struct intel_atomic_state *state, + const struct intel_crtc *crtc, + const struct intel_shared_dpll *pll, + const struct intel_dpll_hw_state *pll_state) +{ + struct drm_i915_private *i915 = to_i915(state->base.dev); + struct intel_shared_dpll_state *shared_dpll; + const enum intel_dpll_id id = pll->info->id; + + shared_dpll = intel_atomic_get_shared_dpll_state(&state->base); + + if (shared_dpll[id].pipe_mask == 0) + shared_dpll[id].hw_state = *pll_state; + + drm_dbg(&i915->drm, "using %s for pipe %c\n", pll->info->name, + pipe_name(crtc->pipe)); + + shared_dpll[id].pipe_mask |= BIT(crtc->pipe); +} + +static void intel_unreference_shared_dpll(struct intel_atomic_state *state, + const struct intel_crtc *crtc, + const struct intel_shared_dpll *pll) +{ + struct intel_shared_dpll_state *shared_dpll; + + shared_dpll = intel_atomic_get_shared_dpll_state(&state->base); + shared_dpll[pll->info->id].pipe_mask &= ~BIT(crtc->pipe); +} + +static void intel_put_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + new_crtc_state->shared_dpll = NULL; + + if (!old_crtc_state->shared_dpll) + return; + + intel_unreference_shared_dpll(state, crtc, old_crtc_state->shared_dpll); +} + +/** + * intel_shared_dpll_swap_state - make atomic DPLL configuration effective + * @state: atomic state + * + * This is the dpll version of drm_atomic_helper_swap_state() since the + * helper does not handle driver-specific global state. + * + * For consistency with atomic helpers this function does a complete swap, + * i.e. it also puts the current state into @state, even though there is no + * need for that at this moment. + */ +void intel_shared_dpll_swap_state(struct intel_atomic_state *state) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_shared_dpll_state *shared_dpll = state->shared_dpll; + enum intel_dpll_id i; + + if (!state->dpll_set) + return; + + for (i = 0; i < dev_priv->display.dpll.num_shared_dpll; i++) { + struct intel_shared_dpll *pll = + &dev_priv->display.dpll.shared_dplls[i]; + + swap(pll->state, shared_dpll[i]); + } +} + +static bool ibx_pch_dpll_get_hw_state(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll, + struct intel_dpll_hw_state *hw_state) +{ + const enum intel_dpll_id id = pll->info->id; + intel_wakeref_t wakeref; + u32 val; + + wakeref = intel_display_power_get_if_enabled(dev_priv, + POWER_DOMAIN_DISPLAY_CORE); + if (!wakeref) + return false; + + val = intel_de_read(dev_priv, PCH_DPLL(id)); + hw_state->dpll = val; + hw_state->fp0 = intel_de_read(dev_priv, PCH_FP0(id)); + hw_state->fp1 = intel_de_read(dev_priv, PCH_FP1(id)); + + intel_display_power_put(dev_priv, POWER_DOMAIN_DISPLAY_CORE, wakeref); + + return val & DPLL_VCO_ENABLE; +} + +static void ibx_assert_pch_refclk_enabled(struct drm_i915_private *dev_priv) +{ + u32 val; + bool enabled; + + I915_STATE_WARN_ON(!(HAS_PCH_IBX(dev_priv) || HAS_PCH_CPT(dev_priv))); + + val = intel_de_read(dev_priv, PCH_DREF_CONTROL); + enabled = !!(val & (DREF_SSC_SOURCE_MASK | DREF_NONSPREAD_SOURCE_MASK | + DREF_SUPERSPREAD_SOURCE_MASK)); + I915_STATE_WARN(!enabled, "PCH refclk assertion failure, should be active but is disabled\n"); +} + +static void ibx_pch_dpll_enable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + const enum intel_dpll_id id = pll->info->id; + + /* PCH refclock must be enabled first */ + ibx_assert_pch_refclk_enabled(dev_priv); + + intel_de_write(dev_priv, PCH_FP0(id), pll->state.hw_state.fp0); + intel_de_write(dev_priv, PCH_FP1(id), pll->state.hw_state.fp1); + + intel_de_write(dev_priv, PCH_DPLL(id), pll->state.hw_state.dpll); + + /* Wait for the clocks to stabilize. */ + intel_de_posting_read(dev_priv, PCH_DPLL(id)); + udelay(150); + + /* The pixel multiplier can only be updated once the + * DPLL is enabled and the clocks are stable. + * + * So write it again. + */ + intel_de_write(dev_priv, PCH_DPLL(id), pll->state.hw_state.dpll); + intel_de_posting_read(dev_priv, PCH_DPLL(id)); + udelay(200); +} + +static void ibx_pch_dpll_disable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + const enum intel_dpll_id id = pll->info->id; + + intel_de_write(dev_priv, PCH_DPLL(id), 0); + intel_de_posting_read(dev_priv, PCH_DPLL(id)); + udelay(200); +} + +static int ibx_compute_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder) +{ + return 0; +} + +static int ibx_get_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_shared_dpll *pll; + enum intel_dpll_id i; + + if (HAS_PCH_IBX(dev_priv)) { + /* Ironlake PCH has a fixed PLL->PCH pipe mapping. */ + i = (enum intel_dpll_id) crtc->pipe; + pll = &dev_priv->display.dpll.shared_dplls[i]; + + drm_dbg_kms(&dev_priv->drm, + "[CRTC:%d:%s] using pre-allocated %s\n", + crtc->base.base.id, crtc->base.name, + pll->info->name); + } else { + pll = intel_find_shared_dpll(state, crtc, + &crtc_state->dpll_hw_state, + BIT(DPLL_ID_PCH_PLL_B) | + BIT(DPLL_ID_PCH_PLL_A)); + } + + if (!pll) + return -EINVAL; + + /* reference the pll */ + intel_reference_shared_dpll(state, crtc, + pll, &crtc_state->dpll_hw_state); + + crtc_state->shared_dpll = pll; + + return 0; +} + +static void ibx_dump_hw_state(struct drm_i915_private *dev_priv, + const struct intel_dpll_hw_state *hw_state) +{ + drm_dbg_kms(&dev_priv->drm, + "dpll_hw_state: dpll: 0x%x, dpll_md: 0x%x, " + "fp0: 0x%x, fp1: 0x%x\n", + hw_state->dpll, + hw_state->dpll_md, + hw_state->fp0, + hw_state->fp1); +} + +static const struct intel_shared_dpll_funcs ibx_pch_dpll_funcs = { + .enable = ibx_pch_dpll_enable, + .disable = ibx_pch_dpll_disable, + .get_hw_state = ibx_pch_dpll_get_hw_state, +}; + +static const struct dpll_info pch_plls[] = { + { "PCH DPLL A", &ibx_pch_dpll_funcs, DPLL_ID_PCH_PLL_A, 0 }, + { "PCH DPLL B", &ibx_pch_dpll_funcs, DPLL_ID_PCH_PLL_B, 0 }, + { }, +}; + +static const struct intel_dpll_mgr pch_pll_mgr = { + .dpll_info = pch_plls, + .compute_dplls = ibx_compute_dpll, + .get_dplls = ibx_get_dpll, + .put_dplls = intel_put_dpll, + .dump_hw_state = ibx_dump_hw_state, +}; + +static void hsw_ddi_wrpll_enable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + const enum intel_dpll_id id = pll->info->id; + + intel_de_write(dev_priv, WRPLL_CTL(id), pll->state.hw_state.wrpll); + intel_de_posting_read(dev_priv, WRPLL_CTL(id)); + udelay(20); +} + +static void hsw_ddi_spll_enable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + intel_de_write(dev_priv, SPLL_CTL, pll->state.hw_state.spll); + intel_de_posting_read(dev_priv, SPLL_CTL); + udelay(20); +} + +static void hsw_ddi_wrpll_disable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + const enum intel_dpll_id id = pll->info->id; + u32 val; + + val = intel_de_read(dev_priv, WRPLL_CTL(id)); + intel_de_write(dev_priv, WRPLL_CTL(id), val & ~WRPLL_PLL_ENABLE); + intel_de_posting_read(dev_priv, WRPLL_CTL(id)); + + /* + * Try to set up the PCH reference clock once all DPLLs + * that depend on it have been shut down. + */ + if (dev_priv->pch_ssc_use & BIT(id)) + intel_init_pch_refclk(dev_priv); +} + +static void hsw_ddi_spll_disable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + enum intel_dpll_id id = pll->info->id; + u32 val; + + val = intel_de_read(dev_priv, SPLL_CTL); + intel_de_write(dev_priv, SPLL_CTL, val & ~SPLL_PLL_ENABLE); + intel_de_posting_read(dev_priv, SPLL_CTL); + + /* + * Try to set up the PCH reference clock once all DPLLs + * that depend on it have been shut down. + */ + if (dev_priv->pch_ssc_use & BIT(id)) + intel_init_pch_refclk(dev_priv); +} + +static bool hsw_ddi_wrpll_get_hw_state(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll, + struct intel_dpll_hw_state *hw_state) +{ + const enum intel_dpll_id id = pll->info->id; + intel_wakeref_t wakeref; + u32 val; + + wakeref = intel_display_power_get_if_enabled(dev_priv, + POWER_DOMAIN_DISPLAY_CORE); + if (!wakeref) + return false; + + val = intel_de_read(dev_priv, WRPLL_CTL(id)); + hw_state->wrpll = val; + + intel_display_power_put(dev_priv, POWER_DOMAIN_DISPLAY_CORE, wakeref); + + return val & WRPLL_PLL_ENABLE; +} + +static bool hsw_ddi_spll_get_hw_state(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll, + struct intel_dpll_hw_state *hw_state) +{ + intel_wakeref_t wakeref; + u32 val; + + wakeref = intel_display_power_get_if_enabled(dev_priv, + POWER_DOMAIN_DISPLAY_CORE); + if (!wakeref) + return false; + + val = intel_de_read(dev_priv, SPLL_CTL); + hw_state->spll = val; + + intel_display_power_put(dev_priv, POWER_DOMAIN_DISPLAY_CORE, wakeref); + + return val & SPLL_PLL_ENABLE; +} + +#define LC_FREQ 2700 +#define LC_FREQ_2K U64_C(LC_FREQ * 2000) + +#define P_MIN 2 +#define P_MAX 64 +#define P_INC 2 + +/* Constraints for PLL good behavior */ +#define REF_MIN 48 +#define REF_MAX 400 +#define VCO_MIN 2400 +#define VCO_MAX 4800 + +struct hsw_wrpll_rnp { + unsigned p, n2, r2; +}; + +static unsigned hsw_wrpll_get_budget_for_freq(int clock) +{ + unsigned budget; + + switch (clock) { + case 25175000: + case 25200000: + case 27000000: + case 27027000: + case 37762500: + case 37800000: + case 40500000: + case 40541000: + case 54000000: + case 54054000: + case 59341000: + case 59400000: + case 72000000: + case 74176000: + case 74250000: + case 81000000: + case 81081000: + case 89012000: + case 89100000: + case 108000000: + case 108108000: + case 111264000: + case 111375000: + case 148352000: + case 148500000: + case 162000000: + case 162162000: + case 222525000: + case 222750000: + case 296703000: + case 297000000: + budget = 0; + break; + case 233500000: + case 245250000: + case 247750000: + case 253250000: + case 298000000: + budget = 1500; + break; + case 169128000: + case 169500000: + case 179500000: + case 202000000: + budget = 2000; + break; + case 256250000: + case 262500000: + case 270000000: + case 272500000: + case 273750000: + case 280750000: + case 281250000: + case 286000000: + case 291750000: + budget = 4000; + break; + case 267250000: + case 268500000: + budget = 5000; + break; + default: + budget = 1000; + break; + } + + return budget; +} + +static void hsw_wrpll_update_rnp(u64 freq2k, unsigned int budget, + unsigned int r2, unsigned int n2, + unsigned int p, + struct hsw_wrpll_rnp *best) +{ + u64 a, b, c, d, diff, diff_best; + + /* No best (r,n,p) yet */ + if (best->p == 0) { + best->p = p; + best->n2 = n2; + best->r2 = r2; + return; + } + + /* + * Output clock is (LC_FREQ_2K / 2000) * N / (P * R), which compares to + * freq2k. + * + * delta = 1e6 * + * abs(freq2k - (LC_FREQ_2K * n2/(p * r2))) / + * freq2k; + * + * and we would like delta <= budget. + * + * If the discrepancy is above the PPM-based budget, always prefer to + * improve upon the previous solution. However, if you're within the + * budget, try to maximize Ref * VCO, that is N / (P * R^2). + */ + a = freq2k * budget * p * r2; + b = freq2k * budget * best->p * best->r2; + diff = abs_diff(freq2k * p * r2, LC_FREQ_2K * n2); + diff_best = abs_diff(freq2k * best->p * best->r2, + LC_FREQ_2K * best->n2); + c = 1000000 * diff; + d = 1000000 * diff_best; + + if (a < c && b < d) { + /* If both are above the budget, pick the closer */ + if (best->p * best->r2 * diff < p * r2 * diff_best) { + best->p = p; + best->n2 = n2; + best->r2 = r2; + } + } else if (a >= c && b < d) { + /* If A is below the threshold but B is above it? Update. */ + best->p = p; + best->n2 = n2; + best->r2 = r2; + } else if (a >= c && b >= d) { + /* Both are below the limit, so pick the higher n2/(r2*r2) */ + if (n2 * best->r2 * best->r2 > best->n2 * r2 * r2) { + best->p = p; + best->n2 = n2; + best->r2 = r2; + } + } + /* Otherwise a < c && b >= d, do nothing */ +} + +static void +hsw_ddi_calculate_wrpll(int clock /* in Hz */, + unsigned *r2_out, unsigned *n2_out, unsigned *p_out) +{ + u64 freq2k; + unsigned p, n2, r2; + struct hsw_wrpll_rnp best = {}; + unsigned budget; + + freq2k = clock / 100; + + budget = hsw_wrpll_get_budget_for_freq(clock); + + /* Special case handling for 540 pixel clock: bypass WR PLL entirely + * and directly pass the LC PLL to it. */ + if (freq2k == 5400000) { + *n2_out = 2; + *p_out = 1; + *r2_out = 2; + return; + } + + /* + * Ref = LC_FREQ / R, where Ref is the actual reference input seen by + * the WR PLL. + * + * We want R so that REF_MIN <= Ref <= REF_MAX. + * Injecting R2 = 2 * R gives: + * REF_MAX * r2 > LC_FREQ * 2 and + * REF_MIN * r2 < LC_FREQ * 2 + * + * Which means the desired boundaries for r2 are: + * LC_FREQ * 2 / REF_MAX < r2 < LC_FREQ * 2 / REF_MIN + * + */ + for (r2 = LC_FREQ * 2 / REF_MAX + 1; + r2 <= LC_FREQ * 2 / REF_MIN; + r2++) { + + /* + * VCO = N * Ref, that is: VCO = N * LC_FREQ / R + * + * Once again we want VCO_MIN <= VCO <= VCO_MAX. + * Injecting R2 = 2 * R and N2 = 2 * N, we get: + * VCO_MAX * r2 > n2 * LC_FREQ and + * VCO_MIN * r2 < n2 * LC_FREQ) + * + * Which means the desired boundaries for n2 are: + * VCO_MIN * r2 / LC_FREQ < n2 < VCO_MAX * r2 / LC_FREQ + */ + for (n2 = VCO_MIN * r2 / LC_FREQ + 1; + n2 <= VCO_MAX * r2 / LC_FREQ; + n2++) { + + for (p = P_MIN; p <= P_MAX; p += P_INC) + hsw_wrpll_update_rnp(freq2k, budget, + r2, n2, p, &best); + } + } + + *n2_out = best.n2; + *p_out = best.p; + *r2_out = best.r2; +} + +static int hsw_ddi_wrpll_get_freq(struct drm_i915_private *dev_priv, + const struct intel_shared_dpll *pll, + const struct intel_dpll_hw_state *pll_state) +{ + int refclk; + int n, p, r; + u32 wrpll = pll_state->wrpll; + + switch (wrpll & WRPLL_REF_MASK) { + case WRPLL_REF_SPECIAL_HSW: + /* Muxed-SSC for BDW, non-SSC for non-ULT HSW. */ + if (IS_HASWELL(dev_priv) && !IS_HSW_ULT(dev_priv)) { + refclk = dev_priv->display.dpll.ref_clks.nssc; + break; + } + fallthrough; + case WRPLL_REF_PCH_SSC: + /* + * We could calculate spread here, but our checking + * code only cares about 5% accuracy, and spread is a max of + * 0.5% downspread. + */ + refclk = dev_priv->display.dpll.ref_clks.ssc; + break; + case WRPLL_REF_LCPLL: + refclk = 2700000; + break; + default: + MISSING_CASE(wrpll); + return 0; + } + + r = wrpll & WRPLL_DIVIDER_REF_MASK; + p = (wrpll & WRPLL_DIVIDER_POST_MASK) >> WRPLL_DIVIDER_POST_SHIFT; + n = (wrpll & WRPLL_DIVIDER_FB_MASK) >> WRPLL_DIVIDER_FB_SHIFT; + + /* Convert to KHz, p & r have a fixed point portion */ + return (refclk * n / 10) / (p * r) * 2; +} + +static int +hsw_ddi_wrpll_compute_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *i915 = to_i915(state->base.dev); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + unsigned int p, n2, r2; + + hsw_ddi_calculate_wrpll(crtc_state->port_clock * 1000, &r2, &n2, &p); + + crtc_state->dpll_hw_state.wrpll = + WRPLL_PLL_ENABLE | WRPLL_REF_LCPLL | + WRPLL_DIVIDER_REFERENCE(r2) | WRPLL_DIVIDER_FEEDBACK(n2) | + WRPLL_DIVIDER_POST(p); + + crtc_state->port_clock = hsw_ddi_wrpll_get_freq(i915, NULL, + &crtc_state->dpll_hw_state); + + return 0; +} + +static struct intel_shared_dpll * +hsw_ddi_wrpll_get_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + return intel_find_shared_dpll(state, crtc, + &crtc_state->dpll_hw_state, + BIT(DPLL_ID_WRPLL2) | + BIT(DPLL_ID_WRPLL1)); +} + +static int +hsw_ddi_lcpll_compute_dpll(struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + int clock = crtc_state->port_clock; + + switch (clock / 2) { + case 81000: + case 135000: + case 270000: + return 0; + default: + drm_dbg_kms(&dev_priv->drm, "Invalid clock for DP: %d\n", + clock); + return -EINVAL; + } +} + +static struct intel_shared_dpll * +hsw_ddi_lcpll_get_dpll(struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + struct intel_shared_dpll *pll; + enum intel_dpll_id pll_id; + int clock = crtc_state->port_clock; + + switch (clock / 2) { + case 81000: + pll_id = DPLL_ID_LCPLL_810; + break; + case 135000: + pll_id = DPLL_ID_LCPLL_1350; + break; + case 270000: + pll_id = DPLL_ID_LCPLL_2700; + break; + default: + MISSING_CASE(clock / 2); + return NULL; + } + + pll = intel_get_shared_dpll_by_id(dev_priv, pll_id); + + if (!pll) + return NULL; + + return pll; +} + +static int hsw_ddi_lcpll_get_freq(struct drm_i915_private *i915, + const struct intel_shared_dpll *pll, + const struct intel_dpll_hw_state *pll_state) +{ + int link_clock = 0; + + switch (pll->info->id) { + case DPLL_ID_LCPLL_810: + link_clock = 81000; + break; + case DPLL_ID_LCPLL_1350: + link_clock = 135000; + break; + case DPLL_ID_LCPLL_2700: + link_clock = 270000; + break; + default: + drm_WARN(&i915->drm, 1, "bad port clock sel\n"); + break; + } + + return link_clock * 2; +} + +static int +hsw_ddi_spll_compute_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + if (drm_WARN_ON(crtc->base.dev, crtc_state->port_clock / 2 != 135000)) + return -EINVAL; + + crtc_state->dpll_hw_state.spll = + SPLL_PLL_ENABLE | SPLL_FREQ_1350MHz | SPLL_REF_MUXED_SSC; + + return 0; +} + +static struct intel_shared_dpll * +hsw_ddi_spll_get_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + return intel_find_shared_dpll(state, crtc, &crtc_state->dpll_hw_state, + BIT(DPLL_ID_SPLL)); +} + +static int hsw_ddi_spll_get_freq(struct drm_i915_private *i915, + const struct intel_shared_dpll *pll, + const struct intel_dpll_hw_state *pll_state) +{ + int link_clock = 0; + + switch (pll_state->spll & SPLL_FREQ_MASK) { + case SPLL_FREQ_810MHz: + link_clock = 81000; + break; + case SPLL_FREQ_1350MHz: + link_clock = 135000; + break; + case SPLL_FREQ_2700MHz: + link_clock = 270000; + break; + default: + drm_WARN(&i915->drm, 1, "bad spll freq\n"); + break; + } + + return link_clock * 2; +} + +static int hsw_compute_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return hsw_ddi_wrpll_compute_dpll(state, crtc); + else if (intel_crtc_has_dp_encoder(crtc_state)) + return hsw_ddi_lcpll_compute_dpll(crtc_state); + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_ANALOG)) + return hsw_ddi_spll_compute_dpll(state, crtc); + else + return -EINVAL; +} + +static int hsw_get_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct intel_shared_dpll *pll = NULL; + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + pll = hsw_ddi_wrpll_get_dpll(state, crtc); + else if (intel_crtc_has_dp_encoder(crtc_state)) + pll = hsw_ddi_lcpll_get_dpll(crtc_state); + else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_ANALOG)) + pll = hsw_ddi_spll_get_dpll(state, crtc); + + if (!pll) + return -EINVAL; + + intel_reference_shared_dpll(state, crtc, + pll, &crtc_state->dpll_hw_state); + + crtc_state->shared_dpll = pll; + + return 0; +} + +static void hsw_update_dpll_ref_clks(struct drm_i915_private *i915) +{ + i915->display.dpll.ref_clks.ssc = 135000; + /* Non-SSC is only used on non-ULT HSW. */ + if (intel_de_read(i915, FUSE_STRAP3) & HSW_REF_CLK_SELECT) + i915->display.dpll.ref_clks.nssc = 24000; + else + i915->display.dpll.ref_clks.nssc = 135000; +} + +static void hsw_dump_hw_state(struct drm_i915_private *dev_priv, + const struct intel_dpll_hw_state *hw_state) +{ + drm_dbg_kms(&dev_priv->drm, "dpll_hw_state: wrpll: 0x%x spll: 0x%x\n", + hw_state->wrpll, hw_state->spll); +} + +static const struct intel_shared_dpll_funcs hsw_ddi_wrpll_funcs = { + .enable = hsw_ddi_wrpll_enable, + .disable = hsw_ddi_wrpll_disable, + .get_hw_state = hsw_ddi_wrpll_get_hw_state, + .get_freq = hsw_ddi_wrpll_get_freq, +}; + +static const struct intel_shared_dpll_funcs hsw_ddi_spll_funcs = { + .enable = hsw_ddi_spll_enable, + .disable = hsw_ddi_spll_disable, + .get_hw_state = hsw_ddi_spll_get_hw_state, + .get_freq = hsw_ddi_spll_get_freq, +}; + +static void hsw_ddi_lcpll_enable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ +} + +static void hsw_ddi_lcpll_disable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ +} + +static bool hsw_ddi_lcpll_get_hw_state(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll, + struct intel_dpll_hw_state *hw_state) +{ + return true; +} + +static const struct intel_shared_dpll_funcs hsw_ddi_lcpll_funcs = { + .enable = hsw_ddi_lcpll_enable, + .disable = hsw_ddi_lcpll_disable, + .get_hw_state = hsw_ddi_lcpll_get_hw_state, + .get_freq = hsw_ddi_lcpll_get_freq, +}; + +static const struct dpll_info hsw_plls[] = { + { "WRPLL 1", &hsw_ddi_wrpll_funcs, DPLL_ID_WRPLL1, 0 }, + { "WRPLL 2", &hsw_ddi_wrpll_funcs, DPLL_ID_WRPLL2, 0 }, + { "SPLL", &hsw_ddi_spll_funcs, DPLL_ID_SPLL, 0 }, + { "LCPLL 810", &hsw_ddi_lcpll_funcs, DPLL_ID_LCPLL_810, INTEL_DPLL_ALWAYS_ON }, + { "LCPLL 1350", &hsw_ddi_lcpll_funcs, DPLL_ID_LCPLL_1350, INTEL_DPLL_ALWAYS_ON }, + { "LCPLL 2700", &hsw_ddi_lcpll_funcs, DPLL_ID_LCPLL_2700, INTEL_DPLL_ALWAYS_ON }, + { }, +}; + +static const struct intel_dpll_mgr hsw_pll_mgr = { + .dpll_info = hsw_plls, + .compute_dplls = hsw_compute_dpll, + .get_dplls = hsw_get_dpll, + .put_dplls = intel_put_dpll, + .update_ref_clks = hsw_update_dpll_ref_clks, + .dump_hw_state = hsw_dump_hw_state, +}; + +struct skl_dpll_regs { + i915_reg_t ctl, cfgcr1, cfgcr2; +}; + +/* this array is indexed by the *shared* pll id */ +static const struct skl_dpll_regs skl_dpll_regs[4] = { + { + /* DPLL 0 */ + .ctl = LCPLL1_CTL, + /* DPLL 0 doesn't support HDMI mode */ + }, + { + /* DPLL 1 */ + .ctl = LCPLL2_CTL, + .cfgcr1 = DPLL_CFGCR1(SKL_DPLL1), + .cfgcr2 = DPLL_CFGCR2(SKL_DPLL1), + }, + { + /* DPLL 2 */ + .ctl = WRPLL_CTL(0), + .cfgcr1 = DPLL_CFGCR1(SKL_DPLL2), + .cfgcr2 = DPLL_CFGCR2(SKL_DPLL2), + }, + { + /* DPLL 3 */ + .ctl = WRPLL_CTL(1), + .cfgcr1 = DPLL_CFGCR1(SKL_DPLL3), + .cfgcr2 = DPLL_CFGCR2(SKL_DPLL3), + }, +}; + +static void skl_ddi_pll_write_ctrl1(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + const enum intel_dpll_id id = pll->info->id; + u32 val; + + val = intel_de_read(dev_priv, DPLL_CTRL1); + + val &= ~(DPLL_CTRL1_HDMI_MODE(id) | + DPLL_CTRL1_SSC(id) | + DPLL_CTRL1_LINK_RATE_MASK(id)); + val |= pll->state.hw_state.ctrl1 << (id * 6); + + intel_de_write(dev_priv, DPLL_CTRL1, val); + intel_de_posting_read(dev_priv, DPLL_CTRL1); +} + +static void skl_ddi_pll_enable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + const struct skl_dpll_regs *regs = skl_dpll_regs; + const enum intel_dpll_id id = pll->info->id; + + skl_ddi_pll_write_ctrl1(dev_priv, pll); + + intel_de_write(dev_priv, regs[id].cfgcr1, pll->state.hw_state.cfgcr1); + intel_de_write(dev_priv, regs[id].cfgcr2, pll->state.hw_state.cfgcr2); + intel_de_posting_read(dev_priv, regs[id].cfgcr1); + intel_de_posting_read(dev_priv, regs[id].cfgcr2); + + /* the enable bit is always bit 31 */ + intel_de_write(dev_priv, regs[id].ctl, + intel_de_read(dev_priv, regs[id].ctl) | LCPLL_PLL_ENABLE); + + if (intel_de_wait_for_set(dev_priv, DPLL_STATUS, DPLL_LOCK(id), 5)) + drm_err(&dev_priv->drm, "DPLL %d not locked\n", id); +} + +static void skl_ddi_dpll0_enable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + skl_ddi_pll_write_ctrl1(dev_priv, pll); +} + +static void skl_ddi_pll_disable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + const struct skl_dpll_regs *regs = skl_dpll_regs; + const enum intel_dpll_id id = pll->info->id; + + /* the enable bit is always bit 31 */ + intel_de_write(dev_priv, regs[id].ctl, + intel_de_read(dev_priv, regs[id].ctl) & ~LCPLL_PLL_ENABLE); + intel_de_posting_read(dev_priv, regs[id].ctl); +} + +static void skl_ddi_dpll0_disable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ +} + +static bool skl_ddi_pll_get_hw_state(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll, + struct intel_dpll_hw_state *hw_state) +{ + u32 val; + const struct skl_dpll_regs *regs = skl_dpll_regs; + const enum intel_dpll_id id = pll->info->id; + intel_wakeref_t wakeref; + bool ret; + + wakeref = intel_display_power_get_if_enabled(dev_priv, + POWER_DOMAIN_DISPLAY_CORE); + if (!wakeref) + return false; + + ret = false; + + val = intel_de_read(dev_priv, regs[id].ctl); + if (!(val & LCPLL_PLL_ENABLE)) + goto out; + + val = intel_de_read(dev_priv, DPLL_CTRL1); + hw_state->ctrl1 = (val >> (id * 6)) & 0x3f; + + /* avoid reading back stale values if HDMI mode is not enabled */ + if (val & DPLL_CTRL1_HDMI_MODE(id)) { + hw_state->cfgcr1 = intel_de_read(dev_priv, regs[id].cfgcr1); + hw_state->cfgcr2 = intel_de_read(dev_priv, regs[id].cfgcr2); + } + ret = true; + +out: + intel_display_power_put(dev_priv, POWER_DOMAIN_DISPLAY_CORE, wakeref); + + return ret; +} + +static bool skl_ddi_dpll0_get_hw_state(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll, + struct intel_dpll_hw_state *hw_state) +{ + const struct skl_dpll_regs *regs = skl_dpll_regs; + const enum intel_dpll_id id = pll->info->id; + intel_wakeref_t wakeref; + u32 val; + bool ret; + + wakeref = intel_display_power_get_if_enabled(dev_priv, + POWER_DOMAIN_DISPLAY_CORE); + if (!wakeref) + return false; + + ret = false; + + /* DPLL0 is always enabled since it drives CDCLK */ + val = intel_de_read(dev_priv, regs[id].ctl); + if (drm_WARN_ON(&dev_priv->drm, !(val & LCPLL_PLL_ENABLE))) + goto out; + + val = intel_de_read(dev_priv, DPLL_CTRL1); + hw_state->ctrl1 = (val >> (id * 6)) & 0x3f; + + ret = true; + +out: + intel_display_power_put(dev_priv, POWER_DOMAIN_DISPLAY_CORE, wakeref); + + return ret; +} + +struct skl_wrpll_context { + u64 min_deviation; /* current minimal deviation */ + u64 central_freq; /* chosen central freq */ + u64 dco_freq; /* chosen dco freq */ + unsigned int p; /* chosen divider */ +}; + +/* DCO freq must be within +1%/-6% of the DCO central freq */ +#define SKL_DCO_MAX_PDEVIATION 100 +#define SKL_DCO_MAX_NDEVIATION 600 + +static void skl_wrpll_try_divider(struct skl_wrpll_context *ctx, + u64 central_freq, + u64 dco_freq, + unsigned int divider) +{ + u64 deviation; + + deviation = div64_u64(10000 * abs_diff(dco_freq, central_freq), + central_freq); + + /* positive deviation */ + if (dco_freq >= central_freq) { + if (deviation < SKL_DCO_MAX_PDEVIATION && + deviation < ctx->min_deviation) { + ctx->min_deviation = deviation; + ctx->central_freq = central_freq; + ctx->dco_freq = dco_freq; + ctx->p = divider; + } + /* negative deviation */ + } else if (deviation < SKL_DCO_MAX_NDEVIATION && + deviation < ctx->min_deviation) { + ctx->min_deviation = deviation; + ctx->central_freq = central_freq; + ctx->dco_freq = dco_freq; + ctx->p = divider; + } +} + +static void skl_wrpll_get_multipliers(unsigned int p, + unsigned int *p0 /* out */, + unsigned int *p1 /* out */, + unsigned int *p2 /* out */) +{ + /* even dividers */ + if (p % 2 == 0) { + unsigned int half = p / 2; + + if (half == 1 || half == 2 || half == 3 || half == 5) { + *p0 = 2; + *p1 = 1; + *p2 = half; + } else if (half % 2 == 0) { + *p0 = 2; + *p1 = half / 2; + *p2 = 2; + } else if (half % 3 == 0) { + *p0 = 3; + *p1 = half / 3; + *p2 = 2; + } else if (half % 7 == 0) { + *p0 = 7; + *p1 = half / 7; + *p2 = 2; + } + } else if (p == 3 || p == 9) { /* 3, 5, 7, 9, 15, 21, 35 */ + *p0 = 3; + *p1 = 1; + *p2 = p / 3; + } else if (p == 5 || p == 7) { + *p0 = p; + *p1 = 1; + *p2 = 1; + } else if (p == 15) { + *p0 = 3; + *p1 = 1; + *p2 = 5; + } else if (p == 21) { + *p0 = 7; + *p1 = 1; + *p2 = 3; + } else if (p == 35) { + *p0 = 7; + *p1 = 1; + *p2 = 5; + } +} + +struct skl_wrpll_params { + u32 dco_fraction; + u32 dco_integer; + u32 qdiv_ratio; + u32 qdiv_mode; + u32 kdiv; + u32 pdiv; + u32 central_freq; +}; + +static void skl_wrpll_params_populate(struct skl_wrpll_params *params, + u64 afe_clock, + int ref_clock, + u64 central_freq, + u32 p0, u32 p1, u32 p2) +{ + u64 dco_freq; + + switch (central_freq) { + case 9600000000ULL: + params->central_freq = 0; + break; + case 9000000000ULL: + params->central_freq = 1; + break; + case 8400000000ULL: + params->central_freq = 3; + } + + switch (p0) { + case 1: + params->pdiv = 0; + break; + case 2: + params->pdiv = 1; + break; + case 3: + params->pdiv = 2; + break; + case 7: + params->pdiv = 4; + break; + default: + WARN(1, "Incorrect PDiv\n"); + } + + switch (p2) { + case 5: + params->kdiv = 0; + break; + case 2: + params->kdiv = 1; + break; + case 3: + params->kdiv = 2; + break; + case 1: + params->kdiv = 3; + break; + default: + WARN(1, "Incorrect KDiv\n"); + } + + params->qdiv_ratio = p1; + params->qdiv_mode = (params->qdiv_ratio == 1) ? 0 : 1; + + dco_freq = p0 * p1 * p2 * afe_clock; + + /* + * Intermediate values are in Hz. + * Divide by MHz to match bsepc + */ + params->dco_integer = div_u64(dco_freq, ref_clock * KHz(1)); + params->dco_fraction = + div_u64((div_u64(dco_freq, ref_clock / KHz(1)) - + params->dco_integer * MHz(1)) * 0x8000, MHz(1)); +} + +static int +skl_ddi_calculate_wrpll(int clock /* in Hz */, + int ref_clock, + struct skl_wrpll_params *wrpll_params) +{ + static const u64 dco_central_freq[3] = { 8400000000ULL, + 9000000000ULL, + 9600000000ULL }; + static const u8 even_dividers[] = { 4, 6, 8, 10, 12, 14, 16, 18, 20, + 24, 28, 30, 32, 36, 40, 42, 44, + 48, 52, 54, 56, 60, 64, 66, 68, + 70, 72, 76, 78, 80, 84, 88, 90, + 92, 96, 98 }; + static const u8 odd_dividers[] = { 3, 5, 7, 9, 15, 21, 35 }; + static const struct { + const u8 *list; + int n_dividers; + } dividers[] = { + { even_dividers, ARRAY_SIZE(even_dividers) }, + { odd_dividers, ARRAY_SIZE(odd_dividers) }, + }; + struct skl_wrpll_context ctx = { + .min_deviation = U64_MAX, + }; + unsigned int dco, d, i; + unsigned int p0, p1, p2; + u64 afe_clock = clock * 5; /* AFE Clock is 5x Pixel clock */ + + for (d = 0; d < ARRAY_SIZE(dividers); d++) { + for (dco = 0; dco < ARRAY_SIZE(dco_central_freq); dco++) { + for (i = 0; i < dividers[d].n_dividers; i++) { + unsigned int p = dividers[d].list[i]; + u64 dco_freq = p * afe_clock; + + skl_wrpll_try_divider(&ctx, + dco_central_freq[dco], + dco_freq, + p); + /* + * Skip the remaining dividers if we're sure to + * have found the definitive divider, we can't + * improve a 0 deviation. + */ + if (ctx.min_deviation == 0) + goto skip_remaining_dividers; + } + } + +skip_remaining_dividers: + /* + * If a solution is found with an even divider, prefer + * this one. + */ + if (d == 0 && ctx.p) + break; + } + + if (!ctx.p) + return -EINVAL; + + /* + * gcc incorrectly analyses that these can be used without being + * initialized. To be fair, it's hard to guess. + */ + p0 = p1 = p2 = 0; + skl_wrpll_get_multipliers(ctx.p, &p0, &p1, &p2); + skl_wrpll_params_populate(wrpll_params, afe_clock, ref_clock, + ctx.central_freq, p0, p1, p2); + + return 0; +} + +static int skl_ddi_wrpll_get_freq(struct drm_i915_private *i915, + const struct intel_shared_dpll *pll, + const struct intel_dpll_hw_state *pll_state) +{ + int ref_clock = i915->display.dpll.ref_clks.nssc; + u32 p0, p1, p2, dco_freq; + + p0 = pll_state->cfgcr2 & DPLL_CFGCR2_PDIV_MASK; + p2 = pll_state->cfgcr2 & DPLL_CFGCR2_KDIV_MASK; + + if (pll_state->cfgcr2 & DPLL_CFGCR2_QDIV_MODE(1)) + p1 = (pll_state->cfgcr2 & DPLL_CFGCR2_QDIV_RATIO_MASK) >> 8; + else + p1 = 1; + + + switch (p0) { + case DPLL_CFGCR2_PDIV_1: + p0 = 1; + break; + case DPLL_CFGCR2_PDIV_2: + p0 = 2; + break; + case DPLL_CFGCR2_PDIV_3: + p0 = 3; + break; + case DPLL_CFGCR2_PDIV_7_INVALID: + /* + * Incorrect ASUS-Z170M BIOS setting, the HW seems to ignore bit#0, + * handling it the same way as PDIV_7. + */ + drm_dbg_kms(&i915->drm, "Invalid WRPLL PDIV divider value, fixing it.\n"); + fallthrough; + case DPLL_CFGCR2_PDIV_7: + p0 = 7; + break; + default: + MISSING_CASE(p0); + return 0; + } + + switch (p2) { + case DPLL_CFGCR2_KDIV_5: + p2 = 5; + break; + case DPLL_CFGCR2_KDIV_2: + p2 = 2; + break; + case DPLL_CFGCR2_KDIV_3: + p2 = 3; + break; + case DPLL_CFGCR2_KDIV_1: + p2 = 1; + break; + default: + MISSING_CASE(p2); + return 0; + } + + dco_freq = (pll_state->cfgcr1 & DPLL_CFGCR1_DCO_INTEGER_MASK) * + ref_clock; + + dco_freq += ((pll_state->cfgcr1 & DPLL_CFGCR1_DCO_FRACTION_MASK) >> 9) * + ref_clock / 0x8000; + + if (drm_WARN_ON(&i915->drm, p0 == 0 || p1 == 0 || p2 == 0)) + return 0; + + return dco_freq / (p0 * p1 * p2 * 5); +} + +static int skl_ddi_hdmi_pll_dividers(struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(crtc_state->uapi.crtc->dev); + struct skl_wrpll_params wrpll_params = {}; + u32 ctrl1, cfgcr1, cfgcr2; + int ret; + + /* + * See comment in intel_dpll_hw_state to understand why we always use 0 + * as the DPLL id in this function. + */ + ctrl1 = DPLL_CTRL1_OVERRIDE(0); + + ctrl1 |= DPLL_CTRL1_HDMI_MODE(0); + + ret = skl_ddi_calculate_wrpll(crtc_state->port_clock * 1000, + i915->display.dpll.ref_clks.nssc, &wrpll_params); + if (ret) + return ret; + + cfgcr1 = DPLL_CFGCR1_FREQ_ENABLE | + DPLL_CFGCR1_DCO_FRACTION(wrpll_params.dco_fraction) | + wrpll_params.dco_integer; + + cfgcr2 = DPLL_CFGCR2_QDIV_RATIO(wrpll_params.qdiv_ratio) | + DPLL_CFGCR2_QDIV_MODE(wrpll_params.qdiv_mode) | + DPLL_CFGCR2_KDIV(wrpll_params.kdiv) | + DPLL_CFGCR2_PDIV(wrpll_params.pdiv) | + wrpll_params.central_freq; + + crtc_state->dpll_hw_state.ctrl1 = ctrl1; + crtc_state->dpll_hw_state.cfgcr1 = cfgcr1; + crtc_state->dpll_hw_state.cfgcr2 = cfgcr2; + + crtc_state->port_clock = skl_ddi_wrpll_get_freq(i915, NULL, + &crtc_state->dpll_hw_state); + + return 0; +} + +static int +skl_ddi_dp_set_dpll_hw_state(struct intel_crtc_state *crtc_state) +{ + u32 ctrl1; + + /* + * See comment in intel_dpll_hw_state to understand why we always use 0 + * as the DPLL id in this function. + */ + ctrl1 = DPLL_CTRL1_OVERRIDE(0); + switch (crtc_state->port_clock / 2) { + case 81000: + ctrl1 |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_810, 0); + break; + case 135000: + ctrl1 |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1350, 0); + break; + case 270000: + ctrl1 |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_2700, 0); + break; + /* eDP 1.4 rates */ + case 162000: + ctrl1 |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1620, 0); + break; + case 108000: + ctrl1 |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1080, 0); + break; + case 216000: + ctrl1 |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_2160, 0); + break; + } + + crtc_state->dpll_hw_state.ctrl1 = ctrl1; + + return 0; +} + +static int skl_ddi_lcpll_get_freq(struct drm_i915_private *i915, + const struct intel_shared_dpll *pll, + const struct intel_dpll_hw_state *pll_state) +{ + int link_clock = 0; + + switch ((pll_state->ctrl1 & DPLL_CTRL1_LINK_RATE_MASK(0)) >> + DPLL_CTRL1_LINK_RATE_SHIFT(0)) { + case DPLL_CTRL1_LINK_RATE_810: + link_clock = 81000; + break; + case DPLL_CTRL1_LINK_RATE_1080: + link_clock = 108000; + break; + case DPLL_CTRL1_LINK_RATE_1350: + link_clock = 135000; + break; + case DPLL_CTRL1_LINK_RATE_1620: + link_clock = 162000; + break; + case DPLL_CTRL1_LINK_RATE_2160: + link_clock = 216000; + break; + case DPLL_CTRL1_LINK_RATE_2700: + link_clock = 270000; + break; + default: + drm_WARN(&i915->drm, 1, "Unsupported link rate\n"); + break; + } + + return link_clock * 2; +} + +static int skl_compute_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return skl_ddi_hdmi_pll_dividers(crtc_state); + else if (intel_crtc_has_dp_encoder(crtc_state)) + return skl_ddi_dp_set_dpll_hw_state(crtc_state); + else + return -EINVAL; +} + +static int skl_get_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct intel_shared_dpll *pll; + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP)) + pll = intel_find_shared_dpll(state, crtc, + &crtc_state->dpll_hw_state, + BIT(DPLL_ID_SKL_DPLL0)); + else + pll = intel_find_shared_dpll(state, crtc, + &crtc_state->dpll_hw_state, + BIT(DPLL_ID_SKL_DPLL3) | + BIT(DPLL_ID_SKL_DPLL2) | + BIT(DPLL_ID_SKL_DPLL1)); + if (!pll) + return -EINVAL; + + intel_reference_shared_dpll(state, crtc, + pll, &crtc_state->dpll_hw_state); + + crtc_state->shared_dpll = pll; + + return 0; +} + +static int skl_ddi_pll_get_freq(struct drm_i915_private *i915, + const struct intel_shared_dpll *pll, + const struct intel_dpll_hw_state *pll_state) +{ + /* + * ctrl1 register is already shifted for each pll, just use 0 to get + * the internal shift for each field + */ + if (pll_state->ctrl1 & DPLL_CTRL1_HDMI_MODE(0)) + return skl_ddi_wrpll_get_freq(i915, pll, pll_state); + else + return skl_ddi_lcpll_get_freq(i915, pll, pll_state); +} + +static void skl_update_dpll_ref_clks(struct drm_i915_private *i915) +{ + /* No SSC ref */ + i915->display.dpll.ref_clks.nssc = i915->display.cdclk.hw.ref; +} + +static void skl_dump_hw_state(struct drm_i915_private *dev_priv, + const struct intel_dpll_hw_state *hw_state) +{ + drm_dbg_kms(&dev_priv->drm, "dpll_hw_state: " + "ctrl1: 0x%x, cfgcr1: 0x%x, cfgcr2: 0x%x\n", + hw_state->ctrl1, + hw_state->cfgcr1, + hw_state->cfgcr2); +} + +static const struct intel_shared_dpll_funcs skl_ddi_pll_funcs = { + .enable = skl_ddi_pll_enable, + .disable = skl_ddi_pll_disable, + .get_hw_state = skl_ddi_pll_get_hw_state, + .get_freq = skl_ddi_pll_get_freq, +}; + +static const struct intel_shared_dpll_funcs skl_ddi_dpll0_funcs = { + .enable = skl_ddi_dpll0_enable, + .disable = skl_ddi_dpll0_disable, + .get_hw_state = skl_ddi_dpll0_get_hw_state, + .get_freq = skl_ddi_pll_get_freq, +}; + +static const struct dpll_info skl_plls[] = { + { "DPLL 0", &skl_ddi_dpll0_funcs, DPLL_ID_SKL_DPLL0, INTEL_DPLL_ALWAYS_ON }, + { "DPLL 1", &skl_ddi_pll_funcs, DPLL_ID_SKL_DPLL1, 0 }, + { "DPLL 2", &skl_ddi_pll_funcs, DPLL_ID_SKL_DPLL2, 0 }, + { "DPLL 3", &skl_ddi_pll_funcs, DPLL_ID_SKL_DPLL3, 0 }, + { }, +}; + +static const struct intel_dpll_mgr skl_pll_mgr = { + .dpll_info = skl_plls, + .compute_dplls = skl_compute_dpll, + .get_dplls = skl_get_dpll, + .put_dplls = intel_put_dpll, + .update_ref_clks = skl_update_dpll_ref_clks, + .dump_hw_state = skl_dump_hw_state, +}; + +static void bxt_ddi_pll_enable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + u32 temp; + enum port port = (enum port)pll->info->id; /* 1:1 port->PLL mapping */ + enum dpio_phy phy; + enum dpio_channel ch; + + bxt_port_to_phy_channel(dev_priv, port, &phy, &ch); + + /* Non-SSC reference */ + temp = intel_de_read(dev_priv, BXT_PORT_PLL_ENABLE(port)); + temp |= PORT_PLL_REF_SEL; + intel_de_write(dev_priv, BXT_PORT_PLL_ENABLE(port), temp); + + if (IS_GEMINILAKE(dev_priv)) { + temp = intel_de_read(dev_priv, BXT_PORT_PLL_ENABLE(port)); + temp |= PORT_PLL_POWER_ENABLE; + intel_de_write(dev_priv, BXT_PORT_PLL_ENABLE(port), temp); + + if (wait_for_us((intel_de_read(dev_priv, BXT_PORT_PLL_ENABLE(port)) & + PORT_PLL_POWER_STATE), 200)) + drm_err(&dev_priv->drm, + "Power state not set for PLL:%d\n", port); + } + + /* Disable 10 bit clock */ + temp = intel_de_read(dev_priv, BXT_PORT_PLL_EBB_4(phy, ch)); + temp &= ~PORT_PLL_10BIT_CLK_ENABLE; + intel_de_write(dev_priv, BXT_PORT_PLL_EBB_4(phy, ch), temp); + + /* Write P1 & P2 */ + temp = intel_de_read(dev_priv, BXT_PORT_PLL_EBB_0(phy, ch)); + temp &= ~(PORT_PLL_P1_MASK | PORT_PLL_P2_MASK); + temp |= pll->state.hw_state.ebb0; + intel_de_write(dev_priv, BXT_PORT_PLL_EBB_0(phy, ch), temp); + + /* Write M2 integer */ + temp = intel_de_read(dev_priv, BXT_PORT_PLL(phy, ch, 0)); + temp &= ~PORT_PLL_M2_INT_MASK; + temp |= pll->state.hw_state.pll0; + intel_de_write(dev_priv, BXT_PORT_PLL(phy, ch, 0), temp); + + /* Write N */ + temp = intel_de_read(dev_priv, BXT_PORT_PLL(phy, ch, 1)); + temp &= ~PORT_PLL_N_MASK; + temp |= pll->state.hw_state.pll1; + intel_de_write(dev_priv, BXT_PORT_PLL(phy, ch, 1), temp); + + /* Write M2 fraction */ + temp = intel_de_read(dev_priv, BXT_PORT_PLL(phy, ch, 2)); + temp &= ~PORT_PLL_M2_FRAC_MASK; + temp |= pll->state.hw_state.pll2; + intel_de_write(dev_priv, BXT_PORT_PLL(phy, ch, 2), temp); + + /* Write M2 fraction enable */ + temp = intel_de_read(dev_priv, BXT_PORT_PLL(phy, ch, 3)); + temp &= ~PORT_PLL_M2_FRAC_ENABLE; + temp |= pll->state.hw_state.pll3; + intel_de_write(dev_priv, BXT_PORT_PLL(phy, ch, 3), temp); + + /* Write coeff */ + temp = intel_de_read(dev_priv, BXT_PORT_PLL(phy, ch, 6)); + temp &= ~PORT_PLL_PROP_COEFF_MASK; + temp &= ~PORT_PLL_INT_COEFF_MASK; + temp &= ~PORT_PLL_GAIN_CTL_MASK; + temp |= pll->state.hw_state.pll6; + intel_de_write(dev_priv, BXT_PORT_PLL(phy, ch, 6), temp); + + /* Write calibration val */ + temp = intel_de_read(dev_priv, BXT_PORT_PLL(phy, ch, 8)); + temp &= ~PORT_PLL_TARGET_CNT_MASK; + temp |= pll->state.hw_state.pll8; + intel_de_write(dev_priv, BXT_PORT_PLL(phy, ch, 8), temp); + + temp = intel_de_read(dev_priv, BXT_PORT_PLL(phy, ch, 9)); + temp &= ~PORT_PLL_LOCK_THRESHOLD_MASK; + temp |= pll->state.hw_state.pll9; + intel_de_write(dev_priv, BXT_PORT_PLL(phy, ch, 9), temp); + + temp = intel_de_read(dev_priv, BXT_PORT_PLL(phy, ch, 10)); + temp &= ~PORT_PLL_DCO_AMP_OVR_EN_H; + temp &= ~PORT_PLL_DCO_AMP_MASK; + temp |= pll->state.hw_state.pll10; + intel_de_write(dev_priv, BXT_PORT_PLL(phy, ch, 10), temp); + + /* Recalibrate with new settings */ + temp = intel_de_read(dev_priv, BXT_PORT_PLL_EBB_4(phy, ch)); + temp |= PORT_PLL_RECALIBRATE; + intel_de_write(dev_priv, BXT_PORT_PLL_EBB_4(phy, ch), temp); + temp &= ~PORT_PLL_10BIT_CLK_ENABLE; + temp |= pll->state.hw_state.ebb4; + intel_de_write(dev_priv, BXT_PORT_PLL_EBB_4(phy, ch), temp); + + /* Enable PLL */ + temp = intel_de_read(dev_priv, BXT_PORT_PLL_ENABLE(port)); + temp |= PORT_PLL_ENABLE; + intel_de_write(dev_priv, BXT_PORT_PLL_ENABLE(port), temp); + intel_de_posting_read(dev_priv, BXT_PORT_PLL_ENABLE(port)); + + if (wait_for_us((intel_de_read(dev_priv, BXT_PORT_PLL_ENABLE(port)) & PORT_PLL_LOCK), + 200)) + drm_err(&dev_priv->drm, "PLL %d not locked\n", port); + + if (IS_GEMINILAKE(dev_priv)) { + temp = intel_de_read(dev_priv, BXT_PORT_TX_DW5_LN0(phy, ch)); + temp |= DCC_DELAY_RANGE_2; + intel_de_write(dev_priv, BXT_PORT_TX_DW5_GRP(phy, ch), temp); + } + + /* + * While we write to the group register to program all lanes at once we + * can read only lane registers and we pick lanes 0/1 for that. + */ + temp = intel_de_read(dev_priv, BXT_PORT_PCS_DW12_LN01(phy, ch)); + temp &= ~LANE_STAGGER_MASK; + temp &= ~LANESTAGGER_STRAP_OVRD; + temp |= pll->state.hw_state.pcsdw12; + intel_de_write(dev_priv, BXT_PORT_PCS_DW12_GRP(phy, ch), temp); +} + +static void bxt_ddi_pll_disable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + enum port port = (enum port)pll->info->id; /* 1:1 port->PLL mapping */ + u32 temp; + + temp = intel_de_read(dev_priv, BXT_PORT_PLL_ENABLE(port)); + temp &= ~PORT_PLL_ENABLE; + intel_de_write(dev_priv, BXT_PORT_PLL_ENABLE(port), temp); + intel_de_posting_read(dev_priv, BXT_PORT_PLL_ENABLE(port)); + + if (IS_GEMINILAKE(dev_priv)) { + temp = intel_de_read(dev_priv, BXT_PORT_PLL_ENABLE(port)); + temp &= ~PORT_PLL_POWER_ENABLE; + intel_de_write(dev_priv, BXT_PORT_PLL_ENABLE(port), temp); + + if (wait_for_us(!(intel_de_read(dev_priv, BXT_PORT_PLL_ENABLE(port)) & + PORT_PLL_POWER_STATE), 200)) + drm_err(&dev_priv->drm, + "Power state not reset for PLL:%d\n", port); + } +} + +static bool bxt_ddi_pll_get_hw_state(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll, + struct intel_dpll_hw_state *hw_state) +{ + enum port port = (enum port)pll->info->id; /* 1:1 port->PLL mapping */ + intel_wakeref_t wakeref; + enum dpio_phy phy; + enum dpio_channel ch; + u32 val; + bool ret; + + bxt_port_to_phy_channel(dev_priv, port, &phy, &ch); + + wakeref = intel_display_power_get_if_enabled(dev_priv, + POWER_DOMAIN_DISPLAY_CORE); + if (!wakeref) + return false; + + ret = false; + + val = intel_de_read(dev_priv, BXT_PORT_PLL_ENABLE(port)); + if (!(val & PORT_PLL_ENABLE)) + goto out; + + hw_state->ebb0 = intel_de_read(dev_priv, BXT_PORT_PLL_EBB_0(phy, ch)); + hw_state->ebb0 &= PORT_PLL_P1_MASK | PORT_PLL_P2_MASK; + + hw_state->ebb4 = intel_de_read(dev_priv, BXT_PORT_PLL_EBB_4(phy, ch)); + hw_state->ebb4 &= PORT_PLL_10BIT_CLK_ENABLE; + + hw_state->pll0 = intel_de_read(dev_priv, BXT_PORT_PLL(phy, ch, 0)); + hw_state->pll0 &= PORT_PLL_M2_INT_MASK; + + hw_state->pll1 = intel_de_read(dev_priv, BXT_PORT_PLL(phy, ch, 1)); + hw_state->pll1 &= PORT_PLL_N_MASK; + + hw_state->pll2 = intel_de_read(dev_priv, BXT_PORT_PLL(phy, ch, 2)); + hw_state->pll2 &= PORT_PLL_M2_FRAC_MASK; + + hw_state->pll3 = intel_de_read(dev_priv, BXT_PORT_PLL(phy, ch, 3)); + hw_state->pll3 &= PORT_PLL_M2_FRAC_ENABLE; + + hw_state->pll6 = intel_de_read(dev_priv, BXT_PORT_PLL(phy, ch, 6)); + hw_state->pll6 &= PORT_PLL_PROP_COEFF_MASK | + PORT_PLL_INT_COEFF_MASK | + PORT_PLL_GAIN_CTL_MASK; + + hw_state->pll8 = intel_de_read(dev_priv, BXT_PORT_PLL(phy, ch, 8)); + hw_state->pll8 &= PORT_PLL_TARGET_CNT_MASK; + + hw_state->pll9 = intel_de_read(dev_priv, BXT_PORT_PLL(phy, ch, 9)); + hw_state->pll9 &= PORT_PLL_LOCK_THRESHOLD_MASK; + + hw_state->pll10 = intel_de_read(dev_priv, BXT_PORT_PLL(phy, ch, 10)); + hw_state->pll10 &= PORT_PLL_DCO_AMP_OVR_EN_H | + PORT_PLL_DCO_AMP_MASK; + + /* + * While we write to the group register to program all lanes at once we + * can read only lane registers. We configure all lanes the same way, so + * here just read out lanes 0/1 and output a note if lanes 2/3 differ. + */ + hw_state->pcsdw12 = intel_de_read(dev_priv, + BXT_PORT_PCS_DW12_LN01(phy, ch)); + if (intel_de_read(dev_priv, BXT_PORT_PCS_DW12_LN23(phy, ch)) != hw_state->pcsdw12) + drm_dbg(&dev_priv->drm, + "lane stagger config different for lane 01 (%08x) and 23 (%08x)\n", + hw_state->pcsdw12, + intel_de_read(dev_priv, + BXT_PORT_PCS_DW12_LN23(phy, ch))); + hw_state->pcsdw12 &= LANE_STAGGER_MASK | LANESTAGGER_STRAP_OVRD; + + ret = true; + +out: + intel_display_power_put(dev_priv, POWER_DOMAIN_DISPLAY_CORE, wakeref); + + return ret; +} + +/* pre-calculated values for DP linkrates */ +static const struct dpll bxt_dp_clk_val[] = { + /* m2 is .22 binary fixed point */ + { .dot = 162000, .p1 = 4, .p2 = 2, .n = 1, .m1 = 2, .m2 = 0x819999a /* 32.4 */ }, + { .dot = 270000, .p1 = 4, .p2 = 1, .n = 1, .m1 = 2, .m2 = 0x6c00000 /* 27.0 */ }, + { .dot = 540000, .p1 = 2, .p2 = 1, .n = 1, .m1 = 2, .m2 = 0x6c00000 /* 27.0 */ }, + { .dot = 216000, .p1 = 3, .p2 = 2, .n = 1, .m1 = 2, .m2 = 0x819999a /* 32.4 */ }, + { .dot = 243000, .p1 = 4, .p2 = 1, .n = 1, .m1 = 2, .m2 = 0x6133333 /* 24.3 */ }, + { .dot = 324000, .p1 = 4, .p2 = 1, .n = 1, .m1 = 2, .m2 = 0x819999a /* 32.4 */ }, + { .dot = 432000, .p1 = 3, .p2 = 1, .n = 1, .m1 = 2, .m2 = 0x819999a /* 32.4 */ }, +}; + +static int +bxt_ddi_hdmi_pll_dividers(struct intel_crtc_state *crtc_state, + struct dpll *clk_div) +{ + struct drm_i915_private *i915 = to_i915(crtc_state->uapi.crtc->dev); + + /* Calculate HDMI div */ + /* + * FIXME: tie the following calculation into + * i9xx_crtc_compute_clock + */ + if (!bxt_find_best_dpll(crtc_state, clk_div)) + return -EINVAL; + + drm_WARN_ON(&i915->drm, clk_div->m1 != 2); + + return 0; +} + +static void bxt_ddi_dp_pll_dividers(struct intel_crtc_state *crtc_state, + struct dpll *clk_div) +{ + struct drm_i915_private *i915 = to_i915(crtc_state->uapi.crtc->dev); + int i; + + *clk_div = bxt_dp_clk_val[0]; + for (i = 0; i < ARRAY_SIZE(bxt_dp_clk_val); ++i) { + if (crtc_state->port_clock == bxt_dp_clk_val[i].dot) { + *clk_div = bxt_dp_clk_val[i]; + break; + } + } + + chv_calc_dpll_params(i915->display.dpll.ref_clks.nssc, clk_div); + + drm_WARN_ON(&i915->drm, clk_div->vco == 0 || + clk_div->dot != crtc_state->port_clock); +} + +static int bxt_ddi_set_dpll_hw_state(struct intel_crtc_state *crtc_state, + const struct dpll *clk_div) +{ + struct drm_i915_private *i915 = to_i915(crtc_state->uapi.crtc->dev); + struct intel_dpll_hw_state *dpll_hw_state = &crtc_state->dpll_hw_state; + int clock = crtc_state->port_clock; + int vco = clk_div->vco; + u32 prop_coef, int_coef, gain_ctl, targ_cnt; + u32 lanestagger; + + if (vco >= 6200000 && vco <= 6700000) { + prop_coef = 4; + int_coef = 9; + gain_ctl = 3; + targ_cnt = 8; + } else if ((vco > 5400000 && vco < 6200000) || + (vco >= 4800000 && vco < 5400000)) { + prop_coef = 5; + int_coef = 11; + gain_ctl = 3; + targ_cnt = 9; + } else if (vco == 5400000) { + prop_coef = 3; + int_coef = 8; + gain_ctl = 1; + targ_cnt = 9; + } else { + drm_err(&i915->drm, "Invalid VCO\n"); + return -EINVAL; + } + + if (clock > 270000) + lanestagger = 0x18; + else if (clock > 135000) + lanestagger = 0x0d; + else if (clock > 67000) + lanestagger = 0x07; + else if (clock > 33000) + lanestagger = 0x04; + else + lanestagger = 0x02; + + dpll_hw_state->ebb0 = PORT_PLL_P1(clk_div->p1) | PORT_PLL_P2(clk_div->p2); + dpll_hw_state->pll0 = PORT_PLL_M2_INT(clk_div->m2 >> 22); + dpll_hw_state->pll1 = PORT_PLL_N(clk_div->n); + dpll_hw_state->pll2 = PORT_PLL_M2_FRAC(clk_div->m2 & 0x3fffff); + + if (clk_div->m2 & 0x3fffff) + dpll_hw_state->pll3 = PORT_PLL_M2_FRAC_ENABLE; + + dpll_hw_state->pll6 = PORT_PLL_PROP_COEFF(prop_coef) | + PORT_PLL_INT_COEFF(int_coef) | + PORT_PLL_GAIN_CTL(gain_ctl); + + dpll_hw_state->pll8 = PORT_PLL_TARGET_CNT(targ_cnt); + + dpll_hw_state->pll9 = PORT_PLL_LOCK_THRESHOLD(5); + + dpll_hw_state->pll10 = PORT_PLL_DCO_AMP(15) | + PORT_PLL_DCO_AMP_OVR_EN_H; + + dpll_hw_state->ebb4 = PORT_PLL_10BIT_CLK_ENABLE; + + dpll_hw_state->pcsdw12 = LANESTAGGER_STRAP_OVRD | lanestagger; + + return 0; +} + +static int bxt_ddi_pll_get_freq(struct drm_i915_private *i915, + const struct intel_shared_dpll *pll, + const struct intel_dpll_hw_state *pll_state) +{ + struct dpll clock; + + clock.m1 = 2; + clock.m2 = REG_FIELD_GET(PORT_PLL_M2_INT_MASK, pll_state->pll0) << 22; + if (pll_state->pll3 & PORT_PLL_M2_FRAC_ENABLE) + clock.m2 |= REG_FIELD_GET(PORT_PLL_M2_FRAC_MASK, pll_state->pll2); + clock.n = REG_FIELD_GET(PORT_PLL_N_MASK, pll_state->pll1); + clock.p1 = REG_FIELD_GET(PORT_PLL_P1_MASK, pll_state->ebb0); + clock.p2 = REG_FIELD_GET(PORT_PLL_P2_MASK, pll_state->ebb0); + + return chv_calc_dpll_params(i915->display.dpll.ref_clks.nssc, &clock); +} + +static int +bxt_ddi_dp_set_dpll_hw_state(struct intel_crtc_state *crtc_state) +{ + struct dpll clk_div = {}; + + bxt_ddi_dp_pll_dividers(crtc_state, &clk_div); + + return bxt_ddi_set_dpll_hw_state(crtc_state, &clk_div); +} + +static int +bxt_ddi_hdmi_set_dpll_hw_state(struct intel_crtc_state *crtc_state) +{ + struct drm_i915_private *i915 = to_i915(crtc_state->uapi.crtc->dev); + struct dpll clk_div = {}; + int ret; + + bxt_ddi_hdmi_pll_dividers(crtc_state, &clk_div); + + ret = bxt_ddi_set_dpll_hw_state(crtc_state, &clk_div); + if (ret) + return ret; + + crtc_state->port_clock = bxt_ddi_pll_get_freq(i915, NULL, + &crtc_state->dpll_hw_state); + + return 0; +} + +static int bxt_compute_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) + return bxt_ddi_hdmi_set_dpll_hw_state(crtc_state); + else if (intel_crtc_has_dp_encoder(crtc_state)) + return bxt_ddi_dp_set_dpll_hw_state(crtc_state); + else + return -EINVAL; +} + +static int bxt_get_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_shared_dpll *pll; + enum intel_dpll_id id; + + /* 1:1 mapping between ports and PLLs */ + id = (enum intel_dpll_id) encoder->port; + pll = intel_get_shared_dpll_by_id(dev_priv, id); + + drm_dbg_kms(&dev_priv->drm, "[CRTC:%d:%s] using pre-allocated %s\n", + crtc->base.base.id, crtc->base.name, pll->info->name); + + intel_reference_shared_dpll(state, crtc, + pll, &crtc_state->dpll_hw_state); + + crtc_state->shared_dpll = pll; + + return 0; +} + +static void bxt_update_dpll_ref_clks(struct drm_i915_private *i915) +{ + i915->display.dpll.ref_clks.ssc = 100000; + i915->display.dpll.ref_clks.nssc = 100000; + /* DSI non-SSC ref 19.2MHz */ +} + +static void bxt_dump_hw_state(struct drm_i915_private *dev_priv, + const struct intel_dpll_hw_state *hw_state) +{ + drm_dbg_kms(&dev_priv->drm, "dpll_hw_state: ebb0: 0x%x, ebb4: 0x%x," + "pll0: 0x%x, pll1: 0x%x, pll2: 0x%x, pll3: 0x%x, " + "pll6: 0x%x, pll8: 0x%x, pll9: 0x%x, pll10: 0x%x, pcsdw12: 0x%x\n", + hw_state->ebb0, + hw_state->ebb4, + hw_state->pll0, + hw_state->pll1, + hw_state->pll2, + hw_state->pll3, + hw_state->pll6, + hw_state->pll8, + hw_state->pll9, + hw_state->pll10, + hw_state->pcsdw12); +} + +static const struct intel_shared_dpll_funcs bxt_ddi_pll_funcs = { + .enable = bxt_ddi_pll_enable, + .disable = bxt_ddi_pll_disable, + .get_hw_state = bxt_ddi_pll_get_hw_state, + .get_freq = bxt_ddi_pll_get_freq, +}; + +static const struct dpll_info bxt_plls[] = { + { "PORT PLL A", &bxt_ddi_pll_funcs, DPLL_ID_SKL_DPLL0, 0 }, + { "PORT PLL B", &bxt_ddi_pll_funcs, DPLL_ID_SKL_DPLL1, 0 }, + { "PORT PLL C", &bxt_ddi_pll_funcs, DPLL_ID_SKL_DPLL2, 0 }, + { }, +}; + +static const struct intel_dpll_mgr bxt_pll_mgr = { + .dpll_info = bxt_plls, + .compute_dplls = bxt_compute_dpll, + .get_dplls = bxt_get_dpll, + .put_dplls = intel_put_dpll, + .update_ref_clks = bxt_update_dpll_ref_clks, + .dump_hw_state = bxt_dump_hw_state, +}; + +static void icl_wrpll_get_multipliers(int bestdiv, int *pdiv, + int *qdiv, int *kdiv) +{ + /* even dividers */ + if (bestdiv % 2 == 0) { + if (bestdiv == 2) { + *pdiv = 2; + *qdiv = 1; + *kdiv = 1; + } else if (bestdiv % 4 == 0) { + *pdiv = 2; + *qdiv = bestdiv / 4; + *kdiv = 2; + } else if (bestdiv % 6 == 0) { + *pdiv = 3; + *qdiv = bestdiv / 6; + *kdiv = 2; + } else if (bestdiv % 5 == 0) { + *pdiv = 5; + *qdiv = bestdiv / 10; + *kdiv = 2; + } else if (bestdiv % 14 == 0) { + *pdiv = 7; + *qdiv = bestdiv / 14; + *kdiv = 2; + } + } else { + if (bestdiv == 3 || bestdiv == 5 || bestdiv == 7) { + *pdiv = bestdiv; + *qdiv = 1; + *kdiv = 1; + } else { /* 9, 15, 21 */ + *pdiv = bestdiv / 3; + *qdiv = 1; + *kdiv = 3; + } + } +} + +static void icl_wrpll_params_populate(struct skl_wrpll_params *params, + u32 dco_freq, u32 ref_freq, + int pdiv, int qdiv, int kdiv) +{ + u32 dco; + + switch (kdiv) { + case 1: + params->kdiv = 1; + break; + case 2: + params->kdiv = 2; + break; + case 3: + params->kdiv = 4; + break; + default: + WARN(1, "Incorrect KDiv\n"); + } + + switch (pdiv) { + case 2: + params->pdiv = 1; + break; + case 3: + params->pdiv = 2; + break; + case 5: + params->pdiv = 4; + break; + case 7: + params->pdiv = 8; + break; + default: + WARN(1, "Incorrect PDiv\n"); + } + + WARN_ON(kdiv != 2 && qdiv != 1); + + params->qdiv_ratio = qdiv; + params->qdiv_mode = (qdiv == 1) ? 0 : 1; + + dco = div_u64((u64)dco_freq << 15, ref_freq); + + params->dco_integer = dco >> 15; + params->dco_fraction = dco & 0x7fff; +} + +/* + * Display WA #22010492432: ehl, tgl, adl-s, adl-p + * Program half of the nominal DCO divider fraction value. + */ +static bool +ehl_combo_pll_div_frac_wa_needed(struct drm_i915_private *i915) +{ + return ((IS_PLATFORM(i915, INTEL_ELKHARTLAKE) && + IS_JSL_EHL_DISPLAY_STEP(i915, STEP_B0, STEP_FOREVER)) || + IS_TIGERLAKE(i915) || IS_ALDERLAKE_S(i915) || IS_ALDERLAKE_P(i915)) && + i915->display.dpll.ref_clks.nssc == 38400; +} + +struct icl_combo_pll_params { + int clock; + struct skl_wrpll_params wrpll; +}; + +/* + * These values alrea already adjusted: they're the bits we write to the + * registers, not the logical values. + */ +static const struct icl_combo_pll_params icl_dp_combo_pll_24MHz_values[] = { + { 540000, + { .dco_integer = 0x151, .dco_fraction = 0x4000, /* [0]: 5.4 */ + .pdiv = 0x2 /* 3 */, .kdiv = 1, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, + { 270000, + { .dco_integer = 0x151, .dco_fraction = 0x4000, /* [1]: 2.7 */ + .pdiv = 0x2 /* 3 */, .kdiv = 2, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, + { 162000, + { .dco_integer = 0x151, .dco_fraction = 0x4000, /* [2]: 1.62 */ + .pdiv = 0x4 /* 5 */, .kdiv = 2, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, + { 324000, + { .dco_integer = 0x151, .dco_fraction = 0x4000, /* [3]: 3.24 */ + .pdiv = 0x4 /* 5 */, .kdiv = 1, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, + { 216000, + { .dco_integer = 0x168, .dco_fraction = 0x0000, /* [4]: 2.16 */ + .pdiv = 0x1 /* 2 */, .kdiv = 2, .qdiv_mode = 1, .qdiv_ratio = 2, }, }, + { 432000, + { .dco_integer = 0x168, .dco_fraction = 0x0000, /* [5]: 4.32 */ + .pdiv = 0x1 /* 2 */, .kdiv = 2, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, + { 648000, + { .dco_integer = 0x195, .dco_fraction = 0x0000, /* [6]: 6.48 */ + .pdiv = 0x2 /* 3 */, .kdiv = 1, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, + { 810000, + { .dco_integer = 0x151, .dco_fraction = 0x4000, /* [7]: 8.1 */ + .pdiv = 0x1 /* 2 */, .kdiv = 1, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, +}; + + +/* Also used for 38.4 MHz values. */ +static const struct icl_combo_pll_params icl_dp_combo_pll_19_2MHz_values[] = { + { 540000, + { .dco_integer = 0x1A5, .dco_fraction = 0x7000, /* [0]: 5.4 */ + .pdiv = 0x2 /* 3 */, .kdiv = 1, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, + { 270000, + { .dco_integer = 0x1A5, .dco_fraction = 0x7000, /* [1]: 2.7 */ + .pdiv = 0x2 /* 3 */, .kdiv = 2, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, + { 162000, + { .dco_integer = 0x1A5, .dco_fraction = 0x7000, /* [2]: 1.62 */ + .pdiv = 0x4 /* 5 */, .kdiv = 2, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, + { 324000, + { .dco_integer = 0x1A5, .dco_fraction = 0x7000, /* [3]: 3.24 */ + .pdiv = 0x4 /* 5 */, .kdiv = 1, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, + { 216000, + { .dco_integer = 0x1C2, .dco_fraction = 0x0000, /* [4]: 2.16 */ + .pdiv = 0x1 /* 2 */, .kdiv = 2, .qdiv_mode = 1, .qdiv_ratio = 2, }, }, + { 432000, + { .dco_integer = 0x1C2, .dco_fraction = 0x0000, /* [5]: 4.32 */ + .pdiv = 0x1 /* 2 */, .kdiv = 2, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, + { 648000, + { .dco_integer = 0x1FA, .dco_fraction = 0x2000, /* [6]: 6.48 */ + .pdiv = 0x2 /* 3 */, .kdiv = 1, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, + { 810000, + { .dco_integer = 0x1A5, .dco_fraction = 0x7000, /* [7]: 8.1 */ + .pdiv = 0x1 /* 2 */, .kdiv = 1, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, +}; + +static const struct skl_wrpll_params icl_tbt_pll_24MHz_values = { + .dco_integer = 0x151, .dco_fraction = 0x4000, + .pdiv = 0x4 /* 5 */, .kdiv = 1, .qdiv_mode = 0, .qdiv_ratio = 0, +}; + +static const struct skl_wrpll_params icl_tbt_pll_19_2MHz_values = { + .dco_integer = 0x1A5, .dco_fraction = 0x7000, + .pdiv = 0x4 /* 5 */, .kdiv = 1, .qdiv_mode = 0, .qdiv_ratio = 0, +}; + +static const struct skl_wrpll_params tgl_tbt_pll_19_2MHz_values = { + .dco_integer = 0x54, .dco_fraction = 0x3000, + /* the following params are unused */ + .pdiv = 0, .kdiv = 0, .qdiv_mode = 0, .qdiv_ratio = 0, +}; + +static const struct skl_wrpll_params tgl_tbt_pll_24MHz_values = { + .dco_integer = 0x43, .dco_fraction = 0x4000, + /* the following params are unused */ +}; + +static int icl_calc_dp_combo_pll(struct intel_crtc_state *crtc_state, + struct skl_wrpll_params *pll_params) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + const struct icl_combo_pll_params *params = + dev_priv->display.dpll.ref_clks.nssc == 24000 ? + icl_dp_combo_pll_24MHz_values : + icl_dp_combo_pll_19_2MHz_values; + int clock = crtc_state->port_clock; + int i; + + for (i = 0; i < ARRAY_SIZE(icl_dp_combo_pll_24MHz_values); i++) { + if (clock == params[i].clock) { + *pll_params = params[i].wrpll; + return 0; + } + } + + MISSING_CASE(clock); + return -EINVAL; +} + +static int icl_calc_tbt_pll(struct intel_crtc_state *crtc_state, + struct skl_wrpll_params *pll_params) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + + if (DISPLAY_VER(dev_priv) >= 12) { + switch (dev_priv->display.dpll.ref_clks.nssc) { + default: + MISSING_CASE(dev_priv->display.dpll.ref_clks.nssc); + fallthrough; + case 19200: + case 38400: + *pll_params = tgl_tbt_pll_19_2MHz_values; + break; + case 24000: + *pll_params = tgl_tbt_pll_24MHz_values; + break; + } + } else { + switch (dev_priv->display.dpll.ref_clks.nssc) { + default: + MISSING_CASE(dev_priv->display.dpll.ref_clks.nssc); + fallthrough; + case 19200: + case 38400: + *pll_params = icl_tbt_pll_19_2MHz_values; + break; + case 24000: + *pll_params = icl_tbt_pll_24MHz_values; + break; + } + } + + return 0; +} + +static int icl_ddi_tbt_pll_get_freq(struct drm_i915_private *i915, + const struct intel_shared_dpll *pll, + const struct intel_dpll_hw_state *pll_state) +{ + /* + * The PLL outputs multiple frequencies at the same time, selection is + * made at DDI clock mux level. + */ + drm_WARN_ON(&i915->drm, 1); + + return 0; +} + +static int icl_wrpll_ref_clock(struct drm_i915_private *i915) +{ + int ref_clock = i915->display.dpll.ref_clks.nssc; + + /* + * For ICL+, the spec states: if reference frequency is 38.4, + * use 19.2 because the DPLL automatically divides that by 2. + */ + if (ref_clock == 38400) + ref_clock = 19200; + + return ref_clock; +} + +static int +icl_calc_wrpll(struct intel_crtc_state *crtc_state, + struct skl_wrpll_params *wrpll_params) +{ + struct drm_i915_private *i915 = to_i915(crtc_state->uapi.crtc->dev); + int ref_clock = icl_wrpll_ref_clock(i915); + u32 afe_clock = crtc_state->port_clock * 5; + u32 dco_min = 7998000; + u32 dco_max = 10000000; + u32 dco_mid = (dco_min + dco_max) / 2; + static const int dividers[] = { 2, 4, 6, 8, 10, 12, 14, 16, + 18, 20, 24, 28, 30, 32, 36, 40, + 42, 44, 48, 50, 52, 54, 56, 60, + 64, 66, 68, 70, 72, 76, 78, 80, + 84, 88, 90, 92, 96, 98, 100, 102, + 3, 5, 7, 9, 15, 21 }; + u32 dco, best_dco = 0, dco_centrality = 0; + u32 best_dco_centrality = U32_MAX; /* Spec meaning of 999999 MHz */ + int d, best_div = 0, pdiv = 0, qdiv = 0, kdiv = 0; + + for (d = 0; d < ARRAY_SIZE(dividers); d++) { + dco = afe_clock * dividers[d]; + + if (dco <= dco_max && dco >= dco_min) { + dco_centrality = abs(dco - dco_mid); + + if (dco_centrality < best_dco_centrality) { + best_dco_centrality = dco_centrality; + best_div = dividers[d]; + best_dco = dco; + } + } + } + + if (best_div == 0) + return -EINVAL; + + icl_wrpll_get_multipliers(best_div, &pdiv, &qdiv, &kdiv); + icl_wrpll_params_populate(wrpll_params, best_dco, ref_clock, + pdiv, qdiv, kdiv); + + return 0; +} + +static int icl_ddi_combo_pll_get_freq(struct drm_i915_private *i915, + const struct intel_shared_dpll *pll, + const struct intel_dpll_hw_state *pll_state) +{ + int ref_clock = icl_wrpll_ref_clock(i915); + u32 dco_fraction; + u32 p0, p1, p2, dco_freq; + + p0 = pll_state->cfgcr1 & DPLL_CFGCR1_PDIV_MASK; + p2 = pll_state->cfgcr1 & DPLL_CFGCR1_KDIV_MASK; + + if (pll_state->cfgcr1 & DPLL_CFGCR1_QDIV_MODE(1)) + p1 = (pll_state->cfgcr1 & DPLL_CFGCR1_QDIV_RATIO_MASK) >> + DPLL_CFGCR1_QDIV_RATIO_SHIFT; + else + p1 = 1; + + switch (p0) { + case DPLL_CFGCR1_PDIV_2: + p0 = 2; + break; + case DPLL_CFGCR1_PDIV_3: + p0 = 3; + break; + case DPLL_CFGCR1_PDIV_5: + p0 = 5; + break; + case DPLL_CFGCR1_PDIV_7: + p0 = 7; + break; + } + + switch (p2) { + case DPLL_CFGCR1_KDIV_1: + p2 = 1; + break; + case DPLL_CFGCR1_KDIV_2: + p2 = 2; + break; + case DPLL_CFGCR1_KDIV_3: + p2 = 3; + break; + } + + dco_freq = (pll_state->cfgcr0 & DPLL_CFGCR0_DCO_INTEGER_MASK) * + ref_clock; + + dco_fraction = (pll_state->cfgcr0 & DPLL_CFGCR0_DCO_FRACTION_MASK) >> + DPLL_CFGCR0_DCO_FRACTION_SHIFT; + + if (ehl_combo_pll_div_frac_wa_needed(i915)) + dco_fraction *= 2; + + dco_freq += (dco_fraction * ref_clock) / 0x8000; + + if (drm_WARN_ON(&i915->drm, p0 == 0 || p1 == 0 || p2 == 0)) + return 0; + + return dco_freq / (p0 * p1 * p2 * 5); +} + +static void icl_calc_dpll_state(struct drm_i915_private *i915, + const struct skl_wrpll_params *pll_params, + struct intel_dpll_hw_state *pll_state) +{ + u32 dco_fraction = pll_params->dco_fraction; + + if (ehl_combo_pll_div_frac_wa_needed(i915)) + dco_fraction = DIV_ROUND_CLOSEST(dco_fraction, 2); + + pll_state->cfgcr0 = DPLL_CFGCR0_DCO_FRACTION(dco_fraction) | + pll_params->dco_integer; + + pll_state->cfgcr1 = DPLL_CFGCR1_QDIV_RATIO(pll_params->qdiv_ratio) | + DPLL_CFGCR1_QDIV_MODE(pll_params->qdiv_mode) | + DPLL_CFGCR1_KDIV(pll_params->kdiv) | + DPLL_CFGCR1_PDIV(pll_params->pdiv); + + if (DISPLAY_VER(i915) >= 12) + pll_state->cfgcr1 |= TGL_DPLL_CFGCR1_CFSELOVRD_NORMAL_XTAL; + else + pll_state->cfgcr1 |= DPLL_CFGCR1_CENTRAL_FREQ_8400; + + if (i915->display.vbt.override_afc_startup) + pll_state->div0 = TGL_DPLL0_DIV0_AFC_STARTUP(i915->display.vbt.override_afc_startup_val); +} + +static int icl_mg_pll_find_divisors(int clock_khz, bool is_dp, bool use_ssc, + u32 *target_dco_khz, + struct intel_dpll_hw_state *state, + bool is_dkl) +{ + static const u8 div1_vals[] = { 7, 5, 3, 2 }; + u32 dco_min_freq, dco_max_freq; + unsigned int i; + int div2; + + dco_min_freq = is_dp ? 8100000 : use_ssc ? 8000000 : 7992000; + dco_max_freq = is_dp ? 8100000 : 10000000; + + for (i = 0; i < ARRAY_SIZE(div1_vals); i++) { + int div1 = div1_vals[i]; + + for (div2 = 10; div2 > 0; div2--) { + int dco = div1 * div2 * clock_khz * 5; + int a_divratio, tlinedrv, inputsel; + u32 hsdiv; + + if (dco < dco_min_freq || dco > dco_max_freq) + continue; + + if (div2 >= 2) { + /* + * Note: a_divratio not matching TGL BSpec + * algorithm but matching hardcoded values and + * working on HW for DP alt-mode at least + */ + a_divratio = is_dp ? 10 : 5; + tlinedrv = is_dkl ? 1 : 2; + } else { + a_divratio = 5; + tlinedrv = 0; + } + inputsel = is_dp ? 0 : 1; + + switch (div1) { + default: + MISSING_CASE(div1); + fallthrough; + case 2: + hsdiv = MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_2; + break; + case 3: + hsdiv = MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_3; + break; + case 5: + hsdiv = MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_5; + break; + case 7: + hsdiv = MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_7; + break; + } + + *target_dco_khz = dco; + + state->mg_refclkin_ctl = MG_REFCLKIN_CTL_OD_2_MUX(1); + + state->mg_clktop2_coreclkctl1 = + MG_CLKTOP2_CORECLKCTL1_A_DIVRATIO(a_divratio); + + state->mg_clktop2_hsclkctl = + MG_CLKTOP2_HSCLKCTL_TLINEDRV_CLKSEL(tlinedrv) | + MG_CLKTOP2_HSCLKCTL_CORE_INPUTSEL(inputsel) | + hsdiv | + MG_CLKTOP2_HSCLKCTL_DSDIV_RATIO(div2); + + return 0; + } + } + + return -EINVAL; +} + +/* + * The specification for this function uses real numbers, so the math had to be + * adapted to integer-only calculation, that's why it looks so different. + */ +static int icl_calc_mg_pll_state(struct intel_crtc_state *crtc_state, + struct intel_dpll_hw_state *pll_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc_state->uapi.crtc->dev); + int refclk_khz = dev_priv->display.dpll.ref_clks.nssc; + int clock = crtc_state->port_clock; + u32 dco_khz, m1div, m2div_int, m2div_rem, m2div_frac; + u32 iref_ndiv, iref_trim, iref_pulse_w; + u32 prop_coeff, int_coeff; + u32 tdc_targetcnt, feedfwgain; + u64 ssc_stepsize, ssc_steplen, ssc_steplog; + u64 tmp; + bool use_ssc = false; + bool is_dp = !intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI); + bool is_dkl = DISPLAY_VER(dev_priv) >= 12; + int ret; + + ret = icl_mg_pll_find_divisors(clock, is_dp, use_ssc, &dco_khz, + pll_state, is_dkl); + if (ret) + return ret; + + m1div = 2; + m2div_int = dco_khz / (refclk_khz * m1div); + if (m2div_int > 255) { + if (!is_dkl) { + m1div = 4; + m2div_int = dco_khz / (refclk_khz * m1div); + } + + if (m2div_int > 255) + return -EINVAL; + } + m2div_rem = dco_khz % (refclk_khz * m1div); + + tmp = (u64)m2div_rem * (1 << 22); + do_div(tmp, refclk_khz * m1div); + m2div_frac = tmp; + + switch (refclk_khz) { + case 19200: + iref_ndiv = 1; + iref_trim = 28; + iref_pulse_w = 1; + break; + case 24000: + iref_ndiv = 1; + iref_trim = 25; + iref_pulse_w = 2; + break; + case 38400: + iref_ndiv = 2; + iref_trim = 28; + iref_pulse_w = 1; + break; + default: + MISSING_CASE(refclk_khz); + return -EINVAL; + } + + /* + * tdc_res = 0.000003 + * tdc_targetcnt = int(2 / (tdc_res * 8 * 50 * 1.1) / refclk_mhz + 0.5) + * + * The multiplication by 1000 is due to refclk MHz to KHz conversion. It + * was supposed to be a division, but we rearranged the operations of + * the formula to avoid early divisions so we don't multiply the + * rounding errors. + * + * 0.000003 * 8 * 50 * 1.1 = 0.00132, also known as 132 / 100000, which + * we also rearrange to work with integers. + * + * The 0.5 transformed to 5 results in a multiplication by 10 and the + * last division by 10. + */ + tdc_targetcnt = (2 * 1000 * 100000 * 10 / (132 * refclk_khz) + 5) / 10; + + /* + * Here we divide dco_khz by 10 in order to allow the dividend to fit in + * 32 bits. That's not a problem since we round the division down + * anyway. + */ + feedfwgain = (use_ssc || m2div_rem > 0) ? + m1div * 1000000 * 100 / (dco_khz * 3 / 10) : 0; + + if (dco_khz >= 9000000) { + prop_coeff = 5; + int_coeff = 10; + } else { + prop_coeff = 4; + int_coeff = 8; + } + + if (use_ssc) { + tmp = mul_u32_u32(dco_khz, 47 * 32); + do_div(tmp, refclk_khz * m1div * 10000); + ssc_stepsize = tmp; + + tmp = mul_u32_u32(dco_khz, 1000); + ssc_steplen = DIV_ROUND_UP_ULL(tmp, 32 * 2 * 32); + } else { + ssc_stepsize = 0; + ssc_steplen = 0; + } + ssc_steplog = 4; + + /* write pll_state calculations */ + if (is_dkl) { + pll_state->mg_pll_div0 = DKL_PLL_DIV0_INTEG_COEFF(int_coeff) | + DKL_PLL_DIV0_PROP_COEFF(prop_coeff) | + DKL_PLL_DIV0_FBPREDIV(m1div) | + DKL_PLL_DIV0_FBDIV_INT(m2div_int); + if (dev_priv->display.vbt.override_afc_startup) { + u8 val = dev_priv->display.vbt.override_afc_startup_val; + + pll_state->mg_pll_div0 |= DKL_PLL_DIV0_AFC_STARTUP(val); + } + + pll_state->mg_pll_div1 = DKL_PLL_DIV1_IREF_TRIM(iref_trim) | + DKL_PLL_DIV1_TDC_TARGET_CNT(tdc_targetcnt); + + pll_state->mg_pll_ssc = DKL_PLL_SSC_IREF_NDIV_RATIO(iref_ndiv) | + DKL_PLL_SSC_STEP_LEN(ssc_steplen) | + DKL_PLL_SSC_STEP_NUM(ssc_steplog) | + (use_ssc ? DKL_PLL_SSC_EN : 0); + + pll_state->mg_pll_bias = (m2div_frac ? DKL_PLL_BIAS_FRAC_EN_H : 0) | + DKL_PLL_BIAS_FBDIV_FRAC(m2div_frac); + + pll_state->mg_pll_tdc_coldst_bias = + DKL_PLL_TDC_SSC_STEP_SIZE(ssc_stepsize) | + DKL_PLL_TDC_FEED_FWD_GAIN(feedfwgain); + + } else { + pll_state->mg_pll_div0 = + (m2div_rem > 0 ? MG_PLL_DIV0_FRACNEN_H : 0) | + MG_PLL_DIV0_FBDIV_FRAC(m2div_frac) | + MG_PLL_DIV0_FBDIV_INT(m2div_int); + + pll_state->mg_pll_div1 = + MG_PLL_DIV1_IREF_NDIVRATIO(iref_ndiv) | + MG_PLL_DIV1_DITHER_DIV_2 | + MG_PLL_DIV1_NDIVRATIO(1) | + MG_PLL_DIV1_FBPREDIV(m1div); + + pll_state->mg_pll_lf = + MG_PLL_LF_TDCTARGETCNT(tdc_targetcnt) | + MG_PLL_LF_AFCCNTSEL_512 | + MG_PLL_LF_GAINCTRL(1) | + MG_PLL_LF_INT_COEFF(int_coeff) | + MG_PLL_LF_PROP_COEFF(prop_coeff); + + pll_state->mg_pll_frac_lock = + MG_PLL_FRAC_LOCK_TRUELOCK_CRIT_32 | + MG_PLL_FRAC_LOCK_EARLYLOCK_CRIT_32 | + MG_PLL_FRAC_LOCK_LOCKTHRESH(10) | + MG_PLL_FRAC_LOCK_DCODITHEREN | + MG_PLL_FRAC_LOCK_FEEDFWRDGAIN(feedfwgain); + if (use_ssc || m2div_rem > 0) + pll_state->mg_pll_frac_lock |= + MG_PLL_FRAC_LOCK_FEEDFWRDCAL_EN; + + pll_state->mg_pll_ssc = + (use_ssc ? MG_PLL_SSC_EN : 0) | + MG_PLL_SSC_TYPE(2) | + MG_PLL_SSC_STEPLENGTH(ssc_steplen) | + MG_PLL_SSC_STEPNUM(ssc_steplog) | + MG_PLL_SSC_FLLEN | + MG_PLL_SSC_STEPSIZE(ssc_stepsize); + + pll_state->mg_pll_tdc_coldst_bias = + MG_PLL_TDC_COLDST_COLDSTART | + MG_PLL_TDC_COLDST_IREFINT_EN | + MG_PLL_TDC_COLDST_REFBIAS_START_PULSE_W(iref_pulse_w) | + MG_PLL_TDC_TDCOVCCORR_EN | + MG_PLL_TDC_TDCSEL(3); + + pll_state->mg_pll_bias = + MG_PLL_BIAS_BIAS_GB_SEL(3) | + MG_PLL_BIAS_INIT_DCOAMP(0x3F) | + MG_PLL_BIAS_BIAS_BONUS(10) | + MG_PLL_BIAS_BIASCAL_EN | + MG_PLL_BIAS_CTRIM(12) | + MG_PLL_BIAS_VREF_RDAC(4) | + MG_PLL_BIAS_IREFTRIM(iref_trim); + + if (refclk_khz == 38400) { + pll_state->mg_pll_tdc_coldst_bias_mask = + MG_PLL_TDC_COLDST_COLDSTART; + pll_state->mg_pll_bias_mask = 0; + } else { + pll_state->mg_pll_tdc_coldst_bias_mask = -1U; + pll_state->mg_pll_bias_mask = -1U; + } + + pll_state->mg_pll_tdc_coldst_bias &= + pll_state->mg_pll_tdc_coldst_bias_mask; + pll_state->mg_pll_bias &= pll_state->mg_pll_bias_mask; + } + + return 0; +} + +static int icl_ddi_mg_pll_get_freq(struct drm_i915_private *dev_priv, + const struct intel_shared_dpll *pll, + const struct intel_dpll_hw_state *pll_state) +{ + u32 m1, m2_int, m2_frac, div1, div2, ref_clock; + u64 tmp; + + ref_clock = dev_priv->display.dpll.ref_clks.nssc; + + if (DISPLAY_VER(dev_priv) >= 12) { + m1 = pll_state->mg_pll_div0 & DKL_PLL_DIV0_FBPREDIV_MASK; + m1 = m1 >> DKL_PLL_DIV0_FBPREDIV_SHIFT; + m2_int = pll_state->mg_pll_div0 & DKL_PLL_DIV0_FBDIV_INT_MASK; + + if (pll_state->mg_pll_bias & DKL_PLL_BIAS_FRAC_EN_H) { + m2_frac = pll_state->mg_pll_bias & + DKL_PLL_BIAS_FBDIV_FRAC_MASK; + m2_frac = m2_frac >> DKL_PLL_BIAS_FBDIV_SHIFT; + } else { + m2_frac = 0; + } + } else { + m1 = pll_state->mg_pll_div1 & MG_PLL_DIV1_FBPREDIV_MASK; + m2_int = pll_state->mg_pll_div0 & MG_PLL_DIV0_FBDIV_INT_MASK; + + if (pll_state->mg_pll_div0 & MG_PLL_DIV0_FRACNEN_H) { + m2_frac = pll_state->mg_pll_div0 & + MG_PLL_DIV0_FBDIV_FRAC_MASK; + m2_frac = m2_frac >> MG_PLL_DIV0_FBDIV_FRAC_SHIFT; + } else { + m2_frac = 0; + } + } + + switch (pll_state->mg_clktop2_hsclkctl & + MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_MASK) { + case MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_2: + div1 = 2; + break; + case MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_3: + div1 = 3; + break; + case MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_5: + div1 = 5; + break; + case MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_7: + div1 = 7; + break; + default: + MISSING_CASE(pll_state->mg_clktop2_hsclkctl); + return 0; + } + + div2 = (pll_state->mg_clktop2_hsclkctl & + MG_CLKTOP2_HSCLKCTL_DSDIV_RATIO_MASK) >> + MG_CLKTOP2_HSCLKCTL_DSDIV_RATIO_SHIFT; + + /* div2 value of 0 is same as 1 means no div */ + if (div2 == 0) + div2 = 1; + + /* + * Adjust the original formula to delay the division by 2^22 in order to + * minimize possible rounding errors. + */ + tmp = (u64)m1 * m2_int * ref_clock + + (((u64)m1 * m2_frac * ref_clock) >> 22); + tmp = div_u64(tmp, 5 * div1 * div2); + + return tmp; +} + +/** + * icl_set_active_port_dpll - select the active port DPLL for a given CRTC + * @crtc_state: state for the CRTC to select the DPLL for + * @port_dpll_id: the active @port_dpll_id to select + * + * Select the given @port_dpll_id instance from the DPLLs reserved for the + * CRTC. + */ +void icl_set_active_port_dpll(struct intel_crtc_state *crtc_state, + enum icl_port_dpll_id port_dpll_id) +{ + struct icl_port_dpll *port_dpll = + &crtc_state->icl_port_dplls[port_dpll_id]; + + crtc_state->shared_dpll = port_dpll->pll; + crtc_state->dpll_hw_state = port_dpll->hw_state; +} + +static void icl_update_active_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct intel_digital_port *primary_port; + enum icl_port_dpll_id port_dpll_id = ICL_PORT_DPLL_DEFAULT; + + primary_port = encoder->type == INTEL_OUTPUT_DP_MST ? + enc_to_mst(encoder)->primary : + enc_to_dig_port(encoder); + + if (primary_port && + (intel_tc_port_in_dp_alt_mode(primary_port) || + intel_tc_port_in_legacy_mode(primary_port))) + port_dpll_id = ICL_PORT_DPLL_MG_PHY; + + icl_set_active_port_dpll(crtc_state, port_dpll_id); +} + +static u32 intel_get_hti_plls(struct drm_i915_private *i915) +{ + if (!(i915->hti_state & HDPORT_ENABLED)) + return 0; + + return REG_FIELD_GET(HDPORT_DPLL_USED_MASK, i915->hti_state); +} + +static int icl_compute_combo_phy_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct icl_port_dpll *port_dpll = + &crtc_state->icl_port_dplls[ICL_PORT_DPLL_DEFAULT]; + struct skl_wrpll_params pll_params = {}; + int ret; + + if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI) || + intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DSI)) + ret = icl_calc_wrpll(crtc_state, &pll_params); + else + ret = icl_calc_dp_combo_pll(crtc_state, &pll_params); + + if (ret) + return ret; + + icl_calc_dpll_state(dev_priv, &pll_params, &port_dpll->hw_state); + + /* this is mainly for the fastset check */ + icl_set_active_port_dpll(crtc_state, ICL_PORT_DPLL_DEFAULT); + + crtc_state->port_clock = icl_ddi_combo_pll_get_freq(dev_priv, NULL, + &port_dpll->hw_state); + + return 0; +} + +static int icl_get_combo_phy_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct icl_port_dpll *port_dpll = + &crtc_state->icl_port_dplls[ICL_PORT_DPLL_DEFAULT]; + enum port port = encoder->port; + unsigned long dpll_mask; + + if (IS_ALDERLAKE_S(dev_priv)) { + dpll_mask = + BIT(DPLL_ID_DG1_DPLL3) | + BIT(DPLL_ID_DG1_DPLL2) | + BIT(DPLL_ID_ICL_DPLL1) | + BIT(DPLL_ID_ICL_DPLL0); + } else if (IS_DG1(dev_priv)) { + if (port == PORT_D || port == PORT_E) { + dpll_mask = + BIT(DPLL_ID_DG1_DPLL2) | + BIT(DPLL_ID_DG1_DPLL3); + } else { + dpll_mask = + BIT(DPLL_ID_DG1_DPLL0) | + BIT(DPLL_ID_DG1_DPLL1); + } + } else if (IS_ROCKETLAKE(dev_priv)) { + dpll_mask = + BIT(DPLL_ID_EHL_DPLL4) | + BIT(DPLL_ID_ICL_DPLL1) | + BIT(DPLL_ID_ICL_DPLL0); + } else if (IS_JSL_EHL(dev_priv) && port != PORT_A) { + dpll_mask = + BIT(DPLL_ID_EHL_DPLL4) | + BIT(DPLL_ID_ICL_DPLL1) | + BIT(DPLL_ID_ICL_DPLL0); + } else { + dpll_mask = BIT(DPLL_ID_ICL_DPLL1) | BIT(DPLL_ID_ICL_DPLL0); + } + + /* Eliminate DPLLs from consideration if reserved by HTI */ + dpll_mask &= ~intel_get_hti_plls(dev_priv); + + port_dpll->pll = intel_find_shared_dpll(state, crtc, + &port_dpll->hw_state, + dpll_mask); + if (!port_dpll->pll) + return -EINVAL; + + intel_reference_shared_dpll(state, crtc, + port_dpll->pll, &port_dpll->hw_state); + + icl_update_active_dpll(state, crtc, encoder); + + return 0; +} + +static int icl_compute_tc_phy_dplls(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct icl_port_dpll *port_dpll = + &crtc_state->icl_port_dplls[ICL_PORT_DPLL_DEFAULT]; + struct skl_wrpll_params pll_params = {}; + int ret; + + port_dpll = &crtc_state->icl_port_dplls[ICL_PORT_DPLL_DEFAULT]; + ret = icl_calc_tbt_pll(crtc_state, &pll_params); + if (ret) + return ret; + + icl_calc_dpll_state(dev_priv, &pll_params, &port_dpll->hw_state); + + port_dpll = &crtc_state->icl_port_dplls[ICL_PORT_DPLL_MG_PHY]; + ret = icl_calc_mg_pll_state(crtc_state, &port_dpll->hw_state); + if (ret) + return ret; + + /* this is mainly for the fastset check */ + icl_set_active_port_dpll(crtc_state, ICL_PORT_DPLL_MG_PHY); + + crtc_state->port_clock = icl_ddi_mg_pll_get_freq(dev_priv, NULL, + &port_dpll->hw_state); + + return 0; +} + +static int icl_get_tc_phy_dplls(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct icl_port_dpll *port_dpll = + &crtc_state->icl_port_dplls[ICL_PORT_DPLL_DEFAULT]; + enum intel_dpll_id dpll_id; + int ret; + + port_dpll = &crtc_state->icl_port_dplls[ICL_PORT_DPLL_DEFAULT]; + port_dpll->pll = intel_find_shared_dpll(state, crtc, + &port_dpll->hw_state, + BIT(DPLL_ID_ICL_TBTPLL)); + if (!port_dpll->pll) + return -EINVAL; + intel_reference_shared_dpll(state, crtc, + port_dpll->pll, &port_dpll->hw_state); + + + port_dpll = &crtc_state->icl_port_dplls[ICL_PORT_DPLL_MG_PHY]; + dpll_id = icl_tc_port_to_pll_id(intel_port_to_tc(dev_priv, + encoder->port)); + port_dpll->pll = intel_find_shared_dpll(state, crtc, + &port_dpll->hw_state, + BIT(dpll_id)); + if (!port_dpll->pll) { + ret = -EINVAL; + goto err_unreference_tbt_pll; + } + intel_reference_shared_dpll(state, crtc, + port_dpll->pll, &port_dpll->hw_state); + + icl_update_active_dpll(state, crtc, encoder); + + return 0; + +err_unreference_tbt_pll: + port_dpll = &crtc_state->icl_port_dplls[ICL_PORT_DPLL_DEFAULT]; + intel_unreference_shared_dpll(state, crtc, port_dpll->pll); + + return ret; +} + +static int icl_compute_dplls(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + enum phy phy = intel_port_to_phy(dev_priv, encoder->port); + + if (intel_phy_is_combo(dev_priv, phy)) + return icl_compute_combo_phy_dpll(state, crtc); + else if (intel_phy_is_tc(dev_priv, phy)) + return icl_compute_tc_phy_dplls(state, crtc); + + MISSING_CASE(phy); + + return 0; +} + +static int icl_get_dplls(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + enum phy phy = intel_port_to_phy(dev_priv, encoder->port); + + if (intel_phy_is_combo(dev_priv, phy)) + return icl_get_combo_phy_dpll(state, crtc, encoder); + else if (intel_phy_is_tc(dev_priv, phy)) + return icl_get_tc_phy_dplls(state, crtc, encoder); + + MISSING_CASE(phy); + + return -EINVAL; +} + +static void icl_put_dplls(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + enum icl_port_dpll_id id; + + new_crtc_state->shared_dpll = NULL; + + for (id = ICL_PORT_DPLL_DEFAULT; id < ICL_PORT_DPLL_COUNT; id++) { + const struct icl_port_dpll *old_port_dpll = + &old_crtc_state->icl_port_dplls[id]; + struct icl_port_dpll *new_port_dpll = + &new_crtc_state->icl_port_dplls[id]; + + new_port_dpll->pll = NULL; + + if (!old_port_dpll->pll) + continue; + + intel_unreference_shared_dpll(state, crtc, old_port_dpll->pll); + } +} + +static bool mg_pll_get_hw_state(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll, + struct intel_dpll_hw_state *hw_state) +{ + const enum intel_dpll_id id = pll->info->id; + enum tc_port tc_port = icl_pll_id_to_tc_port(id); + intel_wakeref_t wakeref; + bool ret = false; + u32 val; + + i915_reg_t enable_reg = intel_tc_pll_enable_reg(dev_priv, pll); + + wakeref = intel_display_power_get_if_enabled(dev_priv, + POWER_DOMAIN_DISPLAY_CORE); + if (!wakeref) + return false; + + val = intel_de_read(dev_priv, enable_reg); + if (!(val & PLL_ENABLE)) + goto out; + + hw_state->mg_refclkin_ctl = intel_de_read(dev_priv, + MG_REFCLKIN_CTL(tc_port)); + hw_state->mg_refclkin_ctl &= MG_REFCLKIN_CTL_OD_2_MUX_MASK; + + hw_state->mg_clktop2_coreclkctl1 = + intel_de_read(dev_priv, MG_CLKTOP2_CORECLKCTL1(tc_port)); + hw_state->mg_clktop2_coreclkctl1 &= + MG_CLKTOP2_CORECLKCTL1_A_DIVRATIO_MASK; + + hw_state->mg_clktop2_hsclkctl = + intel_de_read(dev_priv, MG_CLKTOP2_HSCLKCTL(tc_port)); + hw_state->mg_clktop2_hsclkctl &= + MG_CLKTOP2_HSCLKCTL_TLINEDRV_CLKSEL_MASK | + MG_CLKTOP2_HSCLKCTL_CORE_INPUTSEL_MASK | + MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_MASK | + MG_CLKTOP2_HSCLKCTL_DSDIV_RATIO_MASK; + + hw_state->mg_pll_div0 = intel_de_read(dev_priv, MG_PLL_DIV0(tc_port)); + hw_state->mg_pll_div1 = intel_de_read(dev_priv, MG_PLL_DIV1(tc_port)); + hw_state->mg_pll_lf = intel_de_read(dev_priv, MG_PLL_LF(tc_port)); + hw_state->mg_pll_frac_lock = intel_de_read(dev_priv, + MG_PLL_FRAC_LOCK(tc_port)); + hw_state->mg_pll_ssc = intel_de_read(dev_priv, MG_PLL_SSC(tc_port)); + + hw_state->mg_pll_bias = intel_de_read(dev_priv, MG_PLL_BIAS(tc_port)); + hw_state->mg_pll_tdc_coldst_bias = + intel_de_read(dev_priv, MG_PLL_TDC_COLDST_BIAS(tc_port)); + + if (dev_priv->display.dpll.ref_clks.nssc == 38400) { + hw_state->mg_pll_tdc_coldst_bias_mask = MG_PLL_TDC_COLDST_COLDSTART; + hw_state->mg_pll_bias_mask = 0; + } else { + hw_state->mg_pll_tdc_coldst_bias_mask = -1U; + hw_state->mg_pll_bias_mask = -1U; + } + + hw_state->mg_pll_tdc_coldst_bias &= hw_state->mg_pll_tdc_coldst_bias_mask; + hw_state->mg_pll_bias &= hw_state->mg_pll_bias_mask; + + ret = true; +out: + intel_display_power_put(dev_priv, POWER_DOMAIN_DISPLAY_CORE, wakeref); + return ret; +} + +static bool dkl_pll_get_hw_state(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll, + struct intel_dpll_hw_state *hw_state) +{ + const enum intel_dpll_id id = pll->info->id; + enum tc_port tc_port = icl_pll_id_to_tc_port(id); + intel_wakeref_t wakeref; + bool ret = false; + u32 val; + + wakeref = intel_display_power_get_if_enabled(dev_priv, + POWER_DOMAIN_DISPLAY_CORE); + if (!wakeref) + return false; + + val = intel_de_read(dev_priv, intel_tc_pll_enable_reg(dev_priv, pll)); + if (!(val & PLL_ENABLE)) + goto out; + + /* + * All registers read here have the same HIP_INDEX_REG even though + * they are on different building blocks + */ + hw_state->mg_refclkin_ctl = intel_dkl_phy_read(dev_priv, + DKL_REFCLKIN_CTL(tc_port), 2); + hw_state->mg_refclkin_ctl &= MG_REFCLKIN_CTL_OD_2_MUX_MASK; + + hw_state->mg_clktop2_hsclkctl = + intel_dkl_phy_read(dev_priv, DKL_CLKTOP2_HSCLKCTL(tc_port), 2); + hw_state->mg_clktop2_hsclkctl &= + MG_CLKTOP2_HSCLKCTL_TLINEDRV_CLKSEL_MASK | + MG_CLKTOP2_HSCLKCTL_CORE_INPUTSEL_MASK | + MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_MASK | + MG_CLKTOP2_HSCLKCTL_DSDIV_RATIO_MASK; + + hw_state->mg_clktop2_coreclkctl1 = + intel_dkl_phy_read(dev_priv, DKL_CLKTOP2_CORECLKCTL1(tc_port), 2); + hw_state->mg_clktop2_coreclkctl1 &= + MG_CLKTOP2_CORECLKCTL1_A_DIVRATIO_MASK; + + hw_state->mg_pll_div0 = intel_dkl_phy_read(dev_priv, DKL_PLL_DIV0(tc_port), 2); + val = DKL_PLL_DIV0_MASK; + if (dev_priv->display.vbt.override_afc_startup) + val |= DKL_PLL_DIV0_AFC_STARTUP_MASK; + hw_state->mg_pll_div0 &= val; + + hw_state->mg_pll_div1 = intel_dkl_phy_read(dev_priv, DKL_PLL_DIV1(tc_port), 2); + hw_state->mg_pll_div1 &= (DKL_PLL_DIV1_IREF_TRIM_MASK | + DKL_PLL_DIV1_TDC_TARGET_CNT_MASK); + + hw_state->mg_pll_ssc = intel_dkl_phy_read(dev_priv, DKL_PLL_SSC(tc_port), 2); + hw_state->mg_pll_ssc &= (DKL_PLL_SSC_IREF_NDIV_RATIO_MASK | + DKL_PLL_SSC_STEP_LEN_MASK | + DKL_PLL_SSC_STEP_NUM_MASK | + DKL_PLL_SSC_EN); + + hw_state->mg_pll_bias = intel_dkl_phy_read(dev_priv, DKL_PLL_BIAS(tc_port), 2); + hw_state->mg_pll_bias &= (DKL_PLL_BIAS_FRAC_EN_H | + DKL_PLL_BIAS_FBDIV_FRAC_MASK); + + hw_state->mg_pll_tdc_coldst_bias = + intel_dkl_phy_read(dev_priv, DKL_PLL_TDC_COLDST_BIAS(tc_port), 2); + hw_state->mg_pll_tdc_coldst_bias &= (DKL_PLL_TDC_SSC_STEP_SIZE_MASK | + DKL_PLL_TDC_FEED_FWD_GAIN_MASK); + + ret = true; +out: + intel_display_power_put(dev_priv, POWER_DOMAIN_DISPLAY_CORE, wakeref); + return ret; +} + +static bool icl_pll_get_hw_state(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll, + struct intel_dpll_hw_state *hw_state, + i915_reg_t enable_reg) +{ + const enum intel_dpll_id id = pll->info->id; + intel_wakeref_t wakeref; + bool ret = false; + u32 val; + + wakeref = intel_display_power_get_if_enabled(dev_priv, + POWER_DOMAIN_DISPLAY_CORE); + if (!wakeref) + return false; + + val = intel_de_read(dev_priv, enable_reg); + if (!(val & PLL_ENABLE)) + goto out; + + if (IS_ALDERLAKE_S(dev_priv)) { + hw_state->cfgcr0 = intel_de_read(dev_priv, ADLS_DPLL_CFGCR0(id)); + hw_state->cfgcr1 = intel_de_read(dev_priv, ADLS_DPLL_CFGCR1(id)); + } else if (IS_DG1(dev_priv)) { + hw_state->cfgcr0 = intel_de_read(dev_priv, DG1_DPLL_CFGCR0(id)); + hw_state->cfgcr1 = intel_de_read(dev_priv, DG1_DPLL_CFGCR1(id)); + } else if (IS_ROCKETLAKE(dev_priv)) { + hw_state->cfgcr0 = intel_de_read(dev_priv, + RKL_DPLL_CFGCR0(id)); + hw_state->cfgcr1 = intel_de_read(dev_priv, + RKL_DPLL_CFGCR1(id)); + } else if (DISPLAY_VER(dev_priv) >= 12) { + hw_state->cfgcr0 = intel_de_read(dev_priv, + TGL_DPLL_CFGCR0(id)); + hw_state->cfgcr1 = intel_de_read(dev_priv, + TGL_DPLL_CFGCR1(id)); + if (dev_priv->display.vbt.override_afc_startup) { + hw_state->div0 = intel_de_read(dev_priv, TGL_DPLL0_DIV0(id)); + hw_state->div0 &= TGL_DPLL0_DIV0_AFC_STARTUP_MASK; + } + } else { + if (IS_JSL_EHL(dev_priv) && id == DPLL_ID_EHL_DPLL4) { + hw_state->cfgcr0 = intel_de_read(dev_priv, + ICL_DPLL_CFGCR0(4)); + hw_state->cfgcr1 = intel_de_read(dev_priv, + ICL_DPLL_CFGCR1(4)); + } else { + hw_state->cfgcr0 = intel_de_read(dev_priv, + ICL_DPLL_CFGCR0(id)); + hw_state->cfgcr1 = intel_de_read(dev_priv, + ICL_DPLL_CFGCR1(id)); + } + } + + ret = true; +out: + intel_display_power_put(dev_priv, POWER_DOMAIN_DISPLAY_CORE, wakeref); + return ret; +} + +static bool combo_pll_get_hw_state(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll, + struct intel_dpll_hw_state *hw_state) +{ + i915_reg_t enable_reg = intel_combo_pll_enable_reg(dev_priv, pll); + + return icl_pll_get_hw_state(dev_priv, pll, hw_state, enable_reg); +} + +static bool tbt_pll_get_hw_state(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll, + struct intel_dpll_hw_state *hw_state) +{ + return icl_pll_get_hw_state(dev_priv, pll, hw_state, TBT_PLL_ENABLE); +} + +static void icl_dpll_write(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + struct intel_dpll_hw_state *hw_state = &pll->state.hw_state; + const enum intel_dpll_id id = pll->info->id; + i915_reg_t cfgcr0_reg, cfgcr1_reg, div0_reg = INVALID_MMIO_REG; + + if (IS_ALDERLAKE_S(dev_priv)) { + cfgcr0_reg = ADLS_DPLL_CFGCR0(id); + cfgcr1_reg = ADLS_DPLL_CFGCR1(id); + } else if (IS_DG1(dev_priv)) { + cfgcr0_reg = DG1_DPLL_CFGCR0(id); + cfgcr1_reg = DG1_DPLL_CFGCR1(id); + } else if (IS_ROCKETLAKE(dev_priv)) { + cfgcr0_reg = RKL_DPLL_CFGCR0(id); + cfgcr1_reg = RKL_DPLL_CFGCR1(id); + } else if (DISPLAY_VER(dev_priv) >= 12) { + cfgcr0_reg = TGL_DPLL_CFGCR0(id); + cfgcr1_reg = TGL_DPLL_CFGCR1(id); + div0_reg = TGL_DPLL0_DIV0(id); + } else { + if (IS_JSL_EHL(dev_priv) && id == DPLL_ID_EHL_DPLL4) { + cfgcr0_reg = ICL_DPLL_CFGCR0(4); + cfgcr1_reg = ICL_DPLL_CFGCR1(4); + } else { + cfgcr0_reg = ICL_DPLL_CFGCR0(id); + cfgcr1_reg = ICL_DPLL_CFGCR1(id); + } + } + + intel_de_write(dev_priv, cfgcr0_reg, hw_state->cfgcr0); + intel_de_write(dev_priv, cfgcr1_reg, hw_state->cfgcr1); + drm_WARN_ON_ONCE(&dev_priv->drm, dev_priv->display.vbt.override_afc_startup && + !i915_mmio_reg_valid(div0_reg)); + if (dev_priv->display.vbt.override_afc_startup && + i915_mmio_reg_valid(div0_reg)) + intel_de_rmw(dev_priv, div0_reg, TGL_DPLL0_DIV0_AFC_STARTUP_MASK, + hw_state->div0); + intel_de_posting_read(dev_priv, cfgcr1_reg); +} + +static void icl_mg_pll_write(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + struct intel_dpll_hw_state *hw_state = &pll->state.hw_state; + enum tc_port tc_port = icl_pll_id_to_tc_port(pll->info->id); + u32 val; + + /* + * Some of the following registers have reserved fields, so program + * these with RMW based on a mask. The mask can be fixed or generated + * during the calc/readout phase if the mask depends on some other HW + * state like refclk, see icl_calc_mg_pll_state(). + */ + val = intel_de_read(dev_priv, MG_REFCLKIN_CTL(tc_port)); + val &= ~MG_REFCLKIN_CTL_OD_2_MUX_MASK; + val |= hw_state->mg_refclkin_ctl; + intel_de_write(dev_priv, MG_REFCLKIN_CTL(tc_port), val); + + val = intel_de_read(dev_priv, MG_CLKTOP2_CORECLKCTL1(tc_port)); + val &= ~MG_CLKTOP2_CORECLKCTL1_A_DIVRATIO_MASK; + val |= hw_state->mg_clktop2_coreclkctl1; + intel_de_write(dev_priv, MG_CLKTOP2_CORECLKCTL1(tc_port), val); + + val = intel_de_read(dev_priv, MG_CLKTOP2_HSCLKCTL(tc_port)); + val &= ~(MG_CLKTOP2_HSCLKCTL_TLINEDRV_CLKSEL_MASK | + MG_CLKTOP2_HSCLKCTL_CORE_INPUTSEL_MASK | + MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_MASK | + MG_CLKTOP2_HSCLKCTL_DSDIV_RATIO_MASK); + val |= hw_state->mg_clktop2_hsclkctl; + intel_de_write(dev_priv, MG_CLKTOP2_HSCLKCTL(tc_port), val); + + intel_de_write(dev_priv, MG_PLL_DIV0(tc_port), hw_state->mg_pll_div0); + intel_de_write(dev_priv, MG_PLL_DIV1(tc_port), hw_state->mg_pll_div1); + intel_de_write(dev_priv, MG_PLL_LF(tc_port), hw_state->mg_pll_lf); + intel_de_write(dev_priv, MG_PLL_FRAC_LOCK(tc_port), + hw_state->mg_pll_frac_lock); + intel_de_write(dev_priv, MG_PLL_SSC(tc_port), hw_state->mg_pll_ssc); + + val = intel_de_read(dev_priv, MG_PLL_BIAS(tc_port)); + val &= ~hw_state->mg_pll_bias_mask; + val |= hw_state->mg_pll_bias; + intel_de_write(dev_priv, MG_PLL_BIAS(tc_port), val); + + val = intel_de_read(dev_priv, MG_PLL_TDC_COLDST_BIAS(tc_port)); + val &= ~hw_state->mg_pll_tdc_coldst_bias_mask; + val |= hw_state->mg_pll_tdc_coldst_bias; + intel_de_write(dev_priv, MG_PLL_TDC_COLDST_BIAS(tc_port), val); + + intel_de_posting_read(dev_priv, MG_PLL_TDC_COLDST_BIAS(tc_port)); +} + +static void dkl_pll_write(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + struct intel_dpll_hw_state *hw_state = &pll->state.hw_state; + enum tc_port tc_port = icl_pll_id_to_tc_port(pll->info->id); + u32 val; + + /* + * All registers programmed here have the same HIP_INDEX_REG even + * though on different building block + */ + /* All the registers are RMW */ + val = intel_dkl_phy_read(dev_priv, DKL_REFCLKIN_CTL(tc_port), 2); + val &= ~MG_REFCLKIN_CTL_OD_2_MUX_MASK; + val |= hw_state->mg_refclkin_ctl; + intel_dkl_phy_write(dev_priv, DKL_REFCLKIN_CTL(tc_port), 2, val); + + val = intel_dkl_phy_read(dev_priv, DKL_CLKTOP2_CORECLKCTL1(tc_port), 2); + val &= ~MG_CLKTOP2_CORECLKCTL1_A_DIVRATIO_MASK; + val |= hw_state->mg_clktop2_coreclkctl1; + intel_dkl_phy_write(dev_priv, DKL_CLKTOP2_CORECLKCTL1(tc_port), 2, val); + + val = intel_dkl_phy_read(dev_priv, DKL_CLKTOP2_HSCLKCTL(tc_port), 2); + val &= ~(MG_CLKTOP2_HSCLKCTL_TLINEDRV_CLKSEL_MASK | + MG_CLKTOP2_HSCLKCTL_CORE_INPUTSEL_MASK | + MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_MASK | + MG_CLKTOP2_HSCLKCTL_DSDIV_RATIO_MASK); + val |= hw_state->mg_clktop2_hsclkctl; + intel_dkl_phy_write(dev_priv, DKL_CLKTOP2_HSCLKCTL(tc_port), 2, val); + + val = DKL_PLL_DIV0_MASK; + if (dev_priv->display.vbt.override_afc_startup) + val |= DKL_PLL_DIV0_AFC_STARTUP_MASK; + intel_dkl_phy_rmw(dev_priv, DKL_PLL_DIV0(tc_port), 2, val, + hw_state->mg_pll_div0); + + val = intel_dkl_phy_read(dev_priv, DKL_PLL_DIV1(tc_port), 2); + val &= ~(DKL_PLL_DIV1_IREF_TRIM_MASK | + DKL_PLL_DIV1_TDC_TARGET_CNT_MASK); + val |= hw_state->mg_pll_div1; + intel_dkl_phy_write(dev_priv, DKL_PLL_DIV1(tc_port), 2, val); + + val = intel_dkl_phy_read(dev_priv, DKL_PLL_SSC(tc_port), 2); + val &= ~(DKL_PLL_SSC_IREF_NDIV_RATIO_MASK | + DKL_PLL_SSC_STEP_LEN_MASK | + DKL_PLL_SSC_STEP_NUM_MASK | + DKL_PLL_SSC_EN); + val |= hw_state->mg_pll_ssc; + intel_dkl_phy_write(dev_priv, DKL_PLL_SSC(tc_port), 2, val); + + val = intel_dkl_phy_read(dev_priv, DKL_PLL_BIAS(tc_port), 2); + val &= ~(DKL_PLL_BIAS_FRAC_EN_H | + DKL_PLL_BIAS_FBDIV_FRAC_MASK); + val |= hw_state->mg_pll_bias; + intel_dkl_phy_write(dev_priv, DKL_PLL_BIAS(tc_port), 2, val); + + val = intel_dkl_phy_read(dev_priv, DKL_PLL_TDC_COLDST_BIAS(tc_port), 2); + val &= ~(DKL_PLL_TDC_SSC_STEP_SIZE_MASK | + DKL_PLL_TDC_FEED_FWD_GAIN_MASK); + val |= hw_state->mg_pll_tdc_coldst_bias; + intel_dkl_phy_write(dev_priv, DKL_PLL_TDC_COLDST_BIAS(tc_port), 2, val); + + intel_dkl_phy_posting_read(dev_priv, DKL_PLL_TDC_COLDST_BIAS(tc_port), 2); +} + +static void icl_pll_power_enable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll, + i915_reg_t enable_reg) +{ + u32 val; + + val = intel_de_read(dev_priv, enable_reg); + val |= PLL_POWER_ENABLE; + intel_de_write(dev_priv, enable_reg, val); + + /* + * The spec says we need to "wait" but it also says it should be + * immediate. + */ + if (intel_de_wait_for_set(dev_priv, enable_reg, PLL_POWER_STATE, 1)) + drm_err(&dev_priv->drm, "PLL %d Power not enabled\n", + pll->info->id); +} + +static void icl_pll_enable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll, + i915_reg_t enable_reg) +{ + u32 val; + + val = intel_de_read(dev_priv, enable_reg); + val |= PLL_ENABLE; + intel_de_write(dev_priv, enable_reg, val); + + /* Timeout is actually 600us. */ + if (intel_de_wait_for_set(dev_priv, enable_reg, PLL_LOCK, 1)) + drm_err(&dev_priv->drm, "PLL %d not locked\n", pll->info->id); +} + +static void adlp_cmtg_clock_gating_wa(struct drm_i915_private *i915, struct intel_shared_dpll *pll) +{ + u32 val; + + if (!IS_ADLP_DISPLAY_STEP(i915, STEP_A0, STEP_B0) || + pll->info->id != DPLL_ID_ICL_DPLL0) + return; + /* + * Wa_16011069516:adl-p[a0] + * + * All CMTG regs are unreliable until CMTG clock gating is disabled, + * so we can only assume the default TRANS_CMTG_CHICKEN reg value and + * sanity check this assumption with a double read, which presumably + * returns the correct value even with clock gating on. + * + * Instead of the usual place for workarounds we apply this one here, + * since TRANS_CMTG_CHICKEN is only accessible while DPLL0 is enabled. + */ + val = intel_de_read(i915, TRANS_CMTG_CHICKEN); + val = intel_de_read(i915, TRANS_CMTG_CHICKEN); + intel_de_write(i915, TRANS_CMTG_CHICKEN, DISABLE_DPT_CLK_GATING); + if (drm_WARN_ON(&i915->drm, val & ~DISABLE_DPT_CLK_GATING)) + drm_dbg_kms(&i915->drm, "Unexpected flags in TRANS_CMTG_CHICKEN: %08x\n", val); +} + +static void combo_pll_enable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + i915_reg_t enable_reg = intel_combo_pll_enable_reg(dev_priv, pll); + + if (IS_JSL_EHL(dev_priv) && + pll->info->id == DPLL_ID_EHL_DPLL4) { + + /* + * We need to disable DC states when this DPLL is enabled. + * This can be done by taking a reference on DPLL4 power + * domain. + */ + pll->wakeref = intel_display_power_get(dev_priv, + POWER_DOMAIN_DC_OFF); + } + + icl_pll_power_enable(dev_priv, pll, enable_reg); + + icl_dpll_write(dev_priv, pll); + + /* + * DVFS pre sequence would be here, but in our driver the cdclk code + * paths should already be setting the appropriate voltage, hence we do + * nothing here. + */ + + icl_pll_enable(dev_priv, pll, enable_reg); + + adlp_cmtg_clock_gating_wa(dev_priv, pll); + + /* DVFS post sequence would be here. See the comment above. */ +} + +static void tbt_pll_enable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + icl_pll_power_enable(dev_priv, pll, TBT_PLL_ENABLE); + + icl_dpll_write(dev_priv, pll); + + /* + * DVFS pre sequence would be here, but in our driver the cdclk code + * paths should already be setting the appropriate voltage, hence we do + * nothing here. + */ + + icl_pll_enable(dev_priv, pll, TBT_PLL_ENABLE); + + /* DVFS post sequence would be here. See the comment above. */ +} + +static void mg_pll_enable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + i915_reg_t enable_reg = intel_tc_pll_enable_reg(dev_priv, pll); + + icl_pll_power_enable(dev_priv, pll, enable_reg); + + if (DISPLAY_VER(dev_priv) >= 12) + dkl_pll_write(dev_priv, pll); + else + icl_mg_pll_write(dev_priv, pll); + + /* + * DVFS pre sequence would be here, but in our driver the cdclk code + * paths should already be setting the appropriate voltage, hence we do + * nothing here. + */ + + icl_pll_enable(dev_priv, pll, enable_reg); + + /* DVFS post sequence would be here. See the comment above. */ +} + +static void icl_pll_disable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll, + i915_reg_t enable_reg) +{ + u32 val; + + /* The first steps are done by intel_ddi_post_disable(). */ + + /* + * DVFS pre sequence would be here, but in our driver the cdclk code + * paths should already be setting the appropriate voltage, hence we do + * nothing here. + */ + + val = intel_de_read(dev_priv, enable_reg); + val &= ~PLL_ENABLE; + intel_de_write(dev_priv, enable_reg, val); + + /* Timeout is actually 1us. */ + if (intel_de_wait_for_clear(dev_priv, enable_reg, PLL_LOCK, 1)) + drm_err(&dev_priv->drm, "PLL %d locked\n", pll->info->id); + + /* DVFS post sequence would be here. See the comment above. */ + + val = intel_de_read(dev_priv, enable_reg); + val &= ~PLL_POWER_ENABLE; + intel_de_write(dev_priv, enable_reg, val); + + /* + * The spec says we need to "wait" but it also says it should be + * immediate. + */ + if (intel_de_wait_for_clear(dev_priv, enable_reg, PLL_POWER_STATE, 1)) + drm_err(&dev_priv->drm, "PLL %d Power not disabled\n", + pll->info->id); +} + +static void combo_pll_disable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + i915_reg_t enable_reg = intel_combo_pll_enable_reg(dev_priv, pll); + + icl_pll_disable(dev_priv, pll, enable_reg); + + if (IS_JSL_EHL(dev_priv) && + pll->info->id == DPLL_ID_EHL_DPLL4) + intel_display_power_put(dev_priv, POWER_DOMAIN_DC_OFF, + pll->wakeref); +} + +static void tbt_pll_disable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + icl_pll_disable(dev_priv, pll, TBT_PLL_ENABLE); +} + +static void mg_pll_disable(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll) +{ + i915_reg_t enable_reg = intel_tc_pll_enable_reg(dev_priv, pll); + + icl_pll_disable(dev_priv, pll, enable_reg); +} + +static void icl_update_dpll_ref_clks(struct drm_i915_private *i915) +{ + /* No SSC ref */ + i915->display.dpll.ref_clks.nssc = i915->display.cdclk.hw.ref; +} + +static void icl_dump_hw_state(struct drm_i915_private *dev_priv, + const struct intel_dpll_hw_state *hw_state) +{ + drm_dbg_kms(&dev_priv->drm, + "dpll_hw_state: cfgcr0: 0x%x, cfgcr1: 0x%x, div0: 0x%x, " + "mg_refclkin_ctl: 0x%x, hg_clktop2_coreclkctl1: 0x%x, " + "mg_clktop2_hsclkctl: 0x%x, mg_pll_div0: 0x%x, " + "mg_pll_div2: 0x%x, mg_pll_lf: 0x%x, " + "mg_pll_frac_lock: 0x%x, mg_pll_ssc: 0x%x, " + "mg_pll_bias: 0x%x, mg_pll_tdc_coldst_bias: 0x%x\n", + hw_state->cfgcr0, hw_state->cfgcr1, + hw_state->div0, + hw_state->mg_refclkin_ctl, + hw_state->mg_clktop2_coreclkctl1, + hw_state->mg_clktop2_hsclkctl, + hw_state->mg_pll_div0, + hw_state->mg_pll_div1, + hw_state->mg_pll_lf, + hw_state->mg_pll_frac_lock, + hw_state->mg_pll_ssc, + hw_state->mg_pll_bias, + hw_state->mg_pll_tdc_coldst_bias); +} + +static const struct intel_shared_dpll_funcs combo_pll_funcs = { + .enable = combo_pll_enable, + .disable = combo_pll_disable, + .get_hw_state = combo_pll_get_hw_state, + .get_freq = icl_ddi_combo_pll_get_freq, +}; + +static const struct intel_shared_dpll_funcs tbt_pll_funcs = { + .enable = tbt_pll_enable, + .disable = tbt_pll_disable, + .get_hw_state = tbt_pll_get_hw_state, + .get_freq = icl_ddi_tbt_pll_get_freq, +}; + +static const struct intel_shared_dpll_funcs mg_pll_funcs = { + .enable = mg_pll_enable, + .disable = mg_pll_disable, + .get_hw_state = mg_pll_get_hw_state, + .get_freq = icl_ddi_mg_pll_get_freq, +}; + +static const struct dpll_info icl_plls[] = { + { "DPLL 0", &combo_pll_funcs, DPLL_ID_ICL_DPLL0, 0 }, + { "DPLL 1", &combo_pll_funcs, DPLL_ID_ICL_DPLL1, 0 }, + { "TBT PLL", &tbt_pll_funcs, DPLL_ID_ICL_TBTPLL, 0 }, + { "MG PLL 1", &mg_pll_funcs, DPLL_ID_ICL_MGPLL1, 0 }, + { "MG PLL 2", &mg_pll_funcs, DPLL_ID_ICL_MGPLL2, 0 }, + { "MG PLL 3", &mg_pll_funcs, DPLL_ID_ICL_MGPLL3, 0 }, + { "MG PLL 4", &mg_pll_funcs, DPLL_ID_ICL_MGPLL4, 0 }, + { }, +}; + +static const struct intel_dpll_mgr icl_pll_mgr = { + .dpll_info = icl_plls, + .compute_dplls = icl_compute_dplls, + .get_dplls = icl_get_dplls, + .put_dplls = icl_put_dplls, + .update_active_dpll = icl_update_active_dpll, + .update_ref_clks = icl_update_dpll_ref_clks, + .dump_hw_state = icl_dump_hw_state, +}; + +static const struct dpll_info ehl_plls[] = { + { "DPLL 0", &combo_pll_funcs, DPLL_ID_ICL_DPLL0, 0 }, + { "DPLL 1", &combo_pll_funcs, DPLL_ID_ICL_DPLL1, 0 }, + { "DPLL 4", &combo_pll_funcs, DPLL_ID_EHL_DPLL4, 0 }, + { }, +}; + +static const struct intel_dpll_mgr ehl_pll_mgr = { + .dpll_info = ehl_plls, + .compute_dplls = icl_compute_dplls, + .get_dplls = icl_get_dplls, + .put_dplls = icl_put_dplls, + .update_ref_clks = icl_update_dpll_ref_clks, + .dump_hw_state = icl_dump_hw_state, +}; + +static const struct intel_shared_dpll_funcs dkl_pll_funcs = { + .enable = mg_pll_enable, + .disable = mg_pll_disable, + .get_hw_state = dkl_pll_get_hw_state, + .get_freq = icl_ddi_mg_pll_get_freq, +}; + +static const struct dpll_info tgl_plls[] = { + { "DPLL 0", &combo_pll_funcs, DPLL_ID_ICL_DPLL0, 0 }, + { "DPLL 1", &combo_pll_funcs, DPLL_ID_ICL_DPLL1, 0 }, + { "TBT PLL", &tbt_pll_funcs, DPLL_ID_ICL_TBTPLL, 0 }, + { "TC PLL 1", &dkl_pll_funcs, DPLL_ID_ICL_MGPLL1, 0 }, + { "TC PLL 2", &dkl_pll_funcs, DPLL_ID_ICL_MGPLL2, 0 }, + { "TC PLL 3", &dkl_pll_funcs, DPLL_ID_ICL_MGPLL3, 0 }, + { "TC PLL 4", &dkl_pll_funcs, DPLL_ID_ICL_MGPLL4, 0 }, + { "TC PLL 5", &dkl_pll_funcs, DPLL_ID_TGL_MGPLL5, 0 }, + { "TC PLL 6", &dkl_pll_funcs, DPLL_ID_TGL_MGPLL6, 0 }, + { }, +}; + +static const struct intel_dpll_mgr tgl_pll_mgr = { + .dpll_info = tgl_plls, + .compute_dplls = icl_compute_dplls, + .get_dplls = icl_get_dplls, + .put_dplls = icl_put_dplls, + .update_active_dpll = icl_update_active_dpll, + .update_ref_clks = icl_update_dpll_ref_clks, + .dump_hw_state = icl_dump_hw_state, +}; + +static const struct dpll_info rkl_plls[] = { + { "DPLL 0", &combo_pll_funcs, DPLL_ID_ICL_DPLL0, 0 }, + { "DPLL 1", &combo_pll_funcs, DPLL_ID_ICL_DPLL1, 0 }, + { "DPLL 4", &combo_pll_funcs, DPLL_ID_EHL_DPLL4, 0 }, + { }, +}; + +static const struct intel_dpll_mgr rkl_pll_mgr = { + .dpll_info = rkl_plls, + .compute_dplls = icl_compute_dplls, + .get_dplls = icl_get_dplls, + .put_dplls = icl_put_dplls, + .update_ref_clks = icl_update_dpll_ref_clks, + .dump_hw_state = icl_dump_hw_state, +}; + +static const struct dpll_info dg1_plls[] = { + { "DPLL 0", &combo_pll_funcs, DPLL_ID_DG1_DPLL0, 0 }, + { "DPLL 1", &combo_pll_funcs, DPLL_ID_DG1_DPLL1, 0 }, + { "DPLL 2", &combo_pll_funcs, DPLL_ID_DG1_DPLL2, 0 }, + { "DPLL 3", &combo_pll_funcs, DPLL_ID_DG1_DPLL3, 0 }, + { }, +}; + +static const struct intel_dpll_mgr dg1_pll_mgr = { + .dpll_info = dg1_plls, + .compute_dplls = icl_compute_dplls, + .get_dplls = icl_get_dplls, + .put_dplls = icl_put_dplls, + .update_ref_clks = icl_update_dpll_ref_clks, + .dump_hw_state = icl_dump_hw_state, +}; + +static const struct dpll_info adls_plls[] = { + { "DPLL 0", &combo_pll_funcs, DPLL_ID_ICL_DPLL0, 0 }, + { "DPLL 1", &combo_pll_funcs, DPLL_ID_ICL_DPLL1, 0 }, + { "DPLL 2", &combo_pll_funcs, DPLL_ID_DG1_DPLL2, 0 }, + { "DPLL 3", &combo_pll_funcs, DPLL_ID_DG1_DPLL3, 0 }, + { }, +}; + +static const struct intel_dpll_mgr adls_pll_mgr = { + .dpll_info = adls_plls, + .compute_dplls = icl_compute_dplls, + .get_dplls = icl_get_dplls, + .put_dplls = icl_put_dplls, + .update_ref_clks = icl_update_dpll_ref_clks, + .dump_hw_state = icl_dump_hw_state, +}; + +static const struct dpll_info adlp_plls[] = { + { "DPLL 0", &combo_pll_funcs, DPLL_ID_ICL_DPLL0, 0 }, + { "DPLL 1", &combo_pll_funcs, DPLL_ID_ICL_DPLL1, 0 }, + { "TBT PLL", &tbt_pll_funcs, DPLL_ID_ICL_TBTPLL, 0 }, + { "TC PLL 1", &dkl_pll_funcs, DPLL_ID_ICL_MGPLL1, 0 }, + { "TC PLL 2", &dkl_pll_funcs, DPLL_ID_ICL_MGPLL2, 0 }, + { "TC PLL 3", &dkl_pll_funcs, DPLL_ID_ICL_MGPLL3, 0 }, + { "TC PLL 4", &dkl_pll_funcs, DPLL_ID_ICL_MGPLL4, 0 }, + { }, +}; + +static const struct intel_dpll_mgr adlp_pll_mgr = { + .dpll_info = adlp_plls, + .compute_dplls = icl_compute_dplls, + .get_dplls = icl_get_dplls, + .put_dplls = icl_put_dplls, + .update_active_dpll = icl_update_active_dpll, + .update_ref_clks = icl_update_dpll_ref_clks, + .dump_hw_state = icl_dump_hw_state, +}; + +/** + * intel_shared_dpll_init - Initialize shared DPLLs + * @dev_priv: i915 device + * + * Initialize shared DPLLs for @dev_priv. + */ +void intel_shared_dpll_init(struct drm_i915_private *dev_priv) +{ + const struct intel_dpll_mgr *dpll_mgr = NULL; + const struct dpll_info *dpll_info; + int i; + + if (IS_DG2(dev_priv)) + /* No shared DPLLs on DG2; port PLLs are part of the PHY */ + dpll_mgr = NULL; + else if (IS_ALDERLAKE_P(dev_priv)) + dpll_mgr = &adlp_pll_mgr; + else if (IS_ALDERLAKE_S(dev_priv)) + dpll_mgr = &adls_pll_mgr; + else if (IS_DG1(dev_priv)) + dpll_mgr = &dg1_pll_mgr; + else if (IS_ROCKETLAKE(dev_priv)) + dpll_mgr = &rkl_pll_mgr; + else if (DISPLAY_VER(dev_priv) >= 12) + dpll_mgr = &tgl_pll_mgr; + else if (IS_JSL_EHL(dev_priv)) + dpll_mgr = &ehl_pll_mgr; + else if (DISPLAY_VER(dev_priv) >= 11) + dpll_mgr = &icl_pll_mgr; + else if (IS_GEMINILAKE(dev_priv) || IS_BROXTON(dev_priv)) + dpll_mgr = &bxt_pll_mgr; + else if (DISPLAY_VER(dev_priv) == 9) + dpll_mgr = &skl_pll_mgr; + else if (HAS_DDI(dev_priv)) + dpll_mgr = &hsw_pll_mgr; + else if (HAS_PCH_IBX(dev_priv) || HAS_PCH_CPT(dev_priv)) + dpll_mgr = &pch_pll_mgr; + + if (!dpll_mgr) { + dev_priv->display.dpll.num_shared_dpll = 0; + return; + } + + dpll_info = dpll_mgr->dpll_info; + + for (i = 0; dpll_info[i].name; i++) { + if (drm_WARN_ON(&dev_priv->drm, + i >= ARRAY_SIZE(dev_priv->display.dpll.shared_dplls))) + break; + + drm_WARN_ON(&dev_priv->drm, i != dpll_info[i].id); + dev_priv->display.dpll.shared_dplls[i].info = &dpll_info[i]; + } + + dev_priv->display.dpll.mgr = dpll_mgr; + dev_priv->display.dpll.num_shared_dpll = i; + mutex_init(&dev_priv->display.dpll.lock); +} + +/** + * intel_compute_shared_dplls - compute DPLL state CRTC and encoder combination + * @state: atomic state + * @crtc: CRTC to compute DPLLs for + * @encoder: encoder + * + * This function computes the DPLL state for the given CRTC and encoder. + * + * The new configuration in the atomic commit @state is made effective by + * calling intel_shared_dpll_swap_state(). + * + * Returns: + * 0 on success, negative error code on falure. + */ +int intel_compute_shared_dplls(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + const struct intel_dpll_mgr *dpll_mgr = dev_priv->display.dpll.mgr; + + if (drm_WARN_ON(&dev_priv->drm, !dpll_mgr)) + return -EINVAL; + + return dpll_mgr->compute_dplls(state, crtc, encoder); +} + +/** + * intel_reserve_shared_dplls - reserve DPLLs for CRTC and encoder combination + * @state: atomic state + * @crtc: CRTC to reserve DPLLs for + * @encoder: encoder + * + * This function reserves all required DPLLs for the given CRTC and encoder + * combination in the current atomic commit @state and the new @crtc atomic + * state. + * + * The new configuration in the atomic commit @state is made effective by + * calling intel_shared_dpll_swap_state(). + * + * The reserved DPLLs should be released by calling + * intel_release_shared_dplls(). + * + * Returns: + * 0 if all required DPLLs were successfully reserved, + * negative error code otherwise. + */ +int intel_reserve_shared_dplls(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + const struct intel_dpll_mgr *dpll_mgr = dev_priv->display.dpll.mgr; + + if (drm_WARN_ON(&dev_priv->drm, !dpll_mgr)) + return -EINVAL; + + return dpll_mgr->get_dplls(state, crtc, encoder); +} + +/** + * intel_release_shared_dplls - end use of DPLLs by CRTC in atomic state + * @state: atomic state + * @crtc: crtc from which the DPLLs are to be released + * + * This function releases all DPLLs reserved by intel_reserve_shared_dplls() + * from the current atomic commit @state and the old @crtc atomic state. + * + * The new configuration in the atomic commit @state is made effective by + * calling intel_shared_dpll_swap_state(). + */ +void intel_release_shared_dplls(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct drm_i915_private *dev_priv = to_i915(state->base.dev); + const struct intel_dpll_mgr *dpll_mgr = dev_priv->display.dpll.mgr; + + /* + * FIXME: this function is called for every platform having a + * compute_clock hook, even though the platform doesn't yet support + * the shared DPLL framework and intel_reserve_shared_dplls() is not + * called on those. + */ + if (!dpll_mgr) + return; + + dpll_mgr->put_dplls(state, crtc); +} + +/** + * intel_update_active_dpll - update the active DPLL for a CRTC/encoder + * @state: atomic state + * @crtc: the CRTC for which to update the active DPLL + * @encoder: encoder determining the type of port DPLL + * + * Update the active DPLL for the given @crtc/@encoder in @crtc's atomic state, + * from the port DPLLs reserved previously by intel_reserve_shared_dplls(). The + * DPLL selected will be based on the current mode of the encoder's port. + */ +void intel_update_active_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder) +{ + struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); + const struct intel_dpll_mgr *dpll_mgr = dev_priv->display.dpll.mgr; + + if (drm_WARN_ON(&dev_priv->drm, !dpll_mgr)) + return; + + dpll_mgr->update_active_dpll(state, crtc, encoder); +} + +/** + * intel_dpll_get_freq - calculate the DPLL's output frequency + * @i915: i915 device + * @pll: DPLL for which to calculate the output frequency + * @pll_state: DPLL state from which to calculate the output frequency + * + * Return the output frequency corresponding to @pll's passed in @pll_state. + */ +int intel_dpll_get_freq(struct drm_i915_private *i915, + const struct intel_shared_dpll *pll, + const struct intel_dpll_hw_state *pll_state) +{ + if (drm_WARN_ON(&i915->drm, !pll->info->funcs->get_freq)) + return 0; + + return pll->info->funcs->get_freq(i915, pll, pll_state); +} + +/** + * intel_dpll_get_hw_state - readout the DPLL's hardware state + * @i915: i915 device + * @pll: DPLL for which to calculate the output frequency + * @hw_state: DPLL's hardware state + * + * Read out @pll's hardware state into @hw_state. + */ +bool intel_dpll_get_hw_state(struct drm_i915_private *i915, + struct intel_shared_dpll *pll, + struct intel_dpll_hw_state *hw_state) +{ + return pll->info->funcs->get_hw_state(i915, pll, hw_state); +} + +static void readout_dpll_hw_state(struct drm_i915_private *i915, + struct intel_shared_dpll *pll) +{ + struct intel_crtc *crtc; + + pll->on = intel_dpll_get_hw_state(i915, pll, &pll->state.hw_state); + + if (IS_JSL_EHL(i915) && pll->on && + pll->info->id == DPLL_ID_EHL_DPLL4) { + pll->wakeref = intel_display_power_get(i915, + POWER_DOMAIN_DC_OFF); + } + + pll->state.pipe_mask = 0; + for_each_intel_crtc(&i915->drm, crtc) { + struct intel_crtc_state *crtc_state = + to_intel_crtc_state(crtc->base.state); + + if (crtc_state->hw.active && crtc_state->shared_dpll == pll) + pll->state.pipe_mask |= BIT(crtc->pipe); + } + pll->active_mask = pll->state.pipe_mask; + + drm_dbg_kms(&i915->drm, + "%s hw state readout: pipe_mask 0x%x, on %i\n", + pll->info->name, pll->state.pipe_mask, pll->on); +} + +void intel_dpll_update_ref_clks(struct drm_i915_private *i915) +{ + if (i915->display.dpll.mgr && i915->display.dpll.mgr->update_ref_clks) + i915->display.dpll.mgr->update_ref_clks(i915); +} + +void intel_dpll_readout_hw_state(struct drm_i915_private *i915) +{ + int i; + + for (i = 0; i < i915->display.dpll.num_shared_dpll; i++) + readout_dpll_hw_state(i915, &i915->display.dpll.shared_dplls[i]); +} + +static void sanitize_dpll_state(struct drm_i915_private *i915, + struct intel_shared_dpll *pll) +{ + if (!pll->on) + return; + + adlp_cmtg_clock_gating_wa(i915, pll); + + if (pll->active_mask) + return; + + drm_dbg_kms(&i915->drm, + "%s enabled but not in use, disabling\n", + pll->info->name); + + pll->info->funcs->disable(i915, pll); + pll->on = false; +} + +void intel_dpll_sanitize_state(struct drm_i915_private *i915) +{ + int i; + + for (i = 0; i < i915->display.dpll.num_shared_dpll; i++) + sanitize_dpll_state(i915, &i915->display.dpll.shared_dplls[i]); +} + +/** + * intel_dpll_dump_hw_state - write hw_state to dmesg + * @dev_priv: i915 drm device + * @hw_state: hw state to be written to the log + * + * Write the relevant values in @hw_state to dmesg using drm_dbg_kms. + */ +void intel_dpll_dump_hw_state(struct drm_i915_private *dev_priv, + const struct intel_dpll_hw_state *hw_state) +{ + if (dev_priv->display.dpll.mgr) { + dev_priv->display.dpll.mgr->dump_hw_state(dev_priv, hw_state); + } else { + /* fallback for platforms that don't use the shared dpll + * infrastructure + */ + drm_dbg_kms(&dev_priv->drm, + "dpll_hw_state: dpll: 0x%x, dpll_md: 0x%x, " + "fp0: 0x%x, fp1: 0x%x\n", + hw_state->dpll, + hw_state->dpll_md, + hw_state->fp0, + hw_state->fp1); + } +} + +static void +verify_single_dpll_state(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll, + struct intel_crtc *crtc, + struct intel_crtc_state *new_crtc_state) +{ + struct intel_dpll_hw_state dpll_hw_state; + u8 pipe_mask; + bool active; + + memset(&dpll_hw_state, 0, sizeof(dpll_hw_state)); + + drm_dbg_kms(&dev_priv->drm, "%s\n", pll->info->name); + + active = intel_dpll_get_hw_state(dev_priv, pll, &dpll_hw_state); + + if (!(pll->info->flags & INTEL_DPLL_ALWAYS_ON)) { + I915_STATE_WARN(!pll->on && pll->active_mask, + "pll in active use but not on in sw tracking\n"); + I915_STATE_WARN(pll->on && !pll->active_mask, + "pll is on but not used by any active pipe\n"); + I915_STATE_WARN(pll->on != active, + "pll on state mismatch (expected %i, found %i)\n", + pll->on, active); + } + + if (!crtc) { + I915_STATE_WARN(pll->active_mask & ~pll->state.pipe_mask, + "more active pll users than references: 0x%x vs 0x%x\n", + pll->active_mask, pll->state.pipe_mask); + + return; + } + + pipe_mask = BIT(crtc->pipe); + + if (new_crtc_state->hw.active) + I915_STATE_WARN(!(pll->active_mask & pipe_mask), + "pll active mismatch (expected pipe %c in active mask 0x%x)\n", + pipe_name(crtc->pipe), pll->active_mask); + else + I915_STATE_WARN(pll->active_mask & pipe_mask, + "pll active mismatch (didn't expect pipe %c in active mask 0x%x)\n", + pipe_name(crtc->pipe), pll->active_mask); + + I915_STATE_WARN(!(pll->state.pipe_mask & pipe_mask), + "pll enabled crtcs mismatch (expected 0x%x in 0x%x)\n", + pipe_mask, pll->state.pipe_mask); + + I915_STATE_WARN(pll->on && memcmp(&pll->state.hw_state, + &dpll_hw_state, + sizeof(dpll_hw_state)), + "pll hw state mismatch\n"); +} + +void intel_shared_dpll_state_verify(struct intel_crtc *crtc, + struct intel_crtc_state *old_crtc_state, + struct intel_crtc_state *new_crtc_state) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + + if (new_crtc_state->shared_dpll) + verify_single_dpll_state(dev_priv, new_crtc_state->shared_dpll, + crtc, new_crtc_state); + + if (old_crtc_state->shared_dpll && + old_crtc_state->shared_dpll != new_crtc_state->shared_dpll) { + u8 pipe_mask = BIT(crtc->pipe); + struct intel_shared_dpll *pll = old_crtc_state->shared_dpll; + + I915_STATE_WARN(pll->active_mask & pipe_mask, + "pll active mismatch (didn't expect pipe %c in active mask (0x%x))\n", + pipe_name(crtc->pipe), pll->active_mask); + I915_STATE_WARN(pll->state.pipe_mask & pipe_mask, + "pll enabled crtcs mismatch (found %x in enabled mask (0x%x))\n", + pipe_name(crtc->pipe), pll->state.pipe_mask); + } +} + +void intel_shared_dpll_verify_disabled(struct drm_i915_private *i915) +{ + int i; + + for (i = 0; i < i915->display.dpll.num_shared_dpll; i++) + verify_single_dpll_state(i915, &i915->display.dpll.shared_dplls[i], + NULL, NULL); +} diff --git a/drivers/gpu/drm/i915/display/intel_dpll_mgr.h b/drivers/gpu/drm/i915/display/intel_dpll_mgr.h new file mode 100644 index 000000000..3247dc300 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dpll_mgr.h @@ -0,0 +1,376 @@ +/* + * Copyright © 2012-2016 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +#ifndef _INTEL_DPLL_MGR_H_ +#define _INTEL_DPLL_MGR_H_ + +#include + +#include "intel_wakeref.h" + +/*FIXME: Move this to a more appropriate place. */ +#define abs_diff(a, b) ({ \ + typeof(a) __a = (a); \ + typeof(b) __b = (b); \ + (void) (&__a == &__b); \ + __a > __b ? (__a - __b) : (__b - __a); }) + +enum tc_port; +struct drm_i915_private; +struct intel_atomic_state; +struct intel_crtc; +struct intel_crtc_state; +struct intel_encoder; +struct intel_shared_dpll; +struct intel_shared_dpll_funcs; + +/** + * enum intel_dpll_id - possible DPLL ids + * + * Enumeration of possible IDs for a DPLL. Real shared dpll ids must be >= 0. + */ +enum intel_dpll_id { + /** + * @DPLL_ID_PRIVATE: non-shared dpll in use + */ + DPLL_ID_PRIVATE = -1, + + /** + * @DPLL_ID_PCH_PLL_A: DPLL A in ILK, SNB and IVB + */ + DPLL_ID_PCH_PLL_A = 0, + /** + * @DPLL_ID_PCH_PLL_B: DPLL B in ILK, SNB and IVB + */ + DPLL_ID_PCH_PLL_B = 1, + + + /** + * @DPLL_ID_WRPLL1: HSW and BDW WRPLL1 + */ + DPLL_ID_WRPLL1 = 0, + /** + * @DPLL_ID_WRPLL2: HSW and BDW WRPLL2 + */ + DPLL_ID_WRPLL2 = 1, + /** + * @DPLL_ID_SPLL: HSW and BDW SPLL + */ + DPLL_ID_SPLL = 2, + /** + * @DPLL_ID_LCPLL_810: HSW and BDW 0.81 GHz LCPLL + */ + DPLL_ID_LCPLL_810 = 3, + /** + * @DPLL_ID_LCPLL_1350: HSW and BDW 1.35 GHz LCPLL + */ + DPLL_ID_LCPLL_1350 = 4, + /** + * @DPLL_ID_LCPLL_2700: HSW and BDW 2.7 GHz LCPLL + */ + DPLL_ID_LCPLL_2700 = 5, + + + /** + * @DPLL_ID_SKL_DPLL0: SKL and later DPLL0 + */ + DPLL_ID_SKL_DPLL0 = 0, + /** + * @DPLL_ID_SKL_DPLL1: SKL and later DPLL1 + */ + DPLL_ID_SKL_DPLL1 = 1, + /** + * @DPLL_ID_SKL_DPLL2: SKL and later DPLL2 + */ + DPLL_ID_SKL_DPLL2 = 2, + /** + * @DPLL_ID_SKL_DPLL3: SKL and later DPLL3 + */ + DPLL_ID_SKL_DPLL3 = 3, + + + /** + * @DPLL_ID_ICL_DPLL0: ICL/TGL combo PHY DPLL0 + */ + DPLL_ID_ICL_DPLL0 = 0, + /** + * @DPLL_ID_ICL_DPLL1: ICL/TGL combo PHY DPLL1 + */ + DPLL_ID_ICL_DPLL1 = 1, + /** + * @DPLL_ID_EHL_DPLL4: EHL combo PHY DPLL4 + */ + DPLL_ID_EHL_DPLL4 = 2, + /** + * @DPLL_ID_ICL_TBTPLL: ICL/TGL TBT PLL + */ + DPLL_ID_ICL_TBTPLL = 2, + /** + * @DPLL_ID_ICL_MGPLL1: ICL MG PLL 1 port 1 (C), + * TGL TC PLL 1 port 1 (TC1) + */ + DPLL_ID_ICL_MGPLL1 = 3, + /** + * @DPLL_ID_ICL_MGPLL2: ICL MG PLL 1 port 2 (D) + * TGL TC PLL 1 port 2 (TC2) + */ + DPLL_ID_ICL_MGPLL2 = 4, + /** + * @DPLL_ID_ICL_MGPLL3: ICL MG PLL 1 port 3 (E) + * TGL TC PLL 1 port 3 (TC3) + */ + DPLL_ID_ICL_MGPLL3 = 5, + /** + * @DPLL_ID_ICL_MGPLL4: ICL MG PLL 1 port 4 (F) + * TGL TC PLL 1 port 4 (TC4) + */ + DPLL_ID_ICL_MGPLL4 = 6, + /** + * @DPLL_ID_TGL_MGPLL5: TGL TC PLL port 5 (TC5) + */ + DPLL_ID_TGL_MGPLL5 = 7, + /** + * @DPLL_ID_TGL_MGPLL6: TGL TC PLL port 6 (TC6) + */ + DPLL_ID_TGL_MGPLL6 = 8, + + /** + * @DPLL_ID_DG1_DPLL0: DG1 combo PHY DPLL0 + */ + DPLL_ID_DG1_DPLL0 = 0, + /** + * @DPLL_ID_DG1_DPLL1: DG1 combo PHY DPLL1 + */ + DPLL_ID_DG1_DPLL1 = 1, + /** + * @DPLL_ID_DG1_DPLL2: DG1 combo PHY DPLL2 + */ + DPLL_ID_DG1_DPLL2 = 2, + /** + * @DPLL_ID_DG1_DPLL3: DG1 combo PHY DPLL3 + */ + DPLL_ID_DG1_DPLL3 = 3, +}; + +#define I915_NUM_PLLS 9 + +enum icl_port_dpll_id { + ICL_PORT_DPLL_DEFAULT, + ICL_PORT_DPLL_MG_PHY, + + ICL_PORT_DPLL_COUNT, +}; + +struct intel_dpll_hw_state { + /* i9xx, pch plls */ + u32 dpll; + u32 dpll_md; + u32 fp0; + u32 fp1; + + /* hsw, bdw */ + u32 wrpll; + u32 spll; + + /* skl */ + /* + * DPLL_CTRL1 has 6 bits for each each this DPLL. We store those in + * lower part of ctrl1 and they get shifted into position when writing + * the register. This allows us to easily compare the state to share + * the DPLL. + */ + u32 ctrl1; + /* HDMI only, 0 when used for DP */ + u32 cfgcr1, cfgcr2; + + /* icl */ + u32 cfgcr0; + + /* tgl */ + u32 div0; + + /* bxt */ + u32 ebb0, ebb4, pll0, pll1, pll2, pll3, pll6, pll8, pll9, pll10, pcsdw12; + + /* + * ICL uses the following, already defined: + * u32 cfgcr0, cfgcr1; + */ + u32 mg_refclkin_ctl; + u32 mg_clktop2_coreclkctl1; + u32 mg_clktop2_hsclkctl; + u32 mg_pll_div0; + u32 mg_pll_div1; + u32 mg_pll_lf; + u32 mg_pll_frac_lock; + u32 mg_pll_ssc; + u32 mg_pll_bias; + u32 mg_pll_tdc_coldst_bias; + u32 mg_pll_bias_mask; + u32 mg_pll_tdc_coldst_bias_mask; +}; + +/** + * struct intel_shared_dpll_state - hold the DPLL atomic state + * + * This structure holds an atomic state for the DPLL, that can represent + * either its current state (in struct &intel_shared_dpll) or a desired + * future state which would be applied by an atomic mode set (stored in + * a struct &intel_atomic_state). + * + * See also intel_reserve_shared_dplls() and intel_release_shared_dplls(). + */ +struct intel_shared_dpll_state { + /** + * @pipe_mask: mask of pipes using this DPLL, active or not + */ + u8 pipe_mask; + + /** + * @hw_state: hardware configuration for the DPLL stored in + * struct &intel_dpll_hw_state. + */ + struct intel_dpll_hw_state hw_state; +}; + +/** + * struct dpll_info - display PLL platform specific info + */ +struct dpll_info { + /** + * @name: DPLL name; used for logging + */ + const char *name; + + /** + * @funcs: platform specific hooks + */ + const struct intel_shared_dpll_funcs *funcs; + + /** + * @id: unique indentifier for this DPLL; should match the index in the + * dev_priv->shared_dplls array + */ + enum intel_dpll_id id; + +#define INTEL_DPLL_ALWAYS_ON (1 << 0) + /** + * @flags: + * + * INTEL_DPLL_ALWAYS_ON + * Inform the state checker that the DPLL is kept enabled even if + * not in use by any CRTC. + */ + u32 flags; +}; + +/** + * struct intel_shared_dpll - display PLL with tracked state and users + */ +struct intel_shared_dpll { + /** + * @state: + * + * Store the state for the pll, including its hw state + * and CRTCs using it. + */ + struct intel_shared_dpll_state state; + + /** + * @active_mask: mask of active pipes (i.e. DPMS on) using this DPLL + */ + u8 active_mask; + + /** + * @on: is the PLL actually active? Disabled during modeset + */ + bool on; + + /** + * @info: platform specific info + */ + const struct dpll_info *info; + + /** + * @wakeref: In some platforms a device-level runtime pm reference may + * need to be grabbed to disable DC states while this DPLL is enabled + */ + intel_wakeref_t wakeref; +}; + +#define SKL_DPLL0 0 +#define SKL_DPLL1 1 +#define SKL_DPLL2 2 +#define SKL_DPLL3 3 + +/* shared dpll functions */ +struct intel_shared_dpll * +intel_get_shared_dpll_by_id(struct drm_i915_private *dev_priv, + enum intel_dpll_id id); +enum intel_dpll_id +intel_get_shared_dpll_id(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll); +void assert_shared_dpll(struct drm_i915_private *dev_priv, + struct intel_shared_dpll *pll, + bool state); +#define assert_shared_dpll_enabled(d, p) assert_shared_dpll(d, p, true) +#define assert_shared_dpll_disabled(d, p) assert_shared_dpll(d, p, false) +int intel_compute_shared_dplls(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder); +int intel_reserve_shared_dplls(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder); +void intel_release_shared_dplls(struct intel_atomic_state *state, + struct intel_crtc *crtc); +void icl_set_active_port_dpll(struct intel_crtc_state *crtc_state, + enum icl_port_dpll_id port_dpll_id); +void intel_update_active_dpll(struct intel_atomic_state *state, + struct intel_crtc *crtc, + struct intel_encoder *encoder); +int intel_dpll_get_freq(struct drm_i915_private *i915, + const struct intel_shared_dpll *pll, + const struct intel_dpll_hw_state *pll_state); +bool intel_dpll_get_hw_state(struct drm_i915_private *i915, + struct intel_shared_dpll *pll, + struct intel_dpll_hw_state *hw_state); +void intel_enable_shared_dpll(const struct intel_crtc_state *crtc_state); +void intel_disable_shared_dpll(const struct intel_crtc_state *crtc_state); +void intel_shared_dpll_swap_state(struct intel_atomic_state *state); +void intel_shared_dpll_init(struct drm_i915_private *dev_priv); +void intel_dpll_update_ref_clks(struct drm_i915_private *dev_priv); +void intel_dpll_readout_hw_state(struct drm_i915_private *dev_priv); +void intel_dpll_sanitize_state(struct drm_i915_private *dev_priv); + +void intel_dpll_dump_hw_state(struct drm_i915_private *dev_priv, + const struct intel_dpll_hw_state *hw_state); +enum intel_dpll_id icl_tc_port_to_pll_id(enum tc_port tc_port); +bool intel_dpll_is_combophy(enum intel_dpll_id id); + +void intel_shared_dpll_state_verify(struct intel_crtc *crtc, + struct intel_crtc_state *old_crtc_state, + struct intel_crtc_state *new_crtc_state); +void intel_shared_dpll_verify_disabled(struct drm_i915_private *i915); + +#endif /* _INTEL_DPLL_MGR_H_ */ diff --git a/drivers/gpu/drm/i915/display/intel_dpt.c b/drivers/gpu/drm/i915/display/intel_dpt.c new file mode 100644 index 000000000..ea8a08b9c --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dpt.c @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2021 Intel Corporation + */ + +#include "gem/i915_gem_domain.h" +#include "gem/i915_gem_internal.h" +#include "gt/gen8_ppgtt.h" + +#include "i915_drv.h" +#include "intel_display_types.h" +#include "intel_dpt.h" +#include "intel_fb.h" + +struct i915_dpt { + struct i915_address_space vm; + + struct drm_i915_gem_object *obj; + struct i915_vma *vma; + void __iomem *iomem; +}; + +#define i915_is_dpt(vm) ((vm)->is_dpt) + +static inline struct i915_dpt * +i915_vm_to_dpt(struct i915_address_space *vm) +{ + BUILD_BUG_ON(offsetof(struct i915_dpt, vm)); + GEM_BUG_ON(!i915_is_dpt(vm)); + return container_of(vm, struct i915_dpt, vm); +} + +#define dpt_total_entries(dpt) ((dpt)->vm.total >> PAGE_SHIFT) + +static void gen8_set_pte(void __iomem *addr, gen8_pte_t pte) +{ + writeq(pte, addr); +} + +static void dpt_insert_page(struct i915_address_space *vm, + dma_addr_t addr, + u64 offset, + enum i915_cache_level level, + u32 flags) +{ + struct i915_dpt *dpt = i915_vm_to_dpt(vm); + gen8_pte_t __iomem *base = dpt->iomem; + + gen8_set_pte(base + offset / I915_GTT_PAGE_SIZE, + vm->pte_encode(addr, level, flags)); +} + +static void dpt_insert_entries(struct i915_address_space *vm, + struct i915_vma_resource *vma_res, + enum i915_cache_level level, + u32 flags) +{ + struct i915_dpt *dpt = i915_vm_to_dpt(vm); + gen8_pte_t __iomem *base = dpt->iomem; + const gen8_pte_t pte_encode = vm->pte_encode(0, level, flags); + struct sgt_iter sgt_iter; + dma_addr_t addr; + int i; + + /* + * Note that we ignore PTE_READ_ONLY here. The caller must be careful + * not to allow the user to override access to a read only page. + */ + + i = vma_res->start / I915_GTT_PAGE_SIZE; + for_each_sgt_daddr(addr, sgt_iter, vma_res->bi.pages) + gen8_set_pte(&base[i++], pte_encode | addr); +} + +static void dpt_clear_range(struct i915_address_space *vm, + u64 start, u64 length) +{ +} + +static void dpt_bind_vma(struct i915_address_space *vm, + struct i915_vm_pt_stash *stash, + struct i915_vma_resource *vma_res, + enum i915_cache_level cache_level, + u32 flags) +{ + u32 pte_flags; + + if (vma_res->bound_flags) + return; + + /* Applicable to VLV (gen8+ do not support RO in the GGTT) */ + pte_flags = 0; + if (vm->has_read_only && vma_res->bi.readonly) + pte_flags |= PTE_READ_ONLY; + if (vma_res->bi.lmem) + pte_flags |= PTE_LM; + + vm->insert_entries(vm, vma_res, cache_level, pte_flags); + + vma_res->page_sizes_gtt = I915_GTT_PAGE_SIZE; + + /* + * Without aliasing PPGTT there's no difference between + * GLOBAL/LOCAL_BIND, it's all the same ptes. Hence unconditionally + * upgrade to both bound if we bind either to avoid double-binding. + */ + vma_res->bound_flags = I915_VMA_GLOBAL_BIND | I915_VMA_LOCAL_BIND; +} + +static void dpt_unbind_vma(struct i915_address_space *vm, + struct i915_vma_resource *vma_res) +{ + vm->clear_range(vm, vma_res->start, vma_res->vma_size); +} + +static void dpt_cleanup(struct i915_address_space *vm) +{ + struct i915_dpt *dpt = i915_vm_to_dpt(vm); + + i915_gem_object_put(dpt->obj); +} + +struct i915_vma *intel_dpt_pin(struct i915_address_space *vm) +{ + struct drm_i915_private *i915 = vm->i915; + struct i915_dpt *dpt = i915_vm_to_dpt(vm); + intel_wakeref_t wakeref; + struct i915_vma *vma; + void __iomem *iomem; + struct i915_gem_ww_ctx ww; + u64 pin_flags = 0; + int err; + + if (i915_gem_object_is_stolen(dpt->obj)) + pin_flags |= PIN_MAPPABLE; + + wakeref = intel_runtime_pm_get(&i915->runtime_pm); + atomic_inc(&i915->gpu_error.pending_fb_pin); + + for_i915_gem_ww(&ww, err, true) { + err = i915_gem_object_lock(dpt->obj, &ww); + if (err) + continue; + + vma = i915_gem_object_ggtt_pin_ww(dpt->obj, &ww, NULL, 0, 4096, + pin_flags); + if (IS_ERR(vma)) { + err = PTR_ERR(vma); + continue; + } + + iomem = i915_vma_pin_iomap(vma); + i915_vma_unpin(vma); + + if (IS_ERR(iomem)) { + err = PTR_ERR(iomem); + continue; + } + + dpt->vma = vma; + dpt->iomem = iomem; + + i915_vma_get(vma); + } + + dpt->obj->mm.dirty = true; + + atomic_dec(&i915->gpu_error.pending_fb_pin); + intel_runtime_pm_put(&i915->runtime_pm, wakeref); + + return err ? ERR_PTR(err) : vma; +} + +void intel_dpt_unpin(struct i915_address_space *vm) +{ + struct i915_dpt *dpt = i915_vm_to_dpt(vm); + + i915_vma_unpin_iomap(dpt->vma); + i915_vma_put(dpt->vma); +} + +/** + * intel_dpt_resume - restore the memory mapping for all DPT FBs during system resume + * @i915: device instance + * + * Restore the memory mapping during system resume for all framebuffers which + * are mapped to HW via a GGTT->DPT page table. The content of these page + * tables are not stored in the hibernation image during S4 and S3RST->S4 + * transitions, so here we reprogram the PTE entries in those tables. + * + * This function must be called after the mappings in GGTT have been restored calling + * i915_ggtt_resume(). + */ +void intel_dpt_resume(struct drm_i915_private *i915) +{ + struct drm_framebuffer *drm_fb; + + if (!HAS_DISPLAY(i915)) + return; + + mutex_lock(&i915->drm.mode_config.fb_lock); + drm_for_each_fb(drm_fb, &i915->drm) { + struct intel_framebuffer *fb = to_intel_framebuffer(drm_fb); + + if (fb->dpt_vm) + i915_ggtt_resume_vm(fb->dpt_vm); + } + mutex_unlock(&i915->drm.mode_config.fb_lock); +} + +/** + * intel_dpt_suspend - suspend the memory mapping for all DPT FBs during system suspend + * @i915: device instance + * + * Suspend the memory mapping during system suspend for all framebuffers which + * are mapped to HW via a GGTT->DPT page table. + * + * This function must be called before the mappings in GGTT are suspended calling + * i915_ggtt_suspend(). + */ +void intel_dpt_suspend(struct drm_i915_private *i915) +{ + struct drm_framebuffer *drm_fb; + + if (!HAS_DISPLAY(i915)) + return; + + mutex_lock(&i915->drm.mode_config.fb_lock); + + drm_for_each_fb(drm_fb, &i915->drm) { + struct intel_framebuffer *fb = to_intel_framebuffer(drm_fb); + + if (fb->dpt_vm) + i915_ggtt_suspend_vm(fb->dpt_vm); + } + + mutex_unlock(&i915->drm.mode_config.fb_lock); +} + +struct i915_address_space * +intel_dpt_create(struct intel_framebuffer *fb) +{ + struct drm_gem_object *obj = &intel_fb_obj(&fb->base)->base; + struct drm_i915_private *i915 = to_i915(obj->dev); + struct drm_i915_gem_object *dpt_obj; + struct i915_address_space *vm; + struct i915_dpt *dpt; + size_t size; + int ret; + + if (intel_fb_needs_pot_stride_remap(fb)) + size = intel_remapped_info_size(&fb->remapped_view.gtt.remapped); + else + size = DIV_ROUND_UP_ULL(obj->size, I915_GTT_PAGE_SIZE); + + size = round_up(size * sizeof(gen8_pte_t), I915_GTT_PAGE_SIZE); + + dpt_obj = i915_gem_object_create_lmem(i915, size, I915_BO_ALLOC_CONTIGUOUS); + if (IS_ERR(dpt_obj) && i915_ggtt_has_aperture(to_gt(i915)->ggtt)) + dpt_obj = i915_gem_object_create_stolen(i915, size); + if (IS_ERR(dpt_obj) && !HAS_LMEM(i915)) { + drm_dbg_kms(&i915->drm, "Allocating dpt from smem\n"); + dpt_obj = i915_gem_object_create_shmem(i915, size); + } + if (IS_ERR(dpt_obj)) + return ERR_CAST(dpt_obj); + + ret = i915_gem_object_lock_interruptible(dpt_obj, NULL); + if (!ret) { + ret = i915_gem_object_set_cache_level(dpt_obj, I915_CACHE_NONE); + i915_gem_object_unlock(dpt_obj); + } + if (ret) { + i915_gem_object_put(dpt_obj); + return ERR_PTR(ret); + } + + dpt = kzalloc(sizeof(*dpt), GFP_KERNEL); + if (!dpt) { + i915_gem_object_put(dpt_obj); + return ERR_PTR(-ENOMEM); + } + + vm = &dpt->vm; + + vm->gt = to_gt(i915); + vm->i915 = i915; + vm->dma = i915->drm.dev; + vm->total = (size / sizeof(gen8_pte_t)) * I915_GTT_PAGE_SIZE; + vm->is_dpt = true; + + i915_address_space_init(vm, VM_CLASS_DPT); + + vm->insert_page = dpt_insert_page; + vm->clear_range = dpt_clear_range; + vm->insert_entries = dpt_insert_entries; + vm->cleanup = dpt_cleanup; + + vm->vma_ops.bind_vma = dpt_bind_vma; + vm->vma_ops.unbind_vma = dpt_unbind_vma; + + vm->pte_encode = gen8_ggtt_pte_encode; + + dpt->obj = dpt_obj; + dpt->obj->is_dpt = true; + + return &dpt->vm; +} + +void intel_dpt_destroy(struct i915_address_space *vm) +{ + struct i915_dpt *dpt = i915_vm_to_dpt(vm); + + dpt->obj->is_dpt = false; + i915_vm_put(&dpt->vm); +} diff --git a/drivers/gpu/drm/i915/display/intel_dpt.h b/drivers/gpu/drm/i915/display/intel_dpt.h new file mode 100644 index 000000000..e18a9f767 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dpt.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2021 Intel Corporation + */ + +#ifndef __INTEL_DPT_H__ +#define __INTEL_DPT_H__ + +struct drm_i915_private; + +struct i915_address_space; +struct i915_vma; +struct intel_framebuffer; + +void intel_dpt_destroy(struct i915_address_space *vm); +struct i915_vma *intel_dpt_pin(struct i915_address_space *vm); +void intel_dpt_unpin(struct i915_address_space *vm); +void intel_dpt_suspend(struct drm_i915_private *i915); +void intel_dpt_resume(struct drm_i915_private *i915); +struct i915_address_space * +intel_dpt_create(struct intel_framebuffer *fb); + +#endif /* __INTEL_DPT_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_drrs.c b/drivers/gpu/drm/i915/display/intel_drrs.c new file mode 100644 index 000000000..7da4a9cbe --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_drrs.c @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2021 Intel Corporation + */ + +#include "i915_drv.h" +#include "intel_atomic.h" +#include "intel_de.h" +#include "intel_display_types.h" +#include "intel_drrs.h" +#include "intel_panel.h" + +/** + * DOC: Display Refresh Rate Switching (DRRS) + * + * Display Refresh Rate Switching (DRRS) is a power conservation feature + * which enables swtching between low and high refresh rates, + * dynamically, based on the usage scenario. This feature is applicable + * for internal panels. + * + * Indication that the panel supports DRRS is given by the panel EDID, which + * would list multiple refresh rates for one resolution. + * + * DRRS is of 2 types - static and seamless. + * Static DRRS involves changing refresh rate (RR) by doing a full modeset + * (may appear as a blink on screen) and is used in dock-undock scenario. + * Seamless DRRS involves changing RR without any visual effect to the user + * and can be used during normal system usage. This is done by programming + * certain registers. + * + * Support for static/seamless DRRS may be indicated in the VBT based on + * inputs from the panel spec. + * + * DRRS saves power by switching to low RR based on usage scenarios. + * + * The implementation is based on frontbuffer tracking implementation. When + * there is a disturbance on the screen triggered by user activity or a periodic + * system activity, DRRS is disabled (RR is changed to high RR). When there is + * no movement on screen, after a timeout of 1 second, a switch to low RR is + * made. + * + * For integration with frontbuffer tracking code, intel_drrs_invalidate() + * and intel_drrs_flush() are called. + * + * DRRS can be further extended to support other internal panels and also + * the scenario of video playback wherein RR is set based on the rate + * requested by userspace. + */ + +const char *intel_drrs_type_str(enum drrs_type drrs_type) +{ + static const char * const str[] = { + [DRRS_TYPE_NONE] = "none", + [DRRS_TYPE_STATIC] = "static", + [DRRS_TYPE_SEAMLESS] = "seamless", + }; + + if (drrs_type >= ARRAY_SIZE(str)) + return ""; + + return str[drrs_type]; +} + +static void +intel_drrs_set_refresh_rate_pipeconf(struct intel_crtc *crtc, + enum drrs_refresh_rate refresh_rate) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + enum transcoder cpu_transcoder = crtc->drrs.cpu_transcoder; + u32 val, bit; + + if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) + bit = PIPECONF_REFRESH_RATE_ALT_VLV; + else + bit = PIPECONF_REFRESH_RATE_ALT_ILK; + + val = intel_de_read(dev_priv, PIPECONF(cpu_transcoder)); + + if (refresh_rate == DRRS_REFRESH_RATE_LOW) + val |= bit; + else + val &= ~bit; + + intel_de_write(dev_priv, PIPECONF(cpu_transcoder), val); +} + +static void +intel_drrs_set_refresh_rate_m_n(struct intel_crtc *crtc, + enum drrs_refresh_rate refresh_rate) +{ + intel_cpu_transcoder_set_m1_n1(crtc, crtc->drrs.cpu_transcoder, + refresh_rate == DRRS_REFRESH_RATE_LOW ? + &crtc->drrs.m2_n2 : &crtc->drrs.m_n); +} + +bool intel_drrs_is_active(struct intel_crtc *crtc) +{ + return crtc->drrs.cpu_transcoder != INVALID_TRANSCODER; +} + +static void intel_drrs_set_state(struct intel_crtc *crtc, + enum drrs_refresh_rate refresh_rate) +{ + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + + if (refresh_rate == crtc->drrs.refresh_rate) + return; + + if (intel_cpu_transcoder_has_m2_n2(dev_priv, crtc->drrs.cpu_transcoder)) + intel_drrs_set_refresh_rate_pipeconf(crtc, refresh_rate); + else + intel_drrs_set_refresh_rate_m_n(crtc, refresh_rate); + + crtc->drrs.refresh_rate = refresh_rate; +} + +static void intel_drrs_schedule_work(struct intel_crtc *crtc) +{ + mod_delayed_work(system_wq, &crtc->drrs.work, msecs_to_jiffies(1000)); +} + +static unsigned int intel_drrs_frontbuffer_bits(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + unsigned int frontbuffer_bits; + + frontbuffer_bits = INTEL_FRONTBUFFER_ALL_MASK(crtc->pipe); + + for_each_intel_crtc_in_pipe_mask(&i915->drm, crtc, + crtc_state->bigjoiner_pipes) + frontbuffer_bits |= INTEL_FRONTBUFFER_ALL_MASK(crtc->pipe); + + return frontbuffer_bits; +} + +/** + * intel_drrs_activate - activate DRRS + * @crtc_state: the crtc state + * + * Activates DRRS on the crtc. + */ +void intel_drrs_activate(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + if (!crtc_state->has_drrs) + return; + + if (!crtc_state->hw.active) + return; + + if (intel_crtc_is_bigjoiner_slave(crtc_state)) + return; + + mutex_lock(&crtc->drrs.mutex); + + crtc->drrs.cpu_transcoder = crtc_state->cpu_transcoder; + crtc->drrs.m_n = crtc_state->dp_m_n; + crtc->drrs.m2_n2 = crtc_state->dp_m2_n2; + crtc->drrs.frontbuffer_bits = intel_drrs_frontbuffer_bits(crtc_state); + crtc->drrs.busy_frontbuffer_bits = 0; + + intel_drrs_schedule_work(crtc); + + mutex_unlock(&crtc->drrs.mutex); +} + +/** + * intel_drrs_deactivate - deactivate DRRS + * @old_crtc_state: the old crtc state + * + * Deactivates DRRS on the crtc. + */ +void intel_drrs_deactivate(const struct intel_crtc_state *old_crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->uapi.crtc); + + if (!old_crtc_state->has_drrs) + return; + + if (!old_crtc_state->hw.active) + return; + + if (intel_crtc_is_bigjoiner_slave(old_crtc_state)) + return; + + mutex_lock(&crtc->drrs.mutex); + + if (intel_drrs_is_active(crtc)) + intel_drrs_set_state(crtc, DRRS_REFRESH_RATE_HIGH); + + crtc->drrs.cpu_transcoder = INVALID_TRANSCODER; + crtc->drrs.frontbuffer_bits = 0; + crtc->drrs.busy_frontbuffer_bits = 0; + + mutex_unlock(&crtc->drrs.mutex); + + cancel_delayed_work_sync(&crtc->drrs.work); +} + +static void intel_drrs_downclock_work(struct work_struct *work) +{ + struct intel_crtc *crtc = container_of(work, typeof(*crtc), drrs.work.work); + + mutex_lock(&crtc->drrs.mutex); + + if (intel_drrs_is_active(crtc) && !crtc->drrs.busy_frontbuffer_bits) + intel_drrs_set_state(crtc, DRRS_REFRESH_RATE_LOW); + + mutex_unlock(&crtc->drrs.mutex); +} + +static void intel_drrs_frontbuffer_update(struct drm_i915_private *dev_priv, + unsigned int all_frontbuffer_bits, + bool invalidate) +{ + struct intel_crtc *crtc; + + for_each_intel_crtc(&dev_priv->drm, crtc) { + unsigned int frontbuffer_bits; + + mutex_lock(&crtc->drrs.mutex); + + frontbuffer_bits = all_frontbuffer_bits & crtc->drrs.frontbuffer_bits; + if (!frontbuffer_bits) { + mutex_unlock(&crtc->drrs.mutex); + continue; + } + + if (invalidate) + crtc->drrs.busy_frontbuffer_bits |= frontbuffer_bits; + else + crtc->drrs.busy_frontbuffer_bits &= ~frontbuffer_bits; + + /* flush/invalidate means busy screen hence upclock */ + intel_drrs_set_state(crtc, DRRS_REFRESH_RATE_HIGH); + + /* + * flush also means no more activity hence schedule downclock, if all + * other fbs are quiescent too + */ + if (!crtc->drrs.busy_frontbuffer_bits) + intel_drrs_schedule_work(crtc); + else + cancel_delayed_work(&crtc->drrs.work); + + mutex_unlock(&crtc->drrs.mutex); + } +} + +/** + * intel_drrs_invalidate - Disable Idleness DRRS + * @dev_priv: i915 device + * @frontbuffer_bits: frontbuffer plane tracking bits + * + * This function gets called everytime rendering on the given planes start. + * Hence DRRS needs to be Upclocked, i.e. (LOW_RR -> HIGH_RR). + * + * Dirty frontbuffers relevant to DRRS are tracked in busy_frontbuffer_bits. + */ +void intel_drrs_invalidate(struct drm_i915_private *dev_priv, + unsigned int frontbuffer_bits) +{ + intel_drrs_frontbuffer_update(dev_priv, frontbuffer_bits, true); +} + +/** + * intel_drrs_flush - Restart Idleness DRRS + * @dev_priv: i915 device + * @frontbuffer_bits: frontbuffer plane tracking bits + * + * This function gets called every time rendering on the given planes has + * completed or flip on a crtc is completed. So DRRS should be upclocked + * (LOW_RR -> HIGH_RR). And also Idleness detection should be started again, + * if no other planes are dirty. + * + * Dirty frontbuffers relevant to DRRS are tracked in busy_frontbuffer_bits. + */ +void intel_drrs_flush(struct drm_i915_private *dev_priv, + unsigned int frontbuffer_bits) +{ + intel_drrs_frontbuffer_update(dev_priv, frontbuffer_bits, false); +} + +/** + * intel_crtc_drrs_init - Init DRRS for CRTC + * @crtc: crtc + * + * This function is called only once at driver load to initialize basic + * DRRS stuff. + * + */ +void intel_crtc_drrs_init(struct intel_crtc *crtc) +{ + INIT_DELAYED_WORK(&crtc->drrs.work, intel_drrs_downclock_work); + mutex_init(&crtc->drrs.mutex); + crtc->drrs.cpu_transcoder = INVALID_TRANSCODER; +} diff --git a/drivers/gpu/drm/i915/display/intel_drrs.h b/drivers/gpu/drm/i915/display/intel_drrs.h new file mode 100644 index 000000000..3ad1be1ad --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_drrs.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2021 Intel Corporation + */ + +#ifndef __INTEL_DRRS_H__ +#define __INTEL_DRRS_H__ + +#include + +enum drrs_type; +struct drm_i915_private; +struct intel_atomic_state; +struct intel_crtc; +struct intel_crtc_state; +struct intel_connector; + +const char *intel_drrs_type_str(enum drrs_type drrs_type); +bool intel_drrs_is_active(struct intel_crtc *crtc); +void intel_drrs_activate(const struct intel_crtc_state *crtc_state); +void intel_drrs_deactivate(const struct intel_crtc_state *crtc_state); +void intel_drrs_invalidate(struct drm_i915_private *dev_priv, + unsigned int frontbuffer_bits); +void intel_drrs_flush(struct drm_i915_private *dev_priv, + unsigned int frontbuffer_bits); +void intel_crtc_drrs_init(struct intel_crtc *crtc); + +#endif /* __INTEL_DRRS_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_dsb.c b/drivers/gpu/drm/i915/display/intel_dsb.c new file mode 100644 index 000000000..fc9c3e41c --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dsb.c @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2019 Intel Corporation + * + */ + +#include "gem/i915_gem_internal.h" + +#include "i915_drv.h" +#include "intel_de.h" +#include "intel_display_types.h" +#include "intel_dsb.h" + +struct i915_vma; + +enum dsb_id { + INVALID_DSB = -1, + DSB1, + DSB2, + DSB3, + MAX_DSB_PER_PIPE +}; + +struct intel_dsb { + enum dsb_id id; + u32 *cmd_buf; + struct i915_vma *vma; + + /* + * free_pos will point the first free entry position + * and help in calculating tail of command buffer. + */ + int free_pos; + + /* + * ins_start_offset will help to store start address of the dsb + * instuction and help in identifying the batch of auto-increment + * register. + */ + u32 ins_start_offset; +}; + +#define DSB_BUF_SIZE (2 * PAGE_SIZE) + +/** + * DOC: DSB + * + * A DSB (Display State Buffer) is a queue of MMIO instructions in the memory + * which can be offloaded to DSB HW in Display Controller. DSB HW is a DMA + * engine that can be programmed to download the DSB from memory. + * It allows driver to batch submit display HW programming. This helps to + * reduce loading time and CPU activity, thereby making the context switch + * faster. DSB Support added from Gen12 Intel graphics based platform. + * + * DSB's can access only the pipe, plane, and transcoder Data Island Packet + * registers. + * + * DSB HW can support only register writes (both indexed and direct MMIO + * writes). There are no registers reads possible with DSB HW engine. + */ + +/* DSB opcodes. */ +#define DSB_OPCODE_SHIFT 24 +#define DSB_OPCODE_MMIO_WRITE 0x1 +#define DSB_OPCODE_INDEXED_WRITE 0x9 +#define DSB_BYTE_EN 0xF +#define DSB_BYTE_EN_SHIFT 20 +#define DSB_REG_VALUE_MASK 0xfffff + +static bool is_dsb_busy(struct drm_i915_private *i915, enum pipe pipe, + enum dsb_id id) +{ + return DSB_STATUS & intel_de_read(i915, DSB_CTRL(pipe, id)); +} + +static bool intel_dsb_enable_engine(struct drm_i915_private *i915, + enum pipe pipe, enum dsb_id id) +{ + u32 dsb_ctrl; + + dsb_ctrl = intel_de_read(i915, DSB_CTRL(pipe, id)); + if (DSB_STATUS & dsb_ctrl) { + drm_dbg_kms(&i915->drm, "DSB engine is busy.\n"); + return false; + } + + dsb_ctrl |= DSB_ENABLE; + intel_de_write(i915, DSB_CTRL(pipe, id), dsb_ctrl); + + intel_de_posting_read(i915, DSB_CTRL(pipe, id)); + return true; +} + +static bool intel_dsb_disable_engine(struct drm_i915_private *i915, + enum pipe pipe, enum dsb_id id) +{ + u32 dsb_ctrl; + + dsb_ctrl = intel_de_read(i915, DSB_CTRL(pipe, id)); + if (DSB_STATUS & dsb_ctrl) { + drm_dbg_kms(&i915->drm, "DSB engine is busy.\n"); + return false; + } + + dsb_ctrl &= ~DSB_ENABLE; + intel_de_write(i915, DSB_CTRL(pipe, id), dsb_ctrl); + + intel_de_posting_read(i915, DSB_CTRL(pipe, id)); + return true; +} + +/** + * intel_dsb_indexed_reg_write() -Write to the DSB context for auto + * increment register. + * @crtc_state: intel_crtc_state structure + * @reg: register address. + * @val: value. + * + * This function is used for writing register-value pair in command + * buffer of DSB for auto-increment register. During command buffer overflow, + * a warning is thrown and rest all erroneous condition register programming + * is done through mmio write. + */ + +void intel_dsb_indexed_reg_write(const struct intel_crtc_state *crtc_state, + i915_reg_t reg, u32 val) +{ + struct intel_dsb *dsb = crtc_state->dsb; + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + u32 *buf; + u32 reg_val; + + if (!dsb) { + intel_de_write_fw(dev_priv, reg, val); + return; + } + buf = dsb->cmd_buf; + if (drm_WARN_ON(&dev_priv->drm, dsb->free_pos >= DSB_BUF_SIZE)) { + drm_dbg_kms(&dev_priv->drm, "DSB buffer overflow\n"); + return; + } + + /* + * For example the buffer will look like below for 3 dwords for auto + * increment register: + * +--------------------------------------------------------+ + * | size = 3 | offset &| value1 | value2 | value3 | zero | + * | | opcode | | | | | + * +--------------------------------------------------------+ + * + + + + + + + + * 0 4 8 12 16 20 24 + * Byte + * + * As every instruction is 8 byte aligned the index of dsb instruction + * will start always from even number while dealing with u32 array. If + * we are writing odd no of dwords, Zeros will be added in the end for + * padding. + */ + reg_val = buf[dsb->ins_start_offset + 1] & DSB_REG_VALUE_MASK; + if (reg_val != i915_mmio_reg_offset(reg)) { + /* Every instruction should be 8 byte aligned. */ + dsb->free_pos = ALIGN(dsb->free_pos, 2); + + dsb->ins_start_offset = dsb->free_pos; + + /* Update the size. */ + buf[dsb->free_pos++] = 1; + + /* Update the opcode and reg. */ + buf[dsb->free_pos++] = (DSB_OPCODE_INDEXED_WRITE << + DSB_OPCODE_SHIFT) | + i915_mmio_reg_offset(reg); + + /* Update the value. */ + buf[dsb->free_pos++] = val; + } else { + /* Update the new value. */ + buf[dsb->free_pos++] = val; + + /* Update the size. */ + buf[dsb->ins_start_offset]++; + } + + /* if number of data words is odd, then the last dword should be 0.*/ + if (dsb->free_pos & 0x1) + buf[dsb->free_pos] = 0; +} + +/** + * intel_dsb_reg_write() -Write to the DSB context for normal + * register. + * @crtc_state: intel_crtc_state structure + * @reg: register address. + * @val: value. + * + * This function is used for writing register-value pair in command + * buffer of DSB. During command buffer overflow, a warning is thrown + * and rest all erroneous condition register programming is done + * through mmio write. + */ +void intel_dsb_reg_write(const struct intel_crtc_state *crtc_state, + i915_reg_t reg, u32 val) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); + struct intel_dsb *dsb; + u32 *buf; + + dsb = crtc_state->dsb; + if (!dsb) { + intel_de_write_fw(dev_priv, reg, val); + return; + } + + buf = dsb->cmd_buf; + if (drm_WARN_ON(&dev_priv->drm, dsb->free_pos >= DSB_BUF_SIZE)) { + drm_dbg_kms(&dev_priv->drm, "DSB buffer overflow\n"); + return; + } + + dsb->ins_start_offset = dsb->free_pos; + buf[dsb->free_pos++] = val; + buf[dsb->free_pos++] = (DSB_OPCODE_MMIO_WRITE << DSB_OPCODE_SHIFT) | + (DSB_BYTE_EN << DSB_BYTE_EN_SHIFT) | + i915_mmio_reg_offset(reg); +} + +/** + * intel_dsb_commit() - Trigger workload execution of DSB. + * @crtc_state: intel_crtc_state structure + * + * This function is used to do actual write to hardware using DSB. + * On errors, fall back to MMIO. Also this function help to reset the context. + */ +void intel_dsb_commit(const struct intel_crtc_state *crtc_state) +{ + struct intel_dsb *dsb = crtc_state->dsb; + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_device *dev = crtc->base.dev; + struct drm_i915_private *dev_priv = to_i915(dev); + enum pipe pipe = crtc->pipe; + u32 tail; + + if (!(dsb && dsb->free_pos)) + return; + + if (!intel_dsb_enable_engine(dev_priv, pipe, dsb->id)) + goto reset; + + if (is_dsb_busy(dev_priv, pipe, dsb->id)) { + drm_err(&dev_priv->drm, + "HEAD_PTR write failed - dsb engine is busy.\n"); + goto reset; + } + intel_de_write(dev_priv, DSB_HEAD(pipe, dsb->id), + i915_ggtt_offset(dsb->vma)); + + tail = ALIGN(dsb->free_pos * 4, CACHELINE_BYTES); + if (tail > dsb->free_pos * 4) + memset(&dsb->cmd_buf[dsb->free_pos], 0, + (tail - dsb->free_pos * 4)); + + if (is_dsb_busy(dev_priv, pipe, dsb->id)) { + drm_err(&dev_priv->drm, + "TAIL_PTR write failed - dsb engine is busy.\n"); + goto reset; + } + drm_dbg_kms(&dev_priv->drm, + "DSB execution started - head 0x%x, tail 0x%x\n", + i915_ggtt_offset(dsb->vma), tail); + intel_de_write(dev_priv, DSB_TAIL(pipe, dsb->id), + i915_ggtt_offset(dsb->vma) + tail); + if (wait_for(!is_dsb_busy(dev_priv, pipe, dsb->id), 1)) { + drm_err(&dev_priv->drm, + "Timed out waiting for DSB workload completion.\n"); + goto reset; + } + +reset: + dsb->free_pos = 0; + dsb->ins_start_offset = 0; + intel_dsb_disable_engine(dev_priv, pipe, dsb->id); +} + +/** + * intel_dsb_prepare() - Allocate, pin and map the DSB command buffer. + * @crtc_state: intel_crtc_state structure to prepare associated dsb instance. + * + * This function prepare the command buffer which is used to store dsb + * instructions with data. + */ +void intel_dsb_prepare(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_i915_private *i915 = to_i915(crtc->base.dev); + struct intel_dsb *dsb; + struct drm_i915_gem_object *obj; + struct i915_vma *vma; + u32 *buf; + intel_wakeref_t wakeref; + + if (!HAS_DSB(i915)) + return; + + dsb = kmalloc(sizeof(*dsb), GFP_KERNEL); + if (!dsb) { + drm_err(&i915->drm, "DSB object creation failed\n"); + return; + } + + wakeref = intel_runtime_pm_get(&i915->runtime_pm); + + obj = i915_gem_object_create_internal(i915, DSB_BUF_SIZE); + if (IS_ERR(obj)) { + kfree(dsb); + goto out; + } + + vma = i915_gem_object_ggtt_pin(obj, NULL, 0, 0, 0); + if (IS_ERR(vma)) { + i915_gem_object_put(obj); + kfree(dsb); + goto out; + } + + buf = i915_gem_object_pin_map_unlocked(vma->obj, I915_MAP_WC); + if (IS_ERR(buf)) { + i915_vma_unpin_and_release(&vma, I915_VMA_RELEASE_MAP); + kfree(dsb); + goto out; + } + + dsb->id = DSB1; + dsb->vma = vma; + dsb->cmd_buf = buf; + dsb->free_pos = 0; + dsb->ins_start_offset = 0; + crtc_state->dsb = dsb; +out: + if (!crtc_state->dsb) + drm_info(&i915->drm, + "DSB queue setup failed, will fallback to MMIO for display HW programming\n"); + + intel_runtime_pm_put(&i915->runtime_pm, wakeref); +} + +/** + * intel_dsb_cleanup() - To cleanup DSB context. + * @crtc_state: intel_crtc_state structure to cleanup associated dsb instance. + * + * This function cleanup the DSB context by unpinning and releasing + * the VMA object associated with it. + */ +void intel_dsb_cleanup(struct intel_crtc_state *crtc_state) +{ + if (!crtc_state->dsb) + return; + + i915_vma_unpin_and_release(&crtc_state->dsb->vma, I915_VMA_RELEASE_MAP); + kfree(crtc_state->dsb); + crtc_state->dsb = NULL; +} diff --git a/drivers/gpu/drm/i915/display/intel_dsb.h b/drivers/gpu/drm/i915/display/intel_dsb.h new file mode 100644 index 000000000..74dd2b334 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dsb.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright © 2019 Intel Corporation + */ + +#ifndef _INTEL_DSB_H +#define _INTEL_DSB_H + +#include + +#include "i915_reg_defs.h" + +struct intel_crtc_state; + +void intel_dsb_prepare(struct intel_crtc_state *crtc_state); +void intel_dsb_cleanup(struct intel_crtc_state *crtc_state); +void intel_dsb_reg_write(const struct intel_crtc_state *crtc_state, + i915_reg_t reg, u32 val); +void intel_dsb_indexed_reg_write(const struct intel_crtc_state *crtc_state, + i915_reg_t reg, u32 val); +void intel_dsb_commit(const struct intel_crtc_state *crtc_state); + +#endif diff --git a/drivers/gpu/drm/i915/display/intel_dsi.c b/drivers/gpu/drm/i915/display/intel_dsi.c new file mode 100644 index 000000000..5efdd471a --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dsi.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2018 Intel Corporation + */ + +#include + +#include "i915_drv.h" +#include "intel_dsi.h" +#include "intel_panel.h" + +int intel_dsi_bitrate(const struct intel_dsi *intel_dsi) +{ + int bpp = mipi_dsi_pixel_format_to_bpp(intel_dsi->pixel_format); + + if (WARN_ON(bpp < 0)) + bpp = 16; + + return intel_dsi->pclk * bpp / intel_dsi->lane_count; +} + +int intel_dsi_tlpx_ns(const struct intel_dsi *intel_dsi) +{ + switch (intel_dsi->escape_clk_div) { + default: + case 0: + return 50; + case 1: + return 100; + case 2: + return 200; + } +} + +int intel_dsi_get_modes(struct drm_connector *connector) +{ + return intel_panel_get_modes(to_intel_connector(connector)); +} + +enum drm_mode_status intel_dsi_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct drm_i915_private *dev_priv = to_i915(connector->dev); + struct intel_connector *intel_connector = to_intel_connector(connector); + const struct drm_display_mode *fixed_mode = + intel_panel_fixed_mode(intel_connector, mode); + int max_dotclk = to_i915(connector->dev)->max_dotclk_freq; + enum drm_mode_status status; + + drm_dbg_kms(&dev_priv->drm, "\n"); + + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) + return MODE_NO_DBLESCAN; + + status = intel_panel_mode_valid(intel_connector, mode); + if (status != MODE_OK) + return status; + + if (fixed_mode->clock > max_dotclk) + return MODE_CLOCK_HIGH; + + return intel_mode_valid_max_plane_size(dev_priv, mode, false); +} + +struct intel_dsi_host *intel_dsi_host_init(struct intel_dsi *intel_dsi, + const struct mipi_dsi_host_ops *funcs, + enum port port) +{ + struct intel_dsi_host *host; + struct mipi_dsi_device *device; + + host = kzalloc(sizeof(*host), GFP_KERNEL); + if (!host) + return NULL; + + host->base.ops = funcs; + host->intel_dsi = intel_dsi; + host->port = port; + + /* + * We should call mipi_dsi_host_register(&host->base) here, but we don't + * have a host->dev, and we don't have OF stuff either. So just use the + * dsi framework as a library and hope for the best. Create the dsi + * devices by ourselves here too. Need to be careful though, because we + * don't initialize any of the driver model devices here. + */ + device = kzalloc(sizeof(*device), GFP_KERNEL); + if (!device) { + kfree(host); + return NULL; + } + + device->host = &host->base; + host->device = device; + + return host; +} + +enum drm_panel_orientation +intel_dsi_get_panel_orientation(struct intel_connector *connector) +{ + struct drm_i915_private *dev_priv = to_i915(connector->base.dev); + enum drm_panel_orientation orientation; + + orientation = connector->panel.vbt.dsi.orientation; + if (orientation != DRM_MODE_PANEL_ORIENTATION_UNKNOWN) + return orientation; + + orientation = dev_priv->display.vbt.orientation; + if (orientation != DRM_MODE_PANEL_ORIENTATION_UNKNOWN) + return orientation; + + return DRM_MODE_PANEL_ORIENTATION_NORMAL; +} diff --git a/drivers/gpu/drm/i915/display/intel_dsi.h b/drivers/gpu/drm/i915/display/intel_dsi.h new file mode 100644 index 000000000..ce80bd8be --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dsi.h @@ -0,0 +1,177 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef _INTEL_DSI_H +#define _INTEL_DSI_H + +#include +#include + +#include "intel_display_types.h" + +#define INTEL_DSI_VIDEO_MODE 0 +#define INTEL_DSI_COMMAND_MODE 1 + +/* Dual Link support */ +#define DSI_DUAL_LINK_NONE 0 +#define DSI_DUAL_LINK_FRONT_BACK 1 +#define DSI_DUAL_LINK_PIXEL_ALT 2 + +struct intel_dsi_host; + +struct intel_dsi { + struct intel_encoder base; + + struct intel_dsi_host *dsi_hosts[I915_MAX_PORTS]; + intel_wakeref_t io_wakeref[I915_MAX_PORTS]; + + /* GPIO Desc for panel and backlight control */ + struct gpio_desc *gpio_panel; + struct gpio_desc *gpio_backlight; + + struct intel_connector *attached_connector; + + /* bit mask of ports (vlv dsi) or phys (icl dsi) being driven */ + union { + u16 ports; /* VLV DSI */ + u16 phys; /* ICL DSI */ + }; + + /* if true, use HS mode, otherwise LP */ + bool hs; + + /* virtual channel */ + int channel; + + /* Video mode or command mode */ + u16 operation_mode; + + /* number of DSI lanes */ + unsigned int lane_count; + + /* i2c bus associated with the slave device */ + int i2c_bus_num; + + /* + * video mode pixel format + * + * XXX: consolidate on .format in struct mipi_dsi_device. + */ + enum mipi_dsi_pixel_format pixel_format; + + /* NON_BURST_SYNC_PULSE, NON_BURST_SYNC_EVENTS, or BURST_MODE */ + int video_mode; + + /* eot for MIPI_EOT_DISABLE register */ + u8 eotp_pkt; + u8 clock_stop; + + u8 escape_clk_div; + u8 dual_link; + + /* RGB or BGR */ + bool bgr_enabled; + + u8 pixel_overlap; + u32 port_bits; + u32 bw_timer; + u32 dphy_reg; + + /* data lanes dphy timing */ + u32 dphy_data_lane_reg; + u32 video_frmt_cfg_bits; + u16 lp_byte_clk; + + /* timeouts in byte clocks */ + u16 hs_tx_timeout; + u16 lp_rx_timeout; + u16 turn_arnd_val; + u16 rst_timer_val; + u16 hs_to_lp_count; + u16 clk_lp_to_hs_count; + u16 clk_hs_to_lp_count; + + u16 init_count; + u32 pclk; + u16 burst_mode_ratio; + + /* all delays in ms */ + u16 backlight_off_delay; + u16 backlight_on_delay; + u16 panel_on_delay; + u16 panel_off_delay; + u16 panel_pwr_cycle_delay; + ktime_t panel_power_off_time; +}; + +struct intel_dsi_host { + struct mipi_dsi_host base; + struct intel_dsi *intel_dsi; + enum port port; + + /* our little hack */ + struct mipi_dsi_device *device; +}; + +static inline struct intel_dsi_host *to_intel_dsi_host(struct mipi_dsi_host *h) +{ + return container_of(h, struct intel_dsi_host, base); +} + +#define for_each_dsi_port(__port, __ports_mask) \ + for_each_port_masked(__port, __ports_mask) +#define for_each_dsi_phy(__phy, __phys_mask) \ + for_each_phy_masked(__phy, __phys_mask) + +static inline struct intel_dsi *enc_to_intel_dsi(struct intel_encoder *encoder) +{ + return container_of(&encoder->base, struct intel_dsi, base.base); +} + +static inline bool is_vid_mode(struct intel_dsi *intel_dsi) +{ + return intel_dsi->operation_mode == INTEL_DSI_VIDEO_MODE; +} + +static inline bool is_cmd_mode(struct intel_dsi *intel_dsi) +{ + return intel_dsi->operation_mode == INTEL_DSI_COMMAND_MODE; +} + +static inline u16 intel_dsi_encoder_ports(struct intel_encoder *encoder) +{ + return enc_to_intel_dsi(encoder)->ports; +} + +int intel_dsi_bitrate(const struct intel_dsi *intel_dsi); +int intel_dsi_tlpx_ns(const struct intel_dsi *intel_dsi); +enum drm_panel_orientation +intel_dsi_get_panel_orientation(struct intel_connector *connector); +int intel_dsi_get_modes(struct drm_connector *connector); +enum drm_mode_status intel_dsi_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode); +struct intel_dsi_host *intel_dsi_host_init(struct intel_dsi *intel_dsi, + const struct mipi_dsi_host_ops *funcs, + enum port port); + +#endif /* _INTEL_DSI_H */ diff --git a/drivers/gpu/drm/i915/display/intel_dsi_dcs_backlight.c b/drivers/gpu/drm/i915/display/intel_dsi_dcs_backlight.c new file mode 100644 index 000000000..20e466d84 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_dsi_dcs_backlight.c @@ -0,0 +1,200 @@ +/* + * Copyright © 2016 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Author: Deepak M + */ + +#include +#include