From a5bde5d8fde8a8cb28e59a672d5ddc5b9c1e7656 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Wed, 5 Aug 2026 07:52:36 -0700 Subject: workqueue: skip the node_nr_active update for non-unbound workqueues apply_wqattrs_commit() updates node_nr_active->max unconditionally. wq->node_nr_active[] is only allocated for unbound workqueues, so guard the call before per-cpu workqueues start using this path. No functional change: only unbound workqueues reach apply_wqattrs_*() today. Signed-off-by: Breno Leitao Signed-off-by: Tejun Heo --- drivers/gpu/drm/i915/.gitignore | 2 + drivers/gpu/drm/i915/Kconfig | 185 + drivers/gpu/drm/i915/Kconfig.debug | 278 + drivers/gpu/drm/i915/Kconfig.profile | 139 + drivers/gpu/drm/i915/Makefile | 459 ++ drivers/gpu/drm/i915/TODO.txt | 41 + drivers/gpu/drm/i915/display/bxt_dpio_phy_regs.h | 273 + drivers/gpu/drm/i915/display/dvo_ch7017.c | 417 + drivers/gpu/drm/i915/display/dvo_ch7xxx.c | 387 + drivers/gpu/drm/i915/display/dvo_ivch.c | 505 ++ drivers/gpu/drm/i915/display/dvo_ns2501.c | 710 ++ drivers/gpu/drm/i915/display/dvo_sil164.c | 291 + drivers/gpu/drm/i915/display/dvo_tfp410.c | 321 + drivers/gpu/drm/i915/display/g4x_dp.c | 1428 ++++ drivers/gpu/drm/i915/display/g4x_dp.h | 45 + drivers/gpu/drm/i915/display/g4x_hdmi.c | 778 ++ drivers/gpu/drm/i915/display/g4x_hdmi.h | 36 + drivers/gpu/drm/i915/display/hsw_ips.c | 374 + drivers/gpu/drm/i915/display/hsw_ips.h | 62 + drivers/gpu/drm/i915/display/i9xx_display_sr.c | 97 + drivers/gpu/drm/i915/display/i9xx_display_sr.h | 14 + drivers/gpu/drm/i915/display/i9xx_plane.c | 1271 +++ drivers/gpu/drm/i915/display/i9xx_plane.h | 64 + drivers/gpu/drm/i915/display/i9xx_plane_regs.h | 112 + drivers/gpu/drm/i915/display/i9xx_wm.c | 4185 ++++++++++ drivers/gpu/drm/i915/display/i9xx_wm.h | 37 + drivers/gpu/drm/i915/display/i9xx_wm_regs.h | 257 + drivers/gpu/drm/i915/display/icl_dsi.c | 2043 +++++ drivers/gpu/drm/i915/display/icl_dsi.h | 17 + drivers/gpu/drm/i915/display/icl_dsi_regs.h | 344 + drivers/gpu/drm/i915/display/intel_acpi.c | 363 + drivers/gpu/drm/i915/display/intel_acpi.h | 31 + drivers/gpu/drm/i915/display/intel_alpm.c | 647 ++ drivers/gpu/drm/i915/display/intel_alpm.h | 42 + drivers/gpu/drm/i915/display/intel_atomic.c | 368 + drivers/gpu/drm/i915/display/intel_atomic.h | 54 + drivers/gpu/drm/i915/display/intel_audio.c | 1416 ++++ drivers/gpu/drm/i915/display/intel_audio.h | 35 + drivers/gpu/drm/i915/display/intel_audio_regs.h | 170 + drivers/gpu/drm/i915/display/intel_backlight.c | 1856 +++++ drivers/gpu/drm/i915/display/intel_backlight.h | 52 + .../gpu/drm/i915/display/intel_backlight_regs.h | 121 + drivers/gpu/drm/i915/display/intel_bios.c | 3861 +++++++++ drivers/gpu/drm/i915/display/intel_bios.h | 108 + drivers/gpu/drm/i915/display/intel_bo.c | 114 + drivers/gpu/drm/i915/display/intel_bo.h | 42 + drivers/gpu/drm/i915/display/intel_bw.c | 1520 ++++ drivers/gpu/drm/i915/display/intel_bw.h | 42 + drivers/gpu/drm/i915/display/intel_casf.c | 245 + drivers/gpu/drm/i915/display/intel_casf.h | 17 + drivers/gpu/drm/i915/display/intel_casf_regs.h | 33 + drivers/gpu/drm/i915/display/intel_cdclk.c | 4261 ++++++++++ drivers/gpu/drm/i915/display/intel_cdclk.h | 80 + drivers/gpu/drm/i915/display/intel_cmtg.c | 187 + drivers/gpu/drm/i915/display/intel_cmtg.h | 13 + drivers/gpu/drm/i915/display/intel_cmtg_regs.h | 21 + drivers/gpu/drm/i915/display/intel_color.c | 4389 ++++++++++ drivers/gpu/drm/i915/display/intel_color.h | 53 + .../gpu/drm/i915/display/intel_color_pipeline.c | 182 + .../gpu/drm/i915/display/intel_color_pipeline.h | 14 + drivers/gpu/drm/i915/display/intel_color_regs.h | 348 + drivers/gpu/drm/i915/display/intel_colorop.c | 43 + drivers/gpu/drm/i915/display/intel_colorop.h | 18 + drivers/gpu/drm/i915/display/intel_combo_phy.c | 417 + drivers/gpu/drm/i915/display/intel_combo_phy.h | 20 + .../gpu/drm/i915/display/intel_combo_phy_regs.h | 167 + drivers/gpu/drm/i915/display/intel_connector.c | 340 + drivers/gpu/drm/i915/display/intel_connector.h | 38 + drivers/gpu/drm/i915/display/intel_crt.c | 1139 +++ drivers/gpu/drm/i915/display/intel_crt.h | 34 + drivers/gpu/drm/i915/display/intel_crt_regs.h | 48 + drivers/gpu/drm/i915/display/intel_crtc.c | 904 +++ drivers/gpu/drm/i915/display/intel_crtc.h | 72 + .../gpu/drm/i915/display/intel_crtc_state_dump.c | 400 + .../gpu/drm/i915/display/intel_crtc_state_dump.h | 18 + drivers/gpu/drm/i915/display/intel_cursor.c | 1115 +++ drivers/gpu/drm/i915/display/intel_cursor.h | 22 + drivers/gpu/drm/i915/display/intel_cursor_regs.h | 112 + drivers/gpu/drm/i915/display/intel_cx0_phy.c | 3859 +++++++++ drivers/gpu/drm/i915/display/intel_cx0_phy.h | 89 + drivers/gpu/drm/i915/display/intel_cx0_phy_regs.h | 436 + drivers/gpu/drm/i915/display/intel_dbuf_bw.c | 294 + drivers/gpu/drm/i915/display/intel_dbuf_bw.h | 37 + drivers/gpu/drm/i915/display/intel_ddi.c | 5516 +++++++++++++ drivers/gpu/drm/i915/display/intel_ddi.h | 88 + drivers/gpu/drm/i915/display/intel_ddi_buf_trans.c | 1861 +++++ drivers/gpu/drm/i915/display/intel_ddi_buf_trans.h | 84 + drivers/gpu/drm/i915/display/intel_de.c | 186 + drivers/gpu/drm/i915/display/intel_de.h | 183 + drivers/gpu/drm/i915/display/intel_display.c | 8458 ++++++++++++++++++++ drivers/gpu/drm/i915/display/intel_display.h | 521 ++ .../drm/i915/display/intel_display_clock_gating.c | 270 + .../drm/i915/display/intel_display_clock_gating.h | 27 + .../drm/i915/display/intel_display_conversion.c | 21 + .../drm/i915/display/intel_display_conversion.h | 16 + drivers/gpu/drm/i915/display/intel_display_core.h | 650 ++ .../gpu/drm/i915/display/intel_display_debugfs.c | 1401 ++++ .../gpu/drm/i915/display/intel_display_debugfs.h | 23 + .../i915/display/intel_display_debugfs_params.c | 177 + .../i915/display/intel_display_debugfs_params.h | 13 + .../gpu/drm/i915/display/intel_display_device.c | 2013 +++++ .../gpu/drm/i915/display/intel_display_device.h | 352 + .../gpu/drm/i915/display/intel_display_driver.c | 782 ++ .../gpu/drm/i915/display/intel_display_driver.h | 42 + drivers/gpu/drm/i915/display/intel_display_irq.c | 2668 ++++++ drivers/gpu/drm/i915/display/intel_display_irq.h | 85 + .../gpu/drm/i915/display/intel_display_jiffies.h | 43 + .../gpu/drm/i915/display/intel_display_limits.h | 176 + .../gpu/drm/i915/display/intel_display_params.c | 242 + .../gpu/drm/i915/display/intel_display_params.h | 65 + drivers/gpu/drm/i915/display/intel_display_power.c | 2581 ++++++ drivers/gpu/drm/i915/display/intel_display_power.h | 315 + .../gpu/drm/i915/display/intel_display_power_map.c | 1919 +++++ .../gpu/drm/i915/display/intel_display_power_map.h | 14 + .../drm/i915/display/intel_display_power_well.c | 2120 +++++ .../drm/i915/display/intel_display_power_well.h | 179 + .../gpu/drm/i915/display/intel_display_reg_defs.h | 77 + drivers/gpu/drm/i915/display/intel_display_regs.h | 3253 ++++++++ drivers/gpu/drm/i915/display/intel_display_reset.c | 126 + drivers/gpu/drm/i915/display/intel_display_reset.h | 20 + drivers/gpu/drm/i915/display/intel_display_rpm.c | 62 + drivers/gpu/drm/i915/display/intel_display_rpm.h | 37 + drivers/gpu/drm/i915/display/intel_display_rps.c | 109 + drivers/gpu/drm/i915/display/intel_display_rps.h | 25 + .../gpu/drm/i915/display/intel_display_snapshot.c | 75 + .../gpu/drm/i915/display/intel_display_snapshot.h | 16 + drivers/gpu/drm/i915/display/intel_display_trace.c | 9 + drivers/gpu/drm/i915/display/intel_display_trace.h | 858 ++ drivers/gpu/drm/i915/display/intel_display_types.h | 2315 ++++++ drivers/gpu/drm/i915/display/intel_display_utils.c | 32 + drivers/gpu/drm/i915/display/intel_display_utils.h | 27 + drivers/gpu/drm/i915/display/intel_display_wa.c | 162 + drivers/gpu/drm/i915/display/intel_display_wa.h | 71 + drivers/gpu/drm/i915/display/intel_dkl_phy.c | 117 + drivers/gpu/drm/i915/display/intel_dkl_phy.h | 25 + drivers/gpu/drm/i915/display/intel_dkl_phy_regs.h | 207 + drivers/gpu/drm/i915/display/intel_dmc.c | 1785 +++++ drivers/gpu/drm/i915/display/intel_dmc.h | 56 + drivers/gpu/drm/i915/display/intel_dmc_regs.h | 647 ++ drivers/gpu/drm/i915/display/intel_dmc_wl.c | 513 ++ drivers/gpu/drm/i915/display/intel_dmc_wl.h | 41 + drivers/gpu/drm/i915/display/intel_dp.c | 7651 ++++++++++++++++++ drivers/gpu/drm/i915/display/intel_dp.h | 246 + drivers/gpu/drm/i915/display/intel_dp_aux.c | 929 +++ drivers/gpu/drm/i915/display/intel_dp_aux.h | 25 + .../gpu/drm/i915/display/intel_dp_aux_backlight.c | 761 ++ .../gpu/drm/i915/display/intel_dp_aux_backlight.h | 13 + drivers/gpu/drm/i915/display/intel_dp_aux_regs.h | 106 + drivers/gpu/drm/i915/display/intel_dp_hdcp.c | 908 +++ drivers/gpu/drm/i915/display/intel_dp_hdcp.h | 15 + .../gpu/drm/i915/display/intel_dp_link_training.c | 2235 ++++++ .../gpu/drm/i915/display/intel_dp_link_training.h | 59 + drivers/gpu/drm/i915/display/intel_dp_mst.c | 2192 +++++ drivers/gpu/drm/i915/display/intel_dp_mst.h | 39 + drivers/gpu/drm/i915/display/intel_dp_test.c | 764 ++ drivers/gpu/drm/i915/display/intel_dp_test.h | 23 + drivers/gpu/drm/i915/display/intel_dp_tunnel.c | 875 ++ drivers/gpu/drm/i915/display/intel_dp_tunnel.h | 142 + drivers/gpu/drm/i915/display/intel_dpcd.h | 15 + drivers/gpu/drm/i915/display/intel_dpio_phy.c | 1202 +++ drivers/gpu/drm/i915/display/intel_dpio_phy.h | 181 + drivers/gpu/drm/i915/display/intel_dpll.c | 2319 ++++++ drivers/gpu/drm/i915/display/intel_dpll.h | 53 + drivers/gpu/drm/i915/display/intel_dpll_mgr.c | 5190 ++++++++++++ drivers/gpu/drm/i915/display/intel_dpll_mgr.h | 458 ++ drivers/gpu/drm/i915/display/intel_dpt.c | 94 + drivers/gpu/drm/i915/display/intel_dpt.h | 16 + drivers/gpu/drm/i915/display/intel_dram.c | 855 ++ drivers/gpu/drm/i915/display/intel_dram.h | 43 + drivers/gpu/drm/i915/display/intel_drrs.c | 421 + drivers/gpu/drm/i915/display/intel_drrs.h | 33 + drivers/gpu/drm/i915/display/intel_dsb.c | 1125 +++ drivers/gpu/drm/i915/display/intel_dsb.h | 77 + drivers/gpu/drm/i915/display/intel_dsb_regs.h | 100 + drivers/gpu/drm/i915/display/intel_dsi.c | 131 + drivers/gpu/drm/i915/display/intel_dsi.h | 177 + .../gpu/drm/i915/display/intel_dsi_dcs_backlight.c | 206 + .../gpu/drm/i915/display/intel_dsi_dcs_backlight.h | 13 + drivers/gpu/drm/i915/display/intel_dsi_vbt.c | 960 +++ drivers/gpu/drm/i915/display/intel_dsi_vbt.h | 20 + drivers/gpu/drm/i915/display/intel_dsi_vbt_defs.h | 197 + drivers/gpu/drm/i915/display/intel_dvo.c | 559 ++ drivers/gpu/drm/i915/display/intel_dvo.h | 19 + drivers/gpu/drm/i915/display/intel_dvo_dev.h | 116 + drivers/gpu/drm/i915/display/intel_dvo_regs.h | 54 + drivers/gpu/drm/i915/display/intel_encoder.c | 123 + drivers/gpu/drm/i915/display/intel_encoder.h | 26 + drivers/gpu/drm/i915/display/intel_fb.c | 2407 ++++++ drivers/gpu/drm/i915/display/intel_fb.h | 126 + drivers/gpu/drm/i915/display/intel_fbc.c | 2480 ++++++ drivers/gpu/drm/i915/display/intel_fbc.h | 61 + drivers/gpu/drm/i915/display/intel_fbc_regs.h | 141 + drivers/gpu/drm/i915/display/intel_fbdev.c | 568 ++ drivers/gpu/drm/i915/display/intel_fbdev.h | 41 + drivers/gpu/drm/i915/display/intel_fdi.c | 1118 +++ drivers/gpu/drm/i915/display/intel_fdi.h | 48 + drivers/gpu/drm/i915/display/intel_fdi_regs.h | 151 + drivers/gpu/drm/i915/display/intel_fifo_underrun.c | 585 ++ drivers/gpu/drm/i915/display/intel_fifo_underrun.h | 29 + drivers/gpu/drm/i915/display/intel_fixed.h | 148 + drivers/gpu/drm/i915/display/intel_flipq.c | 465 ++ drivers/gpu/drm/i915/display/intel_flipq.h | 37 + drivers/gpu/drm/i915/display/intel_frontbuffer.c | 241 + drivers/gpu/drm/i915/display/intel_frontbuffer.h | 136 + drivers/gpu/drm/i915/display/intel_global_state.c | 417 + drivers/gpu/drm/i915/display/intel_global_state.h | 65 + drivers/gpu/drm/i915/display/intel_gmbus.c | 1034 +++ drivers/gpu/drm/i915/display/intel_gmbus.h | 50 + drivers/gpu/drm/i915/display/intel_gmbus_regs.h | 81 + drivers/gpu/drm/i915/display/intel_gvt_api.c | 43 + drivers/gpu/drm/i915/display/intel_gvt_api.h | 21 + drivers/gpu/drm/i915/display/intel_hdcp.c | 2891 +++++++ drivers/gpu/drm/i915/display/intel_hdcp.h | 50 + .../gpu/drm/i915/display/intel_hdcp_gsc_message.c | 666 ++ .../gpu/drm/i915/display/intel_hdcp_gsc_message.h | 14 + drivers/gpu/drm/i915/display/intel_hdcp_regs.h | 272 + drivers/gpu/drm/i915/display/intel_hdcp_shim.h | 137 + drivers/gpu/drm/i915/display/intel_hdmi.c | 3399 ++++++++ drivers/gpu/drm/i915/display/intel_hdmi.h | 74 + drivers/gpu/drm/i915/display/intel_hotplug.c | 1342 ++++ drivers/gpu/drm/i915/display/intel_hotplug.h | 38 + drivers/gpu/drm/i915/display/intel_hotplug_irq.c | 1491 ++++ drivers/gpu/drm/i915/display/intel_hotplug_irq.h | 35 + drivers/gpu/drm/i915/display/intel_hti.c | 43 + drivers/gpu/drm/i915/display/intel_hti.h | 18 + drivers/gpu/drm/i915/display/intel_hti_regs.h | 16 + drivers/gpu/drm/i915/display/intel_initial_plane.c | 240 + drivers/gpu/drm/i915/display/intel_initial_plane.h | 15 + drivers/gpu/drm/i915/display/intel_link_bw.c | 497 ++ drivers/gpu/drm/i915/display/intel_link_bw.h | 39 + drivers/gpu/drm/i915/display/intel_load_detect.c | 225 + drivers/gpu/drm/i915/display/intel_load_detect.h | 20 + drivers/gpu/drm/i915/display/intel_lpe_audio.c | 367 + drivers/gpu/drm/i915/display/intel_lpe_audio.h | 40 + drivers/gpu/drm/i915/display/intel_lspcon.c | 756 ++ drivers/gpu/drm/i915/display/intel_lspcon.h | 40 + drivers/gpu/drm/i915/display/intel_lt_phy.c | 2351 ++++++ drivers/gpu/drm/i915/display/intel_lt_phy.h | 53 + drivers/gpu/drm/i915/display/intel_lt_phy_regs.h | 90 + drivers/gpu/drm/i915/display/intel_lvds.c | 1015 +++ drivers/gpu/drm/i915/display/intel_lvds.h | 41 + drivers/gpu/drm/i915/display/intel_lvds_regs.h | 65 + drivers/gpu/drm/i915/display/intel_mchbar.c | 78 + drivers/gpu/drm/i915/display/intel_mchbar.h | 21 + drivers/gpu/drm/i915/display/intel_mg_phy_regs.h | 282 + drivers/gpu/drm/i915/display/intel_modeset_lock.c | 50 + drivers/gpu/drm/i915/display/intel_modeset_lock.h | 33 + drivers/gpu/drm/i915/display/intel_modeset_setup.c | 1015 +++ drivers/gpu/drm/i915/display/intel_modeset_setup.h | 15 + .../gpu/drm/i915/display/intel_modeset_verify.c | 256 + .../gpu/drm/i915/display/intel_modeset_verify.h | 16 + drivers/gpu/drm/i915/display/intel_opregion.c | 1306 +++ drivers/gpu/drm/i915/display/intel_opregion.h | 145 + drivers/gpu/drm/i915/display/intel_oprom_regs.h | 15 + drivers/gpu/drm/i915/display/intel_overlay.c | 1072 +++ drivers/gpu/drm/i915/display/intel_overlay.h | 27 + drivers/gpu/drm/i915/display/intel_panel.c | 603 ++ drivers/gpu/drm/i915/display/intel_panel.h | 61 + drivers/gpu/drm/i915/display/intel_parent.c | 455 ++ drivers/gpu/drm/i915/display/intel_parent.h | 159 + drivers/gpu/drm/i915/display/intel_pch.c | 433 + drivers/gpu/drm/i915/display/intel_pch.h | 57 + drivers/gpu/drm/i915/display/intel_pch_display.c | 643 ++ drivers/gpu/drm/i915/display/intel_pch_display.h | 98 + drivers/gpu/drm/i915/display/intel_pch_refclk.c | 683 ++ drivers/gpu/drm/i915/display/intel_pch_refclk.h | 45 + drivers/gpu/drm/i915/display/intel_pfit.c | 753 ++ drivers/gpu/drm/i915/display/intel_pfit.h | 29 + drivers/gpu/drm/i915/display/intel_pfit_regs.h | 79 + drivers/gpu/drm/i915/display/intel_pipe_crc.c | 671 ++ drivers/gpu/drm/i915/display/intel_pipe_crc.h | 37 + drivers/gpu/drm/i915/display/intel_pipe_crc_regs.h | 152 + drivers/gpu/drm/i915/display/intel_plane.c | 1926 +++++ drivers/gpu/drm/i915/display/intel_plane.h | 99 + drivers/gpu/drm/i915/display/intel_pmdemand.c | 675 ++ drivers/gpu/drm/i915/display/intel_pmdemand.h | 39 + drivers/gpu/drm/i915/display/intel_pps.c | 1873 +++++ drivers/gpu/drm/i915/display/intel_pps.h | 67 + drivers/gpu/drm/i915/display/intel_pps_regs.h | 77 + drivers/gpu/drm/i915/display/intel_psr.c | 4711 +++++++++++ drivers/gpu/drm/i915/display/intel_psr.h | 91 + drivers/gpu/drm/i915/display/intel_psr_regs.h | 342 + drivers/gpu/drm/i915/display/intel_qp_tables.c | 475 ++ drivers/gpu/drm/i915/display/intel_qp_tables.h | 14 + drivers/gpu/drm/i915/display/intel_quirks.c | 345 + drivers/gpu/drm/i915/display/intel_quirks.h | 34 + drivers/gpu/drm/i915/display/intel_rom.c | 163 + drivers/gpu/drm/i915/display/intel_rom.h | 25 + drivers/gpu/drm/i915/display/intel_sbi.c | 92 + drivers/gpu/drm/i915/display/intel_sbi.h | 27 + drivers/gpu/drm/i915/display/intel_sbi_regs.h | 65 + drivers/gpu/drm/i915/display/intel_sdvo.c | 3523 ++++++++ drivers/gpu/drm/i915/display/intel_sdvo.h | 35 + drivers/gpu/drm/i915/display/intel_sdvo_regs.h | 741 ++ drivers/gpu/drm/i915/display/intel_snps_hdmi_pll.c | 364 + drivers/gpu/drm/i915/display/intel_snps_hdmi_pll.h | 17 + drivers/gpu/drm/i915/display/intel_snps_phy.c | 2028 +++++ 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 | 1736 ++++ drivers/gpu/drm/i915/display/intel_sprite.h | 36 + drivers/gpu/drm/i915/display/intel_sprite_regs.h | 388 + drivers/gpu/drm/i915/display/intel_sprite_uapi.c | 128 + drivers/gpu/drm/i915/display/intel_sprite_uapi.h | 15 + drivers/gpu/drm/i915/display/intel_tc.c | 2024 +++++ drivers/gpu/drm/i915/display/intel_tc.h | 119 + drivers/gpu/drm/i915/display/intel_tdf.h | 25 + drivers/gpu/drm/i915/display/intel_tv.c | 2023 +++++ drivers/gpu/drm/i915/display/intel_tv.h | 19 + drivers/gpu/drm/i915/display/intel_tv_regs.h | 490 ++ drivers/gpu/drm/i915/display/intel_vblank.c | 805 ++ drivers/gpu/drm/i915/display/intel_vblank.h | 53 + drivers/gpu/drm/i915/display/intel_vbt_defs.h | 1672 ++++ drivers/gpu/drm/i915/display/intel_vdsc.c | 1206 +++ drivers/gpu/drm/i915/display/intel_vdsc.h | 51 + drivers/gpu/drm/i915/display/intel_vdsc_regs.h | 366 + drivers/gpu/drm/i915/display/intel_vga.c | 343 + drivers/gpu/drm/i915/display/intel_vga.h | 19 + drivers/gpu/drm/i915/display/intel_vga_regs.h | 36 + drivers/gpu/drm/i915/display/intel_vrr.c | 1228 +++ drivers/gpu/drm/i915/display/intel_vrr.h | 57 + drivers/gpu/drm/i915/display/intel_vrr_regs.h | 195 + drivers/gpu/drm/i915/display/intel_wm.c | 404 + drivers/gpu/drm/i915/display/intel_wm.h | 36 + drivers/gpu/drm/i915/display/intel_wm_types.h | 76 + drivers/gpu/drm/i915/display/skl_prefill.c | 157 + drivers/gpu/drm/i915/display/skl_prefill.h | 46 + drivers/gpu/drm/i915/display/skl_scaler.c | 1162 +++ drivers/gpu/drm/i915/display/skl_scaler.h | 63 + drivers/gpu/drm/i915/display/skl_universal_plane.c | 3252 ++++++++ drivers/gpu/drm/i915/display/skl_universal_plane.h | 46 + .../drm/i915/display/skl_universal_plane_regs.h | 585 ++ drivers/gpu/drm/i915/display/skl_watermark.c | 4094 ++++++++++ drivers/gpu/drm/i915/display/skl_watermark.h | 86 + drivers/gpu/drm/i915/display/skl_watermark_regs.h | 87 + drivers/gpu/drm/i915/display/vlv_clock.c | 89 + drivers/gpu/drm/i915/display/vlv_clock.h | 38 + drivers/gpu/drm/i915/display/vlv_dpio_phy_regs.h | 309 + drivers/gpu/drm/i915/display/vlv_dsi.c | 2057 +++++ drivers/gpu/drm/i915/display/vlv_dsi.h | 31 + drivers/gpu/drm/i915/display/vlv_dsi_pll.c | 623 ++ drivers/gpu/drm/i915/display/vlv_dsi_pll.h | 52 + drivers/gpu/drm/i915/display/vlv_dsi_pll_regs.h | 109 + drivers/gpu/drm/i915/display/vlv_dsi_regs.h | 475 ++ drivers/gpu/drm/i915/display/vlv_sideband.c | 174 + drivers/gpu/drm/i915/display/vlv_sideband.h | 48 + drivers/gpu/drm/i915/gem/i915_gem_busy.c | 163 + drivers/gpu/drm/i915/gem/i915_gem_clflush.c | 137 + drivers/gpu/drm/i915/gem/i915_gem_clflush.h | 19 + drivers/gpu/drm/i915/gem/i915_gem_context.c | 2721 +++++++ drivers/gpu/drm/i915/gem/i915_gem_context.h | 247 + drivers/gpu/drm/i915/gem/i915_gem_context_types.h | 422 + drivers/gpu/drm/i915/gem/i915_gem_create.c | 497 ++ drivers/gpu/drm/i915/gem/i915_gem_create.h | 17 + drivers/gpu/drm/i915/gem/i915_gem_dmabuf.c | 347 + drivers/gpu/drm/i915/gem/i915_gem_dmabuf.h | 18 + drivers/gpu/drm/i915/gem/i915_gem_domain.c | 770 ++ drivers/gpu/drm/i915/gem/i915_gem_domain.h | 15 + drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c | 3642 +++++++++ drivers/gpu/drm/i915/gem/i915_gem_internal.c | 198 + drivers/gpu/drm/i915/gem/i915_gem_internal.h | 23 + drivers/gpu/drm/i915/gem/i915_gem_ioctls.h | 51 + drivers/gpu/drm/i915/gem/i915_gem_lmem.c | 119 + drivers/gpu/drm/i915/gem/i915_gem_lmem.h | 36 + drivers/gpu/drm/i915/gem/i915_gem_mman.c | 1156 +++ drivers/gpu/drm/i915/gem/i915_gem_mman.h | 32 + drivers/gpu/drm/i915/gem/i915_gem_object.c | 977 +++ drivers/gpu/drm/i915/gem/i915_gem_object.h | 874 ++ .../gpu/drm/i915/gem/i915_gem_object_frontbuffer.c | 172 + .../gpu/drm/i915/gem/i915_gem_object_frontbuffer.h | 96 + drivers/gpu/drm/i915/gem/i915_gem_object_types.h | 748 ++ drivers/gpu/drm/i915/gem/i915_gem_pages.c | 805 ++ drivers/gpu/drm/i915/gem/i915_gem_phys.c | 266 + drivers/gpu/drm/i915/gem/i915_gem_pm.c | 265 + drivers/gpu/drm/i915/gem/i915_gem_pm.h | 25 + drivers/gpu/drm/i915/gem/i915_gem_region.c | 208 + drivers/gpu/drm/i915/gem/i915_gem_region.h | 67 + drivers/gpu/drm/i915/gem/i915_gem_shmem.c | 708 ++ drivers/gpu/drm/i915/gem/i915_gem_shrinker.c | 597 ++ drivers/gpu/drm/i915/gem/i915_gem_shrinker.h | 33 + drivers/gpu/drm/i915/gem/i915_gem_stolen.c | 1104 +++ drivers/gpu/drm/i915/gem/i915_gem_stolen.h | 31 + drivers/gpu/drm/i915/gem/i915_gem_throttle.c | 101 + drivers/gpu/drm/i915/gem/i915_gem_tiling.c | 472 ++ drivers/gpu/drm/i915/gem/i915_gem_tiling.h | 20 + drivers/gpu/drm/i915/gem/i915_gem_ttm.c | 1386 ++++ drivers/gpu/drm/i915/gem/i915_gem_ttm.h | 107 + drivers/gpu/drm/i915/gem/i915_gem_ttm_move.c | 757 ++ drivers/gpu/drm/i915/gem/i915_gem_ttm_move.h | 42 + drivers/gpu/drm/i915/gem/i915_gem_ttm_pm.c | 233 + drivers/gpu/drm/i915/gem/i915_gem_ttm_pm.h | 26 + drivers/gpu/drm/i915/gem/i915_gem_userptr.c | 556 ++ drivers/gpu/drm/i915/gem/i915_gem_wait.c | 296 + .../gpu/drm/i915/gem/selftests/huge_gem_object.c | 131 + .../gpu/drm/i915/gem/selftests/huge_gem_object.h | 33 + drivers/gpu/drm/i915/gem/selftests/huge_pages.c | 2025 +++++ .../drm/i915/gem/selftests/i915_gem_client_blt.c | 746 ++ .../drm/i915/gem/selftests/i915_gem_coherency.c | 434 + .../gpu/drm/i915/gem/selftests/i915_gem_context.c | 1916 +++++ .../gpu/drm/i915/gem/selftests/i915_gem_dmabuf.c | 560 ++ .../gpu/drm/i915/gem/selftests/i915_gem_migrate.c | 528 ++ drivers/gpu/drm/i915/gem/selftests/i915_gem_mman.c | 1873 +++++ .../gpu/drm/i915/gem/selftests/i915_gem_object.c | 99 + drivers/gpu/drm/i915/gem/selftests/i915_gem_phys.c | 87 + drivers/gpu/drm/i915/gem/selftests/igt_gem_utils.c | 157 + drivers/gpu/drm/i915/gem/selftests/igt_gem_utils.h | 46 + drivers/gpu/drm/i915/gem/selftests/mock_context.c | 142 + drivers/gpu/drm/i915/gem/selftests/mock_context.h | 30 + drivers/gpu/drm/i915/gem/selftests/mock_dmabuf.c | 133 + drivers/gpu/drm/i915/gem/selftests/mock_dmabuf.h | 22 + .../gpu/drm/i915/gem/selftests/mock_gem_object.h | 16 + drivers/gpu/drm/i915/gt/gen2_engine_cs.c | 314 + drivers/gpu/drm/i915/gt/gen2_engine_cs.h | 36 + drivers/gpu/drm/i915/gt/gen6_engine_cs.c | 456 ++ drivers/gpu/drm/i915/gt/gen6_engine_cs.h | 39 + drivers/gpu/drm/i915/gt/gen6_ppgtt.c | 469 ++ drivers/gpu/drm/i915/gt/gen6_ppgtt.h | 77 + drivers/gpu/drm/i915/gt/gen6_renderstate.c | 297 + drivers/gpu/drm/i915/gt/gen7_renderclear.c | 454 ++ drivers/gpu/drm/i915/gt/gen7_renderclear.h | 15 + drivers/gpu/drm/i915/gt/gen7_renderstate.c | 261 + drivers/gpu/drm/i915/gt/gen8_engine_cs.c | 855 ++ drivers/gpu/drm/i915/gt/gen8_engine_cs.h | 142 + drivers/gpu/drm/i915/gt/gen8_ppgtt.c | 1085 +++ drivers/gpu/drm/i915/gt/gen8_ppgtt.h | 21 + drivers/gpu/drm/i915/gt/gen8_renderstate.c | 965 +++ drivers/gpu/drm/i915/gt/gen9_renderstate.c | 981 +++ drivers/gpu/drm/i915/gt/hsw_clear_kernel.c | 61 + drivers/gpu/drm/i915/gt/intel_breadcrumbs.c | 526 ++ drivers/gpu/drm/i915/gt/intel_breadcrumbs.h | 63 + drivers/gpu/drm/i915/gt/intel_breadcrumbs_types.h | 56 + drivers/gpu/drm/i915/gt/intel_context.c | 650 ++ drivers/gpu/drm/i915/gt/intel_context.h | 409 + drivers/gpu/drm/i915/gt/intel_context_param.h | 19 + drivers/gpu/drm/i915/gt/intel_context_sseu.c | 97 + drivers/gpu/drm/i915/gt/intel_context_types.h | 328 + drivers/gpu/drm/i915/gt/intel_engine.h | 389 + drivers/gpu/drm/i915/gt/intel_engine_cs.c | 2562 ++++++ drivers/gpu/drm/i915/gt/intel_engine_heartbeat.c | 429 + drivers/gpu/drm/i915/gt/intel_engine_heartbeat.h | 26 + drivers/gpu/drm/i915/gt/intel_engine_pm.c | 328 + drivers/gpu/drm/i915/gt/intel_engine_pm.h | 112 + drivers/gpu/drm/i915/gt/intel_engine_regs.h | 269 + drivers/gpu/drm/i915/gt/intel_engine_stats.h | 61 + drivers/gpu/drm/i915/gt/intel_engine_types.h | 712 ++ drivers/gpu/drm/i915/gt/intel_engine_user.c | 328 + drivers/gpu/drm/i915/gt/intel_engine_user.h | 24 + .../gpu/drm/i915/gt/intel_execlists_submission.c | 4176 ++++++++++ .../gpu/drm/i915/gt/intel_execlists_submission.h | 42 + drivers/gpu/drm/i915/gt/intel_ggtt.c | 1682 ++++ drivers/gpu/drm/i915/gt/intel_ggtt_fencing.c | 930 +++ drivers/gpu/drm/i915/gt/intel_ggtt_fencing.h | 59 + drivers/gpu/drm/i915/gt/intel_ggtt_gmch.c | 140 + drivers/gpu/drm/i915/gt/intel_ggtt_gmch.h | 27 + drivers/gpu/drm/i915/gt/intel_gpu_commands.h | 475 ++ drivers/gpu/drm/i915/gt/intel_gsc.c | 321 + drivers/gpu/drm/i915/gt/intel_gsc.h | 43 + drivers/gpu/drm/i915/gt/intel_gt.c | 1066 +++ drivers/gpu/drm/i915/gt/intel_gt.h | 216 + drivers/gpu/drm/i915/gt/intel_gt_buffer_pool.c | 249 + drivers/gpu/drm/i915/gt/intel_gt_buffer_pool.h | 43 + .../gpu/drm/i915/gt/intel_gt_buffer_pool_types.h | 36 + drivers/gpu/drm/i915/gt/intel_gt_ccs_mode.c | 39 + drivers/gpu/drm/i915/gt/intel_gt_ccs_mode.h | 13 + drivers/gpu/drm/i915/gt/intel_gt_clock_utils.c | 237 + drivers/gpu/drm/i915/gt/intel_gt_clock_utils.h | 27 + drivers/gpu/drm/i915/gt/intel_gt_debugfs.c | 122 + drivers/gpu/drm/i915/gt/intel_gt_debugfs.h | 53 + drivers/gpu/drm/i915/gt/intel_gt_defines.h | 11 + drivers/gpu/drm/i915/gt/intel_gt_engines_debugfs.c | 36 + drivers/gpu/drm/i915/gt/intel_gt_engines_debugfs.h | 14 + drivers/gpu/drm/i915/gt/intel_gt_irq.c | 558 ++ drivers/gpu/drm/i915/gt/intel_gt_irq.h | 65 + drivers/gpu/drm/i915/gt/intel_gt_mcr.c | 870 ++ drivers/gpu/drm/i915/gt/intel_gt_mcr.h | 71 + drivers/gpu/drm/i915/gt/intel_gt_pm.c | 434 + drivers/gpu/drm/i915/gt/intel_gt_pm.h | 117 + drivers/gpu/drm/i915/gt/intel_gt_pm_debugfs.c | 602 ++ drivers/gpu/drm/i915/gt/intel_gt_pm_debugfs.h | 20 + drivers/gpu/drm/i915/gt/intel_gt_pm_irq.c | 109 + drivers/gpu/drm/i915/gt/intel_gt_pm_irq.h | 21 + drivers/gpu/drm/i915/gt/intel_gt_print.h | 54 + drivers/gpu/drm/i915/gt/intel_gt_regs.h | 1642 ++++ drivers/gpu/drm/i915/gt/intel_gt_requests.c | 268 + drivers/gpu/drm/i915/gt/intel_gt_requests.h | 32 + drivers/gpu/drm/i915/gt/intel_gt_sysfs.c | 116 + drivers/gpu/drm/i915/gt/intel_gt_sysfs.h | 31 + drivers/gpu/drm/i915/gt/intel_gt_sysfs_pm.c | 932 +++ drivers/gpu/drm/i915/gt/intel_gt_sysfs_pm.h | 15 + drivers/gpu/drm/i915/gt/intel_gt_types.h | 324 + drivers/gpu/drm/i915/gt/intel_gtt.c | 737 ++ drivers/gpu/drm/i915/gt/intel_gtt.h | 707 ++ drivers/gpu/drm/i915/gt/intel_hwconfig.h | 21 + drivers/gpu/drm/i915/gt/intel_llc.c | 164 + drivers/gpu/drm/i915/gt/intel_llc.h | 14 + drivers/gpu/drm/i915/gt/intel_llc_types.h | 12 + drivers/gpu/drm/i915/gt/intel_lrc.c | 1975 +++++ drivers/gpu/drm/i915/gt/intel_lrc.h | 145 + drivers/gpu/drm/i915/gt/intel_lrc_reg.h | 81 + drivers/gpu/drm/i915/gt/intel_migrate.c | 1164 +++ drivers/gpu/drm/i915/gt/intel_migrate.h | 65 + drivers/gpu/drm/i915/gt/intel_migrate_types.h | 15 + drivers/gpu/drm/i915/gt/intel_mocs.c | 689 ++ drivers/gpu/drm/i915/gt/intel_mocs.h | 41 + drivers/gpu/drm/i915/gt/intel_ppgtt.c | 323 + drivers/gpu/drm/i915/gt/intel_rc6.c | 872 ++ drivers/gpu/drm/i915/gt/intel_rc6.h | 32 + drivers/gpu/drm/i915/gt/intel_rc6_types.h | 43 + drivers/gpu/drm/i915/gt/intel_region_lmem.c | 276 + drivers/gpu/drm/i915/gt/intel_region_lmem.h | 13 + drivers/gpu/drm/i915/gt/intel_renderstate.c | 254 + drivers/gpu/drm/i915/gt/intel_renderstate.h | 52 + drivers/gpu/drm/i915/gt/intel_reset.c | 1734 ++++ drivers/gpu/drm/i915/gt/intel_reset.h | 86 + drivers/gpu/drm/i915/gt/intel_reset_types.h | 60 + drivers/gpu/drm/i915/gt/intel_ring.c | 313 + drivers/gpu/drm/i915/gt/intel_ring.h | 141 + drivers/gpu/drm/i915/gt/intel_ring_submission.c | 1459 ++++ drivers/gpu/drm/i915/gt/intel_ring_types.h | 51 + drivers/gpu/drm/i915/gt/intel_rps.c | 2955 +++++++ drivers/gpu/drm/i915/gt/intel_rps.h | 133 + drivers/gpu/drm/i915/gt/intel_rps_types.h | 117 + drivers/gpu/drm/i915/gt/intel_sa_media.c | 48 + drivers/gpu/drm/i915/gt/intel_sa_media.h | 15 + drivers/gpu/drm/i915/gt/intel_sseu.c | 893 +++ drivers/gpu/drm/i915/gt/intel_sseu.h | 182 + drivers/gpu/drm/i915/gt/intel_sseu_debugfs.c | 301 + drivers/gpu/drm/i915/gt/intel_sseu_debugfs.h | 17 + drivers/gpu/drm/i915/gt/intel_timeline.c | 494 ++ drivers/gpu/drm/i915/gt/intel_timeline.h | 104 + drivers/gpu/drm/i915/gt/intel_timeline_types.h | 92 + drivers/gpu/drm/i915/gt/intel_tlb.c | 173 + drivers/gpu/drm/i915/gt/intel_tlb.h | 29 + drivers/gpu/drm/i915/gt/intel_wopcm.c | 321 + drivers/gpu/drm/i915/gt/intel_wopcm.h | 59 + drivers/gpu/drm/i915/gt/intel_workarounds.c | 3131 ++++++++ drivers/gpu/drm/i915/gt/intel_workarounds.h | 39 + drivers/gpu/drm/i915/gt/intel_workarounds_types.h | 37 + drivers/gpu/drm/i915/gt/ivb_clear_kernel.c | 61 + drivers/gpu/drm/i915/gt/mock_engine.c | 445 + drivers/gpu/drm/i915/gt/mock_engine.h | 32 + drivers/gpu/drm/i915/gt/selftest_context.c | 455 ++ drivers/gpu/drm/i915/gt/selftest_engine.c | 27 + drivers/gpu/drm/i915/gt/selftest_engine.h | 13 + drivers/gpu/drm/i915/gt/selftest_engine_cs.c | 443 + .../gpu/drm/i915/gt/selftest_engine_heartbeat.c | 319 + .../gpu/drm/i915/gt/selftest_engine_heartbeat.h | 16 + drivers/gpu/drm/i915/gt/selftest_engine_pm.c | 424 + drivers/gpu/drm/i915/gt/selftest_execlists.c | 4506 +++++++++++ drivers/gpu/drm/i915/gt/selftest_gt_pm.c | 213 + drivers/gpu/drm/i915/gt/selftest_hangcheck.c | 2036 +++++ drivers/gpu/drm/i915/gt/selftest_llc.c | 70 + drivers/gpu/drm/i915/gt/selftest_llc.h | 13 + drivers/gpu/drm/i915/gt/selftest_lrc.c | 1994 +++++ drivers/gpu/drm/i915/gt/selftest_migrate.c | 1039 +++ drivers/gpu/drm/i915/gt/selftest_mocs.c | 453 ++ drivers/gpu/drm/i915/gt/selftest_rc6.c | 285 + drivers/gpu/drm/i915/gt/selftest_rc6.h | 12 + drivers/gpu/drm/i915/gt/selftest_reset.c | 394 + drivers/gpu/drm/i915/gt/selftest_ring.c | 110 + drivers/gpu/drm/i915/gt/selftest_ring_submission.c | 299 + drivers/gpu/drm/i915/gt/selftest_rps.c | 1351 ++++ drivers/gpu/drm/i915/gt/selftest_rps.h | 17 + drivers/gpu/drm/i915/gt/selftest_slpc.c | 557 ++ drivers/gpu/drm/i915/gt/selftest_timeline.c | 1429 ++++ drivers/gpu/drm/i915/gt/selftest_tlb.c | 405 + drivers/gpu/drm/i915/gt/selftest_workarounds.c | 1382 ++++ drivers/gpu/drm/i915/gt/selftests/mock_timeline.c | 29 + drivers/gpu/drm/i915/gt/selftests/mock_timeline.h | 17 + drivers/gpu/drm/i915/gt/shaders/README | 46 + .../gpu/drm/i915/gt/shaders/clear_kernel/hsw.asm | 119 + .../gpu/drm/i915/gt/shaders/clear_kernel/ivb.asm | 117 + drivers/gpu/drm/i915/gt/shmem_utils.c | 174 + drivers/gpu/drm/i915/gt/shmem_utils.h | 26 + drivers/gpu/drm/i915/gt/st_shmem_utils.c | 63 + drivers/gpu/drm/i915/gt/sysfs_engines.c | 538 ++ drivers/gpu/drm/i915/gt/sysfs_engines.h | 13 + drivers/gpu/drm/i915/gt/uc/abi/guc_actions_abi.h | 217 + .../gpu/drm/i915/gt/uc/abi/guc_actions_slpc_abi.h | 275 + .../drm/i915/gt/uc/abi/guc_communication_ctb_abi.h | 170 + .../i915/gt/uc/abi/guc_communication_mmio_abi.h | 49 + drivers/gpu/drm/i915/gt/uc/abi/guc_errors_abi.h | 60 + drivers/gpu/drm/i915/gt/uc/abi/guc_klvs_abi.h | 113 + drivers/gpu/drm/i915/gt/uc/abi/guc_messages_abi.h | 264 + drivers/gpu/drm/i915/gt/uc/guc_capture_fwif.h | 226 + .../gpu/drm/i915/gt/uc/intel_gsc_binary_headers.h | 135 + drivers/gpu/drm/i915/gt/uc/intel_gsc_fw.c | 524 ++ drivers/gpu/drm/i915/gt/uc/intel_gsc_fw.h | 21 + drivers/gpu/drm/i915/gt/uc/intel_gsc_proxy.c | 429 + drivers/gpu/drm/i915/gt/uc/intel_gsc_proxy.h | 18 + drivers/gpu/drm/i915/gt/uc/intel_gsc_uc.c | 358 + drivers/gpu/drm/i915/gt/uc/intel_gsc_uc.h | 85 + drivers/gpu/drm/i915/gt/uc/intel_gsc_uc_debugfs.c | 39 + drivers/gpu/drm/i915/gt/uc/intel_gsc_uc_debugfs.h | 14 + .../drm/i915/gt/uc/intel_gsc_uc_heci_cmd_submit.c | 231 + .../drm/i915/gt/uc/intel_gsc_uc_heci_cmd_submit.h | 93 + drivers/gpu/drm/i915/gt/uc/intel_guc.c | 978 +++ drivers/gpu/drm/i915/gt/uc/intel_guc.h | 554 ++ drivers/gpu/drm/i915/gt/uc/intel_guc_ads.c | 1065 +++ drivers/gpu/drm/i915/gt/uc/intel_guc_ads.h | 25 + drivers/gpu/drm/i915/gt/uc/intel_guc_capture.c | 1654 ++++ drivers/gpu/drm/i915/gt/uc/intel_guc_capture.h | 35 + drivers/gpu/drm/i915/gt/uc/intel_guc_ct.c | 1399 ++++ drivers/gpu/drm/i915/gt/uc/intel_guc_ct.h | 139 + drivers/gpu/drm/i915/gt/uc/intel_guc_debugfs.c | 149 + drivers/gpu/drm/i915/gt/uc/intel_guc_debugfs.h | 14 + drivers/gpu/drm/i915/gt/uc/intel_guc_fw.c | 335 + drivers/gpu/drm/i915/gt/uc/intel_guc_fw.h | 13 + drivers/gpu/drm/i915/gt/uc/intel_guc_fwif.h | 508 ++ drivers/gpu/drm/i915/gt/uc/intel_guc_hwconfig.c | 167 + drivers/gpu/drm/i915/gt/uc/intel_guc_log.c | 937 +++ drivers/gpu/drm/i915/gt/uc/intel_guc_log.h | 113 + drivers/gpu/drm/i915/gt/uc/intel_guc_log_debugfs.c | 175 + drivers/gpu/drm/i915/gt/uc/intel_guc_log_debugfs.h | 15 + drivers/gpu/drm/i915/gt/uc/intel_guc_print.h | 51 + drivers/gpu/drm/i915/gt/uc/intel_guc_rc.c | 81 + drivers/gpu/drm/i915/gt/uc/intel_guc_rc.h | 31 + drivers/gpu/drm/i915/gt/uc/intel_guc_reg.h | 158 + drivers/gpu/drm/i915/gt/uc/intel_guc_slpc.c | 906 +++ drivers/gpu/drm/i915/gt/uc/intel_guc_slpc.h | 51 + drivers/gpu/drm/i915/gt/uc/intel_guc_slpc_types.h | 52 + drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c | 6014 ++++++++++++++ drivers/gpu/drm/i915/gt/uc/intel_guc_submission.h | 58 + drivers/gpu/drm/i915/gt/uc/intel_huc.c | 688 ++ drivers/gpu/drm/i915/gt/uc/intel_huc.h | 101 + drivers/gpu/drm/i915/gt/uc/intel_huc_debugfs.c | 36 + drivers/gpu/drm/i915/gt/uc/intel_huc_debugfs.h | 14 + drivers/gpu/drm/i915/gt/uc/intel_huc_fw.c | 284 + drivers/gpu/drm/i915/gt/uc/intel_huc_fw.h | 18 + drivers/gpu/drm/i915/gt/uc/intel_huc_print.h | 21 + drivers/gpu/drm/i915/gt/uc/intel_uc.c | 768 ++ drivers/gpu/drm/i915/gt/uc/intel_uc.h | 121 + drivers/gpu/drm/i915/gt/uc/intel_uc_debugfs.c | 65 + drivers/gpu/drm/i915/gt/uc/intel_uc_debugfs.h | 14 + drivers/gpu/drm/i915/gt/uc/intel_uc_fw.c | 1386 ++++ drivers/gpu/drm/i915/gt/uc/intel_uc_fw.h | 314 + drivers/gpu/drm/i915/gt/uc/intel_uc_fw_abi.h | 87 + drivers/gpu/drm/i915/gt/uc/selftest_guc.c | 419 + .../gpu/drm/i915/gt/uc/selftest_guc_hangcheck.c | 162 + .../gpu/drm/i915/gt/uc/selftest_guc_multi_lrc.c | 184 + drivers/gpu/drm/i915/gvt/Makefile | 25 + drivers/gpu/drm/i915/gvt/aperture_gm.c | 370 + drivers/gpu/drm/i915/gvt/cfg_space.c | 402 + drivers/gpu/drm/i915/gvt/cmd_parser.c | 3275 ++++++++ drivers/gpu/drm/i915/gvt/cmd_parser.h | 58 + drivers/gpu/drm/i915/gvt/debug.h | 65 + drivers/gpu/drm/i915/gvt/debugfs.c | 233 + drivers/gpu/drm/i915/gvt/display.c | 829 ++ drivers/gpu/drm/i915/gvt/display.h | 169 + drivers/gpu/drm/i915/gvt/display_helpers.h | 37 + drivers/gpu/drm/i915/gvt/dmabuf.c | 593 ++ drivers/gpu/drm/i915/gvt/dmabuf.h | 72 + drivers/gpu/drm/i915/gvt/edid.c | 585 ++ drivers/gpu/drm/i915/gvt/edid.h | 146 + drivers/gpu/drm/i915/gvt/execlist.c | 558 ++ drivers/gpu/drm/i915/gvt/execlist.h | 185 + drivers/gpu/drm/i915/gvt/fb_decoder.c | 402 + drivers/gpu/drm/i915/gvt/fb_decoder.h | 158 + drivers/gpu/drm/i915/gvt/firmware.c | 253 + drivers/gpu/drm/i915/gvt/gtt.c | 2796 +++++++ drivers/gpu/drm/i915/gvt/gtt.h | 293 + drivers/gpu/drm/i915/gvt/gvt.h | 760 ++ drivers/gpu/drm/i915/gvt/handlers.c | 3255 ++++++++ drivers/gpu/drm/i915/gvt/interrupt.c | 737 ++ drivers/gpu/drm/i915/gvt/interrupt.h | 211 + drivers/gpu/drm/i915/gvt/kvmgt.c | 1970 +++++ drivers/gpu/drm/i915/gvt/mmio.c | 340 + drivers/gpu/drm/i915/gvt/mmio.h | 108 + drivers/gpu/drm/i915/gvt/mmio_context.c | 620 ++ drivers/gpu/drm/i915/gvt/mmio_context.h | 58 + drivers/gpu/drm/i915/gvt/opregion.c | 485 ++ drivers/gpu/drm/i915/gvt/page_track.c | 180 + drivers/gpu/drm/i915/gvt/page_track.h | 59 + drivers/gpu/drm/i915/gvt/reg.h | 141 + drivers/gpu/drm/i915/gvt/sched_policy.c | 481 ++ drivers/gpu/drm/i915/gvt/sched_policy.h | 65 + drivers/gpu/drm/i915/gvt/scheduler.c | 1795 +++++ drivers/gpu/drm/i915/gvt/scheduler.h | 169 + drivers/gpu/drm/i915/gvt/trace.h | 383 + drivers/gpu/drm/i915/gvt/trace_points.c | 36 + drivers/gpu/drm/i915/gvt/vgpu.c | 513 ++ drivers/gpu/drm/i915/i915_active.c | 1192 +++ drivers/gpu/drm/i915/i915_active.h | 237 + drivers/gpu/drm/i915/i915_active_types.h | 47 + drivers/gpu/drm/i915/i915_bo.c | 270 + drivers/gpu/drm/i915/i915_bo.h | 15 + drivers/gpu/drm/i915/i915_cmd_parser.c | 1624 ++++ drivers/gpu/drm/i915/i915_cmd_parser.h | 26 + drivers/gpu/drm/i915/i915_config.c | 17 + drivers/gpu/drm/i915/i915_config.h | 19 + drivers/gpu/drm/i915/i915_debugfs.c | 746 ++ drivers/gpu/drm/i915/i915_debugfs.h | 22 + drivers/gpu/drm/i915/i915_debugfs_params.c | 270 + drivers/gpu/drm/i915/i915_debugfs_params.h | 14 + drivers/gpu/drm/i915/i915_deps.c | 237 + drivers/gpu/drm/i915/i915_deps.h | 45 + drivers/gpu/drm/i915/i915_display_pc8.c | 31 + drivers/gpu/drm/i915/i915_display_pc8.h | 9 + drivers/gpu/drm/i915/i915_dpt.c | 276 + drivers/gpu/drm/i915/i915_dpt.h | 20 + drivers/gpu/drm/i915/i915_driver.c | 1897 +++++ drivers/gpu/drm/i915/i915_driver.h | 33 + drivers/gpu/drm/i915/i915_drm_client.c | 220 + drivers/gpu/drm/i915/i915_drm_client.h | 92 + drivers/gpu/drm/i915/i915_drv.h | 667 ++ drivers/gpu/drm/i915/i915_dsb_buffer.c | 117 + drivers/gpu/drm/i915/i915_dsb_buffer.h | 9 + drivers/gpu/drm/i915/i915_edram.c | 44 + drivers/gpu/drm/i915/i915_edram.h | 11 + drivers/gpu/drm/i915/i915_fb_pin.c | 311 + drivers/gpu/drm/i915/i915_fb_pin.h | 9 + drivers/gpu/drm/i915/i915_file_private.h | 111 + drivers/gpu/drm/i915/i915_freq.c | 111 + drivers/gpu/drm/i915/i915_freq.h | 13 + drivers/gpu/drm/i915/i915_gem.c | 1355 ++++ drivers/gpu/drm/i915/i915_gem.h | 139 + drivers/gpu/drm/i915/i915_gem_evict.c | 545 ++ drivers/gpu/drm/i915/i915_gem_evict.h | 30 + drivers/gpu/drm/i915/i915_gem_gtt.c | 296 + drivers/gpu/drm/i915/i915_gem_gtt.h | 57 + drivers/gpu/drm/i915/i915_gem_ww.c | 63 + drivers/gpu/drm/i915/i915_gem_ww.h | 41 + drivers/gpu/drm/i915/i915_getparam.c | 210 + drivers/gpu/drm/i915/i915_getparam.h | 15 + drivers/gpu/drm/i915/i915_gmch.c | 141 + drivers/gpu/drm/i915/i915_gmch.h | 13 + drivers/gpu/drm/i915/i915_gpu_error.c | 2622 ++++++ drivers/gpu/drm/i915/i915_gpu_error.h | 423 + drivers/gpu/drm/i915/i915_gtt_view_types.h | 74 + drivers/gpu/drm/i915/i915_hdcp_gsc.c | 247 + drivers/gpu/drm/i915/i915_hdcp_gsc.h | 9 + drivers/gpu/drm/i915/i915_hwmon.c | 996 +++ drivers/gpu/drm/i915/i915_hwmon.h | 27 + drivers/gpu/drm/i915/i915_initial_plane.c | 283 + drivers/gpu/drm/i915/i915_initial_plane.h | 9 + drivers/gpu/drm/i915/i915_ioc32.c | 94 + drivers/gpu/drm/i915/i915_ioc32.h | 17 + drivers/gpu/drm/i915/i915_ioctl.c | 94 + drivers/gpu/drm/i915/i915_ioctl.h | 14 + drivers/gpu/drm/i915/i915_iosf_mbi.h | 36 + drivers/gpu/drm/i915/i915_irq.c | 1208 +++ drivers/gpu/drm/i915/i915_irq.h | 49 + drivers/gpu/drm/i915/i915_jiffies.h | 16 + drivers/gpu/drm/i915/i915_list_util.h | 23 + drivers/gpu/drm/i915/i915_memcpy.c | 172 + drivers/gpu/drm/i915/i915_memcpy.h | 34 + drivers/gpu/drm/i915/i915_mitigations.c | 147 + drivers/gpu/drm/i915/i915_mitigations.h | 13 + drivers/gpu/drm/i915/i915_mm.c | 164 + drivers/gpu/drm/i915/i915_mm.h | 36 + drivers/gpu/drm/i915/i915_mmio_range.c | 18 + drivers/gpu/drm/i915/i915_mmio_range.h | 19 + drivers/gpu/drm/i915/i915_module.c | 129 + drivers/gpu/drm/i915/i915_overlay.c | 523 ++ drivers/gpu/drm/i915/i915_overlay.h | 11 + drivers/gpu/drm/i915/i915_panic.c | 35 + drivers/gpu/drm/i915/i915_panic.h | 9 + drivers/gpu/drm/i915/i915_params.c | 224 + drivers/gpu/drm/i915/i915_params.h | 80 + drivers/gpu/drm/i915/i915_pci.c | 1042 +++ drivers/gpu/drm/i915/i915_pci.h | 18 + drivers/gpu/drm/i915/i915_perf.c | 5262 ++++++++++++ drivers/gpu/drm/i915/i915_perf.h | 62 + drivers/gpu/drm/i915/i915_perf_oa_regs.h | 215 + drivers/gpu/drm/i915/i915_perf_types.h | 511 ++ drivers/gpu/drm/i915/i915_pmu.c | 1233 +++ drivers/gpu/drm/i915/i915_pmu.h | 162 + drivers/gpu/drm/i915/i915_priolist_types.h | 47 + drivers/gpu/drm/i915/i915_ptr_util.h | 66 + drivers/gpu/drm/i915/i915_pvinfo.h | 123 + drivers/gpu/drm/i915/i915_query.c | 639 ++ drivers/gpu/drm/i915/i915_query.h | 15 + drivers/gpu/drm/i915/i915_reg.h | 753 ++ drivers/gpu/drm/i915/i915_reg_defs.h | 54 + drivers/gpu/drm/i915/i915_request.c | 2315 ++++++ drivers/gpu/drm/i915/i915_request.h | 733 ++ drivers/gpu/drm/i915/i915_scatterlist.c | 242 + drivers/gpu/drm/i915/i915_scatterlist.h | 240 + drivers/gpu/drm/i915/i915_scheduler.c | 511 ++ drivers/gpu/drm/i915/i915_scheduler.h | 109 + drivers/gpu/drm/i915/i915_scheduler_types.h | 205 + drivers/gpu/drm/i915/i915_selftest.h | 145 + drivers/gpu/drm/i915/i915_sw_fence.c | 610 ++ drivers/gpu/drm/i915/i915_sw_fence.h | 123 + drivers/gpu/drm/i915/i915_sw_fence_work.c | 99 + drivers/gpu/drm/i915/i915_sw_fence_work.h | 67 + drivers/gpu/drm/i915/i915_switcheroo.c | 79 + drivers/gpu/drm/i915/i915_switcheroo.h | 14 + drivers/gpu/drm/i915/i915_syncmap.c | 408 + drivers/gpu/drm/i915/i915_syncmap.h | 38 + drivers/gpu/drm/i915/i915_sysfs.c | 200 + drivers/gpu/drm/i915/i915_sysfs.h | 17 + drivers/gpu/drm/i915/i915_tasklet.h | 43 + drivers/gpu/drm/i915/i915_timer_util.c | 36 + drivers/gpu/drm/i915/i915_timer_util.h | 23 + drivers/gpu/drm/i915/i915_trace.h | 726 ++ drivers/gpu/drm/i915/i915_trace_points.c | 14 + drivers/gpu/drm/i915/i915_ttm_buddy_manager.c | 441 + drivers/gpu/drm/i915/i915_ttm_buddy_manager.h | 72 + drivers/gpu/drm/i915/i915_user_extensions.c | 61 + drivers/gpu/drm/i915/i915_user_extensions.h | 20 + drivers/gpu/drm/i915/i915_utils.c | 46 + drivers/gpu/drm/i915/i915_utils.h | 109 + drivers/gpu/drm/i915/i915_vgpu.c | 340 + drivers/gpu/drm/i915/i915_vgpu.h | 42 + drivers/gpu/drm/i915/i915_vma.c | 2334 ++++++ drivers/gpu/drm/i915/i915_vma.h | 479 ++ drivers/gpu/drm/i915/i915_vma_resource.c | 425 + drivers/gpu/drm/i915/i915_vma_resource.h | 261 + drivers/gpu/drm/i915/i915_vma_types.h | 255 + drivers/gpu/drm/i915/i915_wait_util.h | 119 + drivers/gpu/drm/i915/intel_clock_gating.c | 543 ++ drivers/gpu/drm/i915/intel_clock_gating.h | 14 + drivers/gpu/drm/i915/intel_cpu_info.c | 44 + drivers/gpu/drm/i915/intel_cpu_info.h | 13 + drivers/gpu/drm/i915/intel_device_info.c | 429 + drivers/gpu/drm/i915/intel_device_info.h | 268 + drivers/gpu/drm/i915/intel_gvt.c | 325 + drivers/gpu/drm/i915/intel_gvt.h | 78 + drivers/gpu/drm/i915/intel_gvt_mmio_table.c | 1328 +++ drivers/gpu/drm/i915/intel_memory_region.c | 430 + drivers/gpu/drm/i915/intel_memory_region.h | 135 + drivers/gpu/drm/i915/intel_pcode.c | 308 + drivers/gpu/drm/i915/intel_pcode.h | 32 + drivers/gpu/drm/i915/intel_region_ttm.c | 266 + drivers/gpu/drm/i915/intel_region_ttm.h | 44 + drivers/gpu/drm/i915/intel_runtime_pm.c | 548 ++ drivers/gpu/drm/i915/intel_runtime_pm.h | 232 + drivers/gpu/drm/i915/intel_step.c | 272 + drivers/gpu/drm/i915/intel_step.h | 27 + drivers/gpu/drm/i915/intel_uncore.c | 2928 +++++++ drivers/gpu/drm/i915/intel_uncore.h | 518 ++ drivers/gpu/drm/i915/intel_uncore_trace.c | 7 + drivers/gpu/drm/i915/intel_uncore_trace.h | 49 + drivers/gpu/drm/i915/intel_wakeref.c | 229 + drivers/gpu/drm/i915/intel_wakeref.h | 351 + drivers/gpu/drm/i915/pxp/intel_pxp.c | 544 ++ drivers/gpu/drm/i915/pxp/intel_pxp.h | 38 + drivers/gpu/drm/i915/pxp/intel_pxp_cmd.c | 143 + drivers/gpu/drm/i915/pxp/intel_pxp_cmd.h | 15 + .../gpu/drm/i915/pxp/intel_pxp_cmd_interface_42.h | 43 + .../gpu/drm/i915/pxp/intel_pxp_cmd_interface_43.h | 61 + .../gpu/drm/i915/pxp/intel_pxp_cmd_interface_cmn.h | 42 + drivers/gpu/drm/i915/pxp/intel_pxp_debugfs.c | 91 + drivers/gpu/drm/i915/pxp/intel_pxp_debugfs.h | 21 + drivers/gpu/drm/i915/pxp/intel_pxp_gsccs.c | 448 ++ drivers/gpu/drm/i915/pxp/intel_pxp_gsccs.h | 45 + drivers/gpu/drm/i915/pxp/intel_pxp_huc.c | 73 + drivers/gpu/drm/i915/pxp/intel_pxp_huc.h | 13 + drivers/gpu/drm/i915/pxp/intel_pxp_irq.c | 109 + drivers/gpu/drm/i915/pxp/intel_pxp_irq.h | 40 + drivers/gpu/drm/i915/pxp/intel_pxp_pm.c | 80 + drivers/gpu/drm/i915/pxp/intel_pxp_pm.h | 38 + drivers/gpu/drm/i915/pxp/intel_pxp_regs.h | 27 + drivers/gpu/drm/i915/pxp/intel_pxp_session.c | 192 + drivers/gpu/drm/i915/pxp/intel_pxp_session.h | 25 + drivers/gpu/drm/i915/pxp/intel_pxp_tee.c | 413 + drivers/gpu/drm/i915/pxp/intel_pxp_tee.h | 22 + drivers/gpu/drm/i915/pxp/intel_pxp_types.h | 130 + drivers/gpu/drm/i915/selftests/i915_active.c | 354 + drivers/gpu/drm/i915/selftests/i915_gem.c | 262 + drivers/gpu/drm/i915/selftests/i915_gem_evict.c | 571 ++ drivers/gpu/drm/i915/selftests/i915_gem_gtt.c | 1983 +++++ .../gpu/drm/i915/selftests/i915_live_selftests.h | 55 + .../gpu/drm/i915/selftests/i915_mock_selftests.h | 35 + drivers/gpu/drm/i915/selftests/i915_perf.c | 445 + .../gpu/drm/i915/selftests/i915_perf_selftests.h | 21 + drivers/gpu/drm/i915/selftests/i915_random.c | 109 + drivers/gpu/drm/i915/selftests/i915_random.h | 65 + drivers/gpu/drm/i915/selftests/i915_request.c | 3303 ++++++++ drivers/gpu/drm/i915/selftests/i915_selftest.c | 590 ++ drivers/gpu/drm/i915/selftests/i915_sw_fence.c | 767 ++ drivers/gpu/drm/i915/selftests/i915_syncmap.c | 616 ++ drivers/gpu/drm/i915/selftests/i915_vma.c | 1112 +++ drivers/gpu/drm/i915/selftests/igt_atomic.c | 47 + drivers/gpu/drm/i915/selftests/igt_atomic.h | 17 + drivers/gpu/drm/i915/selftests/igt_flush_test.c | 51 + drivers/gpu/drm/i915/selftests/igt_flush_test.h | 14 + drivers/gpu/drm/i915/selftests/igt_live_test.c | 83 + drivers/gpu/drm/i915/selftests/igt_live_test.h | 36 + drivers/gpu/drm/i915/selftests/igt_mmap.c | 65 + drivers/gpu/drm/i915/selftests/igt_mmap.h | 29 + drivers/gpu/drm/i915/selftests/igt_reset.c | 51 + drivers/gpu/drm/i915/selftests/igt_reset.h | 18 + drivers/gpu/drm/i915/selftests/igt_spinner.c | 265 + drivers/gpu/drm/i915/selftests/igt_spinner.h | 43 + .../gpu/drm/i915/selftests/intel_memory_region.c | 1405 ++++ .../drm/i915/selftests/intel_scheduler_helpers.c | 101 + .../drm/i915/selftests/intel_scheduler_helpers.h | 35 + drivers/gpu/drm/i915/selftests/intel_uncore.c | 293 + drivers/gpu/drm/i915/selftests/lib_sw_fence.c | 136 + drivers/gpu/drm/i915/selftests/lib_sw_fence.h | 43 + drivers/gpu/drm/i915/selftests/librapl.c | 34 + drivers/gpu/drm/i915/selftests/librapl.h | 17 + drivers/gpu/drm/i915/selftests/mock_drm.h | 45 + drivers/gpu/drm/i915/selftests/mock_gem_device.c | 284 + drivers/gpu/drm/i915/selftests/mock_gem_device.h | 12 + drivers/gpu/drm/i915/selftests/mock_gtt.c | 136 + drivers/gpu/drm/i915/selftests/mock_gtt.h | 37 + drivers/gpu/drm/i915/selftests/mock_region.c | 124 + drivers/gpu/drm/i915/selftests/mock_region.h | 22 + drivers/gpu/drm/i915/selftests/mock_request.c | 59 + drivers/gpu/drm/i915/selftests/mock_request.h | 37 + drivers/gpu/drm/i915/selftests/mock_uncore.c | 49 + drivers/gpu/drm/i915/selftests/mock_uncore.h | 34 + drivers/gpu/drm/i915/selftests/scatterlist.c | 384 + drivers/gpu/drm/i915/vlv_iosf_sb.c | 239 + drivers/gpu/drm/i915/vlv_iosf_sb.h | 27 + drivers/gpu/drm/i915/vlv_suspend.c | 482 ++ drivers/gpu/drm/i915/vlv_suspend.h | 18 + 910 files changed, 420078 insertions(+) create mode 100644 drivers/gpu/drm/i915/.gitignore create mode 100644 drivers/gpu/drm/i915/Kconfig create mode 100644 drivers/gpu/drm/i915/Kconfig.debug create mode 100644 drivers/gpu/drm/i915/Kconfig.profile create mode 100644 drivers/gpu/drm/i915/Makefile create mode 100644 drivers/gpu/drm/i915/TODO.txt create mode 100644 drivers/gpu/drm/i915/display/bxt_dpio_phy_regs.h 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_display_sr.c create mode 100644 drivers/gpu/drm/i915/display/i9xx_display_sr.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/i9xx_plane_regs.h create mode 100644 drivers/gpu/drm/i915/display/i9xx_wm.c create mode 100644 drivers/gpu/drm/i915/display/i9xx_wm.h create mode 100644 drivers/gpu/drm/i915/display/i9xx_wm_regs.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_alpm.c create mode 100644 drivers/gpu/drm/i915/display/intel_alpm.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_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_bo.c create mode 100644 drivers/gpu/drm/i915/display/intel_bo.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_casf.c create mode 100644 drivers/gpu/drm/i915/display/intel_casf.h create mode 100644 drivers/gpu/drm/i915/display/intel_casf_regs.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_cmtg.c create mode 100644 drivers/gpu/drm/i915/display/intel_cmtg.h create mode 100644 drivers/gpu/drm/i915/display/intel_cmtg_regs.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_color_pipeline.c create mode 100644 drivers/gpu/drm/i915/display/intel_color_pipeline.h create mode 100644 drivers/gpu/drm/i915/display/intel_color_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_colorop.c create mode 100644 drivers/gpu/drm/i915/display/intel_colorop.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_crt_regs.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_cursor_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_cx0_phy.c create mode 100644 drivers/gpu/drm/i915/display/intel_cx0_phy.h create mode 100644 drivers/gpu/drm/i915/display/intel_cx0_phy_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_dbuf_bw.c create mode 100644 drivers/gpu/drm/i915/display/intel_dbuf_bw.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.c 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_clock_gating.c create mode 100644 drivers/gpu/drm/i915/display/intel_display_clock_gating.h create mode 100644 drivers/gpu/drm/i915/display/intel_display_conversion.c create mode 100644 drivers/gpu/drm/i915/display/intel_display_conversion.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_debugfs_params.c create mode 100644 drivers/gpu/drm/i915/display/intel_display_debugfs_params.h create mode 100644 drivers/gpu/drm/i915/display/intel_display_device.c create mode 100644 drivers/gpu/drm/i915/display/intel_display_device.h create mode 100644 drivers/gpu/drm/i915/display/intel_display_driver.c create mode 100644 drivers/gpu/drm/i915/display/intel_display_driver.h create mode 100644 drivers/gpu/drm/i915/display/intel_display_irq.c create mode 100644 drivers/gpu/drm/i915/display/intel_display_irq.h create mode 100644 drivers/gpu/drm/i915/display/intel_display_jiffies.h create mode 100644 drivers/gpu/drm/i915/display/intel_display_limits.h create mode 100644 drivers/gpu/drm/i915/display/intel_display_params.c create mode 100644 drivers/gpu/drm/i915/display/intel_display_params.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_reg_defs.h create mode 100644 drivers/gpu/drm/i915/display/intel_display_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_display_reset.c create mode 100644 drivers/gpu/drm/i915/display/intel_display_reset.h create mode 100644 drivers/gpu/drm/i915/display/intel_display_rpm.c create mode 100644 drivers/gpu/drm/i915/display/intel_display_rpm.h create mode 100644 drivers/gpu/drm/i915/display/intel_display_rps.c create mode 100644 drivers/gpu/drm/i915/display/intel_display_rps.h create mode 100644 drivers/gpu/drm/i915/display/intel_display_snapshot.c create mode 100644 drivers/gpu/drm/i915/display/intel_display_snapshot.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_display_utils.c create mode 100644 drivers/gpu/drm/i915/display/intel_display_utils.h create mode 100644 drivers/gpu/drm/i915/display/intel_display_wa.c create mode 100644 drivers/gpu/drm/i915/display/intel_display_wa.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_dkl_phy_regs.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_dmc_wl.c create mode 100644 drivers/gpu/drm/i915/display/intel_dmc_wl.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_aux_regs.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_dp_test.c create mode 100644 drivers/gpu/drm/i915/display/intel_dp_test.h create mode 100644 drivers/gpu/drm/i915/display/intel_dp_tunnel.c create mode 100644 drivers/gpu/drm/i915/display/intel_dp_tunnel.h create mode 100644 drivers/gpu/drm/i915/display/intel_dpcd.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_dram.c create mode 100644 drivers/gpu/drm/i915/display/intel_dram.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_dsb_regs.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_dsi_vbt_defs.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_dvo_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_encoder.c create mode 100644 drivers/gpu/drm/i915/display/intel_encoder.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_fbc.c create mode 100644 drivers/gpu/drm/i915/display/intel_fbc.h create mode 100644 drivers/gpu/drm/i915/display/intel_fbc_regs.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_fdi_regs.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_fixed.h create mode 100644 drivers/gpu/drm/i915/display/intel_flipq.c create mode 100644 drivers/gpu/drm/i915/display/intel_flipq.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_gvt_api.c create mode 100644 drivers/gpu/drm/i915/display/intel_gvt_api.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_gsc_message.c create mode 100644 drivers/gpu/drm/i915/display/intel_hdcp_gsc_message.h create mode 100644 drivers/gpu/drm/i915/display/intel_hdcp_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_hdcp_shim.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_hotplug_irq.c create mode 100644 drivers/gpu/drm/i915/display/intel_hotplug_irq.h create mode 100644 drivers/gpu/drm/i915/display/intel_hti.c create mode 100644 drivers/gpu/drm/i915/display/intel_hti.h create mode 100644 drivers/gpu/drm/i915/display/intel_hti_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_initial_plane.c create mode 100644 drivers/gpu/drm/i915/display/intel_initial_plane.h create mode 100644 drivers/gpu/drm/i915/display/intel_link_bw.c create mode 100644 drivers/gpu/drm/i915/display/intel_link_bw.h create mode 100644 drivers/gpu/drm/i915/display/intel_load_detect.c create mode 100644 drivers/gpu/drm/i915/display/intel_load_detect.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_lt_phy.c create mode 100644 drivers/gpu/drm/i915/display/intel_lt_phy.h create mode 100644 drivers/gpu/drm/i915/display/intel_lt_phy_regs.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_lvds_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_mchbar.c create mode 100644 drivers/gpu/drm/i915/display/intel_mchbar.h create mode 100644 drivers/gpu/drm/i915/display/intel_mg_phy_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_modeset_lock.c create mode 100644 drivers/gpu/drm/i915/display/intel_modeset_lock.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_oprom_regs.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_parent.c create mode 100644 drivers/gpu/drm/i915/display/intel_parent.h create mode 100644 drivers/gpu/drm/i915/display/intel_pch.c create mode 100644 drivers/gpu/drm/i915/display/intel_pch.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_pfit.c create mode 100644 drivers/gpu/drm/i915/display/intel_pfit.h create mode 100644 drivers/gpu/drm/i915/display/intel_pfit_regs.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_pipe_crc_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_plane.c create mode 100644 drivers/gpu/drm/i915/display/intel_plane.h create mode 100644 drivers/gpu/drm/i915/display/intel_pmdemand.c create mode 100644 drivers/gpu/drm/i915/display/intel_pmdemand.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_pps_regs.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_psr_regs.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_rom.c create mode 100644 drivers/gpu/drm/i915/display/intel_rom.h create mode 100644 drivers/gpu/drm/i915/display/intel_sbi.c create mode 100644 drivers/gpu/drm/i915/display/intel_sbi.h create mode 100644 drivers/gpu/drm/i915/display/intel_sbi_regs.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_hdmi_pll.c create mode 100644 drivers/gpu/drm/i915/display/intel_snps_hdmi_pll.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_sprite_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_sprite_uapi.c create mode 100644 drivers/gpu/drm/i915/display/intel_sprite_uapi.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_tdf.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_tv_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_vblank.c create mode 100644 drivers/gpu/drm/i915/display/intel_vblank.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_vdsc_regs.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_vga_regs.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/intel_vrr_regs.h create mode 100644 drivers/gpu/drm/i915/display/intel_wm.c create mode 100644 drivers/gpu/drm/i915/display/intel_wm.h create mode 100644 drivers/gpu/drm/i915/display/intel_wm_types.h create mode 100644 drivers/gpu/drm/i915/display/skl_prefill.c create mode 100644 drivers/gpu/drm/i915/display/skl_prefill.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_universal_plane_regs.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/skl_watermark_regs.h create mode 100644 drivers/gpu/drm/i915/display/vlv_clock.c create mode 100644 drivers/gpu/drm/i915/display/vlv_clock.h create mode 100644 drivers/gpu/drm/i915/display/vlv_dpio_phy_regs.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 create mode 100644 drivers/gpu/drm/i915/display/vlv_sideband.c create mode 100644 drivers/gpu/drm/i915/display/vlv_sideband.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_busy.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_clflush.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_clflush.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_context.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_context.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_context_types.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_create.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_create.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_dmabuf.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_dmabuf.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_domain.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_domain.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_internal.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_internal.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_ioctls.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_lmem.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_lmem.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_mman.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_mman.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_object.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_object.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_object_frontbuffer.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_object_frontbuffer.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_object_types.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_pages.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_phys.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_pm.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_pm.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_region.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_region.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_shmem.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_shrinker.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_shrinker.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_stolen.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_stolen.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_throttle.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_tiling.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_tiling.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_ttm.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_ttm.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_ttm_move.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_ttm_move.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_ttm_pm.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_ttm_pm.h create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_userptr.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_wait.c create mode 100644 drivers/gpu/drm/i915/gem/selftests/huge_gem_object.c create mode 100644 drivers/gpu/drm/i915/gem/selftests/huge_gem_object.h create mode 100644 drivers/gpu/drm/i915/gem/selftests/huge_pages.c create mode 100644 drivers/gpu/drm/i915/gem/selftests/i915_gem_client_blt.c create mode 100644 drivers/gpu/drm/i915/gem/selftests/i915_gem_coherency.c create mode 100644 drivers/gpu/drm/i915/gem/selftests/i915_gem_context.c create mode 100644 drivers/gpu/drm/i915/gem/selftests/i915_gem_dmabuf.c create mode 100644 drivers/gpu/drm/i915/gem/selftests/i915_gem_migrate.c create mode 100644 drivers/gpu/drm/i915/gem/selftests/i915_gem_mman.c create mode 100644 drivers/gpu/drm/i915/gem/selftests/i915_gem_object.c create mode 100644 drivers/gpu/drm/i915/gem/selftests/i915_gem_phys.c create mode 100644 drivers/gpu/drm/i915/gem/selftests/igt_gem_utils.c create mode 100644 drivers/gpu/drm/i915/gem/selftests/igt_gem_utils.h create mode 100644 drivers/gpu/drm/i915/gem/selftests/mock_context.c create mode 100644 drivers/gpu/drm/i915/gem/selftests/mock_context.h create mode 100644 drivers/gpu/drm/i915/gem/selftests/mock_dmabuf.c create mode 100644 drivers/gpu/drm/i915/gem/selftests/mock_dmabuf.h create mode 100644 drivers/gpu/drm/i915/gem/selftests/mock_gem_object.h create mode 100644 drivers/gpu/drm/i915/gt/gen2_engine_cs.c create mode 100644 drivers/gpu/drm/i915/gt/gen2_engine_cs.h create mode 100644 drivers/gpu/drm/i915/gt/gen6_engine_cs.c create mode 100644 drivers/gpu/drm/i915/gt/gen6_engine_cs.h create mode 100644 drivers/gpu/drm/i915/gt/gen6_ppgtt.c create mode 100644 drivers/gpu/drm/i915/gt/gen6_ppgtt.h create mode 100644 drivers/gpu/drm/i915/gt/gen6_renderstate.c create mode 100644 drivers/gpu/drm/i915/gt/gen7_renderclear.c create mode 100644 drivers/gpu/drm/i915/gt/gen7_renderclear.h create mode 100644 drivers/gpu/drm/i915/gt/gen7_renderstate.c create mode 100644 drivers/gpu/drm/i915/gt/gen8_engine_cs.c create mode 100644 drivers/gpu/drm/i915/gt/gen8_engine_cs.h create mode 100644 drivers/gpu/drm/i915/gt/gen8_ppgtt.c create mode 100644 drivers/gpu/drm/i915/gt/gen8_ppgtt.h create mode 100644 drivers/gpu/drm/i915/gt/gen8_renderstate.c create mode 100644 drivers/gpu/drm/i915/gt/gen9_renderstate.c create mode 100644 drivers/gpu/drm/i915/gt/hsw_clear_kernel.c create mode 100644 drivers/gpu/drm/i915/gt/intel_breadcrumbs.c create mode 100644 drivers/gpu/drm/i915/gt/intel_breadcrumbs.h create mode 100644 drivers/gpu/drm/i915/gt/intel_breadcrumbs_types.h create mode 100644 drivers/gpu/drm/i915/gt/intel_context.c create mode 100644 drivers/gpu/drm/i915/gt/intel_context.h create mode 100644 drivers/gpu/drm/i915/gt/intel_context_param.h create mode 100644 drivers/gpu/drm/i915/gt/intel_context_sseu.c create mode 100644 drivers/gpu/drm/i915/gt/intel_context_types.h create mode 100644 drivers/gpu/drm/i915/gt/intel_engine.h create mode 100644 drivers/gpu/drm/i915/gt/intel_engine_cs.c create mode 100644 drivers/gpu/drm/i915/gt/intel_engine_heartbeat.c create mode 100644 drivers/gpu/drm/i915/gt/intel_engine_heartbeat.h create mode 100644 drivers/gpu/drm/i915/gt/intel_engine_pm.c create mode 100644 drivers/gpu/drm/i915/gt/intel_engine_pm.h create mode 100644 drivers/gpu/drm/i915/gt/intel_engine_regs.h create mode 100644 drivers/gpu/drm/i915/gt/intel_engine_stats.h create mode 100644 drivers/gpu/drm/i915/gt/intel_engine_types.h create mode 100644 drivers/gpu/drm/i915/gt/intel_engine_user.c create mode 100644 drivers/gpu/drm/i915/gt/intel_engine_user.h create mode 100644 drivers/gpu/drm/i915/gt/intel_execlists_submission.c create mode 100644 drivers/gpu/drm/i915/gt/intel_execlists_submission.h create mode 100644 drivers/gpu/drm/i915/gt/intel_ggtt.c create mode 100644 drivers/gpu/drm/i915/gt/intel_ggtt_fencing.c create mode 100644 drivers/gpu/drm/i915/gt/intel_ggtt_fencing.h create mode 100644 drivers/gpu/drm/i915/gt/intel_ggtt_gmch.c create mode 100644 drivers/gpu/drm/i915/gt/intel_ggtt_gmch.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gpu_commands.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gsc.c create mode 100644 drivers/gpu/drm/i915/gt/intel_gsc.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gt.c create mode 100644 drivers/gpu/drm/i915/gt/intel_gt.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_buffer_pool.c create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_buffer_pool.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_buffer_pool_types.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_ccs_mode.c create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_ccs_mode.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_clock_utils.c create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_clock_utils.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_debugfs.c create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_debugfs.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_defines.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_engines_debugfs.c create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_engines_debugfs.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_irq.c create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_irq.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_mcr.c create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_mcr.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_pm.c create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_pm.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_pm_debugfs.c create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_pm_debugfs.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_pm_irq.c create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_pm_irq.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_print.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_regs.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_requests.c create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_requests.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_sysfs.c create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_sysfs.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_sysfs_pm.c create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_sysfs_pm.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gt_types.h create mode 100644 drivers/gpu/drm/i915/gt/intel_gtt.c create mode 100644 drivers/gpu/drm/i915/gt/intel_gtt.h create mode 100644 drivers/gpu/drm/i915/gt/intel_hwconfig.h create mode 100644 drivers/gpu/drm/i915/gt/intel_llc.c create mode 100644 drivers/gpu/drm/i915/gt/intel_llc.h create mode 100644 drivers/gpu/drm/i915/gt/intel_llc_types.h create mode 100644 drivers/gpu/drm/i915/gt/intel_lrc.c create mode 100644 drivers/gpu/drm/i915/gt/intel_lrc.h create mode 100644 drivers/gpu/drm/i915/gt/intel_lrc_reg.h create mode 100644 drivers/gpu/drm/i915/gt/intel_migrate.c create mode 100644 drivers/gpu/drm/i915/gt/intel_migrate.h create mode 100644 drivers/gpu/drm/i915/gt/intel_migrate_types.h create mode 100644 drivers/gpu/drm/i915/gt/intel_mocs.c create mode 100644 drivers/gpu/drm/i915/gt/intel_mocs.h create mode 100644 drivers/gpu/drm/i915/gt/intel_ppgtt.c create mode 100644 drivers/gpu/drm/i915/gt/intel_rc6.c create mode 100644 drivers/gpu/drm/i915/gt/intel_rc6.h create mode 100644 drivers/gpu/drm/i915/gt/intel_rc6_types.h create mode 100644 drivers/gpu/drm/i915/gt/intel_region_lmem.c create mode 100644 drivers/gpu/drm/i915/gt/intel_region_lmem.h create mode 100644 drivers/gpu/drm/i915/gt/intel_renderstate.c create mode 100644 drivers/gpu/drm/i915/gt/intel_renderstate.h create mode 100644 drivers/gpu/drm/i915/gt/intel_reset.c create mode 100644 drivers/gpu/drm/i915/gt/intel_reset.h create mode 100644 drivers/gpu/drm/i915/gt/intel_reset_types.h create mode 100644 drivers/gpu/drm/i915/gt/intel_ring.c create mode 100644 drivers/gpu/drm/i915/gt/intel_ring.h create mode 100644 drivers/gpu/drm/i915/gt/intel_ring_submission.c create mode 100644 drivers/gpu/drm/i915/gt/intel_ring_types.h create mode 100644 drivers/gpu/drm/i915/gt/intel_rps.c create mode 100644 drivers/gpu/drm/i915/gt/intel_rps.h create mode 100644 drivers/gpu/drm/i915/gt/intel_rps_types.h create mode 100644 drivers/gpu/drm/i915/gt/intel_sa_media.c create mode 100644 drivers/gpu/drm/i915/gt/intel_sa_media.h create mode 100644 drivers/gpu/drm/i915/gt/intel_sseu.c create mode 100644 drivers/gpu/drm/i915/gt/intel_sseu.h create mode 100644 drivers/gpu/drm/i915/gt/intel_sseu_debugfs.c create mode 100644 drivers/gpu/drm/i915/gt/intel_sseu_debugfs.h create mode 100644 drivers/gpu/drm/i915/gt/intel_timeline.c create mode 100644 drivers/gpu/drm/i915/gt/intel_timeline.h create mode 100644 drivers/gpu/drm/i915/gt/intel_timeline_types.h create mode 100644 drivers/gpu/drm/i915/gt/intel_tlb.c create mode 100644 drivers/gpu/drm/i915/gt/intel_tlb.h create mode 100644 drivers/gpu/drm/i915/gt/intel_wopcm.c create mode 100644 drivers/gpu/drm/i915/gt/intel_wopcm.h create mode 100644 drivers/gpu/drm/i915/gt/intel_workarounds.c create mode 100644 drivers/gpu/drm/i915/gt/intel_workarounds.h create mode 100644 drivers/gpu/drm/i915/gt/intel_workarounds_types.h create mode 100644 drivers/gpu/drm/i915/gt/ivb_clear_kernel.c create mode 100644 drivers/gpu/drm/i915/gt/mock_engine.c create mode 100644 drivers/gpu/drm/i915/gt/mock_engine.h create mode 100644 drivers/gpu/drm/i915/gt/selftest_context.c create mode 100644 drivers/gpu/drm/i915/gt/selftest_engine.c create mode 100644 drivers/gpu/drm/i915/gt/selftest_engine.h create mode 100644 drivers/gpu/drm/i915/gt/selftest_engine_cs.c create mode 100644 drivers/gpu/drm/i915/gt/selftest_engine_heartbeat.c create mode 100644 drivers/gpu/drm/i915/gt/selftest_engine_heartbeat.h create mode 100644 drivers/gpu/drm/i915/gt/selftest_engine_pm.c create mode 100644 drivers/gpu/drm/i915/gt/selftest_execlists.c create mode 100644 drivers/gpu/drm/i915/gt/selftest_gt_pm.c create mode 100644 drivers/gpu/drm/i915/gt/selftest_hangcheck.c create mode 100644 drivers/gpu/drm/i915/gt/selftest_llc.c create mode 100644 drivers/gpu/drm/i915/gt/selftest_llc.h create mode 100644 drivers/gpu/drm/i915/gt/selftest_lrc.c create mode 100644 drivers/gpu/drm/i915/gt/selftest_migrate.c create mode 100644 drivers/gpu/drm/i915/gt/selftest_mocs.c create mode 100644 drivers/gpu/drm/i915/gt/selftest_rc6.c create mode 100644 drivers/gpu/drm/i915/gt/selftest_rc6.h create mode 100644 drivers/gpu/drm/i915/gt/selftest_reset.c create mode 100644 drivers/gpu/drm/i915/gt/selftest_ring.c create mode 100644 drivers/gpu/drm/i915/gt/selftest_ring_submission.c create mode 100644 drivers/gpu/drm/i915/gt/selftest_rps.c create mode 100644 drivers/gpu/drm/i915/gt/selftest_rps.h create mode 100644 drivers/gpu/drm/i915/gt/selftest_slpc.c create mode 100644 drivers/gpu/drm/i915/gt/selftest_timeline.c create mode 100644 drivers/gpu/drm/i915/gt/selftest_tlb.c create mode 100644 drivers/gpu/drm/i915/gt/selftest_workarounds.c create mode 100644 drivers/gpu/drm/i915/gt/selftests/mock_timeline.c create mode 100644 drivers/gpu/drm/i915/gt/selftests/mock_timeline.h create mode 100644 drivers/gpu/drm/i915/gt/shaders/README create mode 100644 drivers/gpu/drm/i915/gt/shaders/clear_kernel/hsw.asm create mode 100644 drivers/gpu/drm/i915/gt/shaders/clear_kernel/ivb.asm create mode 100644 drivers/gpu/drm/i915/gt/shmem_utils.c create mode 100644 drivers/gpu/drm/i915/gt/shmem_utils.h create mode 100644 drivers/gpu/drm/i915/gt/st_shmem_utils.c create mode 100644 drivers/gpu/drm/i915/gt/sysfs_engines.c create mode 100644 drivers/gpu/drm/i915/gt/sysfs_engines.h create mode 100644 drivers/gpu/drm/i915/gt/uc/abi/guc_actions_abi.h create mode 100644 drivers/gpu/drm/i915/gt/uc/abi/guc_actions_slpc_abi.h create mode 100644 drivers/gpu/drm/i915/gt/uc/abi/guc_communication_ctb_abi.h create mode 100644 drivers/gpu/drm/i915/gt/uc/abi/guc_communication_mmio_abi.h create mode 100644 drivers/gpu/drm/i915/gt/uc/abi/guc_errors_abi.h create mode 100644 drivers/gpu/drm/i915/gt/uc/abi/guc_klvs_abi.h create mode 100644 drivers/gpu/drm/i915/gt/uc/abi/guc_messages_abi.h create mode 100644 drivers/gpu/drm/i915/gt/uc/guc_capture_fwif.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_gsc_binary_headers.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_gsc_fw.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_gsc_fw.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_gsc_proxy.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_gsc_proxy.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_gsc_uc.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_gsc_uc.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_gsc_uc_debugfs.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_gsc_uc_debugfs.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_gsc_uc_heci_cmd_submit.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_gsc_uc_heci_cmd_submit.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_ads.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_ads.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_capture.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_capture.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_ct.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_ct.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_debugfs.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_debugfs.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_fw.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_fw.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_fwif.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_hwconfig.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_log.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_log.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_log_debugfs.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_log_debugfs.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_print.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_rc.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_rc.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_reg.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_slpc.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_slpc.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_slpc_types.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_guc_submission.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_huc.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_huc.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_huc_debugfs.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_huc_debugfs.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_huc_fw.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_huc_fw.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_huc_print.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_uc.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_uc.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_uc_debugfs.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_uc_debugfs.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_uc_fw.c create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_uc_fw.h create mode 100644 drivers/gpu/drm/i915/gt/uc/intel_uc_fw_abi.h create mode 100644 drivers/gpu/drm/i915/gt/uc/selftest_guc.c create mode 100644 drivers/gpu/drm/i915/gt/uc/selftest_guc_hangcheck.c create mode 100644 drivers/gpu/drm/i915/gt/uc/selftest_guc_multi_lrc.c create mode 100644 drivers/gpu/drm/i915/gvt/Makefile create mode 100644 drivers/gpu/drm/i915/gvt/aperture_gm.c create mode 100644 drivers/gpu/drm/i915/gvt/cfg_space.c create mode 100644 drivers/gpu/drm/i915/gvt/cmd_parser.c create mode 100644 drivers/gpu/drm/i915/gvt/cmd_parser.h create mode 100644 drivers/gpu/drm/i915/gvt/debug.h create mode 100644 drivers/gpu/drm/i915/gvt/debugfs.c create mode 100644 drivers/gpu/drm/i915/gvt/display.c create mode 100644 drivers/gpu/drm/i915/gvt/display.h create mode 100644 drivers/gpu/drm/i915/gvt/display_helpers.h create mode 100644 drivers/gpu/drm/i915/gvt/dmabuf.c create mode 100644 drivers/gpu/drm/i915/gvt/dmabuf.h create mode 100644 drivers/gpu/drm/i915/gvt/edid.c create mode 100644 drivers/gpu/drm/i915/gvt/edid.h create mode 100644 drivers/gpu/drm/i915/gvt/execlist.c create mode 100644 drivers/gpu/drm/i915/gvt/execlist.h create mode 100644 drivers/gpu/drm/i915/gvt/fb_decoder.c create mode 100644 drivers/gpu/drm/i915/gvt/fb_decoder.h create mode 100644 drivers/gpu/drm/i915/gvt/firmware.c create mode 100644 drivers/gpu/drm/i915/gvt/gtt.c create mode 100644 drivers/gpu/drm/i915/gvt/gtt.h create mode 100644 drivers/gpu/drm/i915/gvt/gvt.h create mode 100644 drivers/gpu/drm/i915/gvt/handlers.c create mode 100644 drivers/gpu/drm/i915/gvt/interrupt.c create mode 100644 drivers/gpu/drm/i915/gvt/interrupt.h create mode 100644 drivers/gpu/drm/i915/gvt/kvmgt.c create mode 100644 drivers/gpu/drm/i915/gvt/mmio.c create mode 100644 drivers/gpu/drm/i915/gvt/mmio.h create mode 100644 drivers/gpu/drm/i915/gvt/mmio_context.c create mode 100644 drivers/gpu/drm/i915/gvt/mmio_context.h create mode 100644 drivers/gpu/drm/i915/gvt/opregion.c create mode 100644 drivers/gpu/drm/i915/gvt/page_track.c create mode 100644 drivers/gpu/drm/i915/gvt/page_track.h create mode 100644 drivers/gpu/drm/i915/gvt/reg.h create mode 100644 drivers/gpu/drm/i915/gvt/sched_policy.c create mode 100644 drivers/gpu/drm/i915/gvt/sched_policy.h create mode 100644 drivers/gpu/drm/i915/gvt/scheduler.c create mode 100644 drivers/gpu/drm/i915/gvt/scheduler.h create mode 100644 drivers/gpu/drm/i915/gvt/trace.h create mode 100644 drivers/gpu/drm/i915/gvt/trace_points.c create mode 100644 drivers/gpu/drm/i915/gvt/vgpu.c create mode 100644 drivers/gpu/drm/i915/i915_active.c create mode 100644 drivers/gpu/drm/i915/i915_active.h create mode 100644 drivers/gpu/drm/i915/i915_active_types.h create mode 100644 drivers/gpu/drm/i915/i915_bo.c create mode 100644 drivers/gpu/drm/i915/i915_bo.h create mode 100644 drivers/gpu/drm/i915/i915_cmd_parser.c create mode 100644 drivers/gpu/drm/i915/i915_cmd_parser.h create mode 100644 drivers/gpu/drm/i915/i915_config.c create mode 100644 drivers/gpu/drm/i915/i915_config.h create mode 100644 drivers/gpu/drm/i915/i915_debugfs.c create mode 100644 drivers/gpu/drm/i915/i915_debugfs.h create mode 100644 drivers/gpu/drm/i915/i915_debugfs_params.c create mode 100644 drivers/gpu/drm/i915/i915_debugfs_params.h create mode 100644 drivers/gpu/drm/i915/i915_deps.c create mode 100644 drivers/gpu/drm/i915/i915_deps.h create mode 100644 drivers/gpu/drm/i915/i915_display_pc8.c create mode 100644 drivers/gpu/drm/i915/i915_display_pc8.h create mode 100644 drivers/gpu/drm/i915/i915_dpt.c create mode 100644 drivers/gpu/drm/i915/i915_dpt.h create mode 100644 drivers/gpu/drm/i915/i915_driver.c create mode 100644 drivers/gpu/drm/i915/i915_driver.h create mode 100644 drivers/gpu/drm/i915/i915_drm_client.c create mode 100644 drivers/gpu/drm/i915/i915_drm_client.h create mode 100644 drivers/gpu/drm/i915/i915_drv.h create mode 100644 drivers/gpu/drm/i915/i915_dsb_buffer.c create mode 100644 drivers/gpu/drm/i915/i915_dsb_buffer.h create mode 100644 drivers/gpu/drm/i915/i915_edram.c create mode 100644 drivers/gpu/drm/i915/i915_edram.h create mode 100644 drivers/gpu/drm/i915/i915_fb_pin.c create mode 100644 drivers/gpu/drm/i915/i915_fb_pin.h create mode 100644 drivers/gpu/drm/i915/i915_file_private.h create mode 100644 drivers/gpu/drm/i915/i915_freq.c create mode 100644 drivers/gpu/drm/i915/i915_freq.h create mode 100644 drivers/gpu/drm/i915/i915_gem.c create mode 100644 drivers/gpu/drm/i915/i915_gem.h create mode 100644 drivers/gpu/drm/i915/i915_gem_evict.c create mode 100644 drivers/gpu/drm/i915/i915_gem_evict.h create mode 100644 drivers/gpu/drm/i915/i915_gem_gtt.c create mode 100644 drivers/gpu/drm/i915/i915_gem_gtt.h create mode 100644 drivers/gpu/drm/i915/i915_gem_ww.c create mode 100644 drivers/gpu/drm/i915/i915_gem_ww.h create mode 100644 drivers/gpu/drm/i915/i915_getparam.c create mode 100644 drivers/gpu/drm/i915/i915_getparam.h create mode 100644 drivers/gpu/drm/i915/i915_gmch.c create mode 100644 drivers/gpu/drm/i915/i915_gmch.h create mode 100644 drivers/gpu/drm/i915/i915_gpu_error.c create mode 100644 drivers/gpu/drm/i915/i915_gpu_error.h create mode 100644 drivers/gpu/drm/i915/i915_gtt_view_types.h create mode 100644 drivers/gpu/drm/i915/i915_hdcp_gsc.c create mode 100644 drivers/gpu/drm/i915/i915_hdcp_gsc.h create mode 100644 drivers/gpu/drm/i915/i915_hwmon.c create mode 100644 drivers/gpu/drm/i915/i915_hwmon.h create mode 100644 drivers/gpu/drm/i915/i915_initial_plane.c create mode 100644 drivers/gpu/drm/i915/i915_initial_plane.h create mode 100644 drivers/gpu/drm/i915/i915_ioc32.c create mode 100644 drivers/gpu/drm/i915/i915_ioc32.h create mode 100644 drivers/gpu/drm/i915/i915_ioctl.c create mode 100644 drivers/gpu/drm/i915/i915_ioctl.h create mode 100644 drivers/gpu/drm/i915/i915_iosf_mbi.h create mode 100644 drivers/gpu/drm/i915/i915_irq.c create mode 100644 drivers/gpu/drm/i915/i915_irq.h create mode 100644 drivers/gpu/drm/i915/i915_jiffies.h create mode 100644 drivers/gpu/drm/i915/i915_list_util.h create mode 100644 drivers/gpu/drm/i915/i915_memcpy.c create mode 100644 drivers/gpu/drm/i915/i915_memcpy.h create mode 100644 drivers/gpu/drm/i915/i915_mitigations.c create mode 100644 drivers/gpu/drm/i915/i915_mitigations.h create mode 100644 drivers/gpu/drm/i915/i915_mm.c create mode 100644 drivers/gpu/drm/i915/i915_mm.h create mode 100644 drivers/gpu/drm/i915/i915_mmio_range.c create mode 100644 drivers/gpu/drm/i915/i915_mmio_range.h create mode 100644 drivers/gpu/drm/i915/i915_module.c create mode 100644 drivers/gpu/drm/i915/i915_overlay.c create mode 100644 drivers/gpu/drm/i915/i915_overlay.h create mode 100644 drivers/gpu/drm/i915/i915_panic.c create mode 100644 drivers/gpu/drm/i915/i915_panic.h create mode 100644 drivers/gpu/drm/i915/i915_params.c create mode 100644 drivers/gpu/drm/i915/i915_params.h create mode 100644 drivers/gpu/drm/i915/i915_pci.c create mode 100644 drivers/gpu/drm/i915/i915_pci.h create mode 100644 drivers/gpu/drm/i915/i915_perf.c create mode 100644 drivers/gpu/drm/i915/i915_perf.h create mode 100644 drivers/gpu/drm/i915/i915_perf_oa_regs.h create mode 100644 drivers/gpu/drm/i915/i915_perf_types.h create mode 100644 drivers/gpu/drm/i915/i915_pmu.c create mode 100644 drivers/gpu/drm/i915/i915_pmu.h create mode 100644 drivers/gpu/drm/i915/i915_priolist_types.h create mode 100644 drivers/gpu/drm/i915/i915_ptr_util.h create mode 100644 drivers/gpu/drm/i915/i915_pvinfo.h create mode 100644 drivers/gpu/drm/i915/i915_query.c create mode 100644 drivers/gpu/drm/i915/i915_query.h create mode 100644 drivers/gpu/drm/i915/i915_reg.h create mode 100644 drivers/gpu/drm/i915/i915_reg_defs.h create mode 100644 drivers/gpu/drm/i915/i915_request.c create mode 100644 drivers/gpu/drm/i915/i915_request.h create mode 100644 drivers/gpu/drm/i915/i915_scatterlist.c create mode 100644 drivers/gpu/drm/i915/i915_scatterlist.h create mode 100644 drivers/gpu/drm/i915/i915_scheduler.c create mode 100644 drivers/gpu/drm/i915/i915_scheduler.h create mode 100644 drivers/gpu/drm/i915/i915_scheduler_types.h create mode 100644 drivers/gpu/drm/i915/i915_selftest.h create mode 100644 drivers/gpu/drm/i915/i915_sw_fence.c create mode 100644 drivers/gpu/drm/i915/i915_sw_fence.h create mode 100644 drivers/gpu/drm/i915/i915_sw_fence_work.c create mode 100644 drivers/gpu/drm/i915/i915_sw_fence_work.h create mode 100644 drivers/gpu/drm/i915/i915_switcheroo.c create mode 100644 drivers/gpu/drm/i915/i915_switcheroo.h create mode 100644 drivers/gpu/drm/i915/i915_syncmap.c create mode 100644 drivers/gpu/drm/i915/i915_syncmap.h create mode 100644 drivers/gpu/drm/i915/i915_sysfs.c create mode 100644 drivers/gpu/drm/i915/i915_sysfs.h create mode 100644 drivers/gpu/drm/i915/i915_tasklet.h create mode 100644 drivers/gpu/drm/i915/i915_timer_util.c create mode 100644 drivers/gpu/drm/i915/i915_timer_util.h create mode 100644 drivers/gpu/drm/i915/i915_trace.h create mode 100644 drivers/gpu/drm/i915/i915_trace_points.c create mode 100644 drivers/gpu/drm/i915/i915_ttm_buddy_manager.c create mode 100644 drivers/gpu/drm/i915/i915_ttm_buddy_manager.h create mode 100644 drivers/gpu/drm/i915/i915_user_extensions.c create mode 100644 drivers/gpu/drm/i915/i915_user_extensions.h create mode 100644 drivers/gpu/drm/i915/i915_utils.c create mode 100644 drivers/gpu/drm/i915/i915_utils.h create mode 100644 drivers/gpu/drm/i915/i915_vgpu.c create mode 100644 drivers/gpu/drm/i915/i915_vgpu.h create mode 100644 drivers/gpu/drm/i915/i915_vma.c create mode 100644 drivers/gpu/drm/i915/i915_vma.h create mode 100644 drivers/gpu/drm/i915/i915_vma_resource.c create mode 100644 drivers/gpu/drm/i915/i915_vma_resource.h create mode 100644 drivers/gpu/drm/i915/i915_vma_types.h create mode 100644 drivers/gpu/drm/i915/i915_wait_util.h create mode 100644 drivers/gpu/drm/i915/intel_clock_gating.c create mode 100644 drivers/gpu/drm/i915/intel_clock_gating.h create mode 100644 drivers/gpu/drm/i915/intel_cpu_info.c create mode 100644 drivers/gpu/drm/i915/intel_cpu_info.h create mode 100644 drivers/gpu/drm/i915/intel_device_info.c create mode 100644 drivers/gpu/drm/i915/intel_device_info.h create mode 100644 drivers/gpu/drm/i915/intel_gvt.c create mode 100644 drivers/gpu/drm/i915/intel_gvt.h create mode 100644 drivers/gpu/drm/i915/intel_gvt_mmio_table.c create mode 100644 drivers/gpu/drm/i915/intel_memory_region.c create mode 100644 drivers/gpu/drm/i915/intel_memory_region.h create mode 100644 drivers/gpu/drm/i915/intel_pcode.c create mode 100644 drivers/gpu/drm/i915/intel_pcode.h create mode 100644 drivers/gpu/drm/i915/intel_region_ttm.c create mode 100644 drivers/gpu/drm/i915/intel_region_ttm.h create mode 100644 drivers/gpu/drm/i915/intel_runtime_pm.c create mode 100644 drivers/gpu/drm/i915/intel_runtime_pm.h create mode 100644 drivers/gpu/drm/i915/intel_step.c create mode 100644 drivers/gpu/drm/i915/intel_step.h create mode 100644 drivers/gpu/drm/i915/intel_uncore.c create mode 100644 drivers/gpu/drm/i915/intel_uncore.h create mode 100644 drivers/gpu/drm/i915/intel_uncore_trace.c create mode 100644 drivers/gpu/drm/i915/intel_uncore_trace.h create mode 100644 drivers/gpu/drm/i915/intel_wakeref.c create mode 100644 drivers/gpu/drm/i915/intel_wakeref.h create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp.c create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp.h create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_cmd.c create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_cmd.h create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_cmd_interface_42.h create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_cmd_interface_43.h create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_cmd_interface_cmn.h create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_debugfs.c create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_debugfs.h create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_gsccs.c create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_gsccs.h create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_huc.c create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_huc.h create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_irq.c create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_irq.h create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_pm.c create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_pm.h create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_regs.h create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_session.c create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_session.h create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_tee.c create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_tee.h create mode 100644 drivers/gpu/drm/i915/pxp/intel_pxp_types.h create mode 100644 drivers/gpu/drm/i915/selftests/i915_active.c create mode 100644 drivers/gpu/drm/i915/selftests/i915_gem.c create mode 100644 drivers/gpu/drm/i915/selftests/i915_gem_evict.c create mode 100644 drivers/gpu/drm/i915/selftests/i915_gem_gtt.c create mode 100644 drivers/gpu/drm/i915/selftests/i915_live_selftests.h create mode 100644 drivers/gpu/drm/i915/selftests/i915_mock_selftests.h create mode 100644 drivers/gpu/drm/i915/selftests/i915_perf.c create mode 100644 drivers/gpu/drm/i915/selftests/i915_perf_selftests.h create mode 100644 drivers/gpu/drm/i915/selftests/i915_random.c create mode 100644 drivers/gpu/drm/i915/selftests/i915_random.h create mode 100644 drivers/gpu/drm/i915/selftests/i915_request.c create mode 100644 drivers/gpu/drm/i915/selftests/i915_selftest.c create mode 100644 drivers/gpu/drm/i915/selftests/i915_sw_fence.c create mode 100644 drivers/gpu/drm/i915/selftests/i915_syncmap.c create mode 100644 drivers/gpu/drm/i915/selftests/i915_vma.c create mode 100644 drivers/gpu/drm/i915/selftests/igt_atomic.c create mode 100644 drivers/gpu/drm/i915/selftests/igt_atomic.h create mode 100644 drivers/gpu/drm/i915/selftests/igt_flush_test.c create mode 100644 drivers/gpu/drm/i915/selftests/igt_flush_test.h create mode 100644 drivers/gpu/drm/i915/selftests/igt_live_test.c create mode 100644 drivers/gpu/drm/i915/selftests/igt_live_test.h create mode 100644 drivers/gpu/drm/i915/selftests/igt_mmap.c create mode 100644 drivers/gpu/drm/i915/selftests/igt_mmap.h create mode 100644 drivers/gpu/drm/i915/selftests/igt_reset.c create mode 100644 drivers/gpu/drm/i915/selftests/igt_reset.h create mode 100644 drivers/gpu/drm/i915/selftests/igt_spinner.c create mode 100644 drivers/gpu/drm/i915/selftests/igt_spinner.h create mode 100644 drivers/gpu/drm/i915/selftests/intel_memory_region.c create mode 100644 drivers/gpu/drm/i915/selftests/intel_scheduler_helpers.c create mode 100644 drivers/gpu/drm/i915/selftests/intel_scheduler_helpers.h create mode 100644 drivers/gpu/drm/i915/selftests/intel_uncore.c create mode 100644 drivers/gpu/drm/i915/selftests/lib_sw_fence.c create mode 100644 drivers/gpu/drm/i915/selftests/lib_sw_fence.h create mode 100644 drivers/gpu/drm/i915/selftests/librapl.c create mode 100644 drivers/gpu/drm/i915/selftests/librapl.h create mode 100644 drivers/gpu/drm/i915/selftests/mock_drm.h create mode 100644 drivers/gpu/drm/i915/selftests/mock_gem_device.c create mode 100644 drivers/gpu/drm/i915/selftests/mock_gem_device.h create mode 100644 drivers/gpu/drm/i915/selftests/mock_gtt.c create mode 100644 drivers/gpu/drm/i915/selftests/mock_gtt.h create mode 100644 drivers/gpu/drm/i915/selftests/mock_region.c create mode 100644 drivers/gpu/drm/i915/selftests/mock_region.h create mode 100644 drivers/gpu/drm/i915/selftests/mock_request.c create mode 100644 drivers/gpu/drm/i915/selftests/mock_request.h create mode 100644 drivers/gpu/drm/i915/selftests/mock_uncore.c create mode 100644 drivers/gpu/drm/i915/selftests/mock_uncore.h create mode 100644 drivers/gpu/drm/i915/selftests/scatterlist.c create mode 100644 drivers/gpu/drm/i915/vlv_iosf_sb.c create mode 100644 drivers/gpu/drm/i915/vlv_iosf_sb.h create mode 100644 drivers/gpu/drm/i915/vlv_suspend.c create mode 100644 drivers/gpu/drm/i915/vlv_suspend.h (limited to 'drivers/gpu/drm/i915') diff --git a/drivers/gpu/drm/i915/.gitignore b/drivers/gpu/drm/i915/.gitignore new file mode 100644 index 000000000..81972dce1 --- /dev/null +++ b/drivers/gpu/drm/i915/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +*.hdrtest diff --git a/drivers/gpu/drm/i915/Kconfig b/drivers/gpu/drm/i915/Kconfig new file mode 100644 index 000000000..5e939004b --- /dev/null +++ b/drivers/gpu/drm/i915/Kconfig @@ -0,0 +1,185 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_I915 + tristate "Intel 8xx/9xx/G3x/G4x/HD Graphics" + depends on DRM + depends on X86 && PCI + depends on !PREEMPT_RT + select INTEL_GTT if X86 + select INTERVAL_TREE + # we need shmfs for the swappable backing store, and in particular + # the shmem_readpage() which depends upon tmpfs + select SHMEM + select TMPFS + select DRM_CLIENT_SELECTION + select DRM_DISPLAY_DP_HELPER + select DRM_DISPLAY_DSC_HELPER + select DRM_DISPLAY_HDCP_HELPER + select DRM_DISPLAY_HDMI_HELPER + select DRM_DISPLAY_HELPER + select DRM_KMS_HELPER + select DRM_PANEL + select DRM_MIPI_DSI + select FB_IOMEM_HELPERS if DRM_FBDEV_EMULATION + select RELAY + select I2C + select I2C_ALGOBIT + select IRQ_WORK + # i915 depends on ACPI_VIDEO when ACPI is enabled + # but for select to work, need to select ACPI_VIDEO's dependencies, ick + select BACKLIGHT_CLASS_DEVICE if ACPI + select INPUT if ACPI + select X86_PLATFORM_DEVICES if ACPI + select ACPI_WMI if ACPI + select ACPI_VIDEO if ACPI + select SYNC_FILE + select IOSF_MBI if X86 + select CRC32 + select SND_HDA_I915 if SND_HDA_CORE + select CEC_CORE if CEC_NOTIFIER + select VMAP_PFN + select DRM_TTM + select DRM_BUDDY + select AUXILIARY_BUS + help + Choose this option if you have a system that has "Intel Graphics + Media Accelerator" or "HD Graphics" integrated graphics, + including 830M, 845G, 852GM, 855GM, 865G, 915G, 945G, 965G, + G35, G41, G43, G45 chipsets and Celeron, Pentium, Core i3, + Core i5, Core i7 as well as Atom CPUs with integrated graphics. + + This driver is used by the Intel driver in X.org 6.8 and + XFree86 4.4 and above. It replaces the older i830 module that + supported a subset of the hardware in older X.org releases. + + Note that the older i810/i815 chipsets require the use of the + i810 driver instead, and the Atom z5xx series has an entirely + different implementation. + + If "M" is selected, the module will be called i915. + +config DRM_I915_FORCE_PROBE + string "Force probe i915 for selected Intel hardware IDs" + depends on DRM_I915 + help + This is the default value for the i915.force_probe module + parameter. Using the module parameter overrides this option. + + Force probe the i915 driver for Intel graphics devices that are + recognized but not properly supported by this kernel version. Force + probing an unsupported device taints the kernel. It is recommended to + upgrade to a kernel version with proper support as soon as it is + available. + + It can also be used to block the probe of recognized and fully + supported devices. + + Use "" to disable force probe. If in doubt, use this. + + Use "[,,...]" to force probe the i915 for listed + devices. For example, "4500" or "4500,4571". + + Use "*" to force probe the driver for all known devices. Not + recommended. + + Use "!" right before the ID to block the probe of the device. For + example, "4500,!4571" forces the probe of 4500 and blocks the probe of + 4571. + + Use "!*" to block the probe of the driver for all known devices. + +config DRM_I915_CAPTURE_ERROR + bool "Enable capturing GPU state following a hang" + depends on DRM_I915 + default y + help + This option enables capturing the GPU state when a hang is detected. + This information is vital for triaging hangs and assists in debugging. + Please report any hang for triaging according to: + https://drm.pages.freedesktop.org/intel-docs/how-to-file-i915-bugs.html + + If in doubt, say "Y". + +config DRM_I915_COMPRESS_ERROR + bool "Compress GPU error state" + depends on DRM_I915_CAPTURE_ERROR + select ZLIB_DEFLATE + default y + help + This option selects ZLIB_DEFLATE if it isn't already + selected and causes any error state captured upon a GPU hang + to be compressed using zlib. + + If in doubt, say "Y". + +config DRM_I915_USERPTR + bool "Always enable userptr support" + depends on DRM_I915 + select MMU_NOTIFIER + default y + help + This option selects CONFIG_MMU_NOTIFIER if it isn't already + selected to enabled full userptr support. + + If in doubt, say "Y". + +config DRM_I915_GVT_KVMGT + tristate "Enable KVM host support Intel GVT-g graphics virtualization" + depends on DRM_I915 + depends on KVM_X86 + depends on 64BIT + depends on VFIO + select DRM_I915_GVT + select KVM_EXTERNAL_WRITE_TRACKING + select VFIO_MDEV + + help + Choose this option if you want to enable Intel GVT-g graphics + virtualization technology host support with integrated graphics. + With GVT-g, it's possible to have one integrated graphics + device shared by multiple VMs under KVM. + + Note that this driver only supports newer device from Broadwell on. + For further information and setup guide, you can visit: + https://github.com/intel/gvt-linux/wiki. + + If in doubt, say "N". + +config DRM_I915_PXP + bool "Enable Intel PXP support" + depends on DRM_I915 + depends on INTEL_MEI && INTEL_MEI_PXP + default n + help + PXP (Protected Xe Path) is an i915 component, available on graphics + version 12 and newer GPUs, that helps to establish the hardware + protected session and manage the status of the alive software session, + as well as its life cycle. + +config DRM_I915_DP_TUNNEL + bool "Enable DP tunnel support" + depends on DRM_I915 + depends on USB4 + select DRM_DISPLAY_DP_TUNNEL + default y + help + Choose this option to detect DP tunnels and enable the Bandwidth + Allocation mode for such tunnels. This allows using the maximum + resolution allowed by the link BW on all displays sharing the + link BW, for instance on a Thunderbolt link. + + If in doubt, say "Y". + +menu "drm/i915 Debugging" +depends on DRM_I915 +depends on EXPERT +source "drivers/gpu/drm/i915/Kconfig.debug" +endmenu + +menu "drm/i915 Profile Guided Optimisation" + visible if EXPERT + depends on DRM_I915 + source "drivers/gpu/drm/i915/Kconfig.profile" +endmenu + +config DRM_I915_GVT + bool diff --git a/drivers/gpu/drm/i915/Kconfig.debug b/drivers/gpu/drm/i915/Kconfig.debug new file mode 100644 index 000000000..52a3a59b4 --- /dev/null +++ b/drivers/gpu/drm/i915/Kconfig.debug @@ -0,0 +1,278 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_I915_WERROR + bool "Force GCC to throw an error instead of a warning when compiling" + # As this may inadvertently break the build, only allow the user + # to shoot oneself in the foot iff they aim really hard + depends on EXPERT + # We use the dependency on !COMPILE_TEST to not be enabled in + # allmodconfig or allyesconfig configurations + depends on !COMPILE_TEST + default n + help + Add -Werror to the build flags for (and only for) i915.ko. + Do not enable this unless you are writing code for the i915.ko module. + + Recommended for driver developers only. + + If in doubt, say "N". + +config DRM_I915_REPLAY_GPU_HANGS_API + bool "Enable GPU hang replay userspace API" + depends on DRM_I915 + depends on EXPERT + default n + help + Choose this option if you want to enable special and unstable + userspace API used for replaying GPU hangs on a running system. + + This API is intended to be used by userspace graphics stack developers + and provides no stability guarantees. + + The API needs to be activated at boot time using the + enable_debug_only_api module parameter. + + If in doubt, say "N". + +config DRM_I915_DEBUG + bool "Enable additional driver debugging" + depends on DRM_I915 + depends on EXPERT # only for developers + depends on !COMPILE_TEST # never built by robots + select DEBUG_FS + select PREEMPT_COUNT + select I2C_CHARDEV + select REF_TRACKER + select STACKDEPOT + select STACKTRACE + select DRM_DISPLAY_DP_AUX_CHARDEV + select DRM_DISPLAY_DP_TUNNEL_STATE_DEBUG if DRM_I915_DP_TUNNEL + select X86_MSR # used by igt/pm_rpm + select DRM_VGEM # used by igt/prime_vgem (dmabuf interop checks) + select DRM_DEBUG_MM if DRM=y + select DRM_EXPORT_FOR_TESTS if m + select DRM_KUNIT_TEST if KUNIT + select DMABUF_KUNIT_TEST if KUNIT + select SW_SYNC # signaling validation framework (igt/syncobj*) + select DRM_I915_WERROR + select DRM_I915_DEBUG_GEM + select DRM_I915_DEBUG_GEM_ONCE + select DRM_I915_DEBUG_MMIO + select DRM_I915_DEBUG_RUNTIME_PM + select DRM_I915_DEBUG_WAKEREF + select DRM_I915_SW_FENCE_DEBUG_OBJECTS + select DRM_I915_SELFTEST + default n + help + Choose this option to turn on extra driver debugging that may affect + performance but will catch some internal issues. + + Recommended for driver developers only. + + If in doubt, say "N". + +config DRM_I915_DEBUG_MMIO + bool "Always insert extra checks around mmio access by default" + default n + help + By default, always enables the extra sanity checks (extra register + reads) around every mmio (register) access that will slow the system + down. This sets the default value of i915.mmio_debug to -1 and can + be overridden at module load. + + Recommended for driver developers only. + + If in doubt, say "N". + +config DRM_I915_DEBUG_GEM + bool "Insert extra checks into the GEM internals" + default n + depends on DRM_I915_WERROR + help + Enable extra sanity checks (including BUGs) along the GEM driver + paths that may slow the system down and if hit hang the machine. + + Recommended for driver developers only. + + If in doubt, say "N". + +config DRM_I915_DEBUG_GEM_ONCE + bool "Make a GEM debug failure fatal" + default n + depends on DRM_I915_DEBUG_GEM + help + During development, we often only want the very first failure + as that would otherwise be lost in the deluge of subsequent + failures. However, more casual testers may not want to trigger + a hard BUG_ON and hope that the system remains sufficiently usable + to capture a bug report in situ. + + Recommended for driver developers only. + + If in doubt, say "N". + +config DRM_I915_ERRLOG_GEM + bool "Insert extra logging (very verbose) for common GEM errors" + default n + depends on DRM_I915_DEBUG_GEM + help + Enable additional logging that may help track down the cause of + principally userspace errors. + + Recommended for driver developers only. + + If in doubt, say "N". + +config DRM_I915_TRACE_GEM + bool "Insert extra ftrace output from the GEM internals" + depends on DRM_I915_DEBUG_GEM + select TRACING + default n + help + Enable additional and verbose debugging output that will spam + ordinary tests, but may be vital for post-mortem debugging when + used with /proc/sys/kernel/ftrace_dump_on_oops + + Recommended for driver developers only. + + If in doubt, say "N". + +config DRM_I915_TRACE_GTT + bool "Insert extra ftrace output from the GTT internals" + depends on DRM_I915_DEBUG_GEM + select TRACING + default n + help + Enable additional and verbose debugging output that will spam + ordinary tests, but may be vital for post-mortem debugging when + used with /proc/sys/kernel/ftrace_dump_on_oops + + Recommended for driver developers only. + + If in doubt, say "N". + +config DRM_I915_SW_FENCE_DEBUG_OBJECTS + bool "Enable additional driver debugging for fence objects" + depends on DRM_I915 + select DEBUG_OBJECTS + default n + help + Choose this option to turn on extra driver debugging that may affect + performance but will catch some internal issues. + + Recommended for driver developers only. + + If in doubt, say "N". + +config DRM_I915_SW_FENCE_CHECK_DAG + bool "Enable additional driver debugging for detecting dependency cycles" + depends on DRM_I915 + default n + help + Choose this option to turn on extra driver debugging that may affect + performance but will catch some internal issues. + + Recommended for driver developers only. + + If in doubt, say "N". + +config DRM_I915_DEBUG_GUC + bool "Enable additional driver debugging for GuC" + depends on DRM_I915 + select STACKDEPOT + default n + help + Choose this option to turn on extra driver debugging that may affect + performance but will help resolve GuC related issues. + + Recommended for driver developers only. + + If in doubt, say "N". + +config DRM_I915_SELFTEST + bool "Enable selftests upon driver load" + depends on DRM_I915 + default n + select DRM_EXPORT_FOR_TESTS if m + select FAULT_INJECTION + select PRIME_NUMBERS + select CRC32 + help + Choose this option to allow the driver to perform selftests upon + loading; also requires the i915.selftest=1 module parameter. To + exit the module after running the selftests (i.e. to prevent normal + module initialisation afterwards) use i915.selftest=-1. + + Recommended for driver developers only. + + If in doubt, say "N". + +config DRM_I915_SELFTEST_BROKEN + bool "Enable broken and dangerous selftests" + depends on DRM_I915_SELFTEST + depends on BROKEN + default n + help + This option enables the execution of selftests that are "dangerous" + and may trigger unintended HW side-effects as they break strict + rules given in the HW specification. For science. + + Recommended for masochistic driver developers only. + + If in doubt, say "N". + +config DRM_I915_LOW_LEVEL_TRACEPOINTS + bool "Enable low level request tracing events" + depends on DRM_I915 + default n + help + Choose this option to turn on low level request tracing events. + This provides the ability to precisely monitor engine utilisation + and also analyze the request dependency resolving timeline. + + Recommended for driver developers only. + + If in doubt, say "N". + +config DRM_I915_DEBUG_VBLANK_EVADE + bool "Enable extra debug warnings for vblank evasion" + depends on DRM_I915 + default n + help + Choose this option to turn on extra debug warnings for the + vblank evade mechanism. This gives a warning every time the + the deadline allotted for the vblank evade critical section + is exceeded, even if there isn't an actual risk of missing + the vblank. + + Recommended for driver developers only. + + If in doubt, say "N". + +config DRM_I915_DEBUG_RUNTIME_PM + bool "Enable extra state checking for runtime PM" + depends on DRM_I915 + default n + select REF_TRACKER + select STACKDEPOT + select STACKTRACE + help + Choose this option to turn on extra state checking for the + runtime PM functionality. This may introduce overhead during + driver loading, suspend and resume operations. + + Recommended for driver developers only. + + If in doubt, say "N" + +config DRM_I915_DEBUG_WAKEREF + bool "Enable extra tracking for wakerefs" + depends on DRM_I915 + select REF_TRACKER + select STACKDEPOT + select STACKTRACE + help + Choose this option to turn on extra state checking and usage + tracking for the wakerefPM functionality. This may introduce + overhead during driver runtime. + + If in doubt, say "N" diff --git a/drivers/gpu/drm/i915/Kconfig.profile b/drivers/gpu/drm/i915/Kconfig.profile new file mode 100644 index 000000000..7cc38d25e --- /dev/null +++ b/drivers/gpu/drm/i915/Kconfig.profile @@ -0,0 +1,139 @@ +config DRM_I915_REQUEST_TIMEOUT + int "Default timeout for requests (ms)" + default 20000 # milliseconds + help + Configures the default timeout after which any user submissions will + be forcefully terminated. + + Beware setting this value lower, or close to heartbeat interval + rounded to whole seconds times three, in order to avoid allowing + misbehaving applications causing total rendering failure in unrelated + clients. + + May be 0 to disable the timeout. + +config DRM_I915_FENCE_TIMEOUT + int "Timeout for unsignaled foreign fences (ms, jiffy granularity)" + default 10000 # milliseconds + help + When listening to a foreign fence, we install a supplementary timer + to ensure that we are always signaled and our userspace is able to + make forward progress. This value specifies the timeout used for an + unsignaled foreign fence. + + May be 0 to disable the timeout, and rely on the foreign fence being + eventually signaled. + +config DRM_I915_USERFAULT_AUTOSUSPEND + int "Runtime autosuspend delay for userspace GGTT mmaps (ms)" + default 250 # milliseconds + help + On runtime suspend, as we suspend the device, we have to revoke + userspace GGTT mmaps and force userspace to take a pagefault on + their next access. The revocation and subsequent recreation of + the GGTT mmap can be very slow and so we impose a small hysteris + that complements the runtime-pm autosuspend and provides a lower + floor on the autosuspend delay. + + May be 0 to disable the extra delay and solely use the device level + runtime pm autosuspend delay tunable. + +config DRM_I915_HEARTBEAT_INTERVAL + int "Interval between heartbeat pulses (ms)" + default 2500 # milliseconds + help + The driver sends a periodic heartbeat down all active engines to + check the health of the GPU and undertake regular house-keeping of + internal driver state. + + This is adjustable via + /sys/class/drm/card?/engine/*/heartbeat_interval_ms + + May be 0 to disable heartbeats and therefore disable automatic GPU + hang detection. + +config DRM_I915_PREEMPT_TIMEOUT + int "Preempt timeout (ms, jiffy granularity)" + default 640 # milliseconds + help + How long to wait (in milliseconds) for a preemption event to occur + when submitting a new context. If the current context does not hit + an arbitration point and yield to HW before the timer expires, the + HW will be reset to allow the more important context to execute. + + This is adjustable via + /sys/class/drm/card?/engine/*/preempt_timeout_ms + + May be 0 to disable the timeout. + + The compiled in default may get overridden at driver probe time on + certain platforms and certain engines which will be reflected in the + sysfs control. + +config DRM_I915_PREEMPT_TIMEOUT_COMPUTE + int "Preempt timeout for compute engines (ms, jiffy granularity)" + default 7500 # milliseconds + help + How long to wait (in milliseconds) for a preemption event to occur + when submitting a new context to a compute capable engine. If the + current context does not hit an arbitration point and yield to HW + before the timer expires, the HW will be reset to allow the more + important context to execute. + + This is adjustable via + /sys/class/drm/card?/engine/*/preempt_timeout_ms + + May be 0 to disable the timeout. + + The compiled in default may get overridden at driver probe time on + certain platforms and certain engines which will be reflected in the + sysfs control. + +config DRM_I915_MAX_REQUEST_BUSYWAIT + int "Busywait for request completion limit (ns)" + default 8000 # nanoseconds + help + Before sleeping waiting for a request (GPU operation) to complete, + we may spend some time polling for its completion. As the IRQ may + take a non-negligible time to setup, we do a short spin first to + check if the request will complete in the time it would have taken + us to enable the interrupt. + + This is adjustable via + /sys/class/drm/card?/engine/*/max_busywait_duration_ns + + May be 0 to disable the initial spin. In practice, we estimate + the cost of enabling the interrupt (if currently disabled) to be + a few microseconds. + +config DRM_I915_STOP_TIMEOUT + int "How long to wait for an engine to quiesce gracefully before reset (ms)" + default 100 # milliseconds + help + By stopping submission and sleeping for a short time before resetting + the GPU, we allow the innocent contexts also on the system to quiesce. + It is then less likely for a hanging context to cause collateral + damage as the system is reset in order to recover. The corollary is + that the reset itself may take longer and so be more disruptive to + interactive or low latency workloads. + + This is adjustable via + /sys/class/drm/card?/engine/*/stop_timeout_ms + +config DRM_I915_TIMESLICE_DURATION + int "Scheduling quantum for userspace batches (ms, jiffy granularity)" + default 1 # milliseconds + help + When two user batches of equal priority are executing, we will + alternate execution of each batch to ensure forward progress of + all users. This is necessary in some cases where there may be + an implicit dependency between those batches that requires + concurrent execution in order for them to proceed, e.g. they + interact with each other via userspace semaphores. Each context + is scheduled for execution for the timeslice duration, before + switching to the next context. + + This is adjustable via + /sys/class/drm/card?/engine/*/timeslice_duration_ms + + May be 0 to disable timeslicing. diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile new file mode 100644 index 000000000..07802a7f4 --- /dev/null +++ b/drivers/gpu/drm/i915/Makefile @@ -0,0 +1,459 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the drm device driver. This driver provides support for the +# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher. + +# Enable W=1 warnings not enabled in drm subsystem Makefile +subdir-ccflags-y += $(call cc-option, -Wformat-truncation) + +# Enable -Werror in CI and development +subdir-ccflags-$(CONFIG_DRM_I915_WERROR) += -Werror + +# Support compiling the display code separately for both i915 and xe +# drivers. Define I915 when building i915. +subdir-ccflags-y += -DI915 + +# FIXME: Disable tracepoints on i915 for PREEMPT_RT, unfortunately +# it's an all or nothing flag. You cannot selectively disable +# only some tracepoints. +subdir-ccflags-$(CONFIG_PREEMPT_RT) += -DNOTRACE + +subdir-ccflags-y += -I$(src) + +# Please keep these build lists sorted! + +# core driver code +i915-y += \ + i915_config.o \ + i915_driver.o \ + i915_drm_client.o \ + i915_edram.o \ + i915_freq.o \ + i915_getparam.o \ + i915_gmch.o \ + i915_ioctl.o \ + i915_irq.o \ + i915_mitigations.o \ + i915_mmio_range.o \ + i915_module.o \ + i915_params.o \ + i915_pci.o \ + i915_scatterlist.o \ + i915_switcheroo.o \ + i915_sysfs.o \ + i915_timer_util.o \ + i915_utils.o \ + intel_clock_gating.o \ + intel_cpu_info.o \ + intel_device_info.o \ + intel_memory_region.o \ + intel_pcode.o \ + intel_region_ttm.o \ + intel_runtime_pm.o \ + intel_step.o \ + intel_uncore.o \ + intel_uncore_trace.o \ + intel_wakeref.o \ + vlv_iosf_sb.o \ + vlv_suspend.o + +# core library code +i915-y += \ + i915_memcpy.o \ + i915_mm.o \ + i915_sw_fence.o \ + i915_sw_fence_work.o \ + i915_syncmap.o \ + i915_user_extensions.o + +i915-$(CONFIG_COMPAT) += \ + i915_ioc32.o +i915-$(CONFIG_DEBUG_FS) += \ + i915_debugfs.o \ + i915_debugfs_params.o +i915-$(CONFIG_PERF_EVENTS) += \ + i915_pmu.o + +# core display adaptation +i915-y += \ + i915_bo.o \ + i915_display_pc8.o \ + i915_dpt.o \ + i915_dsb_buffer.o \ + i915_fb_pin.o \ + i915_hdcp_gsc.o \ + i915_initial_plane.o \ + i915_overlay.o \ + i915_panic.o + +# "Graphics Technology" (aka we talk to the gpu) +gt-y += \ + gt/gen2_engine_cs.o \ + gt/gen6_engine_cs.o \ + gt/gen6_ppgtt.o \ + gt/gen7_renderclear.o \ + gt/gen8_engine_cs.o \ + gt/gen8_ppgtt.o \ + gt/intel_breadcrumbs.o \ + gt/intel_context.o \ + gt/intel_context_sseu.o \ + gt/intel_engine_cs.o \ + gt/intel_engine_heartbeat.o \ + gt/intel_engine_pm.o \ + gt/intel_engine_user.o \ + gt/intel_execlists_submission.o \ + gt/intel_ggtt.o \ + gt/intel_ggtt_fencing.o \ + gt/intel_gt.o \ + gt/intel_gt_buffer_pool.o \ + gt/intel_gt_ccs_mode.o \ + gt/intel_gt_clock_utils.o \ + gt/intel_gt_debugfs.o \ + gt/intel_gt_engines_debugfs.o \ + gt/intel_gt_irq.o \ + gt/intel_gt_mcr.o \ + gt/intel_gt_pm.o \ + gt/intel_gt_pm_debugfs.o \ + gt/intel_gt_pm_irq.o \ + gt/intel_gt_requests.o \ + gt/intel_gt_sysfs.o \ + gt/intel_gt_sysfs_pm.o \ + gt/intel_gtt.o \ + gt/intel_llc.o \ + gt/intel_lrc.o \ + gt/intel_migrate.o \ + gt/intel_mocs.o \ + gt/intel_ppgtt.o \ + gt/intel_rc6.o \ + gt/intel_region_lmem.o \ + gt/intel_renderstate.o \ + gt/intel_reset.o \ + gt/intel_ring.o \ + gt/intel_ring_submission.o \ + gt/intel_rps.o \ + gt/intel_sa_media.o \ + gt/intel_sseu.o \ + gt/intel_sseu_debugfs.o \ + gt/intel_timeline.o \ + gt/intel_tlb.o \ + gt/intel_wopcm.o \ + gt/intel_workarounds.o \ + gt/shmem_utils.o \ + gt/sysfs_engines.o + +# x86 intel-gtt module support +gt-$(CONFIG_X86) += \ + gt/intel_ggtt_gmch.o +# autogenerated null render state +gt-y += \ + gt/gen6_renderstate.o \ + gt/gen7_renderstate.o \ + gt/gen8_renderstate.o \ + gt/gen9_renderstate.o +i915-y += $(gt-y) + +# GEM (Graphics Execution Management) code +gem-y += \ + gem/i915_gem_busy.o \ + gem/i915_gem_clflush.o \ + gem/i915_gem_context.o \ + gem/i915_gem_create.o \ + gem/i915_gem_dmabuf.o \ + gem/i915_gem_domain.o \ + gem/i915_gem_execbuffer.o \ + gem/i915_gem_internal.o \ + gem/i915_gem_lmem.o \ + gem/i915_gem_mman.o \ + gem/i915_gem_object.o \ + gem/i915_gem_object_frontbuffer.o \ + gem/i915_gem_pages.o \ + gem/i915_gem_phys.o \ + gem/i915_gem_pm.o \ + gem/i915_gem_region.o \ + gem/i915_gem_shmem.o \ + gem/i915_gem_shrinker.o \ + gem/i915_gem_stolen.o \ + gem/i915_gem_throttle.o \ + gem/i915_gem_tiling.o \ + gem/i915_gem_ttm.o \ + gem/i915_gem_ttm_move.o \ + gem/i915_gem_ttm_pm.o \ + gem/i915_gem_userptr.o \ + gem/i915_gem_wait.o +i915-y += \ + $(gem-y) \ + i915_active.o \ + i915_cmd_parser.o \ + i915_deps.o \ + i915_gem.o \ + i915_gem_evict.o \ + i915_gem_gtt.o \ + i915_gem_ww.o \ + i915_query.o \ + i915_request.o \ + i915_scheduler.o \ + i915_trace_points.o \ + i915_ttm_buddy_manager.o \ + i915_vma.o \ + i915_vma_resource.o + +# general-purpose microcontroller (GuC) support +i915-y += \ + gt/uc/intel_gsc_fw.o \ + gt/uc/intel_gsc_proxy.o \ + gt/uc/intel_gsc_uc.o \ + gt/uc/intel_gsc_uc_debugfs.o \ + gt/uc/intel_gsc_uc_heci_cmd_submit.o\ + gt/uc/intel_guc.o \ + gt/uc/intel_guc_ads.o \ + gt/uc/intel_guc_capture.o \ + gt/uc/intel_guc_ct.o \ + gt/uc/intel_guc_debugfs.o \ + gt/uc/intel_guc_fw.o \ + gt/uc/intel_guc_hwconfig.o \ + gt/uc/intel_guc_log.o \ + gt/uc/intel_guc_log_debugfs.o \ + gt/uc/intel_guc_rc.o \ + gt/uc/intel_guc_slpc.o \ + gt/uc/intel_guc_submission.o \ + gt/uc/intel_huc.o \ + gt/uc/intel_huc_debugfs.o \ + gt/uc/intel_huc_fw.o \ + gt/uc/intel_uc.o \ + gt/uc/intel_uc_debugfs.o \ + gt/uc/intel_uc_fw.o + +# graphics system controller (GSC) support +i915-y += \ + gt/intel_gsc.o + +# graphics hardware monitoring (HWMON) support +i915-$(CONFIG_HWMON) += \ + i915_hwmon.o + +# modesetting core code +i915-y += \ + display/hsw_ips.o \ + display/i9xx_display_sr.o \ + display/i9xx_plane.o \ + display/i9xx_wm.o \ + display/intel_alpm.o \ + display/intel_atomic.o \ + display/intel_audio.o \ + display/intel_bios.o \ + display/intel_bo.o \ + display/intel_bw.o \ + display/intel_casf.o \ + display/intel_cdclk.o \ + display/intel_cmtg.o \ + display/intel_color.o \ + display/intel_colorop.o \ + display/intel_color_pipeline.o \ + display/intel_combo_phy.o \ + display/intel_connector.o \ + display/intel_crtc.o \ + display/intel_crtc_state_dump.o \ + display/intel_cursor.o \ + display/intel_dbuf_bw.o \ + display/intel_de.o \ + display/intel_display_clock_gating.o \ + display/intel_display.o \ + display/intel_display_conversion.o \ + display/intel_display_driver.o \ + display/intel_display_irq.o \ + display/intel_display_params.o \ + display/intel_display_power.o \ + display/intel_display_power_map.o \ + display/intel_display_power_well.o \ + display/intel_display_reset.o \ + display/intel_display_rpm.o \ + display/intel_display_rps.o \ + display/intel_display_snapshot.o \ + display/intel_display_utils.o \ + display/intel_display_wa.o \ + display/intel_dmc.o \ + display/intel_dmc_wl.o \ + display/intel_dpio_phy.o \ + display/intel_dpll.o \ + display/intel_dpll_mgr.o \ + display/intel_dpt.o \ + display/intel_dram.o \ + display/intel_drrs.o \ + display/intel_dsb.o \ + display/intel_fb.o \ + display/intel_fbc.o \ + display/intel_fdi.o \ + display/intel_fifo_underrun.o \ + display/intel_flipq.o \ + display/intel_frontbuffer.o \ + display/intel_global_state.o \ + display/intel_hdcp.o \ + display/intel_hdcp_gsc_message.o \ + display/intel_hotplug.o \ + display/intel_hotplug_irq.o \ + display/intel_hti.o \ + display/intel_initial_plane.o \ + display/intel_link_bw.o \ + display/intel_load_detect.o \ + display/intel_lpe_audio.o \ + display/intel_mchbar.o \ + display/intel_modeset_lock.o \ + display/intel_modeset_setup.o \ + display/intel_modeset_verify.o \ + display/intel_overlay.o \ + display/intel_parent.o \ + display/intel_pch.o \ + display/intel_pch_display.o \ + display/intel_pch_refclk.o \ + display/intel_plane.o \ + display/intel_pmdemand.o \ + display/intel_psr.o \ + display/intel_quirks.o \ + display/intel_rom.o \ + display/intel_sbi.o \ + display/intel_sprite.o \ + display/intel_sprite_uapi.o \ + display/intel_tc.o \ + display/intel_vblank.o \ + display/intel_vga.o \ + display/intel_wm.o \ + display/skl_prefill.o \ + display/skl_scaler.o \ + display/skl_universal_plane.o \ + display/skl_watermark.o \ + display/vlv_clock.o \ + display/vlv_sideband.o +i915-$(CONFIG_ACPI) += \ + display/intel_acpi.o \ + display/intel_opregion.o +i915-$(CONFIG_DRM_FBDEV_EMULATION) += \ + display/intel_fbdev.o +i915-$(CONFIG_DEBUG_FS) += \ + display/intel_display_debugfs.o \ + display/intel_display_debugfs_params.o \ + display/intel_pipe_crc.o + +# modesetting output/encoder code +i915-y += \ + display/dvo_ch7017.o \ + display/dvo_ch7xxx.o \ + display/dvo_ivch.o \ + display/dvo_ns2501.o \ + display/dvo_sil164.o \ + display/dvo_tfp410.o \ + display/g4x_dp.o \ + display/g4x_hdmi.o \ + display/icl_dsi.o \ + display/intel_backlight.o \ + display/intel_crt.o \ + display/intel_cx0_phy.o \ + display/intel_ddi.o \ + display/intel_ddi_buf_trans.o \ + display/intel_display_device.o \ + display/intel_display_trace.o \ + display/intel_dkl_phy.o \ + display/intel_dp.o \ + display/intel_dp_aux.o \ + display/intel_dp_aux_backlight.o \ + display/intel_dp_hdcp.o \ + display/intel_dp_link_training.o \ + display/intel_dp_mst.o \ + display/intel_dp_test.o \ + display/intel_dsi.o \ + display/intel_dsi_dcs_backlight.o \ + display/intel_dsi_vbt.o \ + display/intel_dvo.o \ + display/intel_encoder.o \ + display/intel_gmbus.o \ + display/intel_hdmi.o \ + display/intel_lspcon.o \ + display/intel_lt_phy.o \ + display/intel_lvds.o \ + display/intel_panel.o \ + display/intel_pfit.o \ + display/intel_pps.o \ + display/intel_qp_tables.o \ + display/intel_sdvo.o \ + display/intel_snps_hdmi_pll.o \ + display/intel_snps_phy.o \ + display/intel_tv.o \ + display/intel_vdsc.o \ + display/intel_vrr.o \ + display/vlv_dsi.o \ + display/vlv_dsi_pll.o + +i915-$(CONFIG_DRM_I915_DP_TUNNEL) += \ + display/intel_dp_tunnel.o + +i915-$(CONFIG_DRM_I915_GVT) += \ + display/intel_gvt_api.o + +i915-y += \ + i915_perf.o + +# Protected execution platform (PXP) support. Base support is required for HuC +i915-y += \ + pxp/intel_pxp.o \ + pxp/intel_pxp_huc.o \ + pxp/intel_pxp_tee.o + +i915-$(CONFIG_DRM_I915_PXP) += \ + pxp/intel_pxp_cmd.o \ + pxp/intel_pxp_debugfs.o \ + pxp/intel_pxp_gsccs.o \ + pxp/intel_pxp_irq.o \ + pxp/intel_pxp_pm.o \ + pxp/intel_pxp_session.o + +# Post-mortem debug and GPU hang state capture +i915-$(CONFIG_DRM_I915_CAPTURE_ERROR) += \ + i915_gpu_error.o +i915-$(CONFIG_DRM_I915_SELFTEST) += \ + gem/selftests/i915_gem_client_blt.o \ + gem/selftests/igt_gem_utils.o \ + selftests/i915_random.o \ + selftests/i915_selftest.o \ + selftests/igt_atomic.o \ + selftests/igt_flush_test.o \ + selftests/igt_live_test.o \ + selftests/igt_mmap.o \ + selftests/igt_reset.o \ + selftests/igt_spinner.o \ + selftests/intel_scheduler_helpers.o \ + selftests/librapl.o + +# virtual gpu code +i915-y += \ + i915_vgpu.o + +i915-$(CONFIG_DRM_I915_GVT) += \ + intel_gvt.o \ + intel_gvt_mmio_table.o +include $(src)/gvt/Makefile + +obj-$(CONFIG_DRM_I915) += i915.o +obj-$(CONFIG_DRM_I915_GVT_KVMGT) += kvmgt.o + +# kernel-doc test +# +# Enable locally for CONFIG_DRM_I915_WERROR=y. See also scripts/Makefile.build +ifdef CONFIG_DRM_I915_WERROR + cmd_checkdoc = PYTHONDONTWRITEBYTECODE=1 $(PYTHON3) $(KERNELDOC) -none -Werror $< +endif + +# header test + +# exclude some broken headers from the test coverage +no-header-test := \ + display/intel_vbt_defs.h + +always-$(CONFIG_DRM_I915_WERROR) += \ + $(patsubst %.h,%.hdrtest, $(filter-out $(no-header-test), \ + $(shell cd $(src) && find * -name '*.h'))) + +quiet_cmd_hdrtest = HDRTEST $(patsubst %.hdrtest,%.h,$@) + cmd_hdrtest = $(CC) $(filter-out $(CFLAGS_GCOV), $(c_flags)) -S -o /dev/null -x c /dev/null -include $<; \ + $(KERNELDOC) -none -Werror $<; touch $@ + +$(obj)/%.hdrtest: $(src)/%.h FORCE + $(call if_changed_dep,hdrtest) diff --git a/drivers/gpu/drm/i915/TODO.txt b/drivers/gpu/drm/i915/TODO.txt new file mode 100644 index 000000000..879b08ca3 --- /dev/null +++ b/drivers/gpu/drm/i915/TODO.txt @@ -0,0 +1,41 @@ +gem/gt TODO items +----------------- + +- For discrete memory manager, merge enough dg1 to be able to refactor it to + TTM. Then land pci ids (just in case that turns up an uapi problem). TTM has + improved a lot the past 2 years, there's no reason anymore not to use it. + +- Come up with a plan what to do with drm/scheduler and how to get there. + +- Roll out dma_fence critical section annotations. + +- There's a lot of complexity added past few years to make relocations faster. + That doesn't make sense given hw and gpu apis moved away from this model years + ago: + 1. Land a modern pre-bound uapi like VM_BIND + 2. Any complexity added in this area past few years which can't be justified + with VM_BIND using userspace should be removed. Looking at amdgpu dma_resv on + the bo and vm, plus some lru locks is all that needed. No complex rcu, + refcounts, caching, ... on everything. + This is the matching task on the vm side compared to ttm/dma_resv on the + backing storage side. + +- i915_sw_fence seems to be the main structure for the i915-gem dma_fence model. + How-to-dma_fence is core and drivers really shouldn't build their own world + here, treating everything else as a fixed platform. i915_sw_fence concepts + should be moved to dma_fence, drm/scheduler or atomic commit helpers. Or + removed if dri-devel consensus is that it's not a good idea. Once that's done + maybe even remove it if there's nothing left. + +Smaller things: +- i915_utils.h needs to be moved to the right places. + +- dma_fence_work should be in drivers/dma-buf + +- i915_mm.c should be moved to the right places. Some of the helpers also look a + bit fishy: + + https://lore.kernel.org/linux-mm/20210301083320.943079-1-hch@lst.de/ + +- tasklet helpers in i915_tasklet.h also look a bit misplaced and should + probably be moved to tasklet headers. diff --git a/drivers/gpu/drm/i915/display/bxt_dpio_phy_regs.h b/drivers/gpu/drm/i915/display/bxt_dpio_phy_regs.h new file mode 100644 index 000000000..275f4d9c3 --- /dev/null +++ b/drivers/gpu/drm/i915/display/bxt_dpio_phy_regs.h @@ -0,0 +1,273 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2023 Intel Corporation + */ + +#ifndef __BXT_DPIO_PHY_REGS_H__ +#define __BXT_DPIO_PHY_REGS_H__ + +#include "intel_display_reg_defs.h" + +/* BXT PHY registers */ +#define _BXT_PHY0_BASE 0x6C000 +#define _BXT_PHY1_BASE 0x162000 +#define _BXT_PHY2_BASE 0x163000 +#define BXT_PHY_BASE(phy) \ + _PICK_EVEN_2RANGES(phy, 1, \ + _BXT_PHY0_BASE, _BXT_PHY0_BASE, \ + _BXT_PHY1_BASE, _BXT_PHY2_BASE) + +#define _BXT_PHY(phy, reg) \ + _MMIO(BXT_PHY_BASE(phy) - _BXT_PHY0_BASE + (reg)) + +#define _BXT_PHY_CH(phy, ch, reg_ch0, reg_ch1) \ + (BXT_PHY_BASE(phy) + _PIPE((ch), (reg_ch0) - _BXT_PHY0_BASE, \ + (reg_ch1) - _BXT_PHY0_BASE)) +#define _MMIO_BXT_PHY_CH(phy, ch, reg_ch0, reg_ch1) \ + _MMIO(_BXT_PHY_CH(phy, ch, reg_ch0, reg_ch1)) +#define _BXT_LANE_OFFSET(lane) (((lane) >> 1) * 0x200 + \ + ((lane) & 1) * 0x80) +#define _MMIO_BXT_PHY_CH_LN(phy, ch, lane, reg_ch0, reg_ch1) \ + _MMIO(_BXT_PHY_CH(phy, ch, reg_ch0, reg_ch1) + _BXT_LANE_OFFSET(lane)) + +/* BXT PHY PLL registers */ +#define _PORT_PLL_A 0x46074 +#define _PORT_PLL_B 0x46078 +#define _PORT_PLL_C 0x4607c +#define PORT_PLL_ENABLE REG_BIT(31) +#define PORT_PLL_LOCK REG_BIT(30) +#define PORT_PLL_REF_SEL REG_BIT(27) +#define PORT_PLL_POWER_ENABLE REG_BIT(26) +#define PORT_PLL_POWER_STATE REG_BIT(25) +#define BXT_PORT_PLL_ENABLE(port) _MMIO_PORT(port, _PORT_PLL_A, _PORT_PLL_B) + +#define _PORT_PLL_EBB_0_A 0x162034 +#define _PORT_PLL_EBB_0_B 0x6C034 +#define _PORT_PLL_EBB_0_C 0x6C340 +#define PORT_PLL_P1_MASK REG_GENMASK(15, 13) +#define PORT_PLL_P1(p1) REG_FIELD_PREP(PORT_PLL_P1_MASK, (p1)) +#define PORT_PLL_P2_MASK REG_GENMASK(12, 8) +#define PORT_PLL_P2(p2) REG_FIELD_PREP(PORT_PLL_P2_MASK, (p2)) +#define BXT_PORT_PLL_EBB_0(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ + _PORT_PLL_EBB_0_B, \ + _PORT_PLL_EBB_0_C) + +#define _PORT_PLL_EBB_4_A 0x162038 +#define _PORT_PLL_EBB_4_B 0x6C038 +#define _PORT_PLL_EBB_4_C 0x6C344 +#define PORT_PLL_RECALIBRATE REG_BIT(14) +#define PORT_PLL_10BIT_CLK_ENABLE REG_BIT(13) +#define BXT_PORT_PLL_EBB_4(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ + _PORT_PLL_EBB_4_B, \ + _PORT_PLL_EBB_4_C) + +#define _PORT_PLL_0_A 0x162100 +#define _PORT_PLL_0_B 0x6C100 +#define _PORT_PLL_0_C 0x6C380 +/* PORT_PLL_0_A */ +#define PORT_PLL_M2_INT_MASK REG_GENMASK(7, 0) +#define PORT_PLL_M2_INT(m2_int) REG_FIELD_PREP(PORT_PLL_M2_INT_MASK, (m2_int)) +/* PORT_PLL_1_A */ +#define PORT_PLL_N_MASK REG_GENMASK(11, 8) +#define PORT_PLL_N(n) REG_FIELD_PREP(PORT_PLL_N_MASK, (n)) +/* PORT_PLL_2_A */ +#define PORT_PLL_M2_FRAC_MASK REG_GENMASK(21, 0) +#define PORT_PLL_M2_FRAC(m2_frac) REG_FIELD_PREP(PORT_PLL_M2_FRAC_MASK, (m2_frac)) +/* PORT_PLL_3_A */ +#define PORT_PLL_M2_FRAC_ENABLE REG_BIT(16) +/* PORT_PLL_6_A */ +#define PORT_PLL_GAIN_CTL_MASK REG_GENMASK(18, 16) +#define PORT_PLL_GAIN_CTL(x) REG_FIELD_PREP(PORT_PLL_GAIN_CTL_MASK, (x)) +#define PORT_PLL_INT_COEFF_MASK REG_GENMASK(12, 8) +#define PORT_PLL_INT_COEFF(x) REG_FIELD_PREP(PORT_PLL_INT_COEFF_MASK, (x)) +#define PORT_PLL_PROP_COEFF_MASK REG_GENMASK(3, 0) +#define PORT_PLL_PROP_COEFF(x) REG_FIELD_PREP(PORT_PLL_PROP_COEFF_MASK, (x)) +/* PORT_PLL_8_A */ +#define PORT_PLL_TARGET_CNT_MASK REG_GENMASK(9, 0) +#define PORT_PLL_TARGET_CNT(x) REG_FIELD_PREP(PORT_PLL_TARGET_CNT_MASK, (x)) +/* PORT_PLL_9_A */ +#define PORT_PLL_LOCK_THRESHOLD_MASK REG_GENMASK(3, 1) +#define PORT_PLL_LOCK_THRESHOLD(x) REG_FIELD_PREP(PORT_PLL_LOCK_THRESHOLD_MASK, (x)) +/* PORT_PLL_10_A */ +#define PORT_PLL_DCO_AMP_OVR_EN_H REG_BIT(27) +#define PORT_PLL_DCO_AMP_MASK REG_GENMASK(13, 10) +#define PORT_PLL_DCO_AMP(x) REG_FIELD_PREP(PORT_PLL_DCO_AMP_MASK, (x)) +#define _PORT_PLL_BASE(phy, ch) _BXT_PHY_CH(phy, ch, \ + _PORT_PLL_0_B, \ + _PORT_PLL_0_C) +#define BXT_PORT_PLL(phy, ch, idx) _MMIO(_PORT_PLL_BASE(phy, ch) + \ + (idx) * 4) + +/* BXT PHY common lane registers */ +#define _PORT_CL1CM_DW0_A 0x162000 +#define _PORT_CL1CM_DW0_BC 0x6C000 +#define PHY_POWER_GOOD REG_BIT(16) +#define PHY_RESERVED REG_BIT(7) +#define BXT_PORT_CL1CM_DW0(phy) _BXT_PHY((phy), _PORT_CL1CM_DW0_BC) + +#define _PORT_CL1CM_DW9_A 0x162024 +#define _PORT_CL1CM_DW9_BC 0x6C024 +#define IREF0RC_OFFSET_MASK REG_GENMASK(15, 8) +#define IREF0RC_OFFSET(x) REG_FIELD_PREP(IREF0RC_OFFSET_MASK, (x)) +#define BXT_PORT_CL1CM_DW9(phy) _BXT_PHY((phy), _PORT_CL1CM_DW9_BC) + +#define _PORT_CL1CM_DW10_A 0x162028 +#define _PORT_CL1CM_DW10_BC 0x6C028 +#define IREF1RC_OFFSET_MASK REG_GENMASK(15, 8) +#define IREF1RC_OFFSET(x) REG_FIELD_PREP(IREF1RC_OFFSET_MASK, (x)) +#define BXT_PORT_CL1CM_DW10(phy) _BXT_PHY((phy), _PORT_CL1CM_DW10_BC) + +#define _PORT_CL1CM_DW28_A 0x162070 +#define _PORT_CL1CM_DW28_BC 0x6C070 +#define OCL1_POWER_DOWN_EN REG_BIT(23) +#define DW28_OLDO_DYN_PWR_DOWN_EN REG_BIT(22) +#define SUS_CLK_CONFIG REG_GENMASK(1, 0) +#define BXT_PORT_CL1CM_DW28(phy) _BXT_PHY((phy), _PORT_CL1CM_DW28_BC) + +#define _PORT_CL1CM_DW30_A 0x162078 +#define _PORT_CL1CM_DW30_BC 0x6C078 +#define OCL2_LDOFUSE_PWR_DIS REG_BIT(6) +#define BXT_PORT_CL1CM_DW30(phy) _BXT_PHY((phy), _PORT_CL1CM_DW30_BC) + +/* The spec defines this only for BXT PHY0, but lets assume that this + * would exist for PHY1 too if it had a second channel. + */ +#define _PORT_CL2CM_DW6_A 0x162358 +#define _PORT_CL2CM_DW6_BC 0x6C358 +#define BXT_PORT_CL2CM_DW6(phy) _BXT_PHY((phy), _PORT_CL2CM_DW6_BC) +#define DW6_OLDO_DYN_PWR_DOWN_EN REG_BIT(28) + +/* BXT PHY Ref registers */ +#define _PORT_REF_DW3_A 0x16218C +#define _PORT_REF_DW3_BC 0x6C18C +#define GRC_DONE REG_BIT(22) +#define BXT_PORT_REF_DW3(phy) _BXT_PHY((phy), _PORT_REF_DW3_BC) + +#define _PORT_REF_DW6_A 0x162198 +#define _PORT_REF_DW6_BC 0x6C198 +#define GRC_CODE_MASK REG_GENMASK(31, 24) +#define GRC_CODE(x) REG_FIELD_PREP(GRC_CODE_MASK, (x)) +#define GRC_CODE_FAST_MASK REG_GENMASK(23, 16) +#define GRC_CODE_FAST(x) REG_FIELD_PREP(GRC_CODE_FAST_MASK, (x)) +#define GRC_CODE_SLOW_MASK REG_GENMASK(15, 8) +#define GRC_CODE_SLOW(x) REG_FIELD_PREP(GRC_CODE_SLOW_MASK, (x)) +#define GRC_CODE_NOM_MASK REG_GENMASK(7, 0) +#define GRC_CODE_NOM(x) REG_FIELD_PREP(GRC_CODE_NOM_MASK, (x)) +#define BXT_PORT_REF_DW6(phy) _BXT_PHY((phy), _PORT_REF_DW6_BC) + +#define _PORT_REF_DW8_A 0x1621A0 +#define _PORT_REF_DW8_BC 0x6C1A0 +#define GRC_DIS REG_BIT(15) +#define GRC_RDY_OVRD REG_BIT(1) +#define BXT_PORT_REF_DW8(phy) _BXT_PHY((phy), _PORT_REF_DW8_BC) + +/* BXT PHY PCS registers */ +#define _PORT_PCS_DW10_LN01_A 0x162428 +#define _PORT_PCS_DW10_LN01_B 0x6C428 +#define _PORT_PCS_DW10_LN01_C 0x6C828 +#define _PORT_PCS_DW10_GRP_A 0x162C28 +#define _PORT_PCS_DW10_GRP_B 0x6CC28 +#define _PORT_PCS_DW10_GRP_C 0x6CE28 +#define BXT_PORT_PCS_DW10_LN01(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ + _PORT_PCS_DW10_LN01_B, \ + _PORT_PCS_DW10_LN01_C) +#define BXT_PORT_PCS_DW10_GRP(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ + _PORT_PCS_DW10_GRP_B, \ + _PORT_PCS_DW10_GRP_C) + +#define TX2_SWING_CALC_INIT REG_BIT(31) +#define TX1_SWING_CALC_INIT REG_BIT(30) + +#define _PORT_PCS_DW12_LN01_A 0x162430 +#define _PORT_PCS_DW12_LN01_B 0x6C430 +#define _PORT_PCS_DW12_LN01_C 0x6C830 +#define _PORT_PCS_DW12_LN23_A 0x162630 +#define _PORT_PCS_DW12_LN23_B 0x6C630 +#define _PORT_PCS_DW12_LN23_C 0x6CA30 +#define _PORT_PCS_DW12_GRP_A 0x162c30 +#define _PORT_PCS_DW12_GRP_B 0x6CC30 +#define _PORT_PCS_DW12_GRP_C 0x6CE30 +#define LANESTAGGER_STRAP_OVRD REG_BIT(6) +#define LANE_STAGGER_MASK REG_GENMASK(4, 0) +#define BXT_PORT_PCS_DW12_LN01(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ + _PORT_PCS_DW12_LN01_B, \ + _PORT_PCS_DW12_LN01_C) +#define BXT_PORT_PCS_DW12_LN23(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ + _PORT_PCS_DW12_LN23_B, \ + _PORT_PCS_DW12_LN23_C) +#define BXT_PORT_PCS_DW12_GRP(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ + _PORT_PCS_DW12_GRP_B, \ + _PORT_PCS_DW12_GRP_C) + +/* BXT PHY TX registers */ +#define _PORT_TX_DW2_LN0_A 0x162508 +#define _PORT_TX_DW2_LN0_B 0x6C508 +#define _PORT_TX_DW2_LN0_C 0x6C908 +#define _PORT_TX_DW2_GRP_A 0x162D08 +#define _PORT_TX_DW2_GRP_B 0x6CD08 +#define _PORT_TX_DW2_GRP_C 0x6CF08 +#define BXT_PORT_TX_DW2_LN(phy, ch, lane) _MMIO_BXT_PHY_CH_LN(phy, ch, lane, \ + _PORT_TX_DW2_LN0_B, \ + _PORT_TX_DW2_LN0_C) +#define BXT_PORT_TX_DW2_GRP(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ + _PORT_TX_DW2_GRP_B, \ + _PORT_TX_DW2_GRP_C) +#define MARGIN_000_MASK REG_GENMASK(23, 16) +#define MARGIN_000(x) REG_FIELD_PREP(MARGIN_000_MASK, (x)) +#define UNIQ_TRANS_SCALE_MASK REG_GENMASK(15, 8) +#define UNIQ_TRANS_SCALE(x) REG_FIELD_PREP(UNIQ_TRANS_SCALE_MASK, (x)) + +#define _PORT_TX_DW3_LN0_A 0x16250C +#define _PORT_TX_DW3_LN0_B 0x6C50C +#define _PORT_TX_DW3_LN0_C 0x6C90C +#define _PORT_TX_DW3_GRP_A 0x162D0C +#define _PORT_TX_DW3_GRP_B 0x6CD0C +#define _PORT_TX_DW3_GRP_C 0x6CF0C +#define BXT_PORT_TX_DW3_LN(phy, ch, lane) _MMIO_BXT_PHY_CH_LN(phy, ch, lane, \ + _PORT_TX_DW3_LN0_B, \ + _PORT_TX_DW3_LN0_C) +#define BXT_PORT_TX_DW3_GRP(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ + _PORT_TX_DW3_GRP_B, \ + _PORT_TX_DW3_GRP_C) +#define SCALE_DCOMP_METHOD REG_BIT(26) +#define UNIQUE_TRANGE_EN_METHOD REG_BIT(27) + +#define _PORT_TX_DW4_LN0_A 0x162510 +#define _PORT_TX_DW4_LN0_B 0x6C510 +#define _PORT_TX_DW4_LN0_C 0x6C910 +#define _PORT_TX_DW4_GRP_A 0x162D10 +#define _PORT_TX_DW4_GRP_B 0x6CD10 +#define _PORT_TX_DW4_GRP_C 0x6CF10 +#define BXT_PORT_TX_DW4_LN(phy, ch, lane) _MMIO_BXT_PHY_CH_LN(phy, ch, lane, \ + _PORT_TX_DW4_LN0_B, \ + _PORT_TX_DW4_LN0_C) +#define BXT_PORT_TX_DW4_GRP(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ + _PORT_TX_DW4_GRP_B, \ + _PORT_TX_DW4_GRP_C) +#define DE_EMPHASIS_MASK REG_GENMASK(31, 24) +#define DE_EMPHASIS(x) REG_FIELD_PREP(DE_EMPHASIS_MASK, (x)) + +#define _PORT_TX_DW5_LN0_A 0x162514 +#define _PORT_TX_DW5_LN0_B 0x6C514 +#define _PORT_TX_DW5_LN0_C 0x6C914 +#define _PORT_TX_DW5_GRP_A 0x162D14 +#define _PORT_TX_DW5_GRP_B 0x6CD14 +#define _PORT_TX_DW5_GRP_C 0x6CF14 +#define BXT_PORT_TX_DW5_LN(phy, ch, lane) _MMIO_BXT_PHY_CH_LN(phy, ch, lane, \ + _PORT_TX_DW5_LN0_B, \ + _PORT_TX_DW5_LN0_C) +#define BXT_PORT_TX_DW5_GRP(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ + _PORT_TX_DW5_GRP_B, \ + _PORT_TX_DW5_GRP_C) +#define DCC_DELAY_RANGE_1 REG_BIT(9) +#define DCC_DELAY_RANGE_2 REG_BIT(8) + +#define _PORT_TX_DW14_LN0_A 0x162538 +#define _PORT_TX_DW14_LN0_B 0x6C538 +#define _PORT_TX_DW14_LN0_C 0x6C938 +#define LATENCY_OPTIM REG_BIT(30) +#define BXT_PORT_TX_DW14_LN(phy, ch, lane) _MMIO_BXT_PHY_CH_LN(phy, ch, lane, \ + _PORT_TX_DW14_LN0_B, \ + _PORT_TX_DW14_LN0_C) + +#endif /* __BXT_DPIO_PHY_REGS_H__ */ 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..326b08e7a --- /dev/null +++ b/drivers/gpu/drm/i915/display/dvo_ch7017.c @@ -0,0 +1,417 @@ +/* + * 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 "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->target_addr, + .flags = 0, + .len = 1, + .buf = &addr, + }, + { + .addr = dvo->target_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->target_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 target 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_obj(*priv); + 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 " + "target %d.\n", + val, adapter->name, dvo->target_addr); + goto fail; + } + + DRM_DEBUG_KMS("%s detected on %s, addr %d\n", + str, adapter->name, dvo->target_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, + const 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..1382fe195 --- /dev/null +++ b/drivers/gpu/drm/i915/display/dvo_ch7xxx.c @@ -0,0 +1,387 @@ +/************************************************************************** + +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 + +#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_IBS (1<<7) +#define CH7xxx_IDF_DES (1<<6) +#define CH7xxx_IDF_HSP (1<<3) +#define CH7xxx_IDF_VSP (1<<4) + +#define CH7xxx_CONNECTION_DETECT 0x20 +#define CH7xxx_CDET_DVI (1<<5) + +#define CH7xxx_DAC_CNTL 0x21 +#define CH7xxx_SYNCO_MASK (3 << 3) +#define CH7xxx_SYNCO_VGA_HSYNC (1 << 3) + +#define CH7xxx_CLOCK_OUTPUT 0x22 +#define CH7xxx_BCOEN (1 << 4) +#define CH7xxx_BCOP (1 << 3) +#define CH7xxx_BCO_MASK (7 << 0) +#define CH7xxx_BCO_VGA_VSYNC (6 << 0) + +#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->target_addr, + .flags = 0, + .len = 1, + .buf = out_buf, + }, + { + .addr = dvo->target_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->target_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->target_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->target_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_obj(*ch7xxx); + 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 target %d.\n", + vendor, adapter->name, dvo->target_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 target %d.\n", + device, adapter->name, dvo->target_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, + const 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_IBS; + + 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); + + ch7xxx_writeb(dvo, CH7xxx_DAC_CNTL, + CH7xxx_SYNCO_VGA_HSYNC); + ch7xxx_writeb(dvo, CH7xxx_CLOCK_OUTPUT, + CH7xxx_BCOEN | CH7xxx_BCO_VGA_VSYNC); +} + +/* 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..0970dc6fd --- /dev/null +++ b/drivers/gpu/drm/i915/display/dvo_ivch.c @@ -0,0 +1,505 @@ +/* + * 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 + +#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->target_addr, + .flags = I2C_M_RD, + .len = 0, + }, + { + .addr = 0, + .flags = I2C_M_NOSTART, + .len = 1, + .buf = out_buf, + }, + { + .addr = dvo->target_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->target_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->target_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->target_addr); + } + + return false; +} + +/* Probes the given bus and target 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_obj(*priv); + 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->target_addr) { + DRM_DEBUG_KMS("ivch detect failed due to address mismatch " + "(%d vs %d)\n", + (temp & VR00_BASE_ADDRESS_MASK), dvo->target_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, + const 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..6a6072b78 --- /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 + +#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->target_addr, + .flags = 0, + .len = 1, + .buf = out_buf, + }, + { + .addr = dvo->target_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->target_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->target_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->target_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_obj(*ns); + 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 Target %d.\n", + ch, adapter->name, dvo->target_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 Target %d.\n", + ch, adapter->name, dvo->target_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 chip. + */ + return connector_status_connected; +} + +static enum drm_mode_status ns2501_mode_valid(struct intel_dvo_device *dvo, + const 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 = 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 = 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..c6989a4ba --- /dev/null +++ b/drivers/gpu/drm/i915/display/dvo_sil164.c @@ -0,0 +1,291 @@ +/************************************************************************** + +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 + +#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 +#define SIL164_C_SCNT (1<<7) +#define SIL164_C_PLLF_MASK (0xf<<1) +#define SIL164_C_PLLF_REC (4<<1) +#define SIL164_C_PFEN (1<<0) + +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->target_addr, + .flags = 0, + .len = 1, + .buf = out_buf, + }, + { + .addr = dvo->target_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->target_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->target_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->target_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_obj(*sil); + 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 Target %d.\n", + ch, adapter->name, dvo->target_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 Target %d.\n", + ch, adapter->name, dvo->target_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, + const 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 */ + + sil164_writeb(dvo, SIL164_REG8, + SIL164_8_VEN | SIL164_8_HEN); + sil164_writeb(dvo, SIL164_REG9, + SIL164_9_TSEL); + sil164_writeb(dvo, SIL164_REGC, + SIL164_C_PLLF_REC | SIL164_C_PFEN); +} + +/* 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); +} + +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..10d8dd4f4 --- /dev/null +++ b/drivers/gpu/drm/i915/display/dvo_tfp410.c @@ -0,0 +1,321 @@ +/* + * 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 + +#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->target_addr, + .flags = 0, + .len = 1, + .buf = out_buf, + }, + { + .addr = dvo->target_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->target_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->target_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->target_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_obj(*tfp); + 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 " + "Target %d.\n", + id, adapter->name, dvo->target_addr); + goto out; + } + + if ((id = tfp410_getid(dvo, TFP410_DID_LO)) != TFP410_DID) { + DRM_DEBUG_KMS("tfp410 not detected got DID %X: from %s " + "Target %d.\n", + id, adapter->name, dvo->target_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, + const 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..5ff1cdf45 --- /dev/null +++ b/drivers/gpu/drm/i915/display/g4x_dp.c @@ -0,0 +1,1428 @@ +// 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 + +#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_regs.h" +#include "intel_display_types.h" +#include "intel_display_utils.h" +#include "intel_dp.h" +#include "intel_dp_aux.h" +#include "intel_dp_link_training.h" +#include "intel_dp_test.h" +#include "intel_dpio_phy.h" +#include "intel_encoder.h" +#include "intel_fifo_underrun.h" +#include "intel_hdmi.h" +#include "intel_hotplug.h" +#include "intel_pch_display.h" +#include "intel_pps.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 intel_display *display) +{ + return display->platform.cherryview ? &chv_dpll[0] : &vlv_dpll[0]; +} + +static void g4x_dp_set_clock(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config) +{ + struct intel_display *display = to_intel_display(encoder); + const struct dpll *divisor = NULL; + int i, count = 0; + + if (display->platform.g4x) { + divisor = g4x_dpll; + count = ARRAY_SIZE(g4x_dpll); + } else if (HAS_PCH_SPLIT(display)) { + divisor = pch_dpll; + count = ARRAY_SIZE(pch_dpll); + } else if (display->platform.cherryview) { + divisor = chv_dpll; + count = ARRAY_SIZE(chv_dpll); + } else if (display->platform.valleyview) { + 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 intel_display *display = to_intel_display(encoder); + 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(display, 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 (display->platform.ivybridge && 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 (pipe_config->enhanced_framing) + intel_dp->DP |= DP_ENHANCED_FRAMING; + + intel_dp->DP |= DP_PIPE_SEL_IVB(crtc->pipe); + } else if (HAS_PCH_CPT(display) && port != PORT_A) { + intel_dp->DP |= DP_LINK_TRAIN_OFF_CPT; + + intel_de_rmw(display, TRANS_DP_CTL(crtc->pipe), + TRANS_DP_ENH_FRAMING, + pipe_config->enhanced_framing ? + TRANS_DP_ENH_FRAMING : 0); + } else { + if (display->platform.g4x && 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 (pipe_config->enhanced_framing) + intel_dp->DP |= DP_ENHANCED_FRAMING; + + if (display->platform.cherryview) + 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_display *display = to_intel_display(intel_dp); + struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); + bool cur_state = intel_de_read(display, intel_dp->output_reg) & DP_PORT_EN; + + INTEL_DISPLAY_STATE_WARN(display, 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 intel_display *display, bool state) +{ + bool cur_state = intel_de_read(display, DP_A) & EDP_PLL_ENABLE; + + INTEL_DISPLAY_STATE_WARN(display, 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_display *display = to_intel_display(intel_dp); + struct intel_crtc *crtc = to_intel_crtc(pipe_config->uapi.crtc); + + assert_transcoder_disabled(display, pipe_config->cpu_transcoder); + assert_dp_port_disabled(intel_dp); + assert_edp_pll_disabled(display); + + drm_dbg_kms(display->drm, "enabling eDP PLL for clock %d\n", + pipe_config->port_clock); + + intel_dp->DP &= ~EDP_PLL_FREQ_MASK; + + if (pipe_config->port_clock == 162000) + intel_dp->DP |= EDP_PLL_FREQ_162MHZ; + else + intel_dp->DP |= EDP_PLL_FREQ_270MHZ; + + intel_de_write(display, DP_A, intel_dp->DP); + intel_de_posting_read(display, 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 (display->platform.ironlake) + intel_wait_for_vblank_if_active(display, !crtc->pipe); + + intel_dp->DP |= EDP_PLL_ENABLE; + + intel_de_write(display, DP_A, intel_dp->DP); + intel_de_posting_read(display, 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_display *display = to_intel_display(intel_dp); + + assert_transcoder_disabled(display, old_crtc_state->cpu_transcoder); + assert_dp_port_disabled(intel_dp); + assert_edp_pll_enabled(display); + + drm_dbg_kms(display->drm, "disabling eDP PLL\n"); + + intel_dp->DP &= ~EDP_PLL_ENABLE; + + intel_de_write(display, DP_A, intel_dp->DP); + intel_de_posting_read(display, DP_A); + udelay(200); +} + +static bool cpt_dp_port_selected(struct intel_display *display, + enum port port, enum pipe *pipe) +{ + enum pipe p; + + for_each_pipe(display, p) { + u32 val = intel_de_read(display, TRANS_DP_CTL(p)); + + if ((val & TRANS_DP_PORT_SEL_MASK) == TRANS_DP_PORT_SEL(port)) { + *pipe = p; + return true; + } + } + + drm_dbg_kms(display->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 intel_display *display, + intel_reg_t dp_reg, enum port port, + enum pipe *pipe) +{ + bool ret; + u32 val; + + val = intel_de_read(display, dp_reg); + + ret = val & DP_PORT_EN; + + /* asserts want to know the pipe even if the port is disabled */ + if (display->platform.ivybridge && port == PORT_A) + *pipe = REG_FIELD_GET(DP_PIPE_SEL_MASK_IVB, val); + else if (HAS_PCH_CPT(display) && port != PORT_A) + ret &= cpt_dp_port_selected(display, port, pipe); + else if (display->platform.cherryview) + *pipe = REG_FIELD_GET(DP_PIPE_SEL_MASK_CHV, val); + else + *pipe = REG_FIELD_GET(DP_PIPE_SEL_MASK, val); + + return ret; +} + +static bool intel_dp_get_hw_state(struct intel_encoder *encoder, + enum pipe *pipe) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + struct ref_tracker *wakeref; + bool ret; + + wakeref = intel_display_power_get_if_enabled(display, + encoder->power_domain); + if (!wakeref) + return false; + + ret = g4x_dp_port_enabled(display, intel_dp->output_reg, + encoder->port, pipe); + + intel_display_power_put(display, 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 intel_display *display = to_intel_display(encoder); + 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(display, intel_dp->output_reg); + + pipe_config->has_audio = tmp & DP_AUDIO_OUTPUT_ENABLE && port != PORT_A; + + if (HAS_PCH_CPT(display) && port != PORT_A) { + u32 trans_dp = intel_de_read(display, + TRANS_DP_CTL(crtc->pipe)); + + if (trans_dp & TRANS_DP_ENH_FRAMING) + pipe_config->enhanced_framing = true; + + 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_ENHANCED_FRAMING) + pipe_config->enhanced_framing = true; + + 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 (display->platform.g4x && tmp & DP_COLOR_RANGE_16_235) + pipe_config->limited_color_range = true; + + pipe_config->lane_count = REG_FIELD_GET(DP_PORT_WIDTH_MASK, tmp) + 1; + + g4x_dp_get_m_n(pipe_config); + + if (port == PORT_A) { + if ((intel_de_read(display, DP_A) & EDP_PLL_FREQ_MASK) == EDP_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); + + intel_audio_codec_get_config(encoder, pipe_config); +} + +static void +intel_dp_link_down(struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state) +{ + struct intel_display *display = to_intel_display(encoder); + 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(display->drm, + (intel_de_read(display, intel_dp->output_reg) & + DP_PORT_EN) == 0)) + return; + + drm_dbg_kms(display->drm, "\n"); + + intel_dp->DP &= ~DP_PORT_EN; + intel_de_write(display, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(display, 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(display) && 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(display, PIPE_A, false); + intel_set_pch_fifo_underrun_reporting(display, 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(display, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(display, intel_dp->output_reg); + + intel_dp->DP &= ~DP_PORT_EN; + intel_de_write(display, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(display, intel_dp->output_reg); + + intel_wait_for_vblank_if_active(display, PIPE_A); + intel_set_cpu_fifo_underrun_reporting(display, PIPE_A, true); + intel_set_pch_fifo_underrun_reporting(display, PIPE_A, true); + } + + msleep(intel_dp->pps.panel_power_down_delay); + + if (display->platform.valleyview || display->platform.cherryview) + vlv_pps_port_disable(encoder, old_crtc_state); +} + +static void g4x_dp_audio_enable(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + + if (!crtc_state->has_audio) + return; + + /* Enable audio presence detect */ + intel_dp->DP |= DP_AUDIO_OUTPUT_ENABLE; + intel_de_write(display, intel_dp->output_reg, intel_dp->DP); + + intel_audio_codec_enable(encoder, crtc_state, conn_state); +} + +static void g4x_dp_audio_disable(struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + + if (!old_crtc_state->has_audio) + return; + + intel_audio_codec_disable(encoder, old_crtc_state, old_conn_state); + + /* Disable audio presence detect */ + intel_dp->DP &= ~DP_AUDIO_OUTPUT_ENABLE; + intel_de_write(display, intel_dp->output_reg, intel_dp->DP); +} + +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.active = false; + + /* + * 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) +{ + intel_dp_link_down(encoder, old_crtc_state); + + /* Assert data lane reset */ + chv_data_lane_soft_reset(encoder, old_crtc_state, true); +} + +static void +cpt_set_link_train(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state, + u8 dp_train_pat) +{ + struct intel_display *display = to_intel_display(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(display, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(display, intel_dp->output_reg); +} + +static void +cpt_set_idle_link_train(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(intel_dp); + + intel_dp->DP &= ~DP_LINK_TRAIN_MASK_CPT; + intel_dp->DP |= DP_LINK_TRAIN_PAT_IDLE_CPT; + + intel_de_write(display, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(display, 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 intel_display *display = to_intel_display(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(display, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(display, intel_dp->output_reg); +} + +static void +g4x_set_idle_link_train(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(intel_dp); + + intel_dp->DP &= ~DP_LINK_TRAIN_MASK; + intel_dp->DP |= DP_LINK_TRAIN_PAT_IDLE; + + intel_de_write(display, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(display, intel_dp->output_reg); +} + +static void intel_dp_enable_port(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(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; + + intel_de_write(display, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(display, 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 intel_display *display = to_intel_display(state); + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + u32 dp_reg = intel_de_read(display, intel_dp->output_reg); + + if (drm_WARN_ON(display->drm, dp_reg & DP_PORT_EN)) + return; + + with_intel_pps_lock(intel_dp) { + if (display->platform.valleyview || display->platform.cherryview) + vlv_pps_port_enable_unlocked(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 (display->platform.valleyview || display->platform.cherryview) { + unsigned int lane_mask = 0x0; + + if (display->platform.cherryview) + lane_mask = intel_dp_unused_lane_mask(pipe_config->lane_count); + + vlv_wait_port_ready(encoder, 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(state, intel_dp, pipe_config); + intel_dp_stop_link_train(intel_dp, pipe_config); +} + +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 intel_display *display = to_intel_display(encoder); + 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(display->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(display, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(display, 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 intel_display *display = to_intel_display(encoder); + 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(display->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(display, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(display, 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 intel_display *display = to_intel_display(encoder); + 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(display->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(display, intel_dp->output_reg, intel_dp->DP); + intel_de_posting_read(display, 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); + enum intel_hotplug_state state; + + if (intel_dp_test_phy(intel_dp)) + return INTEL_HOTPLUG_UNCHANGED; + + state = intel_encoder_hotplug(encoder, connector); + + intel_dp_check_link_state(intel_dp); + + /* + * 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 intel_display *display = to_intel_display(encoder); + u32 bit = display->hotplug.pch_hpd[encoder->hpd_pin]; + + return intel_de_read(display, SDEISR) & bit; +} + +static bool g4x_digital_port_connected(struct intel_encoder *encoder) +{ + struct intel_display *display = to_intel_display(encoder); + 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(display, PORT_HOTPLUG_STAT(display)) & bit; +} + +static bool ilk_digital_port_connected(struct intel_encoder *encoder) +{ + struct intel_display *display = to_intel_display(encoder); + u32 bit = display->hotplug.hpd[encoder->hpd_pin]; + + return intel_de_read(display, DEISR) & bit; +} + +static int g4x_dp_compute_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct intel_display *display = to_intel_display(encoder); + int ret; + + if (HAS_PCH_SPLIT(display) && encoder->port != PORT_A) + crtc_state->has_pch_encoder = true; + + ret = intel_dp_compute_config(encoder, crtc_state, conn_state); + if (ret) + return ret; + + g4x_dp_set_clock(encoder, crtc_state); + + return 0; +} + +static void g4x_dp_suspend_complete(struct intel_encoder *encoder) +{ + /* + * TODO: Move this to intel_dp_encoder_suspend(), + * once modeset locking around that is removed. + */ + intel_encoder_link_check_flush_work(encoder); +} + +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))); +} + +static void intel_dp_encoder_reset(struct drm_encoder *encoder) +{ + struct intel_display *display = to_intel_display(encoder->dev); + struct intel_dp *intel_dp = enc_to_intel_dp(to_intel_encoder(encoder)); + + intel_dp->DP = intel_de_read(display, intel_dp->output_reg); + + intel_dp->reset_link_params = true; + intel_dp_invalidate_source_oui(intel_dp); + + if (display->platform.valleyview || display->platform.cherryview) + vlv_pps_pipe_reset(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 intel_display *display, + intel_reg_t output_reg, enum port port) +{ + const struct intel_bios_encoder_data *devdata; + struct intel_digital_port *dig_port; + struct intel_encoder *intel_encoder; + struct drm_encoder *encoder; + struct intel_connector *intel_connector; + + if (!assert_port_valid(display, port)) + return false; + + devdata = intel_bios_encoder_data_lookup(display, port); + + /* FIXME bail? */ + if (!devdata) + drm_dbg_kms(display->drm, "No VBT child device for DP-%c\n", + port_name(port)); + + dig_port = intel_dig_port_alloc(); + 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; + + intel_encoder->devdata = devdata; + + if (drm_encoder_init(display->drm, &intel_encoder->base, + &intel_dp_enc_funcs, DRM_MODE_ENCODER_TMDS, + "DP %c", port_name(port))) + goto err_encoder_init; + + intel_encoder_link_check_init(intel_encoder, intel_dp_link_check); + + intel_encoder->hotplug = intel_dp_hotplug; + intel_encoder->compute_config = g4x_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->suspend_complete = g4x_dp_suspend_complete; + intel_encoder->shutdown = intel_dp_encoder_shutdown; + if (display->platform.cherryview) { + 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 (display->platform.valleyview) { + 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; + } + intel_encoder->audio_enable = g4x_dp_audio_enable; + intel_encoder->audio_disable = g4x_dp_audio_disable; + + if ((display->platform.ivybridge && port == PORT_A) || + (HAS_PCH_CPT(display) && port != PORT_A)) { + dig_port->dp.set_link_train = cpt_set_link_train; + dig_port->dp.set_idle_link_train = cpt_set_idle_link_train; + } else { + dig_port->dp.set_link_train = g4x_set_link_train; + dig_port->dp.set_idle_link_train = g4x_set_idle_link_train; + } + + if (display->platform.cherryview) + intel_encoder->set_signal_levels = chv_set_signal_levels; + else if (display->platform.valleyview) + intel_encoder->set_signal_levels = vlv_set_signal_levels; + else if (display->platform.ivybridge && port == PORT_A) + intel_encoder->set_signal_levels = ivb_cpu_edp_set_signal_levels; + else if (display->platform.sandybridge && 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 (display->platform.valleyview || display->platform.cherryview || + (HAS_PCH_SPLIT(display) && 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; + + intel_encoder->type = INTEL_OUTPUT_DP; + intel_encoder->power_domain = intel_display_power_ddi_lanes_domain(display, port); + if (display->platform.cherryview) { + 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(port); + + dig_port->hpd_pulse = intel_dp_hpd_pulse; + + if (HAS_GMCH(display)) { + 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_dp_aux_ch(intel_encoder); + if (dig_port->aux_ch == AUX_CH_NONE) + goto err_init_connector; + + 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..99de690eb --- /dev/null +++ b/drivers/gpu/drm/i915/display/g4x_dp.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2020 Intel Corporation + */ + +#ifndef _G4X_DP_H_ +#define _G4X_DP_H_ + +#include + +#include "intel_display_reg_defs.h" + +enum pipe; +enum port; +struct intel_crtc_state; +struct intel_display; +struct intel_dp; +struct intel_encoder; + +#ifdef I915 +const struct dpll *vlv_get_dpll(struct intel_display *display); +bool g4x_dp_port_enabled(struct intel_display *display, + intel_reg_t dp_reg, enum port port, + enum pipe *pipe); +bool g4x_dp_init(struct intel_display *display, + intel_reg_t output_reg, enum port port); +#else +static inline const struct dpll *vlv_get_dpll(struct intel_display *display) +{ + return NULL; +} +static inline bool g4x_dp_port_enabled(struct intel_display *display, + intel_reg_t dp_reg, int port, + enum pipe *pipe) +{ + return false; +} +static inline bool g4x_dp_init(struct intel_display *display, + intel_reg_t output_reg, int port) +{ + return false; +} +#endif + +#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..acb36cab9 --- /dev/null +++ b/drivers/gpu/drm/i915/display/g4x_hdmi.c @@ -0,0 +1,778 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2020 Intel Corporation + * + * HDMI support for G4x,ILK,SNB,IVB,VLV,CHV (HSW+ handled by the DDI code). + */ + +#include + +#include "g4x_hdmi.h" +#include "intel_atomic.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_regs.h" +#include "intel_display_types.h" +#include "intel_dp_aux.h" +#include "intel_dpio_phy.h" +#include "intel_encoder.h" +#include "intel_fifo_underrun.h" +#include "intel_hdmi.h" +#include "intel_hotplug.h" +#include "intel_sdvo.h" + +static void intel_hdmi_prepare(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(encoder); + 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(display) && 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(display)) + hdmi_val |= SDVO_PIPE_SEL_CPT(crtc->pipe); + else if (display->platform.cherryview) + hdmi_val |= SDVO_PIPE_SEL_CHV(crtc->pipe); + else + hdmi_val |= SDVO_PIPE_SEL(crtc->pipe); + + intel_de_write(display, intel_hdmi->hdmi_reg, hdmi_val); + intel_de_posting_read(display, intel_hdmi->hdmi_reg); +} + +static bool intel_hdmi_get_hw_state(struct intel_encoder *encoder, + enum pipe *pipe) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder); + struct ref_tracker *wakeref; + bool ret; + + wakeref = intel_display_power_get_if_enabled(display, + encoder->power_domain); + if (!wakeref) + return false; + + ret = intel_sdvo_port_enabled(display, intel_hdmi->hdmi_reg, pipe); + + intel_display_power_put(display, encoder->power_domain, wakeref); + + return ret; +} + +static bool connector_is_hdmi(struct drm_connector *connector) +{ + struct intel_encoder *encoder = + intel_attached_encoder(to_intel_connector(connector)); + + return encoder && encoder->type == INTEL_OUTPUT_HDMI; +} + +static bool g4x_compute_has_hdmi_sink(struct intel_atomic_state *state, + struct intel_crtc *this_crtc) +{ + const struct drm_connector_state *conn_state; + struct drm_connector *connector; + int i; + + /* + * On g4x only one HDMI port can transmit infoframes/audio at + * any given time. Select the first suitable port for this duty. + * + * See also g4x_hdmi_connector_atomic_check(). + */ + for_each_new_connector_in_state(&state->base, connector, conn_state, i) { + struct intel_encoder *encoder = to_intel_encoder(conn_state->best_encoder); + const struct intel_crtc_state *crtc_state; + struct intel_crtc *crtc; + + if (!connector_is_hdmi(connector)) + continue; + + crtc = to_intel_crtc(conn_state->crtc); + if (!crtc) + continue; + + crtc_state = intel_atomic_get_new_crtc_state(state, crtc); + + if (!intel_hdmi_compute_has_hdmi_sink(encoder, crtc_state, conn_state)) + continue; + + return crtc == this_crtc; + } + + return false; +} + +static int g4x_hdmi_compute_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_atomic_state *state = to_intel_atomic_state(crtc_state->uapi.state); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + if (HAS_PCH_SPLIT(display)) + crtc_state->has_pch_encoder = true; + + if (display->platform.g4x) + crtc_state->has_hdmi_sink = g4x_compute_has_hdmi_sink(state, crtc); + else + crtc_state->has_hdmi_sink = + intel_hdmi_compute_has_hdmi_sink(encoder, crtc_state, conn_state); + + return intel_hdmi_compute_config(encoder, crtc_state, conn_state); +} + +static void intel_hdmi_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *pipe_config) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder); + u32 tmp, flags = 0; + int dotclock; + + pipe_config->output_types |= BIT(INTEL_OUTPUT_HDMI); + + tmp = intel_de_read(display, 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(display) && + 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); + + intel_audio_codec_get_config(encoder, pipe_config); +} + +static void g4x_hdmi_enable_port(struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder); + u32 temp; + + temp = intel_de_read(display, intel_hdmi->hdmi_reg); + + temp |= SDVO_ENABLE; + + intel_de_write(display, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(display, intel_hdmi->hdmi_reg); +} + +static void g4x_hdmi_audio_enable(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_hdmi *hdmi = enc_to_intel_hdmi(encoder); + + if (!crtc_state->has_audio) + return; + + drm_WARN_ON(display->drm, !crtc_state->has_hdmi_sink); + + /* Enable audio presence detect */ + intel_de_rmw(display, hdmi->hdmi_reg, 0, HDMI_AUDIO_ENABLE); + + intel_audio_codec_enable(encoder, crtc_state, conn_state); +} + +static void g4x_hdmi_audio_disable(struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_hdmi *hdmi = enc_to_intel_hdmi(encoder); + + if (!old_crtc_state->has_audio) + return; + + intel_audio_codec_disable(encoder, old_crtc_state, old_conn_state); + + /* Disable audio presence detect */ + intel_de_rmw(display, hdmi->hdmi_reg, HDMI_AUDIO_ENABLE, 0); +} + +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) +{ + g4x_hdmi_enable_port(encoder, pipe_config); +} + +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 intel_display *display = to_intel_display(encoder); + struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder); + u32 temp; + + temp = intel_de_read(display, intel_hdmi->hdmi_reg); + + temp |= SDVO_ENABLE; + + /* + * HW workaround, need to write this twice for issue + * that may result in first write getting masked. + */ + intel_de_write(display, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(display, intel_hdmi->hdmi_reg); + intel_de_write(display, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(display, 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(display, intel_hdmi->hdmi_reg, + temp & ~SDVO_ENABLE); + intel_de_posting_read(display, intel_hdmi->hdmi_reg); + + /* + * HW workaround, need to write this twice for issue + * that may result in first write getting masked. + */ + intel_de_write(display, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(display, intel_hdmi->hdmi_reg); + intel_de_write(display, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(display, intel_hdmi->hdmi_reg); + } +} + +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 intel_display *display = to_intel_display(encoder); + 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(display, intel_hdmi->hdmi_reg); + + temp |= SDVO_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_rmw(display, TRANS_CHICKEN1(pipe), + 0, TRANS_CHICKEN1_HDMIUNIT_GC_DISABLE); + + temp &= ~SDVO_COLOR_FORMAT_MASK; + temp |= SDVO_COLOR_FORMAT_8bpc; + } + + intel_de_write(display, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(display, intel_hdmi->hdmi_reg); + + if (pipe_config->pipe_bpp > 24) { + temp &= ~SDVO_COLOR_FORMAT_MASK; + temp |= HDMI_COLOR_FORMAT_12bpc; + + intel_de_write(display, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(display, intel_hdmi->hdmi_reg); + + intel_de_rmw(display, TRANS_CHICKEN1(pipe), + TRANS_CHICKEN1_HDMIUNIT_GC_DISABLE, 0); + } +} + +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 intel_display *display = to_intel_display(encoder); + 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(display, intel_hdmi->hdmi_reg); + + temp &= ~SDVO_ENABLE; + intel_de_write(display, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(display, 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(display) && 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(display, PIPE_A, false); + intel_set_pch_fifo_underrun_reporting(display, 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(display, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(display, intel_hdmi->hdmi_reg); + intel_de_write(display, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(display, intel_hdmi->hdmi_reg); + + temp &= ~SDVO_ENABLE; + intel_de_write(display, intel_hdmi->hdmi_reg, temp); + intel_de_posting_read(display, intel_hdmi->hdmi_reg); + + intel_wait_for_vblank_if_active(display, PIPE_A); + intel_set_cpu_fifo_underrun_reporting(display, PIPE_A, true); + intel_set_pch_fifo_underrun_reporting(display, 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_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) +{ +} + +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); + + 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_hdmi_enable_port(encoder, pipe_config); + + vlv_wait_port_ready(encoder, 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) +{ + /* Assert data lane reset */ + chv_data_lane_soft_reset(encoder, old_crtc_state, true); +} + +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); + + 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_hdmi_enable_port(encoder, pipe_config); + + vlv_wait_port_ready(encoder, 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; +} + +int g4x_hdmi_connector_atomic_check(struct drm_connector *connector, + struct drm_atomic_commit *state) +{ + struct intel_display *display = to_intel_display(connector->dev); + struct drm_connector_list_iter conn_iter; + struct drm_connector *conn; + int ret; + + ret = intel_digital_connector_atomic_check(connector, state); + if (ret) + return ret; + + if (!display->platform.g4x) + return 0; + + if (!intel_connector_needs_modeset(to_intel_atomic_state(state), connector)) + return 0; + + /* + * On g4x only one HDMI port can transmit infoframes/audio + * at any given time. Make sure all enabled HDMI ports are + * included in the state so that it's possible to select + * one of them for this duty. + * + * See also g4x_compute_has_hdmi_sink(). + */ + drm_connector_list_iter_begin(display->drm, &conn_iter); + drm_for_each_connector_iter(conn, &conn_iter) { + struct drm_connector_state *conn_state; + struct drm_crtc_state *crtc_state; + struct drm_crtc *crtc; + + if (!connector_is_hdmi(conn)) + continue; + + drm_dbg_kms(display->drm, "Adding [CONNECTOR:%d:%s]\n", + conn->base.id, conn->name); + + conn_state = drm_atomic_get_connector_state(state, conn); + if (IS_ERR(conn_state)) { + ret = PTR_ERR(conn_state); + break; + } + + crtc = conn_state->crtc; + if (!crtc) + continue; + + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + crtc_state->mode_changed = true; + + ret = drm_atomic_add_affected_planes(state, crtc); + if (ret) + break; + } + drm_connector_list_iter_end(&conn_iter); + + return ret; +} + +static bool is_hdmi_port_valid(struct intel_display *display, enum port port) +{ + if (display->platform.g4x || display->platform.valleyview) + return port == PORT_B || port == PORT_C; + else + return port == PORT_B || port == PORT_C || port == PORT_D; +} + +static bool assert_hdmi_port_valid(struct intel_display *display, enum port port) +{ + return !drm_WARN(display->drm, !is_hdmi_port_valid(display, port), + "Platform does not support HDMI %c\n", port_name(port)); +} + +bool g4x_hdmi_init(struct intel_display *display, + intel_reg_t hdmi_reg, enum port port) +{ + const struct intel_bios_encoder_data *devdata; + struct intel_digital_port *dig_port; + struct intel_encoder *intel_encoder; + struct intel_connector *intel_connector; + + if (!assert_port_valid(display, port)) + return false; + + if (!assert_hdmi_port_valid(display, port)) + return false; + + devdata = intel_bios_encoder_data_lookup(display, port); + + /* FIXME bail? */ + if (!devdata) + drm_dbg_kms(display->drm, "No VBT child device for HDMI-%c\n", + port_name(port)); + + dig_port = intel_dig_port_alloc(); + if (!dig_port) + return false; + + intel_connector = intel_connector_alloc(); + if (!intel_connector) + goto err_connector_alloc; + + intel_encoder = &dig_port->base; + + intel_encoder->devdata = devdata; + + if (drm_encoder_init(display->drm, &intel_encoder->base, + &intel_hdmi_enc_funcs, DRM_MODE_ENCODER_TMDS, + "HDMI %c", port_name(port))) + goto err_encoder_init; + + intel_encoder->hotplug = intel_hdmi_hotplug; + intel_encoder->compute_config = g4x_hdmi_compute_config; + if (HAS_PCH_SPLIT(display)) { + 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 (display->platform.cherryview) { + 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 (display->platform.valleyview) { + 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(display)) + intel_encoder->enable = cpt_enable_hdmi; + else if (HAS_PCH_IBX(display)) + intel_encoder->enable = ibx_enable_hdmi; + else + intel_encoder->enable = g4x_enable_hdmi; + } + intel_encoder->audio_enable = g4x_hdmi_audio_enable; + intel_encoder->audio_disable = g4x_hdmi_audio_disable; + intel_encoder->shutdown = intel_hdmi_encoder_shutdown; + + intel_encoder->type = INTEL_OUTPUT_HDMI; + intel_encoder->power_domain = intel_display_power_ddi_lanes_domain(display, port); + intel_encoder->port = port; + if (display->platform.cherryview) { + 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 = BIT(INTEL_OUTPUT_ANALOG); + intel_encoder->hpd_pin = intel_hpd_pin_default(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 (display->platform.g4x) + intel_encoder->cloneable |= BIT(INTEL_OUTPUT_HDMI); + + dig_port->hdmi.hdmi_reg = hdmi_reg; + + intel_infoframe_init(dig_port); + + if (!intel_hdmi_init_connector(dig_port, intel_connector)) + goto err_init_connector; + + return true; + +err_init_connector: + drm_encoder_cleanup(&intel_encoder->base); +err_encoder_init: + kfree(intel_connector); +err_connector_alloc: + kfree(dig_port); + + return false; +} 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..7586b1a6e --- /dev/null +++ b/drivers/gpu/drm/i915/display/g4x_hdmi.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2020 Intel Corporation + */ + +#ifndef _G4X_HDMI_H_ +#define _G4X_HDMI_H_ + +#include + +#include "intel_display_reg_defs.h" + +enum port; +struct drm_atomic_commit; +struct drm_connector; +struct intel_display; + +#ifdef I915 +bool g4x_hdmi_init(struct intel_display *display, + intel_reg_t hdmi_reg, enum port port); +int g4x_hdmi_connector_atomic_check(struct drm_connector *connector, + struct drm_atomic_commit *state); +#else +static inline bool g4x_hdmi_init(struct intel_display *display, + intel_reg_t hdmi_reg, int port) +{ + return false; +} +static inline int g4x_hdmi_connector_atomic_check(struct drm_connector *connector, + struct drm_atomic_commit *state) +{ + return 0; +} +#endif + +#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..cbaef3f13 --- /dev/null +++ b/drivers/gpu/drm/i915/display/hsw_ips.c @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2022 Intel Corporation + */ + +#include + +#include +#include + +#include "hsw_ips.h" +#include "intel_color_regs.h" +#include "intel_de.h" +#include "intel_display_regs.h" +#include "intel_display_rpm.h" +#include "intel_display_types.h" +#include "intel_parent.h" + +static void hsw_ips_enable(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + u32 val; + + 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(display->drm, + !(crtc_state->active_planes & ~BIT(PLANE_CURSOR))); + + val = IPS_ENABLE; + + if (display->ips.false_color) + val |= IPS_FALSE_COLOR; + + if (display->platform.broadwell) { + drm_WARN_ON(display->drm, + intel_parent_pcode_write(display, DISPLAY_IPS_CONTROL, + val | 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(display, IPS_CTL, val); + /* + * 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_ms(display, IPS_CTL, IPS_ENABLE, 50)) + drm_err(display->drm, + "Timed out waiting for IPS enable\n"); + } +} + +bool hsw_ips_disable(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + bool need_vblank_wait = false; + + if (!crtc_state->ips_enabled) + return need_vblank_wait; + + if (display->platform.broadwell) { + drm_WARN_ON(display->drm, + intel_parent_pcode_write(display, 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_ms(display, IPS_CTL, IPS_ENABLE, 100)) + drm_err(display->drm, + "Timed out waiting for IPS disable\n"); + } else { + intel_de_write(display, IPS_CTL, 0); + intel_de_posting_read(display, 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 intel_display *display = to_intel_display(state); + 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 (display->platform.haswell && + intel_crtc_needs_color_update(new_crtc_state) && + 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 intel_display *display = to_intel_display(state); + 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 (display->platform.haswell && + intel_crtc_needs_color_update(new_crtc_state) && + 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 (intel_crtc_needs_fastset(new_crtc_state) && 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) +{ + struct intel_display *display = to_intel_display(crtc); + + return HAS_IPS(display) && crtc->pipe == PIPE_A; +} + +static bool hsw_crtc_state_ips_capable(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + if (!hsw_crtc_supports_ips(crtc)) + return false; + + if (crtc_state->pipe_bpp > 24) + return false; + + return true; +} + +static int _hsw_ips_min_cdclk(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + if (display->platform.broadwell) + return DIV_ROUND_UP(crtc_state->pixel_rate * 100, 95); + + /* no IPS specific limits to worry about */ + return 0; +} + +int hsw_ips_min_cdclk(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + int min_cdclk; + + if (!hsw_crtc_state_ips_capable(crtc_state)) + return 0; + + min_cdclk = _hsw_ips_min_cdclk(crtc_state); + + /* + * Do not ask for more than the max CDCLK frequency, + * if that is not enough IPS will simply not be used. + */ + if (min_cdclk > display->cdclk.max_cdclk_freq) + return 0; + + return min_cdclk; +} + +int hsw_ips_compute_config(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(state); + 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; + + if (_hsw_ips_min_cdclk(crtc_state) > display->cdclk.max_cdclk_freq) + return 0; + + if (!display->params.enable_ips) + 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; + + crtc_state->ips_enabled = true; + + return 0; +} + +void hsw_ips_get_config(struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + if (!hsw_crtc_supports_ips(crtc)) + return; + + if (display->platform.haswell) { + crtc_state->ips_enabled = intel_de_read(display, 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; + } +} + +static int hsw_ips_debugfs_false_color_get(void *data, u64 *val) +{ + struct intel_crtc *crtc = data; + struct intel_display *display = to_intel_display(crtc); + + *val = display->ips.false_color; + + return 0; +} + +static int hsw_ips_debugfs_false_color_set(void *data, u64 val) +{ + struct intel_crtc *crtc = data; + struct intel_display *display = to_intel_display(crtc); + struct intel_crtc_state *crtc_state; + int ret; + + ret = drm_modeset_lock(&crtc->base.mutex, NULL); + if (ret) + return ret; + + display->ips.false_color = val; + + crtc_state = to_intel_crtc_state(crtc->base.state); + + if (!crtc_state->hw.active) + goto unlock; + + if (crtc_state->uapi.commit && + !try_wait_for_completion(&crtc_state->uapi.commit->hw_done)) + goto unlock; + + hsw_ips_enable(crtc_state); + + unlock: + drm_modeset_unlock(&crtc->base.mutex); + + return ret; +} + +DEFINE_DEBUGFS_ATTRIBUTE(hsw_ips_debugfs_false_color_fops, + hsw_ips_debugfs_false_color_get, + hsw_ips_debugfs_false_color_set, + "%llu\n"); + +static int hsw_ips_debugfs_status_show(struct seq_file *m, void *unused) +{ + struct intel_crtc *crtc = m->private; + struct intel_display *display = to_intel_display(crtc); + struct ref_tracker *wakeref; + + wakeref = intel_display_rpm_get(display); + + seq_printf(m, "Enabled by kernel parameter: %s\n", + str_yes_no(display->params.enable_ips)); + + if (DISPLAY_VER(display) >= 8) { + seq_puts(m, "Currently: unknown\n"); + } else { + if (intel_de_read(display, IPS_CTL) & IPS_ENABLE) + seq_puts(m, "Currently: enabled\n"); + else + seq_puts(m, "Currently: disabled\n"); + } + + intel_display_rpm_put(display, wakeref); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(hsw_ips_debugfs_status); + +void hsw_ips_crtc_debugfs_add(struct intel_crtc *crtc) +{ + if (!hsw_crtc_supports_ips(crtc)) + return; + + debugfs_create_file("i915_ips_false_color", 0644, crtc->base.debugfs_entry, + crtc, &hsw_ips_debugfs_false_color_fops); + + debugfs_create_file("i915_ips_status", 0444, crtc->base.debugfs_entry, + crtc, &hsw_ips_debugfs_status_fops); +} 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..7af12f88a --- /dev/null +++ b/drivers/gpu/drm/i915/display/hsw_ips.h @@ -0,0 +1,62 @@ +/* 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; + +#ifdef I915 +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); +int hsw_ips_min_cdclk(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); +void hsw_ips_crtc_debugfs_add(struct intel_crtc *crtc); +#else +static inline bool hsw_ips_disable(const struct intel_crtc_state *crtc_state) +{ + return false; +} +static inline bool hsw_ips_pre_update(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + return false; +} +static inline void hsw_ips_post_update(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ +} +static inline bool hsw_crtc_supports_ips(struct intel_crtc *crtc) +{ + return false; +} +static inline int hsw_ips_min_cdclk(const struct intel_crtc_state *crtc_state) +{ + return 0; +} +static inline int hsw_ips_compute_config(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + return 0; +} +static inline void hsw_ips_get_config(struct intel_crtc_state *crtc_state) +{ +} +static inline void hsw_ips_crtc_debugfs_add(struct intel_crtc *crtc) +{ +} +#endif + +#endif /* __HSW_IPS_H__ */ diff --git a/drivers/gpu/drm/i915/display/i9xx_display_sr.c b/drivers/gpu/drm/i915/display/i9xx_display_sr.c new file mode 100644 index 000000000..1eb2f636c --- /dev/null +++ b/drivers/gpu/drm/i915/display/i9xx_display_sr.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2024 Intel Corporation + */ + +#include +#include + +#include "i9xx_display_sr.h" +#include "i9xx_wm_regs.h" +#include "intel_de.h" +#include "intel_display_regs.h" +#include "intel_gmbus.h" + +static void i9xx_display_save_swf(struct intel_display *display) +{ + int i; + + /* Scratch space */ + if (DISPLAY_VER(display) == 2 && display->platform.mobile) { + for (i = 0; i < 7; i++) { + display->restore.saveSWF0[i] = intel_de_read(display, SWF0(display, i)); + display->restore.saveSWF1[i] = intel_de_read(display, SWF1(display, i)); + } + for (i = 0; i < 3; i++) + display->restore.saveSWF3[i] = intel_de_read(display, SWF3(display, i)); + } else if (DISPLAY_VER(display) == 2) { + for (i = 0; i < 7; i++) + display->restore.saveSWF1[i] = intel_de_read(display, SWF1(display, i)); + } else if (HAS_GMCH(display)) { + for (i = 0; i < 16; i++) { + display->restore.saveSWF0[i] = intel_de_read(display, SWF0(display, i)); + display->restore.saveSWF1[i] = intel_de_read(display, SWF1(display, i)); + } + for (i = 0; i < 3; i++) + display->restore.saveSWF3[i] = intel_de_read(display, SWF3(display, i)); + } +} + +static void i9xx_display_restore_swf(struct intel_display *display) +{ + int i; + + /* Scratch space */ + if (DISPLAY_VER(display) == 2 && display->platform.mobile) { + for (i = 0; i < 7; i++) { + intel_de_write(display, SWF0(display, i), display->restore.saveSWF0[i]); + intel_de_write(display, SWF1(display, i), display->restore.saveSWF1[i]); + } + for (i = 0; i < 3; i++) + intel_de_write(display, SWF3(display, i), display->restore.saveSWF3[i]); + } else if (DISPLAY_VER(display) == 2) { + for (i = 0; i < 7; i++) + intel_de_write(display, SWF1(display, i), display->restore.saveSWF1[i]); + } else if (HAS_GMCH(display)) { + for (i = 0; i < 16; i++) { + intel_de_write(display, SWF0(display, i), display->restore.saveSWF0[i]); + intel_de_write(display, SWF1(display, i), display->restore.saveSWF1[i]); + } + for (i = 0; i < 3; i++) + intel_de_write(display, SWF3(display, i), display->restore.saveSWF3[i]); + } +} + +void i9xx_display_sr_save(struct intel_display *display) +{ + struct pci_dev *pdev = to_pci_dev(display->drm->dev); + + if (!HAS_DISPLAY(display)) + return; + + /* Display arbitration control */ + if (DISPLAY_VER(display) <= 4) + display->restore.saveDSPARB = intel_de_read(display, DSPARB(display)); + + if (DISPLAY_VER(display) == 4) + pci_read_config_word(pdev, GCDGMBUS, &display->restore.saveGCDGMBUS); + + i9xx_display_save_swf(display); +} + +void i9xx_display_sr_restore(struct intel_display *display) +{ + struct pci_dev *pdev = to_pci_dev(display->drm->dev); + + if (!HAS_DISPLAY(display)) + return; + + i9xx_display_restore_swf(display); + + if (DISPLAY_VER(display) == 4) + pci_write_config_word(pdev, GCDGMBUS, display->restore.saveGCDGMBUS); + + /* Display arbitration */ + if (DISPLAY_VER(display) <= 4) + intel_de_write(display, DSPARB(display), display->restore.saveDSPARB); +} diff --git a/drivers/gpu/drm/i915/display/i9xx_display_sr.h b/drivers/gpu/drm/i915/display/i9xx_display_sr.h new file mode 100644 index 000000000..39b8c18fe --- /dev/null +++ b/drivers/gpu/drm/i915/display/i9xx_display_sr.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2024 Intel Corporation + */ + +#ifndef __I9XX_DISPLAY_SR_H__ +#define __I9XX_DISPLAY_SR_H__ + +struct intel_display; + +void i9xx_display_sr_save(struct intel_display *display); +void i9xx_display_sr_restore(struct intel_display *display); + +#endif 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..70734d32a --- /dev/null +++ b/drivers/gpu/drm/i915/display/i9xx_plane.c @@ -0,0 +1,1271 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2020 Intel Corporation + */ + +#include + +#include +#include +#include +#include + +#include "i9xx_plane.h" +#include "i9xx_plane_regs.h" +#include "intel_atomic.h" +#include "intel_de.h" +#include "intel_display_irq.h" +#include "intel_display_regs.h" +#include "intel_display_types.h" +#include "intel_display_utils.h" +#include "intel_fb.h" +#include "intel_fbc.h" +#include "intel_frontbuffer.h" +#include "intel_plane.h" +#include "intel_sprite.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 intel_display *display, + enum i9xx_plane_id i9xx_plane) +{ + if (!HAS_FBC(display)) + return false; + + if (display->platform.broadwell || display->platform.haswell) + return i9xx_plane == PLANE_A; /* tied to pipe A */ + else if (display->platform.ivybridge) + return i9xx_plane == PLANE_A || i9xx_plane == PLANE_B || + i9xx_plane == PLANE_C; + else if (DISPLAY_VER(display) >= 4) + return i9xx_plane == PLANE_A || i9xx_plane == PLANE_B; + else + return i9xx_plane == PLANE_A; +} + +static struct intel_fbc *i9xx_plane_fbc(struct intel_display *display, + enum i9xx_plane_id i9xx_plane) +{ + if (i9xx_plane_has_fbc(display, i9xx_plane)) + return display->fbc.instances[INTEL_FBC_A]; + else + return NULL; +} + +static bool i9xx_plane_has_windowing(struct intel_plane *plane) +{ + struct intel_display *display = to_intel_display(plane); + enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; + + if (display->platform.cherryview) + return i9xx_plane == PLANE_B; + else if (DISPLAY_VER(display) >= 5 || display->platform.g4x) + return false; + else if (DISPLAY_VER(display) == 4) + return i9xx_plane == PLANE_C; + else + return i9xx_plane == PLANE_B || + i9xx_plane == PLANE_C; +} + +static u32 i9xx_plane_ctl(const struct intel_plane_state *plane_state) +{ + struct intel_display *display = to_intel_display(plane_state); + const struct drm_framebuffer *fb = plane_state->hw.fb; + unsigned int rotation = plane_state->hw.rotation; + u32 dspcntr; + + dspcntr = DISP_ENABLE; + + if (display->platform.g4x || display->platform.ironlake || + display->platform.sandybridge || display->platform.ivybridge) + 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(display) >= 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 intel_display *display = to_intel_display(plane_state); + struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane); + 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(display) && fb->format->cpp[0] == 8 && src_w > 2048) { + drm_dbg_kms(display->drm, + "[PLANE:%d:%s] plane too wide (%d) for 64bpp\n", + plane->base.base.id, plane->base.name, src_w); + return -EINVAL; + } + + intel_add_fb_offsets(&src_x, &src_y, plane_state, 0); + + if (DISPLAY_VER(display) >= 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(display) >= 4 && fb->modifier == I915_FORMAT_MOD_X_TILED) { + unsigned int alignment = plane->min_alignment(plane, 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(display->drm, + "[PLANE:%d:%s] unable to find suitable display surface offset due to X-tiling\n", + plane->base.base.id, plane->base.name); + 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 (!display->platform.haswell && !display->platform.broadwell) { + 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 (display->platform.haswell || display->platform.broadwell) { + drm_WARN_ON(display->drm, src_x > 8191 || src_y > 4095); + } else if (DISPLAY_VER(display) >= 4 && + fb->modifier == I915_FORMAT_MOD_X_TILED) { + drm_WARN_ON(display->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_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(plane_state); + + return 0; +} + +static u32 i8xx_plane_surf_offset(const struct intel_plane_state *plane_state) +{ + int x = plane_state->view.color_plane[0].x; + int y = plane_state->view.color_plane[0].y; + + return intel_fb_xy_to_linear(x, y, plane_state, 0); +} + +u32 i965_plane_surf_offset(const struct intel_plane_state *plane_state) +{ + return plane_state->view.color_plane[0].offset; +} + +static u32 i9xx_plane_ctl_crtc(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + 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(display) < 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_dsb *dsb, + struct intel_plane *plane, + const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state) +{ + struct intel_display *display = to_intel_display(plane); + enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; + + intel_de_write_fw(display, DSPSTRIDE(display, i9xx_plane), + plane_state->view.color_plane[0].mapping_stride); + + if (DISPLAY_VER(display) < 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(display, DSPPOS(display, i9xx_plane), + DISP_POS_Y(crtc_y) | DISP_POS_X(crtc_x)); + intel_de_write_fw(display, DSPSIZE(display, i9xx_plane), + DISP_HEIGHT(crtc_h - 1) | DISP_WIDTH(crtc_w - 1)); + } +} + +static void i9xx_plane_update_arm(struct intel_dsb *dsb, + struct intel_plane *plane, + const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state) +{ + struct intel_display *display = to_intel_display(plane); + 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; + + dspcntr = plane_state->ctl | i9xx_plane_ctl_crtc(crtc_state); + + /* see intel_plane_atomic_calc_changes() */ + if (plane->need_async_flip_toggle_wa && + crtc_state->async_flip_planes & BIT(plane->id)) + dspcntr |= DISP_ASYNC_FLIP; + + if (display->platform.cherryview && 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(display, PRIMPOS(display, i9xx_plane), + PRIM_POS_Y(crtc_y) | PRIM_POS_X(crtc_x)); + intel_de_write_fw(display, PRIMSIZE(display, i9xx_plane), + PRIM_HEIGHT(crtc_h - 1) | PRIM_WIDTH(crtc_w - 1)); + intel_de_write_fw(display, + PRIMCNSTALPHA(display, i9xx_plane), 0); + } + + if (display->platform.haswell || display->platform.broadwell) { + intel_de_write_fw(display, DSPOFFSET(display, i9xx_plane), + DISP_OFFSET_Y(y) | DISP_OFFSET_X(x)); + } else if (DISPLAY_VER(display) >= 4) { + intel_de_write_fw(display, DSPLINOFF(display, i9xx_plane), + intel_fb_xy_to_linear(x, y, plane_state, 0)); + intel_de_write_fw(display, DSPTILEOFF(display, 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(display, DSPCNTR(display, i9xx_plane), dspcntr); + + if (DISPLAY_VER(display) >= 4) + intel_de_write_fw(display, DSPSURF(display, i9xx_plane), plane_state->surf); + else + intel_de_write_fw(display, DSPADDR(display, i9xx_plane), plane_state->surf); +} + +static void i830_plane_update_arm(struct intel_dsb *dsb, + 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(dsb, plane, crtc_state, plane_state); + i9xx_plane_update_arm(dsb, plane, crtc_state, plane_state); +} + +static void i9xx_plane_disable_arm(struct intel_dsb *dsb, + struct intel_plane *plane, + const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(plane); + 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(display, DSPCNTR(display, i9xx_plane), dspcntr); + + if (DISPLAY_VER(display) >= 4) + intel_de_write_fw(display, DSPSURF(display, i9xx_plane), 0); + else + intel_de_write_fw(display, DSPADDR(display, i9xx_plane), 0); +} + +static void g4x_primary_capture_error(struct intel_crtc *crtc, + struct intel_plane *plane, + struct intel_plane_error *error) +{ + struct intel_display *display = to_intel_display(plane); + enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; + + error->ctl = intel_de_read(display, DSPCNTR(display, i9xx_plane)); + error->surf = intel_de_read(display, DSPSURF(display, i9xx_plane)); + error->surflive = intel_de_read(display, DSPSURFLIVE(display, i9xx_plane)); +} + +static void i965_plane_capture_error(struct intel_crtc *crtc, + struct intel_plane *plane, + struct intel_plane_error *error) +{ + struct intel_display *display = to_intel_display(plane); + enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; + + error->ctl = intel_de_read(display, DSPCNTR(display, i9xx_plane)); + error->surf = intel_de_read(display, DSPSURF(display, i9xx_plane)); +} + +static void i8xx_plane_capture_error(struct intel_crtc *crtc, + struct intel_plane *plane, + struct intel_plane_error *error) +{ + struct intel_display *display = to_intel_display(plane); + enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; + + error->ctl = intel_de_read(display, DSPCNTR(display, i9xx_plane)); + error->surf = intel_de_read(display, DSPADDR(display, i9xx_plane)); +} + +static void +g4x_primary_async_flip(struct intel_dsb *dsb, + struct intel_plane *plane, + const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state, + bool async_flip) +{ + struct intel_display *display = to_intel_display(plane); + u32 dspcntr = plane_state->ctl | i9xx_plane_ctl_crtc(crtc_state); + enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; + + if (async_flip) + dspcntr |= DISP_ASYNC_FLIP; + + intel_de_write_fw(display, DSPCNTR(display, i9xx_plane), dspcntr); + intel_de_write_fw(display, DSPSURF(display, i9xx_plane), plane_state->surf); +} + +static void +vlv_primary_async_flip(struct intel_dsb *dsb, + struct intel_plane *plane, + const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state, + bool async_flip) +{ + struct intel_display *display = to_intel_display(plane); + enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; + + intel_de_write_fw(display, DSPADDR_VLV(display, i9xx_plane), plane_state->surf); +} + +static void +bdw_primary_enable_flip_done(struct intel_plane *plane) +{ + struct intel_display *display = to_intel_display(plane); + enum pipe pipe = plane->pipe; + + spin_lock_irq(&display->irq.lock); + bdw_enable_pipe_irq(display, pipe, GEN8_PIPE_PRIMARY_FLIP_DONE); + spin_unlock_irq(&display->irq.lock); +} + +static void +bdw_primary_disable_flip_done(struct intel_plane *plane) +{ + struct intel_display *display = to_intel_display(plane); + enum pipe pipe = plane->pipe; + + spin_lock_irq(&display->irq.lock); + bdw_disable_pipe_irq(display, pipe, GEN8_PIPE_PRIMARY_FLIP_DONE); + spin_unlock_irq(&display->irq.lock); +} + +static void +ivb_primary_enable_flip_done(struct intel_plane *plane) +{ + struct intel_display *display = to_intel_display(plane); + + spin_lock_irq(&display->irq.lock); + ilk_enable_display_irq(display, DE_PLANE_FLIP_DONE_IVB(plane->i9xx_plane)); + spin_unlock_irq(&display->irq.lock); +} + +static void +ivb_primary_disable_flip_done(struct intel_plane *plane) +{ + struct intel_display *display = to_intel_display(plane); + + spin_lock_irq(&display->irq.lock); + ilk_disable_display_irq(display, DE_PLANE_FLIP_DONE_IVB(plane->i9xx_plane)); + spin_unlock_irq(&display->irq.lock); +} + +static void +ilk_primary_enable_flip_done(struct intel_plane *plane) +{ + struct intel_display *display = to_intel_display(plane); + + spin_lock_irq(&display->irq.lock); + ilk_enable_display_irq(display, DE_PLANE_FLIP_DONE(plane->i9xx_plane)); + spin_unlock_irq(&display->irq.lock); +} + +static void +ilk_primary_disable_flip_done(struct intel_plane *plane) +{ + struct intel_display *display = to_intel_display(plane); + + spin_lock_irq(&display->irq.lock); + ilk_disable_display_irq(display, DE_PLANE_FLIP_DONE(plane->i9xx_plane)); + spin_unlock_irq(&display->irq.lock); +} + +static void +vlv_primary_enable_flip_done(struct intel_plane *plane) +{ + struct intel_display *display = to_intel_display(plane); + enum pipe pipe = plane->pipe; + + spin_lock_irq(&display->irq.lock); + i915_enable_pipestat(display, pipe, PLANE_FLIP_DONE_INT_STATUS_VLV); + spin_unlock_irq(&display->irq.lock); +} + +static void +vlv_primary_disable_flip_done(struct intel_plane *plane) +{ + struct intel_display *display = to_intel_display(plane); + enum pipe pipe = plane->pipe; + + spin_lock_irq(&display->irq.lock); + i915_disable_pipestat(display, pipe, PLANE_FLIP_DONE_INT_STATUS_VLV); + spin_unlock_irq(&display->irq.lock); +} + +static bool i9xx_plane_can_async_flip(u64 modifier) +{ + return modifier == I915_FORMAT_MOD_X_TILED; +} + +static bool i9xx_plane_get_hw_state(struct intel_plane *plane, + enum pipe *pipe) +{ + struct intel_display *display = to_intel_display(plane); + enum intel_display_power_domain power_domain; + enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; + struct ref_tracker *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(display, power_domain); + if (!wakeref) + return false; + + val = intel_de_read(display, DSPCNTR(display, i9xx_plane)); + + ret = val & DISP_ENABLE; + + if (DISPLAY_VER(display) >= 5) + *pipe = plane->pipe; + else + *pipe = REG_FIELD_GET(DISP_PIPE_SEL_MASK, val); + + intel_display_power_put(display, power_domain, wakeref); + + return ret; +} + +static unsigned int +hsw_primary_max_stride(struct intel_plane *plane, + const struct drm_format_info *info, + u64 modifier, unsigned int rotation) +{ + 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, + const struct drm_format_info *info, + u64 modifier, unsigned int rotation) +{ + 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, + const struct drm_format_info *info, + u64 modifier, unsigned int rotation) +{ + 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 +i915_plane_max_stride(struct intel_plane *plane, + const struct drm_format_info *info, + u64 modifier, unsigned int rotation) +{ + if (modifier == I915_FORMAT_MOD_X_TILED) + return 8 * 1024; + else + return 16 * 1024; +} + +static unsigned int +i8xx_plane_max_stride(struct intel_plane *plane, + const struct drm_format_info *info, + u64 modifier, unsigned int rotation) +{ + if (plane->i9xx_plane == PLANE_C) + return 4 * 1024; + else + return 8 * 1024; +} + +unsigned int vlv_plane_min_alignment(struct intel_plane *plane, + const struct drm_framebuffer *fb, + int color_plane) +{ + struct intel_display *display = to_intel_display(plane); + + if (intel_plane_can_async_flip(plane, fb->format, fb->modifier)) + return 256 * 1024; + + /* FIXME undocumented so not sure what's actually needed */ + if (intel_scanout_needs_vtd_wa(display)) + return 256 * 1024; + + switch (fb->modifier) { + case I915_FORMAT_MOD_X_TILED: + return 4 * 1024; + case DRM_FORMAT_MOD_LINEAR: + return 128 * 1024; + default: + MISSING_CASE(fb->modifier); + return 0; + } +} + +static unsigned int g4x_primary_min_alignment(struct intel_plane *plane, + const struct drm_framebuffer *fb, + int color_plane) +{ + struct intel_display *display = to_intel_display(plane); + + if (intel_plane_can_async_flip(plane, fb->format, fb->modifier)) + return 256 * 1024; + + if (intel_scanout_needs_vtd_wa(display)) + return 256 * 1024; + + switch (fb->modifier) { + case I915_FORMAT_MOD_X_TILED: + case DRM_FORMAT_MOD_LINEAR: + return 4 * 1024; + default: + MISSING_CASE(fb->modifier); + return 0; + } +} + +static unsigned int i965_plane_min_alignment(struct intel_plane *plane, + const struct drm_framebuffer *fb, + int color_plane) +{ + switch (fb->modifier) { + case I915_FORMAT_MOD_X_TILED: + return 4 * 1024; + case DRM_FORMAT_MOD_LINEAR: + return 128 * 1024; + default: + MISSING_CASE(fb->modifier); + return 0; + } +} + +static unsigned int i9xx_plane_min_alignment(struct intel_plane *plane, + const struct drm_framebuffer *fb, + int color_plane) +{ + return 0; +} + +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, + .format_mod_supported_async = intel_plane_format_mod_supported_async, +}; + +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, + .format_mod_supported_async = intel_plane_format_mod_supported_async, +}; + +static void i9xx_disable_tiling(struct intel_plane *plane) +{ + struct intel_display *display = to_intel_display(plane); + enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; + u32 dspcntr; + u32 reg; + + dspcntr = intel_de_read_fw(display, DSPCNTR(display, i9xx_plane)); + dspcntr &= ~DISP_TILED; + intel_de_write_fw(display, DSPCNTR(display, i9xx_plane), dspcntr); + + if (DISPLAY_VER(display) >= 4) { + reg = intel_de_read_fw(display, DSPSURF(display, i9xx_plane)); + intel_de_write_fw(display, DSPSURF(display, i9xx_plane), reg); + + } else { + reg = intel_de_read_fw(display, DSPADDR(display, i9xx_plane)); + intel_de_write_fw(display, DSPADDR(display, i9xx_plane), reg); + } +} + +struct intel_plane * +intel_primary_plane_create(struct intel_display *display, 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(display) && DISPLAY_VER(display) < 4 && + INTEL_NUM_PIPES(display) == 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(display, plane->i9xx_plane), plane); + + if (display->platform.valleyview || display->platform.cherryview) { + formats = vlv_primary_formats; + num_formats = ARRAY_SIZE(vlv_primary_formats); + } else if (DISPLAY_VER(display) >= 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 (display->platform.ivybridge) { + 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(display) >= 4) + plane_funcs = &i965_plane_funcs; + else + plane_funcs = &i8xx_plane_funcs; + + if (display->platform.valleyview || display->platform.cherryview) + plane->min_cdclk = vlv_plane_min_cdclk; + else if (display->platform.broadwell || display->platform.haswell) + plane->min_cdclk = hsw_plane_min_cdclk; + else if (display->platform.ivybridge) + plane->min_cdclk = ivb_plane_min_cdclk; + else + plane->min_cdclk = i9xx_plane_min_cdclk; + + if (HAS_GMCH(display)) { + if (DISPLAY_VER(display) >= 4) + plane->max_stride = i965_plane_max_stride; + else if (DISPLAY_VER(display) == 3) + plane->max_stride = i915_plane_max_stride; + else + plane->max_stride = i8xx_plane_max_stride; + } else { + if (display->platform.broadwell || display->platform.haswell) + plane->max_stride = hsw_primary_max_stride; + else + plane->max_stride = ilk_primary_max_stride; + } + + if (display->platform.valleyview || display->platform.cherryview) + plane->min_alignment = vlv_plane_min_alignment; + else if (DISPLAY_VER(display) >= 5 || display->platform.g4x) + plane->min_alignment = g4x_primary_min_alignment; + else if (DISPLAY_VER(display) == 4) + plane->min_alignment = i965_plane_min_alignment; + else + plane->min_alignment = i9xx_plane_min_alignment; + + /* FIXME undocumented for VLV/CHV so not sure what's actually needed */ + if (intel_scanout_needs_vtd_wa(display)) + plane->vtd_guard = 128; + + if (display->platform.i830 || display->platform.i845g) { + 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 (DISPLAY_VER(display) >= 4) + plane->surf_offset = i965_plane_surf_offset; + else + plane->surf_offset = i8xx_plane_surf_offset; + + if (DISPLAY_VER(display) >= 5 || display->platform.g4x) + plane->capture_error = g4x_primary_capture_error; + else if (DISPLAY_VER(display) >= 4) + plane->capture_error = i965_plane_capture_error; + else + plane->capture_error = i8xx_plane_capture_error; + + if (HAS_ASYNC_FLIPS(display)) { + if (display->platform.valleyview || display->platform.cherryview) { + 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; + plane->can_async_flip = i9xx_plane_can_async_flip; + } else if (display->platform.broadwell) { + plane->need_async_flip_toggle_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; + plane->can_async_flip = i9xx_plane_can_async_flip; + } else if (DISPLAY_VER(display) >= 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; + plane->can_async_flip = i9xx_plane_can_async_flip; + } else if (DISPLAY_VER(display) >= 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; + plane->can_async_flip = i9xx_plane_can_async_flip; + } + } + + plane->disable_tiling = i9xx_disable_tiling; + + modifiers = intel_fb_plane_get_modifiers(display, INTEL_PLANE_CAP_TILING_X); + + if (DISPLAY_VER(display) >= 5 || display->platform.g4x) + ret = drm_universal_plane_init(display->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(display->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 (display->platform.cherryview && pipe == PIPE_B) { + supported_rotations = + DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_180 | + DRM_MODE_REFLECT_X; + } else if (DISPLAY_VER(display) >= 4) { + supported_rotations = + DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_180; + } else { + supported_rotations = DRM_MODE_ROTATE_0; + } + + if (DISPLAY_VER(display) >= 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 intel_display *display = to_intel_display(crtc); + 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(display->drm, pipe != crtc->pipe); + + intel_fb = intel_framebuffer_alloc(); + if (!intel_fb) { + drm_dbg_kms(display->drm, "failed to alloc fb\n"); + return; + } + + fb = &intel_fb->base; + + fb->dev = display->drm; + + val = intel_de_read(display, DSPCNTR(display, i9xx_plane)); + + if (DISPLAY_VER(display) >= 4) { + if (val & DISP_TILED) + fb->modifier = I915_FORMAT_MOD_X_TILED; + + if (val & DISP_ROTATE_180) + plane_config->rotation = DRM_MODE_ROTATE_180; + } + + if (display->platform.cherryview && + 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_get_format_info(display->drm, fourcc, fb->modifier); + + if (display->platform.haswell || display->platform.broadwell) { + offset = intel_de_read(display, + DSPOFFSET(display, i9xx_plane)); + base = intel_de_read(display, DSPSURF(display, i9xx_plane)) & DISP_ADDR_MASK; + } else if (DISPLAY_VER(display) >= 4) { + if (fb->modifier == I915_FORMAT_MOD_X_TILED) + offset = intel_de_read(display, + DSPTILEOFF(display, i9xx_plane)); + else + offset = intel_de_read(display, + DSPLINOFF(display, i9xx_plane)); + base = intel_de_read(display, DSPSURF(display, i9xx_plane)) & DISP_ADDR_MASK; + } else { + offset = 0; + base = intel_de_read(display, DSPADDR(display, i9xx_plane)); + } + plane_config->base = base; + + drm_WARN_ON(display->drm, offset != 0); + + val = intel_de_read(display, PIPESRC(display, 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(display, DSPSTRIDE(display, 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(display->drm, + "[CRTC:%d:%s][PLANE:%d:%s] with fb: size=%dx%d@%d, offset=%x, pitch %d, size 0x%x\n", + crtc->base.base.id, crtc->base.name, + plane->base.base.id, plane->base.name, + fb->width, fb->height, fb->format->cpp[0] * 8, + base, fb->pitches[0], plane_config->size); + + plane_config->fb = &intel_fb->base; +} + +bool i9xx_fixup_initial_plane_config(struct intel_crtc *crtc, + const struct intel_initial_plane_config *plane_config) +{ + struct intel_display *display = to_intel_display(crtc); + struct intel_plane *plane = to_intel_plane(crtc->base.primary); + const struct intel_plane_state *plane_state = + to_intel_plane_state(plane->base.state); + enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; + + if (!plane_state->uapi.visible) + return false; + + /* + * We may have moved the surface to a different + * part of ggtt, make the plane aware of that. + */ + if (plane_config->base == plane_state->surf) + return false; + + if (DISPLAY_VER(display) >= 4) + intel_de_write(display, DSPSURF(display, i9xx_plane), plane_state->surf); + else + intel_de_write(display, DSPADDR(display, i9xx_plane), plane_state->surf); + + return true; +} 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..ec78bf4dd --- /dev/null +++ b/drivers/gpu/drm/i915/display/i9xx_plane.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2020 Intel Corporation + */ + +#ifndef _I9XX_PLANE_H_ +#define _I9XX_PLANE_H_ + +#include + +enum pipe; +struct drm_format_info; +struct drm_framebuffer; +struct intel_crtc; +struct intel_display; +struct intel_initial_plane_config; +struct intel_plane; +struct intel_plane_state; + +#ifdef I915 +unsigned int i965_plane_max_stride(struct intel_plane *plane, + const struct drm_format_info *info, + u64 modifier, unsigned int rotation); +unsigned int vlv_plane_min_alignment(struct intel_plane *plane, + const struct drm_framebuffer *fb, + int colot_plane); +int i9xx_check_plane_surface(struct intel_plane_state *plane_state); +u32 i965_plane_surf_offset(const struct intel_plane_state *plane_state); + +struct intel_plane * +intel_primary_plane_create(struct intel_display *display, enum pipe pipe); + +void i9xx_get_initial_plane_config(struct intel_crtc *crtc, + struct intel_initial_plane_config *plane_config); +bool i9xx_fixup_initial_plane_config(struct intel_crtc *crtc, + const struct intel_initial_plane_config *plane_config); +#else +static inline unsigned int i965_plane_max_stride(struct intel_plane *plane, + u32 pixel_format, u64 modifier, + unsigned int rotation) +{ + return 0; +} +static inline int i9xx_check_plane_surface(struct intel_plane_state *plane_state) +{ + return 0; +} +static inline struct intel_plane * +intel_primary_plane_create(struct intel_display *display, int pipe) +{ + return NULL; +} +static inline void i9xx_get_initial_plane_config(struct intel_crtc *crtc, + struct intel_initial_plane_config *plane_config) +{ +} +static inline bool i9xx_fixup_initial_plane_config(struct intel_crtc *crtc, + const struct intel_initial_plane_config *plane_config) +{ + return false; +} +#endif + +#endif diff --git a/drivers/gpu/drm/i915/display/i9xx_plane_regs.h b/drivers/gpu/drm/i915/display/i9xx_plane_regs.h new file mode 100644 index 000000000..5d7ba824f --- /dev/null +++ b/drivers/gpu/drm/i915/display/i9xx_plane_regs.h @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2024 Intel Corporation + */ + +#ifndef __I9XX_PLANE_REGS_H__ +#define __I9XX_PLANE_REGS_H__ + +#include "intel_display_reg_defs.h" + +#define _DSPAADDR_VLV 0x7017C /* vlv/chv */ +#define DSPADDR_VLV(dev_priv, plane) _MMIO_PIPE2(dev_priv, plane, _DSPAADDR_VLV) + +#define _DSPACNTR 0x70180 +#define DSPCNTR(dev_priv, plane) _MMIO_PIPE2(dev_priv, plane, _DSPACNTR) +#define DISP_ENABLE REG_BIT(31) +#define DISP_PIPE_GAMMA_ENABLE REG_BIT(30) +#define DISP_FORMAT_MASK REG_GENMASK(29, 26) +#define DISP_FORMAT_8BPP REG_FIELD_PREP(DISP_FORMAT_MASK, 2) +#define DISP_FORMAT_BGRA555 REG_FIELD_PREP(DISP_FORMAT_MASK, 3) +#define DISP_FORMAT_BGRX555 REG_FIELD_PREP(DISP_FORMAT_MASK, 4) +#define DISP_FORMAT_BGRX565 REG_FIELD_PREP(DISP_FORMAT_MASK, 5) +#define DISP_FORMAT_BGRX888 REG_FIELD_PREP(DISP_FORMAT_MASK, 6) +#define DISP_FORMAT_BGRA888 REG_FIELD_PREP(DISP_FORMAT_MASK, 7) +#define DISP_FORMAT_RGBX101010 REG_FIELD_PREP(DISP_FORMAT_MASK, 8) +#define DISP_FORMAT_RGBA101010 REG_FIELD_PREP(DISP_FORMAT_MASK, 9) +#define DISP_FORMAT_BGRX101010 REG_FIELD_PREP(DISP_FORMAT_MASK, 10) +#define DISP_FORMAT_BGRA101010 REG_FIELD_PREP(DISP_FORMAT_MASK, 11) +#define DISP_FORMAT_RGBX161616 REG_FIELD_PREP(DISP_FORMAT_MASK, 12) +#define DISP_FORMAT_RGBX888 REG_FIELD_PREP(DISP_FORMAT_MASK, 14) +#define DISP_FORMAT_RGBA888 REG_FIELD_PREP(DISP_FORMAT_MASK, 15) +#define DISP_STEREO_ENABLE REG_BIT(25) +#define DISP_PIPE_CSC_ENABLE REG_BIT(24) /* ilk+ */ +#define DISP_PIPE_SEL_MASK REG_GENMASK(25, 24) +#define DISP_PIPE_SEL(pipe) REG_FIELD_PREP(DISP_PIPE_SEL_MASK, (pipe)) +#define DISP_SRC_KEY_ENABLE REG_BIT(22) +#define DISP_LINE_DOUBLE REG_BIT(20) +#define DISP_STEREO_POLARITY_SECOND REG_BIT(18) +#define DISP_ALPHA_PREMULTIPLY REG_BIT(16) /* CHV pipe B */ +#define DISP_ROTATE_180 REG_BIT(15) /* i965+ */ +#define DISP_ALPHA_TRANS_ENABLE REG_BIT(15) /* pre-g4x plane B */ +#define DISP_TRICKLE_FEED_DISABLE REG_BIT(14) /* g4x+ */ +#define DISP_TILED REG_BIT(10) /* i965+ */ +#define DISP_ASYNC_FLIP REG_BIT(9) /* g4x+ */ +#define DISP_MIRROR REG_BIT(8) /* CHV pipe B */ +#define DISP_SPRITE_ABOVE_OVERLAY REG_BIT(0) /* pre-g4x plane B/C */ + +#define _DSPAADDR 0x70184 /* pre-i965 */ +#define DSPADDR(dev_priv, plane) _MMIO_PIPE2(dev_priv, plane, _DSPAADDR) + +#define _DSPALINOFF 0x70184 /* i965+ */ +#define DSPLINOFF(dev_priv, plane) _MMIO_PIPE2(dev_priv, plane, _DSPALINOFF) + +#define _DSPASTRIDE 0x70188 +#define DSPSTRIDE(dev_priv, plane) _MMIO_PIPE2(dev_priv, plane, _DSPASTRIDE) + +#define _DSPAPOS 0x7018C /* pre-g4x */ +#define DSPPOS(dev_priv, plane) _MMIO_PIPE2(dev_priv, plane, _DSPAPOS) +#define DISP_POS_Y_MASK REG_GENMASK(31, 16) +#define DISP_POS_Y(y) REG_FIELD_PREP(DISP_POS_Y_MASK, (y)) +#define DISP_POS_X_MASK REG_GENMASK(15, 0) +#define DISP_POS_X(x) REG_FIELD_PREP(DISP_POS_X_MASK, (x)) + +#define _DSPASIZE 0x70190 /* pre-g4x */ +#define DSPSIZE(dev_priv, plane) _MMIO_PIPE2(dev_priv, plane, _DSPASIZE) +#define DISP_HEIGHT_MASK REG_GENMASK(31, 16) +#define DISP_HEIGHT(h) REG_FIELD_PREP(DISP_HEIGHT_MASK, (h)) +#define DISP_WIDTH_MASK REG_GENMASK(15, 0) +#define DISP_WIDTH(w) REG_FIELD_PREP(DISP_WIDTH_MASK, (w)) + +#define _DSPASURF 0x7019C /* i965+ */ +#define DSPSURF(dev_priv, plane) _MMIO_PIPE2(dev_priv, plane, _DSPASURF) +#define DISP_ADDR_MASK REG_GENMASK(31, 12) + +#define _DSPATILEOFF 0x701A4 /* i965+ */ +#define DSPTILEOFF(dev_priv, plane) _MMIO_PIPE2(dev_priv, plane, _DSPATILEOFF) +#define DISP_OFFSET_Y_MASK REG_GENMASK(31, 16) +#define DISP_OFFSET_Y(y) REG_FIELD_PREP(DISP_OFFSET_Y_MASK, (y)) +#define DISP_OFFSET_X_MASK REG_GENMASK(15, 0) +#define DISP_OFFSET_X(x) REG_FIELD_PREP(DISP_OFFSET_X_MASK, (x)) + +#define _DSPAOFFSET 0x701A4 /* hsw+ */ +#define DSPOFFSET(dev_priv, plane) _MMIO_PIPE2(dev_priv, plane, _DSPAOFFSET) + +#define _DSPASURFLIVE 0x701AC /* g4x+ */ +#define DSPSURFLIVE(dev_priv, plane) _MMIO_PIPE2(dev_priv, plane, _DSPASURFLIVE) + +#define _DSPAGAMC 0x701E0 /* pre-g4x */ +#define DSPGAMC(dev_priv, plane, i) _MMIO_PIPE2(dev_priv, plane, _DSPAGAMC + (5 - (i)) * 4) /* plane C only, 6 x u0.8 */ + +/* CHV pipe B primary plane */ +#define _PRIMPOS_A 0x60a08 +#define PRIMPOS(dev_priv, plane) _MMIO_TRANS2(dev_priv, plane, _PRIMPOS_A) +#define PRIM_POS_Y_MASK REG_GENMASK(31, 16) +#define PRIM_POS_Y(y) REG_FIELD_PREP(PRIM_POS_Y_MASK, (y)) +#define PRIM_POS_X_MASK REG_GENMASK(15, 0) +#define PRIM_POS_X(x) REG_FIELD_PREP(PRIM_POS_X_MASK, (x)) + +#define _PRIMSIZE_A 0x60a0c +#define PRIMSIZE(dev_priv, plane) _MMIO_TRANS2(dev_priv, plane, _PRIMSIZE_A) +#define PRIM_HEIGHT_MASK REG_GENMASK(31, 16) +#define PRIM_HEIGHT(h) REG_FIELD_PREP(PRIM_HEIGHT_MASK, (h)) +#define PRIM_WIDTH_MASK REG_GENMASK(15, 0) +#define PRIM_WIDTH(w) REG_FIELD_PREP(PRIM_WIDTH_MASK, (w)) + +#define _PRIMCNSTALPHA_A 0x60a10 +#define PRIMCNSTALPHA(dev_priv, plane) _MMIO_TRANS2(dev_priv, plane, _PRIMCNSTALPHA_A) +#define PRIM_CONST_ALPHA_ENABLE REG_BIT(31) +#define PRIM_CONST_ALPHA_MASK REG_GENMASK(7, 0) +#define PRIM_CONST_ALPHA(alpha) REG_FIELD_PREP(PRIM_CONST_ALPHA_MASK, (alpha)) + +#endif /* __I9XX_PLANE_REGS_H__ */ diff --git a/drivers/gpu/drm/i915/display/i9xx_wm.c b/drivers/gpu/drm/i915/display/i9xx_wm.c new file mode 100644 index 000000000..86d1c9f7f --- /dev/null +++ b/drivers/gpu/drm/i915/display/i9xx_wm.c @@ -0,0 +1,4185 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2023 Intel Corporation + */ + +#include + +#include +#include + +#include "i9xx_wm.h" +#include "i9xx_wm_regs.h" +#include "intel_atomic.h" +#include "intel_bo.h" +#include "intel_de.h" +#include "intel_display.h" +#include "intel_display_regs.h" +#include "intel_display_trace.h" +#include "intel_display_utils.h" +#include "intel_dram.h" +#include "intel_fb.h" +#include "intel_mchbar.h" +#include "intel_wm.h" +#include "skl_watermark.h" +#include "vlv_sideband.h" + +struct intel_watermark_params { + u16 fifo_size; + u16 max_wm; + u8 default_wm; + u8 guard_size; + u8 cacheline_size; +}; + +/* used in computing the new watermarks state */ +struct intel_wm_config { + unsigned int num_pipes_active; + bool sprites_enabled; + bool sprites_scaled; +}; + +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; +}; + +static const struct cxsr_latency cxsr_latency_table[] = { + {1, 0, 800, 400, 3382, 33382, 3983, 33983}, /* DDR2-400 SC */ + {1, 0, 800, 667, 3354, 33354, 3807, 33807}, /* DDR2-667 SC */ + {1, 0, 800, 800, 3347, 33347, 3763, 33763}, /* DDR2-800 SC */ + {1, 1, 800, 667, 6420, 36420, 6873, 36873}, /* DDR3-667 SC */ + {1, 1, 800, 800, 5902, 35902, 6318, 36318}, /* DDR3-800 SC */ + + {1, 0, 667, 400, 3400, 33400, 4021, 34021}, /* DDR2-400 SC */ + {1, 0, 667, 667, 3372, 33372, 3845, 33845}, /* DDR2-667 SC */ + {1, 0, 667, 800, 3386, 33386, 3822, 33822}, /* DDR2-800 SC */ + {1, 1, 667, 667, 6438, 36438, 6911, 36911}, /* DDR3-667 SC */ + {1, 1, 667, 800, 5941, 35941, 6377, 36377}, /* DDR3-800 SC */ + + {1, 0, 400, 400, 3472, 33472, 4173, 34173}, /* DDR2-400 SC */ + {1, 0, 400, 667, 3443, 33443, 3996, 33996}, /* DDR2-667 SC */ + {1, 0, 400, 800, 3430, 33430, 3946, 33946}, /* DDR2-800 SC */ + {1, 1, 400, 667, 6509, 36509, 7062, 37062}, /* DDR3-667 SC */ + {1, 1, 400, 800, 5985, 35985, 6501, 36501}, /* DDR3-800 SC */ + + {0, 0, 800, 400, 3438, 33438, 4065, 34065}, /* DDR2-400 SC */ + {0, 0, 800, 667, 3410, 33410, 3889, 33889}, /* DDR2-667 SC */ + {0, 0, 800, 800, 3403, 33403, 3845, 33845}, /* DDR2-800 SC */ + {0, 1, 800, 667, 6476, 36476, 6955, 36955}, /* DDR3-667 SC */ + {0, 1, 800, 800, 5958, 35958, 6400, 36400}, /* DDR3-800 SC */ + + {0, 0, 667, 400, 3456, 33456, 4103, 34106}, /* DDR2-400 SC */ + {0, 0, 667, 667, 3428, 33428, 3927, 33927}, /* DDR2-667 SC */ + {0, 0, 667, 800, 3443, 33443, 3905, 33905}, /* DDR2-800 SC */ + {0, 1, 667, 667, 6494, 36494, 6993, 36993}, /* DDR3-667 SC */ + {0, 1, 667, 800, 5998, 35998, 6460, 36460}, /* DDR3-800 SC */ + + {0, 0, 400, 400, 3528, 33528, 4255, 34255}, /* DDR2-400 SC */ + {0, 0, 400, 667, 3500, 33500, 4079, 34079}, /* DDR2-667 SC */ + {0, 0, 400, 800, 3487, 33487, 4029, 34029}, /* DDR2-800 SC */ + {0, 1, 400, 667, 6566, 36566, 7145, 37145}, /* DDR3-667 SC */ + {0, 1, 400, 800, 6042, 36042, 6584, 36584}, /* DDR3-800 SC */ +}; + +static const struct cxsr_latency *pnv_get_cxsr_latency(struct intel_display *display) +{ + const struct dram_info *dram_info = intel_dram_info(display); + bool is_ddr3 = dram_info->type == INTEL_DRAM_DDR3; + int i; + + for (i = 0; i < ARRAY_SIZE(cxsr_latency_table); i++) { + const struct cxsr_latency *latency = &cxsr_latency_table[i]; + bool is_desktop = !display->platform.mobile; + + if (is_desktop == latency->is_desktop && + is_ddr3 == latency->is_ddr3 && + DIV_ROUND_CLOSEST(dram_info->fsb_freq, 1000) == latency->fsb_freq && + DIV_ROUND_CLOSEST(dram_info->mem_freq, 1000) == latency->mem_freq) + return latency; + } + + drm_dbg_kms(display->drm, + "Could not find CxSR latency for %s, FSB %u kHz, MEM %u kHz\n", + intel_dram_type_str(dram_info->type), + dram_info->fsb_freq, dram_info->mem_freq); + + return NULL; +} + +static void chv_set_memory_dvfs(struct intel_display *display, bool enable) +{ + u32 val; + int ret; + + vlv_punit_get(display); + + val = vlv_punit_read(display, PUNIT_REG_DDR_SETUP2); + if (enable) + val &= ~FORCE_DDR_HIGH_FREQ; + else + val |= FORCE_DDR_HIGH_FREQ; + val &= ~FORCE_DDR_LOW_FREQ; + val |= FORCE_DDR_FREQ_REQ_ACK; + vlv_punit_write(display, PUNIT_REG_DDR_SETUP2, val); + + ret = poll_timeout_us(val = vlv_punit_read(display, PUNIT_REG_DDR_SETUP2), + (val & FORCE_DDR_FREQ_REQ_ACK) == 0, + 500, 3000, false); + if (ret) + drm_err(display->drm, + "timed out waiting for Punit DDR DVFS request\n"); + + vlv_punit_put(display); +} + +static void chv_set_memory_pm5(struct intel_display *display, bool enable) +{ + u32 val; + + vlv_punit_get(display); + + val = vlv_punit_read(display, PUNIT_REG_DSPSSPM); + if (enable) + val |= DSP_MAXFIFO_PM5_ENABLE; + else + val &= ~DSP_MAXFIFO_PM5_ENABLE; + vlv_punit_write(display, PUNIT_REG_DSPSSPM, val); + + vlv_punit_put(display); +} + +#define FW_WM(value, plane) \ + (((value) << DSPFW_ ## plane ## _SHIFT) & DSPFW_ ## plane ## _MASK) + +static bool _intel_set_memory_cxsr(struct intel_display *display, bool enable) +{ + bool was_enabled; + u32 val; + + if (display->platform.valleyview || display->platform.cherryview) { + was_enabled = intel_de_read(display, FW_BLC_SELF_VLV) & FW_CSPWRDWNEN; + intel_de_write(display, FW_BLC_SELF_VLV, enable ? FW_CSPWRDWNEN : 0); + intel_de_posting_read(display, FW_BLC_SELF_VLV); + } else if (display->platform.g4x || display->platform.i965gm) { + was_enabled = intel_de_read(display, FW_BLC_SELF) & FW_BLC_SELF_EN; + intel_de_write(display, FW_BLC_SELF, enable ? FW_BLC_SELF_EN : 0); + intel_de_posting_read(display, FW_BLC_SELF); + } else if (display->platform.pineview) { + val = intel_de_read(display, DSPFW3(display)); + was_enabled = val & PINEVIEW_SELF_REFRESH_EN; + if (enable) + val |= PINEVIEW_SELF_REFRESH_EN; + else + val &= ~PINEVIEW_SELF_REFRESH_EN; + intel_de_write(display, DSPFW3(display), val); + intel_de_posting_read(display, DSPFW3(display)); + } else if (display->platform.i945g || display->platform.i945gm) { + was_enabled = intel_de_read(display, FW_BLC_SELF) & FW_BLC_SELF_EN; + val = enable ? REG_MASKED_FIELD_ENABLE(FW_BLC_SELF_EN) : + REG_MASKED_FIELD_DISABLE(FW_BLC_SELF_EN); + intel_de_write(display, FW_BLC_SELF, val); + intel_de_posting_read(display, FW_BLC_SELF); + } else if (display->platform.i915gm) { + /* + * FIXME can't find a bit like this for 915G, and + * yet it does have the related watermark in + * FW_BLC_SELF. What's going on? + */ + was_enabled = intel_de_read(display, INSTPM) & INSTPM_SELF_EN; + val = enable ? REG_MASKED_FIELD_ENABLE(INSTPM_SELF_EN) : + REG_MASKED_FIELD_DISABLE(INSTPM_SELF_EN); + intel_de_write(display, INSTPM, val); + intel_de_posting_read(display, INSTPM); + } else { + return false; + } + + trace_intel_memory_cxsr(display, was_enabled, enable); + + drm_dbg_kms(display->drm, "memory self-refresh is %s (was %s)\n", + str_enabled_disabled(enable), + str_enabled_disabled(was_enabled)); + + return was_enabled; +} + +/** + * intel_set_memory_cxsr - Configure CxSR state + * @display: display device + * @enable: Allow vs. disallow CxSR + * + * Allow or disallow the system to enter a special CxSR + * (C-state self refresh) state. What typically happens in CxSR mode + * is that several display FIFOs may get combined into a single larger + * FIFO for a particular plane (so called max FIFO mode) to allow the + * system to defer memory fetches longer, and the memory will enter + * self refresh. + * + * Note that enabling CxSR does not guarantee that the system enter + * this special mode, nor does it guarantee that the system stays + * in that mode once entered. So this just allows/disallows the system + * to autonomously utilize the CxSR mode. Other factors such as core + * C-states will affect when/if the system actually enters/exits the + * CxSR mode. + * + * Note that on VLV/CHV this actually only controls the max FIFO mode, + * and the system is free to enter/exit memory self refresh at any time + * even when the use of CxSR has been disallowed. + * + * While the system is actually in the CxSR/max FIFO mode, some plane + * control registers will not get latched on vblank. Thus in order to + * guarantee the system will respond to changes in the plane registers + * we must always disallow CxSR prior to making changes to those registers. + * Unfortunately the system will re-evaluate the CxSR conditions at + * frame start which happens after vblank start (which is when the plane + * registers would get latched), so we can't proceed with the plane update + * during the same frame where we disallowed CxSR. + * + * Certain platforms also have a deeper HPLL SR mode. Fortunately the + * HPLL SR mode depends on CxSR itself, so we don't have to hand hold + * the hardware w.r.t. HPLL SR when writing to plane registers. + * Disallowing just CxSR is sufficient. + */ +bool intel_set_memory_cxsr(struct intel_display *display, bool enable) +{ + bool ret; + + mutex_lock(&display->wm.wm_mutex); + ret = _intel_set_memory_cxsr(display, enable); + if (display->platform.valleyview || display->platform.cherryview) + display->wm.vlv.cxsr = enable; + else if (display->platform.g4x) + display->wm.g4x.cxsr = enable; + mutex_unlock(&display->wm.wm_mutex); + + return ret; +} + +/* + * Latency for FIFO fetches is dependent on several factors: + * - memory configuration (speed, channels) + * - chipset + * - current MCH state + * It can be fairly high in some situations, so here we assume a fairly + * pessimal value. It's a tradeoff between extra memory fetches (if we + * set this value too high, the FIFO will fetch frequently to stay full) + * and power consumption (set it too low to save power and we might see + * FIFO underruns and display "flicker"). + * + * A value of 5us seems to be a good balance; safe for very low end + * platforms but not overly aggressive on lower latency configs. + */ +static const int pessimal_latency_ns = 5000; + +#define VLV_FIFO_START(dsparb, dsparb2, lo_shift, hi_shift) \ + ((((dsparb) >> (lo_shift)) & 0xff) | ((((dsparb2) >> (hi_shift)) & 0x1) << 8)) + +static void vlv_get_fifo_size(struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct vlv_fifo_state *fifo_state = &crtc_state->wm.vlv.fifo_state; + enum pipe pipe = crtc->pipe; + int sprite0_start, sprite1_start; + u32 dsparb, dsparb2, dsparb3; + + switch (pipe) { + case PIPE_A: + dsparb = intel_de_read(display, DSPARB(display)); + dsparb2 = intel_de_read(display, DSPARB2); + sprite0_start = VLV_FIFO_START(dsparb, dsparb2, 0, 0); + sprite1_start = VLV_FIFO_START(dsparb, dsparb2, 8, 4); + break; + case PIPE_B: + dsparb = intel_de_read(display, DSPARB(display)); + dsparb2 = intel_de_read(display, DSPARB2); + sprite0_start = VLV_FIFO_START(dsparb, dsparb2, 16, 8); + sprite1_start = VLV_FIFO_START(dsparb, dsparb2, 24, 12); + break; + case PIPE_C: + dsparb2 = intel_de_read(display, DSPARB2); + dsparb3 = intel_de_read(display, DSPARB3); + sprite0_start = VLV_FIFO_START(dsparb3, dsparb2, 0, 16); + sprite1_start = VLV_FIFO_START(dsparb3, dsparb2, 8, 20); + break; + default: + MISSING_CASE(pipe); + return; + } + + fifo_state->plane[PLANE_PRIMARY] = sprite0_start; + fifo_state->plane[PLANE_SPRITE0] = sprite1_start - sprite0_start; + fifo_state->plane[PLANE_SPRITE1] = 511 - sprite1_start; + fifo_state->plane[PLANE_CURSOR] = 63; +} + +static int i9xx_get_fifo_size(struct intel_display *display, + enum i9xx_plane_id i9xx_plane) +{ + u32 dsparb = intel_de_read(display, DSPARB(display)); + int size; + + size = dsparb & 0x7f; + if (i9xx_plane == PLANE_B) + size = ((dsparb >> DSPARB_CSTART_SHIFT) & 0x7f) - size; + + drm_dbg_kms(display->drm, "FIFO size - (0x%08x) %c: %d\n", + dsparb, plane_name(i9xx_plane), size); + + return size; +} + +static int i830_get_fifo_size(struct intel_display *display, + enum i9xx_plane_id i9xx_plane) +{ + u32 dsparb = intel_de_read(display, DSPARB(display)); + int size; + + size = dsparb & 0x1ff; + if (i9xx_plane == PLANE_B) + size = ((dsparb >> DSPARB_BEND_SHIFT) & 0x1ff) - size; + size >>= 1; /* Convert to cachelines */ + + drm_dbg_kms(display->drm, "FIFO size - (0x%08x) %c: %d\n", + dsparb, plane_name(i9xx_plane), size); + + return size; +} + +static int i845_get_fifo_size(struct intel_display *display, + enum i9xx_plane_id i9xx_plane) +{ + u32 dsparb = intel_de_read(display, DSPARB(display)); + int size; + + size = dsparb & 0x7f; + size >>= 2; /* Convert to cachelines */ + + drm_dbg_kms(display->drm, "FIFO size - (0x%08x) %c: %d\n", + dsparb, plane_name(i9xx_plane), size); + + return size; +} + +/* Pineview has different values for various configs */ +static const struct intel_watermark_params pnv_display_wm = { + .fifo_size = PINEVIEW_DISPLAY_FIFO, + .max_wm = PINEVIEW_MAX_WM, + .default_wm = PINEVIEW_DFT_WM, + .guard_size = PINEVIEW_GUARD_WM, + .cacheline_size = PINEVIEW_FIFO_LINE_SIZE, +}; + +static const struct intel_watermark_params pnv_display_hplloff_wm = { + .fifo_size = PINEVIEW_DISPLAY_FIFO, + .max_wm = PINEVIEW_MAX_WM, + .default_wm = PINEVIEW_DFT_HPLLOFF_WM, + .guard_size = PINEVIEW_GUARD_WM, + .cacheline_size = PINEVIEW_FIFO_LINE_SIZE, +}; + +static const struct intel_watermark_params pnv_cursor_wm = { + .fifo_size = PINEVIEW_CURSOR_FIFO, + .max_wm = PINEVIEW_CURSOR_MAX_WM, + .default_wm = PINEVIEW_CURSOR_DFT_WM, + .guard_size = PINEVIEW_CURSOR_GUARD_WM, + .cacheline_size = PINEVIEW_FIFO_LINE_SIZE, +}; + +static const struct intel_watermark_params pnv_cursor_hplloff_wm = { + .fifo_size = PINEVIEW_CURSOR_FIFO, + .max_wm = PINEVIEW_CURSOR_MAX_WM, + .default_wm = PINEVIEW_CURSOR_DFT_WM, + .guard_size = PINEVIEW_CURSOR_GUARD_WM, + .cacheline_size = PINEVIEW_FIFO_LINE_SIZE, +}; + +static const struct intel_watermark_params i965_cursor_wm_info = { + .fifo_size = I965_CURSOR_FIFO, + .max_wm = I965_CURSOR_MAX_WM, + .default_wm = I965_CURSOR_DFT_WM, + .guard_size = 2, + .cacheline_size = I915_FIFO_LINE_SIZE, +}; + +static const struct intel_watermark_params i945_wm_info = { + .fifo_size = I945_FIFO_SIZE, + .max_wm = I915_MAX_WM, + .default_wm = 1, + .guard_size = 2, + .cacheline_size = I915_FIFO_LINE_SIZE, +}; + +static const struct intel_watermark_params i915_wm_info = { + .fifo_size = I915_FIFO_SIZE, + .max_wm = I915_MAX_WM, + .default_wm = 1, + .guard_size = 2, + .cacheline_size = I915_FIFO_LINE_SIZE, +}; + +static const struct intel_watermark_params i830_a_wm_info = { + .fifo_size = I855GM_FIFO_SIZE, + .max_wm = I915_MAX_WM, + .default_wm = 1, + .guard_size = 2, + .cacheline_size = I830_FIFO_LINE_SIZE, +}; + +static const struct intel_watermark_params i830_bc_wm_info = { + .fifo_size = I855GM_FIFO_SIZE, + .max_wm = I915_MAX_WM / 2, + .default_wm = 1, + .guard_size = 2, + .cacheline_size = I830_FIFO_LINE_SIZE, +}; + +static const struct intel_watermark_params i845_wm_info = { + .fifo_size = I830_FIFO_SIZE, + .max_wm = I915_MAX_WM, + .default_wm = 1, + .guard_size = 2, + .cacheline_size = I830_FIFO_LINE_SIZE, +}; + +/** + * intel_wm_method1 - Method 1 / "small buffer" watermark formula + * @pixel_rate: Pipe pixel rate in kHz + * @cpp: Plane bytes per pixel + * @latency: Memory wakeup latency in 0.1us units + * + * Compute the watermark using the method 1 or "small buffer" + * formula. The caller may additionally add extra cachelines + * to account for TLB misses and clock crossings. + * + * This method is concerned with the short term drain rate + * of the FIFO, ie. it does not account for blanking periods + * which would effectively reduce the average drain rate across + * a longer period. The name "small" refers to the fact the + * FIFO is relatively small compared to the amount of data + * fetched. + * + * The FIFO level vs. time graph might look something like: + * + * |\ |\ + * | \ | \ + * __---__---__ (- plane active, _ blanking) + * -> time + * + * or perhaps like this: + * + * |\|\ |\|\ + * __----__----__ (- plane active, _ blanking) + * -> time + * + * Returns: + * The watermark in bytes + */ +static unsigned int intel_wm_method1(unsigned int pixel_rate, + unsigned int cpp, + unsigned int latency) +{ + u64 ret; + + ret = mul_u32_u32(pixel_rate, cpp * latency); + ret = DIV_ROUND_UP_ULL(ret, 10000); + + return ret; +} + +/** + * intel_wm_method2 - Method 2 / "large buffer" watermark formula + * @pixel_rate: Pipe pixel rate in kHz + * @htotal: Pipe horizontal total + * @width: Plane width in pixels + * @cpp: Plane bytes per pixel + * @latency: Memory wakeup latency in 0.1us units + * + * Compute the watermark using the method 2 or "large buffer" + * formula. The caller may additionally add extra cachelines + * to account for TLB misses and clock crossings. + * + * This method is concerned with the long term drain rate + * of the FIFO, ie. it does account for blanking periods + * which effectively reduce the average drain rate across + * a longer period. The name "large" refers to the fact the + * FIFO is relatively large compared to the amount of data + * fetched. + * + * The FIFO level vs. time graph might look something like: + * + * |\___ |\___ + * | \___ | \___ + * | \ | \ + * __ --__--__--__--__--__--__ (- plane active, _ blanking) + * -> time + * + * Returns: + * The watermark in bytes + */ +static unsigned int intel_wm_method2(unsigned int pixel_rate, + unsigned int htotal, + unsigned int width, + unsigned int cpp, + unsigned int latency) +{ + unsigned int ret; + + /* + * FIXME remove once all users are computing + * watermarks in the correct place. + */ + if (WARN_ON_ONCE(htotal == 0)) + htotal = 1; + + ret = (latency * pixel_rate) / (htotal * 10000); + ret = (ret + 1) * width * cpp; + + return ret; +} + +/** + * intel_calculate_wm - calculate watermark level + * @display: display device + * @pixel_rate: pixel clock + * @wm: chip FIFO params + * @fifo_size: size of the FIFO buffer + * @cpp: bytes per pixel + * @latency_ns: memory latency for the platform + * + * Calculate the watermark level (the level at which the display plane will + * start fetching from memory again). Each chip has a different display + * FIFO size and allocation, so the caller needs to figure that out and pass + * in the correct intel_watermark_params structure. + * + * As the pixel clock runs, the FIFO will be drained at a rate that depends + * on the pixel size. When it reaches the watermark level, it'll start + * fetching FIFO line sized based chunks from memory until the FIFO fills + * past the watermark point. If the FIFO drains completely, a FIFO underrun + * will occur, and a display engine hang could result. + */ +static unsigned int intel_calculate_wm(struct intel_display *display, + int pixel_rate, + const struct intel_watermark_params *wm, + int fifo_size, int cpp, + unsigned int latency_ns) +{ + int entries, wm_size; + + /* + * Note: we need to make sure we don't overflow for various clock & + * latency values. + * clocks go from a few thousand to several hundred thousand. + * latency is usually a few thousand + */ + entries = intel_wm_method1(pixel_rate, cpp, + latency_ns / 100); + entries = DIV_ROUND_UP(entries, wm->cacheline_size) + + wm->guard_size; + drm_dbg_kms(display->drm, "FIFO entries required for mode: %d\n", entries); + + wm_size = fifo_size - entries; + drm_dbg_kms(display->drm, "FIFO watermark level: %d\n", wm_size); + + /* Don't promote wm_size to unsigned... */ + if (wm_size > wm->max_wm) + wm_size = wm->max_wm; + if (wm_size <= 0) + wm_size = wm->default_wm; + + /* + * Bspec seems to indicate that the value shouldn't be lower than + * 'burst size + 1'. Certainly 830 is quite unhappy with low values. + * Lets go for 8 which is the burst size since certain platforms + * already use a hardcoded 8 (which is what the spec says should be + * done). + */ + if (wm_size <= 8) + wm_size = 8; + + return wm_size; +} + +static bool is_disabling(int old, int new, int threshold) +{ + return old >= threshold && new < threshold; +} + +static bool is_enabling(int old, int new, int threshold) +{ + return old < threshold && new >= threshold; +} + +static bool intel_crtc_active(struct intel_crtc *crtc) +{ + /* Be paranoid as we can arrive here with only partial + * state retrieved from the hardware during setup. + * + * We can ditch the adjusted_mode.crtc_clock check as soon + * as Haswell has gained clock readout/fastboot support. + * + * We can ditch the crtc->primary->state->fb check as soon as we can + * properly reconstruct framebuffers. + * + * FIXME: The intel_crtc->active here should be switched to + * crtc->state->active once we have proper CRTC states wired up + * for atomic. + */ + return crtc->active && crtc->base.primary->state->fb && + crtc->config->hw.adjusted_mode.crtc_clock; +} + +static struct intel_crtc *single_enabled_crtc(struct intel_display *display) +{ + struct intel_crtc *crtc, *enabled = NULL; + + for_each_intel_crtc(display, crtc) { + if (intel_crtc_active(crtc)) { + if (enabled) + return NULL; + enabled = crtc; + } + } + + return enabled; +} + +static void pnv_update_wm(struct intel_display *display) +{ + struct intel_crtc *crtc; + const struct cxsr_latency *latency; + u32 reg; + unsigned int wm; + + latency = pnv_get_cxsr_latency(display); + if (!latency) { + drm_dbg_kms(display->drm, "Unknown FSB/MEM, disabling CxSR\n"); + intel_set_memory_cxsr(display, false); + return; + } + + crtc = single_enabled_crtc(display); + if (crtc) { + const struct drm_framebuffer *fb = + crtc->base.primary->state->fb; + int pixel_rate = crtc->config->pixel_rate; + int cpp = fb->format->cpp[0]; + + /* Display SR */ + wm = intel_calculate_wm(display, pixel_rate, + &pnv_display_wm, + pnv_display_wm.fifo_size, + cpp, latency->display_sr); + reg = intel_de_read(display, DSPFW1(display)); + reg &= ~DSPFW_SR_MASK; + reg |= FW_WM(wm, SR); + intel_de_write(display, DSPFW1(display), reg); + drm_dbg_kms(display->drm, "DSPFW1 register is %x\n", reg); + + /* cursor SR */ + wm = intel_calculate_wm(display, pixel_rate, + &pnv_cursor_wm, + pnv_display_wm.fifo_size, + 4, latency->cursor_sr); + intel_de_rmw(display, DSPFW3(display), + DSPFW_CURSOR_SR_MASK, FW_WM(wm, CURSOR_SR)); + + /* Display HPLL off SR */ + wm = intel_calculate_wm(display, pixel_rate, + &pnv_display_hplloff_wm, + pnv_display_hplloff_wm.fifo_size, + cpp, latency->display_hpll_disable); + intel_de_rmw(display, DSPFW3(display), + DSPFW_HPLL_SR_MASK, FW_WM(wm, HPLL_SR)); + + /* cursor HPLL off SR */ + wm = intel_calculate_wm(display, pixel_rate, + &pnv_cursor_hplloff_wm, + pnv_display_hplloff_wm.fifo_size, + 4, latency->cursor_hpll_disable); + reg = intel_de_read(display, DSPFW3(display)); + reg &= ~DSPFW_HPLL_CURSOR_MASK; + reg |= FW_WM(wm, HPLL_CURSOR); + intel_de_write(display, DSPFW3(display), reg); + drm_dbg_kms(display->drm, "DSPFW3 register is %x\n", reg); + + intel_set_memory_cxsr(display, true); + } else { + intel_set_memory_cxsr(display, false); + } +} + +static bool i9xx_wm_need_update(const struct intel_plane_state *old_plane_state, + const struct intel_plane_state *new_plane_state) +{ + /* Update watermarks on tiling or size changes. */ + if (old_plane_state->uapi.visible != new_plane_state->uapi.visible) + return true; + + if (!old_plane_state->hw.fb || !new_plane_state->hw.fb) + return false; + + if (old_plane_state->hw.fb->modifier != new_plane_state->hw.fb->modifier || + old_plane_state->hw.rotation != new_plane_state->hw.rotation || + drm_rect_width(&old_plane_state->uapi.src) != drm_rect_width(&new_plane_state->uapi.src) || + drm_rect_height(&old_plane_state->uapi.src) != drm_rect_height(&new_plane_state->uapi.src) || + drm_rect_width(&old_plane_state->uapi.dst) != drm_rect_width(&new_plane_state->uapi.dst) || + drm_rect_height(&old_plane_state->uapi.dst) != drm_rect_height(&new_plane_state->uapi.dst)) + return true; + + return false; +} + +static void i9xx_wm_compute(struct intel_crtc_state *new_crtc_state, + const struct intel_plane_state *old_plane_state, + const struct intel_plane_state *new_plane_state) +{ + bool turn_off, turn_on, visible, was_visible, mode_changed; + + mode_changed = intel_crtc_needs_modeset(new_crtc_state); + was_visible = old_plane_state->uapi.visible; + visible = new_plane_state->uapi.visible; + + if (!was_visible && !visible) + return; + + turn_off = was_visible && (!visible || mode_changed); + turn_on = visible && (!was_visible || mode_changed); + + /* FIXME nuke when all wm code is atomic */ + if (turn_on) { + new_crtc_state->update_wm_pre = true; + } else if (turn_off) { + new_crtc_state->update_wm_post = true; + } else if (i9xx_wm_need_update(old_plane_state, new_plane_state)) { + /* FIXME bollocks */ + new_crtc_state->update_wm_pre = true; + new_crtc_state->update_wm_post = true; + } +} + +static int i9xx_compute_watermarks(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + const struct intel_plane_state *old_plane_state; + const struct intel_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) { + if (plane->pipe != crtc->pipe) + continue; + + i9xx_wm_compute(new_crtc_state, old_plane_state, new_plane_state); + } + + return 0; +} + +/* + * Documentation says: + * "If the line size is small, the TLB fetches can get in the way of the + * data fetches, causing some lag in the pixel data return which is not + * accounted for in the above formulas. The following adjustment only + * needs to be applied if eight whole lines fit in the buffer at once. + * The WM is adjusted upwards by the difference between the FIFO size + * and the size of 8 whole lines. This adjustment is always performed + * in the actual pixel depth regardless of whether FBC is enabled or not." + */ +static unsigned int g4x_tlb_miss_wa(int fifo_size, int width, int cpp) +{ + int tlb_miss = fifo_size * 64 - width * cpp * 8; + + return max(0, tlb_miss); +} + +static void g4x_write_wm_values(struct intel_display *display, + const struct g4x_wm_values *wm) +{ + enum pipe pipe; + + for_each_pipe(display, pipe) + trace_g4x_wm(intel_crtc_for_pipe(display, pipe), wm); + + intel_de_write(display, DSPFW1(display), + FW_WM(wm->sr.plane, SR) | + FW_WM(wm->pipe[PIPE_B].plane[PLANE_CURSOR], CURSORB) | + FW_WM(wm->pipe[PIPE_B].plane[PLANE_PRIMARY], PLANEB) | + FW_WM(wm->pipe[PIPE_A].plane[PLANE_PRIMARY], PLANEA)); + intel_de_write(display, DSPFW2(display), + (wm->fbc_en ? DSPFW_FBC_SR_EN : 0) | + FW_WM(wm->sr.fbc, FBC_SR) | + FW_WM(wm->hpll.fbc, FBC_HPLL_SR) | + FW_WM(wm->pipe[PIPE_B].plane[PLANE_SPRITE0], SPRITEB) | + FW_WM(wm->pipe[PIPE_A].plane[PLANE_CURSOR], CURSORA) | + FW_WM(wm->pipe[PIPE_A].plane[PLANE_SPRITE0], SPRITEA)); + intel_de_write(display, DSPFW3(display), + (wm->hpll_en ? DSPFW_HPLL_SR_EN : 0) | + FW_WM(wm->sr.cursor, CURSOR_SR) | + FW_WM(wm->hpll.cursor, HPLL_CURSOR) | + FW_WM(wm->hpll.plane, HPLL_SR)); + + intel_de_posting_read(display, DSPFW1(display)); +} + +#define FW_WM_VLV(value, plane) \ + (((value) << DSPFW_ ## plane ## _SHIFT) & DSPFW_ ## plane ## _MASK_VLV) + +static void vlv_write_wm_values(struct intel_display *display, + const struct vlv_wm_values *wm) +{ + enum pipe pipe; + + for_each_pipe(display, pipe) { + trace_vlv_wm(intel_crtc_for_pipe(display, pipe), wm); + + intel_de_write(display, VLV_DDL(pipe), + (wm->ddl[pipe].plane[PLANE_CURSOR] << DDL_CURSOR_SHIFT) | + (wm->ddl[pipe].plane[PLANE_SPRITE1] << DDL_SPRITE_SHIFT(1)) | + (wm->ddl[pipe].plane[PLANE_SPRITE0] << DDL_SPRITE_SHIFT(0)) | + (wm->ddl[pipe].plane[PLANE_PRIMARY] << DDL_PLANE_SHIFT)); + } + + /* + * Zero the (unused) WM1 watermarks, and also clear all the + * high order bits so that there are no out of bounds values + * present in the registers during the reprogramming. + */ + intel_de_write(display, DSPHOWM, 0); + intel_de_write(display, DSPHOWM1, 0); + intel_de_write(display, DSPFW4, 0); + intel_de_write(display, DSPFW5, 0); + intel_de_write(display, DSPFW6, 0); + + intel_de_write(display, DSPFW1(display), + FW_WM(wm->sr.plane, SR) | + FW_WM(wm->pipe[PIPE_B].plane[PLANE_CURSOR], CURSORB) | + FW_WM_VLV(wm->pipe[PIPE_B].plane[PLANE_PRIMARY], PLANEB) | + FW_WM_VLV(wm->pipe[PIPE_A].plane[PLANE_PRIMARY], PLANEA)); + intel_de_write(display, DSPFW2(display), + FW_WM_VLV(wm->pipe[PIPE_A].plane[PLANE_SPRITE1], SPRITEB) | + FW_WM(wm->pipe[PIPE_A].plane[PLANE_CURSOR], CURSORA) | + FW_WM_VLV(wm->pipe[PIPE_A].plane[PLANE_SPRITE0], SPRITEA)); + intel_de_write(display, DSPFW3(display), + FW_WM(wm->sr.cursor, CURSOR_SR)); + + if (display->platform.cherryview) { + intel_de_write(display, DSPFW7_CHV, + FW_WM_VLV(wm->pipe[PIPE_B].plane[PLANE_SPRITE1], SPRITED) | + FW_WM_VLV(wm->pipe[PIPE_B].plane[PLANE_SPRITE0], SPRITEC)); + intel_de_write(display, DSPFW8_CHV, + FW_WM_VLV(wm->pipe[PIPE_C].plane[PLANE_SPRITE1], SPRITEF) | + FW_WM_VLV(wm->pipe[PIPE_C].plane[PLANE_SPRITE0], SPRITEE)); + intel_de_write(display, DSPFW9_CHV, + FW_WM_VLV(wm->pipe[PIPE_C].plane[PLANE_PRIMARY], PLANEC) | + FW_WM(wm->pipe[PIPE_C].plane[PLANE_CURSOR], CURSORC)); + intel_de_write(display, DSPHOWM, + FW_WM(wm->sr.plane >> 9, SR_HI) | + FW_WM(wm->pipe[PIPE_C].plane[PLANE_SPRITE1] >> 8, SPRITEF_HI) | + FW_WM(wm->pipe[PIPE_C].plane[PLANE_SPRITE0] >> 8, SPRITEE_HI) | + FW_WM(wm->pipe[PIPE_C].plane[PLANE_PRIMARY] >> 8, PLANEC_HI) | + FW_WM(wm->pipe[PIPE_B].plane[PLANE_SPRITE1] >> 8, SPRITED_HI) | + FW_WM(wm->pipe[PIPE_B].plane[PLANE_SPRITE0] >> 8, SPRITEC_HI) | + FW_WM(wm->pipe[PIPE_B].plane[PLANE_PRIMARY] >> 8, PLANEB_HI) | + FW_WM(wm->pipe[PIPE_A].plane[PLANE_SPRITE1] >> 8, SPRITEB_HI) | + FW_WM(wm->pipe[PIPE_A].plane[PLANE_SPRITE0] >> 8, SPRITEA_HI) | + FW_WM(wm->pipe[PIPE_A].plane[PLANE_PRIMARY] >> 8, PLANEA_HI)); + } else { + intel_de_write(display, DSPFW7, + FW_WM_VLV(wm->pipe[PIPE_B].plane[PLANE_SPRITE1], SPRITED) | + FW_WM_VLV(wm->pipe[PIPE_B].plane[PLANE_SPRITE0], SPRITEC)); + intel_de_write(display, DSPHOWM, + FW_WM(wm->sr.plane >> 9, SR_HI) | + FW_WM(wm->pipe[PIPE_B].plane[PLANE_SPRITE1] >> 8, SPRITED_HI) | + FW_WM(wm->pipe[PIPE_B].plane[PLANE_SPRITE0] >> 8, SPRITEC_HI) | + FW_WM(wm->pipe[PIPE_B].plane[PLANE_PRIMARY] >> 8, PLANEB_HI) | + FW_WM(wm->pipe[PIPE_A].plane[PLANE_SPRITE1] >> 8, SPRITEB_HI) | + FW_WM(wm->pipe[PIPE_A].plane[PLANE_SPRITE0] >> 8, SPRITEA_HI) | + FW_WM(wm->pipe[PIPE_A].plane[PLANE_PRIMARY] >> 8, PLANEA_HI)); + } + + intel_de_posting_read(display, DSPFW1(display)); +} + +#undef FW_WM_VLV + +static void g4x_setup_wm_latency(struct intel_display *display) +{ + /* all latencies in usec */ + display->wm.pri_latency[G4X_WM_LEVEL_NORMAL] = 5; + display->wm.pri_latency[G4X_WM_LEVEL_SR] = 12; + display->wm.pri_latency[G4X_WM_LEVEL_HPLL] = 35; + + display->wm.num_levels = G4X_WM_LEVEL_HPLL + 1; +} + +static int g4x_plane_fifo_size(enum plane_id plane_id, int level) +{ + /* + * DSPCNTR[13] supposedly controls whether the + * primary plane can use the FIFO space otherwise + * reserved for the sprite plane. It's not 100% clear + * what the actual FIFO size is, but it looks like we + * can happily set both primary and sprite watermarks + * up to 127 cachelines. So that would seem to mean + * that either DSPCNTR[13] doesn't do anything, or that + * the total FIFO is >= 256 cachelines in size. Either + * way, we don't seem to have to worry about this + * repartitioning as the maximum watermark value the + * register can hold for each plane is lower than the + * minimum FIFO size. + */ + switch (plane_id) { + case PLANE_CURSOR: + return 63; + case PLANE_PRIMARY: + return level == G4X_WM_LEVEL_NORMAL ? 127 : 511; + case PLANE_SPRITE0: + return level == G4X_WM_LEVEL_NORMAL ? 127 : 0; + default: + MISSING_CASE(plane_id); + return 0; + } +} + +static int g4x_fbc_fifo_size(int level) +{ + switch (level) { + case G4X_WM_LEVEL_SR: + return 7; + case G4X_WM_LEVEL_HPLL: + return 15; + default: + MISSING_CASE(level); + return 0; + } +} + +static u16 g4x_compute_wm(const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state, + int level) +{ + struct intel_display *display = to_intel_display(plane_state); + struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane); + const struct drm_display_mode *pipe_mode = + &crtc_state->hw.pipe_mode; + unsigned int latency = display->wm.pri_latency[level] * 10; + unsigned int pixel_rate, htotal, cpp, width, wm; + + if (latency == 0) + return USHRT_MAX; + + if (!intel_wm_plane_visible(crtc_state, plane_state)) + return 0; + + cpp = plane_state->hw.fb->format->cpp[0]; + + /* + * WaUse32BppForSRWM:ctg,elk + * + * The spec fails to list this restriction for the + * HPLL watermark, which seems a little strange. + * Let's use 32bpp for the HPLL watermark as well. + */ + if (plane->id == PLANE_PRIMARY && + level != G4X_WM_LEVEL_NORMAL) + cpp = max(cpp, 4u); + + pixel_rate = crtc_state->pixel_rate; + htotal = pipe_mode->crtc_htotal; + width = drm_rect_width(&plane_state->uapi.src) >> 16; + + if (plane->id == PLANE_CURSOR) { + wm = intel_wm_method2(pixel_rate, htotal, width, cpp, latency); + } else if (plane->id == PLANE_PRIMARY && + level == G4X_WM_LEVEL_NORMAL) { + wm = intel_wm_method1(pixel_rate, cpp, latency); + } else { + unsigned int small, large; + + small = intel_wm_method1(pixel_rate, cpp, latency); + large = intel_wm_method2(pixel_rate, htotal, width, cpp, latency); + + wm = min(small, large); + } + + wm += g4x_tlb_miss_wa(g4x_plane_fifo_size(plane->id, level), + width, cpp); + + wm = DIV_ROUND_UP(wm, 64) + 2; + + return min_t(unsigned int, wm, USHRT_MAX); +} + +static bool g4x_raw_plane_wm_set(struct intel_crtc_state *crtc_state, + int level, enum plane_id plane_id, u16 value) +{ + struct intel_display *display = to_intel_display(crtc_state); + bool dirty = false; + + for (; level < display->wm.num_levels; level++) { + struct g4x_pipe_wm *raw = &crtc_state->wm.g4x.raw[level]; + + dirty |= raw->plane[plane_id] != value; + raw->plane[plane_id] = value; + } + + return dirty; +} + +static bool g4x_raw_fbc_wm_set(struct intel_crtc_state *crtc_state, + int level, u16 value) +{ + struct intel_display *display = to_intel_display(crtc_state); + bool dirty = false; + + /* NORMAL level doesn't have an FBC watermark */ + level = max(level, G4X_WM_LEVEL_SR); + + for (; level < display->wm.num_levels; level++) { + struct g4x_pipe_wm *raw = &crtc_state->wm.g4x.raw[level]; + + dirty |= raw->fbc != value; + raw->fbc = value; + } + + return dirty; +} + +static u32 ilk_compute_fbc_wm(const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state, + u32 pri_val); + +static bool g4x_raw_plane_wm_compute(struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane); + enum plane_id plane_id = plane->id; + bool dirty = false; + int level; + + if (!intel_wm_plane_visible(crtc_state, plane_state)) { + dirty |= g4x_raw_plane_wm_set(crtc_state, 0, plane_id, 0); + if (plane_id == PLANE_PRIMARY) + dirty |= g4x_raw_fbc_wm_set(crtc_state, 0, 0); + goto out; + } + + for (level = 0; level < display->wm.num_levels; level++) { + struct g4x_pipe_wm *raw = &crtc_state->wm.g4x.raw[level]; + int wm, max_wm; + + wm = g4x_compute_wm(crtc_state, plane_state, level); + max_wm = g4x_plane_fifo_size(plane_id, level); + + if (wm > max_wm) + break; + + dirty |= raw->plane[plane_id] != wm; + raw->plane[plane_id] = wm; + + if (plane_id != PLANE_PRIMARY || + level == G4X_WM_LEVEL_NORMAL) + continue; + + wm = ilk_compute_fbc_wm(crtc_state, plane_state, + raw->plane[plane_id]); + max_wm = g4x_fbc_fifo_size(level); + + /* + * FBC wm is not mandatory as we + * can always just disable its use. + */ + if (wm > max_wm) + wm = USHRT_MAX; + + dirty |= raw->fbc != wm; + raw->fbc = wm; + } + + /* mark watermarks as invalid */ + dirty |= g4x_raw_plane_wm_set(crtc_state, level, plane_id, USHRT_MAX); + + if (plane_id == PLANE_PRIMARY) + dirty |= g4x_raw_fbc_wm_set(crtc_state, level, USHRT_MAX); + + out: + if (dirty) { + drm_dbg_kms(display->drm, + "%s watermarks: normal=%d, SR=%d, HPLL=%d\n", + plane->base.name, + crtc_state->wm.g4x.raw[G4X_WM_LEVEL_NORMAL].plane[plane_id], + crtc_state->wm.g4x.raw[G4X_WM_LEVEL_SR].plane[plane_id], + crtc_state->wm.g4x.raw[G4X_WM_LEVEL_HPLL].plane[plane_id]); + + if (plane_id == PLANE_PRIMARY) + drm_dbg_kms(display->drm, + "FBC watermarks: SR=%d, HPLL=%d\n", + crtc_state->wm.g4x.raw[G4X_WM_LEVEL_SR].fbc, + crtc_state->wm.g4x.raw[G4X_WM_LEVEL_HPLL].fbc); + } + + return dirty; +} + +static bool g4x_raw_plane_wm_is_valid(const struct intel_crtc_state *crtc_state, + enum plane_id plane_id, int level) +{ + const struct g4x_pipe_wm *raw = &crtc_state->wm.g4x.raw[level]; + + return raw->plane[plane_id] <= g4x_plane_fifo_size(plane_id, level); +} + +static bool g4x_raw_crtc_wm_is_valid(const struct intel_crtc_state *crtc_state, + int level) +{ + struct intel_display *display = to_intel_display(crtc_state); + + if (level >= display->wm.num_levels) + return false; + + return g4x_raw_plane_wm_is_valid(crtc_state, PLANE_PRIMARY, level) && + g4x_raw_plane_wm_is_valid(crtc_state, PLANE_SPRITE0, level) && + g4x_raw_plane_wm_is_valid(crtc_state, PLANE_CURSOR, level); +} + +/* mark all levels starting from 'level' as invalid */ +static void g4x_invalidate_wms(struct intel_crtc *crtc, + struct g4x_wm_state *wm_state, int level) +{ + if (level <= G4X_WM_LEVEL_NORMAL) { + enum plane_id plane_id; + + for_each_plane_id_on_crtc(crtc, plane_id) + wm_state->wm.plane[plane_id] = USHRT_MAX; + } + + if (level <= G4X_WM_LEVEL_SR) { + wm_state->cxsr = false; + wm_state->sr.cursor = USHRT_MAX; + wm_state->sr.plane = USHRT_MAX; + wm_state->sr.fbc = USHRT_MAX; + } + + if (level <= G4X_WM_LEVEL_HPLL) { + wm_state->hpll_en = false; + wm_state->hpll.cursor = USHRT_MAX; + wm_state->hpll.plane = USHRT_MAX; + wm_state->hpll.fbc = USHRT_MAX; + } +} + +static bool g4x_compute_fbc_en(const struct g4x_wm_state *wm_state, + int level) +{ + if (level < G4X_WM_LEVEL_SR) + return false; + + if (level >= G4X_WM_LEVEL_SR && + wm_state->sr.fbc > g4x_fbc_fifo_size(G4X_WM_LEVEL_SR)) + return false; + + if (level >= G4X_WM_LEVEL_HPLL && + wm_state->hpll.fbc > g4x_fbc_fifo_size(G4X_WM_LEVEL_HPLL)) + return false; + + return true; +} + +static int _g4x_compute_pipe_wm(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct g4x_wm_state *wm_state = &crtc_state->wm.g4x.optimal; + u8 active_planes = crtc_state->active_planes & ~BIT(PLANE_CURSOR); + const struct g4x_pipe_wm *raw; + enum plane_id plane_id; + int level; + + level = G4X_WM_LEVEL_NORMAL; + if (!g4x_raw_crtc_wm_is_valid(crtc_state, level)) + goto out; + + raw = &crtc_state->wm.g4x.raw[level]; + for_each_plane_id_on_crtc(crtc, plane_id) + wm_state->wm.plane[plane_id] = raw->plane[plane_id]; + + level = G4X_WM_LEVEL_SR; + if (!g4x_raw_crtc_wm_is_valid(crtc_state, level)) + goto out; + + raw = &crtc_state->wm.g4x.raw[level]; + wm_state->sr.plane = raw->plane[PLANE_PRIMARY]; + wm_state->sr.cursor = raw->plane[PLANE_CURSOR]; + wm_state->sr.fbc = raw->fbc; + + wm_state->cxsr = active_planes == BIT(PLANE_PRIMARY); + + level = G4X_WM_LEVEL_HPLL; + if (!g4x_raw_crtc_wm_is_valid(crtc_state, level)) + goto out; + + raw = &crtc_state->wm.g4x.raw[level]; + wm_state->hpll.plane = raw->plane[PLANE_PRIMARY]; + wm_state->hpll.cursor = raw->plane[PLANE_CURSOR]; + wm_state->hpll.fbc = raw->fbc; + + wm_state->hpll_en = wm_state->cxsr; + + level++; + + out: + if (level == G4X_WM_LEVEL_NORMAL) + return -EINVAL; + + /* invalidate the higher levels */ + g4x_invalidate_wms(crtc, wm_state, level); + + /* + * Determine if the FBC watermark(s) can be used. IF + * this isn't the case we prefer to disable the FBC + * watermark(s) rather than disable the SR/HPLL + * level(s) entirely. 'level-1' is the highest valid + * level here. + */ + wm_state->fbc_en = g4x_compute_fbc_en(wm_state, level - 1); + + return 0; +} + +static int g4x_compute_pipe_wm(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_plane_state *old_plane_state; + const struct intel_plane_state *new_plane_state; + struct intel_plane *plane; + unsigned int dirty = 0; + int i; + + for_each_oldnew_intel_plane_in_state(state, plane, + old_plane_state, + new_plane_state, i) { + if (new_plane_state->hw.crtc != &crtc->base && + old_plane_state->hw.crtc != &crtc->base) + continue; + + if (g4x_raw_plane_wm_compute(crtc_state, new_plane_state)) + dirty |= BIT(plane->id); + } + + if (!dirty) + return 0; + + return _g4x_compute_pipe_wm(crtc_state); +} + +static int g4x_compute_intermediate_wm(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(state); + struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + struct g4x_wm_state *intermediate = &new_crtc_state->wm.g4x.intermediate; + const struct g4x_wm_state *optimal = &new_crtc_state->wm.g4x.optimal; + const struct g4x_wm_state *active = &old_crtc_state->wm.g4x.optimal; + enum plane_id plane_id; + + if (!new_crtc_state->hw.active || + intel_crtc_needs_modeset(new_crtc_state)) { + *intermediate = *optimal; + + intermediate->cxsr = false; + intermediate->hpll_en = false; + goto out; + } + + intermediate->cxsr = optimal->cxsr && active->cxsr && + !new_crtc_state->disable_cxsr; + intermediate->hpll_en = optimal->hpll_en && active->hpll_en && + !new_crtc_state->disable_cxsr; + intermediate->fbc_en = optimal->fbc_en && active->fbc_en; + + for_each_plane_id_on_crtc(crtc, plane_id) { + intermediate->wm.plane[plane_id] = + max(optimal->wm.plane[plane_id], + active->wm.plane[plane_id]); + + drm_WARN_ON(display->drm, intermediate->wm.plane[plane_id] > + g4x_plane_fifo_size(plane_id, G4X_WM_LEVEL_NORMAL)); + } + + intermediate->sr.plane = max(optimal->sr.plane, + active->sr.plane); + intermediate->sr.cursor = max(optimal->sr.cursor, + active->sr.cursor); + intermediate->sr.fbc = max(optimal->sr.fbc, + active->sr.fbc); + + intermediate->hpll.plane = max(optimal->hpll.plane, + active->hpll.plane); + intermediate->hpll.cursor = max(optimal->hpll.cursor, + active->hpll.cursor); + intermediate->hpll.fbc = max(optimal->hpll.fbc, + active->hpll.fbc); + + drm_WARN_ON(display->drm, + (intermediate->sr.plane > + g4x_plane_fifo_size(PLANE_PRIMARY, G4X_WM_LEVEL_SR) || + intermediate->sr.cursor > + g4x_plane_fifo_size(PLANE_CURSOR, G4X_WM_LEVEL_SR)) && + intermediate->cxsr); + drm_WARN_ON(display->drm, + (intermediate->sr.plane > + g4x_plane_fifo_size(PLANE_PRIMARY, G4X_WM_LEVEL_HPLL) || + intermediate->sr.cursor > + g4x_plane_fifo_size(PLANE_CURSOR, G4X_WM_LEVEL_HPLL)) && + intermediate->hpll_en); + + drm_WARN_ON(display->drm, + intermediate->sr.fbc > g4x_fbc_fifo_size(1) && + intermediate->fbc_en && intermediate->cxsr); + drm_WARN_ON(display->drm, + intermediate->hpll.fbc > g4x_fbc_fifo_size(2) && + intermediate->fbc_en && intermediate->hpll_en); + +out: + /* + * If our intermediate WM are identical to the final WM, then we can + * omit the post-vblank programming; only update if it's different. + */ + if (memcmp(intermediate, optimal, sizeof(*intermediate)) != 0) + new_crtc_state->wm.need_postvbl_update = true; + + return 0; +} + +static int g4x_compute_watermarks(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + int ret; + + ret = g4x_compute_pipe_wm(state, crtc); + if (ret) + return ret; + + ret = g4x_compute_intermediate_wm(state, crtc); + if (ret) + return ret; + + return 0; +} + +static void g4x_merge_wm(struct intel_display *display, + struct g4x_wm_values *wm) +{ + struct intel_crtc *crtc; + int num_active_pipes = 0; + + wm->cxsr = true; + wm->hpll_en = true; + wm->fbc_en = true; + + for_each_intel_crtc(display, crtc) { + const struct g4x_wm_state *wm_state = &crtc->wm.active.g4x; + + if (!crtc->active) + continue; + + if (!wm_state->cxsr) + wm->cxsr = false; + if (!wm_state->hpll_en) + wm->hpll_en = false; + if (!wm_state->fbc_en) + wm->fbc_en = false; + + num_active_pipes++; + } + + if (num_active_pipes != 1) { + wm->cxsr = false; + wm->hpll_en = false; + wm->fbc_en = false; + } + + for_each_intel_crtc(display, crtc) { + const struct g4x_wm_state *wm_state = &crtc->wm.active.g4x; + enum pipe pipe = crtc->pipe; + + wm->pipe[pipe] = wm_state->wm; + if (crtc->active && wm->cxsr) + wm->sr = wm_state->sr; + if (crtc->active && wm->hpll_en) + wm->hpll = wm_state->hpll; + } +} + +static void g4x_program_watermarks(struct intel_display *display) +{ + struct g4x_wm_values *old_wm = &display->wm.g4x; + struct g4x_wm_values new_wm = {}; + + g4x_merge_wm(display, &new_wm); + + if (memcmp(old_wm, &new_wm, sizeof(new_wm)) == 0) + return; + + if (is_disabling(old_wm->cxsr, new_wm.cxsr, true)) + _intel_set_memory_cxsr(display, false); + + g4x_write_wm_values(display, &new_wm); + + if (is_enabling(old_wm->cxsr, new_wm.cxsr, true)) + _intel_set_memory_cxsr(display, true); + + *old_wm = new_wm; +} + +static void g4x_initial_watermarks(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + const struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + mutex_lock(&display->wm.wm_mutex); + crtc->wm.active.g4x = crtc_state->wm.g4x.intermediate; + g4x_program_watermarks(display); + mutex_unlock(&display->wm.wm_mutex); +} + +static void g4x_optimize_watermarks(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + const struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + if (!crtc_state->wm.need_postvbl_update) + return; + + mutex_lock(&display->wm.wm_mutex); + crtc->wm.active.g4x = crtc_state->wm.g4x.optimal; + g4x_program_watermarks(display); + mutex_unlock(&display->wm.wm_mutex); +} + +/* latency must be in 0.1us units. */ +static unsigned int vlv_wm_method2(unsigned int pixel_rate, + unsigned int htotal, + unsigned int width, + unsigned int cpp, + unsigned int latency) +{ + unsigned int ret; + + ret = intel_wm_method2(pixel_rate, htotal, + width, cpp, latency); + ret = DIV_ROUND_UP(ret, 64); + + return ret; +} + +static void vlv_setup_wm_latency(struct intel_display *display) +{ + /* all latencies in usec */ + display->wm.pri_latency[VLV_WM_LEVEL_PM2] = 3; + + display->wm.num_levels = VLV_WM_LEVEL_PM2 + 1; + + if (display->platform.cherryview) { + display->wm.pri_latency[VLV_WM_LEVEL_PM5] = 12; + display->wm.pri_latency[VLV_WM_LEVEL_DDR_DVFS] = 33; + + display->wm.num_levels = VLV_WM_LEVEL_DDR_DVFS + 1; + } +} + +static u16 vlv_compute_wm_level(const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state, + int level) +{ + struct intel_display *display = to_intel_display(plane_state); + struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane); + const struct drm_display_mode *pipe_mode = + &crtc_state->hw.pipe_mode; + unsigned int pixel_rate, htotal, cpp, width, wm; + + if (display->wm.pri_latency[level] == 0) + return USHRT_MAX; + + if (!intel_wm_plane_visible(crtc_state, plane_state)) + return 0; + + cpp = plane_state->hw.fb->format->cpp[0]; + pixel_rate = crtc_state->pixel_rate; + htotal = pipe_mode->crtc_htotal; + width = drm_rect_width(&plane_state->uapi.src) >> 16; + + if (plane->id == PLANE_CURSOR) { + /* + * FIXME the formula gives values that are + * too big for the cursor FIFO, and hence we + * would never be able to use cursors. For + * now just hardcode the watermark. + */ + wm = 63; + } else { + wm = vlv_wm_method2(pixel_rate, htotal, width, cpp, + display->wm.pri_latency[level] * 10); + } + + return min_t(unsigned int, wm, USHRT_MAX); +} + +static bool vlv_need_sprite0_fifo_workaround(unsigned int active_planes) +{ + return (active_planes & (BIT(PLANE_SPRITE0) | + BIT(PLANE_SPRITE1))) == BIT(PLANE_SPRITE1); +} + +static int vlv_compute_fifo(struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + const struct g4x_pipe_wm *raw = + &crtc_state->wm.vlv.raw[VLV_WM_LEVEL_PM2]; + struct vlv_fifo_state *fifo_state = &crtc_state->wm.vlv.fifo_state; + u8 active_planes = crtc_state->active_planes & ~BIT(PLANE_CURSOR); + int num_active_planes = hweight8(active_planes); + const int fifo_size = 511; + int fifo_extra, fifo_left = fifo_size; + int sprite0_fifo_extra = 0; + unsigned int total_rate; + enum plane_id plane_id; + + /* + * When enabling sprite0 after sprite1 has already been enabled + * we tend to get an underrun unless sprite0 already has some + * FIFO space allocated. Hence we always allocate at least one + * cacheline for sprite0 whenever sprite1 is enabled. + * + * All other plane enable sequences appear immune to this problem. + */ + if (vlv_need_sprite0_fifo_workaround(active_planes)) + sprite0_fifo_extra = 1; + + total_rate = raw->plane[PLANE_PRIMARY] + + raw->plane[PLANE_SPRITE0] + + raw->plane[PLANE_SPRITE1] + + sprite0_fifo_extra; + + if (total_rate > fifo_size) + return -EINVAL; + + if (total_rate == 0) + total_rate = 1; + + for_each_plane_id_on_crtc(crtc, plane_id) { + unsigned int rate; + + if ((active_planes & BIT(plane_id)) == 0) { + fifo_state->plane[plane_id] = 0; + continue; + } + + rate = raw->plane[plane_id]; + fifo_state->plane[plane_id] = fifo_size * rate / total_rate; + fifo_left -= fifo_state->plane[plane_id]; + } + + fifo_state->plane[PLANE_SPRITE0] += sprite0_fifo_extra; + fifo_left -= sprite0_fifo_extra; + + fifo_state->plane[PLANE_CURSOR] = 63; + + fifo_extra = DIV_ROUND_UP(fifo_left, num_active_planes ?: 1); + + /* spread the remainder evenly */ + for_each_plane_id_on_crtc(crtc, plane_id) { + int plane_extra; + + if (fifo_left == 0) + break; + + if ((active_planes & BIT(plane_id)) == 0) + continue; + + plane_extra = min(fifo_extra, fifo_left); + fifo_state->plane[plane_id] += plane_extra; + fifo_left -= plane_extra; + } + + drm_WARN_ON(display->drm, active_planes != 0 && fifo_left != 0); + + /* give it all to the first plane if none are active */ + if (active_planes == 0) { + drm_WARN_ON(display->drm, fifo_left != fifo_size); + fifo_state->plane[PLANE_PRIMARY] = fifo_left; + } + + return 0; +} + +/* mark all levels starting from 'level' as invalid */ +static void vlv_invalidate_wms(struct intel_crtc *crtc, + struct vlv_wm_state *wm_state, int level) +{ + struct intel_display *display = to_intel_display(crtc); + + for (; level < display->wm.num_levels; level++) { + enum plane_id plane_id; + + for_each_plane_id_on_crtc(crtc, plane_id) + wm_state->wm[level].plane[plane_id] = USHRT_MAX; + + wm_state->sr[level].cursor = USHRT_MAX; + wm_state->sr[level].plane = USHRT_MAX; + } +} + +static u16 vlv_invert_wm_value(u16 wm, u16 fifo_size) +{ + if (wm > fifo_size) + return USHRT_MAX; + else + return fifo_size - wm; +} + +/* + * Starting from 'level' set all higher + * levels to 'value' in the "raw" watermarks. + */ +static bool vlv_raw_plane_wm_set(struct intel_crtc_state *crtc_state, + int level, enum plane_id plane_id, u16 value) +{ + struct intel_display *display = to_intel_display(crtc_state); + bool dirty = false; + + for (; level < display->wm.num_levels; level++) { + struct g4x_pipe_wm *raw = &crtc_state->wm.vlv.raw[level]; + + dirty |= raw->plane[plane_id] != value; + raw->plane[plane_id] = value; + } + + return dirty; +} + +static bool vlv_raw_plane_wm_compute(struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane); + enum plane_id plane_id = plane->id; + int level; + bool dirty = false; + + if (!intel_wm_plane_visible(crtc_state, plane_state)) { + dirty |= vlv_raw_plane_wm_set(crtc_state, 0, plane_id, 0); + goto out; + } + + for (level = 0; level < display->wm.num_levels; level++) { + struct g4x_pipe_wm *raw = &crtc_state->wm.vlv.raw[level]; + int wm = vlv_compute_wm_level(crtc_state, plane_state, level); + int max_wm = plane_id == PLANE_CURSOR ? 63 : 511; + + if (wm > max_wm) + break; + + dirty |= raw->plane[plane_id] != wm; + raw->plane[plane_id] = wm; + } + + /* mark all higher levels as invalid */ + dirty |= vlv_raw_plane_wm_set(crtc_state, level, plane_id, USHRT_MAX); + +out: + if (dirty) + drm_dbg_kms(display->drm, + "%s watermarks: PM2=%d, PM5=%d, DDR DVFS=%d\n", + plane->base.name, + crtc_state->wm.vlv.raw[VLV_WM_LEVEL_PM2].plane[plane_id], + crtc_state->wm.vlv.raw[VLV_WM_LEVEL_PM5].plane[plane_id], + crtc_state->wm.vlv.raw[VLV_WM_LEVEL_DDR_DVFS].plane[plane_id]); + + return dirty; +} + +static bool vlv_raw_plane_wm_is_valid(const struct intel_crtc_state *crtc_state, + enum plane_id plane_id, int level) +{ + const struct g4x_pipe_wm *raw = + &crtc_state->wm.vlv.raw[level]; + const struct vlv_fifo_state *fifo_state = + &crtc_state->wm.vlv.fifo_state; + + return raw->plane[plane_id] <= fifo_state->plane[plane_id]; +} + +static bool vlv_raw_crtc_wm_is_valid(const struct intel_crtc_state *crtc_state, int level) +{ + return vlv_raw_plane_wm_is_valid(crtc_state, PLANE_PRIMARY, level) && + vlv_raw_plane_wm_is_valid(crtc_state, PLANE_SPRITE0, level) && + vlv_raw_plane_wm_is_valid(crtc_state, PLANE_SPRITE1, level) && + vlv_raw_plane_wm_is_valid(crtc_state, PLANE_CURSOR, level); +} + +static int _vlv_compute_pipe_wm(struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct vlv_wm_state *wm_state = &crtc_state->wm.vlv.optimal; + const struct vlv_fifo_state *fifo_state = + &crtc_state->wm.vlv.fifo_state; + u8 active_planes = crtc_state->active_planes & ~BIT(PLANE_CURSOR); + int num_active_planes = hweight8(active_planes); + enum plane_id plane_id; + int level; + + /* initially allow all levels */ + wm_state->num_levels = display->wm.num_levels; + /* + * Note that enabling cxsr with no primary/sprite planes + * enabled can wedge the pipe. Hence we only allow cxsr + * with exactly one enabled primary/sprite plane. + */ + wm_state->cxsr = crtc->pipe != PIPE_C && num_active_planes == 1; + + for (level = 0; level < wm_state->num_levels; level++) { + const struct g4x_pipe_wm *raw = &crtc_state->wm.vlv.raw[level]; + const int sr_fifo_size = INTEL_NUM_PIPES(display) * 512 - 1; + + if (!vlv_raw_crtc_wm_is_valid(crtc_state, level)) + break; + + for_each_plane_id_on_crtc(crtc, plane_id) { + wm_state->wm[level].plane[plane_id] = + vlv_invert_wm_value(raw->plane[plane_id], + fifo_state->plane[plane_id]); + } + + wm_state->sr[level].plane = + vlv_invert_wm_value(max3(raw->plane[PLANE_PRIMARY], + raw->plane[PLANE_SPRITE0], + raw->plane[PLANE_SPRITE1]), + sr_fifo_size); + + wm_state->sr[level].cursor = + vlv_invert_wm_value(raw->plane[PLANE_CURSOR], + 63); + } + + if (level == 0) + return -EINVAL; + + /* limit to only levels we can actually handle */ + wm_state->num_levels = level; + + /* invalidate the higher levels */ + vlv_invalidate_wms(crtc, wm_state, level); + + return 0; +} + +static int vlv_compute_pipe_wm(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_plane_state *old_plane_state; + const struct intel_plane_state *new_plane_state; + struct intel_plane *plane; + unsigned int dirty = 0; + int i; + + for_each_oldnew_intel_plane_in_state(state, plane, + old_plane_state, + new_plane_state, i) { + if (new_plane_state->hw.crtc != &crtc->base && + old_plane_state->hw.crtc != &crtc->base) + continue; + + if (vlv_raw_plane_wm_compute(crtc_state, new_plane_state)) + dirty |= BIT(plane->id); + } + + /* + * DSPARB registers may have been reset due to the + * power well being turned off. Make sure we restore + * them to a consistent state even if no primary/sprite + * planes are initially active. We also force a FIFO + * recomputation so that we are sure to sanitize the + * FIFO setting we took over from the BIOS even if there + * are no active planes on the crtc. + */ + if (intel_crtc_needs_modeset(crtc_state)) + dirty = ~0; + + if (!dirty) + return 0; + + /* cursor changes don't warrant a FIFO recompute */ + if (dirty & ~BIT(PLANE_CURSOR)) { + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + const struct vlv_fifo_state *old_fifo_state = + &old_crtc_state->wm.vlv.fifo_state; + const struct vlv_fifo_state *new_fifo_state = + &crtc_state->wm.vlv.fifo_state; + int ret; + + ret = vlv_compute_fifo(crtc_state); + if (ret) + return ret; + + if (intel_crtc_needs_modeset(crtc_state) || + memcmp(old_fifo_state, new_fifo_state, + sizeof(*new_fifo_state)) != 0) + crtc_state->fifo_changed = true; + } + + return _vlv_compute_pipe_wm(crtc_state); +} + +#define VLV_FIFO(plane, value) \ + (((value) << DSPARB_ ## plane ## _SHIFT_VLV) & DSPARB_ ## plane ## _MASK_VLV) + +static void vlv_atomic_update_fifo(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + struct intel_uncore *uncore = to_intel_uncore(display->drm); + const struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + const struct vlv_fifo_state *fifo_state = + &crtc_state->wm.vlv.fifo_state; + int sprite0_start, sprite1_start, fifo_size; + u32 dsparb, dsparb2, dsparb3; + + if (!crtc_state->fifo_changed) + return; + + sprite0_start = fifo_state->plane[PLANE_PRIMARY]; + sprite1_start = fifo_state->plane[PLANE_SPRITE0] + sprite0_start; + fifo_size = fifo_state->plane[PLANE_SPRITE1] + sprite1_start; + + drm_WARN_ON(display->drm, fifo_state->plane[PLANE_CURSOR] != 63); + drm_WARN_ON(display->drm, fifo_size != 511); + + trace_vlv_fifo_size(crtc, sprite0_start, sprite1_start, fifo_size); + + /* + * uncore.lock serves a double purpose here. It allows us to + * use the less expensive I915_{READ,WRITE}_FW() functions, and + * it protects the DSPARB registers from getting clobbered by + * parallel updates from multiple pipes. + * + * intel_pipe_update_start() has already disabled interrupts + * for us, so a plain spin_lock() is sufficient here. + */ + spin_lock(&uncore->lock); + + switch (crtc->pipe) { + case PIPE_A: + dsparb = intel_de_read_fw(display, DSPARB(display)); + dsparb2 = intel_de_read_fw(display, DSPARB2); + + dsparb &= ~(VLV_FIFO(SPRITEA, 0xff) | + VLV_FIFO(SPRITEB, 0xff)); + dsparb |= (VLV_FIFO(SPRITEA, sprite0_start) | + VLV_FIFO(SPRITEB, sprite1_start)); + + dsparb2 &= ~(VLV_FIFO(SPRITEA_HI, 0x1) | + VLV_FIFO(SPRITEB_HI, 0x1)); + dsparb2 |= (VLV_FIFO(SPRITEA_HI, sprite0_start >> 8) | + VLV_FIFO(SPRITEB_HI, sprite1_start >> 8)); + + intel_de_write_fw(display, DSPARB(display), dsparb); + intel_de_write_fw(display, DSPARB2, dsparb2); + break; + case PIPE_B: + dsparb = intel_de_read_fw(display, DSPARB(display)); + dsparb2 = intel_de_read_fw(display, DSPARB2); + + dsparb &= ~(VLV_FIFO(SPRITEC, 0xff) | + VLV_FIFO(SPRITED, 0xff)); + dsparb |= (VLV_FIFO(SPRITEC, sprite0_start) | + VLV_FIFO(SPRITED, sprite1_start)); + + dsparb2 &= ~(VLV_FIFO(SPRITEC_HI, 0xff) | + VLV_FIFO(SPRITED_HI, 0xff)); + dsparb2 |= (VLV_FIFO(SPRITEC_HI, sprite0_start >> 8) | + VLV_FIFO(SPRITED_HI, sprite1_start >> 8)); + + intel_de_write_fw(display, DSPARB(display), dsparb); + intel_de_write_fw(display, DSPARB2, dsparb2); + break; + case PIPE_C: + dsparb3 = intel_de_read_fw(display, DSPARB3); + dsparb2 = intel_de_read_fw(display, DSPARB2); + + dsparb3 &= ~(VLV_FIFO(SPRITEE, 0xff) | + VLV_FIFO(SPRITEF, 0xff)); + dsparb3 |= (VLV_FIFO(SPRITEE, sprite0_start) | + VLV_FIFO(SPRITEF, sprite1_start)); + + dsparb2 &= ~(VLV_FIFO(SPRITEE_HI, 0xff) | + VLV_FIFO(SPRITEF_HI, 0xff)); + dsparb2 |= (VLV_FIFO(SPRITEE_HI, sprite0_start >> 8) | + VLV_FIFO(SPRITEF_HI, sprite1_start >> 8)); + + intel_de_write_fw(display, DSPARB3, dsparb3); + intel_de_write_fw(display, DSPARB2, dsparb2); + break; + default: + break; + } + + intel_de_read_fw(display, DSPARB(display)); + + spin_unlock(&uncore->lock); +} + +#undef VLV_FIFO + +static int vlv_compute_intermediate_wm(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + struct vlv_wm_state *intermediate = &new_crtc_state->wm.vlv.intermediate; + const struct vlv_wm_state *optimal = &new_crtc_state->wm.vlv.optimal; + const struct vlv_wm_state *active = &old_crtc_state->wm.vlv.optimal; + int level; + + if (!new_crtc_state->hw.active || + intel_crtc_needs_modeset(new_crtc_state)) { + *intermediate = *optimal; + + intermediate->cxsr = false; + goto out; + } + + intermediate->num_levels = min(optimal->num_levels, active->num_levels); + intermediate->cxsr = optimal->cxsr && active->cxsr && + !new_crtc_state->disable_cxsr; + + for (level = 0; level < intermediate->num_levels; level++) { + enum plane_id plane_id; + + for_each_plane_id_on_crtc(crtc, plane_id) { + intermediate->wm[level].plane[plane_id] = + min(optimal->wm[level].plane[plane_id], + active->wm[level].plane[plane_id]); + } + + intermediate->sr[level].plane = min(optimal->sr[level].plane, + active->sr[level].plane); + intermediate->sr[level].cursor = min(optimal->sr[level].cursor, + active->sr[level].cursor); + } + + vlv_invalidate_wms(crtc, intermediate, level); + +out: + /* + * If our intermediate WM are identical to the final WM, then we can + * omit the post-vblank programming; only update if it's different. + */ + if (memcmp(intermediate, optimal, sizeof(*intermediate)) != 0) + new_crtc_state->wm.need_postvbl_update = true; + + return 0; +} + +static int vlv_compute_watermarks(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + int ret; + + ret = vlv_compute_pipe_wm(state, crtc); + if (ret) + return ret; + + ret = vlv_compute_intermediate_wm(state, crtc); + if (ret) + return ret; + + return 0; +} + +static void vlv_merge_wm(struct intel_display *display, + struct vlv_wm_values *wm) +{ + struct intel_crtc *crtc; + int num_active_pipes = 0; + + wm->level = display->wm.num_levels - 1; + wm->cxsr = true; + + for_each_intel_crtc(display, crtc) { + const struct vlv_wm_state *wm_state = &crtc->wm.active.vlv; + + if (!crtc->active) + continue; + + if (!wm_state->cxsr) + wm->cxsr = false; + + num_active_pipes++; + wm->level = min_t(int, wm->level, wm_state->num_levels - 1); + } + + if (num_active_pipes != 1) + wm->cxsr = false; + + if (num_active_pipes > 1) + wm->level = VLV_WM_LEVEL_PM2; + + for_each_intel_crtc(display, crtc) { + const struct vlv_wm_state *wm_state = &crtc->wm.active.vlv; + enum pipe pipe = crtc->pipe; + + wm->pipe[pipe] = wm_state->wm[wm->level]; + if (crtc->active && wm->cxsr) + wm->sr = wm_state->sr[wm->level]; + + wm->ddl[pipe].plane[PLANE_PRIMARY] = DDL_PRECISION_HIGH | 2; + wm->ddl[pipe].plane[PLANE_SPRITE0] = DDL_PRECISION_HIGH | 2; + wm->ddl[pipe].plane[PLANE_SPRITE1] = DDL_PRECISION_HIGH | 2; + wm->ddl[pipe].plane[PLANE_CURSOR] = DDL_PRECISION_HIGH | 2; + } +} + +static void vlv_program_watermarks(struct intel_display *display) +{ + struct vlv_wm_values *old_wm = &display->wm.vlv; + struct vlv_wm_values new_wm = {}; + + vlv_merge_wm(display, &new_wm); + + if (memcmp(old_wm, &new_wm, sizeof(new_wm)) == 0) + return; + + if (is_disabling(old_wm->level, new_wm.level, VLV_WM_LEVEL_DDR_DVFS)) + chv_set_memory_dvfs(display, false); + + if (is_disabling(old_wm->level, new_wm.level, VLV_WM_LEVEL_PM5)) + chv_set_memory_pm5(display, false); + + if (is_disabling(old_wm->cxsr, new_wm.cxsr, true)) + _intel_set_memory_cxsr(display, false); + + vlv_write_wm_values(display, &new_wm); + + if (is_enabling(old_wm->cxsr, new_wm.cxsr, true)) + _intel_set_memory_cxsr(display, true); + + if (is_enabling(old_wm->level, new_wm.level, VLV_WM_LEVEL_PM5)) + chv_set_memory_pm5(display, true); + + if (is_enabling(old_wm->level, new_wm.level, VLV_WM_LEVEL_DDR_DVFS)) + chv_set_memory_dvfs(display, true); + + *old_wm = new_wm; +} + +static void vlv_initial_watermarks(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + const struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + mutex_lock(&display->wm.wm_mutex); + crtc->wm.active.vlv = crtc_state->wm.vlv.intermediate; + vlv_program_watermarks(display); + mutex_unlock(&display->wm.wm_mutex); +} + +static void vlv_optimize_watermarks(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + const struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + if (!crtc_state->wm.need_postvbl_update) + return; + + mutex_lock(&display->wm.wm_mutex); + crtc->wm.active.vlv = crtc_state->wm.vlv.optimal; + vlv_program_watermarks(display); + mutex_unlock(&display->wm.wm_mutex); +} + +static void i965_update_wm(struct intel_display *display) +{ + struct intel_crtc *crtc; + int srwm = 1; + int cursor_sr = 16; + bool cxsr_enabled; + + /* Calc sr entries for one plane configs */ + crtc = single_enabled_crtc(display); + if (crtc) { + /* self-refresh has much higher latency */ + static const int sr_latency_ns = 12000; + const struct drm_display_mode *pipe_mode = + &crtc->config->hw.pipe_mode; + const struct drm_framebuffer *fb = + crtc->base.primary->state->fb; + int pixel_rate = crtc->config->pixel_rate; + int htotal = pipe_mode->crtc_htotal; + int width = drm_rect_width(&crtc->base.primary->state->src) >> 16; + int cpp = fb->format->cpp[0]; + int entries; + + entries = intel_wm_method2(pixel_rate, htotal, + width, cpp, sr_latency_ns / 100); + entries = DIV_ROUND_UP(entries, I915_FIFO_LINE_SIZE); + srwm = I965_FIFO_SIZE - entries; + if (srwm < 0) + srwm = 1; + srwm &= 0x1ff; + drm_dbg_kms(display->drm, + "self-refresh entries: %d, wm: %d\n", + entries, srwm); + + entries = intel_wm_method2(pixel_rate, htotal, + crtc->base.cursor->state->crtc_w, 4, + sr_latency_ns / 100); + entries = DIV_ROUND_UP(entries, + i965_cursor_wm_info.cacheline_size) + + i965_cursor_wm_info.guard_size; + + cursor_sr = i965_cursor_wm_info.fifo_size - entries; + if (cursor_sr > i965_cursor_wm_info.max_wm) + cursor_sr = i965_cursor_wm_info.max_wm; + + drm_dbg_kms(display->drm, + "self-refresh watermark: display plane %d " + "cursor %d\n", srwm, cursor_sr); + + cxsr_enabled = true; + } else { + cxsr_enabled = false; + /* Turn off self refresh if both pipes are enabled */ + intel_set_memory_cxsr(display, false); + } + + drm_dbg_kms(display->drm, + "Setting FIFO watermarks - A: 8, B: 8, C: 8, SR %d\n", + srwm); + + /* 965 has limitations... */ + intel_de_write(display, DSPFW1(display), + FW_WM(srwm, SR) | + FW_WM(8, CURSORB) | + FW_WM(8, PLANEB) | + FW_WM(8, PLANEA)); + intel_de_write(display, DSPFW2(display), + FW_WM(8, CURSORA) | + FW_WM(8, PLANEC_OLD)); + /* update cursor SR watermark */ + intel_de_write(display, DSPFW3(display), + FW_WM(cursor_sr, CURSOR_SR)); + + if (cxsr_enabled) + intel_set_memory_cxsr(display, true); +} + +#undef FW_WM + +static struct intel_crtc *intel_crtc_for_plane(struct intel_display *display, + enum i9xx_plane_id i9xx_plane) +{ + struct intel_plane *plane; + + for_each_intel_plane(display->drm, plane) { + if (plane->id == PLANE_PRIMARY && + plane->i9xx_plane == i9xx_plane) + return intel_crtc_for_pipe(display, plane->pipe); + } + + return NULL; +} + +static void i9xx_update_wm(struct intel_display *display) +{ + const struct intel_watermark_params *wm_info; + u32 fwater_lo; + u32 fwater_hi; + int cwm, srwm = 1; + int fifo_size; + int planea_wm, planeb_wm; + struct intel_crtc *crtc; + + if (display->platform.i945gm) + wm_info = &i945_wm_info; + else if (DISPLAY_VER(display) != 2) + wm_info = &i915_wm_info; + else + wm_info = &i830_a_wm_info; + + if (DISPLAY_VER(display) == 2) + fifo_size = i830_get_fifo_size(display, PLANE_A); + else + fifo_size = i9xx_get_fifo_size(display, PLANE_A); + crtc = intel_crtc_for_plane(display, PLANE_A); + if (intel_crtc_active(crtc)) { + const struct drm_framebuffer *fb = + crtc->base.primary->state->fb; + int cpp; + + if (DISPLAY_VER(display) == 2) + cpp = 4; + else + cpp = fb->format->cpp[0]; + + planea_wm = intel_calculate_wm(display, crtc->config->pixel_rate, + wm_info, fifo_size, cpp, + pessimal_latency_ns); + } else { + planea_wm = fifo_size - wm_info->guard_size; + if (planea_wm > (long)wm_info->max_wm) + planea_wm = wm_info->max_wm; + } + + if (DISPLAY_VER(display) == 2) + wm_info = &i830_bc_wm_info; + + if (DISPLAY_VER(display) == 2) + fifo_size = i830_get_fifo_size(display, PLANE_B); + else + fifo_size = i9xx_get_fifo_size(display, PLANE_B); + crtc = intel_crtc_for_plane(display, PLANE_B); + if (intel_crtc_active(crtc)) { + const struct drm_framebuffer *fb = + crtc->base.primary->state->fb; + int cpp; + + if (DISPLAY_VER(display) == 2) + cpp = 4; + else + cpp = fb->format->cpp[0]; + + planeb_wm = intel_calculate_wm(display, crtc->config->pixel_rate, + wm_info, fifo_size, cpp, + pessimal_latency_ns); + } else { + planeb_wm = fifo_size - wm_info->guard_size; + if (planeb_wm > (long)wm_info->max_wm) + planeb_wm = wm_info->max_wm; + } + + drm_dbg_kms(display->drm, + "FIFO watermarks - A: %d, B: %d\n", planea_wm, planeb_wm); + + crtc = single_enabled_crtc(display); + if (display->platform.i915gm && crtc) { + const struct drm_framebuffer *fb = + crtc->base.primary->state->fb; + + /* self-refresh seems busted with untiled */ + if (fb->modifier == DRM_FORMAT_MOD_LINEAR) + crtc = NULL; + } + + /* + * Overlay gets an aggressive default since video jitter is bad. + */ + cwm = 2; + + /* Play safe and disable self-refresh before adjusting watermarks. */ + intel_set_memory_cxsr(display, false); + + /* Calc sr entries for one plane configs */ + if (HAS_FW_BLC(display) && crtc) { + /* self-refresh has much higher latency */ + static const int sr_latency_ns = 6000; + const struct drm_display_mode *pipe_mode = + &crtc->config->hw.pipe_mode; + const struct drm_framebuffer *fb = + crtc->base.primary->state->fb; + int pixel_rate = crtc->config->pixel_rate; + int htotal = pipe_mode->crtc_htotal; + int width = drm_rect_width(&crtc->base.primary->state->src) >> 16; + int cpp; + int entries; + + if (display->platform.i915gm || display->platform.i945gm) + cpp = 4; + else + cpp = fb->format->cpp[0]; + + entries = intel_wm_method2(pixel_rate, htotal, width, cpp, + sr_latency_ns / 100); + entries = DIV_ROUND_UP(entries, wm_info->cacheline_size); + drm_dbg_kms(display->drm, + "self-refresh entries: %d\n", entries); + srwm = wm_info->fifo_size - entries; + if (srwm < 0) + srwm = 1; + + if (display->platform.i945g || display->platform.i945gm) + intel_de_write(display, FW_BLC_SELF, + FW_BLC_SELF_FIFO_MASK | (srwm & 0xff)); + else + intel_de_write(display, FW_BLC_SELF, srwm & 0x3f); + } + + drm_dbg_kms(display->drm, + "Setting FIFO watermarks - A: %d, B: %d, C: %d, SR %d\n", + planea_wm, planeb_wm, cwm, srwm); + + fwater_lo = ((planeb_wm & 0x3f) << 16) | (planea_wm & 0x3f); + fwater_hi = (cwm & 0x1f); + + /* Set request length to 8 cachelines per fetch */ + fwater_lo = fwater_lo | (1 << 24) | (1 << 8); + fwater_hi = fwater_hi | (1 << 8); + + intel_de_write(display, FW_BLC, fwater_lo); + intel_de_write(display, FW_BLC2, fwater_hi); + + if (crtc) + intel_set_memory_cxsr(display, true); +} + +static void i845_update_wm(struct intel_display *display) +{ + struct intel_crtc *crtc; + u32 fwater_lo; + int planea_wm; + + crtc = single_enabled_crtc(display); + if (crtc == NULL) + return; + + planea_wm = intel_calculate_wm(display, crtc->config->pixel_rate, + &i845_wm_info, + i845_get_fifo_size(display, PLANE_A), + 4, pessimal_latency_ns); + fwater_lo = intel_de_read(display, FW_BLC) & ~0xfff; + fwater_lo |= (3<<8) | planea_wm; + + drm_dbg_kms(display->drm, + "Setting FIFO watermarks - A: %d\n", planea_wm); + + intel_de_write(display, FW_BLC, fwater_lo); +} + +/* latency must be in 0.1us units. */ +static unsigned int ilk_wm_method1(unsigned int pixel_rate, + unsigned int cpp, + unsigned int latency) +{ + unsigned int ret; + + ret = intel_wm_method1(pixel_rate, cpp, latency); + ret = DIV_ROUND_UP(ret, 64) + 2; + + return ret; +} + +/* latency must be in 0.1us units. */ +static unsigned int ilk_wm_method2(unsigned int pixel_rate, + unsigned int htotal, + unsigned int width, + unsigned int cpp, + unsigned int latency) +{ + unsigned int ret; + + ret = intel_wm_method2(pixel_rate, htotal, + width, cpp, latency); + ret = DIV_ROUND_UP(ret, 64) + 2; + + return ret; +} + +static u32 ilk_wm_fbc(u32 pri_val, u32 horiz_pixels, u8 cpp) +{ + /* + * Neither of these should be possible since this function shouldn't be + * called if the CRTC is off or the plane is invisible. But let's be + * extra paranoid to avoid a potential divide-by-zero if we screw up + * elsewhere in the driver. + */ + if (WARN_ON(!cpp)) + return 0; + if (WARN_ON(!horiz_pixels)) + return 0; + + return DIV_ROUND_UP(pri_val * 64, horiz_pixels * cpp) + 2; +} + +struct ilk_wm_maximums { + u16 pri; + u16 spr; + u16 cur; + u16 fbc; +}; + +/* + * For both WM_PIPE and WM_LP. + * mem_value must be in 0.1us units. + */ +static u32 ilk_compute_pri_wm(const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state, + u32 mem_value, bool is_lp) +{ + u32 method1, method2; + int cpp; + + if (mem_value == 0) + return U32_MAX; + + if (!intel_wm_plane_visible(crtc_state, plane_state)) + return 0; + + cpp = plane_state->hw.fb->format->cpp[0]; + + method1 = ilk_wm_method1(crtc_state->pixel_rate, cpp, mem_value); + + if (!is_lp) + return method1; + + method2 = ilk_wm_method2(crtc_state->pixel_rate, + crtc_state->hw.pipe_mode.crtc_htotal, + drm_rect_width(&plane_state->uapi.src) >> 16, + cpp, mem_value); + + return min(method1, method2); +} + +/* + * For both WM_PIPE and WM_LP. + * mem_value must be in 0.1us units. + */ +static u32 ilk_compute_spr_wm(const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state, + u32 mem_value) +{ + u32 method1, method2; + int cpp; + + if (mem_value == 0) + return U32_MAX; + + if (!intel_wm_plane_visible(crtc_state, plane_state)) + return 0; + + cpp = plane_state->hw.fb->format->cpp[0]; + + method1 = ilk_wm_method1(crtc_state->pixel_rate, cpp, mem_value); + method2 = ilk_wm_method2(crtc_state->pixel_rate, + crtc_state->hw.pipe_mode.crtc_htotal, + drm_rect_width(&plane_state->uapi.src) >> 16, + cpp, mem_value); + return min(method1, method2); +} + +/* + * For both WM_PIPE and WM_LP. + * mem_value must be in 0.1us units. + */ +static u32 ilk_compute_cur_wm(const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state, + u32 mem_value) +{ + int cpp; + + if (mem_value == 0) + return U32_MAX; + + if (!intel_wm_plane_visible(crtc_state, plane_state)) + return 0; + + cpp = plane_state->hw.fb->format->cpp[0]; + + return ilk_wm_method2(crtc_state->pixel_rate, + crtc_state->hw.pipe_mode.crtc_htotal, + drm_rect_width(&plane_state->uapi.src) >> 16, + cpp, mem_value); +} + +/* Only for WM_LP. */ +static u32 ilk_compute_fbc_wm(const struct intel_crtc_state *crtc_state, + const struct intel_plane_state *plane_state, + u32 pri_val) +{ + int cpp; + + if (!intel_wm_plane_visible(crtc_state, plane_state)) + return 0; + + cpp = plane_state->hw.fb->format->cpp[0]; + + return ilk_wm_fbc(pri_val, drm_rect_width(&plane_state->uapi.src) >> 16, + cpp); +} + +static unsigned int +ilk_display_fifo_size(struct intel_display *display) +{ + if (DISPLAY_VER(display) >= 8) + return 3072; + else if (DISPLAY_VER(display) >= 7) + return 768; + else + return 512; +} + +static unsigned int +ilk_plane_wm_reg_max(struct intel_display *display, + int level, bool is_sprite) +{ + if (DISPLAY_VER(display) >= 8) + /* BDW primary/sprite plane watermarks */ + return level == 0 ? 255 : 2047; + else if (DISPLAY_VER(display) >= 7) + /* IVB/HSW primary/sprite plane watermarks */ + return level == 0 ? 127 : 1023; + else if (!is_sprite) + /* ILK/SNB primary plane watermarks */ + return level == 0 ? 127 : 511; + else + /* ILK/SNB sprite plane watermarks */ + return level == 0 ? 63 : 255; +} + +static unsigned int +ilk_cursor_wm_reg_max(struct intel_display *display, int level) +{ + if (DISPLAY_VER(display) >= 7) + return level == 0 ? 63 : 255; + else + return level == 0 ? 31 : 63; +} + +static unsigned int ilk_fbc_wm_reg_max(struct intel_display *display) +{ + if (DISPLAY_VER(display) >= 8) + return 31; + else + return 15; +} + +/* Calculate the maximum primary/sprite plane watermark */ +static unsigned int ilk_plane_wm_max(struct intel_display *display, + int level, + const struct intel_wm_config *config, + enum intel_ddb_partitioning ddb_partitioning, + bool is_sprite) +{ + unsigned int fifo_size = ilk_display_fifo_size(display); + + /* if sprites aren't enabled, sprites get nothing */ + if (is_sprite && !config->sprites_enabled) + return 0; + + /* HSW allows LP1+ watermarks even with multiple pipes */ + if (level == 0 || config->num_pipes_active > 1) { + fifo_size /= INTEL_NUM_PIPES(display); + + /* + * For some reason the non self refresh + * FIFO size is only half of the self + * refresh FIFO size on ILK/SNB. + */ + if (DISPLAY_VER(display) < 7) + fifo_size /= 2; + } + + if (config->sprites_enabled) { + /* level 0 is always calculated with 1:1 split */ + if (level > 0 && ddb_partitioning == INTEL_DDB_PART_5_6) { + if (is_sprite) + fifo_size *= 5; + fifo_size /= 6; + } else { + fifo_size /= 2; + } + } + + /* clamp to max that the registers can hold */ + return min(fifo_size, ilk_plane_wm_reg_max(display, level, is_sprite)); +} + +/* Calculate the maximum cursor plane watermark */ +static unsigned int ilk_cursor_wm_max(struct intel_display *display, + int level, + const struct intel_wm_config *config) +{ + /* HSW LP1+ watermarks w/ multiple pipes */ + if (level > 0 && config->num_pipes_active > 1) + return 64; + + /* otherwise just report max that registers can hold */ + return ilk_cursor_wm_reg_max(display, level); +} + +static void ilk_compute_wm_maximums(struct intel_display *display, + int level, + const struct intel_wm_config *config, + enum intel_ddb_partitioning ddb_partitioning, + struct ilk_wm_maximums *max) +{ + max->pri = ilk_plane_wm_max(display, level, config, ddb_partitioning, false); + max->spr = ilk_plane_wm_max(display, level, config, ddb_partitioning, true); + max->cur = ilk_cursor_wm_max(display, level, config); + max->fbc = ilk_fbc_wm_reg_max(display); +} + +static void ilk_compute_wm_reg_maximums(struct intel_display *display, + int level, + struct ilk_wm_maximums *max) +{ + max->pri = ilk_plane_wm_reg_max(display, level, false); + max->spr = ilk_plane_wm_reg_max(display, level, true); + max->cur = ilk_cursor_wm_reg_max(display, level); + max->fbc = ilk_fbc_wm_reg_max(display); +} + +static bool ilk_validate_wm_level(struct intel_display *display, + int level, + const struct ilk_wm_maximums *max, + struct intel_wm_level *result) +{ + bool ret; + + /* already determined to be invalid? */ + if (!result->enable) + return false; + + result->enable = result->pri_val <= max->pri && + result->spr_val <= max->spr && + result->cur_val <= max->cur; + + ret = result->enable; + + /* + * HACK until we can pre-compute everything, + * and thus fail gracefully if LP0 watermarks + * are exceeded... + */ + if (level == 0 && !result->enable) { + if (result->pri_val > max->pri) + drm_dbg_kms(display->drm, + "Primary WM%d too large %u (max %u)\n", + level, result->pri_val, max->pri); + if (result->spr_val > max->spr) + drm_dbg_kms(display->drm, + "Sprite WM%d too large %u (max %u)\n", + level, result->spr_val, max->spr); + if (result->cur_val > max->cur) + drm_dbg_kms(display->drm, + "Cursor WM%d too large %u (max %u)\n", + level, result->cur_val, max->cur); + + result->pri_val = min_t(u32, result->pri_val, max->pri); + result->spr_val = min_t(u32, result->spr_val, max->spr); + result->cur_val = min_t(u32, result->cur_val, max->cur); + result->enable = true; + } + + return ret; +} + +static void ilk_compute_wm_level(struct intel_display *display, + const struct intel_crtc *crtc, + int level, + struct intel_crtc_state *crtc_state, + const struct intel_plane_state *pristate, + const struct intel_plane_state *sprstate, + const struct intel_plane_state *curstate, + struct intel_wm_level *result) +{ + u16 pri_latency = display->wm.pri_latency[level]; + u16 spr_latency = display->wm.spr_latency[level]; + u16 cur_latency = display->wm.cur_latency[level]; + + /* WM1+ latency values stored in 0.5us units */ + if (level > 0) { + pri_latency *= 5; + spr_latency *= 5; + cur_latency *= 5; + } + + if (pristate) { + result->pri_val = ilk_compute_pri_wm(crtc_state, pristate, + pri_latency, level); + result->fbc_val = ilk_compute_fbc_wm(crtc_state, pristate, result->pri_val); + } + + if (sprstate) + result->spr_val = ilk_compute_spr_wm(crtc_state, sprstate, spr_latency); + + if (curstate) + result->cur_val = ilk_compute_cur_wm(crtc_state, curstate, cur_latency); + + result->enable = true; +} + +static void hsw_read_wm_latency(struct intel_display *display, u16 wm[]) +{ + u64 sskpd; + + display->wm.num_levels = 5; + + sskpd = intel_mchbar_read64_2x32(display, MCH_SSKPD); + + wm[0] = REG_FIELD_GET64(SSKPD_NEW_WM0_MASK_HSW, sskpd); + if (wm[0] == 0) + wm[0] = REG_FIELD_GET64(SSKPD_OLD_WM0_MASK_HSW, sskpd); + wm[1] = REG_FIELD_GET64(SSKPD_WM1_MASK_HSW, sskpd); + wm[2] = REG_FIELD_GET64(SSKPD_WM2_MASK_HSW, sskpd); + wm[3] = REG_FIELD_GET64(SSKPD_WM3_MASK_HSW, sskpd); + wm[4] = REG_FIELD_GET64(SSKPD_WM4_MASK_HSW, sskpd); +} + +static void snb_read_wm_latency(struct intel_display *display, u16 wm[]) +{ + u32 sskpd; + + display->wm.num_levels = 4; + + sskpd = intel_mchbar_read(display, MCH_SSKPD); + + wm[0] = REG_FIELD_GET(SSKPD_WM0_MASK_SNB, sskpd); + wm[1] = REG_FIELD_GET(SSKPD_WM1_MASK_SNB, sskpd); + wm[2] = REG_FIELD_GET(SSKPD_WM2_MASK_SNB, sskpd); + wm[3] = REG_FIELD_GET(SSKPD_WM3_MASK_SNB, sskpd); +} + +static void ilk_read_wm_latency(struct intel_display *display, u16 wm[]) +{ + u32 mltr; + + display->wm.num_levels = 3; + + mltr = intel_mchbar_read(display, MLTR_ILK); + + /* ILK primary LP0 latency is 700 ns */ + wm[0] = 7; + wm[1] = REG_FIELD_GET(MLTR_WM1_MASK, mltr); + wm[2] = REG_FIELD_GET(MLTR_WM2_MASK, mltr); +} + +static void intel_fixup_spr_wm_latency(struct intel_display *display, u16 wm[5]) +{ + /* ILK sprite LP0 latency is 1300 ns */ + if (DISPLAY_VER(display) == 5) + wm[0] = 13; +} + +static void intel_fixup_cur_wm_latency(struct intel_display *display, u16 wm[5]) +{ + /* ILK cursor LP0 latency is 1300 ns */ + if (DISPLAY_VER(display) == 5) + wm[0] = 13; +} + +static bool ilk_increase_wm_latency(struct intel_display *display, u16 wm[5], u16 min) +{ + int level; + + if (wm[0] >= min) + return false; + + wm[0] = max(wm[0], min); + for (level = 1; level < display->wm.num_levels; level++) + wm[level] = max_t(u16, wm[level], DIV_ROUND_UP(min, 5)); + + return true; +} + +static void snb_wm_latency_quirk(struct intel_display *display) +{ + bool changed; + + /* + * The BIOS provided WM memory latency values are often + * inadequate for high resolution displays. Adjust them. + */ + changed = ilk_increase_wm_latency(display, display->wm.pri_latency, 12); + changed |= ilk_increase_wm_latency(display, display->wm.spr_latency, 12); + changed |= ilk_increase_wm_latency(display, display->wm.cur_latency, 12); + + if (!changed) + return; + + drm_dbg_kms(display->drm, + "WM latency values increased to avoid potential underruns\n"); + intel_print_wm_latency(display, "Primary", display->wm.pri_latency); + intel_print_wm_latency(display, "Sprite", display->wm.spr_latency); + intel_print_wm_latency(display, "Cursor", display->wm.cur_latency); +} + +static void snb_wm_lp3_irq_quirk(struct intel_display *display) +{ + /* + * On some SNB machines (Thinkpad X220 Tablet at least) + * LP3 usage can cause vblank interrupts to be lost. + * The DEIIR bit will go high but it looks like the CPU + * never gets interrupted. + * + * It's not clear whether other interrupt source could + * be affected or if this is somehow limited to vblank + * interrupts only. To play it safe we disable LP3 + * watermarks entirely. + */ + if (display->wm.pri_latency[3] == 0 && + display->wm.spr_latency[3] == 0 && + display->wm.cur_latency[3] == 0) + return; + + display->wm.pri_latency[3] = 0; + display->wm.spr_latency[3] = 0; + display->wm.cur_latency[3] = 0; + + drm_dbg_kms(display->drm, + "LP3 watermarks disabled due to potential for lost interrupts\n"); + intel_print_wm_latency(display, "Primary", display->wm.pri_latency); + intel_print_wm_latency(display, "Sprite", display->wm.spr_latency); + intel_print_wm_latency(display, "Cursor", display->wm.cur_latency); +} + +static void ilk_setup_wm_latency(struct intel_display *display) +{ + if (display->platform.broadwell || display->platform.haswell) + hsw_read_wm_latency(display, display->wm.pri_latency); + else if (DISPLAY_VER(display) >= 6) + snb_read_wm_latency(display, display->wm.pri_latency); + else + ilk_read_wm_latency(display, display->wm.pri_latency); + + memcpy(display->wm.spr_latency, display->wm.pri_latency, + sizeof(display->wm.pri_latency)); + memcpy(display->wm.cur_latency, display->wm.pri_latency, + sizeof(display->wm.pri_latency)); + + intel_fixup_spr_wm_latency(display, display->wm.spr_latency); + intel_fixup_cur_wm_latency(display, display->wm.cur_latency); + + intel_print_wm_latency(display, "Primary", display->wm.pri_latency); + intel_print_wm_latency(display, "Sprite", display->wm.spr_latency); + intel_print_wm_latency(display, "Cursor", display->wm.cur_latency); + + if (DISPLAY_VER(display) == 6) { + snb_wm_latency_quirk(display); + snb_wm_lp3_irq_quirk(display); + } +} + +static bool ilk_validate_pipe_wm(struct intel_display *display, + struct intel_pipe_wm *pipe_wm) +{ + /* LP0 watermark maximums depend on this pipe alone */ + const struct intel_wm_config config = { + .num_pipes_active = 1, + .sprites_enabled = pipe_wm->sprites_enabled, + .sprites_scaled = pipe_wm->sprites_scaled, + }; + struct ilk_wm_maximums max; + + /* LP0 watermarks always use 1/2 DDB partitioning */ + ilk_compute_wm_maximums(display, 0, &config, INTEL_DDB_PART_1_2, &max); + + /* At least LP0 must be valid */ + if (!ilk_validate_wm_level(display, 0, &max, &pipe_wm->wm[0])) { + drm_dbg_kms(display->drm, "LP0 watermark invalid\n"); + return false; + } + + return true; +} + +/* Compute new watermarks for the pipe */ +static int ilk_compute_pipe_wm(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(state); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + struct intel_pipe_wm *pipe_wm; + struct intel_plane *plane; + const struct intel_plane_state *plane_state; + const struct intel_plane_state *pristate = NULL; + const struct intel_plane_state *sprstate = NULL; + const struct intel_plane_state *curstate = NULL; + struct ilk_wm_maximums max; + int level, usable_level; + + pipe_wm = &crtc_state->wm.ilk.optimal; + + intel_atomic_crtc_state_for_each_plane_state(plane, plane_state, crtc_state) { + if (plane->base.type == DRM_PLANE_TYPE_PRIMARY) + pristate = plane_state; + else if (plane->base.type == DRM_PLANE_TYPE_OVERLAY) + sprstate = plane_state; + else if (plane->base.type == DRM_PLANE_TYPE_CURSOR) + curstate = plane_state; + } + + pipe_wm->pipe_enabled = crtc_state->hw.active; + pipe_wm->sprites_enabled = crtc_state->active_planes & BIT(PLANE_SPRITE0); + pipe_wm->sprites_scaled = crtc_state->scaled_planes & BIT(PLANE_SPRITE0); + + usable_level = display->wm.num_levels - 1; + + /* ILK/SNB: LP2+ watermarks only w/o sprites */ + if (DISPLAY_VER(display) < 7 && pipe_wm->sprites_enabled) + usable_level = 1; + + /* ILK/SNB/IVB: LP1+ watermarks only w/o scaling */ + if (pipe_wm->sprites_scaled) + usable_level = 0; + + memset(&pipe_wm->wm, 0, sizeof(pipe_wm->wm)); + ilk_compute_wm_level(display, crtc, 0, crtc_state, + pristate, sprstate, curstate, &pipe_wm->wm[0]); + + if (!ilk_validate_pipe_wm(display, pipe_wm)) + return -EINVAL; + + ilk_compute_wm_reg_maximums(display, 1, &max); + + for (level = 1; level <= usable_level; level++) { + struct intel_wm_level *wm = &pipe_wm->wm[level]; + + ilk_compute_wm_level(display, crtc, level, crtc_state, + pristate, sprstate, curstate, wm); + + /* + * Disable any watermark level that exceeds the + * register maximums since such watermarks are + * always invalid. + */ + if (!ilk_validate_wm_level(display, level, &max, wm)) { + memset(wm, 0, sizeof(*wm)); + break; + } + } + + return 0; +} + +/* + * Build a set of 'intermediate' watermark values that satisfy both the old + * state and the new state. These can be programmed to the hardware + * immediately. + */ +static int ilk_compute_intermediate_wm(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + struct intel_crtc_state *new_crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + struct intel_pipe_wm *intermediate = &new_crtc_state->wm.ilk.intermediate; + const struct intel_pipe_wm *optimal = &new_crtc_state->wm.ilk.optimal; + const struct intel_pipe_wm *active = &old_crtc_state->wm.ilk.optimal; + int level; + + /* + * Start with the final, target watermarks, then combine with the + * currently active watermarks to get values that are safe both before + * and after the vblank. + */ + *intermediate = *optimal; + if (!new_crtc_state->hw.active || + intel_crtc_needs_modeset(new_crtc_state) || + state->skip_intermediate_wm) + return 0; + + intermediate->pipe_enabled |= active->pipe_enabled; + intermediate->sprites_enabled |= active->sprites_enabled; + intermediate->sprites_scaled |= active->sprites_scaled; + + for (level = 0; level < display->wm.num_levels; level++) { + struct intel_wm_level *intermediate_wm = &intermediate->wm[level]; + const struct intel_wm_level *active_wm = &active->wm[level]; + + intermediate_wm->enable &= active_wm->enable; + intermediate_wm->pri_val = max(intermediate_wm->pri_val, + active_wm->pri_val); + intermediate_wm->spr_val = max(intermediate_wm->spr_val, + active_wm->spr_val); + intermediate_wm->cur_val = max(intermediate_wm->cur_val, + active_wm->cur_val); + intermediate_wm->fbc_val = max(intermediate_wm->fbc_val, + active_wm->fbc_val); + } + + /* + * We need to make sure that these merged watermark values are + * actually a valid configuration themselves. If they're not, + * there's no safe way to transition from the old state to + * the new state, so we need to fail the atomic transaction. + */ + if (!ilk_validate_pipe_wm(display, intermediate)) + return -EINVAL; + + /* + * If our intermediate WM are identical to the final WM, then we can + * omit the post-vblank programming; only update if it's different. + */ + if (memcmp(intermediate, optimal, sizeof(*intermediate)) != 0) + new_crtc_state->wm.need_postvbl_update = true; + + return 0; +} + +static int ilk_compute_watermarks(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + int ret; + + ret = ilk_compute_pipe_wm(state, crtc); + if (ret) + return ret; + + ret = ilk_compute_intermediate_wm(state, crtc); + if (ret) + return ret; + + return 0; +} + +/* + * Merge the watermarks from all active pipes for a specific level. + */ +static void ilk_merge_wm_level(struct intel_display *display, + int level, + struct intel_wm_level *ret_wm) +{ + const struct intel_crtc *crtc; + + ret_wm->enable = true; + + for_each_intel_crtc(display, crtc) { + const struct intel_pipe_wm *active = &crtc->wm.active.ilk; + const struct intel_wm_level *wm = &active->wm[level]; + + if (!active->pipe_enabled) + continue; + + /* + * The watermark values may have been used in the past, + * so we must maintain them in the registers for some + * time even if the level is now disabled. + */ + if (!wm->enable) + ret_wm->enable = false; + + ret_wm->pri_val = max(ret_wm->pri_val, wm->pri_val); + ret_wm->spr_val = max(ret_wm->spr_val, wm->spr_val); + ret_wm->cur_val = max(ret_wm->cur_val, wm->cur_val); + ret_wm->fbc_val = max(ret_wm->fbc_val, wm->fbc_val); + } +} + +/* + * Merge all low power watermarks for all active pipes. + */ +static void ilk_wm_merge(struct intel_display *display, + const struct intel_wm_config *config, + const struct ilk_wm_maximums *max, + struct intel_pipe_wm *merged) +{ + int level, num_levels = display->wm.num_levels; + int last_enabled_level = num_levels - 1; + + /* ILK/SNB/IVB: LP1+ watermarks only w/ single pipe */ + if ((DISPLAY_VER(display) < 7 || display->platform.ivybridge) && + config->num_pipes_active > 1) + last_enabled_level = 0; + + /* ILK: FBC WM must be disabled always */ + merged->fbc_wm_enabled = DISPLAY_VER(display) >= 6; + + /* merge each WM1+ level */ + for (level = 1; level < num_levels; level++) { + struct intel_wm_level *wm = &merged->wm[level]; + + ilk_merge_wm_level(display, level, wm); + + if (level > last_enabled_level) + wm->enable = false; + else if (!ilk_validate_wm_level(display, level, max, wm)) + /* make sure all following levels get disabled */ + last_enabled_level = level - 1; + + /* + * The spec says it is preferred to disable + * FBC WMs instead of disabling a WM level. + */ + if (wm->fbc_val > max->fbc) { + if (wm->enable) + merged->fbc_wm_enabled = false; + wm->fbc_val = 0; + } + } + + /* ILK: LP2+ must be disabled when FBC WM is disabled but FBC enabled */ + if (DISPLAY_VER(display) == 5 && HAS_FBC(display) && + display->params.enable_fbc && !merged->fbc_wm_enabled) { + for (level = 2; level < num_levels; level++) { + struct intel_wm_level *wm = &merged->wm[level]; + + wm->enable = false; + } + } +} + +static int ilk_wm_lp_to_level(int wm_lp, const struct intel_pipe_wm *pipe_wm) +{ + /* LP1,LP2,LP3 levels are either 1,2,3 or 1,3,4 */ + return wm_lp + (wm_lp >= 2 && pipe_wm->wm[4].enable); +} + +/* The value we need to program into the WM_LPx latency field */ +static unsigned int ilk_wm_lp_latency(struct intel_display *display, + int level) +{ + if (display->platform.haswell || display->platform.broadwell) + return 2 * level; + else + return display->wm.pri_latency[level]; +} + +static void ilk_compute_wm_results(struct intel_display *display, + const struct intel_pipe_wm *merged, + enum intel_ddb_partitioning partitioning, + struct ilk_wm_values *results) +{ + struct intel_crtc *crtc; + int level, wm_lp; + + results->enable_fbc_wm = merged->fbc_wm_enabled; + results->partitioning = partitioning; + + /* LP1+ register values */ + for (wm_lp = 1; wm_lp <= 3; wm_lp++) { + const struct intel_wm_level *r; + + level = ilk_wm_lp_to_level(wm_lp, merged); + + r = &merged->wm[level]; + + /* + * Maintain the watermark values even if the level is + * disabled. Doing otherwise could cause underruns. + */ + results->wm_lp[wm_lp - 1] = + WM_LP_LATENCY(ilk_wm_lp_latency(display, level)) | + WM_LP_PRIMARY(r->pri_val) | + WM_LP_CURSOR(r->cur_val); + + if (r->enable) + results->wm_lp[wm_lp - 1] |= WM_LP_ENABLE; + + if (DISPLAY_VER(display) >= 8) + results->wm_lp[wm_lp - 1] |= WM_LP_FBC_BDW(r->fbc_val); + else + results->wm_lp[wm_lp - 1] |= WM_LP_FBC_ILK(r->fbc_val); + + results->wm_lp_spr[wm_lp - 1] = WM_LP_SPRITE(r->spr_val); + + /* + * Always set WM_LP_SPRITE_EN when spr_val != 0, even if the + * level is disabled. Doing otherwise could cause underruns. + */ + if (DISPLAY_VER(display) < 7 && r->spr_val) { + drm_WARN_ON(display->drm, wm_lp != 1); + results->wm_lp_spr[wm_lp - 1] |= WM_LP_SPRITE_ENABLE; + } + } + + /* LP0 register values */ + for_each_intel_crtc(display, crtc) { + enum pipe pipe = crtc->pipe; + const struct intel_pipe_wm *pipe_wm = &crtc->wm.active.ilk; + const struct intel_wm_level *r = &pipe_wm->wm[0]; + + if (drm_WARN_ON(display->drm, !r->enable)) + continue; + + results->wm_pipe[pipe] = + WM0_PIPE_PRIMARY(r->pri_val) | + WM0_PIPE_SPRITE(r->spr_val) | + WM0_PIPE_CURSOR(r->cur_val); + } +} + +/* + * Find the result with the highest level enabled. Check for enable_fbc_wm in + * case both are at the same level. Prefer r1 in case they're the same. + */ +static struct intel_pipe_wm * +ilk_find_best_result(struct intel_display *display, + struct intel_pipe_wm *r1, + struct intel_pipe_wm *r2) +{ + int level, level1 = 0, level2 = 0; + + for (level = 1; level < display->wm.num_levels; level++) { + if (r1->wm[level].enable) + level1 = level; + if (r2->wm[level].enable) + level2 = level; + } + + if (level1 == level2) { + if (r2->fbc_wm_enabled && !r1->fbc_wm_enabled) + return r2; + else + return r1; + } else if (level1 > level2) { + return r1; + } else { + return r2; + } +} + +/* dirty bits used to track which watermarks need changes */ +#define WM_DIRTY_PIPE(pipe) (1 << (pipe)) +#define WM_DIRTY_LP(wm_lp) (1 << (15 + (wm_lp))) +#define WM_DIRTY_LP_ALL (WM_DIRTY_LP(1) | WM_DIRTY_LP(2) | WM_DIRTY_LP(3)) +#define WM_DIRTY_FBC (1 << 24) +#define WM_DIRTY_DDB (1 << 25) + +static unsigned int ilk_compute_wm_dirty(struct intel_display *display, + const struct ilk_wm_values *old, + const struct ilk_wm_values *new) +{ + unsigned int dirty = 0; + enum pipe pipe; + int wm_lp; + + for_each_pipe(display, pipe) { + if (old->wm_pipe[pipe] != new->wm_pipe[pipe]) { + dirty |= WM_DIRTY_PIPE(pipe); + /* Must disable LP1+ watermarks too */ + dirty |= WM_DIRTY_LP_ALL; + } + } + + if (old->enable_fbc_wm != new->enable_fbc_wm) { + dirty |= WM_DIRTY_FBC; + /* Must disable LP1+ watermarks too */ + dirty |= WM_DIRTY_LP_ALL; + } + + if (old->partitioning != new->partitioning) { + dirty |= WM_DIRTY_DDB; + /* Must disable LP1+ watermarks too */ + dirty |= WM_DIRTY_LP_ALL; + } + + /* LP1+ watermarks already deemed dirty, no need to continue */ + if (dirty & WM_DIRTY_LP_ALL) + return dirty; + + /* Find the lowest numbered LP1+ watermark in need of an update... */ + for (wm_lp = 1; wm_lp <= 3; wm_lp++) { + if (old->wm_lp[wm_lp - 1] != new->wm_lp[wm_lp - 1] || + old->wm_lp_spr[wm_lp - 1] != new->wm_lp_spr[wm_lp - 1]) + break; + } + + /* ...and mark it and all higher numbered LP1+ watermarks as dirty */ + for (; wm_lp <= 3; wm_lp++) + dirty |= WM_DIRTY_LP(wm_lp); + + return dirty; +} + +static bool _ilk_disable_lp_wm(struct intel_display *display, + unsigned int dirty) +{ + struct ilk_wm_values *previous = &display->wm.hw; + bool changed = false; + + if (dirty & WM_DIRTY_LP(3) && previous->wm_lp[2] & WM_LP_ENABLE) { + previous->wm_lp[2] &= ~WM_LP_ENABLE; + intel_de_write(display, WM3_LP_ILK, previous->wm_lp[2]); + changed = true; + } + if (dirty & WM_DIRTY_LP(2) && previous->wm_lp[1] & WM_LP_ENABLE) { + previous->wm_lp[1] &= ~WM_LP_ENABLE; + intel_de_write(display, WM2_LP_ILK, previous->wm_lp[1]); + changed = true; + } + if (dirty & WM_DIRTY_LP(1) && previous->wm_lp[0] & WM_LP_ENABLE) { + previous->wm_lp[0] &= ~WM_LP_ENABLE; + intel_de_write(display, WM1_LP_ILK, previous->wm_lp[0]); + changed = true; + } + + /* + * Don't touch WM_LP_SPRITE_ENABLE here. + * Doing so could cause underruns. + */ + + return changed; +} + +/* + * The spec says we shouldn't write when we don't need, because every write + * causes WMs to be re-evaluated, expending some power. + */ +static void ilk_write_wm_values(struct intel_display *display, + struct ilk_wm_values *results) +{ + struct ilk_wm_values *previous = &display->wm.hw; + unsigned int dirty; + + dirty = ilk_compute_wm_dirty(display, previous, results); + if (!dirty) + return; + + _ilk_disable_lp_wm(display, dirty); + + if (dirty & WM_DIRTY_PIPE(PIPE_A)) + intel_de_write(display, WM0_PIPE_ILK(PIPE_A), results->wm_pipe[0]); + if (dirty & WM_DIRTY_PIPE(PIPE_B)) + intel_de_write(display, WM0_PIPE_ILK(PIPE_B), results->wm_pipe[1]); + if (dirty & WM_DIRTY_PIPE(PIPE_C)) + intel_de_write(display, WM0_PIPE_ILK(PIPE_C), results->wm_pipe[2]); + + if (dirty & WM_DIRTY_DDB) { + if (display->platform.haswell || display->platform.broadwell) + intel_de_rmw(display, WM_MISC, WM_MISC_DATA_PARTITION_5_6, + results->partitioning == INTEL_DDB_PART_1_2 ? 0 : + WM_MISC_DATA_PARTITION_5_6); + else + intel_de_rmw(display, DISP_ARB_CTL2, DISP_DATA_PARTITION_5_6, + results->partitioning == INTEL_DDB_PART_1_2 ? 0 : + DISP_DATA_PARTITION_5_6); + } + + if (dirty & WM_DIRTY_FBC) + intel_de_rmw(display, DISP_ARB_CTL, DISP_FBC_WM_DIS, + results->enable_fbc_wm ? 0 : DISP_FBC_WM_DIS); + + if (dirty & WM_DIRTY_LP(1) && + previous->wm_lp_spr[0] != results->wm_lp_spr[0]) + intel_de_write(display, WM1S_LP_ILK, results->wm_lp_spr[0]); + + if (DISPLAY_VER(display) >= 7) { + if (dirty & WM_DIRTY_LP(2) && previous->wm_lp_spr[1] != results->wm_lp_spr[1]) + intel_de_write(display, WM2S_LP_IVB, results->wm_lp_spr[1]); + if (dirty & WM_DIRTY_LP(3) && previous->wm_lp_spr[2] != results->wm_lp_spr[2]) + intel_de_write(display, WM3S_LP_IVB, results->wm_lp_spr[2]); + } + + if (dirty & WM_DIRTY_LP(1) && previous->wm_lp[0] != results->wm_lp[0]) + intel_de_write(display, WM1_LP_ILK, results->wm_lp[0]); + if (dirty & WM_DIRTY_LP(2) && previous->wm_lp[1] != results->wm_lp[1]) + intel_de_write(display, WM2_LP_ILK, results->wm_lp[1]); + if (dirty & WM_DIRTY_LP(3) && previous->wm_lp[2] != results->wm_lp[2]) + intel_de_write(display, WM3_LP_ILK, results->wm_lp[2]); + + display->wm.hw = *results; +} + +bool ilk_disable_cxsr(struct intel_display *display) +{ + return _ilk_disable_lp_wm(display, WM_DIRTY_LP_ALL); +} + +static void ilk_compute_wm_config(struct intel_display *display, + struct intel_wm_config *config) +{ + struct intel_crtc *crtc; + + /* Compute the currently _active_ config */ + for_each_intel_crtc(display, crtc) { + const struct intel_pipe_wm *wm = &crtc->wm.active.ilk; + + if (!wm->pipe_enabled) + continue; + + config->sprites_enabled |= wm->sprites_enabled; + config->sprites_scaled |= wm->sprites_scaled; + config->num_pipes_active++; + } +} + +static void ilk_program_watermarks(struct intel_display *display) +{ + struct intel_pipe_wm lp_wm_1_2 = {}, lp_wm_5_6 = {}, *best_lp_wm; + struct ilk_wm_maximums max; + struct intel_wm_config config = {}; + struct ilk_wm_values results = {}; + enum intel_ddb_partitioning partitioning; + + ilk_compute_wm_config(display, &config); + + ilk_compute_wm_maximums(display, 1, &config, INTEL_DDB_PART_1_2, &max); + ilk_wm_merge(display, &config, &max, &lp_wm_1_2); + + /* 5/6 split only in single pipe config on IVB+ */ + if (DISPLAY_VER(display) >= 7 && + config.num_pipes_active == 1 && config.sprites_enabled) { + ilk_compute_wm_maximums(display, 1, &config, INTEL_DDB_PART_5_6, &max); + ilk_wm_merge(display, &config, &max, &lp_wm_5_6); + + best_lp_wm = ilk_find_best_result(display, &lp_wm_1_2, &lp_wm_5_6); + } else { + best_lp_wm = &lp_wm_1_2; + } + + partitioning = (best_lp_wm == &lp_wm_1_2) ? + INTEL_DDB_PART_1_2 : INTEL_DDB_PART_5_6; + + ilk_compute_wm_results(display, best_lp_wm, partitioning, &results); + + ilk_write_wm_values(display, &results); +} + +static void ilk_initial_watermarks(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + const struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + mutex_lock(&display->wm.wm_mutex); + crtc->wm.active.ilk = crtc_state->wm.ilk.intermediate; + ilk_program_watermarks(display); + mutex_unlock(&display->wm.wm_mutex); +} + +static void ilk_optimize_watermarks(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + const struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + if (!crtc_state->wm.need_postvbl_update) + return; + + mutex_lock(&display->wm.wm_mutex); + crtc->wm.active.ilk = crtc_state->wm.ilk.optimal; + ilk_program_watermarks(display); + mutex_unlock(&display->wm.wm_mutex); +} + +static void ilk_pipe_wm_get_hw_state(struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + struct ilk_wm_values *hw = &display->wm.hw; + struct intel_crtc_state *crtc_state = to_intel_crtc_state(crtc->base.state); + struct intel_pipe_wm *active = &crtc_state->wm.ilk.optimal; + enum pipe pipe = crtc->pipe; + + hw->wm_pipe[pipe] = intel_de_read(display, WM0_PIPE_ILK(pipe)); + + memset(active, 0, sizeof(*active)); + + active->pipe_enabled = crtc->active; + + if (active->pipe_enabled) { + u32 tmp = hw->wm_pipe[pipe]; + + /* + * For active pipes LP0 watermark is marked as + * enabled, and LP1+ watermaks as disabled since + * we can't really reverse compute them in case + * multiple pipes are active. + */ + active->wm[0].enable = true; + active->wm[0].pri_val = REG_FIELD_GET(WM0_PIPE_PRIMARY_MASK, tmp); + active->wm[0].spr_val = REG_FIELD_GET(WM0_PIPE_SPRITE_MASK, tmp); + active->wm[0].cur_val = REG_FIELD_GET(WM0_PIPE_CURSOR_MASK, tmp); + } else { + int level; + + /* + * For inactive pipes, all watermark levels + * should be marked as enabled but zeroed, + * which is what we'd compute them to. + */ + for (level = 0; level < display->wm.num_levels; level++) + active->wm[level].enable = true; + } + + crtc->wm.active.ilk = *active; +} + +static int ilk_sanitize_watermarks_add_affected(struct drm_atomic_commit *state) +{ + struct intel_display *display = to_intel_display(state->dev); + struct drm_plane *plane; + struct intel_crtc *crtc; + + for_each_intel_crtc(display, 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. + */ +void ilk_wm_sanitize(struct intel_display *display) +{ + struct drm_atomic_commit *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; + + /* Only supported on platforms that use atomic watermark design */ + if (!display->wm.funcs->optimize_watermarks) + return; + + if (drm_WARN_ON(display->drm, DISPLAY_VER(display) >= 9)) + return; + + state = drm_atomic_commit_alloc(display->drm); + if (drm_WARN_ON(display->drm, !state)) + return; + + intel_state = to_intel_atomic_state(state); + + drm_modeset_acquire_init(&ctx, 0); + + state->acquire_ctx = &ctx; + to_intel_atomic_state(state)->internal = true; + +retry: + /* + * 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(display)) + intel_state->skip_intermediate_wm = true; + + ret = ilk_sanitize_watermarks_add_affected(state); + if (ret) + goto fail; + + ret = intel_atomic_check(display->drm, state); + if (ret) + goto fail; + + /* Write calculated watermark values back */ + for_each_new_intel_crtc_in_state(intel_state, crtc, crtc_state) { + 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_commit_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(display->drm, ret, + "Could not determine valid watermarks for inherited state\n"); + + drm_atomic_commit_put(state); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +#define _FW_WM(value, plane) \ + (((value) & DSPFW_ ## plane ## _MASK) >> DSPFW_ ## plane ## _SHIFT) +#define _FW_WM_VLV(value, plane) \ + (((value) & DSPFW_ ## plane ## _MASK_VLV) >> DSPFW_ ## plane ## _SHIFT) + +static void g4x_read_wm_values(struct intel_display *display, + struct g4x_wm_values *wm) +{ + u32 tmp; + + tmp = intel_de_read(display, DSPFW1(display)); + wm->sr.plane = _FW_WM(tmp, SR); + wm->pipe[PIPE_B].plane[PLANE_CURSOR] = _FW_WM(tmp, CURSORB); + wm->pipe[PIPE_B].plane[PLANE_PRIMARY] = _FW_WM(tmp, PLANEB); + wm->pipe[PIPE_A].plane[PLANE_PRIMARY] = _FW_WM(tmp, PLANEA); + + tmp = intel_de_read(display, DSPFW2(display)); + wm->fbc_en = tmp & DSPFW_FBC_SR_EN; + wm->sr.fbc = _FW_WM(tmp, FBC_SR); + wm->hpll.fbc = _FW_WM(tmp, FBC_HPLL_SR); + wm->pipe[PIPE_B].plane[PLANE_SPRITE0] = _FW_WM(tmp, SPRITEB); + wm->pipe[PIPE_A].plane[PLANE_CURSOR] = _FW_WM(tmp, CURSORA); + wm->pipe[PIPE_A].plane[PLANE_SPRITE0] = _FW_WM(tmp, SPRITEA); + + tmp = intel_de_read(display, DSPFW3(display)); + wm->hpll_en = tmp & DSPFW_HPLL_SR_EN; + wm->sr.cursor = _FW_WM(tmp, CURSOR_SR); + wm->hpll.cursor = _FW_WM(tmp, HPLL_CURSOR); + wm->hpll.plane = _FW_WM(tmp, HPLL_SR); +} + +static void vlv_read_wm_values(struct intel_display *display, + struct vlv_wm_values *wm) +{ + enum pipe pipe; + u32 tmp; + + for_each_pipe(display, pipe) { + tmp = intel_de_read(display, VLV_DDL(pipe)); + + wm->ddl[pipe].plane[PLANE_PRIMARY] = + (tmp >> DDL_PLANE_SHIFT) & (DDL_PRECISION_HIGH | DRAIN_LATENCY_MASK); + wm->ddl[pipe].plane[PLANE_CURSOR] = + (tmp >> DDL_CURSOR_SHIFT) & (DDL_PRECISION_HIGH | DRAIN_LATENCY_MASK); + wm->ddl[pipe].plane[PLANE_SPRITE0] = + (tmp >> DDL_SPRITE_SHIFT(0)) & (DDL_PRECISION_HIGH | DRAIN_LATENCY_MASK); + wm->ddl[pipe].plane[PLANE_SPRITE1] = + (tmp >> DDL_SPRITE_SHIFT(1)) & (DDL_PRECISION_HIGH | DRAIN_LATENCY_MASK); + } + + tmp = intel_de_read(display, DSPFW1(display)); + wm->sr.plane = _FW_WM(tmp, SR); + wm->pipe[PIPE_B].plane[PLANE_CURSOR] = _FW_WM(tmp, CURSORB); + wm->pipe[PIPE_B].plane[PLANE_PRIMARY] = _FW_WM_VLV(tmp, PLANEB); + wm->pipe[PIPE_A].plane[PLANE_PRIMARY] = _FW_WM_VLV(tmp, PLANEA); + + tmp = intel_de_read(display, DSPFW2(display)); + wm->pipe[PIPE_A].plane[PLANE_SPRITE1] = _FW_WM_VLV(tmp, SPRITEB); + wm->pipe[PIPE_A].plane[PLANE_CURSOR] = _FW_WM(tmp, CURSORA); + wm->pipe[PIPE_A].plane[PLANE_SPRITE0] = _FW_WM_VLV(tmp, SPRITEA); + + tmp = intel_de_read(display, DSPFW3(display)); + wm->sr.cursor = _FW_WM(tmp, CURSOR_SR); + + if (display->platform.cherryview) { + tmp = intel_de_read(display, DSPFW7_CHV); + wm->pipe[PIPE_B].plane[PLANE_SPRITE1] = _FW_WM_VLV(tmp, SPRITED); + wm->pipe[PIPE_B].plane[PLANE_SPRITE0] = _FW_WM_VLV(tmp, SPRITEC); + + tmp = intel_de_read(display, DSPFW8_CHV); + wm->pipe[PIPE_C].plane[PLANE_SPRITE1] = _FW_WM_VLV(tmp, SPRITEF); + wm->pipe[PIPE_C].plane[PLANE_SPRITE0] = _FW_WM_VLV(tmp, SPRITEE); + + tmp = intel_de_read(display, DSPFW9_CHV); + wm->pipe[PIPE_C].plane[PLANE_PRIMARY] = _FW_WM_VLV(tmp, PLANEC); + wm->pipe[PIPE_C].plane[PLANE_CURSOR] = _FW_WM(tmp, CURSORC); + + tmp = intel_de_read(display, DSPHOWM); + wm->sr.plane |= _FW_WM(tmp, SR_HI) << 9; + wm->pipe[PIPE_C].plane[PLANE_SPRITE1] |= _FW_WM(tmp, SPRITEF_HI) << 8; + wm->pipe[PIPE_C].plane[PLANE_SPRITE0] |= _FW_WM(tmp, SPRITEE_HI) << 8; + wm->pipe[PIPE_C].plane[PLANE_PRIMARY] |= _FW_WM(tmp, PLANEC_HI) << 8; + wm->pipe[PIPE_B].plane[PLANE_SPRITE1] |= _FW_WM(tmp, SPRITED_HI) << 8; + wm->pipe[PIPE_B].plane[PLANE_SPRITE0] |= _FW_WM(tmp, SPRITEC_HI) << 8; + wm->pipe[PIPE_B].plane[PLANE_PRIMARY] |= _FW_WM(tmp, PLANEB_HI) << 8; + wm->pipe[PIPE_A].plane[PLANE_SPRITE1] |= _FW_WM(tmp, SPRITEB_HI) << 8; + wm->pipe[PIPE_A].plane[PLANE_SPRITE0] |= _FW_WM(tmp, SPRITEA_HI) << 8; + wm->pipe[PIPE_A].plane[PLANE_PRIMARY] |= _FW_WM(tmp, PLANEA_HI) << 8; + } else { + tmp = intel_de_read(display, DSPFW7); + wm->pipe[PIPE_B].plane[PLANE_SPRITE1] = _FW_WM_VLV(tmp, SPRITED); + wm->pipe[PIPE_B].plane[PLANE_SPRITE0] = _FW_WM_VLV(tmp, SPRITEC); + + tmp = intel_de_read(display, DSPHOWM); + wm->sr.plane |= _FW_WM(tmp, SR_HI) << 9; + wm->pipe[PIPE_B].plane[PLANE_SPRITE1] |= _FW_WM(tmp, SPRITED_HI) << 8; + wm->pipe[PIPE_B].plane[PLANE_SPRITE0] |= _FW_WM(tmp, SPRITEC_HI) << 8; + wm->pipe[PIPE_B].plane[PLANE_PRIMARY] |= _FW_WM(tmp, PLANEB_HI) << 8; + wm->pipe[PIPE_A].plane[PLANE_SPRITE1] |= _FW_WM(tmp, SPRITEB_HI) << 8; + wm->pipe[PIPE_A].plane[PLANE_SPRITE0] |= _FW_WM(tmp, SPRITEA_HI) << 8; + wm->pipe[PIPE_A].plane[PLANE_PRIMARY] |= _FW_WM(tmp, PLANEA_HI) << 8; + } +} + +#undef _FW_WM +#undef _FW_WM_VLV + +static void g4x_wm_get_hw_state(struct intel_display *display) +{ + struct g4x_wm_values *wm = &display->wm.g4x; + struct intel_crtc *crtc; + + g4x_read_wm_values(display, wm); + + wm->cxsr = intel_de_read(display, FW_BLC_SELF) & FW_BLC_SELF_EN; + + for_each_intel_crtc(display, crtc) { + struct intel_crtc_state *crtc_state = + to_intel_crtc_state(crtc->base.state); + struct g4x_wm_state *active = &crtc->wm.active.g4x; + struct g4x_pipe_wm *raw; + enum pipe pipe = crtc->pipe; + enum plane_id plane_id; + int level, max_level; + + active->cxsr = wm->cxsr; + active->hpll_en = wm->hpll_en; + active->fbc_en = wm->fbc_en; + + active->sr = wm->sr; + active->hpll = wm->hpll; + + for_each_plane_id_on_crtc(crtc, plane_id) { + active->wm.plane[plane_id] = + wm->pipe[pipe].plane[plane_id]; + } + + if (wm->cxsr && wm->hpll_en) + max_level = G4X_WM_LEVEL_HPLL; + else if (wm->cxsr) + max_level = G4X_WM_LEVEL_SR; + else + max_level = G4X_WM_LEVEL_NORMAL; + + level = G4X_WM_LEVEL_NORMAL; + raw = &crtc_state->wm.g4x.raw[level]; + for_each_plane_id_on_crtc(crtc, plane_id) + raw->plane[plane_id] = active->wm.plane[plane_id]; + + level = G4X_WM_LEVEL_SR; + if (level > max_level) + goto out; + + raw = &crtc_state->wm.g4x.raw[level]; + raw->plane[PLANE_PRIMARY] = active->sr.plane; + raw->plane[PLANE_CURSOR] = active->sr.cursor; + raw->plane[PLANE_SPRITE0] = 0; + raw->fbc = active->sr.fbc; + + level = G4X_WM_LEVEL_HPLL; + if (level > max_level) + goto out; + + raw = &crtc_state->wm.g4x.raw[level]; + raw->plane[PLANE_PRIMARY] = active->hpll.plane; + raw->plane[PLANE_CURSOR] = active->hpll.cursor; + raw->plane[PLANE_SPRITE0] = 0; + raw->fbc = active->hpll.fbc; + + level++; + out: + for_each_plane_id_on_crtc(crtc, plane_id) + g4x_raw_plane_wm_set(crtc_state, level, + plane_id, USHRT_MAX); + g4x_raw_fbc_wm_set(crtc_state, level, USHRT_MAX); + + g4x_invalidate_wms(crtc, active, level); + + crtc_state->wm.g4x.optimal = *active; + crtc_state->wm.g4x.intermediate = *active; + + drm_dbg_kms(display->drm, + "Initial watermarks: pipe %c, plane=%d, cursor=%d, sprite=%d\n", + pipe_name(pipe), + wm->pipe[pipe].plane[PLANE_PRIMARY], + wm->pipe[pipe].plane[PLANE_CURSOR], + wm->pipe[pipe].plane[PLANE_SPRITE0]); + } + + drm_dbg_kms(display->drm, + "Initial SR watermarks: plane=%d, cursor=%d fbc=%d\n", + wm->sr.plane, wm->sr.cursor, wm->sr.fbc); + drm_dbg_kms(display->drm, + "Initial HPLL watermarks: plane=%d, SR cursor=%d fbc=%d\n", + wm->hpll.plane, wm->hpll.cursor, wm->hpll.fbc); + drm_dbg_kms(display->drm, "Initial SR=%s HPLL=%s FBC=%s\n", + str_yes_no(wm->cxsr), str_yes_no(wm->hpll_en), + str_yes_no(wm->fbc_en)); +} + +static void g4x_wm_sanitize(struct intel_display *display) +{ + struct intel_plane *plane; + struct intel_crtc *crtc; + + mutex_lock(&display->wm.wm_mutex); + + for_each_intel_plane(display->drm, plane) { + struct intel_crtc *crtc = + intel_crtc_for_pipe(display, plane->pipe); + 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); + enum plane_id plane_id = plane->id; + int level; + + if (plane_state->uapi.visible) + continue; + + for (level = 0; level < display->wm.num_levels; level++) { + struct g4x_pipe_wm *raw = + &crtc_state->wm.g4x.raw[level]; + + raw->plane[plane_id] = 0; + + if (plane_id == PLANE_PRIMARY) + raw->fbc = 0; + } + } + + for_each_intel_crtc(display, crtc) { + struct intel_crtc_state *crtc_state = + to_intel_crtc_state(crtc->base.state); + int ret; + + ret = _g4x_compute_pipe_wm(crtc_state); + drm_WARN_ON(display->drm, ret); + + crtc_state->wm.g4x.intermediate = + crtc_state->wm.g4x.optimal; + crtc->wm.active.g4x = crtc_state->wm.g4x.optimal; + } + + g4x_program_watermarks(display); + + mutex_unlock(&display->wm.wm_mutex); +} + +static void vlv_wm_get_hw_state(struct intel_display *display) +{ + struct vlv_wm_values *wm = &display->wm.vlv; + struct intel_crtc *crtc; + u32 val; + int ret; + + vlv_read_wm_values(display, wm); + + wm->cxsr = intel_de_read(display, FW_BLC_SELF_VLV) & FW_CSPWRDWNEN; + wm->level = VLV_WM_LEVEL_PM2; + + if (display->platform.cherryview) { + vlv_punit_get(display); + + val = vlv_punit_read(display, PUNIT_REG_DSPSSPM); + if (val & DSP_MAXFIFO_PM5_ENABLE) + wm->level = VLV_WM_LEVEL_PM5; + + /* + * If DDR DVFS is disabled in the BIOS, Punit + * will never ack the request. So if that happens + * assume we don't have to enable/disable DDR DVFS + * dynamically. To test that just set the REQ_ACK + * bit to poke the Punit, but don't change the + * HIGH/LOW bits so that we don't actually change + * the current state. + */ + val = vlv_punit_read(display, PUNIT_REG_DDR_SETUP2); + val |= FORCE_DDR_FREQ_REQ_ACK; + vlv_punit_write(display, PUNIT_REG_DDR_SETUP2, val); + + ret = poll_timeout_us(val = vlv_punit_read(display, PUNIT_REG_DDR_SETUP2), + (val & FORCE_DDR_FREQ_REQ_ACK) == 0, + 500, 3000, false); + if (ret) { + drm_dbg_kms(display->drm, + "Punit not acking DDR DVFS request, " + "assuming DDR DVFS is disabled\n"); + display->wm.num_levels = VLV_WM_LEVEL_PM5 + 1; + } else { + val = vlv_punit_read(display, PUNIT_REG_DDR_SETUP2); + if ((val & FORCE_DDR_HIGH_FREQ) == 0) + wm->level = VLV_WM_LEVEL_DDR_DVFS; + } + + vlv_punit_put(display); + } + + for_each_intel_crtc(display, crtc) { + struct intel_crtc_state *crtc_state = + to_intel_crtc_state(crtc->base.state); + struct vlv_wm_state *active = &crtc->wm.active.vlv; + const struct vlv_fifo_state *fifo_state = + &crtc_state->wm.vlv.fifo_state; + enum pipe pipe = crtc->pipe; + enum plane_id plane_id; + int level; + + vlv_get_fifo_size(crtc_state); + + active->num_levels = wm->level + 1; + active->cxsr = wm->cxsr; + + for (level = 0; level < active->num_levels; level++) { + struct g4x_pipe_wm *raw = + &crtc_state->wm.vlv.raw[level]; + + active->sr[level].plane = wm->sr.plane; + active->sr[level].cursor = wm->sr.cursor; + + for_each_plane_id_on_crtc(crtc, plane_id) { + active->wm[level].plane[plane_id] = + wm->pipe[pipe].plane[plane_id]; + + raw->plane[plane_id] = + vlv_invert_wm_value(active->wm[level].plane[plane_id], + fifo_state->plane[plane_id]); + } + } + + for_each_plane_id_on_crtc(crtc, plane_id) + vlv_raw_plane_wm_set(crtc_state, level, + plane_id, USHRT_MAX); + vlv_invalidate_wms(crtc, active, level); + + crtc_state->wm.vlv.optimal = *active; + crtc_state->wm.vlv.intermediate = *active; + + drm_dbg_kms(display->drm, + "Initial watermarks: pipe %c, plane=%d, cursor=%d, sprite0=%d, sprite1=%d\n", + pipe_name(pipe), + wm->pipe[pipe].plane[PLANE_PRIMARY], + wm->pipe[pipe].plane[PLANE_CURSOR], + wm->pipe[pipe].plane[PLANE_SPRITE0], + wm->pipe[pipe].plane[PLANE_SPRITE1]); + } + + drm_dbg_kms(display->drm, + "Initial watermarks: SR plane=%d, SR cursor=%d level=%d cxsr=%d\n", + wm->sr.plane, wm->sr.cursor, wm->level, wm->cxsr); +} + +static void vlv_wm_sanitize(struct intel_display *display) +{ + struct intel_plane *plane; + struct intel_crtc *crtc; + + mutex_lock(&display->wm.wm_mutex); + + for_each_intel_plane(display->drm, plane) { + struct intel_crtc *crtc = + intel_crtc_for_pipe(display, plane->pipe); + 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); + enum plane_id plane_id = plane->id; + int level; + + if (plane_state->uapi.visible) + continue; + + for (level = 0; level < display->wm.num_levels; level++) { + struct g4x_pipe_wm *raw = + &crtc_state->wm.vlv.raw[level]; + + raw->plane[plane_id] = 0; + } + } + + for_each_intel_crtc(display, crtc) { + struct intel_crtc_state *crtc_state = + to_intel_crtc_state(crtc->base.state); + int ret; + + ret = _vlv_compute_pipe_wm(crtc_state); + drm_WARN_ON(display->drm, ret); + + crtc_state->wm.vlv.intermediate = + crtc_state->wm.vlv.optimal; + crtc->wm.active.vlv = crtc_state->wm.vlv.optimal; + } + + vlv_program_watermarks(display); + + mutex_unlock(&display->wm.wm_mutex); +} + +/* + * FIXME should probably kill this and improve + * the real watermark readout/sanitation instead + */ +static void ilk_init_lp_watermarks(struct intel_display *display) +{ + intel_de_rmw(display, WM3_LP_ILK, WM_LP_ENABLE, 0); + intel_de_rmw(display, WM2_LP_ILK, WM_LP_ENABLE, 0); + intel_de_rmw(display, WM1_LP_ILK, WM_LP_ENABLE, 0); + + /* + * Don't touch WM_LP_SPRITE_ENABLE here. + * Doing so could cause underruns. + */ +} + +static void ilk_wm_get_hw_state(struct intel_display *display) +{ + struct ilk_wm_values *hw = &display->wm.hw; + struct intel_crtc *crtc; + + ilk_init_lp_watermarks(display); + + for_each_intel_crtc(display, crtc) + ilk_pipe_wm_get_hw_state(crtc); + + hw->wm_lp[0] = intel_de_read(display, WM1_LP_ILK); + hw->wm_lp[1] = intel_de_read(display, WM2_LP_ILK); + hw->wm_lp[2] = intel_de_read(display, WM3_LP_ILK); + + hw->wm_lp_spr[0] = intel_de_read(display, WM1S_LP_ILK); + if (DISPLAY_VER(display) >= 7) { + hw->wm_lp_spr[1] = intel_de_read(display, WM2S_LP_IVB); + hw->wm_lp_spr[2] = intel_de_read(display, WM3S_LP_IVB); + } + + if (display->platform.haswell || display->platform.broadwell) + hw->partitioning = (intel_de_read(display, WM_MISC) & + WM_MISC_DATA_PARTITION_5_6) ? + INTEL_DDB_PART_5_6 : INTEL_DDB_PART_1_2; + else if (display->platform.ivybridge) + hw->partitioning = (intel_de_read(display, DISP_ARB_CTL2) & + DISP_DATA_PARTITION_5_6) ? + INTEL_DDB_PART_5_6 : INTEL_DDB_PART_1_2; + + hw->enable_fbc_wm = + !(intel_de_read(display, DISP_ARB_CTL) & DISP_FBC_WM_DIS); +} + +static const struct intel_wm_funcs ilk_wm_funcs = { + .compute_watermarks = ilk_compute_watermarks, + .initial_watermarks = ilk_initial_watermarks, + .optimize_watermarks = ilk_optimize_watermarks, + .get_hw_state = ilk_wm_get_hw_state, +}; + +static const struct intel_wm_funcs vlv_wm_funcs = { + .compute_watermarks = vlv_compute_watermarks, + .initial_watermarks = vlv_initial_watermarks, + .optimize_watermarks = vlv_optimize_watermarks, + .atomic_update_watermarks = vlv_atomic_update_fifo, + .get_hw_state = vlv_wm_get_hw_state, + .sanitize = vlv_wm_sanitize, +}; + +static const struct intel_wm_funcs g4x_wm_funcs = { + .compute_watermarks = g4x_compute_watermarks, + .initial_watermarks = g4x_initial_watermarks, + .optimize_watermarks = g4x_optimize_watermarks, + .get_hw_state = g4x_wm_get_hw_state, + .sanitize = g4x_wm_sanitize, +}; + +static const struct intel_wm_funcs pnv_wm_funcs = { + .compute_watermarks = i9xx_compute_watermarks, + .update_wm = pnv_update_wm, +}; + +static const struct intel_wm_funcs i965_wm_funcs = { + .compute_watermarks = i9xx_compute_watermarks, + .update_wm = i965_update_wm, +}; + +static const struct intel_wm_funcs i9xx_wm_funcs = { + .compute_watermarks = i9xx_compute_watermarks, + .update_wm = i9xx_update_wm, +}; + +static const struct intel_wm_funcs i845_wm_funcs = { + .compute_watermarks = i9xx_compute_watermarks, + .update_wm = i845_update_wm, +}; + +static const struct intel_wm_funcs nop_funcs = { +}; + +void i9xx_wm_init(struct intel_display *display) +{ + /* For FIFO watermark updates */ + if (HAS_PCH_SPLIT(display)) { + ilk_setup_wm_latency(display); + display->wm.funcs = &ilk_wm_funcs; + } else if (display->platform.valleyview || display->platform.cherryview) { + vlv_setup_wm_latency(display); + display->wm.funcs = &vlv_wm_funcs; + } else if (display->platform.g4x) { + g4x_setup_wm_latency(display); + display->wm.funcs = &g4x_wm_funcs; + } else if (display->platform.pineview) { + if (!pnv_get_cxsr_latency(display)) { + drm_info(display->drm, "Unknown FSB/MEM, disabling CxSR\n"); + /* Disable CxSR and never update its watermark again */ + intel_set_memory_cxsr(display, false); + display->wm.funcs = &nop_funcs; + } else { + display->wm.funcs = &pnv_wm_funcs; + } + } else if (DISPLAY_VER(display) == 4) { + display->wm.funcs = &i965_wm_funcs; + } else if (DISPLAY_VER(display) == 3) { + display->wm.funcs = &i9xx_wm_funcs; + } else if (DISPLAY_VER(display) == 2) { + if (INTEL_NUM_PIPES(display) == 1) + display->wm.funcs = &i845_wm_funcs; + else + display->wm.funcs = &i9xx_wm_funcs; + } else { + drm_err(display->drm, + "unexpected fall-through in %s\n", __func__); + display->wm.funcs = &nop_funcs; + } +} diff --git a/drivers/gpu/drm/i915/display/i9xx_wm.h b/drivers/gpu/drm/i915/display/i9xx_wm.h new file mode 100644 index 000000000..7bb363b2a --- /dev/null +++ b/drivers/gpu/drm/i915/display/i9xx_wm.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2023 Intel Corporation + */ + +#ifndef __I9XX_WM_H__ +#define __I9XX_WM_H__ + +#include + +struct intel_crtc_state; +struct intel_display; +struct intel_plane_state; + +#ifdef I915 +bool ilk_disable_cxsr(struct intel_display *display); +void ilk_wm_sanitize(struct intel_display *display); +bool intel_set_memory_cxsr(struct intel_display *display, bool enable); +void i9xx_wm_init(struct intel_display *display); +#else +static inline bool ilk_disable_cxsr(struct intel_display *display) +{ + return false; +} +static inline void ilk_wm_sanitize(struct intel_display *display) +{ +} +static inline bool intel_set_memory_cxsr(struct intel_display *display, bool enable) +{ + return false; +} +static inline void i9xx_wm_init(struct intel_display *display) +{ +} +#endif + +#endif /* __I9XX_WM_H__ */ diff --git a/drivers/gpu/drm/i915/display/i9xx_wm_regs.h b/drivers/gpu/drm/i915/display/i9xx_wm_regs.h new file mode 100644 index 000000000..d68d22235 --- /dev/null +++ b/drivers/gpu/drm/i915/display/i9xx_wm_regs.h @@ -0,0 +1,257 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2024 Intel Corporation */ + +#ifndef __I9XX_WM_REGS_H__ +#define __I9XX_WM_REGS_H__ + +#include "intel_display_reg_defs.h" + +#define DSPARB(dev_priv) _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x70030) +#define DSPARB_CSTART_MASK (0x7f << 7) +#define DSPARB_CSTART_SHIFT 7 +#define DSPARB_BSTART_MASK (0x7f) +#define DSPARB_BSTART_SHIFT 0 +#define DSPARB_BEND_SHIFT 9 /* on 855 */ +#define DSPARB_AEND_SHIFT 0 +#define DSPARB_SPRITEA_SHIFT_VLV 0 +#define DSPARB_SPRITEA_MASK_VLV (0xff << 0) +#define DSPARB_SPRITEB_SHIFT_VLV 8 +#define DSPARB_SPRITEB_MASK_VLV (0xff << 8) +#define DSPARB_SPRITEC_SHIFT_VLV 16 +#define DSPARB_SPRITEC_MASK_VLV (0xff << 16) +#define DSPARB_SPRITED_SHIFT_VLV 24 +#define DSPARB_SPRITED_MASK_VLV (0xff << 24) +#define DSPARB2 _MMIO(VLV_DISPLAY_BASE + 0x70060) /* vlv/chv */ +#define DSPARB_SPRITEA_HI_SHIFT_VLV 0 +#define DSPARB_SPRITEA_HI_MASK_VLV (0x1 << 0) +#define DSPARB_SPRITEB_HI_SHIFT_VLV 4 +#define DSPARB_SPRITEB_HI_MASK_VLV (0x1 << 4) +#define DSPARB_SPRITEC_HI_SHIFT_VLV 8 +#define DSPARB_SPRITEC_HI_MASK_VLV (0x1 << 8) +#define DSPARB_SPRITED_HI_SHIFT_VLV 12 +#define DSPARB_SPRITED_HI_MASK_VLV (0x1 << 12) +#define DSPARB_SPRITEE_HI_SHIFT_VLV 16 +#define DSPARB_SPRITEE_HI_MASK_VLV (0x1 << 16) +#define DSPARB_SPRITEF_HI_SHIFT_VLV 20 +#define DSPARB_SPRITEF_HI_MASK_VLV (0x1 << 20) +#define DSPARB3 _MMIO(VLV_DISPLAY_BASE + 0x7006c) /* chv */ +#define DSPARB_SPRITEE_SHIFT_VLV 0 +#define DSPARB_SPRITEE_MASK_VLV (0xff << 0) +#define DSPARB_SPRITEF_SHIFT_VLV 8 +#define DSPARB_SPRITEF_MASK_VLV (0xff << 8) + +/* pnv/gen4/g4x/vlv/chv */ +#define DSPFW1(dev_priv) _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x70034) +#define DSPFW_SR_SHIFT 23 +#define DSPFW_SR_MASK (0x1ff << 23) +#define DSPFW_CURSORB_SHIFT 16 +#define DSPFW_CURSORB_MASK (0x3f << 16) +#define DSPFW_PLANEB_SHIFT 8 +#define DSPFW_PLANEB_MASK (0x7f << 8) +#define DSPFW_PLANEB_MASK_VLV (0xff << 8) /* vlv/chv */ +#define DSPFW_PLANEA_SHIFT 0 +#define DSPFW_PLANEA_MASK (0x7f << 0) +#define DSPFW_PLANEA_MASK_VLV (0xff << 0) /* vlv/chv */ +#define DSPFW2(dev_priv) _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x70038) +#define DSPFW_FBC_SR_EN (1 << 31) /* g4x */ +#define DSPFW_FBC_SR_SHIFT 28 +#define DSPFW_FBC_SR_MASK (0x7 << 28) /* g4x */ +#define DSPFW_FBC_HPLL_SR_SHIFT 24 +#define DSPFW_FBC_HPLL_SR_MASK (0xf << 24) /* g4x */ +#define DSPFW_SPRITEB_SHIFT (16) +#define DSPFW_SPRITEB_MASK (0x7f << 16) /* g4x */ +#define DSPFW_SPRITEB_MASK_VLV (0xff << 16) /* vlv/chv */ +#define DSPFW_CURSORA_SHIFT 8 +#define DSPFW_CURSORA_MASK (0x3f << 8) +#define DSPFW_PLANEC_OLD_SHIFT 0 +#define DSPFW_PLANEC_OLD_MASK (0x7f << 0) /* pre-gen4 sprite C */ +#define DSPFW_SPRITEA_SHIFT 0 +#define DSPFW_SPRITEA_MASK (0x7f << 0) /* g4x */ +#define DSPFW_SPRITEA_MASK_VLV (0xff << 0) /* vlv/chv */ +#define DSPFW3(dev_priv) _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x7003c) +#define DSPFW_HPLL_SR_EN (1 << 31) +#define PINEVIEW_SELF_REFRESH_EN (1 << 30) +#define DSPFW_CURSOR_SR_SHIFT 24 +#define DSPFW_CURSOR_SR_MASK (0x3f << 24) +#define DSPFW_HPLL_CURSOR_SHIFT 16 +#define DSPFW_HPLL_CURSOR_MASK (0x3f << 16) +#define DSPFW_HPLL_SR_SHIFT 0 +#define DSPFW_HPLL_SR_MASK (0x1ff << 0) + +/* vlv/chv */ +#define DSPFW4 _MMIO(VLV_DISPLAY_BASE + 0x70070) +#define DSPFW_SPRITEB_WM1_SHIFT 16 +#define DSPFW_SPRITEB_WM1_MASK (0xff << 16) +#define DSPFW_CURSORA_WM1_SHIFT 8 +#define DSPFW_CURSORA_WM1_MASK (0x3f << 8) +#define DSPFW_SPRITEA_WM1_SHIFT 0 +#define DSPFW_SPRITEA_WM1_MASK (0xff << 0) +#define DSPFW5 _MMIO(VLV_DISPLAY_BASE + 0x70074) +#define DSPFW_PLANEB_WM1_SHIFT 24 +#define DSPFW_PLANEB_WM1_MASK (0xff << 24) +#define DSPFW_PLANEA_WM1_SHIFT 16 +#define DSPFW_PLANEA_WM1_MASK (0xff << 16) +#define DSPFW_CURSORB_WM1_SHIFT 8 +#define DSPFW_CURSORB_WM1_MASK (0x3f << 8) +#define DSPFW_CURSOR_SR_WM1_SHIFT 0 +#define DSPFW_CURSOR_SR_WM1_MASK (0x3f << 0) +#define DSPFW6 _MMIO(VLV_DISPLAY_BASE + 0x70078) +#define DSPFW_SR_WM1_SHIFT 0 +#define DSPFW_SR_WM1_MASK (0x1ff << 0) +#define DSPFW7 _MMIO(VLV_DISPLAY_BASE + 0x7007c) +#define DSPFW7_CHV _MMIO(VLV_DISPLAY_BASE + 0x700b4) /* wtf #1? */ +#define DSPFW_SPRITED_WM1_SHIFT 24 +#define DSPFW_SPRITED_WM1_MASK (0xff << 24) +#define DSPFW_SPRITED_SHIFT 16 +#define DSPFW_SPRITED_MASK_VLV (0xff << 16) +#define DSPFW_SPRITEC_WM1_SHIFT 8 +#define DSPFW_SPRITEC_WM1_MASK (0xff << 8) +#define DSPFW_SPRITEC_SHIFT 0 +#define DSPFW_SPRITEC_MASK_VLV (0xff << 0) +#define DSPFW8_CHV _MMIO(VLV_DISPLAY_BASE + 0x700b8) +#define DSPFW_SPRITEF_WM1_SHIFT 24 +#define DSPFW_SPRITEF_WM1_MASK (0xff << 24) +#define DSPFW_SPRITEF_SHIFT 16 +#define DSPFW_SPRITEF_MASK_VLV (0xff << 16) +#define DSPFW_SPRITEE_WM1_SHIFT 8 +#define DSPFW_SPRITEE_WM1_MASK (0xff << 8) +#define DSPFW_SPRITEE_SHIFT 0 +#define DSPFW_SPRITEE_MASK_VLV (0xff << 0) +#define DSPFW9_CHV _MMIO(VLV_DISPLAY_BASE + 0x7007c) /* wtf #2? */ +#define DSPFW_PLANEC_WM1_SHIFT 24 +#define DSPFW_PLANEC_WM1_MASK (0xff << 24) +#define DSPFW_PLANEC_SHIFT 16 +#define DSPFW_PLANEC_MASK_VLV (0xff << 16) +#define DSPFW_CURSORC_WM1_SHIFT 8 +#define DSPFW_CURSORC_WM1_MASK (0x3f << 16) +#define DSPFW_CURSORC_SHIFT 0 +#define DSPFW_CURSORC_MASK (0x3f << 0) + +/* vlv/chv high order bits */ +#define DSPHOWM _MMIO(VLV_DISPLAY_BASE + 0x70064) +#define DSPFW_SR_HI_SHIFT 24 +#define DSPFW_SR_HI_MASK (3 << 24) /* 2 bits for chv, 1 for vlv */ +#define DSPFW_SPRITEF_HI_SHIFT 23 +#define DSPFW_SPRITEF_HI_MASK (1 << 23) +#define DSPFW_SPRITEE_HI_SHIFT 22 +#define DSPFW_SPRITEE_HI_MASK (1 << 22) +#define DSPFW_PLANEC_HI_SHIFT 21 +#define DSPFW_PLANEC_HI_MASK (1 << 21) +#define DSPFW_SPRITED_HI_SHIFT 20 +#define DSPFW_SPRITED_HI_MASK (1 << 20) +#define DSPFW_SPRITEC_HI_SHIFT 16 +#define DSPFW_SPRITEC_HI_MASK (1 << 16) +#define DSPFW_PLANEB_HI_SHIFT 12 +#define DSPFW_PLANEB_HI_MASK (1 << 12) +#define DSPFW_SPRITEB_HI_SHIFT 8 +#define DSPFW_SPRITEB_HI_MASK (1 << 8) +#define DSPFW_SPRITEA_HI_SHIFT 4 +#define DSPFW_SPRITEA_HI_MASK (1 << 4) +#define DSPFW_PLANEA_HI_SHIFT 0 +#define DSPFW_PLANEA_HI_MASK (1 << 0) +#define DSPHOWM1 _MMIO(VLV_DISPLAY_BASE + 0x70068) +#define DSPFW_SR_WM1_HI_SHIFT 24 +#define DSPFW_SR_WM1_HI_MASK (3 << 24) /* 2 bits for chv, 1 for vlv */ +#define DSPFW_SPRITEF_WM1_HI_SHIFT 23 +#define DSPFW_SPRITEF_WM1_HI_MASK (1 << 23) +#define DSPFW_SPRITEE_WM1_HI_SHIFT 22 +#define DSPFW_SPRITEE_WM1_HI_MASK (1 << 22) +#define DSPFW_PLANEC_WM1_HI_SHIFT 21 +#define DSPFW_PLANEC_WM1_HI_MASK (1 << 21) +#define DSPFW_SPRITED_WM1_HI_SHIFT 20 +#define DSPFW_SPRITED_WM1_HI_MASK (1 << 20) +#define DSPFW_SPRITEC_WM1_HI_SHIFT 16 +#define DSPFW_SPRITEC_WM1_HI_MASK (1 << 16) +#define DSPFW_PLANEB_WM1_HI_SHIFT 12 +#define DSPFW_PLANEB_WM1_HI_MASK (1 << 12) +#define DSPFW_SPRITEB_WM1_HI_SHIFT 8 +#define DSPFW_SPRITEB_WM1_HI_MASK (1 << 8) +#define DSPFW_SPRITEA_WM1_HI_SHIFT 4 +#define DSPFW_SPRITEA_WM1_HI_MASK (1 << 4) +#define DSPFW_PLANEA_WM1_HI_SHIFT 0 +#define DSPFW_PLANEA_WM1_HI_MASK (1 << 0) + +/* drain latency register values*/ +#define VLV_DDL(pipe) _MMIO(VLV_DISPLAY_BASE + 0x70050 + 4 * (pipe)) +#define DDL_CURSOR_SHIFT 24 +#define DDL_SPRITE_SHIFT(sprite) (8 + 8 * (sprite)) +#define DDL_PLANE_SHIFT 0 +#define DDL_PRECISION_HIGH (1 << 7) +#define DDL_PRECISION_LOW (0 << 7) +#define DRAIN_LATENCY_MASK 0x7f + +/* FIFO watermark sizes etc */ +#define G4X_FIFO_LINE_SIZE 64 +#define I915_FIFO_LINE_SIZE 64 +#define I830_FIFO_LINE_SIZE 32 + +#define VALLEYVIEW_FIFO_SIZE 255 +#define G4X_FIFO_SIZE 127 +#define I965_FIFO_SIZE 512 +#define I945_FIFO_SIZE 127 +#define I915_FIFO_SIZE 95 +#define I855GM_FIFO_SIZE 127 /* In cachelines */ +#define I830_FIFO_SIZE 95 + +#define VALLEYVIEW_MAX_WM 0xff +#define G4X_MAX_WM 0x3f +#define I915_MAX_WM 0x3f + +#define PINEVIEW_DISPLAY_FIFO 512 /* in 64byte unit */ +#define PINEVIEW_FIFO_LINE_SIZE 64 +#define PINEVIEW_MAX_WM 0x1ff +#define PINEVIEW_DFT_WM 0x3f +#define PINEVIEW_DFT_HPLLOFF_WM 0 +#define PINEVIEW_GUARD_WM 10 +#define PINEVIEW_CURSOR_FIFO 64 +#define PINEVIEW_CURSOR_MAX_WM 0x3f +#define PINEVIEW_CURSOR_DFT_WM 0 +#define PINEVIEW_CURSOR_GUARD_WM 5 + +#define VALLEYVIEW_CURSOR_MAX_WM 64 +#define I965_CURSOR_FIFO 64 +#define I965_CURSOR_MAX_WM 32 +#define I965_CURSOR_DFT_WM 8 + +/* define the Watermark register on Ironlake */ +#define _WM0_PIPEA_ILK 0x45100 +#define _WM0_PIPEB_ILK 0x45104 +#define _WM0_PIPEC_IVB 0x45200 +#define WM0_PIPE_ILK(pipe) _MMIO_BASE_PIPE3(0, (pipe), _WM0_PIPEA_ILK, \ + _WM0_PIPEB_ILK, _WM0_PIPEC_IVB) +#define WM0_PIPE_PRIMARY_MASK REG_GENMASK(31, 16) +#define WM0_PIPE_SPRITE_MASK REG_GENMASK(15, 8) +#define WM0_PIPE_CURSOR_MASK REG_GENMASK(7, 0) +#define WM0_PIPE_PRIMARY(x) REG_FIELD_PREP(WM0_PIPE_PRIMARY_MASK, (x)) +#define WM0_PIPE_SPRITE(x) REG_FIELD_PREP(WM0_PIPE_SPRITE_MASK, (x)) +#define WM0_PIPE_CURSOR(x) REG_FIELD_PREP(WM0_PIPE_CURSOR_MASK, (x)) +#define WM1_LP_ILK _MMIO(0x45108) +#define WM2_LP_ILK _MMIO(0x4510c) +#define WM3_LP_ILK _MMIO(0x45110) +#define WM_LP_ENABLE REG_BIT(31) +#define WM_LP_LATENCY_MASK REG_GENMASK(30, 24) +#define WM_LP_FBC_MASK_BDW REG_GENMASK(23, 19) +#define WM_LP_FBC_MASK_ILK REG_GENMASK(23, 20) +#define WM_LP_PRIMARY_MASK REG_GENMASK(18, 8) +#define WM_LP_CURSOR_MASK REG_GENMASK(7, 0) +#define WM_LP_LATENCY(x) REG_FIELD_PREP(WM_LP_LATENCY_MASK, (x)) +#define WM_LP_FBC_BDW(x) REG_FIELD_PREP(WM_LP_FBC_MASK_BDW, (x)) +#define WM_LP_FBC_ILK(x) REG_FIELD_PREP(WM_LP_FBC_MASK_ILK, (x)) +#define WM_LP_PRIMARY(x) REG_FIELD_PREP(WM_LP_PRIMARY_MASK, (x)) +#define WM_LP_CURSOR(x) REG_FIELD_PREP(WM_LP_CURSOR_MASK, (x)) +#define WM1S_LP_ILK _MMIO(0x45120) +#define WM2S_LP_IVB _MMIO(0x45124) +#define WM3S_LP_IVB _MMIO(0x45128) +#define WM_LP_SPRITE_ENABLE REG_BIT(31) /* ilk/snb WM1S only */ +#define WM_LP_SPRITE_MASK REG_GENMASK(10, 0) +#define WM_LP_SPRITE(x) REG_FIELD_PREP(WM_LP_SPRITE_MASK, (x)) + +#define WM_MISC _MMIO(0x45260) +#define WM_MISC_DATA_PARTITION_5_6 (1 << 0) + +#define WM_DBG _MMIO(0x45280) +#define WM_DBG_DISALLOW_MULTIPLE_LP (1 << 0) +#define WM_DBG_DISALLOW_MAXFIFO (1 << 1) +#define WM_DBG_DISALLOW_SPRITE (1 << 2) + +#endif /* __I9XX_WM_REGS_H__ */ 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..a549f1fac --- /dev/null +++ b/drivers/gpu/drm/i915/display/icl_dsi.c @@ -0,0 +1,2043 @@ +/* + * 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 +#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_display_regs.h" +#include "intel_display_utils.h" +#include "intel_dsi.h" +#include "intel_dsi_vbt.h" +#include "intel_panel.h" +#include "intel_pfit.h" +#include "intel_vdsc.h" +#include "intel_vdsc_regs.h" +#include "skl_scaler.h" +#include "skl_universal_plane.h" + +static int header_credits_available(struct intel_display *display, + enum transcoder dsi_trans) +{ + return (intel_de_read(display, DSI_CMD_TXCTL(dsi_trans)) & FREE_HEADER_CREDIT_MASK) + >> FREE_HEADER_CREDIT_SHIFT; +} + +static int payload_credits_available(struct intel_display *display, + enum transcoder dsi_trans) +{ + return (intel_de_read(display, DSI_CMD_TXCTL(dsi_trans)) & FREE_PLOAD_CREDIT_MASK) + >> FREE_PLOAD_CREDIT_SHIFT; +} + +static bool wait_for_header_credits(struct intel_display *display, + enum transcoder dsi_trans, int hdr_credit) +{ + int ret, available; + + ret = poll_timeout_us(available = header_credits_available(display, dsi_trans), + available >= hdr_credit, + 10, 100, false); + if (ret) { + drm_err(display->drm, "DSI header credits not released\n"); + return false; + } + + return true; +} + +static bool wait_for_payload_credits(struct intel_display *display, + enum transcoder dsi_trans, int payld_credit) +{ + int ret, available; + + ret = poll_timeout_us(available = payload_credits_available(display, dsi_trans), + available >= payld_credit, + 10, 100, false); + if (ret) { + drm_err(display->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 intel_display *display = to_intel_display(encoder); + 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(display, dsi_trans, MAX_HEADER_CREDIT); + wait_for_payload_credits(display, 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(display->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(display, 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); + + ret = intel_de_wait_for_clear_us(display, + DSI_LP_MSG(dsi_trans), + LPTX_IN_PROGRESS, 20); + if (ret) + drm_err(display->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 intel_display *display = to_intel_display(&intel_dsi->base); + 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(display->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(display, dsi_trans, 1)) + return -EBUSY; + + for (j = 0; j < min_t(u32, len - i, 4); j++) + tmp |= *data++ << 8 * j; + + intel_de_write(display, 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 intel_display *display = to_intel_display(&intel_dsi->base); + enum transcoder dsi_trans = dsi_port_to_transcoder(host->port); + u32 tmp; + + if (!wait_for_header_credits(display, dsi_trans, 1)) + return -EBUSY; + + tmp = intel_de_read(display, DSI_CMD_TXHDR(dsi_trans)); + + if (packet->payload) + tmp |= PAYLOAD_PRESENT; + else + tmp &= ~PAYLOAD_PRESENT; + + tmp &= ~(VBLANK_FENCE | LP_DATA_TRANSFER | PIPELINE_FLUSH); + + if (enable_lpdt) + tmp |= LP_DATA_TRANSFER; + else + tmp |= PIPELINE_FLUSH; + + 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(display, DSI_CMD_TXHDR(dsi_trans), tmp); + + return 0; +} + +void icl_dsi_frame_update(struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + u32 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; + + intel_de_rmw(display, DSI_CMD_FRMCTL(port), 0, + DSI_FRAME_UPDATE_REQUEST); +} + +static void dsi_program_swing_and_deemphasis(struct intel_encoder *encoder) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum phy phy; + u32 tmp, mask, val; + 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 programming. + */ + mask = SCALING_MODE_SEL_MASK | RTERM_SELECT_MASK; + val = SCALING_MODE_SEL(0x2) | TAP2_DISABLE | TAP3_DISABLE | + RTERM_SELECT(0x6); + tmp = intel_de_read(display, ICL_PORT_TX_DW5_LN(0, phy)); + tmp &= ~mask; + tmp |= val; + intel_de_write(display, ICL_PORT_TX_DW5_GRP(phy), tmp); + intel_de_rmw(display, ICL_PORT_TX_DW5_AUX(phy), mask, val); + + mask = SWING_SEL_LOWER_MASK | SWING_SEL_UPPER_MASK | + RCOMP_SCALAR_MASK; + val = SWING_SEL_UPPER(0x2) | SWING_SEL_LOWER(0x2) | + RCOMP_SCALAR(0x98); + tmp = intel_de_read(display, ICL_PORT_TX_DW2_LN(0, phy)); + tmp &= ~mask; + tmp |= val; + intel_de_write(display, ICL_PORT_TX_DW2_GRP(phy), tmp); + intel_de_rmw(display, ICL_PORT_TX_DW2_AUX(phy), mask, val); + + mask = POST_CURSOR_1_MASK | POST_CURSOR_2_MASK | + CURSOR_COEFF_MASK; + val = POST_CURSOR_1(0x0) | POST_CURSOR_2(0x0) | + CURSOR_COEFF(0x3f); + intel_de_rmw(display, ICL_PORT_TX_DW4_AUX(phy), mask, val); + + /* Bspec: must not use GRP register for write */ + for (lane = 0; lane <= 3; lane++) + intel_de_rmw(display, ICL_PORT_TX_DW4_LN(lane, phy), + mask, val); + } +} + +static void configure_dual_link_mode(struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + intel_reg_t dss_ctl1_reg, dss_ctl2_reg; + u32 dss_ctl1; + + /* FIXME: Move all DSS handling to intel_vdsc.c */ + if (DISPLAY_VER(display) >= 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(display, 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; + 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(display->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); + intel_de_rmw(display, dss_ctl2_reg, RIGHT_DL_BUF_TARGET_DEPTH_MASK, + RIGHT_DL_BUF_TARGET_DEPTH(dl_buffer_depth)); + } else { + /* Interleave */ + dss_ctl1 |= DUAL_LINK_MODE_INTERLEAVE; + } + + intel_de_write(display, 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 = fxp_q4_to_int(crtc_state->dsc.compressed_bpp_x16); + 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 intel_display *display = to_intel_display(encoder); + 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 (display->platform.alderlake_s || display->platform.alderlake_p) { + 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(display, ICL_DSI_ESC_CLK_DIV(port), + esc_clk_div_m & ICL_ESC_CLK_DIV_MASK); + intel_de_posting_read(display, ICL_DSI_ESC_CLK_DIV(port)); + } + + for_each_dsi_port(port, intel_dsi->ports) { + intel_de_write(display, ICL_DPHY_ESC_CLK_DIV(port), + esc_clk_div_m & ICL_ESC_CLK_DIV_MASK); + intel_de_posting_read(display, ICL_DPHY_ESC_CLK_DIV(port)); + } + + if (display->platform.alderlake_s || display->platform.alderlake_p) { + for_each_dsi_port(port, intel_dsi->ports) { + intel_de_write(display, ADL_MIPIO_DW(port, 8), + esc_clk_div_m_phy & TX_ESC_CLK_DIV_PHY); + intel_de_posting_read(display, ADL_MIPIO_DW(port, 8)); + } + } +} + +static void get_dsi_io_power_domains(struct intel_dsi *intel_dsi) +{ + struct intel_display *display = to_intel_display(&intel_dsi->base); + enum port port; + + for_each_dsi_port(port, intel_dsi->ports) { + drm_WARN_ON(display->drm, intel_dsi->io_wakeref[port]); + intel_dsi->io_wakeref[port] = + intel_display_power_get(display, + 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 intel_display *display = to_intel_display(encoder); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum port port; + + for_each_dsi_port(port, intel_dsi->ports) + intel_de_rmw(display, ICL_DSI_IO_MODECTL(port), + 0, COMBO_PHY_MODE_DSI); + + get_dsi_io_power_domains(intel_dsi); +} + +static void gen11_dsi_power_up_lanes(struct intel_encoder *encoder) +{ + struct intel_display *display = to_intel_display(encoder); + 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(display, phy, true, + intel_dsi->lane_count, false); +} + +static void gen11_dsi_config_phy_lanes_sequence(struct intel_encoder *encoder) +{ + struct intel_display *display = to_intel_display(encoder); + 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) { + intel_de_rmw(display, ICL_PORT_TX_DW4_AUX(phy), + LOADGEN_SELECT, 0); + for (lane = 0; lane <= 3; lane++) + intel_de_rmw(display, ICL_PORT_TX_DW4_LN(lane, phy), + LOADGEN_SELECT, lane != 2 ? LOADGEN_SELECT : 0); + } + + /* Step 4b(ii) set latency optimization for transmit and aux lanes */ + for_each_dsi_phy(phy, intel_dsi->phys) { + intel_de_rmw(display, ICL_PORT_TX_DW2_AUX(phy), + FRC_LATENCY_OPTIM_MASK, FRC_LATENCY_OPTIM_VAL(0x5)); + tmp = intel_de_read(display, ICL_PORT_TX_DW2_LN(0, phy)); + tmp &= ~FRC_LATENCY_OPTIM_MASK; + tmp |= FRC_LATENCY_OPTIM_VAL(0x5); + intel_de_write(display, ICL_PORT_TX_DW2_GRP(phy), tmp); + + /* For EHL, TGL, set latency optimization for PCS_DW1 lanes */ + if (display->platform.jasperlake || display->platform.elkhartlake || + (DISPLAY_VER(display) >= 12)) { + intel_de_rmw(display, ICL_PORT_PCS_DW1_AUX(phy), + LATENCY_OPTIM_MASK, LATENCY_OPTIM_VAL(0)); + + tmp = intel_de_read(display, + ICL_PORT_PCS_DW1_LN(0, phy)); + tmp &= ~LATENCY_OPTIM_MASK; + tmp |= LATENCY_OPTIM_VAL(0x1); + intel_de_write(display, ICL_PORT_PCS_DW1_GRP(phy), + tmp); + } + } + +} + +static void gen11_dsi_voltage_swing_program_seq(struct intel_encoder *encoder) +{ + struct intel_display *display = to_intel_display(encoder); + 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(display, ICL_PORT_PCS_DW1_LN(0, phy)); + tmp &= ~COMMON_KEEPER_EN; + intel_de_write(display, ICL_PORT_PCS_DW1_GRP(phy), tmp); + intel_de_rmw(display, ICL_PORT_PCS_DW1_AUX(phy), COMMON_KEEPER_EN, 0); + } + + /* + * 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) + intel_de_rmw(display, ICL_PORT_CL_DW5(phy), 0, + SUS_CLOCK_CONFIG); + + /* Clear training enable to change swing values */ + for_each_dsi_phy(phy, intel_dsi->phys) { + tmp = intel_de_read(display, ICL_PORT_TX_DW5_LN(0, phy)); + tmp &= ~TX_TRAINING_EN; + intel_de_write(display, ICL_PORT_TX_DW5_GRP(phy), tmp); + intel_de_rmw(display, ICL_PORT_TX_DW5_AUX(phy), TX_TRAINING_EN, 0); + } + + /* 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(display, ICL_PORT_TX_DW5_LN(0, phy)); + tmp |= TX_TRAINING_EN; + intel_de_write(display, ICL_PORT_TX_DW5_GRP(phy), tmp); + intel_de_rmw(display, ICL_PORT_TX_DW5_AUX(phy), 0, TX_TRAINING_EN); + } +} + +static void gen11_dsi_enable_ddi_buffer(struct intel_encoder *encoder) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum port port; + int ret; + + for_each_dsi_port(port, intel_dsi->ports) { + intel_de_rmw(display, DDI_BUF_CTL(port), 0, DDI_BUF_CTL_ENABLE); + + ret = intel_de_wait_for_clear_us(display, DDI_BUF_CTL(port), + DDI_BUF_IS_IDLE, 500); + if (ret) + drm_err(display->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 intel_display *display = to_intel_display(encoder); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum port port; + enum phy phy; + + /* Program DPHY clock lanes timings */ + for_each_dsi_port(port, intel_dsi->ports) + intel_de_write(display, DPHY_CLK_TIMING_PARAM(port), + intel_dsi->dphy_reg); + + /* Program DPHY data lanes timings */ + for_each_dsi_port(port, intel_dsi->ports) + intel_de_write(display, DPHY_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(display) == 11) { + if (afe_clk(encoder, crtc_state) <= 800000) { + for_each_dsi_port(port, intel_dsi->ports) + intel_de_rmw(display, DPHY_TA_TIMING_PARAM(port), + TA_SURE_MASK, + TA_SURE_OVERRIDE | TA_SURE(0)); + } + } + + if (display->platform.jasperlake || display->platform.elkhartlake) { + for_each_dsi_phy(phy, intel_dsi->phys) + intel_de_rmw(display, ICL_DPHY_CHKN(phy), + 0, ICL_DPHY_CHKN_AFE_OVER_PPI_STRAP); + } +} + +static void +gen11_dsi_setup_timings(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum port port; + + /* Program T-INIT master registers */ + for_each_dsi_port(port, intel_dsi->ports) + intel_de_rmw(display, ICL_DSI_T_INIT_MASTER(port), + DSI_T_INIT_MASTER_MASK, intel_dsi->init_count); + + /* shadow register inside display core */ + for_each_dsi_port(port, intel_dsi->ports) + intel_de_write(display, DSI_CLK_TIMING_PARAM(port), + intel_dsi->dphy_reg); + + /* shadow register inside display core */ + for_each_dsi_port(port, intel_dsi->ports) + intel_de_write(display, DSI_DATA_TIMING_PARAM(port), + intel_dsi->dphy_data_lane_reg); + + /* shadow register inside display core */ + if (DISPLAY_VER(display) == 11) { + if (afe_clk(encoder, crtc_state) <= 800000) { + for_each_dsi_port(port, intel_dsi->ports) { + intel_de_rmw(display, DSI_TA_TIMING_PARAM(port), + TA_SURE_MASK, + TA_SURE_OVERRIDE | TA_SURE(0)); + } + } + } +} + +static void gen11_dsi_gate_clocks(struct intel_encoder *encoder) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + u32 tmp; + enum phy phy; + + mutex_lock(&display->dpll.lock); + tmp = intel_de_read(display, ICL_DPCLKA_CFGCR0); + for_each_dsi_phy(phy, intel_dsi->phys) + tmp |= ICL_DPCLKA_CFGCR0_DDI_CLK_OFF(phy); + + intel_de_write(display, ICL_DPCLKA_CFGCR0, tmp); + mutex_unlock(&display->dpll.lock); +} + +static void gen11_dsi_ungate_clocks(struct intel_encoder *encoder) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + u32 tmp; + enum phy phy; + + mutex_lock(&display->dpll.lock); + tmp = intel_de_read(display, ICL_DPCLKA_CFGCR0); + for_each_dsi_phy(phy, intel_dsi->phys) + tmp &= ~ICL_DPCLKA_CFGCR0_DDI_CLK_OFF(phy); + + intel_de_write(display, ICL_DPCLKA_CFGCR0, tmp); + mutex_unlock(&display->dpll.lock); +} + +static bool gen11_dsi_is_clock_enabled(struct intel_encoder *encoder) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + bool clock_enabled = false; + enum phy phy; + u32 tmp; + + tmp = intel_de_read(display, 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 intel_display *display = to_intel_display(encoder); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + struct intel_dpll *pll = crtc_state->intel_dpll; + enum phy phy; + u32 val; + + mutex_lock(&display->dpll.lock); + + val = intel_de_read(display, 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(display, ICL_DPCLKA_CFGCR0, val); + + for_each_dsi_phy(phy, intel_dsi->phys) { + val &= ~ICL_DPCLKA_CFGCR0_DDI_CLK_OFF(phy); + } + intel_de_write(display, ICL_DPCLKA_CFGCR0, val); + + intel_de_posting_read(display, ICL_DPCLKA_CFGCR0); + + mutex_unlock(&display->dpll.lock); +} + +static void +gen11_dsi_configure_transcoder(struct intel_encoder *encoder, + const struct intel_crtc_state *pipe_config) +{ + struct intel_display *display = to_intel_display(encoder); + 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(display, DSI_TRANS_FUNC_CONF(dsi_trans)); + + if (intel_dsi->eot_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; + + if (DISPLAY_VER(display) >= 12 && + intel_dsi->lp_clock_during_lpm) + tmp |= LP_CLK_DURING_LPM; + else + tmp &= ~LP_CLK_DURING_LPM; + + /* 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(display) >= 12 && + is_vid_mode(intel_dsi) && intel_dsi->blanking_pkt) + tmp |= BLANKING_PACKET_ENABLE; + else + 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(display, 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); + intel_de_rmw(display, + TRANS_DDI_FUNC_CTL2(display, dsi_trans), + 0, PORT_SYNC_MODE_ENABLE); + } + + /* 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(display, + TRANS_DDI_FUNC_CTL(display, dsi_trans)); + tmp &= ~TRANS_DDI_PORT_WIDTH_MASK; + tmp |= TRANS_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(display, + TRANS_DDI_FUNC_CTL(display, dsi_trans), tmp); + } + + /* wait for link ready */ + for_each_dsi_port(port, intel_dsi->ports) { + int ret; + + dsi_trans = dsi_port_to_transcoder(port); + + ret = intel_de_wait_for_set_us(display, + DSI_TRANS_FUNC_CONF(dsi_trans), + LINK_READY, 2500); + if (ret) + drm_err(display->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 intel_display *display = to_intel_display(encoder); + 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 (is_vid_mode(intel_dsi) && crtc_state->dsc.compression_enable) { + mul = fxp_q4_to_int(crtc_state->dsc.compressed_bpp_x16); + 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 = fxp_q4_to_int(crtc_state->dsc.compressed_bpp_x16); + 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(display->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(display->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(display, TRANS_HTOTAL(display, dsi_trans), + HACTIVE(hactive - 1) | HTOTAL(htotal - 1)); + } + + /* 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(display->drm, + "hsync size < 16 pixels\n"); + } + + if (hback_porch < 16) + drm_err(display->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(display, + TRANS_HSYNC(display, dsi_trans), + HSYNC_START(hsync_start - 1) | HSYNC_END(hsync_end - 1)); + } + } + + /* program TRANS_VTOTAL register */ + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + /* + * FIXME: Programming 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(display, TRANS_VTOTAL(display, dsi_trans), + VACTIVE(vactive - 1) | VTOTAL(vtotal - 1)); + } + + if (vsync_end < vsync_start || vsync_end > vtotal) + drm_err(display->drm, "Invalid vsync_end value\n"); + + if (vsync_start < vactive) + drm_err(display->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(display, + TRANS_VSYNC(display, dsi_trans), + VSYNC_START(vsync_start - 1) | VSYNC_END(vsync_end - 1)); + } + } + + /* + * 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(display, + TRANS_VSYNCSHIFT(display, dsi_trans), + vsync_shift); + } + } + + /* + * program TRANS_VBLANK register, should be same as vtotal programmed + * + * FIXME get rid of these local hacks and do it right, + * this will not handle eg. delayed vblank correctly. + */ + if (DISPLAY_VER(display) >= 12) { + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + intel_de_write(display, + TRANS_VBLANK(display, dsi_trans), + VBLANK_START(vactive - 1) | VBLANK_END(vtotal - 1)); + } + } +} + +static void gen11_dsi_enable_transcoder(struct intel_encoder *encoder) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum port port; + enum transcoder dsi_trans; + + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + intel_de_rmw(display, TRANSCONF(display, dsi_trans), 0, + TRANSCONF_ENABLE); + + /* wait for transcoder to be enabled */ + if (intel_de_wait_for_set_ms(display, TRANSCONF(display, dsi_trans), + TRANSCONF_STATE_ENABLE, 10)) + drm_err(display->drm, + "DSI transcoder not enabled\n"); + } +} + +static void gen11_dsi_setup_timeouts(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum port port; + enum transcoder dsi_trans; + u32 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 */ + intel_de_rmw(display, DSI_HSTX_TO(dsi_trans), + HSTX_TIMEOUT_VALUE_MASK, + HSTX_TIMEOUT_VALUE(hs_tx_timeout)); + + /* FIXME: DSI_CALIB_TO */ + + /* program lp_rx_host timeout */ + intel_de_rmw(display, DSI_LPRX_HOST_TO(dsi_trans), + LPRX_TIMEOUT_VALUE_MASK, + LPRX_TIMEOUT_VALUE(lp_rx_timeout)); + + /* FIXME: DSI_PWAIT_TO */ + + /* program turn around timeout */ + intel_de_rmw(display, DSI_TA_TO(dsi_trans), + TA_TIMEOUT_VALUE_MASK, + TA_TIMEOUT_VALUE(ta_timeout)); + } +} + +static void gen11_dsi_config_util_pin(struct intel_encoder *encoder, + bool enable) +{ + struct intel_display *display = to_intel_display(encoder); + 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(display, UTIL_PIN_CTL); + + if (enable) { + tmp |= UTIL_PIN_DIRECTION_INPUT; + tmp |= UTIL_PIN_ENABLE; + } else { + tmp &= ~UTIL_PIN_ENABLE; + } + intel_de_write(display, 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); + + /* setup D-PHY timings */ + gen11_dsi_setup_dphy_timings(encoder, crtc_state); + + /* enable DDI buffer */ + gen11_dsi_enable_ddi_buffer(encoder); + + gen11_dsi_gate_clocks(encoder); + + gen11_dsi_setup_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); +} + +static void gen11_dsi_powerup_panel(struct intel_encoder *encoder) +{ + struct intel_display *display = to_intel_display(encoder); + 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(display, 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(display->drm, + "error setting max return pkt size%d\n", tmp); + } + + intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_INIT_OTP); + + /* 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) +{ + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + + intel_dsi_wait_panel_power_cycle(intel_dsi); + + 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); + + /* 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 intel_display *display = to_intel_display(encoder); + + if (DISPLAY_VER(display) == 11 && pipe == PIPE_B) + intel_de_rmw(display, 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 intel_display *display = to_intel_display(encoder); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum port port; + + if (DISPLAY_VER(display) == 13) { + for_each_dsi_port(port, intel_dsi->ports) + intel_de_rmw(display, 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(crtc_state->uapi.crtc); + + /* 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); + + intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_DISPLAY_ON); + + /* step7: enable backlight */ + intel_backlight_enable(crtc_state, conn_state); + intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_BACKLIGHT_ON); + + intel_panel_prepare(crtc_state, conn_state); + + intel_crtc_vblank_on(crtc_state); +} + +static void gen11_dsi_disable_transcoder(struct intel_encoder *encoder) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum port port; + enum transcoder dsi_trans; + + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + + /* disable transcoder */ + intel_de_rmw(display, TRANSCONF(display, dsi_trans), + TRANSCONF_ENABLE, 0); + + /* wait for transcoder to be disabled */ + if (intel_de_wait_for_clear_ms(display, TRANSCONF(display, dsi_trans), + TRANSCONF_STATE_ENABLE, 50)) + drm_err(display->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); + + /* ensure cmds dispatched to panel */ + wait_for_cmds_dispatched_to_panel(encoder); +} + +static void gen11_dsi_deconfigure_trancoder(struct intel_encoder *encoder) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum port port; + enum transcoder dsi_trans; + u32 tmp; + int ret; + + /* disable periodic update mode */ + if (is_cmd_mode(intel_dsi)) { + for_each_dsi_port(port, intel_dsi->ports) + intel_de_rmw(display, DSI_CMD_FRMCTL(port), + DSI_PERIODIC_FRAME_UPDATE_ENABLE, 0); + } + + /* put dsi link in ULPS */ + for_each_dsi_port(port, intel_dsi->ports) { + dsi_trans = dsi_port_to_transcoder(port); + tmp = intel_de_read(display, DSI_LP_MSG(dsi_trans)); + tmp |= LINK_ENTER_ULPS; + tmp &= ~LINK_ULPS_TYPE_LP11; + intel_de_write(display, DSI_LP_MSG(dsi_trans), tmp); + + ret = intel_de_wait_for_set_us(display, DSI_LP_MSG(dsi_trans), + LINK_IN_ULPS, 10); + if (ret) + drm_err(display->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); + intel_de_rmw(display, + TRANS_DDI_FUNC_CTL(display, dsi_trans), + TRANS_DDI_FUNC_ENABLE, 0); + } + + /* 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); + intel_de_rmw(display, + TRANS_DDI_FUNC_CTL2(display, dsi_trans), + PORT_SYNC_MODE_ENABLE, 0); + } + } +} + +static void gen11_dsi_disable_port(struct intel_encoder *encoder) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum port port; + int ret; + + gen11_dsi_ungate_clocks(encoder); + for_each_dsi_port(port, intel_dsi->ports) { + intel_de_rmw(display, DDI_BUF_CTL(port), DDI_BUF_CTL_ENABLE, 0); + + ret = intel_de_wait_for_set_us(display, DDI_BUF_CTL(port), + DDI_BUF_IS_IDLE, 8); + + if (ret) + drm_err(display->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 intel_display *display = to_intel_display(encoder); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum port port; + + for_each_dsi_port(port, intel_dsi->ports) { + struct ref_tracker *wakeref; + + wakeref = fetch_and_zero(&intel_dsi->io_wakeref[port]); + intel_display_power_put(display, + 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) + intel_de_rmw(display, ICL_DSI_IO_MODECTL(port), + COMBO_PHY_MODE_DSI, 0); +} + +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); + + intel_panel_unprepare(old_conn_state); + + /* step1: turn off backlight */ + intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_BACKLIGHT_OFF); + intel_backlight_disable(old_conn_state); +} + +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) +{ + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->uapi.crtc); + + intel_crtc_vblank_off(old_crtc_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); + + intel_dsc_disable(old_crtc_state); + skl_scaler_disable(old_crtc_state); + + /* 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); + + intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_ASSERT_RESET); + + msleep(intel_dsi->panel_off_delay); + intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_POWER_OFF); + + intel_dsi->panel_power_off_time = ktime_get_boottime(); +} + +static enum drm_mode_status gen11_dsi_mode_valid(struct drm_connector *connector, + const struct drm_display_mode *mode) +{ + struct intel_display *display = to_intel_display(connector->dev); + enum drm_mode_status status; + + status = intel_cpu_transcoder_mode_valid(display, 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 (is_vid_mode(intel_dsi) && pipe_config->dsc.compressed_bpp_x16) { + int div = fxp_q4_to_int(pipe_config->dsc.compressed_bpp_x16); + 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 intel_display *display = to_intel_display(&intel_dsi->base); + 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(display, 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_pipe_misc_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 intel_display *display = to_intel_display(encoder); + 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(display) == 11 && pipe == PIPE_B && + !(intel_de_read(display, CHICKEN_PAR1_1) & IGNORE_KVMR_PIPE_A)) + drm_dbg_kms(display->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 intel_display *display = to_intel_display(encoder); + struct drm_dsc_config *vdsc_cfg = &crtc_state->dsc.config; + int dsc_max_bpc = DISPLAY_VER(display) >= 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: 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(display->drm, vdsc_cfg->vbr_enable); + drm_WARN_ON(display->drm, vdsc_cfg->simple_422); + drm_WARN_ON(display->drm, + vdsc_cfg->pic_width % vdsc_cfg->slice_width); + drm_WARN_ON(display->drm, vdsc_cfg->slice_height < 8); + drm_WARN_ON(display->drm, + vdsc_cfg->pic_height % vdsc_cfg->slice_height); + + ret = drm_dsc_compute_rc_parameters(vdsc_cfg); + if (ret) + return ret; + + intel_dsc_enable_on_crtc(crtc_state); + + 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 intel_display *display = to_intel_display(encoder); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + struct intel_connector *intel_connector = intel_dsi->attached_connector; + struct drm_display_mode *adjusted_mode = + &pipe_config->hw.adjusted_mode; + int ret; + + pipe_config->sink_format = INTEL_OUTPUT_FORMAT_RGB; + pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB; + + ret = intel_panel_compute_config(intel_connector, adjusted_mode); + if (ret) + return ret; + + ret = intel_pfit_compute_config(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(display->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) +{ + get_dsi_io_power_domains(enc_to_intel_dsi(encoder)); +} + +static bool gen11_dsi_get_hw_state(struct intel_encoder *encoder, + enum pipe *pipe) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_dsi *intel_dsi = enc_to_intel_dsi(encoder); + enum transcoder dsi_trans; + struct ref_tracker *wakeref; + enum port port; + bool ret = false; + u32 tmp; + + wakeref = intel_display_power_get_if_enabled(display, + 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(display, + TRANS_DDI_FUNC_CTL(display, 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(display->drm, "Invalid PIPE input\n"); + goto out; + } + + tmp = intel_de_read(display, TRANSCONF(display, dsi_trans)); + ret = tmp & TRANSCONF_ENABLE; + } +out: + intel_display_power_put(display, 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, +}; + +static void icl_dphy_param_init(struct intel_dsi *intel_dsi) +{ + struct intel_connector *connector = intel_dsi->attached_connector; + struct mipi_config *mipi_config = connector->panel.vbt.dsi.config; + u32 tlpx_ns; + u32 tclk_prepare_esc_clk, tclk_zero_esc_clk, tclk_pre_esc_clk; + u32 ths_prepare_esc_clk, ths_zero_esc_clk, ths_exit_esc_clk; + + tlpx_ns = intel_dsi_tlpx_ns(intel_dsi); + + /* + * The clock and data lane prepare timing parameters are in expressed in + * units of 1/4 escape clocks, and all the other timings parameters in + * escape clocks. + */ + tclk_prepare_esc_clk = DIV_ROUND_UP(mipi_config->tclk_prepare * 4, tlpx_ns); + tclk_prepare_esc_clk = min(tclk_prepare_esc_clk, 7); + + tclk_zero_esc_clk = DIV_ROUND_UP(mipi_config->tclk_prepare_clkzero - + mipi_config->tclk_prepare, tlpx_ns); + tclk_zero_esc_clk = min(tclk_zero_esc_clk, 15); + + tclk_pre_esc_clk = DIV_ROUND_UP(mipi_config->tclk_pre, tlpx_ns); + tclk_pre_esc_clk = min(tclk_pre_esc_clk, 3); + + ths_prepare_esc_clk = DIV_ROUND_UP(mipi_config->ths_prepare * 4, tlpx_ns); + ths_prepare_esc_clk = min(ths_prepare_esc_clk, 7); + + ths_zero_esc_clk = DIV_ROUND_UP(mipi_config->ths_prepare_hszero - + mipi_config->ths_prepare, tlpx_ns); + ths_zero_esc_clk = min(ths_zero_esc_clk, 15); + + ths_exit_esc_clk = DIV_ROUND_UP(mipi_config->ths_exit, tlpx_ns); + ths_exit_esc_clk = min(ths_exit_esc_clk, 7); + + /* clock lane dphy timings */ + intel_dsi->dphy_reg = (CLK_PREPARE_OVERRIDE | + CLK_PREPARE(tclk_prepare_esc_clk) | + CLK_ZERO_OVERRIDE | + CLK_ZERO(tclk_zero_esc_clk) | + CLK_PRE_OVERRIDE | + CLK_PRE(tclk_pre_esc_clk)); + + /* data lanes dphy timings */ + intel_dsi->dphy_data_lane_reg = (HS_PREPARE_OVERRIDE | + HS_PREPARE(ths_prepare_esc_clk) | + HS_ZERO_OVERRIDE | + HS_ZERO(ths_zero_esc_clk) | + HS_EXIT_OVERRIDE | + HS_EXIT(ths_exit_esc_clk)); + + 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); + + intel_attach_scaling_mode_property(&connector->base); + + 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 intel_display *display, + const struct intel_bios_encoder_data *devdata) +{ + struct intel_dsi *intel_dsi; + struct intel_encoder *encoder; + struct intel_connector *intel_connector; + struct drm_connector *connector; + enum port port; + + port = intel_bios_encoder_port(devdata); + if (port == PORT_NONE) + return; + + intel_dsi = kzalloc_obj(*intel_dsi); + 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; + + encoder->devdata = devdata; + + /* register DSI encoder with DRM subsystem */ + drm_encoder_init(display->drm, &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; + encoder->shutdown = intel_dsi_shutdown; + + /* register DSI connector with DRM subsystem */ + drm_connector_init(display->drm, 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; + intel_connector->get_hw_state = intel_connector_get_hw_state; + + /* attach connector to encoder */ + intel_connector_attach_encoder(intel_connector, encoder); + + intel_dsi->panel_power_off_time = ktime_get_boottime(); + + intel_bios_init_panel_late(display, &intel_connector->panel, encoder->devdata, NULL); + + mutex_lock(&display->drm->mode_config.mutex); + intel_panel_add_vbt_lfp_fixed_mode(intel_connector); + mutex_unlock(&display->drm->mode_config.mutex); + + if (!intel_panel_preferred_fixed_mode(intel_connector)) { + drm_err(display->drm, "DSI fixed mode info missing\n"); + goto err; + } + + intel_panel_init(intel_connector, NULL); + + 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(display->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(display->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(display->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..099fc50e3 --- /dev/null +++ b/drivers/gpu/drm/i915/display/icl_dsi.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2021 Intel Corporation + */ + +#ifndef __ICL_DSI_H__ +#define __ICL_DSI_H__ + +struct intel_bios_encoder_data; +struct intel_crtc_state; +struct intel_display; + +void icl_dsi_init(struct intel_display *display, + const struct intel_bios_encoder_data *devdata); +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..55ab57adc --- /dev/null +++ b/drivers/gpu/drm/i915/display/icl_dsi_regs.h @@ -0,0 +1,344 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2022 Intel Corporation + */ + +#ifndef __ICL_DSI_REGS_H__ +#define __ICL_DSI_REGS_H__ + +#include "intel_display_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 LP_CLK_DURING_LPM (1 << 7) /* tgl+ */ +#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) /* tgl+ */ +#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 PIPELINE_FLUSH (1 << 28) +#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..e06f32402 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_acpi.c @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel ACPI functions + * + * _DSM related code stolen from nouveau_acpi.c. + */ + +#include +#include +#include + +#include + +#include "intel_acpi.h" +#include "intel_display_core.h" +#include "intel_display_types.h" +#include "intel_display_utils.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"); + ACPI_FREE(pkg); + 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] = {}; + 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 intel_display *display) +{ + struct pci_dev *pdev = to_pci_dev(display->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 intel_display *display) +{ + 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(display->drm, &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 intel_display *display) +{ + struct drm_device *drm_dev = display->drm; + struct drm_connector_list_iter conn_iter; + 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 intel_display *display) +{ + 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(display->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..788a63071 --- /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 intel_display; + +#ifdef CONFIG_ACPI +void intel_register_dsm_handler(void); +void intel_unregister_dsm_handler(void); +void intel_dsm_get_bios_data_funcs_supported(struct intel_display *display); +void intel_acpi_device_id_update(struct intel_display *display); +void intel_acpi_assign_connector_fwnodes(struct intel_display *display); +void intel_acpi_video_register(struct intel_display *display); +#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 intel_display *display) { return; } +static inline +void intel_acpi_device_id_update(struct intel_display *display) { return; } +static inline +void intel_acpi_assign_connector_fwnodes(struct intel_display *display) { return; } +static inline +void intel_acpi_video_register(struct intel_display *display) { return; } +#endif /* CONFIG_ACPI */ + +#endif /* __INTEL_ACPI_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_alpm.c b/drivers/gpu/drm/i915/display/intel_alpm.c new file mode 100644 index 000000000..c6963ea42 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_alpm.c @@ -0,0 +1,647 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright 2024, Intel Corporation. + */ + +#include + +#include + +#include "intel_alpm.h" +#include "intel_crtc.h" +#include "intel_de.h" +#include "intel_display_types.h" +#include "intel_display_utils.h" +#include "intel_dp.h" +#include "intel_dp_aux.h" +#include "intel_psr.h" +#include "intel_psr_regs.h" +#include "intel_vrr.h" + +#define SILENCE_PERIOD_MIN_TIME 80 +#define SILENCE_PERIOD_MAX_TIME 180 +#define SILENCE_PERIOD_TIME (SILENCE_PERIOD_MIN_TIME + \ + (SILENCE_PERIOD_MAX_TIME - \ + SILENCE_PERIOD_MIN_TIME) / 2) + +#define LFPS_CYCLE_COUNT 10 + +bool intel_alpm_aux_wake_supported(struct intel_dp *intel_dp) +{ + return intel_dp->alpm_dpcd & DP_ALPM_CAP; +} + +bool intel_alpm_aux_less_wake_supported(struct intel_dp *intel_dp) +{ + return intel_dp->alpm_dpcd & DP_ALPM_AUX_LESS_CAP; +} + +bool intel_alpm_is_alpm_aux_less(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + return intel_psr_needs_alpm_aux_less(intel_dp, crtc_state) || + (crtc_state->has_lobf && intel_alpm_aux_less_wake_supported(intel_dp)); +} + +void intel_alpm_init(struct intel_dp *intel_dp) +{ + mutex_init(&intel_dp->alpm.lock); +} + +static int get_silence_period_symbols(const struct intel_crtc_state *crtc_state) +{ + return SILENCE_PERIOD_TIME * intel_dp_link_symbol_clock(crtc_state->port_clock) / + 1000 / 1000; +} + +static void get_lfps_cycle_min_max_time(const struct intel_crtc_state *crtc_state, + int *min, int *max) +{ + if (crtc_state->port_clock < 540000) { + *min = 65 * LFPS_CYCLE_COUNT; + *max = 75 * LFPS_CYCLE_COUNT; + } else { + *min = 140; + *max = 800; + } +} + +static int get_lfps_cycle_time(const struct intel_crtc_state *crtc_state) +{ + int tlfps_cycle_min, tlfps_cycle_max; + + get_lfps_cycle_min_max_time(crtc_state, &tlfps_cycle_min, + &tlfps_cycle_max); + + return tlfps_cycle_min + (tlfps_cycle_max - tlfps_cycle_min) / 2; +} + +static int get_lfps_half_cycle_clocks(const struct intel_crtc_state *crtc_state) +{ + return get_lfps_cycle_time(crtc_state) * crtc_state->port_clock / 1000 / + 1000 / (2 * LFPS_CYCLE_COUNT); +} + +/* + * AUX-Less Wake Time = CEILING( ((PHY P2 to P0) + tLFPS_Period, Max+ + * tSilence, Max+ tPHY Establishment + tCDS) / tline) + * For the "PHY P2 to P0" latency see the PHY Power Control page + * (PHY P2 to P0) : https://gfxspecs.intel.com/Predator/Home/Index/68965 + * : 12 us + * The tLFPS_Period, Max term is 800ns + * The tSilence, Max term is 180ns + * The tPHY Establishment (a.k.a. t1) term is 50us + * The tCDS term is 1 or 2 times t2 + * t2 = Number ML_PHY_LOCK * tML_PHY_LOCK + * Number ML_PHY_LOCK = ( 7 + CEILING( 6.5us / tML_PHY_LOCK ) + 1) + * Rounding up the 6.5us padding to the next ML_PHY_LOCK boundary and + * adding the "+ 1" term ensures all ML_PHY_LOCK sequences that start + * within the CDS period complete within the CDS period regardless of + * entry into the period + * tML_PHY_LOCK = TPS4 Length * ( 10 / (Link Rate in MHz) ) + * TPS4 Length = 252 Symbols + */ +static int _lnl_compute_aux_less_wake_time(const struct intel_crtc_state *crtc_state) +{ + int tphy2_p2_to_p0 = 12 * 1000; + int t1 = 50 * 1000; + int tps4 = 252; + /* port_clock is link rate in 10kbit/s units */ + int tml_phy_lock = 1000 * 1000 * tps4 / crtc_state->port_clock; + int num_ml_phy_lock = 7 + DIV_ROUND_UP(6500, tml_phy_lock) + 1; + int t2 = num_ml_phy_lock * tml_phy_lock; + int tcds = 1 * t2; + + return DIV_ROUND_UP(tphy2_p2_to_p0 + get_lfps_cycle_time(crtc_state) + + SILENCE_PERIOD_TIME + t1 + tcds, 1000); +} + +static int +_lnl_compute_aux_less_alpm_params(struct intel_dp *intel_dp, + struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(intel_dp); + int aux_less_wake_time, aux_less_wake_lines, silence_period, + lfps_half_cycle; + + aux_less_wake_time = + _lnl_compute_aux_less_wake_time(crtc_state); + aux_less_wake_lines = intel_usecs_to_scanlines(&crtc_state->hw.adjusted_mode, + aux_less_wake_time); + silence_period = get_silence_period_symbols(crtc_state); + + lfps_half_cycle = get_lfps_half_cycle_clocks(crtc_state); + + if (aux_less_wake_lines > ALPM_CTL_AUX_LESS_WAKE_TIME_MASK || + silence_period > PORT_ALPM_CTL_SILENCE_PERIOD_MASK || + lfps_half_cycle > PORT_ALPM_LFPS_CTL_LAST_LFPS_HALF_CYCLE_DURATION_MASK) + return false; + + if (display->params.psr_safest_params) + aux_less_wake_lines = ALPM_CTL_AUX_LESS_WAKE_TIME_MASK; + + crtc_state->alpm_state.aux_less_wake_lines = aux_less_wake_lines; + crtc_state->alpm_state.silence_period_sym_clocks = silence_period; + crtc_state->alpm_state.lfps_half_cycle_num_of_syms = lfps_half_cycle; + + return true; +} + +static bool _lnl_compute_alpm_params(struct intel_dp *intel_dp, + struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(intel_dp); + int check_entry_lines; + + if (DISPLAY_VER(display) < 20) + return true; + + /* ALPM Entry Check = 2 + CEILING( 5us /tline ) */ + check_entry_lines = 2 + + intel_usecs_to_scanlines(&crtc_state->hw.adjusted_mode, 5); + + if (check_entry_lines > 15) + return false; + + if (!_lnl_compute_aux_less_alpm_params(intel_dp, crtc_state)) + return false; + + if (display->params.psr_safest_params) + check_entry_lines = 15; + + crtc_state->alpm_state.check_entry_lines = check_entry_lines; + + return true; +} + +/* + * IO wake time for DISPLAY_VER < 12 is not directly mentioned in Bspec. There + * are 50 us io wake time and 32 us fast wake time. Clearly preharge pulses are + * not (improperly) included in 32 us fast wake time. 50 us - 32 us = 18 us. + */ +static int skl_io_buffer_wake_time(void) +{ + return 18; +} + +static int tgl_io_buffer_wake_time(void) +{ + return 10; +} + +static int io_buffer_wake_time(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + if (DISPLAY_VER(display) >= 12) + return tgl_io_buffer_wake_time(); + else + return skl_io_buffer_wake_time(); +} + +bool intel_alpm_compute_params(struct intel_dp *intel_dp, + struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(intel_dp); + int io_wake_lines, io_wake_time, fast_wake_lines, fast_wake_time; + int tfw_exit_latency = 20; /* eDP spec */ + int phy_wake = 4; /* eDP spec */ + int preamble = 8; /* eDP spec */ + int precharge = intel_dp_aux_fw_sync_len(intel_dp) - preamble; + u8 max_wake_lines; + + io_wake_time = max(precharge, io_buffer_wake_time(crtc_state)) + + preamble + phy_wake + tfw_exit_latency; + fast_wake_time = precharge + preamble + phy_wake + + tfw_exit_latency; + + if (DISPLAY_VER(display) >= 20) + max_wake_lines = 68; + else if (DISPLAY_VER(display) >= 12) + max_wake_lines = 12; + else + max_wake_lines = 8; + + io_wake_lines = intel_usecs_to_scanlines( + &crtc_state->hw.adjusted_mode, io_wake_time); + fast_wake_lines = intel_usecs_to_scanlines( + &crtc_state->hw.adjusted_mode, fast_wake_time); + + if (io_wake_lines > max_wake_lines || + fast_wake_lines > max_wake_lines) + return false; + + if (!_lnl_compute_alpm_params(intel_dp, crtc_state)) + return false; + + if (display->params.psr_safest_params) + io_wake_lines = fast_wake_lines = max_wake_lines; + + /* According to Bspec lower limit should be set as 7 lines. */ + crtc_state->alpm_state.io_wake_lines = max(io_wake_lines, 7); + crtc_state->alpm_state.fast_wake_lines = max(fast_wake_lines, 7); + + return true; +} + +int intel_alpm_lobf_min_guardband(struct intel_crtc_state *crtc_state) +{ + struct drm_display_mode *adjusted_mode = &crtc_state->hw.adjusted_mode; + int first_sdp_position = adjusted_mode->crtc_vtotal - + adjusted_mode->crtc_vsync_start; + int waketime_in_lines; + + /* + * #FIXME: Need to check if io_wake_lines or aux_less_wake_lines + * is applicable. Currently this information is not readily + * available in crtc_state, so max will suffice for now. + */ + waketime_in_lines = max(crtc_state->alpm_state.io_wake_lines, + crtc_state->alpm_state.aux_less_wake_lines); + + if (!crtc_state->has_lobf) + return 0; + + return first_sdp_position + waketime_in_lines + crtc_state->set_context_latency; +} + +static bool intel_alpm_lobf_is_window1_sufficient(struct intel_crtc_state *crtc_state) +{ + struct drm_display_mode *adjusted_mode = &crtc_state->hw.adjusted_mode; + int vblank = adjusted_mode->crtc_vtotal - adjusted_mode->crtc_vdisplay; + int window1; + + /* + * LOBF must be disabled if the number of lines within Window 1 is not + * greater than ALPM_CTL[ALPM Entry Check] + */ + window1 = vblank - min(vblank, + crtc_state->vrr.guardband + + crtc_state->set_context_latency); + + return window1 > crtc_state->alpm_state.check_entry_lines; +} + +void intel_alpm_lobf_compute_config_late(struct intel_dp *intel_dp, + struct intel_crtc_state *crtc_state) +{ + struct drm_display_mode *adjusted_mode = &crtc_state->hw.adjusted_mode; + int waketime_in_lines, first_sdp_position; + + if (!crtc_state->has_lobf) + return; + + if (!intel_alpm_lobf_is_window1_sufficient(crtc_state)) { + crtc_state->has_lobf = false; + return; + } + + /* + * LOBF can only be enabled if the time from the start of the SCL+Guardband + * window to the position of the first SDP is greater than the time it takes + * to wake the main link. + * + * Position of first sdp : vsync_start + * start of scl + guardband : vtotal - (scl + guardband) + * time in lines to wake main link : waketime_in_lines + * + * Position of first sdp - start of (scl + guardband) > time in lines to wake main link + * vsync_start - (vtotal - (scl + guardband)) > waketime_in_lines + * vsync_start - vtotal + scl + guardband > waketime_in_lines + * scl + guardband > waketime_in_lines + (vtotal - vsync_start) + */ + first_sdp_position = adjusted_mode->crtc_vtotal - adjusted_mode->crtc_vsync_start; + if (intel_alpm_aux_less_wake_supported(intel_dp)) + waketime_in_lines = crtc_state->alpm_state.io_wake_lines; + else + waketime_in_lines = crtc_state->alpm_state.aux_less_wake_lines; + + crtc_state->has_lobf = (crtc_state->set_context_latency + crtc_state->vrr.guardband) > + (first_sdp_position + waketime_in_lines); +} + +void intel_alpm_lobf_compute_config(struct intel_dp *intel_dp, + struct intel_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct intel_display *display = to_intel_display(intel_dp); + + if (intel_dp->alpm.lobf_disable_debug) { + drm_dbg_kms(display->drm, "LOBF is disabled by debug flag\n"); + return; + } + + if (intel_dp->alpm.sink_alpm_error) + return; + + if (!intel_dp_is_edp(intel_dp)) + return; + + if (DISPLAY_VER(display) < 20) + return; + + if (!intel_dp->as_sdp_supported) + return; + + if (crtc_state->has_psr) + return; + + if (!intel_vrr_always_use_vrr_tg(display) || + !intel_vrr_is_fixed_rr(crtc_state)) + return; + + if (!(intel_alpm_aux_wake_supported(intel_dp) || + intel_alpm_aux_less_wake_supported(intel_dp))) + return; + + if (!intel_alpm_compute_params(intel_dp, crtc_state)) + return; + + crtc_state->has_lobf = true; +} + +static u32 get_pr_alpm_as_sdp_transmission_time(const struct intel_crtc_state *crtc_state) +{ + u8 as_sdp_setup_time = intel_dp_as_sdp_transmission_time(); + + switch (as_sdp_setup_time) { + case DP_PR_AS_SDP_SETUP_TIME_T1: + return PR_ALPM_CTL_ADAPTIVE_SYNC_SDP_POSITION_T1; + case DP_PR_AS_SDP_SETUP_TIME_DYNAMIC: + return PR_ALPM_CTL_ADAPTIVE_SYNC_SDP_POSITION_T1_OR_T2; + case DP_PR_AS_SDP_SETUP_TIME_T2: + return PR_ALPM_CTL_ADAPTIVE_SYNC_SDP_POSITION_T2; + default: + MISSING_CASE(as_sdp_setup_time); + return PR_ALPM_CTL_ADAPTIVE_SYNC_SDP_POSITION_T1; + } +} + +static void lnl_alpm_configure(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(intel_dp); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + u32 alpm_ctl; + + if (DISPLAY_VER(display) < 20 || (!intel_psr_needs_alpm(intel_dp, crtc_state) && + !crtc_state->has_lobf)) + return; + + mutex_lock(&intel_dp->alpm.lock); + /* + * Panel Replay on eDP is always using ALPM aux less. I.e. no need to + * check panel support at this point. + */ + if (intel_alpm_is_alpm_aux_less(intel_dp, crtc_state)) { + alpm_ctl = ALPM_CTL_ALPM_ENABLE | + ALPM_CTL_ALPM_AUX_LESS_ENABLE | + ALPM_CTL_AUX_LESS_SLEEP_HOLD_TIME_50_SYMBOLS | + ALPM_CTL_AUX_LESS_WAKE_TIME(crtc_state->alpm_state.aux_less_wake_lines); + + if (intel_dp->as_sdp_supported) { + u32 pr_alpm_ctl = get_pr_alpm_as_sdp_transmission_time(crtc_state); + + if (crtc_state->link_off_after_as_sdp_when_pr_active) + pr_alpm_ctl |= PR_ALPM_CTL_ALLOW_LINK_OFF_BETWEEN_AS_SDP_AND_SU; + if (crtc_state->disable_as_sdp_when_pr_active) + pr_alpm_ctl |= PR_ALPM_CTL_AS_SDP_TRANSMISSION_IN_ACTIVE_DISABLE; + + intel_de_write(display, PR_ALPM_CTL(display, cpu_transcoder), + pr_alpm_ctl); + } + + } else { + alpm_ctl = ALPM_CTL_EXTENDED_FAST_WAKE_ENABLE | + ALPM_CTL_EXTENDED_FAST_WAKE_TIME(crtc_state->alpm_state.fast_wake_lines); + } + + if (crtc_state->has_lobf) { + alpm_ctl |= ALPM_CTL_LOBF_ENABLE; + drm_dbg_kms(display->drm, "Link off between frames (LOBF) enabled\n"); + } + + alpm_ctl |= ALPM_CTL_ALPM_ENTRY_CHECK(crtc_state->alpm_state.check_entry_lines); + + intel_de_write(display, ALPM_CTL(display, cpu_transcoder), alpm_ctl); + mutex_unlock(&intel_dp->alpm.lock); +} + +void intel_alpm_configure(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + lnl_alpm_configure(intel_dp, crtc_state); + intel_dp->alpm.transcoder = crtc_state->cpu_transcoder; +} + +void intel_alpm_port_configure(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(intel_dp); + enum port port = dp_to_dig_port(intel_dp)->base.port; + u32 alpm_ctl_val = 0, lfps_ctl_val = 0; + + if (DISPLAY_VER(display) < 20) + return; + + if (intel_alpm_is_alpm_aux_less(intel_dp, crtc_state)) { + alpm_ctl_val = PORT_ALPM_CTL_ALPM_AUX_LESS_ENABLE | + PORT_ALPM_CTL_MAX_PHY_SWING_SETUP(15) | + PORT_ALPM_CTL_MAX_PHY_SWING_HOLD(0) | + PORT_ALPM_CTL_SILENCE_PERIOD( + crtc_state->alpm_state.silence_period_sym_clocks); + lfps_ctl_val = PORT_ALPM_LFPS_CTL_LFPS_CYCLE_COUNT(LFPS_CYCLE_COUNT) | + PORT_ALPM_LFPS_CTL_LFPS_HALF_CYCLE_DURATION( + crtc_state->alpm_state.lfps_half_cycle_num_of_syms) | + PORT_ALPM_LFPS_CTL_FIRST_LFPS_HALF_CYCLE_DURATION( + crtc_state->alpm_state.lfps_half_cycle_num_of_syms) | + PORT_ALPM_LFPS_CTL_LAST_LFPS_HALF_CYCLE_DURATION( + crtc_state->alpm_state.lfps_half_cycle_num_of_syms); + } + + intel_de_write(display, PORT_ALPM_CTL(port), alpm_ctl_val); + + intel_de_write(display, PORT_ALPM_LFPS_CTL(port), lfps_ctl_val); +} + +void intel_alpm_lobf_disable(const struct intel_crtc_state *new_crtc_state) +{ + struct intel_display *display = to_intel_display(new_crtc_state); + enum transcoder cpu_transcoder = new_crtc_state->cpu_transcoder; + struct intel_encoder *encoder; + + for_each_intel_encoder_mask(display->drm, encoder, + new_crtc_state->uapi.encoder_mask) { + struct intel_dp *intel_dp; + + if (!intel_encoder_is_dp(encoder)) + continue; + + intel_dp = enc_to_intel_dp(encoder); + + if (!intel_dp_is_edp(intel_dp)) + continue; + + mutex_lock(&intel_dp->alpm.lock); + intel_de_write(display, ALPM_CTL(display, cpu_transcoder), 0); + drm_dbg_kms(display->drm, "Link off between frames (LOBF) disabled\n"); + mutex_unlock(&intel_dp->alpm.lock); + } +} + +void intel_alpm_enable_sink(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state) +{ + u8 val; + + if (!intel_psr_needs_alpm(intel_dp, crtc_state) && !crtc_state->has_lobf) + return; + + val = DP_ALPM_ENABLE | DP_ALPM_LOCK_ERROR_IRQ_HPD_ENABLE; + + if (crtc_state->has_panel_replay || (crtc_state->has_lobf && + intel_alpm_aux_less_wake_supported(intel_dp))) + val |= DP_ALPM_MODE_AUX_LESS; + + drm_dp_dpcd_writeb(&intel_dp->aux, DP_RECEIVER_ALPM_CONFIG, val); +} + +void intel_alpm_lobf_enable(const struct intel_crtc_state *new_crtc_state) +{ + struct intel_display *display = to_intel_display(new_crtc_state); + struct intel_encoder *encoder; + + for_each_intel_encoder_mask(display->drm, encoder, + new_crtc_state->uapi.encoder_mask) { + struct intel_dp *intel_dp; + + if (!intel_encoder_is_dp(encoder)) + continue; + + intel_dp = enc_to_intel_dp(encoder); + + if (intel_dp_is_edp(intel_dp)) { + intel_alpm_enable_sink(intel_dp, new_crtc_state); + intel_alpm_configure(intel_dp, new_crtc_state); + } + } +} + +static int i915_edp_lobf_info_show(struct seq_file *m, void *data) +{ + struct intel_connector *connector = m->private; + struct intel_display *display = to_intel_display(connector); + struct drm_crtc *crtc; + struct intel_crtc_state *crtc_state; + enum transcoder cpu_transcoder; + u32 alpm_ctl; + int ret; + + ret = drm_modeset_lock_single_interruptible(&display->drm->mode_config.connection_mutex); + if (ret) + return ret; + + crtc = connector->base.state->crtc; + if (connector->base.status != connector_status_connected || !crtc) { + ret = -ENODEV; + goto out; + } + + crtc_state = to_intel_crtc_state(crtc->state); + cpu_transcoder = crtc_state->cpu_transcoder; + alpm_ctl = intel_de_read(display, ALPM_CTL(display, cpu_transcoder)); + seq_printf(m, "LOBF status: %s\n", str_enabled_disabled(alpm_ctl & ALPM_CTL_LOBF_ENABLE)); + seq_printf(m, "Aux-wake alpm status: %s\n", + str_enabled_disabled(!(alpm_ctl & ALPM_CTL_ALPM_AUX_LESS_ENABLE))); + seq_printf(m, "Aux-less alpm status: %s\n", + str_enabled_disabled(alpm_ctl & ALPM_CTL_ALPM_AUX_LESS_ENABLE)); +out: + drm_modeset_unlock(&display->drm->mode_config.connection_mutex); + + return ret; +} + +DEFINE_SHOW_ATTRIBUTE(i915_edp_lobf_info); + +static int +i915_edp_lobf_debug_get(void *data, u64 *val) +{ + struct intel_connector *connector = data; + struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); + + *val = intel_dp->alpm.lobf_disable_debug; + + return 0; +} + +static int +i915_edp_lobf_debug_set(void *data, u64 val) +{ + struct intel_connector *connector = data; + struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); + + intel_dp->alpm.lobf_disable_debug = val; + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(i915_edp_lobf_debug_fops, + i915_edp_lobf_debug_get, i915_edp_lobf_debug_set, + "%llu\n"); + +void intel_alpm_lobf_debugfs_add(struct intel_connector *connector) +{ + struct intel_display *display = to_intel_display(connector); + struct dentry *root = connector->base.debugfs_entry; + + if (DISPLAY_VER(display) < 20 || + connector->base.connector_type != DRM_MODE_CONNECTOR_eDP) + return; + + debugfs_create_file("i915_edp_lobf_debug", 0644, root, + connector, &i915_edp_lobf_debug_fops); + + debugfs_create_file("i915_edp_lobf_info", 0444, root, + connector, &i915_edp_lobf_info_fops); +} + +void intel_alpm_disable(struct intel_dp *intel_dp) +{ + struct intel_display *display = to_intel_display(intel_dp); + enum transcoder cpu_transcoder = intel_dp->alpm.transcoder; + + if (DISPLAY_VER(display) < 20 || !intel_dp->alpm_dpcd) + return; + + mutex_lock(&intel_dp->alpm.lock); + + intel_de_rmw(display, ALPM_CTL(display, cpu_transcoder), + ALPM_CTL_ALPM_ENABLE | ALPM_CTL_LOBF_ENABLE, 0); + + drm_dbg_kms(display->drm, "Disabling ALPM\n"); + mutex_unlock(&intel_dp->alpm.lock); +} + +bool intel_alpm_get_error(struct intel_dp *intel_dp) +{ + struct intel_display *display = to_intel_display(intel_dp); + struct drm_dp_aux *aux = &intel_dp->aux; + u8 val; + int r; + + r = drm_dp_dpcd_readb(aux, DP_RECEIVER_ALPM_STATUS, &val); + if (r != 1) { + drm_err(display->drm, "Error reading ALPM status\n"); + return true; + } + + if (val & DP_ALPM_LOCK_TIMEOUT_ERROR) { + drm_dbg_kms(display->drm, "ALPM lock timeout error\n"); + + /* Clearing error */ + drm_dp_dpcd_writeb(aux, DP_RECEIVER_ALPM_STATUS, val); + return true; + } + + return false; +} diff --git a/drivers/gpu/drm/i915/display/intel_alpm.h b/drivers/gpu/drm/i915/display/intel_alpm.h new file mode 100644 index 000000000..1cf70668a --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_alpm.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2024 Intel Corporation + */ + +#ifndef _INTEL_ALPM_H +#define _INTEL_ALPM_H + +#include + +struct intel_dp; +struct intel_crtc_state; +struct drm_connector_state; +struct intel_connector; +struct intel_atomic_state; +struct intel_crtc; + +void intel_alpm_init(struct intel_dp *intel_dp); +bool intel_alpm_compute_params(struct intel_dp *intel_dp, + struct intel_crtc_state *crtc_state); +void intel_alpm_lobf_compute_config(struct intel_dp *intel_dp, + struct intel_crtc_state *crtc_state, + struct drm_connector_state *conn_state); +void intel_alpm_configure(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state); +void intel_alpm_enable_sink(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state); +void intel_alpm_lobf_disable(const struct intel_crtc_state *new_crtc_state); +void intel_alpm_port_configure(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state); +void intel_alpm_lobf_enable(const struct intel_crtc_state *new_crtc_state); +void intel_alpm_lobf_debugfs_add(struct intel_connector *connector); +bool intel_alpm_aux_wake_supported(struct intel_dp *intel_dp); +bool intel_alpm_aux_less_wake_supported(struct intel_dp *intel_dp); +bool intel_alpm_is_alpm_aux_less(struct intel_dp *intel_dp, + const struct intel_crtc_state *crtc_state); +void intel_alpm_disable(struct intel_dp *intel_dp); +bool intel_alpm_get_error(struct intel_dp *intel_dp); +void intel_alpm_lobf_compute_config_late(struct intel_dp *intel_dp, + struct intel_crtc_state *crtc_state); +int intel_alpm_lobf_min_guardband(struct intel_crtc_state *crtc_state); +#endif 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..0e4f0678c --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_atomic.c @@ -0,0 +1,368 @@ +/* + * 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_plane.c for the plane-specific atomic functionality. + */ + +#include +#include +#include +#include +#include + +#include "intel_atomic.h" +#include "intel_cdclk.h" +#include "intel_display_core.h" +#include "intel_display_types.h" +#include "intel_dp_tunnel.h" +#include "intel_fb.h" +#include "intel_global_state.h" +#include "intel_hdcp.h" +#include "intel_psr.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 intel_display *display = to_intel_display(connector->dev); + const struct intel_digital_connector_state *intel_conn_state = + to_intel_digital_connector_state(state); + + if (property == display->properties.force_audio) + *val = intel_conn_state->force_audio; + else if (property == display->properties.broadcast_rgb) + *val = intel_conn_state->broadcast_rgb; + else { + drm_dbg_atomic(display->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 intel_display *display = to_intel_display(connector->dev); + struct intel_digital_connector_state *intel_conn_state = + to_intel_digital_connector_state(state); + + if (property == display->properties.force_audio) { + intel_conn_state->force_audio = val; + return 0; + } + + if (property == display->properties.broadcast_rgb) { + intel_conn_state->broadcast_rgb = val; + return 0; + } + + drm_dbg_atomic(display->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_commit *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; + + for_each_new_intel_crtc_in_state(state, crtc, crtc_state) { + 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); + + if (crtc_state->pre_csc_lut) + drm_property_blob_get(crtc_state->pre_csc_lut); + if (crtc_state->post_csc_lut) + drm_property_blob_get(crtc_state->post_csc_lut); + + if (crtc_state->dp_tunnel_ref.tunnel) + drm_dp_tunnel_ref_get(crtc_state->dp_tunnel_ref.tunnel, + &crtc_state->dp_tunnel_ref); + + crtc_state->update_pipe = false; + crtc_state->update_m_n = false; + crtc_state->update_lrr = 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->wm.need_postvbl_update = false; + crtc_state->do_async_flip = false; + crtc_state->fb_bits = 0; + crtc_state->update_planes = 0; + crtc_state->dsb_color = NULL; + crtc_state->dsb_commit = NULL; + crtc_state->use_dsb = false; + + 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); + + drm_property_blob_put(crtc_state->pre_csc_lut); + drm_property_blob_put(crtc_state->post_csc_lut); +} + +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_color); + drm_WARN_ON(crtc->dev, crtc_state->dsb_commit); + + __drm_atomic_helper_crtc_destroy_state(&crtc_state->uapi); + intel_crtc_free_hw_state(crtc_state); + if (crtc_state->dp_tunnel_ref.tunnel) + drm_dp_tunnel_ref_put(&crtc_state->dp_tunnel_ref); + kfree(crtc_state); +} + +struct drm_atomic_commit * +intel_atomic_state_alloc(struct drm_device *dev) +{ + struct intel_atomic_state *state = kzalloc_obj(*state); + + if (!state || drm_atomic_commit_init(dev, &state->base) < 0) { + kfree(state); + return NULL; + } + + return &state->base; +} + +void intel_atomic_state_free(struct drm_atomic_commit *_state) +{ + struct intel_atomic_state *state = to_intel_atomic_state(_state); + + drm_atomic_commit_default_release(&state->base); + kfree(state->global_objs); + kfree(state); +} + +void intel_atomic_state_clear(struct drm_atomic_commit *s) +{ + struct intel_atomic_state *state = to_intel_atomic_state(s); + + drm_atomic_commit_default_clear(&state->base); + intel_atomic_clear_global_state(state); + + /* state->internal not reset on purpose */ + + state->dpll_set = state->modeset = false; + + intel_dp_tunnel_atomic_cleanup_inherited_state(state); +} + +struct intel_crtc_state * +intel_atomic_get_crtc_state(struct drm_atomic_commit *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..4dc5205a4 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_atomic.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_ATOMIC_H__ +#define __INTEL_ATOMIC_H__ + +#include + +struct drm_atomic_commit; +struct drm_connector; +struct drm_connector_state; +struct drm_crtc; +struct drm_crtc_state; +struct drm_device; +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_commit *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_commit *intel_atomic_state_alloc(struct drm_device *dev); +void intel_atomic_state_free(struct drm_atomic_commit *state); +void intel_atomic_state_clear(struct drm_atomic_commit *state); + +struct intel_crtc_state * +intel_atomic_get_crtc_state(struct drm_atomic_commit *state, + struct intel_crtc *crtc); + +#endif /* __INTEL_ATOMIC_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..9729f1837 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_audio.c @@ -0,0 +1,1416 @@ +/* + * 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 +#include +#include + +#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_display_wa.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); + void (*audio_codec_get_config)(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state); +}; + +struct hdmi_aud_ncts { + int sample_rate; + int clock; + int n; + int cts; +}; + +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 intel_display *display = to_intel_display(crtc_state); + 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(display) < 12 && adjusted_mode->crtc_clock > 148500) + i = ARRAY_SIZE(hdmi_audio_clock); + + if (i == ARRAY_SIZE(hdmi_audio_clock)) { + drm_dbg_kms(display->drm, + "HDMI audio pixel clock setting for %d not found, falling back to defaults\n", + adjusted_mode->crtc_clock); + i = 1; + } + + drm_dbg_kms(display->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; +} + +/* ELD buffer size in dwords */ +static int g4x_eld_buffer_size(struct intel_display *display) +{ + u32 tmp; + + tmp = intel_de_read(display, G4X_AUD_CNTL_ST); + + return REG_FIELD_GET(G4X_ELD_BUFFER_SIZE_MASK, tmp); +} + +static void g4x_audio_codec_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(encoder); + u32 *eld = (u32 *)crtc_state->eld; + int eld_buffer_size, len, i; + u32 tmp; + + tmp = intel_de_read(display, G4X_AUD_CNTL_ST); + if ((tmp & G4X_ELD_VALID) == 0) + return; + + intel_de_rmw(display, G4X_AUD_CNTL_ST, G4X_ELD_ADDRESS_MASK, 0); + + eld_buffer_size = g4x_eld_buffer_size(display); + len = min_t(int, sizeof(crtc_state->eld) / 4, eld_buffer_size); + + for (i = 0; i < len; i++) + eld[i] = intel_de_read(display, G4X_HDMIW_HDMIEDID); +} + +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 intel_display *display = to_intel_display(encoder); + struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->uapi.crtc); + + /* Invalidate ELD */ + intel_de_rmw(display, G4X_AUD_CNTL_ST, + G4X_ELD_VALID, 0); + + intel_crtc_wait_for_next_vblank(crtc); + intel_crtc_wait_for_next_vblank(crtc); +} + +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 intel_display *display = to_intel_display(encoder); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + const u32 *eld = (const u32 *)crtc_state->eld; + int eld_buffer_size, len, i; + + intel_crtc_wait_for_next_vblank(crtc); + + intel_de_rmw(display, G4X_AUD_CNTL_ST, + G4X_ELD_VALID | G4X_ELD_ADDRESS_MASK, 0); + + eld_buffer_size = g4x_eld_buffer_size(display); + len = min(drm_eld_size(crtc_state->eld) / 4, eld_buffer_size); + + for (i = 0; i < len; i++) + intel_de_write(display, G4X_HDMIW_HDMIEDID, eld[i]); + for (; i < eld_buffer_size; i++) + intel_de_write(display, G4X_HDMIW_HDMIEDID, 0); + + drm_WARN_ON(display->drm, + (intel_de_read(display, G4X_AUD_CNTL_ST) & G4X_ELD_ADDRESS_MASK) != 0); + + intel_de_rmw(display, G4X_AUD_CNTL_ST, + 0, G4X_ELD_VALID); +} + +static void +hsw_dp_audio_config_update(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(encoder); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + + /* Enable time stamps. Let HW calculate Maud/Naud values */ + intel_de_rmw(display, HSW_AUD_CFG(cpu_transcoder), + AUD_CONFIG_N_VALUE_INDEX | + AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK | + AUD_CONFIG_UPPER_N_MASK | + AUD_CONFIG_LOWER_N_MASK | + AUD_CONFIG_N_PROG_ENABLE, + AUD_CONFIG_N_VALUE_INDEX); + +} + +static void +hsw_hdmi_audio_config_update(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(encoder); + struct i915_audio_component *acomp = 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(display, 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(display->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(display->drm, "using automatic N\n"); + } + + intel_de_write(display, 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(display, HSW_AUD_M_CTS_ENABLE(cpu_transcoder)); + tmp &= ~AUD_M_CTS_M_PROG_ENABLE; + tmp &= ~AUD_M_CTS_M_VALUE_INDEX; + intel_de_write(display, 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 intel_audio_sdp_split_update(const struct intel_crtc_state *crtc_state, + bool enable) +{ + struct intel_display *display = to_intel_display(crtc_state); + enum transcoder trans = crtc_state->cpu_transcoder; + + if (!HAS_DP20(display)) + return; + + intel_de_rmw(display, AUD_DP_2DOT0_CTRL(trans), AUD_ENABLE_SDP_SPLIT, + enable && crtc_state->sdp_split_enable ? AUD_ENABLE_SDP_SPLIT : 0); +} + +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 intel_display *display = to_intel_display(encoder); + struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->uapi.crtc); + enum transcoder cpu_transcoder = old_crtc_state->cpu_transcoder; + + mutex_lock(&display->audio.mutex); + + /* Disable timestamps */ + intel_de_rmw(display, HSW_AUD_CFG(cpu_transcoder), + AUD_CONFIG_N_VALUE_INDEX | + AUD_CONFIG_UPPER_N_MASK | + AUD_CONFIG_LOWER_N_MASK, + AUD_CONFIG_N_PROG_ENABLE | + (intel_crtc_has_dp_encoder(old_crtc_state) ? + AUD_CONFIG_N_VALUE_INDEX : 0)); + + /* Invalidate ELD */ + intel_de_rmw(display, HSW_AUD_PIN_ELD_CP_VLD, + AUDIO_ELD_VALID(cpu_transcoder), 0); + + intel_crtc_wait_for_next_vblank(crtc); + intel_crtc_wait_for_next_vblank(crtc); + + /* Disable audio presence detect */ + intel_de_rmw(display, HSW_AUD_PIN_ELD_CP_VLD, + AUDIO_OUTPUT_ENABLE(cpu_transcoder), 0); + + /* + * WA_14020863754: Implement Audio Workaround + * Corner case with Min Hblank Fix can cause audio hang + */ + if (intel_display_wa(display, INTEL_DISPLAY_WA_14020863754)) + intel_de_rmw(display, AUD_CHICKENBIT_REG3, DACBE_DISABLE_MIN_HBLANK_FIX, 0); + + intel_audio_sdp_split_update(old_crtc_state, false); + + mutex_unlock(&display->audio.mutex); +} + +static unsigned int calc_hblank_early_prog(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(encoder); + 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_bppx16; + 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_bppx16 = crtc_state->dsc.compressed_bpp_x16; + cdclk = 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(display->drm, + "h_active = %u link_clk = %u : lanes = %u vdsc_bpp = " FXP_Q4_FMT " cdclk = %u\n", + h_active, link_clk, lanes, FXP_Q4_ARGS(vdsc_bppx16), cdclk); + + if (WARN_ON(!link_clk || !pixel_clk || !lanes || !vdsc_bppx16 || !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_bppx16 * 8, 1000000), + mul_u32_u32(link_clk * lanes * 16, 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 intel_display *display = to_intel_display(encoder); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + unsigned int hblank_early_prog, samples_room; + unsigned int val; + + if (DISPLAY_VER(display) < 11) + return; + + val = intel_de_read(display, AUD_CONFIG_BE); + + if (DISPLAY_VER(display) == 11) + val |= HBLANK_EARLY_ENABLE_ICL(cpu_transcoder); + else if (DISPLAY_VER(display) >= 12) + val |= HBLANK_EARLY_ENABLE_TGL(cpu_transcoder); + + 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(cpu_transcoder); + hblank_early_prog = calc_hblank_early_prog(encoder, crtc_state); + if (hblank_early_prog < 32) + val |= HBLANK_START_COUNT(cpu_transcoder, HBLANK_START_COUNT_32); + else if (hblank_early_prog < 64) + val |= HBLANK_START_COUNT(cpu_transcoder, HBLANK_START_COUNT_64); + else if (hblank_early_prog < 96) + val |= HBLANK_START_COUNT(cpu_transcoder, HBLANK_START_COUNT_96); + else + val |= HBLANK_START_COUNT(cpu_transcoder, HBLANK_START_COUNT_128); + + /* Get samples room value required */ + val &= ~NUMBER_SAMPLES_PER_LINE_MASK(cpu_transcoder); + samples_room = calc_samples_room(crtc_state); + if (samples_room < 3) + val |= NUMBER_SAMPLES_PER_LINE(cpu_transcoder, samples_room); + else /* Program 0 i.e "All Samples available in buffer" */ + val |= NUMBER_SAMPLES_PER_LINE(cpu_transcoder, 0x0); + } + + intel_de_write(display, AUD_CONFIG_BE, val); +} + +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 intel_display *display = to_intel_display(encoder); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + + mutex_lock(&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); + + intel_audio_sdp_split_update(crtc_state, true); + + /* + * WA_14020863754: Implement Audio Workaround + * Corner case with Min Hblank Fix can cause audio hang + */ + if (intel_display_wa(display, INTEL_DISPLAY_WA_14020863754)) + intel_de_rmw(display, AUD_CHICKENBIT_REG3, 0, DACBE_DISABLE_MIN_HBLANK_FIX); + + /* Enable audio presence detect */ + intel_de_rmw(display, HSW_AUD_PIN_ELD_CP_VLD, + 0, AUDIO_OUTPUT_ENABLE(cpu_transcoder)); + + intel_crtc_wait_for_next_vblank(crtc); + + /* Invalidate ELD */ + intel_de_rmw(display, HSW_AUD_PIN_ELD_CP_VLD, + AUDIO_ELD_VALID(cpu_transcoder), 0); + + /* + * The audio component is used to convey the ELD + * instead using of the hardware ELD buffer. + */ + + /* Enable timestamps */ + hsw_audio_config_update(encoder, crtc_state); + + mutex_unlock(&display->audio.mutex); +} + +struct ibx_audio_regs { + intel_reg_t hdmiw_hdmiedid, aud_config, aud_cntl_st, aud_cntrl_st2; +}; + +static void ibx_audio_regs_init(struct intel_display *display, + enum pipe pipe, + struct ibx_audio_regs *regs) +{ + if (display->platform.valleyview || display->platform.cherryview) { + regs->hdmiw_hdmiedid = VLV_HDMIW_HDMIEDID(pipe); + regs->aud_config = VLV_AUD_CFG(pipe); + regs->aud_cntl_st = VLV_AUD_CNTL_ST(pipe); + regs->aud_cntrl_st2 = VLV_AUD_CNTL_ST2; + } else if (HAS_PCH_CPT(display)) { + regs->hdmiw_hdmiedid = CPT_HDMIW_HDMIEDID(pipe); + regs->aud_config = CPT_AUD_CFG(pipe); + regs->aud_cntl_st = CPT_AUD_CNTL_ST(pipe); + regs->aud_cntrl_st2 = CPT_AUD_CNTRL_ST2; + } else if (HAS_PCH_IBX(display)) { + regs->hdmiw_hdmiedid = IBX_HDMIW_HDMIEDID(pipe); + regs->aud_config = IBX_AUD_CFG(pipe); + regs->aud_cntl_st = IBX_AUD_CNTL_ST(pipe); + regs->aud_cntrl_st2 = IBX_AUD_CNTL_ST2; + } +} + +static void ibx_audio_codec_disable(struct intel_encoder *encoder, + const struct intel_crtc_state *old_crtc_state, + const struct drm_connector_state *old_conn_state) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->uapi.crtc); + enum port port = encoder->port; + enum pipe pipe = crtc->pipe; + struct ibx_audio_regs regs; + + if (drm_WARN_ON(display->drm, port == PORT_A)) + return; + + ibx_audio_regs_init(display, pipe, ®s); + + mutex_lock(&display->audio.mutex); + + /* Disable timestamps */ + intel_de_rmw(display, regs.aud_config, + AUD_CONFIG_N_VALUE_INDEX | + AUD_CONFIG_UPPER_N_MASK | + AUD_CONFIG_LOWER_N_MASK, + AUD_CONFIG_N_PROG_ENABLE | + (intel_crtc_has_dp_encoder(old_crtc_state) ? + AUD_CONFIG_N_VALUE_INDEX : 0)); + + /* Invalidate ELD */ + intel_de_rmw(display, regs.aud_cntrl_st2, + IBX_ELD_VALID(port), 0); + + mutex_unlock(&display->audio.mutex); + + intel_crtc_wait_for_next_vblank(crtc); + intel_crtc_wait_for_next_vblank(crtc); +} + +static void ibx_audio_codec_enable(struct intel_encoder *encoder, + const struct intel_crtc_state *crtc_state, + const struct drm_connector_state *conn_state) +{ + struct intel_display *display = to_intel_display(encoder); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + enum port port = encoder->port; + enum pipe pipe = crtc->pipe; + struct ibx_audio_regs regs; + + if (drm_WARN_ON(display->drm, port == PORT_A)) + return; + + intel_crtc_wait_for_next_vblank(crtc); + + ibx_audio_regs_init(display, pipe, ®s); + + mutex_lock(&display->audio.mutex); + + /* Invalidate ELD */ + intel_de_rmw(display, regs.aud_cntrl_st2, + IBX_ELD_VALID(port), 0); + + /* + * The audio component is used to convey the ELD + * instead using of the hardware ELD buffer. + */ + + /* Enable timestamps */ + intel_de_rmw(display, regs.aud_config, + AUD_CONFIG_N_VALUE_INDEX | + AUD_CONFIG_N_PROG_ENABLE | + AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK, + (intel_crtc_has_dp_encoder(crtc_state) ? + AUD_CONFIG_N_VALUE_INDEX : + audio_config_hdmi_pixel_clock(crtc_state))); + + mutex_unlock(&display->audio.mutex); +} + +bool intel_audio_compute_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct intel_display *display = to_intel_display(encoder); + struct drm_connector *connector = conn_state->connector; + const struct drm_display_mode *adjusted_mode = + &crtc_state->hw.adjusted_mode; + + mutex_lock(&connector->eld_mutex); + if (!connector->eld[0]) { + drm_dbg_kms(display->drm, + "Bogus ELD on [CONNECTOR:%d:%s]\n", + connector->base.id, connector->name); + mutex_unlock(&connector->eld_mutex); + return false; + } + + BUILD_BUG_ON(sizeof(crtc_state->eld) != sizeof(connector->eld)); + memcpy(crtc_state->eld, connector->eld, sizeof(crtc_state->eld)); + + crtc_state->eld[6] = drm_av_sync_delay(connector, adjusted_mode) / 2; + mutex_unlock(&connector->eld_mutex); + + return true; +} + +/** + * 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 intel_display *display = to_intel_display(encoder); + struct i915_audio_component *acomp = display->audio.component; + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct intel_connector *connector = to_intel_connector(conn_state->connector); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + struct intel_audio_state *audio_state; + enum port port = encoder->port; + + if (!crtc_state->has_audio) + return; + + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s][ENCODER:%d:%s] Enable audio codec on [CRTC:%d:%s], %u bytes ELD\n", + connector->base.base.id, connector->base.name, + encoder->base.base.id, encoder->base.name, + crtc->base.base.id, crtc->base.name, + drm_eld_size(crtc_state->eld)); + + if (display->audio.funcs) + display->audio.funcs->audio_codec_enable(encoder, crtc_state, conn_state); + + mutex_lock(&display->audio.mutex); + + audio_state = &display->audio.state[cpu_transcoder]; + + audio_state->encoder = encoder; + BUILD_BUG_ON(sizeof(audio_state->eld) != sizeof(crtc_state->eld)); + memcpy(audio_state->eld, crtc_state->eld, sizeof(audio_state->eld)); + + mutex_unlock(&display->audio.mutex); + + if (acomp && acomp->base.audio_ops && + acomp->base.audio_ops->pin_eld_notify) { + /* audio drivers expect cpu_transcoder = -1 to indicate Non-MST cases */ + if (!intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST)) + cpu_transcoder = -1; + acomp->base.audio_ops->pin_eld_notify(acomp->base.audio_ops->audio_ptr, + (int)port, (int)cpu_transcoder); + } + + intel_lpe_audio_notify(display, cpu_transcoder, port, crtc_state->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 intel_display *display = to_intel_display(encoder); + struct i915_audio_component *acomp = display->audio.component; + struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->uapi.crtc); + struct intel_connector *connector = to_intel_connector(old_conn_state->connector); + enum transcoder cpu_transcoder = old_crtc_state->cpu_transcoder; + struct intel_audio_state *audio_state; + enum port port = encoder->port; + + if (!old_crtc_state->has_audio) + return; + + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s][ENCODER:%d:%s] Disable audio codec on [CRTC:%d:%s]\n", + connector->base.base.id, connector->base.name, + encoder->base.base.id, encoder->base.name, + crtc->base.base.id, crtc->base.name); + + if (display->audio.funcs) + display->audio.funcs->audio_codec_disable(encoder, old_crtc_state, old_conn_state); + + mutex_lock(&display->audio.mutex); + + audio_state = &display->audio.state[cpu_transcoder]; + + audio_state->encoder = NULL; + memset(audio_state->eld, 0, sizeof(audio_state->eld)); + + mutex_unlock(&display->audio.mutex); + + if (acomp && acomp->base.audio_ops && + acomp->base.audio_ops->pin_eld_notify) { + /* audio drivers expect cpu_transcoder = -1 to indicate Non-MST cases */ + if (!intel_crtc_has_type(old_crtc_state, INTEL_OUTPUT_DP_MST)) + cpu_transcoder = -1; + acomp->base.audio_ops->pin_eld_notify(acomp->base.audio_ops->audio_ptr, + (int)port, (int)cpu_transcoder); + } + + intel_lpe_audio_notify(display, cpu_transcoder, port, NULL, 0, false); +} + +static void intel_acomp_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(encoder); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + struct intel_audio_state *audio_state; + + mutex_lock(&display->audio.mutex); + + audio_state = &display->audio.state[cpu_transcoder]; + + if (audio_state->encoder) + memcpy(crtc_state->eld, audio_state->eld, sizeof(audio_state->eld)); + + mutex_unlock(&display->audio.mutex); +} + +void intel_audio_codec_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(encoder); + + if (!crtc_state->has_audio) + return; + + if (display->audio.funcs) + display->audio.funcs->audio_codec_get_config(encoder, crtc_state); +} + +static const struct intel_audio_funcs g4x_audio_funcs = { + .audio_codec_enable = g4x_audio_codec_enable, + .audio_codec_disable = g4x_audio_codec_disable, + .audio_codec_get_config = g4x_audio_codec_get_config, +}; + +static const struct intel_audio_funcs ibx_audio_funcs = { + .audio_codec_enable = ibx_audio_codec_enable, + .audio_codec_disable = ibx_audio_codec_disable, + .audio_codec_get_config = intel_acomp_get_config, +}; + +static const struct intel_audio_funcs hsw_audio_funcs = { + .audio_codec_enable = hsw_audio_codec_enable, + .audio_codec_disable = hsw_audio_codec_disable, + .audio_codec_get_config = intel_acomp_get_config, +}; + +/** + * intel_audio_hooks_init - Set up chip specific audio hooks + * @display: display device + */ +void intel_audio_hooks_init(struct intel_display *display) +{ + if (display->platform.g4x) + display->audio.funcs = &g4x_audio_funcs; + else if (display->platform.valleyview || display->platform.cherryview || + HAS_PCH_CPT(display) || HAS_PCH_IBX(display)) + display->audio.funcs = &ibx_audio_funcs; + else if (display->platform.haswell || DISPLAY_VER(display) >= 8) + display->audio.funcs = &hsw_audio_funcs; +} + +struct aud_ts_cdclk_m_n { + u8 m; + u16 n; +}; + +void intel_audio_cdclk_change_pre(struct intel_display *display) +{ + if (DISPLAY_VER(display) >= 13) + intel_de_rmw(display, 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) +{ + aud_ts->m = 60; + aud_ts->n = cdclk * aud_ts->m / 24000; +} + +void intel_audio_cdclk_change_post(struct intel_display *display) +{ + struct aud_ts_cdclk_m_n aud_ts; + + if (DISPLAY_VER(display) >= 13) { + get_aud_ts_cdclk_m_n(display->cdclk.hw.ref, + display->cdclk.hw.cdclk, &aud_ts); + + intel_de_write(display, AUD_TS_CDCLK_N, aud_ts.n); + intel_de_write(display, AUD_TS_CDCLK_M, aud_ts.m | AUD_TS_CDCLK_M_EN); + drm_dbg_kms(display->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); + + intel_cdclk_force_min_cdclk(cdclk_state, enable ? 2 * 96000 : 0); + + return drm_atomic_commit(&state->base); +} + +static void glk_force_audio_cdclk(struct intel_display *display, + bool enable) +{ + struct drm_modeset_acquire_ctx ctx; + struct drm_atomic_commit *state; + struct intel_crtc *crtc; + int ret; + + crtc = intel_first_crtc(display); + if (!crtc) + return; + + drm_modeset_acquire_init(&ctx, 0); + state = drm_atomic_commit_alloc(display->drm); + if (drm_WARN_ON(display->drm, !state)) + return; + + state->acquire_ctx = &ctx; + to_intel_atomic_state(state)->internal = true; + +retry: + ret = glk_force_audio_cdclk_commit(to_intel_atomic_state(state), crtc, + enable); + if (ret == -EDEADLK) { + drm_atomic_commit_clear(state); + drm_modeset_backoff(&ctx); + goto retry; + } + + drm_WARN_ON(display->drm, ret); + + drm_atomic_commit_put(state); + + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + +int intel_audio_min_cdclk(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + int min_cdclk = 0; + + if (!crtc_state->has_audio) + return 0; + + /* 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->port_clock >= 540000 && + crtc_state->lane_count == 4) { + if (DISPLAY_VER(display) == 10) { + /* Display WA #1145: glk */ + min_cdclk = max(min_cdclk, 316800); + } else if (DISPLAY_VER(display) == 9 || display->platform.broadwell) { + /* Display WA #1144: skl,bxt */ + min_cdclk = max(min_cdclk, 432000); + } + } + + /* + * 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 (DISPLAY_VER(display) >= 9) + min_cdclk = max(min_cdclk, 2 * 96000); + + /* + * "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 ((display->platform.valleyview || display->platform.cherryview) && + intel_crtc_has_dp_encoder(crtc_state)) + min_cdclk = max(min_cdclk, crtc_state->port_clock); + + return min_cdclk; +} + +static unsigned long intel_audio_component_get_power(struct device *kdev) +{ + struct intel_display *display = to_intel_display(kdev); + struct ref_tracker *wakeref; + + /* Catch potential impedance mismatches before they occur! */ + BUILD_BUG_ON(sizeof(wakeref) > sizeof(unsigned long)); + + wakeref = intel_display_power_get(display, POWER_DOMAIN_AUDIO_PLAYBACK); + + if (display->audio.power_refcount++ == 0) { + if (DISPLAY_VER(display) >= 9) { + intel_de_write(display, AUD_FREQ_CNTRL, + display->audio.freq_cntrl); + drm_dbg_kms(display->drm, + "restored AUD_FREQ_CNTRL to 0x%x\n", + display->audio.freq_cntrl); + } + + /* Force CDCLK to 2*BCLK as long as we need audio powered. */ + if (display->platform.geminilake) + glk_force_audio_cdclk(display, true); + + if (DISPLAY_VER(display) >= 10) + intel_de_rmw(display, AUD_PIN_BUF_CTL, + 0, AUD_PIN_BUF_ENABLE); + } + + return (unsigned long)wakeref; +} + +static void intel_audio_component_put_power(struct device *kdev, + unsigned long cookie) +{ + struct intel_display *display = to_intel_display(kdev); + struct ref_tracker *wakeref = (struct ref_tracker *)cookie; + + /* Stop forcing CDCLK to 2*BCLK if no need for audio to be powered. */ + if (--display->audio.power_refcount == 0) + if (display->platform.geminilake) + glk_force_audio_cdclk(display, false); + + intel_display_power_put(display, POWER_DOMAIN_AUDIO_PLAYBACK, wakeref); +} + +static void intel_audio_component_codec_wake_override(struct device *kdev, + bool enable) +{ + struct intel_display *display = to_intel_display(kdev); + unsigned long cookie; + + if (DISPLAY_VER(display) < 9) + return; + + cookie = intel_audio_component_get_power(kdev); + + /* + * Enable/disable generating the codec wake signal, overriding the + * internal logic to generate the codec wake to controller. + */ + intel_de_rmw(display, HSW_AUD_CHICKENBIT, + SKL_AUD_CODEC_WAKE_SIGNAL, 0); + usleep_range(1000, 1500); + + if (enable) { + intel_de_rmw(display, HSW_AUD_CHICKENBIT, + 0, SKL_AUD_CODEC_WAKE_SIGNAL); + usleep_range(1000, 1500); + } + + intel_audio_component_put_power(kdev, cookie); +} + +/* Get CDCLK in kHz */ +static int intel_audio_component_get_cdclk_freq(struct device *kdev) +{ + struct intel_display *display = to_intel_display(kdev); + + if (drm_WARN_ON_ONCE(display->drm, !HAS_DDI(display))) + return -ENODEV; + + return display->cdclk.hw.cdclk; +} + +/* + * get the intel audio state according to the parameter port and cpu_transcoder + * MST & (cpu_transcoder >= 0): return the audio.state[cpu_transcoder].encoder], + * when port is matched + * MST & (cpu_transcoder < 0): this is invalid + * Non-MST & (cpu_transcoder >= 0): only cpu_transcoder = 0 (the first device entry) + * will get the right intel_encoder with port matched + * Non-MST & (cpu_transcoder < 0): get the right intel_encoder with port matched + */ +static struct intel_audio_state *find_audio_state(struct intel_display *display, + int port, int cpu_transcoder) +{ + /* MST */ + if (cpu_transcoder >= 0) { + struct intel_audio_state *audio_state; + struct intel_encoder *encoder; + + if (drm_WARN_ON(display->drm, + cpu_transcoder >= ARRAY_SIZE(display->audio.state))) + return NULL; + + audio_state = &display->audio.state[cpu_transcoder]; + encoder = audio_state->encoder; + + if (encoder && encoder->port == port && + encoder->type == INTEL_OUTPUT_DP_MST) + return audio_state; + } + + /* Non-MST */ + if (cpu_transcoder > 0) + return NULL; + + for_each_cpu_transcoder(display, cpu_transcoder) { + struct intel_audio_state *audio_state; + struct intel_encoder *encoder; + + audio_state = &display->audio.state[cpu_transcoder]; + encoder = audio_state->encoder; + + if (encoder && encoder->port == port && + encoder->type != INTEL_OUTPUT_DP_MST) + return audio_state; + } + + return NULL; +} + +static int intel_audio_component_sync_audio_rate(struct device *kdev, int port, + int cpu_transcoder, int rate) +{ + struct intel_display *display = to_intel_display(kdev); + struct i915_audio_component *acomp = display->audio.component; + const struct intel_audio_state *audio_state; + struct intel_encoder *encoder; + struct intel_crtc *crtc; + unsigned long cookie; + int err = 0; + + if (!HAS_DDI(display)) + return 0; + + cookie = intel_audio_component_get_power(kdev); + mutex_lock(&display->audio.mutex); + + audio_state = find_audio_state(display, port, cpu_transcoder); + if (!audio_state) { + drm_dbg_kms(display->drm, "Not valid for port %c\n", + port_name(port)); + err = -ENODEV; + goto unlock; + } + + encoder = audio_state->encoder; + + /* FIXME stop using the legacy crtc pointer */ + crtc = to_intel_crtc(encoder->base.crtc); + + /* port must be valid now, otherwise the cpu_transcoder will be invalid */ + acomp->aud_sample_rate[port] = rate; + + /* FIXME get rid of the crtc->config stuff */ + hsw_audio_config_update(encoder, crtc->config); + + unlock: + mutex_unlock(&display->audio.mutex); + intel_audio_component_put_power(kdev, cookie); + return err; +} + +static int intel_audio_component_get_eld(struct device *kdev, int port, + int cpu_transcoder, bool *enabled, + unsigned char *buf, int max_bytes) +{ + struct intel_display *display = to_intel_display(kdev); + const struct intel_audio_state *audio_state; + int ret = 0; + + mutex_lock(&display->audio.mutex); + + audio_state = find_audio_state(display, port, cpu_transcoder); + if (!audio_state) { + drm_dbg_kms(display->drm, "Not valid for port %c\n", + port_name(port)); + mutex_unlock(&display->audio.mutex); + return -EINVAL; + } + + *enabled = audio_state->encoder != NULL; + if (*enabled) { + const u8 *eld = audio_state->eld; + + ret = drm_eld_size(eld); + memcpy(buf, eld, min(max_bytes, ret)); + } + + mutex_unlock(&display->audio.mutex); + return ret; +} + +static const struct drm_audio_component_ops intel_audio_component_ops = { + .owner = THIS_MODULE, + .get_power = intel_audio_component_get_power, + .put_power = intel_audio_component_put_power, + .codec_wake_override = intel_audio_component_codec_wake_override, + .get_cdclk_freq = intel_audio_component_get_cdclk_freq, + .sync_audio_rate = intel_audio_component_sync_audio_rate, + .get_eld = intel_audio_component_get_eld, +}; + +static int intel_audio_component_bind(struct device *drv_kdev, + struct device *hda_kdev, void *data) +{ + struct intel_display *display = to_intel_display(drv_kdev); + struct i915_audio_component *acomp = data; + int i; + + if (drm_WARN_ON(display->drm, acomp->base.ops || acomp->base.dev)) + return -EEXIST; + + if (drm_WARN_ON(display->drm, + !device_link_add(hda_kdev, drv_kdev, + DL_FLAG_STATELESS))) + return -ENOMEM; + + drm_modeset_lock_all(display->drm); + acomp->base.ops = &intel_audio_component_ops; + acomp->base.dev = drv_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; + display->audio.component = acomp; + drm_modeset_unlock_all(display->drm); + + return 0; +} + +static void intel_audio_component_unbind(struct device *drv_kdev, + struct device *hda_kdev, void *data) +{ + struct intel_display *display = to_intel_display(drv_kdev); + struct i915_audio_component *acomp = data; + + drm_modeset_lock_all(display->drm); + acomp->base.ops = NULL; + acomp->base.dev = NULL; + display->audio.component = NULL; + drm_modeset_unlock_all(display->drm); + + device_link_remove(hda_kdev, drv_kdev); + + if (display->audio.power_refcount) + drm_err(display->drm, + "audio power refcount %d after unbind\n", + display->audio.power_refcount); +} + +static const struct component_ops intel_audio_component_bind_ops = { + .bind = intel_audio_component_bind, + .unbind = intel_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) + +/** + * intel_audio_component_init - initialize and register the audio component + * @display: display device + * + * 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 intel_audio_component_init(struct intel_display *display) +{ + u32 aud_freq, aud_freq_init; + + if (DISPLAY_VER(display) >= 9) { + aud_freq_init = intel_de_read(display, AUD_FREQ_CNTRL); + + if (DISPLAY_VER(display) >= 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 ((display->platform.tigerlake || display->platform.rocketlake) && + aud_freq_init != AUD_FREQ_TGL_BROKEN) + aud_freq = aud_freq_init; + + drm_dbg_kms(display->drm, + "use AUD_FREQ_CNTRL of 0x%x (init value 0x%x)\n", + aud_freq, aud_freq_init); + + display->audio.freq_cntrl = aud_freq; + } + + /* init with current cdclk */ + intel_audio_cdclk_change_post(display); +} + +static void intel_audio_component_register(struct intel_display *display) +{ + int ret; + + ret = component_add_typed(display->drm->dev, + &intel_audio_component_bind_ops, + I915_COMPONENT_AUDIO); + if (ret < 0) { + drm_err(display->drm, + "failed to add audio component (%d)\n", ret); + /* continue with reduced functionality */ + return; + } + + display->audio.component_registered = true; +} + +/** + * intel_audio_component_cleanup - deregister the audio component + * @display: display device + * + * Deregisters the audio component, breaking any existing binding to the + * corresponding snd_hda_intel driver's master component. + */ +static void intel_audio_component_cleanup(struct intel_display *display) +{ + if (!display->audio.component_registered) + return; + + component_del(display->drm->dev, &intel_audio_component_bind_ops); + display->audio.component_registered = false; +} + +/** + * intel_audio_init() - Initialize the audio driver either using + * component framework or using lpe audio bridge + * @display: display device + * + */ +void intel_audio_init(struct intel_display *display) +{ + if (intel_lpe_audio_init(display) < 0) + intel_audio_component_init(display); +} + +void intel_audio_register(struct intel_display *display) +{ + if (!display->audio.lpe.platdev) + intel_audio_component_register(display); +} + +/** + * intel_audio_deinit() - deinitialize the audio driver + * @display: display device + */ +void intel_audio_deinit(struct intel_display *display) +{ + if (display->audio.lpe.platdev) + intel_lpe_audio_teardown(display); + else + intel_audio_component_cleanup(display); +} 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..42cf886f3 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_audio.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_AUDIO_H__ +#define __INTEL_AUDIO_H__ + +#include + +struct drm_connector_state; +struct intel_crtc_state; +struct intel_display; +struct intel_encoder; + +void intel_audio_hooks_init(struct intel_display *display); +bool intel_audio_compute_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state, + struct drm_connector_state *conn_state); +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_codec_get_config(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state); +void intel_audio_cdclk_change_pre(struct intel_display *display); +void intel_audio_cdclk_change_post(struct intel_display *display); +int intel_audio_min_cdclk(const struct intel_crtc_state *crtc_state); +void intel_audio_init(struct intel_display *display); +void intel_audio_register(struct intel_display *display); +void intel_audio_deinit(struct intel_display *display); + +#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..4c31844d2 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_audio_regs.h @@ -0,0 +1,170 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2022 Intel Corporation + */ + +#ifndef __INTEL_AUDIO_REGS_H__ +#define __INTEL_AUDIO_REGS_H__ + +#include "intel_display_reg_defs.h" + +#define G4X_AUD_CNTL_ST _MMIO(0x620B4) +#define G4X_ELD_VALID REG_BIT(14) +#define G4X_ELD_BUFFER_SIZE_MASK REG_GENMASK(13, 9) +#define G4X_ELD_ADDRESS_MASK REG_GENMASK(8, 5) +#define G4X_ELD_ACK REG_BIT(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 REG_GENMASK(14, 10) +#define IBX_ELD_ADDRESS_MASK REG_GENMASK(9, 5) +#define IBX_ELD_ACK REG_BIT(4) +#define IBX_AUD_CNTL_ST2 _MMIO(0xE20C0) +#define IBX_CP_READY(port) REG_BIT(((port) - 1) * 4 + 1) +#define IBX_ELD_VALID(port) REG_BIT(((port) - 1) * 4 + 0) + +#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 REG_BIT(29) +#define AUD_CONFIG_N_PROG_ENABLE REG_BIT(28) +#define AUD_CONFIG_UPPER_N_MASK REG_GENMASK(27, 20) +#define AUD_CONFIG_LOWER_N_MASK REG_GENMASK(15, 4) +#define AUD_CONFIG_N_MASK (AUD_CONFIG_UPPER_N_MASK | \ + AUD_CONFIG_LOWER_N_MASK) +#define AUD_CONFIG_N(n) (REG_FIELD_PREP(AUD_CONFIG_UPPER_N_MASK, (n) >> 12) | \ + REG_FIELD_PREP(AUD_CONFIG_LOWER_N_MASK, (n) & 0xfff)) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK REG_GENMASK(19, 16) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_25175 REG_FIELD_PREP(AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK, 0) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_25200 REG_FIELD_PREP(AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK, 1) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_27000 REG_FIELD_PREP(AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK, 2) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_27027 REG_FIELD_PREP(AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK, 3) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_54000 REG_FIELD_PREP(AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK, 4) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_54054 REG_FIELD_PREP(AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK, 5) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_74176 REG_FIELD_PREP(AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK, 6) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_74250 REG_FIELD_PREP(AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK, 7) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_148352 REG_FIELD_PREP(AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK, 8) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_148500 REG_FIELD_PREP(AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK, 9) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_296703 REG_FIELD_PREP(AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK, 10) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_297000 REG_FIELD_PREP(AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK, 11) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_593407 REG_FIELD_PREP(AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK, 12) +#define AUD_CONFIG_PIXEL_CLOCK_HDMI_594000 REG_FIELD_PREP(AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK, 13) +#define AUD_CONFIG_DISABLE_NCTS REG_BIT(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 REG_BIT(21) +#define AUD_M_CTS_M_PROG_ENABLE REG_BIT(20) +#define AUD_CONFIG_M_MASK REG_GENMASK(19, 0) + +#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 REG_BIT(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 + +/* LPE Audio */ +#define I915_HDMI_LPE_AUDIO_BASE (VLV_DISPLAY_BASE + 0x65000) +#define I915_HDMI_LPE_AUDIO_SIZE 0x1000 + +#define VLV_AUD_CHICKEN_BIT_REG _MMIO(VLV_DISPLAY_BASE + 0x62F38) +#define VLV_CHICKEN_BIT_DBG_ENABLE (1 << 0) + +#define _VLV_AUD_PORT_EN_B_DBG 0x62F20 +#define _VLV_AUD_PORT_EN_C_DBG 0x62F30 +#define _VLV_AUD_PORT_EN_D_DBG 0x62F34 +#define VLV_AUD_PORT_EN_DBG(port) _MMIO_BASE_PORT3(VLV_DISPLAY_BASE, (port) - PORT_B, \ + _VLV_AUD_PORT_EN_B_DBG, \ + _VLV_AUD_PORT_EN_C_DBG, \ + _VLV_AUD_PORT_EN_D_DBG) +#define VLV_AMP_MUTE (1 << 1) + +#define AUD_CHICKENBIT_REG3 _MMIO(0x65F1C) +#define DACBE_DISABLE_MIN_HBLANK_FIX REG_BIT(18) + +#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..b128896cb --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_backlight.c @@ -0,0 +1,1856 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2021 Intel Corporation + */ + +#include +#include +#include +#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_regs.h" +#include "intel_display_rpm.h" +#include "intel_display_types.h" +#include "intel_display_utils.h" +#include "intel_dp_aux_backlight.h" +#include "intel_dsi_dcs_backlight.h" +#include "intel_panel.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; + + if (WARN_ON(source_min >= source_max) || + WARN_ON(target_min > target_max)) + return target_min; + + /* 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 intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + + drm_WARN_ON(display->drm, panel->backlight.pwm_level_max == 0); + + if (display->params.invert_brightness < 0) + return val; + + if (display->params.invert_brightness > 0 || + intel_has_quirk(display, 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 intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + + drm_dbg_kms(display->drm, "[CONNECTOR:%d:%s] set backlight PWM = %d\n", + connector->base.base.id, connector->base.name, val); + panel->backlight.pwm_funcs->set(conn_state, val); +} + +u32 intel_backlight_level_to_pwm(struct intel_connector *connector, u32 val) +{ + struct intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + + drm_WARN_ON_ONCE(display->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 intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + + drm_WARN_ON_ONCE(display->drm, + panel->backlight.max == 0 || panel->backlight.pwm_level_max == 0); + + if (display->params.invert_brightness > 0 || + (display->params.invert_brightness == 0 && + intel_has_quirk(display, 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 intel_display *display = to_intel_display(connector); + + return intel_de_read(display, BLC_PWM_PCH_CTL2) & BACKLIGHT_DUTY_CYCLE_MASK; +} + +static u32 pch_get_backlight(struct intel_connector *connector, enum pipe unused) +{ + struct intel_display *display = to_intel_display(connector); + + return intel_de_read(display, BLC_PWM_CPU_CTL) & BACKLIGHT_DUTY_CYCLE_MASK; +} + +static u32 i9xx_get_backlight(struct intel_connector *connector, enum pipe unused) +{ + struct intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + u32 val; + + val = intel_de_read(display, BLC_PWM_CTL) & BACKLIGHT_DUTY_CYCLE_MASK; + if (DISPLAY_VER(display) < 4) + val >>= 1; + + if (panel->backlight.combination_mode) { + struct pci_dev *pdev = to_pci_dev(display->drm->dev); + u8 lbpc; + + pci_read_config_byte(pdev, LBPC, &lbpc); + val *= lbpc; + } + + return val; +} + +static u32 vlv_get_backlight(struct intel_connector *connector, enum pipe pipe) +{ + struct intel_display *display = to_intel_display(connector); + + if (drm_WARN_ON(display->drm, pipe != PIPE_A && pipe != PIPE_B)) + return 0; + + return intel_de_read(display, VLV_BLC_PWM_CTL(pipe)) & BACKLIGHT_DUTY_CYCLE_MASK; +} + +static u32 bxt_get_backlight(struct intel_connector *connector, enum pipe unused) +{ + struct intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + + return intel_de_read(display, 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 intel_display *display = to_intel_display(connector); + u32 val; + + val = intel_de_read(display, BLC_PWM_PCH_CTL2) & ~BACKLIGHT_DUTY_CYCLE_MASK; + intel_de_write(display, 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 intel_display *display = to_intel_display(connector); + u32 tmp; + + tmp = intel_de_read(display, BLC_PWM_CPU_CTL) & ~BACKLIGHT_DUTY_CYCLE_MASK; + intel_de_write(display, 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 intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + u32 tmp, mask; + + if (drm_WARN_ON(display->drm, panel->backlight.pwm_level_max == 0)) + return; + + if (panel->backlight.combination_mode) { + struct pci_dev *pdev = to_pci_dev(display->drm->dev); + u8 lbpc; + + lbpc = level * 0xfe / panel->backlight.pwm_level_max + 1; + level /= lbpc; + pci_write_config_byte(pdev, LBPC, lbpc); + } + + if (DISPLAY_VER(display) == 4) { + mask = BACKLIGHT_DUTY_CYCLE_MASK; + } else { + level <<= 1; + mask = BACKLIGHT_DUTY_CYCLE_MASK_PNV; + } + + tmp = intel_de_read(display, BLC_PWM_CTL) & ~mask; + intel_de_write(display, 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 intel_display *display = to_intel_display(connector); + enum pipe pipe = to_intel_crtc(conn_state->crtc)->pipe; + u32 tmp; + + tmp = intel_de_read(display, VLV_BLC_PWM_CTL(pipe)) & ~BACKLIGHT_DUTY_CYCLE_MASK; + intel_de_write(display, 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 intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + + intel_de_write(display, 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_might_sleep(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 intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + + drm_dbg_kms(display->drm, "[CONNECTOR:%d:%s] set backlight level = %d\n", + connector->base.base.id, connector->base.name, 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 intel_display *display = to_intel_display(connector); + 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(&display->backlight.lock); + + drm_WARN_ON(display->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(&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 intel_display *display = to_intel_display(connector); + 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(display, BLC_PWM_CPU_CTL2); + if (tmp & BLM_PWM_ENABLE) { + drm_dbg_kms(display->drm, "[CONNECTOR:%d:%s] CPU backlight was enabled, disabling\n", + connector->base.base.id, connector->base.name); + intel_de_write(display, BLC_PWM_CPU_CTL2, tmp & ~BLM_PWM_ENABLE); + } + + intel_de_rmw(display, BLC_PWM_PCH_CTL1, BLM_PCH_PWM_ENABLE, 0); +} + +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 intel_display *display = to_intel_display(connector); + + intel_backlight_set_pwm_level(old_conn_state, val); + + intel_de_rmw(display, BLC_PWM_CPU_CTL2, BLM_PWM_ENABLE, 0); + + intel_de_rmw(display, BLC_PWM_PCH_CTL1, BLM_PCH_PWM_ENABLE, 0); +} + +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 intel_connector *connector = to_intel_connector(old_conn_state->connector); + struct intel_display *display = to_intel_display(connector); + + intel_backlight_set_pwm_level(old_conn_state, val); + + intel_de_rmw(display, BLC_PWM_CTL2, BLM_PWM_ENABLE, 0); +} + +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 intel_display *display = to_intel_display(connector); + enum pipe pipe = to_intel_crtc(old_conn_state->crtc)->pipe; + + intel_backlight_set_pwm_level(old_conn_state, val); + + intel_de_rmw(display, VLV_BLC_PWM_CTL2(pipe), BLM_PWM_ENABLE, 0); +} + +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 intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + + intel_backlight_set_pwm_level(old_conn_state, val); + + intel_de_rmw(display, BXT_BLC_PWM_CTL(panel->backlight.controller), + BXT_BLC_PWM_ENABLE, 0); + + if (panel->backlight.controller == 1) + intel_de_rmw(display, UTIL_PIN_CTL, UTIL_PIN_ENABLE, 0); +} + +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 intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + + intel_backlight_set_pwm_level(old_conn_state, val); + + intel_de_rmw(display, BXT_BLC_PWM_CTL(panel->backlight.controller), + BXT_BLC_PWM_ENABLE, 0); +} + +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_might_sleep(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 intel_display *display = to_intel_display(connector); + 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 (display->drm->switch_power_state == DRM_SWITCH_POWER_CHANGING) { + drm_dbg_kms(display->drm, "[CONNECTOR:%d:%s] Skipping backlight disable on vga switch\n", + connector->base.base.id, connector->base.name); + return; + } + + mutex_lock(&display->backlight.lock); + + if (panel->backlight.device) + panel->backlight.device->props.power = BACKLIGHT_POWER_OFF; + panel->backlight.enabled = false; + panel->backlight.funcs->disable(old_conn_state, 0); + + mutex_unlock(&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 intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + u32 pch_ctl1, pch_ctl2; + + pch_ctl1 = intel_de_read(display, BLC_PWM_PCH_CTL1); + if (pch_ctl1 & BLM_PCH_PWM_ENABLE) { + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] PCH backlight already enabled\n", + connector->base.base.id, connector->base.name); + pch_ctl1 &= ~BLM_PCH_PWM_ENABLE; + intel_de_write(display, BLC_PWM_PCH_CTL1, pch_ctl1); + } + + if (HAS_PCH_LPT(display)) + intel_de_rmw(display, SOUTH_CHICKEN2, LPT_PWM_GRANULARITY, + panel->backlight.alternate_pwm_increment ? + LPT_PWM_GRANULARITY : 0); + else + intel_de_rmw(display, SOUTH_CHICKEN1, SPT_PWM_GRANULARITY, + panel->backlight.alternate_pwm_increment ? + SPT_PWM_GRANULARITY : 0); + + pch_ctl2 = panel->backlight.pwm_level_max << 16; + intel_de_write(display, 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(display)) + pch_ctl1 |= BLM_PCH_OVERRIDE_ENABLE; + + intel_de_write(display, BLC_PWM_PCH_CTL1, pch_ctl1); + intel_de_posting_read(display, BLC_PWM_PCH_CTL1); + intel_de_write(display, 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 intel_display *display = to_intel_display(connector); + 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(display, BLC_PWM_CPU_CTL2); + if (cpu_ctl2 & BLM_PWM_ENABLE) { + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] CPU backlight already enabled\n", + connector->base.base.id, connector->base.name); + cpu_ctl2 &= ~BLM_PWM_ENABLE; + intel_de_write(display, BLC_PWM_CPU_CTL2, cpu_ctl2); + } + + pch_ctl1 = intel_de_read(display, BLC_PWM_PCH_CTL1); + if (pch_ctl1 & BLM_PCH_PWM_ENABLE) { + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] PCH backlight already enabled\n", + connector->base.base.id, connector->base.name); + pch_ctl1 &= ~BLM_PCH_PWM_ENABLE; + intel_de_write(display, 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(display, BLC_PWM_CPU_CTL2, cpu_ctl2); + intel_de_posting_read(display, BLC_PWM_CPU_CTL2); + intel_de_write(display, 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(display, BLC_PWM_PCH_CTL2, pch_ctl2); + + pch_ctl1 = 0; + if (panel->backlight.active_low_pwm) + pch_ctl1 |= BLM_PCH_POLARITY; + + intel_de_write(display, BLC_PWM_PCH_CTL1, pch_ctl1); + intel_de_posting_read(display, BLC_PWM_PCH_CTL1); + intel_de_write(display, 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 intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + u32 ctl, freq; + + ctl = intel_de_read(display, BLC_PWM_CTL); + if (ctl & BACKLIGHT_DUTY_CYCLE_MASK_PNV) { + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] backlight already enabled\n", + connector->base.base.id, connector->base.name); + intel_de_write(display, 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 (display->platform.pineview && panel->backlight.active_low_pwm) + ctl |= BLM_POLARITY_PNV; + + intel_de_write(display, BLC_PWM_CTL, ctl); + intel_de_posting_read(display, 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(display) == 2) + intel_de_write(display, 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 intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + enum pipe pipe = to_intel_crtc(conn_state->crtc)->pipe; + u32 ctl, ctl2, freq; + + ctl2 = intel_de_read(display, BLC_PWM_CTL2); + if (ctl2 & BLM_PWM_ENABLE) { + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] backlight already enabled\n", + connector->base.base.id, connector->base.name); + ctl2 &= ~BLM_PWM_ENABLE; + intel_de_write(display, BLC_PWM_CTL2, ctl2); + } + + freq = panel->backlight.pwm_level_max; + if (panel->backlight.combination_mode) + freq /= 0xff; + + ctl = freq << 16; + intel_de_write(display, 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(display, BLC_PWM_CTL2, ctl2); + intel_de_posting_read(display, BLC_PWM_CTL2); + intel_de_write(display, 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 intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + enum pipe pipe = to_intel_crtc(crtc_state->uapi.crtc)->pipe; + u32 ctl, ctl2; + + ctl2 = intel_de_read(display, VLV_BLC_PWM_CTL2(pipe)); + if (ctl2 & BLM_PWM_ENABLE) { + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] backlight already enabled\n", + connector->base.base.id, connector->base.name); + ctl2 &= ~BLM_PWM_ENABLE; + intel_de_write(display, VLV_BLC_PWM_CTL2(pipe), ctl2); + } + + ctl = panel->backlight.pwm_level_max << 16; + intel_de_write(display, 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(display, VLV_BLC_PWM_CTL2(pipe), ctl2); + intel_de_posting_read(display, VLV_BLC_PWM_CTL2(pipe)); + intel_de_write(display, 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 intel_display *display = to_intel_display(connector); + 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(display, UTIL_PIN_CTL); + if (val & UTIL_PIN_ENABLE) { + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] utility pin already enabled\n", + connector->base.base.id, connector->base.name); + val &= ~UTIL_PIN_ENABLE; + intel_de_write(display, UTIL_PIN_CTL, val); + } + + val = 0; + if (panel->backlight.util_pin_active_low) + val |= UTIL_PIN_POLARITY; + intel_de_write(display, UTIL_PIN_CTL, + val | UTIL_PIN_PIPE(pipe) | UTIL_PIN_MODE_PWM | UTIL_PIN_ENABLE); + } + + pwm_ctl = intel_de_read(display, BXT_BLC_PWM_CTL(panel->backlight.controller)); + if (pwm_ctl & BXT_BLC_PWM_ENABLE) { + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] backlight already enabled\n", + connector->base.base.id, connector->base.name); + pwm_ctl &= ~BXT_BLC_PWM_ENABLE; + intel_de_write(display, BXT_BLC_PWM_CTL(panel->backlight.controller), + pwm_ctl); + } + + intel_de_write(display, 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(display, BXT_BLC_PWM_CTL(panel->backlight.controller), pwm_ctl); + intel_de_posting_read(display, BXT_BLC_PWM_CTL(panel->backlight.controller)); + intel_de_write(display, 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 intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + u32 pwm_ctl; + + pwm_ctl = intel_de_read(display, BXT_BLC_PWM_CTL(panel->backlight.controller)); + if (pwm_ctl & BXT_BLC_PWM_ENABLE) { + drm_dbg_kms(display->drm, "backlight already enabled\n"); + pwm_ctl &= ~BXT_BLC_PWM_ENABLE; + intel_de_write(display, BXT_BLC_PWM_CTL(panel->backlight.controller), + pwm_ctl); + } + + intel_de_write(display, 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(display, BXT_BLC_PWM_CTL(panel->backlight.controller), pwm_ctl); + intel_de_posting_read(display, BXT_BLC_PWM_CTL(panel->backlight.controller)); + intel_de_write(display, 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_might_sleep(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.min; + 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 = BACKLIGHT_POWER_ON; +} + +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_display *display = to_intel_display(connector); + 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(display->drm, "pipe %c\n", pipe_name(pipe)); + + mutex_lock(&display->backlight.lock); + + __intel_backlight_enable(crtc_state, conn_state); + + mutex_unlock(&display->backlight.lock); +} + +#if IS_ENABLED(CONFIG_BACKLIGHT_CLASS_DEVICE) +static u32 intel_panel_get_backlight(struct intel_connector *connector) +{ + struct intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + u32 val = 0; + + mutex_lock(&display->backlight.lock); + + if (panel->backlight.enabled) + val = panel->backlight.funcs->get(connector, intel_connector_get_pipe(connector)); + + mutex_unlock(&display->backlight.lock); + + drm_dbg_kms(display->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 intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + u32 hw_level; + + if (!panel->backlight.present) + return; + + mutex_lock(&display->backlight.lock); + + drm_WARN_ON(display->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(&display->backlight.lock); +} + +static int intel_backlight_device_update_status(struct backlight_device *bd) +{ + struct intel_connector *connector = bl_get_data(bd); + struct intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + + drm_modeset_lock(&display->drm->mode_config.connection_mutex, NULL); + + drm_dbg_kms(display->drm, "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 == BACKLIGHT_POWER_ON && + bd->props.brightness != 0; + panel->backlight.power(connector, enable); + } + } else { + bd->props.power = BACKLIGHT_POWER_OFF; + } + + drm_modeset_unlock(&display->drm->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 intel_display *display = to_intel_display(connector); + int ret = 0; + + with_intel_display_rpm(display) { + u32 hw_level; + + drm_modeset_lock(&display->drm->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(&display->drm->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 intel_display *display = to_intel_display(connector); + 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(display->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 = BACKLIGHT_POWER_ON; + else + props.power = BACKLIGHT_POWER_OFF; + + 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", + display->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(display->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(display->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 intel_display *display = to_intel_display(connector); + + return DIV_ROUND_CLOSEST(KHz(DISPLAY_RUNTIME_INFO(display)->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 intel_display *display = to_intel_display(connector); + 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(display)) + 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 intel_display *display = to_intel_display(connector); + + return DIV_ROUND_CLOSEST(KHz(DISPLAY_RUNTIME_INFO(display)->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 intel_display *display = to_intel_display(connector); + int clock; + + if (display->platform.pineview) + clock = KHz(DISPLAY_RUNTIME_INFO(display)->rawclk_freq); + else + clock = KHz(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 intel_display *display = to_intel_display(connector); + int clock; + + if (display->platform.g4x) + clock = KHz(DISPLAY_RUNTIME_INFO(display)->rawclk_freq); + else + clock = KHz(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 intel_display *display = to_intel_display(connector); + int mul, clock; + + if ((intel_de_read(display, CBR1_VLV) & CBR_PWM_CLOCK_MUX_SELECT) == 0) { + if (display->platform.cherryview) + clock = KHz(19200); + else + clock = MHz(25); + mul = 16; + } else { + clock = KHz(DISPLAY_RUNTIME_INFO(display)->rawclk_freq); + mul = 128; + } + + return DIV_ROUND_CLOSEST(clock, pwm_freq_hz * mul); +} + +static u16 get_vbt_pwm_freq(struct intel_connector *connector) +{ + struct intel_display *display = to_intel_display(connector); + u16 pwm_freq_hz = connector->panel.vbt.backlight.pwm_freq_hz; + + if (pwm_freq_hz) { + drm_dbg_kms(display->drm, + "VBT defined backlight frequency %u Hz\n", + pwm_freq_hz); + } else { + pwm_freq_hz = 200; + drm_dbg_kms(display->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 intel_display *display = to_intel_display(connector); + 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(display->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(display->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 intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + int min; + + drm_WARN_ON(display->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(display->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 intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + u32 cpu_ctl2, pch_ctl1, pch_ctl2, val; + bool alt, cpu_mode; + + if (HAS_PCH_LPT(display)) + alt = intel_de_read(display, SOUTH_CHICKEN2) & LPT_PWM_GRANULARITY; + else + alt = intel_de_read(display, SOUTH_CHICKEN1) & SPT_PWM_GRANULARITY; + panel->backlight.alternate_pwm_increment = alt; + + pch_ctl1 = intel_de_read(display, BLC_PWM_PCH_CTL1); + panel->backlight.active_low_pwm = pch_ctl1 & BLM_PCH_POLARITY; + + pch_ctl2 = intel_de_read(display, BLC_PWM_PCH_CTL2); + panel->backlight.pwm_level_max = pch_ctl2 >> 16; + + cpu_ctl2 = intel_de_read(display, 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(display) && + !(pch_ctl1 & BLM_PCH_OVERRIDE_ENABLE) && + (cpu_ctl2 & BLM_PWM_ENABLE); + + if (cpu_mode) { + val = pch_get_backlight(connector, unused); + + drm_dbg_kms(display->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(display, BLC_PWM_PCH_CTL1, + pch_ctl1 | BLM_PCH_OVERRIDE_ENABLE); + + intel_de_write(display, BLC_PWM_CPU_CTL2, + cpu_ctl2 & ~BLM_PWM_ENABLE); + } + + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] Using native PCH PWM for backlight control\n", + connector->base.base.id, connector->base.name); + + return 0; +} + +static int pch_setup_backlight(struct intel_connector *connector, enum pipe unused) +{ + struct intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + u32 cpu_ctl2, pch_ctl1, pch_ctl2; + + pch_ctl1 = intel_de_read(display, BLC_PWM_PCH_CTL1); + panel->backlight.active_low_pwm = pch_ctl1 & BLM_PCH_POLARITY; + + pch_ctl2 = intel_de_read(display, 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(display, BLC_PWM_CPU_CTL2); + panel->backlight.pwm_enabled = (cpu_ctl2 & BLM_PWM_ENABLE) && + (pch_ctl1 & BLM_PCH_PWM_ENABLE); + + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] Using native PCH PWM for backlight control\n", + connector->base.base.id, connector->base.name); + + return 0; +} + +static int i9xx_setup_backlight(struct intel_connector *connector, enum pipe unused) +{ + struct intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + u32 ctl, val; + + ctl = intel_de_read(display, BLC_PWM_CTL); + + if (DISPLAY_VER(display) == 2 || display->platform.i915gm || display->platform.i945gm) + panel->backlight.combination_mode = ctl & BLM_LEGACY_MODE; + + if (display->platform.pineview) + 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; + + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] Using native PWM for backlight control\n", + connector->base.base.id, connector->base.name); + + return 0; +} + +static int i965_setup_backlight(struct intel_connector *connector, enum pipe unused) +{ + struct intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + u32 ctl, ctl2; + + ctl2 = intel_de_read(display, BLC_PWM_CTL2); + panel->backlight.combination_mode = ctl2 & BLM_COMBINATION_MODE; + panel->backlight.active_low_pwm = ctl2 & BLM_POLARITY_I965; + + ctl = intel_de_read(display, 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; + + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] Using native PWM for backlight control\n", + connector->base.base.id, connector->base.name); + + return 0; +} + +static int vlv_setup_backlight(struct intel_connector *connector, enum pipe pipe) +{ + struct intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + u32 ctl, ctl2; + + if (drm_WARN_ON(display->drm, pipe != PIPE_A && pipe != PIPE_B)) + return -ENODEV; + + ctl2 = intel_de_read(display, VLV_BLC_PWM_CTL2(pipe)); + panel->backlight.active_low_pwm = ctl2 & BLM_POLARITY_I965; + + ctl = intel_de_read(display, 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; + + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] Using native PWM for backlight control (on pipe %c)\n", + connector->base.base.id, connector->base.name, pipe_name(pipe)); + + return 0; +} + +static int +bxt_setup_backlight(struct intel_connector *connector, enum pipe unused) +{ + struct intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + u32 pwm_ctl, val; + + panel->backlight.controller = connector->panel.vbt.backlight.controller; + + pwm_ctl = intel_de_read(display, + BXT_BLC_PWM_CTL(panel->backlight.controller)); + + /* Controller 1 uses the utility pin. */ + if (panel->backlight.controller == 1) { + val = intel_de_read(display, 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(display, 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; + + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] Using native PWM for backlight control (controller=%d)\n", + connector->base.base.id, connector->base.name, + panel->backlight.controller); + + return 0; +} + +static int cnp_num_backlight_controllers(struct intel_display *display) +{ + if (INTEL_PCH_TYPE(display) >= PCH_MTL) + return 2; + + if (INTEL_PCH_TYPE(display) >= PCH_DG1) + return 1; + + if (INTEL_PCH_TYPE(display) >= PCH_ICP) + return 2; + + return 1; +} + +static bool cnp_backlight_controller_is_valid(struct intel_display *display, int controller) +{ + if (controller < 0 || controller >= cnp_num_backlight_controllers(display)) + return false; + + if (controller == 1 && + INTEL_PCH_TYPE(display) >= PCH_ICP && + INTEL_PCH_TYPE(display) <= PCH_ADP) + return intel_de_read(display, SOUTH_CHICKEN1) & ICP_SECOND_PPS_IO_SELECT; + + return true; +} + +static int +cnp_setup_backlight(struct intel_connector *connector, enum pipe unused) +{ + struct intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + u32 pwm_ctl; + + /* + * CNP has the BXT implementation of backlight, but with only one + * controller. ICP+ can have two controllers, depending on pin muxing. + */ + panel->backlight.controller = connector->panel.vbt.backlight.controller; + if (!cnp_backlight_controller_is_valid(display, panel->backlight.controller)) { + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] Invalid backlight controller %d, assuming 0\n", + connector->base.base.id, connector->base.name, + panel->backlight.controller); + panel->backlight.controller = 0; + } + + pwm_ctl = intel_de_read(display, + 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(display, 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; + + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] Using native PCH PWM for backlight control (controller=%d)\n", + connector->base.base.id, connector->base.name, + panel->backlight.controller); + + return 0; +} + +static int ext_pwm_setup_backlight(struct intel_connector *connector, + enum pipe pipe) +{ + struct intel_display *display = to_intel_display(connector); + 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(display->drm->dev, + "pwm_pmic_backlight"); + desc = "PMIC"; + } else { + panel->backlight.pwm = pwm_get(display->drm->dev, + "pwm_soc_backlight"); + desc = "SoC"; + } + + if (IS_ERR(panel->backlight.pwm)) { + drm_err(display->drm, + "[CONNECTOR:%d:%s] Failed to get the %s PWM chip\n", + connector->base.base.id, connector->base.name, 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(display->drm, + "[CONNECTOR:%d:%s] PWM already enabled at freq %ld, VBT freq %d, level %d\n", + connector->base.base.id, connector->base.name, + 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_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] Using %s PWM for backlight control\n", + connector->base.base.id, connector->base.name, 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; + + 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 intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + + if (!panel->backlight.present) + return; + + mutex_lock(&display->backlight.lock); + if (!panel->backlight.enabled) + __intel_backlight_enable(crtc_state, conn_state); + + mutex_unlock(&display->backlight.lock); +} + +int intel_backlight_setup(struct intel_connector *connector, enum pipe pipe) +{ + struct intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + int ret; + + if (!connector->panel.vbt.backlight.present) { + if (intel_has_quirk(display, QUIRK_BACKLIGHT_PRESENT)) { + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] no backlight present per VBT, but present per quirk\n", + connector->base.base.id, connector->base.name); + } else { + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] no backlight present per VBT\n", + connector->base.base.id, connector->base.name); + return 0; + } + } + + /* ensure intel_panel has been initialized first */ + if (drm_WARN_ON(display->drm, !panel->backlight.funcs)) + return -ENODEV; + + /* set level and max in panel struct */ + mutex_lock(&display->backlight.lock); + ret = panel->backlight.funcs->setup(connector, pipe); + mutex_unlock(&display->backlight.lock); + + if (ret) { + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] failed to setup backlight\n", + connector->base.base.id, connector->base.name); + return ret; + } + + panel->backlight.present = true; + + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] backlight initialized, %s, brightness %u/%u\n", + connector->base.base.id, 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 intel_display *display = to_intel_display(connector); + + if (connector->base.connector_type == DRM_MODE_CONNECTOR_DSI && + intel_dsi_dcs_init_backlight_funcs(connector) == 0) + return; + + if (display->platform.geminilake || display->platform.broxton) { + panel->backlight.pwm_funcs = &bxt_pwm_funcs; + } else if (INTEL_PCH_TYPE(display) >= PCH_CNP) { + panel->backlight.pwm_funcs = &cnp_pwm_funcs; + } else if (INTEL_PCH_TYPE(display) >= PCH_LPT_H) { + if (HAS_PCH_LPT(display)) + panel->backlight.pwm_funcs = &lpt_pwm_funcs; + else + panel->backlight.pwm_funcs = &spt_pwm_funcs; + } else if (HAS_PCH_SPLIT(display)) { + panel->backlight.pwm_funcs = &pch_pwm_funcs; + } else if (display->platform.valleyview || display->platform.cherryview) { + 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(display) == 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(display, 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..d0cdfd631 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_backlight_regs.h @@ -0,0 +1,121 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2022 Intel Corporation + */ + +#ifndef __INTEL_BACKLIGHT_REGS_H__ +#define __INTEL_BACKLIGHT_REGS_H__ + +#include "intel_display_reg_defs.h" + +#define _VLV_BLC_PWM_CTL2_A (VLV_DISPLAY_BASE + 0x61250) +#define _VLV_BLC_PWM_CTL2_B (VLV_DISPLAY_BASE + 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 (VLV_DISPLAY_BASE + 0x61254) +#define _VLV_BLC_PWM_CTL_B (VLV_DISPLAY_BASE + 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 (VLV_DISPLAY_BASE + 0x61260) +#define _VLV_BLC_HIST_CTL_B (VLV_DISPLAY_BASE + 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(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(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(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..b6fe87c29 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_bios.c @@ -0,0 +1,3861 @@ +/* + * 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 +#include +#include +#include + +#include "intel_display.h" +#include "intel_display_core.h" +#include "intel_display_rpm.h" +#include "intel_display_types.h" +#include "intel_display_utils.h" +#include "intel_gmbus.h" +#include "intel_rom.h" +#include "intel_vdsc.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 intel_display *display; + + struct child_device_config child; + struct dsc_compression_parameters_entry *dsc; + struct list_head node; +}; + +#define TARGET_ADDR1 0x70 +#define TARGET_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 * +bdb_find_section(struct intel_display *display, + enum bdb_block_id section_id) +{ + struct bdb_block_entry *entry; + + list_for_each_entry(entry, &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_LVDS_DTD, + .min_size = sizeof(struct bdb_sdvo_lvds_dtd), }, + { .section_id = BDB_EDP, + .min_size = sizeof(struct bdb_edp), }, + { .section_id = BDB_LFP_OPTIONS, + .min_size = sizeof(struct bdb_lfp_options), }, + /* + * BDB_LFP_DATA depends on BDB_LFP_DATA_PTRS, + * so keep the two ordered. + */ + { .section_id = BDB_LFP_DATA_PTRS, + .min_size = sizeof(struct bdb_lfp_data_ptrs), }, + { .section_id = BDB_LFP_DATA, + .min_size = 0, /* special case */ }, + { .section_id = BDB_LFP_BACKLIGHT, + .min_size = sizeof(struct bdb_lfp_backlight), }, + { .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 intel_display *display) +{ + const struct bdb_lfp_data_ptrs *ptrs; + size_t size; + + ptrs = bdb_find_section(display, BDB_LFP_DATA_PTRS); + if (!ptrs) + return 0; + + size = sizeof(struct bdb_lfp_data); + if (ptrs->panel_name.table_size) + size = max(size, ptrs->panel_name.offset + + sizeof(struct bdb_lfp_data_tail)); + + return size; +} + +static bool validate_lfp_data_ptrs(const void *bdb, + const struct bdb_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_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->num_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 bdb_edid_dtd) || + panel_pnp_id_size != sizeof(struct bdb_edid_pnp_id)) + return false; + + /* panel_name is not present in old VBTs */ + if (panel_name_size != 0 && + panel_name_size != sizeof(struct bdb_edid_product_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_lfp_data_ptrs *ptrs = ptrs_block; + u32 offset; + int i; + + offset = raw_block_offset(bdb, BDB_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 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 lfp_data_ptr_table *next, + const struct 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 intel_display *display, + const void *bdb) +{ + int i, size, table_size, block_size, offset, fp_timing_size; + struct bdb_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 (display->vbt.version < 155) + return NULL; + + fp_timing_size = 38; + + block = find_raw_section(bdb, BDB_LFP_DATA); + if (!block) + return NULL; + + drm_dbg_kms(display->drm, "Generating LFP data table pointers\n"); + + block_size = get_blocksize(block); + + size = fp_timing_size + sizeof(struct bdb_edid_dtd) + + sizeof(struct bdb_edid_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_LFP_DATA_PTRS; + *(u16 *)(ptrs_block + 1) = sizeof(*ptrs); + ptrs = ptrs_block + 3; + + table_size = sizeof(struct bdb_edid_pnp_id); + size = make_lfp_data_ptr(&ptrs->ptr[0].panel_pnp_id, table_size, size); + + table_size = sizeof(struct bdb_edid_dtd); + 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->num_entries++; + if (ptrs->ptr[0].dvo_timing.table_size) + ptrs->num_entries++; + if (ptrs->ptr[0].panel_pnp_id.table_size) + ptrs->num_entries++; + + if (size != 0 || ptrs->num_entries != 3) { + kfree(ptrs_block); + return NULL; + } + + size = fp_timing_size + sizeof(struct bdb_edid_dtd) + + sizeof(struct bdb_edid_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 bdb_edid_product_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 intel_display *display, + 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_LFP_DATA_PTRS) { + temp_block = generate_lfp_data_ptrs(display, bdb); + if (temp_block) + block = temp_block + 3; + } + if (!block) + return; + + drm_WARN(display->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_flex(*entry, data, max(min_size, block_size) + 3); + 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(display->drm, + "Found BDB block %d (size %zu, min size %zu)\n", + section_id, block_size, min_size); + + if (section_id == BDB_LFP_DATA_PTRS && + !fixup_lfp_data_ptrs(bdb, entry->data + 3)) { + drm_err(display->drm, + "VBT has malformed LFP data table pointers\n"); + kfree(entry); + return; + } + + list_add_tail(&entry->node, &display->vbt.bdb_blocks); +} + +static void init_bdb_blocks(struct intel_display *display, + 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_LFP_DATA) + min_size = lfp_data_min_size(display); + + init_bdb_block(display, bdb, section_id, min_size); + } +} + +static void +fill_detail_timing_data(struct intel_display *display, + struct drm_display_mode *panel_fixed_mode, + const struct bdb_edid_dtd *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/vsync_end values */ + if (panel_fixed_mode->hsync_end > panel_fixed_mode->htotal) { + drm_dbg_kms(display->drm, "reducing hsync_end %d->%d\n", + panel_fixed_mode->hsync_end, panel_fixed_mode->htotal); + panel_fixed_mode->hsync_end = panel_fixed_mode->htotal; + } + if (panel_fixed_mode->vsync_end > panel_fixed_mode->vtotal) { + drm_dbg_kms(display->drm, "reducing vsync_end %d->%d\n", + panel_fixed_mode->vsync_end, panel_fixed_mode->vtotal); + panel_fixed_mode->vsync_end = panel_fixed_mode->vtotal; + } + + drm_mode_set_name(panel_fixed_mode); +} + +static const struct bdb_edid_dtd * +get_lfp_dvo_timing(const struct bdb_lfp_data *data, + const struct bdb_lfp_data_ptrs *ptrs, + int index) +{ + return (const void *)data + ptrs->ptr[index].dvo_timing.offset; +} + +static const struct fp_timing * +get_lfp_fp_timing(const struct bdb_lfp_data *data, + const struct bdb_lfp_data_ptrs *ptrs, + int index) +{ + return (const void *)data + ptrs->ptr[index].fp_timing.offset; +} + +static const struct drm_edid_product_id * +get_lfp_pnp_id(const struct bdb_lfp_data *data, + const struct bdb_lfp_data_ptrs *ptrs, + int index) +{ + /* These two are supposed to have the same layout in memory. */ + BUILD_BUG_ON(sizeof(struct bdb_edid_pnp_id) != sizeof(struct drm_edid_product_id)); + + return (const void *)data + ptrs->ptr[index].panel_pnp_id.offset; +} + +static const struct bdb_lfp_data_tail * +get_lfp_data_tail(const struct bdb_lfp_data *data, + const struct bdb_lfp_data_ptrs *ptrs) +{ + if (ptrs->panel_name.table_size) + return (const void *)data + ptrs->panel_name.offset; + else + return NULL; +} + +static int opregion_get_panel_type(struct intel_display *display, + const struct intel_bios_encoder_data *devdata, + const struct drm_edid *drm_edid, bool use_fallback) +{ + return intel_opregion_get_panel_type(display); +} + +static int vbt_get_panel_type(struct intel_display *display, + const struct intel_bios_encoder_data *devdata, + const struct drm_edid *drm_edid, bool use_fallback) +{ + const struct bdb_lfp_options *lfp_options; + + lfp_options = bdb_find_section(display, BDB_LFP_OPTIONS); + if (!lfp_options) + return -1; + + if (lfp_options->panel_type > 0xf && + lfp_options->panel_type != 0xff) { + drm_dbg_kms(display->drm, "Invalid VBT panel type 0x%x\n", + lfp_options->panel_type); + return -1; + } + + if (devdata && devdata->child.handle == DEVICE_HANDLE_LFP2) + return lfp_options->panel_type2; + + drm_WARN_ON(display->drm, + devdata && devdata->child.handle != DEVICE_HANDLE_LFP1); + + return lfp_options->panel_type; +} + +static int pnpid_get_panel_type(struct intel_display *display, + const struct intel_bios_encoder_data *devdata, + const struct drm_edid *drm_edid, bool use_fallback) +{ + const struct bdb_lfp_data *data; + const struct bdb_lfp_data_ptrs *ptrs; + struct drm_edid_product_id product_id, product_id_nodate; + struct drm_printer p; + int i, best = -1; + + if (!drm_edid) + return -1; + + drm_edid_get_product_id(drm_edid, &product_id); + + product_id_nodate = product_id; + product_id_nodate.week_of_manufacture = 0; + product_id_nodate.year_of_manufacture = 0; + + p = drm_dbg_printer(display->drm, DRM_UT_KMS, "EDID"); + drm_edid_print_product_id(&p, &product_id, true); + + ptrs = bdb_find_section(display, BDB_LFP_DATA_PTRS); + if (!ptrs) + return -1; + + data = bdb_find_section(display, BDB_LFP_DATA); + if (!data) + return -1; + + for (i = 0; i < 16; i++) { + const struct drm_edid_product_id *vbt_id = + get_lfp_pnp_id(data, ptrs, i); + + /* full match? */ + if (!memcmp(vbt_id, &product_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, &product_id_nodate, sizeof(*vbt_id))) + best = i; + } + + return best; +} + +static int fallback_get_panel_type(struct intel_display *display, + const struct intel_bios_encoder_data *devdata, + const struct drm_edid *drm_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 intel_display *display, + const struct intel_bios_encoder_data *devdata, + const struct drm_edid *drm_edid, bool use_fallback) +{ + struct { + const char *name; + int (*get_panel_type)(struct intel_display *display, + const struct intel_bios_encoder_data *devdata, + const struct drm_edid *drm_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(display, devdata, + drm_edid, use_fallback); + + drm_WARN_ON(display->drm, panel_types[i].panel_type > 0xf && + panel_types[i].panel_type != 0xff); + + if (panel_types[i].panel_type >= 0) + drm_dbg_kms(display->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(display->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 intel_display *display, + struct intel_panel *panel) +{ + const struct bdb_lfp_options *lfp_options; + int panel_type = panel->vbt.panel_type; + int drrs_mode; + + lfp_options = bdb_find_section(display, BDB_LFP_OPTIONS); + if (!lfp_options) + return; + + panel->vbt.lvds_dither = lfp_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(lfp_options) < 16) + return; + + drrs_mode = panel_bits(lfp_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(display->drm, "DRRS supported mode is static\n"); + break; + case 2: + panel->vbt.drrs_type = DRRS_TYPE_SEAMLESS; + drm_dbg_kms(display->drm, + "DRRS supported mode is seamless\n"); + break; + default: + panel->vbt.drrs_type = DRRS_TYPE_NONE; + drm_dbg_kms(display->drm, + "DRRS not supported (VBT input)\n"); + break; + } +} + +static void +parse_lfp_panel_dtd(struct intel_display *display, + struct intel_panel *panel, + const struct bdb_lfp_data *lfp_data, + const struct bdb_lfp_data_ptrs *lfp_data_ptrs) +{ + const struct bdb_edid_dtd *panel_dvo_timing; + const struct fp_timing *fp_timing; + struct drm_display_mode *panel_fixed_mode; + int panel_type = panel->vbt.panel_type; + + panel_dvo_timing = get_lfp_dvo_timing(lfp_data, + lfp_data_ptrs, + panel_type); + + panel_fixed_mode = kzalloc_obj(*panel_fixed_mode); + if (!panel_fixed_mode) + return; + + fill_detail_timing_data(display, panel_fixed_mode, panel_dvo_timing); + + panel->vbt.lfp_vbt_mode = panel_fixed_mode; + + drm_dbg_kms(display->drm, + "Found panel mode in BIOS VBT legacy lfp table: " DRM_MODE_FMT "\n", + DRM_MODE_ARG(panel_fixed_mode)); + + fp_timing = get_lfp_fp_timing(lfp_data, + 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(display->drm, + "VBT initial LVDS value %x\n", + panel->vbt.bios_lvds_val); + } +} + +static void +parse_lfp_data(struct intel_display *display, + struct intel_panel *panel) +{ + const struct bdb_lfp_data *data; + const struct bdb_lfp_data_tail *tail; + const struct bdb_lfp_data_ptrs *ptrs; + const struct drm_edid_product_id *pnp_id; + struct drm_printer p; + int panel_type = panel->vbt.panel_type; + + ptrs = bdb_find_section(display, BDB_LFP_DATA_PTRS); + if (!ptrs) + return; + + data = bdb_find_section(display, BDB_LFP_DATA); + if (!data) + return; + + if (!panel->vbt.lfp_vbt_mode) + parse_lfp_panel_dtd(display, panel, data, ptrs); + + pnp_id = get_lfp_pnp_id(data, ptrs, panel_type); + + p = drm_dbg_printer(display->drm, DRM_UT_KMS, "Panel"); + drm_edid_print_product_id(&p, pnp_id, false); + + tail = get_lfp_data_tail(data, ptrs); + if (!tail) + return; + + drm_dbg_kms(display->drm, "Panel name: %.*s\n", + (int)sizeof(tail->panel_name[0].name), + tail->panel_name[panel_type].name); + + if (display->vbt.version >= 188) { + panel->vbt.seamless_drrs_min_refresh_rate = + tail->seamless_drrs_min_refresh_rate[panel_type]; + drm_dbg_kms(display->drm, + "Seamless DRRS min refresh rate: %d Hz\n", + panel->vbt.seamless_drrs_min_refresh_rate); + } +} + +static void +parse_generic_dtd(struct intel_display *display, + 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 (display->vbt.version < 229) + return; + + generic_dtd = bdb_find_section(display, BDB_GENERIC_DTD); + if (!generic_dtd) + return; + + if (generic_dtd->gdtd_size < sizeof(struct generic_dtd_entry)) { + drm_err(display->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(display->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(display->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_obj(*panel_fixed_mode); + 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(display->drm, + "Found panel mode in BIOS VBT generic dtd table: " DRM_MODE_FMT "\n", + DRM_MODE_ARG(panel_fixed_mode)); + + panel->vbt.lfp_vbt_mode = panel_fixed_mode; +} + +static void +parse_lfp_backlight(struct intel_display *display, + struct intel_panel *panel) +{ + const struct bdb_lfp_backlight *backlight_data; + const struct lfp_backlight_data_entry *entry; + int panel_type = panel->vbt.panel_type; + u16 level; + + backlight_data = bdb_find_section(display, BDB_LFP_BACKLIGHT); + if (!backlight_data) + return; + + if (backlight_data->entry_size != sizeof(backlight_data->data[0])) { + drm_dbg_kms(display->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(display->drm, + "PWM backlight not present in VBT (type %u)\n", + entry->type); + return; + } + + panel->vbt.backlight.type = INTEL_BACKLIGHT_DISPLAY_DDI; + panel->vbt.backlight.controller = 0; + if (display->vbt.version >= 191) { + 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 (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 (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(display->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; + } + + if (display->vbt.version >= 239) + panel->vbt.backlight.hdr_dpcd_refresh_timeout = + DIV_ROUND_UP(backlight_data->hdr_dpcd_refresh_timeout[panel_type], 100); + else + panel->vbt.backlight.hdr_dpcd_refresh_timeout = 30; + + drm_dbg_kms(display->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); +} + +static void +parse_sdvo_lvds_data(struct intel_display *display, + struct intel_panel *panel) +{ + const struct bdb_sdvo_lvds_dtd *dtd; + struct drm_display_mode *panel_fixed_mode; + int index; + + index = display->params.vbt_sdvo_panel_type; + if (index == -2) { + drm_dbg_kms(display->drm, + "Ignore SDVO LVDS mode from BIOS VBT tables.\n"); + return; + } + + if (index == -1) { + const struct bdb_sdvo_lvds_options *sdvo_lvds_options; + + sdvo_lvds_options = bdb_find_section(display, BDB_SDVO_LVDS_OPTIONS); + if (!sdvo_lvds_options) + return; + + index = sdvo_lvds_options->panel_type; + } + + dtd = bdb_find_section(display, BDB_SDVO_LVDS_DTD); + if (!dtd) + return; + + /* + * This should not happen, as long as the panel_type + * enumeration doesn't grow over 4 items. But if it does, it + * could lead to hard-to-detect bugs, so better double-check + * it here to be sure. + */ + if (index >= ARRAY_SIZE(dtd->dtd)) { + drm_err(display->drm, + "index %d is larger than dtd->dtd[4] array\n", + index); + return; + } + + panel_fixed_mode = kzalloc_obj(*panel_fixed_mode); + if (!panel_fixed_mode) + return; + + fill_detail_timing_data(display, panel_fixed_mode, &dtd->dtd[index]); + + panel->vbt.sdvo_lvds_vbt_mode = panel_fixed_mode; + + drm_dbg_kms(display->drm, + "Found SDVO LVDS mode in BIOS VBT tables: " DRM_MODE_FMT "\n", + DRM_MODE_ARG(panel_fixed_mode)); +} + +static int intel_bios_ssc_frequency(struct intel_display *display, + bool alternate) +{ + switch (DISPLAY_VER(display)) { + 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 intel_display *display) +{ + const struct bdb_general_features *general; + + general = bdb_find_section(display, BDB_GENERAL_FEATURES); + if (!general) + return; + + display->vbt.int_tv_support = general->int_tv_support; + /* int_crt_support can't be trusted on earlier platforms */ + if (display->vbt.version >= 155 && + (HAS_DDI(display) || display->platform.valleyview)) + display->vbt.int_crt_support = general->int_crt_support; + display->vbt.lvds_use_ssc = general->enable_ssc; + display->vbt.lvds_ssc_freq = + intel_bios_ssc_frequency(display, general->ssc_freq); + display->vbt.display_clock_mode = general->display_clock_mode; + display->vbt.fdi_rx_polarity_inverted = general->fdi_rx_polarity_inverted; + if (display->vbt.version >= 181) { + display->vbt.orientation = general->rotate_180 ? + DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP : + DRM_MODE_PANEL_ORIENTATION_NORMAL; + } else { + display->vbt.orientation = DRM_MODE_PANEL_ORIENTATION_UNKNOWN; + } + + if (display->vbt.version >= 249 && general->afc_startup_config) { + display->vbt.override_afc_startup = true; + display->vbt.override_afc_startup_val = general->afc_startup_config == 1 ? 0 : 7; + } + + drm_dbg_kms(display->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", + display->vbt.int_tv_support, + display->vbt.int_crt_support, + display->vbt.lvds_use_ssc, + display->vbt.lvds_ssc_freq, + display->vbt.display_clock_mode, + 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 intel_display *display) +{ + const struct intel_bios_encoder_data *devdata; + 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(display, 3, 7)) { + drm_dbg_kms(display->drm, "Skipping SDVO device mapping\n"); + return; + } + + list_for_each_entry(devdata, &display->vbt.display_devices, node) { + const struct child_device_config *child = &devdata->child; + struct sdvo_device_mapping *mapping; + + if (child->target_addr != TARGET_ADDR1 && + child->target_addr != TARGET_ADDR2) { + /* + * If the target 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(display->drm, + "Incorrect SDVO port. Skip it\n"); + continue; + } + drm_dbg_kms(display->drm, + "the SDVO device with target addr %2x is found on" + " %s port\n", + child->target_addr, + (child->dvo_port == DEVICE_PORT_DVOB) ? + "SDVOB" : "SDVOC"); + mapping = &display->vbt.sdvo_mappings[child->dvo_port - 1]; + if (!mapping->initialized) { + mapping->dvo_port = child->dvo_port; + mapping->target_addr = child->target_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(display->drm, + "SDVO device: dvo=%x, addr=%x, wiring=%d, ddc_pin=%d, i2c_pin=%d\n", + mapping->dvo_port, mapping->target_addr, + mapping->dvo_wiring, mapping->ddc_pin, + mapping->i2c_pin); + } else { + drm_dbg_kms(display->drm, + "Maybe one SDVO port is shared by " + "two SDVO device.\n"); + } + if (child->target2_addr) { + /* Maybe this is a SDVO device with multiple inputs */ + /* And the mapping info is not added */ + drm_dbg_kms(display->drm, + "there exists the target2_addr. Maybe this" + " is a SDVO device with multiple inputs.\n"); + } + count++; + } + + if (!count) { + /* No SDVO device info is found */ + drm_dbg_kms(display->drm, + "No SDVO device info is found in VBT\n"); + } +} + +static void +parse_driver_features(struct intel_display *display) +{ + const struct bdb_driver_features *driver; + + driver = bdb_find_section(display, BDB_DRIVER_FEATURES); + if (!driver) + return; + + if (DISPLAY_VER(display) >= 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) + 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 (display->vbt.version >= 134 && + driver->lvds_config != BDB_DRIVER_FEATURE_INT_LVDS && + driver->lvds_config != BDB_DRIVER_FEATURE_INT_SDVO_LVDS) + display->vbt.int_lvds_support = 0; + } +} + +static void +parse_panel_driver_features(struct intel_display *display, + struct intel_panel *panel) +{ + const struct bdb_driver_features *driver; + + driver = bdb_find_section(display, BDB_DRIVER_FEATURES); + if (!driver) + return; + + if (display->vbt.version < 228) { + drm_dbg_kms(display->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 intel_display *display, + 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 (display->vbt.version < 228) + return; + + power = bdb_find_section(display, 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 (display->vbt.version >= 232) + panel->vbt.edp.hobl = panel_bool(power->hobl, panel_type); + + if (display->vbt.version >= 233) + panel->vbt.vrr = panel_bool(power->vrr_feature_enabled, + panel_type); +} + +static void vbt_edp_to_pps_delays(struct intel_pps_delays *pps, + const struct edp_power_seq *edp_pps) +{ + pps->power_up = edp_pps->t1_t3; + pps->backlight_on = edp_pps->t8; + pps->backlight_off = edp_pps->t9; + pps->power_down = edp_pps->t10; + pps->power_cycle = edp_pps->t11_t12; +} + +static void +parse_edp(struct intel_display *display, + struct intel_panel *panel) +{ + const struct bdb_edp *edp; + const struct edp_fast_link_params *edp_link_params; + int panel_type = panel->vbt.panel_type; + + edp = bdb_find_section(display, 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_link_params = &edp->fast_link_params[panel_type]; + + vbt_edp_to_pps_delays(&panel->vbt.edp.pps, + &edp->power_seqs[panel_type]); + + if (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(display->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(display->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(display->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(display->drm, + "VBT has unknown eDP voltage swing value %u\n", + edp_link_params->vswing); + break; + } + + if (display->vbt.version >= 173) { + u8 vswing; + + /* Don't read from VBT if module parameter has valid value*/ + if (display->params.edp_vswing) { + panel->vbt.edp.low_vswing = + display->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 (display->vbt.version >= 244) + panel->vbt.edp.max_link_rate = + edp->edp_max_port_link_rate[panel_type] * 20; + + if (display->vbt.version >= 251) + panel->vbt.edp.dsc_disable = + panel_bool(edp->edp_dsc_disable, panel_type); + + if (display->vbt.version >= 261) + panel->vbt.edp.pipe_joiner_enable = + panel_bool(edp->pipe_joiner_enable, panel_type); +} + +static void +parse_psr(struct intel_display *display, + struct intel_panel *panel) +{ + const struct bdb_psr *psr; + const struct psr_table *psr_table; + int panel_type = panel->vbt.panel_type; + + psr = bdb_find_section(display, BDB_PSR); + if (!psr) { + drm_dbg_kms(display->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; + panel->vbt.psr.idle_frames = 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 (display->vbt.version >= 205 && + (DISPLAY_VER(display) >= 9 && !display->platform.broxton)) { + 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(display->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(display->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 (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 intel_display *display, + struct intel_panel *panel, + enum port port) +{ + enum port port_bc = DISPLAY_VER(display) >= 11 ? PORT_B : PORT_C; + + if (!panel->vbt.dsi.config->dual_link || 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 intel_display *display, + 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(display, &port)) + return; + + /* Initialize this to undefined indicating no generic MIPI support */ + panel->vbt.dsi.panel_id = MIPI_DSI_UNDEFINED_PANEL_ID; + + start = bdb_find_section(display, BDB_MIPI_CONFIG); + if (!start) { + drm_dbg_kms(display->drm, "No MIPI config BDB found"); + return; + } + + drm_dbg_kms(display->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(display, 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(struct intel_display *display, + 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_err(display->drm, + "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_err(display->drm, "Invalid sequence block\n"); + return NULL; + } + + if (current_id == panel_id) { + *seq_size = current_size; + return data + index; + } + + index += current_size; + } + + drm_err(display->drm, + "Sequence block detected but no valid configuration\n"); + + return NULL; +} + +static int goto_next_sequence(struct intel_display *display, + 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_err(display->drm, "Unknown operation byte\n"); + return 0; + } + } + + return 0; +} + +static int goto_next_sequence_v3(struct intel_display *display, + 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_err(display->drm, "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_err(display->drm, "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_err(display->drm, + "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_err(display->drm, "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 intel_display *display, + struct intel_panel *panel) +{ + const u8 *data = panel->vbt.dsi.sequence[MIPI_SEQ_INIT_OTP]; + int index, len; + + if (drm_WARN_ON(display->drm, + !data || panel->vbt.dsi.seq_version >= 3)) + 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/v2 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 vlv_fixup_mipi_sequences(struct intel_display *display, + struct intel_panel *panel) +{ + u8 *init_otp; + int len; + + /* Limit this to v1/v2 vid-mode sequences */ + if (panel->vbt.dsi.config->is_cmd_mode || + panel->vbt.dsi.seq_version >= 3) + 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(display, panel); + if (!len) + return; + + drm_dbg_kms(display->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; +} + +/* + * Some machines (eg. Lenovo 82TQ) appear to have broken + * VBT sequences: + * - INIT_OTP is not present at all + * - what should be in INIT_OTP is in DISPLAY_ON + * - what should be in DISPLAY_ON is in BACKLIGHT_ON + * (along with the actual backlight stuff) + * + * To make those work we simply swap DISPLAY_ON and INIT_OTP. + * + * TODO: Do we need to limit this to specific machines, + * or examine the contents of the sequences to + * avoid false positives? + */ +static void icl_fixup_mipi_sequences(struct intel_display *display, + struct intel_panel *panel) +{ + if (!panel->vbt.dsi.sequence[MIPI_SEQ_INIT_OTP] && + panel->vbt.dsi.sequence[MIPI_SEQ_DISPLAY_ON]) { + drm_dbg_kms(display->drm, + "Broken VBT: Swapping INIT_OTP and DISPLAY_ON sequences\n"); + + swap(panel->vbt.dsi.sequence[MIPI_SEQ_INIT_OTP], + panel->vbt.dsi.sequence[MIPI_SEQ_DISPLAY_ON]); + } +} + +static void fixup_mipi_sequences(struct intel_display *display, + struct intel_panel *panel) +{ + if (DISPLAY_VER(display) >= 11) + icl_fixup_mipi_sequences(display, panel); + else if (display->platform.valleyview) + vlv_fixup_mipi_sequences(display, panel); +} + +static void +parse_mipi_sequence(struct intel_display *display, + 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 = bdb_find_section(display, BDB_MIPI_SEQUENCE); + if (!sequence) { + drm_dbg_kms(display->drm, + "No MIPI Sequence found, parsing complete\n"); + return; + } + + /* Fail gracefully for forward incompatible sequence block. */ + if (sequence->version >= 4) { + drm_err(display->drm, + "Unable to parse MIPI Sequence Block v%u\n", + sequence->version); + return; + } + + drm_dbg_kms(display->drm, "Found MIPI sequence block v%u\n", + sequence->version); + + seq_data = find_panel_sequence_block(display, 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(display->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(display->drm, + "Unsupported sequence %u\n", seq_id); + + panel->vbt.dsi.sequence[seq_id] = data + index; + + if (sequence->version >= 3) + index = goto_next_sequence_v3(display, data, index, seq_size); + else + index = goto_next_sequence(display, data, index, seq_size); + if (!index) { + drm_err(display->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(display, panel); + + drm_dbg_kms(display->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 intel_display *display) +{ + const struct bdb_compression_parameters *params; + struct intel_bios_encoder_data *devdata; + u16 block_size; + int index; + + if (display->vbt.version < 198) + return; + + params = bdb_find_section(display, BDB_COMPRESSION_PARAMETERS); + if (params) { + /* Sanity checks */ + if (params->entry_size != sizeof(params->data[0])) { + drm_dbg_kms(display->drm, + "VBT: unsupported compression param entry size\n"); + return; + } + + block_size = get_blocksize(params); + if (block_size < sizeof(*params)) { + drm_dbg_kms(display->drm, + "VBT: expected 16 compression param entries\n"); + return; + } + } + + list_for_each_entry(devdata, &display->vbt.display_devices, node) { + const struct child_device_config *child = &devdata->child; + + if (!child->compression_enable) + continue; + + if (!params) { + drm_dbg_kms(display->drm, + "VBT: compression params not available\n"); + continue; + } + + if (child->compression_method_cps) { + drm_dbg_kms(display->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(struct intel_display *display, u8 val) +{ + static const u8 mapping[] = { 1, 3, 7 }; /* See VBT spec */ + + if (val >= ARRAY_SIZE(mapping)) { + drm_dbg_kms(display->drm, + "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 */ + [GMBUS_PIN_1_BXT] = DDC_BUS_DDI_B, + [GMBUS_PIN_2_BXT] = DDC_BUS_DDI_C, + [GMBUS_PIN_4_CNP] = DDC_BUS_DDI_D, /* sic */ + [GMBUS_PIN_3_BXT] = DDC_BUS_DDI_F, /* sic */ +}; + +static const u8 icp_ddc_pin_map[] = { + [GMBUS_PIN_1_BXT] = ICL_DDC_BUS_DDI_A, + [GMBUS_PIN_2_BXT] = ICL_DDC_BUS_DDI_B, + [GMBUS_PIN_3_BXT] = TGL_DDC_BUS_DDI_C, + [GMBUS_PIN_9_TC1_ICP] = ICL_DDC_BUS_PORT_1, + [GMBUS_PIN_10_TC2_ICP] = ICL_DDC_BUS_PORT_2, + [GMBUS_PIN_11_TC3_ICP] = ICL_DDC_BUS_PORT_3, + [GMBUS_PIN_12_TC4_ICP] = ICL_DDC_BUS_PORT_4, + [GMBUS_PIN_13_TC5_TGP] = TGL_DDC_BUS_PORT_5, + [GMBUS_PIN_14_TC6_TGP] = TGL_DDC_BUS_PORT_6, +}; + +static const u8 rkl_pch_tgp_ddc_pin_map[] = { + [GMBUS_PIN_1_BXT] = ICL_DDC_BUS_DDI_A, + [GMBUS_PIN_2_BXT] = ICL_DDC_BUS_DDI_B, + [GMBUS_PIN_9_TC1_ICP] = RKL_DDC_BUS_DDI_D, + [GMBUS_PIN_10_TC2_ICP] = RKL_DDC_BUS_DDI_E, +}; + +static const u8 adls_ddc_pin_map[] = { + [GMBUS_PIN_1_BXT] = ICL_DDC_BUS_DDI_A, + [GMBUS_PIN_9_TC1_ICP] = ADLS_DDC_BUS_PORT_TC1, + [GMBUS_PIN_10_TC2_ICP] = ADLS_DDC_BUS_PORT_TC2, + [GMBUS_PIN_11_TC3_ICP] = ADLS_DDC_BUS_PORT_TC3, + [GMBUS_PIN_12_TC4_ICP] = ADLS_DDC_BUS_PORT_TC4, +}; + +static const u8 gen9bc_tgp_ddc_pin_map[] = { + [GMBUS_PIN_2_BXT] = DDC_BUS_DDI_B, + [GMBUS_PIN_9_TC1_ICP] = DDC_BUS_DDI_C, + [GMBUS_PIN_10_TC2_ICP] = DDC_BUS_DDI_D, +}; + +static const u8 adlp_ddc_pin_map[] = { + [GMBUS_PIN_1_BXT] = ICL_DDC_BUS_DDI_A, + [GMBUS_PIN_2_BXT] = ICL_DDC_BUS_DDI_B, + [GMBUS_PIN_9_TC1_ICP] = ADLP_DDC_BUS_PORT_TC1, + [GMBUS_PIN_10_TC2_ICP] = ADLP_DDC_BUS_PORT_TC2, + [GMBUS_PIN_11_TC3_ICP] = ADLP_DDC_BUS_PORT_TC3, + [GMBUS_PIN_12_TC4_ICP] = ADLP_DDC_BUS_PORT_TC4, +}; + +static u8 map_ddc_pin(struct intel_display *display, u8 vbt_pin) +{ + const u8 *ddc_pin_map; + int i, n_entries; + + if (INTEL_PCH_TYPE(display) >= PCH_MTL || display->platform.alderlake_p) { + ddc_pin_map = adlp_ddc_pin_map; + n_entries = ARRAY_SIZE(adlp_ddc_pin_map); + } else if (display->platform.alderlake_s) { + ddc_pin_map = adls_ddc_pin_map; + n_entries = ARRAY_SIZE(adls_ddc_pin_map); + } else if (INTEL_PCH_TYPE(display) >= PCH_DG1) { + return vbt_pin; + } else if (display->platform.rocketlake && INTEL_PCH_TYPE(display) == 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(display) && DISPLAY_VER(display) == 9) { + ddc_pin_map = gen9bc_tgp_ddc_pin_map; + n_entries = ARRAY_SIZE(gen9bc_tgp_ddc_pin_map); + } else if (INTEL_PCH_TYPE(display) >= PCH_ICP) { + ddc_pin_map = icp_ddc_pin_map; + n_entries = ARRAY_SIZE(icp_ddc_pin_map); + } else if (HAS_PCH_CNP(display)) { + ddc_pin_map = cnp_ddc_pin_map; + n_entries = ARRAY_SIZE(cnp_ddc_pin_map); + } else { + /* Assuming direct map */ + return vbt_pin; + } + + for (i = 0; i < n_entries; i++) { + if (ddc_pin_map[i] == vbt_pin) + return i; + } + + drm_dbg_kms(display->drm, + "Ignoring alternate pin: VBT claims DDC pin %d, which is not valid for this platform\n", + vbt_pin); + return 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 intel_display *display, + 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(display) >= 13) + return __dvo_port_to_port(ARRAY_SIZE(xelpd_port_mapping), + ARRAY_SIZE(xelpd_port_mapping[0]), + xelpd_port_mapping, + dvo_port); + else if (display->platform.alderlake_s) + return __dvo_port_to_port(ARRAY_SIZE(adls_port_mapping), + ARRAY_SIZE(adls_port_mapping[0]), + adls_port_mapping, + dvo_port); + else if (display->platform.dg1 || display->platform.rocketlake) + 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 intel_display *display, u8 dvo_port) +{ + switch (dvo_port) { + case DVO_PORT_MIPIA: + return PORT_A; + case DVO_PORT_MIPIC: + if (DISPLAY_VER(display) >= 11) + return PORT_B; + else + return PORT_C; + default: + return PORT_NONE; + } +} + +enum port intel_bios_encoder_port(const struct intel_bios_encoder_data *devdata) +{ + struct intel_display *display = devdata->display; + const struct child_device_config *child = &devdata->child; + enum port port; + + port = dvo_port_to_port(display, child->dvo_port); + if (port == PORT_NONE && DISPLAY_VER(display) >= 11) + port = dsi_dvo_port_to_port(display, child->dvo_port); + + return port; +} + +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 u32 edp_rate_override_mask(int rate) +{ + switch (rate) { + case 2000000: return BDB_263_VBT_EDP_LINK_RATE_20; + case 1350000: return BDB_263_VBT_EDP_LINK_RATE_13_5; + case 1000000: return BDB_263_VBT_EDP_LINK_RATE_10; + case 810000: return BDB_263_VBT_EDP_LINK_RATE_8_1; + case 675000: return BDB_263_VBT_EDP_LINK_RATE_6_75; + case 540000: return BDB_263_VBT_EDP_LINK_RATE_5_4; + case 432000: return BDB_263_VBT_EDP_LINK_RATE_4_32; + case 324000: return BDB_263_VBT_EDP_LINK_RATE_3_24; + case 270000: return BDB_263_VBT_EDP_LINK_RATE_2_7; + case 243000: return BDB_263_VBT_EDP_LINK_RATE_2_43; + case 216000: return BDB_263_VBT_EDP_LINK_RATE_2_16; + case 162000: return BDB_263_VBT_EDP_LINK_RATE_1_62; + default: return 0; + } +} + +int intel_bios_dp_max_link_rate(const struct intel_bios_encoder_data *devdata) +{ + if (!devdata || devdata->display->vbt.version < 216) + return 0; + + if (devdata->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); +} + +int intel_bios_dp_max_lane_count(const struct intel_bios_encoder_data *devdata) +{ + if (!devdata || devdata->display->vbt.version < 244) + return 0; + + return devdata->child.dp_max_lane_count + 1; +} + +bool +intel_bios_encoder_reject_edp_rate(const struct intel_bios_encoder_data *devdata, + int rate) +{ + if (!devdata || devdata->display->vbt.version < 263) + return false; + + if (devdata->child.edp_data_rate_override == BDB_263_VBT_EDP_RATES_MASK) + return false; + + return devdata->child.edp_data_rate_override & edp_rate_override_mask(rate); +} + +static void sanitize_dedicated_external(struct intel_bios_encoder_data *devdata, + enum port port) +{ + struct intel_display *display = devdata->display; + + if (!intel_bios_encoder_is_dedicated_external(devdata)) + return; + + /* + * Since dedicated_external is for ports connected to PHYs outside of + * the Type-C subsystem, clear bits that would only make sense for ports + * with PHYs in the Type-C subsystem. + */ + + /* + * Bit dp_usb_type_c is marked as "don't care" in Bspec when + * dedicated_external is set. + */ + if (devdata->child.dp_usb_type_c) { + drm_dbg_kms(display->drm, + "VBT claims Port %c supports USB Type-C, but the port is dedicated external, ignoring\n", + port_name(port)); + devdata->child.dp_usb_type_c = 0; + } + + /* + * Bit tbt is marked as "don't care" in Bspec when dedicated_external is + * set. + */ + if (devdata->child.tbt) { + drm_dbg_kms(display->drm, + "VBT claims Port %c supports TBT, but the port is dedicated external, ignoring\n", + port_name(port)); + devdata->child.tbt = 0; + } + + /* + * DDI allocation for TC capable ports only make sense for PHYs in the + * Type-C subsystem. + */ + if (devdata->child.dyn_port_over_tc) { + drm_dbg_kms(display->drm, + "VBT claims Port %c supports dynamic DDI allocation in TCSS, but the port is dedicated external, ignoring\n", + port_name(port)); + devdata->child.dyn_port_over_tc = 0; + } +} + +static void sanitize_device_type(struct intel_bios_encoder_data *devdata, + enum port port) +{ + struct intel_display *display = devdata->display; + bool is_hdmi; + + if (port != PORT_A || DISPLAY_VER(display) >= 12) + return; + + if (!intel_bios_encoder_supports_dvi(devdata)) + return; + + is_hdmi = intel_bios_encoder_supports_hdmi(devdata); + + drm_dbg_kms(display->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 void sanitize_hdmi_level_shift(struct intel_bios_encoder_data *devdata, + enum port port) +{ + struct intel_display *display = devdata->display; + + if (!intel_bios_encoder_supports_dvi(devdata)) + return; + + /* + * Some BDW machines (eg. HP Pavilion 15-ab) shipped + * with a HSW VBT where the level shifter value goes + * up to 11, whereas the BDW max is 9. + */ + if (display->platform.broadwell && devdata->child.hdmi_level_shifter_value > 9) { + drm_dbg_kms(display->drm, + "Bogus port %c VBT HDMI level shift %d, adjusting to %d\n", + port_name(port), devdata->child.hdmi_level_shifter_value, 9); + + devdata->child.hdmi_level_shifter_value = 9; + } +} + +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; +} + +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; +} + +bool +intel_bios_encoder_supports_dsi(const struct intel_bios_encoder_data *devdata) +{ + return devdata->child.device_type & DEVICE_TYPE_MIPI_OUTPUT; +} + +bool +intel_bios_encoder_is_lspcon(const struct intel_bios_encoder_data *devdata) +{ + return devdata && HAS_LSPCON(devdata->display) && devdata->child.lspcon; +} + +/* This is an index in the HDMI/DVI DDI buffer translation table, or -1 */ +int intel_bios_hdmi_level_shift(const struct intel_bios_encoder_data *devdata) +{ + if (!devdata || devdata->display->vbt.version < 158 || + DISPLAY_VER(devdata->display) >= 14) + return -1; + + return devdata->child.hdmi_level_shifter_value; +} + +int intel_bios_hdmi_max_tmds_clock(const struct intel_bios_encoder_data *devdata) +{ + if (!devdata || devdata->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 intel_display *display, 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 && display->platform.icelake) + return display->platform.icelake_port_f; + + return true; +} + +static void print_ddi_port(const struct intel_bios_encoder_data *devdata) +{ + struct intel_display *display = devdata->display; + 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; + enum port port; + + port = intel_bios_encoder_port(devdata); + if (port == PORT_NONE) + return; + + 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(display->drm, + "Port %c VBT info: CRT:%d DVI:%d HDMI:%d DP:%d eDP:%d DSI:%d DP++:%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, + intel_bios_encoder_supports_dp_dual_mode(devdata), + intel_bios_encoder_is_lspcon(devdata), + supports_typec_usb, supports_tbt, + devdata->dsc != NULL); + + if (intel_bios_encoder_is_dedicated_external(devdata)) + drm_dbg_kms(display->drm, + "Port %c is dedicated external\n", + port_name(port)); + + if (intel_bios_encoder_supports_dyn_port_over_tc(devdata)) + drm_dbg_kms(display->drm, + "Port %c supports dynamic DDI allocation in TCSS\n", + port_name(port)); + + hdmi_level_shift = intel_bios_hdmi_level_shift(devdata); + if (hdmi_level_shift >= 0) { + drm_dbg_kms(display->drm, + "Port %c VBT HDMI level shift: %d\n", + port_name(port), hdmi_level_shift); + } + + max_tmds_clock = intel_bios_hdmi_max_tmds_clock(devdata); + if (max_tmds_clock) + drm_dbg_kms(display->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_dp_boost_level(devdata); + if (dp_boost_level) + drm_dbg_kms(display->drm, + "Port %c VBT (e)DP boost level: %d\n", + port_name(port), dp_boost_level); + + hdmi_boost_level = intel_bios_hdmi_boost_level(devdata); + if (hdmi_boost_level) + drm_dbg_kms(display->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(display->drm, + "Port %c VBT DP max link rate: %d\n", + port_name(port), dp_max_link_rate); + + /* + * FIXME need to implement support for VBT + * vswing/preemph tables should this ever trigger. + */ + drm_WARN(display->drm, child->use_vbt_vswing, + "Port %c asks to use VBT vswing/preemph tables\n", + port_name(port)); +} + +static void parse_ddi_port(struct intel_bios_encoder_data *devdata) +{ + struct intel_display *display = devdata->display; + enum port port; + + port = intel_bios_encoder_port(devdata); + if (port == PORT_NONE) + return; + + if (!is_port_valid(display, port)) { + drm_dbg_kms(display->drm, + "VBT reports port %c as supported, but that can't be true: skipping\n", + port_name(port)); + return; + } + + sanitize_dedicated_external(devdata, port); + sanitize_device_type(devdata, port); + sanitize_hdmi_level_shift(devdata, port); +} + +static bool has_ddi_port_info(struct intel_display *display) +{ + return DISPLAY_VER(display) >= 5 || display->platform.g4x; +} + +static void parse_ddi_ports(struct intel_display *display) +{ + struct intel_bios_encoder_data *devdata; + + if (!has_ddi_port_info(display)) + return; + + list_for_each_entry(devdata, &display->vbt.display_devices, node) + parse_ddi_port(devdata); + + list_for_each_entry(devdata, &display->vbt.display_devices, node) + print_ddi_port(devdata); +} + +static int child_device_expected_size(u16 version) +{ + BUILD_BUG_ON(sizeof(struct child_device_config) < 40); + + if (version > 264) + return -ENOENT; + else if (version >= 263) + return 44; + else if (version >= 256) + return 40; + else if (version >= 216) + return 39; + else if (version >= 196) + return 38; + else if (version >= 195) + return 37; + else if (version >= 111) + return LEGACY_CHILD_DEVICE_CONFIG_SIZE; + else if (version >= 106) + return 27; + else + return 22; +} + +static bool child_device_size_valid(struct intel_display *display, int size) +{ + int expected_size; + + expected_size = child_device_expected_size(display->vbt.version); + if (expected_size < 0) { + expected_size = sizeof(struct child_device_config); + drm_dbg_kms(display->drm, + "Expected child device config size for VBT version %u not known; assuming %d\n", + display->vbt.version, expected_size); + } + + /* Flag an error for unexpected size, but continue anyway. */ + if (size != expected_size) + drm_err(display->drm, + "Unexpected child device config size %d (expected %d for VBT version %u)\n", + size, expected_size, display->vbt.version); + + /* The legacy sized child device config is the minimum we need. */ + if (size < LEGACY_CHILD_DEVICE_CONFIG_SIZE) { + drm_dbg_kms(display->drm, + "Child device config size %d is too small.\n", + size); + return false; + } + + return true; +} + +static void +parse_general_definitions(struct intel_display *display) +{ + const struct bdb_general_definitions *defs; + struct intel_bios_encoder_data *devdata; + const struct child_device_config *child; + int i, child_device_num; + u16 block_size; + int bus_pin; + + defs = bdb_find_section(display, BDB_GENERAL_DEFINITIONS); + if (!defs) { + drm_dbg_kms(display->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(display->drm, + "General definitions block too small (%u)\n", + block_size); + return; + } + + bus_pin = defs->crt_ddc_gmbus_pin; + drm_dbg_kms(display->drm, "crt_ddc_bus_pin: %d\n", bus_pin); + if (intel_gmbus_is_valid_pin(display, bus_pin)) + display->vbt.crt_ddc_pin = bus_pin; + + if (!child_device_size_valid(display, 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(display->drm, + "Found VBT child device with type 0x%x\n", + child->device_type); + + devdata = kzalloc_obj(*devdata); + if (!devdata) + break; + + devdata->display = display; + + /* + * 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, &display->vbt.display_devices); + } + + if (list_empty(&display->vbt.display_devices)) + drm_dbg_kms(display->drm, + "no child dev is parsed from VBT\n"); +} + +/* Common defaults which may be overridden by VBT. */ +static void +init_vbt_defaults(struct intel_display *display) +{ + display->vbt.crt_ddc_pin = GMBUS_PIN_VGADDC; + + /* general features */ + display->vbt.int_tv_support = 1; + display->vbt.int_crt_support = 1; + + /* driver features */ + display->vbt.int_lvds_support = 1; + + /* Default to using SSC */ + display->vbt.lvds_use_ssc = 1; + /* + * Core/SandyBridge/IvyBridge use alternative (120MHz) reference + * clock for LVDS. + */ + display->vbt.lvds_ssc_freq = intel_bios_ssc_frequency(display, + !HAS_PCH_SPLIT(display)); + drm_dbg_kms(display->drm, "Set default to SSC at %d kHz\n", + 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 intel_display *display) +{ + unsigned int ports = DISPLAY_RUNTIME_INFO(display)->port_mask; + enum port port; + + if (!HAS_DDI(display) && !display->platform.cherryview) + 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(display, port); + + /* + * VBT has the TypeC mode (native,TBT/USB) and we don't want + * to detect it. + */ + if (intel_phy_is_tc(display, phy)) + continue; + + /* Create fake child device config */ + devdata = kzalloc_obj(*devdata); + if (!devdata) + break; + + devdata->display = display; + 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, &display->vbt.display_devices); + + drm_dbg_kms(display->drm, + "Generating default VBT child device with type 0x%04x on port %c\n", + child->device_type, port_name(port)); + } + + /* Bypass some minimum baseline VBT version checks */ + 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; +} + +static const char vbt_signature[] = "$VBT"; +static const int vbt_signature_len = 4; + +/** + * intel_bios_is_valid_vbt - does the given buffer contain a valid VBT + * @display: display device + * @buf: pointer to a buffer to validate + * @size: size of the buffer + * + * Returns true on valid VBT. + */ +bool intel_bios_is_valid_vbt(struct intel_display *display, + 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_dbg_kms(display->drm, "VBT header incomplete\n"); + return false; + } + + if (memcmp(vbt->signature, vbt_signature, vbt_signature_len)) { + drm_dbg_kms(display->drm, "VBT invalid signature\n"); + return false; + } + + if (vbt->vbt_size > size) { + drm_dbg_kms(display->drm, + "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_dbg_kms(display->drm, "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_dbg_kms(display->drm, "BDB incomplete\n"); + return false; + } + + return vbt; +} + +static struct vbt_header *firmware_get_vbt(struct intel_display *display, + size_t *size) +{ + struct vbt_header *vbt = NULL; + const struct firmware *fw = NULL; + const char *name = display->params.vbt_firmware; + int ret; + + if (!name || !*name) + return NULL; + + ret = request_firmware(&fw, name, display->drm->dev); + if (ret) { + drm_err(display->drm, + "Requesting VBT firmware \"%s\" failed (%d)\n", + name, ret); + return NULL; + } + + if (intel_bios_is_valid_vbt(display, fw->data, fw->size)) { + vbt = kmemdup(fw->data, fw->size, GFP_KERNEL); + if (vbt) { + drm_dbg_kms(display->drm, + "Found valid VBT firmware \"%s\"\n", name); + if (size) + *size = fw->size; + } + } else { + drm_dbg_kms(display->drm, "Invalid VBT firmware \"%s\"\n", + name); + } + + release_firmware(fw); + + return vbt; +} + +static struct vbt_header *oprom_get_vbt(struct intel_display *display, + struct intel_rom *rom, + size_t *size, const char *type) +{ + struct vbt_header *vbt; + size_t vbt_size; + loff_t offset; + + if (!rom) + return NULL; + + BUILD_BUG_ON(vbt_signature_len != sizeof(vbt_signature) - 1); + BUILD_BUG_ON(vbt_signature_len != sizeof(u32)); + + offset = intel_rom_find(rom, *(const u32 *)vbt_signature); + if (offset < 0) + goto err_free_rom; + + if (sizeof(struct vbt_header) > intel_rom_size(rom) - offset) { + drm_dbg_kms(display->drm, "VBT header incomplete\n"); + goto err_free_rom; + } + + BUILD_BUG_ON(sizeof(vbt->vbt_size) != sizeof(u16)); + + vbt_size = intel_rom_read16(rom, offset + offsetof(struct vbt_header, vbt_size)); + if (vbt_size > intel_rom_size(rom) - offset) { + drm_dbg_kms(display->drm, "VBT incomplete (vbt_size overflows)\n"); + goto err_free_rom; + } + + vbt = kzalloc(round_up(vbt_size, 4), GFP_KERNEL); + if (!vbt) + goto err_free_rom; + + intel_rom_read_block(rom, vbt, offset, vbt_size); + + if (!intel_bios_is_valid_vbt(display, vbt, vbt_size)) + goto err_free_vbt; + + drm_dbg_kms(display->drm, "Found valid VBT in %s\n", type); + + if (size) + *size = vbt_size; + + intel_rom_free(rom); + + return vbt; + +err_free_vbt: + kfree(vbt); +err_free_rom: + intel_rom_free(rom); + return NULL; +} + +static const struct vbt_header *intel_bios_get_vbt(struct intel_display *display, + size_t *sizep) +{ + const struct vbt_header *vbt = NULL; + + vbt = firmware_get_vbt(display, sizep); + + if (!vbt) + vbt = intel_opregion_get_vbt(display, sizep); + + /* + * If the OpRegion does not have VBT, look in SPI flash + * through MMIO or PCI mapping + */ + if (!vbt && display->platform.dgfx) + with_intel_display_rpm(display) + vbt = oprom_get_vbt(display, intel_rom_spi(display->drm), sizep, "SPI flash"); + + if (!vbt) + with_intel_display_rpm(display) + vbt = oprom_get_vbt(display, intel_rom_pci(display->drm), sizep, "PCI ROM"); + + return vbt; +} + +/** + * intel_bios_init - find VBT and initialize settings from the BIOS + * @display: display 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 intel_display *display) +{ + const struct vbt_header *vbt; + const struct bdb_header *bdb; + + INIT_LIST_HEAD(&display->vbt.display_devices); + INIT_LIST_HEAD(&display->vbt.bdb_blocks); + + if (!HAS_DISPLAY(display)) { + drm_dbg_kms(display->drm, + "Skipping VBT init due to disabled display.\n"); + return; + } + + init_vbt_defaults(display); + + vbt = intel_bios_get_vbt(display, NULL); + + if (!vbt) + goto out; + + bdb = get_bdb_header(vbt); + display->vbt.version = bdb->version; + + drm_dbg_kms(display->drm, + "VBT signature \"%.*s\", BDB version %d\n", + (int)sizeof(vbt->signature), vbt->signature, + display->vbt.version); + + init_bdb_blocks(display, bdb); + + /* Grab useful general definitions */ + parse_general_features(display); + parse_general_definitions(display); + parse_driver_features(display); + + /* Depends on child device list */ + parse_compression_parameters(display); + +out: + if (!vbt) { + drm_info(display->drm, + "Failed to find VBIOS tables (VBT)\n"); + init_vbt_missing_defaults(display); + } + + /* Further processing on pre-parsed or generated child device data */ + parse_sdvo_device_mapping(display); + parse_ddi_ports(display); + + kfree(vbt); +} + +static void intel_bios_init_panel(struct intel_display *display, + struct intel_panel *panel, + const struct intel_bios_encoder_data *devdata, + const struct drm_edid *drm_edid, + bool use_fallback) +{ + /* already have it? */ + if (panel->vbt.panel_type >= 0) { + drm_WARN_ON(display->drm, !use_fallback); + return; + } + + panel->vbt.panel_type = get_panel_type(display, devdata, + drm_edid, use_fallback); + if (panel->vbt.panel_type < 0) { + drm_WARN_ON(display->drm, use_fallback); + return; + } + + init_vbt_panel_defaults(panel); + + parse_panel_options(display, panel); + parse_generic_dtd(display, panel); + parse_lfp_data(display, panel); + parse_lfp_backlight(display, panel); + parse_sdvo_lvds_data(display, panel); + parse_panel_driver_features(display, panel); + parse_power_conservation_features(display, panel); + parse_edp(display, panel); + parse_psr(display, panel); + parse_mipi_config(display, panel); + parse_mipi_sequence(display, panel); +} + +void intel_bios_init_panel_early(struct intel_display *display, + struct intel_panel *panel, + const struct intel_bios_encoder_data *devdata) +{ + intel_bios_init_panel(display, panel, devdata, NULL, false); +} + +void intel_bios_init_panel_late(struct intel_display *display, + struct intel_panel *panel, + const struct intel_bios_encoder_data *devdata, + const struct drm_edid *drm_edid) +{ + intel_bios_init_panel(display, panel, devdata, drm_edid, true); +} + +/** + * intel_bios_driver_remove - Free any resources allocated by intel_bios_init() + * @display: display device instance + */ +void intel_bios_driver_remove(struct intel_display *display) +{ + struct intel_bios_encoder_data *devdata, *nd; + struct bdb_block_entry *entry, *ne; + + list_for_each_entry_safe(devdata, nd, &display->vbt.display_devices, + node) { + list_del(&devdata->node); + kfree(devdata->dsc); + kfree(devdata); + } + + list_for_each_entry_safe(entry, ne, &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_vbt_mode); + panel->vbt.lfp_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 + * @display: display 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 intel_display *display) +{ + const struct intel_bios_encoder_data *devdata; + + if (!display->vbt.int_tv_support) + return false; + + if (list_empty(&display->vbt.display_devices)) + return true; + + list_for_each_entry(devdata, &display->vbt.display_devices, node) { + const struct child_device_config *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 + * @display: display 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 intel_display *display, u8 *i2c_pin) +{ + const struct intel_bios_encoder_data *devdata; + + if (list_empty(&display->vbt.display_devices)) + return true; + + list_for_each_entry(devdata, &display->vbt.display_devices, node) { + const struct child_device_config *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(display, 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. + */ + return intel_opregion_vbt_present(display); + } + + return false; +} + +/** + * intel_bios_is_port_present - is the specified digital port present + * @display: display device instance + * @port: port to check + * + * Return true if the device in %port is present. + */ +bool intel_bios_is_port_present(struct intel_display *display, enum port port) +{ + const struct intel_bios_encoder_data *devdata; + + if (WARN_ON(!has_ddi_port_info(display))) + return true; + + if (!is_port_valid(display, port)) + return false; + + list_for_each_entry(devdata, &display->vbt.display_devices, node) { + const struct child_device_config *child = &devdata->child; + + if (dvo_port_to_port(display, child->dvo_port) == port) + return true; + } + + return false; +} + +bool intel_bios_encoder_supports_dp_dual_mode(const struct intel_bios_encoder_data *devdata) +{ + const struct child_device_config *child = &devdata->child; + + if (!devdata) + return false; + + 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; +} + +/** + * intel_bios_is_dsi_present - is DSI present in VBT + * @display: display 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 intel_display *display, + enum port *port) +{ + const struct intel_bios_encoder_data *devdata; + + list_for_each_entry(devdata, &display->vbt.display_devices, node) { + const struct child_device_config *child = &devdata->child; + u8 dvo_port = child->dvo_port; + + if (!(child->device_type & DEVICE_TYPE_MIPI_OUTPUT)) + continue; + + if (dsi_dvo_port_to_port(display, dvo_port) == PORT_NONE) { + drm_dbg_kms(display->drm, + "VBT has unsupported DSI port %c\n", + port_name(dvo_port - DVO_PORT_MIPIA)); + continue; + } + + if (port) + *port = dsi_dvo_port_to_port(display, dvo_port); + return true; + } + + return false; +} + +static bool fill_dsc(struct intel_crtc_state *crtc_state, + struct dsc_compression_parameters_entry *dsc, + int dsc_max_bpc) +{ + struct intel_display *display = to_intel_display(crtc_state); + struct drm_dsc_config *vdsc_cfg = &crtc_state->dsc.config; + int slices_per_line; + 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_dbg_kms(display->drm, "VBT: Unsupported BPC %d for DCS\n", + dsc_max_bpc); + + crtc_state->pipe_bpp = bpc * 3; + + crtc_state->dsc.compressed_bpp_x16 = fxp_q4_from_int(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. + * + * FIXME: split only when necessary + */ + if (dsc->slices_per_line & BIT(2)) { + slices_per_line = 4; + } else if (dsc->slices_per_line & BIT(1)) { + slices_per_line = 2; + } else { + /* FIXME */ + if (!(dsc->slices_per_line & BIT(0))) + drm_dbg_kms(display->drm, + "VBT: Unsupported DSC slice count for DSI\n"); + + slices_per_line = 1; + } + + if (drm_WARN_ON(display->drm, + !intel_dsc_get_slice_config(display, 1, slices_per_line, + &crtc_state->dsc.slice_config))) + return false; + + if (crtc_state->hw.adjusted_mode.crtc_hdisplay % + intel_dsc_line_slice_count(&crtc_state->dsc.slice_config) != 0) + drm_dbg_kms(display->drm, + "VBT: DSC hdisplay %d not divisible by slice count %d\n", + crtc_state->hw.adjusted_mode.crtc_hdisplay, + intel_dsc_line_slice_count(&crtc_state->dsc.slice_config)); + + /* + * 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; + + return true; +} + +/* 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 intel_display *display = to_intel_display(encoder); + const struct intel_bios_encoder_data *devdata; + + list_for_each_entry(devdata, &display->vbt.display_devices, node) { + const struct child_device_config *child = &devdata->child; + + if (!(child->device_type & DEVICE_TYPE_MIPI_OUTPUT)) + continue; + + if (dsi_dvo_port_to_port(display, child->dvo_port) == encoder->port) { + if (!devdata->dsc) + return false; + + return fill_dsc(crtc_state, devdata->dsc, dsc_max_bpc); + } + } + + return false; +} + +static const u8 adlp_aux_ch_map[] = { + [AUX_CH_A] = DP_AUX_A, + [AUX_CH_B] = DP_AUX_B, + [AUX_CH_C] = DP_AUX_C, + [AUX_CH_D_XELPD] = DP_AUX_D, + [AUX_CH_E_XELPD] = DP_AUX_E, + [AUX_CH_USBC1] = DP_AUX_F, + [AUX_CH_USBC2] = DP_AUX_G, + [AUX_CH_USBC3] = DP_AUX_H, + [AUX_CH_USBC4] = DP_AUX_I, +}; + +/* + * ADL-S VBT uses PHY based mapping. Combo PHYs A,B,C,D,E + * map to DDI A,TC1,TC2,TC3,TC4 respectively. + */ +static const u8 adls_aux_ch_map[] = { + [AUX_CH_A] = DP_AUX_A, + [AUX_CH_USBC1] = DP_AUX_B, + [AUX_CH_USBC2] = DP_AUX_C, + [AUX_CH_USBC3] = DP_AUX_D, + [AUX_CH_USBC4] = DP_AUX_E, +}; + +/* + * RKL/DG1 VBT uses PHY based mapping. Combo PHYs A,B,C,D + * map to DDI A,B,TC1,TC2 respectively. + */ +static const u8 rkl_aux_ch_map[] = { + [AUX_CH_A] = DP_AUX_A, + [AUX_CH_B] = DP_AUX_B, + [AUX_CH_USBC1] = DP_AUX_C, + [AUX_CH_USBC2] = DP_AUX_D, +}; + +static const u8 direct_aux_ch_map[] = { + [AUX_CH_A] = DP_AUX_A, + [AUX_CH_B] = DP_AUX_B, + [AUX_CH_C] = DP_AUX_C, + [AUX_CH_D] = DP_AUX_D, /* aka AUX_CH_USBC1 */ + [AUX_CH_E] = DP_AUX_E, /* aka AUX_CH_USBC2 */ + [AUX_CH_F] = DP_AUX_F, /* aka AUX_CH_USBC3 */ + [AUX_CH_G] = DP_AUX_G, /* aka AUX_CH_USBC4 */ + [AUX_CH_H] = DP_AUX_H, /* aka AUX_CH_USBC5 */ + [AUX_CH_I] = DP_AUX_I, /* aka AUX_CH_USBC6 */ +}; + +static enum aux_ch map_aux_ch(struct intel_display *display, u8 aux_channel) +{ + const u8 *aux_ch_map; + int i, n_entries; + + if (DISPLAY_VER(display) >= 13) { + aux_ch_map = adlp_aux_ch_map; + n_entries = ARRAY_SIZE(adlp_aux_ch_map); + } else if (display->platform.alderlake_s) { + aux_ch_map = adls_aux_ch_map; + n_entries = ARRAY_SIZE(adls_aux_ch_map); + } else if (display->platform.dg1 || display->platform.rocketlake) { + aux_ch_map = rkl_aux_ch_map; + n_entries = ARRAY_SIZE(rkl_aux_ch_map); + } else { + aux_ch_map = direct_aux_ch_map; + n_entries = ARRAY_SIZE(direct_aux_ch_map); + } + + for (i = 0; i < n_entries; i++) { + if (aux_ch_map[i] == aux_channel) + return i; + } + + drm_dbg_kms(display->drm, + "Ignoring alternate AUX CH: VBT claims AUX 0x%x, which is not valid for this platform\n", + aux_channel); + + return AUX_CH_NONE; +} + +enum aux_ch intel_bios_dp_aux_ch(const struct intel_bios_encoder_data *devdata) +{ + if (!devdata || !devdata->child.aux_channel) + return AUX_CH_NONE; + + return map_aux_ch(devdata->display, devdata->child.aux_channel); +} + +bool intel_bios_dp_has_shared_aux_ch(const struct intel_bios_encoder_data *devdata) +{ + struct intel_display *display; + u8 aux_channel; + int count = 0; + + if (!devdata || !devdata->child.aux_channel) + return false; + + display = devdata->display; + aux_channel = devdata->child.aux_channel; + + list_for_each_entry(devdata, &display->vbt.display_devices, node) { + if (intel_bios_encoder_supports_dp(devdata) && + aux_channel == devdata->child.aux_channel) + count++; + } + + return count > 1; +} + +int intel_bios_dp_boost_level(const struct intel_bios_encoder_data *devdata) +{ + if (!devdata || devdata->display->vbt.version < 196 || !devdata->child.iboost) + return 0; + + return translate_iboost(devdata->display, devdata->child.dp_iboost_level); +} + +int intel_bios_hdmi_boost_level(const struct intel_bios_encoder_data *devdata) +{ + if (!devdata || devdata->display->vbt.version < 196 || !devdata->child.iboost) + return 0; + + return translate_iboost(devdata->display, devdata->child.hdmi_iboost_level); +} + +int intel_bios_hdmi_ddc_pin(const struct intel_bios_encoder_data *devdata) +{ + if (!devdata || !devdata->child.ddc_pin) + return 0; + + return map_ddc_pin(devdata->display, devdata->child.ddc_pin); +} + +bool intel_bios_encoder_supports_typec_usb(const struct intel_bios_encoder_data *devdata) +{ + return devdata->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->display->vbt.version >= 209 && devdata->child.tbt; +} + +bool intel_bios_encoder_is_dedicated_external(const struct intel_bios_encoder_data *devdata) +{ + return devdata->display->vbt.version >= 264 && + devdata->child.dedicated_external; +} + +bool intel_bios_encoder_supports_dyn_port_over_tc(const struct intel_bios_encoder_data *devdata) +{ + return devdata->display->vbt.version >= 264 && + devdata->child.dyn_port_over_tc; +} + +bool intel_bios_encoder_lane_reversal(const struct intel_bios_encoder_data *devdata) +{ + return devdata && devdata->child.lane_reversal; +} + +bool intel_bios_encoder_hpd_invert(const struct intel_bios_encoder_data *devdata) +{ + return devdata && devdata->child.hpd_invert; +} + +const struct intel_bios_encoder_data * +intel_bios_encoder_data_lookup(struct intel_display *display, enum port port) +{ + struct intel_bios_encoder_data *devdata; + + list_for_each_entry(devdata, &display->vbt.display_devices, node) { + if (intel_bios_encoder_port(devdata) == port) + return devdata; + } + + return NULL; +} + +void intel_bios_for_each_encoder(struct intel_display *display, + void (*func)(struct intel_display *display, + const struct intel_bios_encoder_data *devdata)) +{ + struct intel_bios_encoder_data *devdata; + + list_for_each_entry(devdata, &display->vbt.display_devices, node) + func(display, devdata); +} + +static int intel_bios_vbt_show(struct seq_file *m, void *unused) +{ + struct intel_display *display = m->private; + const void *vbt; + size_t vbt_size; + + vbt = intel_bios_get_vbt(display, &vbt_size); + + if (vbt) { + seq_write(m, vbt, vbt_size); + kfree(vbt); + } + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(intel_bios_vbt); + +void intel_bios_debugfs_register(struct intel_display *display) +{ + debugfs_create_file("i915_vbt", 0444, display->drm->debugfs_root, + display, &intel_bios_vbt_fops); +} 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..75dff27b4 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_bios.h @@ -0,0 +1,108 @@ +/* + * 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 intel_display. + */ + +#ifndef _INTEL_BIOS_H_ +#define _INTEL_BIOS_H_ + +#include + +struct drm_edid; +struct intel_bios_encoder_data; +struct intel_crtc_state; +struct intel_display; +struct intel_encoder; +struct intel_panel; +enum aux_ch; +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, +}; + +void intel_bios_init(struct intel_display *display); +void intel_bios_init_panel_early(struct intel_display *display, + struct intel_panel *panel, + const struct intel_bios_encoder_data *devdata); +void intel_bios_init_panel_late(struct intel_display *display, + struct intel_panel *panel, + const struct intel_bios_encoder_data *devdata, + const struct drm_edid *drm_edid); +void intel_bios_fini_panel(struct intel_panel *panel); +void intel_bios_driver_remove(struct intel_display *display); +bool intel_bios_is_valid_vbt(struct intel_display *display, + const void *buf, size_t size); +bool intel_bios_is_tv_present(struct intel_display *display); +bool intel_bios_is_lvds_present(struct intel_display *display, u8 *i2c_pin); +bool intel_bios_is_port_present(struct intel_display *display, enum port port); +bool intel_bios_is_dsi_present(struct intel_display *display, enum port *port); +bool intel_bios_get_dsc_params(struct intel_encoder *encoder, + struct intel_crtc_state *crtc_state, + int dsc_max_bpc); + +const struct intel_bios_encoder_data * +intel_bios_encoder_data_lookup(struct intel_display *display, 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_edp(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); +bool intel_bios_encoder_is_dedicated_external(const struct intel_bios_encoder_data *devdata); +bool intel_bios_encoder_supports_dyn_port_over_tc(const struct intel_bios_encoder_data *devdata); +bool intel_bios_encoder_supports_dsi(const struct intel_bios_encoder_data *devdata); +bool intel_bios_encoder_supports_dp_dual_mode(const struct intel_bios_encoder_data *devdata); +bool intel_bios_encoder_is_lspcon(const struct intel_bios_encoder_data *devdata); +bool intel_bios_encoder_lane_reversal(const struct intel_bios_encoder_data *devdata); +bool intel_bios_encoder_hpd_invert(const struct intel_bios_encoder_data *devdata); +enum port intel_bios_encoder_port(const struct intel_bios_encoder_data *devdata); +bool intel_bios_encoder_reject_edp_rate(const struct intel_bios_encoder_data *devdata, + int rate); +enum aux_ch intel_bios_dp_aux_ch(const struct intel_bios_encoder_data *devdata); +int intel_bios_dp_boost_level(const struct intel_bios_encoder_data *devdata); +int intel_bios_dp_max_lane_count(const struct intel_bios_encoder_data *devdata); +int intel_bios_dp_max_link_rate(const struct intel_bios_encoder_data *devdata); +bool intel_bios_dp_has_shared_aux_ch(const struct intel_bios_encoder_data *devdata); +int intel_bios_hdmi_boost_level(const struct intel_bios_encoder_data *devdata); +int intel_bios_hdmi_ddc_pin(const struct intel_bios_encoder_data *devdata); +int intel_bios_hdmi_level_shift(const struct intel_bios_encoder_data *devdata); +int intel_bios_hdmi_max_tmds_clock(const struct intel_bios_encoder_data *devdata); + +void intel_bios_for_each_encoder(struct intel_display *display, + void (*func)(struct intel_display *display, + const struct intel_bios_encoder_data *devdata)); + +void intel_bios_debugfs_register(struct intel_display *display); + +#endif /* _INTEL_BIOS_H_ */ diff --git a/drivers/gpu/drm/i915/display/intel_bo.c b/drivers/gpu/drm/i915/display/intel_bo.c new file mode 100644 index 000000000..8ecdbb7e3 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_bo.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +/* Copyright © 2026 Intel Corporation */ + +#include +#include + +#include "intel_bo.h" +#include "intel_display_core.h" +#include "intel_display_types.h" + +bool intel_bo_is_tiled(struct drm_gem_object *obj) +{ + struct intel_display *display = to_intel_display(obj->dev); + + return display->parent->bo->is_tiled && display->parent->bo->is_tiled(obj); +} + +bool intel_bo_is_userptr(struct drm_gem_object *obj) +{ + struct intel_display *display = to_intel_display(obj->dev); + + return display->parent->bo->is_userptr && display->parent->bo->is_userptr(obj); +} + +bool intel_bo_is_shmem(struct drm_gem_object *obj) +{ + struct intel_display *display = to_intel_display(obj->dev); + + return display->parent->bo->is_shmem && display->parent->bo->is_shmem(obj); +} + +bool intel_bo_is_protected(struct drm_gem_object *obj) +{ + struct intel_display *display = to_intel_display(obj->dev); + + return display->parent->bo->is_protected(obj); +} + +int intel_bo_key_check(struct drm_gem_object *obj) +{ + struct intel_display *display = to_intel_display(obj->dev); + + return display->parent->bo->key_check(obj); +} + +int intel_bo_fb_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma) +{ + struct intel_display *display = to_intel_display(obj->dev); + + return display->parent->bo->fb_mmap(obj, vma); +} + +int intel_bo_read_from_page(struct drm_gem_object *obj, u64 offset, void *dst, int size) +{ + struct intel_display *display = to_intel_display(obj->dev); + + return display->parent->bo->read_from_page(obj, offset, dst, size); +} + +void intel_bo_describe(struct seq_file *m, struct drm_gem_object *obj) +{ + struct intel_display *display = to_intel_display(obj->dev); + + if (display->parent->bo->describe) + display->parent->bo->describe(m, obj); +} + +int intel_bo_framebuffer_init(struct drm_gem_object *obj, struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct intel_display *display = to_intel_display(obj->dev); + + return display->parent->bo->framebuffer_init(obj, mode_cmd); +} + +void intel_bo_framebuffer_fini(struct drm_gem_object *obj) +{ + struct intel_display *display = to_intel_display(obj->dev); + + display->parent->bo->framebuffer_fini(obj); +} + +struct drm_gem_object *intel_bo_framebuffer_lookup(struct intel_display *display, + struct drm_file *filp, + const struct drm_mode_fb_cmd2 *user_mode_cmd) +{ + return display->parent->bo->framebuffer_lookup(display->drm, filp, user_mode_cmd); +} + +#if IS_ENABLED(CONFIG_DRM_FBDEV_EMULATION) +u32 intel_bo_fbdev_pitch_align(struct intel_display *display, u32 stride) +{ + return display->parent->bo->fbdev_pitch_align(stride); +} + +struct drm_gem_object *intel_bo_fbdev_create(struct intel_display *display, int size) +{ + return display->parent->bo->fbdev_create(display->drm, size); +} + +void intel_bo_fbdev_destroy(struct drm_gem_object *obj) +{ + struct intel_display *display = to_intel_display(obj->dev); + + display->parent->bo->fbdev_destroy(obj); +} + +int intel_bo_fbdev_fill_info(struct drm_gem_object *obj, struct fb_info *info, + struct i915_vma *vma) +{ + struct intel_display *display = to_intel_display(obj->dev); + + return display->parent->bo->fbdev_fill_info(obj, info, vma); +} +#endif diff --git a/drivers/gpu/drm/i915/display/intel_bo.h b/drivers/gpu/drm/i915/display/intel_bo.h new file mode 100644 index 000000000..348f7fa66 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_bo.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2024 Intel Corporation */ + +#ifndef __INTEL_BO__ +#define __INTEL_BO__ + +#include + +struct drm_file; +struct drm_gem_object; +struct drm_mode_fb_cmd2; +struct drm_scanout_buffer; +struct fb_info; +struct i915_vma; +struct intel_display; +struct intel_framebuffer; +struct seq_file; +struct vm_area_struct; + +bool intel_bo_is_tiled(struct drm_gem_object *obj); +bool intel_bo_is_userptr(struct drm_gem_object *obj); +bool intel_bo_is_shmem(struct drm_gem_object *obj); +bool intel_bo_is_protected(struct drm_gem_object *obj); +int intel_bo_key_check(struct drm_gem_object *obj); +int intel_bo_fb_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma); +int intel_bo_read_from_page(struct drm_gem_object *obj, u64 offset, void *dst, int size); + +void intel_bo_describe(struct seq_file *m, struct drm_gem_object *obj); + +void intel_bo_framebuffer_fini(struct drm_gem_object *obj); +int intel_bo_framebuffer_init(struct drm_gem_object *obj, struct drm_mode_fb_cmd2 *mode_cmd); +struct drm_gem_object *intel_bo_framebuffer_lookup(struct intel_display *display, + struct drm_file *filp, + const struct drm_mode_fb_cmd2 *user_mode_cmd); + +u32 intel_bo_fbdev_pitch_align(struct intel_display *display, u32 stride); +struct drm_gem_object *intel_bo_fbdev_create(struct intel_display *display, int size); +void intel_bo_fbdev_destroy(struct drm_gem_object *obj); +int intel_bo_fbdev_fill_info(struct drm_gem_object *obj, struct fb_info *info, + struct i915_vma *vma); + +#endif /* __INTEL_BO__ */ 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..dc5a5b639 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_bw.c @@ -0,0 +1,1520 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2019 Intel Corporation + */ + +#include +#include +#include + +#include "intel_bw.h" +#include "intel_crtc.h" +#include "intel_de.h" +#include "intel_display_core.h" +#include "intel_display_regs.h" +#include "intel_display_types.h" +#include "intel_display_utils.h" +#include "intel_dram.h" +#include "intel_mchbar.h" +#include "intel_parent.h" +#include "skl_watermark.h" + +struct intel_bw_state { + struct intel_global_state base; + + /* + * Contains a bit mask, used to determine, whether correspondent + * pipe allows SAGV or not. + */ + u8 pipe_sagv_reject; + + /* + * From MTL onwards, to lock a QGV point, punit expects the peak BW of + * the selected QGV point as the parameter in multiples of 100MB/s + */ + u16 qgv_point_peakbw; + + /* + * Current QGV points mask, which restricts + * some particular SAGV states, not to confuse + * with pipe_sagv_mask. + */ + u16 qgv_points_mask; + + unsigned int data_rate[I915_MAX_PIPES]; + u8 num_active_planes[I915_MAX_PIPES]; +}; + +/* Parameters for Qclk Geyserville (QGV) */ +struct intel_qgv_point { + u16 dclk, t_rp, t_rdpre, t_rc, t_ras, t_rcd; +}; + +#define DEPROGBWPCLIMIT 60 + +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 dclk_freq_mhz(int ratio) +{ + /* multiple of 16.666 MHz (100/6) */ + return DIV_ROUND_CLOSEST(ratio * 100, 6); +} + +static int dg1_mchbar_read_qgv_point_info(struct intel_display *display, + struct intel_qgv_point *sp, + int point) +{ + u32 dclk_ratio; + u32 val; + + val = intel_mchbar_read(display, SA_PERF_STATUS_0_0_0_MCHBAR_PC); + dclk_ratio = REG_FIELD_GET(DG1_QCLK_RATIO_MASK, val); + if (val & DG1_QCLK_REFERENCE) + dclk_ratio *= 6; /* 6 * 16.666 MHz = 100 MHz */ + else + dclk_ratio *= 8; /* 8 * 16.666 MHz = 133 MHz */ + + val = intel_mchbar_read(display, SKL_MC_BIOS_DATA_0_0_0_MCHBAR_PCU); + if (val & DG1_GEAR_TYPE) + dclk_ratio *= 2; + + sp->dclk = dclk_freq_mhz(dclk_ratio); + if (sp->dclk == 0) + return -EINVAL; + + val = intel_mchbar_read(display, 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_mchbar_read(display, 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 intel_display *display, + struct intel_qgv_point *sp, + int point) +{ + u32 val = 0, val2 = 0; + int ret; + + ret = intel_parent_pcode_read(display, ICL_PCODE_MEM_SUBSYSYSTEM_INFO | + ICL_PCODE_MEM_SS_READ_QGV_POINT_INFO(point), + &val, &val2); + if (ret) + return ret; + + sp->dclk = dclk_freq_mhz(val & 0xffff); + 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 intel_display *display, + struct intel_psf_gv_point *points) +{ + u32 val = 0; + int ret; + int i; + + ret = intel_parent_pcode_read(display, 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; +} + +static u16 icl_qgv_points_mask(struct intel_display *display) +{ + unsigned int num_psf_gv_points = display->bw.max[0].num_psf_gv_points; + unsigned int num_qgv_points = 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 bool is_sagv_enabled(struct intel_display *display, u16 points_mask) +{ + return !is_power_of_2(~points_mask & icl_qgv_points_mask(display) & + ICL_PCODE_REQ_QGV_PT_MASK); +} + +static int icl_pcode_restrict_qgv_points(struct intel_display *display, + u32 points_mask) +{ + int ret; + + if (DISPLAY_VER(display) >= 14) + return 0; + + /* bspec says to keep retrying for at least 1 ms */ + ret = intel_parent_pcode_request(display, 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(display->drm, + "Failed to disable qgv points (0x%x) points: 0x%x\n", + ret, points_mask); + return ret; + } + + display->sagv.status = is_sagv_enabled(display, points_mask) ? + I915_SAGV_ENABLED : I915_SAGV_DISABLED; + + return 0; +} + +static int mtl_read_qgv_point_info(struct intel_display *display, + struct intel_qgv_point *sp, int point) +{ + u32 val, val2; + + val = intel_de_read(display, MTL_MEM_SS_INFO_QGV_POINT_LOW(point)); + val2 = intel_de_read(display, MTL_MEM_SS_INFO_QGV_POINT_HIGH(point)); + + sp->dclk = dclk_freq_mhz(REG_FIELD_GET(MTL_DCLK_MASK, val)); + 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 intel_display *display, + struct intel_qgv_point *sp, + int point) +{ + if (DISPLAY_VER(display) >= 14) + return mtl_read_qgv_point_info(display, sp, point); + else if (display->platform.dg1) + return dg1_mchbar_read_qgv_point_info(display, sp, point); + else + return icl_pcode_read_qgv_point_info(display, sp, point); +} + +static bool is_y_tile(struct intel_display *display) +{ + /* assume Y tile may be used if supported */ + return !HAS_4TILE(display); +} + +static int icl_get_qgv_points(struct intel_display *display, + const struct dram_info *dram_info, + struct intel_qgv_info *qi) +{ + int i, ret; + + qi->num_points = dram_info->num_qgv_points; + qi->num_psf_points = dram_info->num_psf_gv_points; + + if (DISPLAY_VER(display) >= 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; + case INTEL_DRAM_GDDR: + case INTEL_DRAM_GDDR_ECC: + qi->channel_width = 32; + break; + default: + MISSING_CASE(dram_info->type); + return -EINVAL; + } + } else if (DISPLAY_VER(display) >= 12) { + switch (dram_info->type) { + case INTEL_DRAM_DDR4: + qi->t_bl = is_y_tile(display) ? 8 : 4; + qi->max_numchannels = 2; + qi->channel_width = 64; + qi->deinterleave = is_y_tile(display) ? 1 : 2; + break; + case INTEL_DRAM_DDR5: + qi->t_bl = is_y_tile(display) ? 16 : 8; + qi->max_numchannels = 4; + qi->channel_width = 32; + qi->deinterleave = is_y_tile(display) ? 1 : 2; + break; + case INTEL_DRAM_LPDDR4: + if (display->platform.rocketlake) { + 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(display) ? 2 : 4; + break; + default: + qi->t_bl = 16; + qi->max_numchannels = 1; + break; + } + } else if (DISPLAY_VER(display) == 11) { + qi->t_bl = dram_info->type == INTEL_DRAM_DDR4 ? 4 : 8; + qi->max_numchannels = 1; + } + + if (drm_WARN_ON(display->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(display, sp, i); + if (ret) { + drm_dbg_kms(display->drm, "Could not read QGV %d info\n", i); + return ret; + } + + drm_dbg_kms(display->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(display, qi->psf_points); + if (ret) { + drm_err(display->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(display->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; +} + +/* + * Bandwidth parameters that are tied to the SoC (as opposed to struct + * intel_display_bw_params). + */ +struct intel_soc_bw_params { + u8 deprogbwlimit; + u8 derating; +}; + +static const struct intel_soc_bw_params icl_bw_params = { + .deprogbwlimit = 25, + .derating = 10, +}; + +static const struct intel_soc_bw_params tgl_bw_params = { + .deprogbwlimit = 34, + .derating = 10, +}; + +static const struct intel_soc_bw_params rkl_bw_params = { + .deprogbwlimit = 20, + .derating = 10, +}; + +static const struct intel_soc_bw_params adl_s_bw_params = { + .deprogbwlimit = 38, + .derating = 10, +}; + +static const struct intel_soc_bw_params adl_p_bw_params = { + .deprogbwlimit = 38, + .derating = 20, +}; + +static const struct intel_soc_bw_params bmg_bw_params = { + .deprogbwlimit = 53, + .derating = 30, +}; + +static const struct intel_soc_bw_params bmg_ecc_bw_params = { + .deprogbwlimit = 53, + .derating = 45, +}; + +static const struct intel_soc_bw_params ptl_bw_params = { + .deprogbwlimit = 65, + .derating = 10, +}; + +static const struct intel_soc_bw_params wcl_bw_params = { + .deprogbwlimit = 22, + .derating = 10, +}; + +static const struct intel_soc_bw_params *get_soc_bw_params(struct intel_display *display, + const struct dram_info *dram_info) +{ + if (display->platform.icelake || + display->platform.jasperlake || + display->platform.elkhartlake) + return &icl_bw_params; + else if (display->platform.tigerlake || + display->platform.dg1) + return &tgl_bw_params; + else if (display->platform.rocketlake) + return &rkl_bw_params; + else if (display->platform.alderlake_s || + display->platform.meteorlake || + display->platform.lunarlake) + return &adl_s_bw_params; + else if (display->platform.alderlake_p) + return &adl_p_bw_params; + else if (display->platform.battlemage && + dram_info->type == INTEL_DRAM_GDDR_ECC) + return &bmg_ecc_bw_params; + else if (display->platform.battlemage) + return &bmg_bw_params; + else if (display->platform.pantherlake_wildcatlake) + return &wcl_bw_params; + else if (display->platform.pantherlake || + display->platform.novalake) + return &ptl_bw_params; + + return NULL; +} + +/* + * Bandwidth parameters that are tied to the display IP (as opposed to struct + * intel_soc_bw_params). + */ +struct intel_display_bw_params { + u16 displayrtids; + u8 deburst; +}; + +static const struct intel_display_bw_params gen11_bw_params = { + .deburst = 8, + .displayrtids = 128, +}; + +static const struct intel_display_bw_params gen12_bw_params = { + .deburst = 16, + .displayrtids = 256, +}; + +static const struct intel_display_bw_params xelpdp_bw_params = { + .deburst = 32, + .displayrtids = 256, +}; + +static const struct intel_display_bw_params *get_display_bw_params(struct intel_display *display) +{ + if (DISPLAY_VER(display) >= 14) { + return &xelpdp_bw_params; + } else if (DISPLAY_VER(display) >= 12) { + /* + * RKL's SoC was based on ICL and the display, even though being + * gen12, had changes to the memory interface to match gen11's, + * consequently inheriting gen11's display-specific bandwidth + * parameters. + */ + if (display->platform.rocketlake) + return &gen11_bw_params; + else + return &gen12_bw_params; + } else if (DISPLAY_VER(display) == 11) { + return &gen11_bw_params; + } + + return NULL; +} + +static int icl_get_bw_info(struct intel_display *display, + const struct dram_info *dram_info, + const struct intel_soc_bw_params *soc_bw_params, + const struct intel_display_bw_params *display_bw_params) +{ + struct intel_qgv_info qi = {}; + int num_channels = max_t(u8, 1, dram_info->num_channels); + int ipqdepth, ipqdepthpch = 16; + int dclk_max; + int maxdebw; + int num_groups = ARRAY_SIZE(display->bw.max); + int i, ret; + + ret = icl_get_qgv_points(display, dram_info, &qi); + if (ret) { + drm_dbg_kms(display->drm, + "Failed to get memory subsystem information, ignoring bandwidth limits"); + return ret; + } + + dclk_max = icl_sagv_max_dclk(&qi); + maxdebw = min(soc_bw_params->deprogbwlimit * 1000, dclk_max * 16 * 6 / 10); + ipqdepth = min(ipqdepthpch, display_bw_params->displayrtids / num_channels); + qi.deinterleave = DIV_ROUND_UP(num_channels, is_y_tile(display) ? 4 : 2); + + for (i = 0; i < num_groups; i++) { + struct intel_bw_info *bi = &display->bw.max[i]; + int clpchgroup; + int j; + + clpchgroup = (display_bw_params->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 = sp->dclk * clpchgroup * 32 * num_channels / ct; + + bi->deratedbw[j] = min(maxdebw, + bw * (100 - soc_bw_params->derating) / 100); + + drm_dbg_kms(display->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) + display->sagv.status = I915_SAGV_NOT_CONTROLLED; + else + display->sagv.status = I915_SAGV_ENABLED; + + return 0; +} + +static int tgl_peakbw(int num_channels, int channel_width, int dclk) +{ + return num_channels * (channel_width / 8) * dclk; +} + +static int tgl_get_bw_info(struct intel_display *display, + const struct dram_info *dram_info, + const struct intel_soc_bw_params *soc_bw_params, + const struct intel_display_bw_params *display_bw_params) +{ + struct intel_qgv_info qi = {}; + int num_channels = max_t(u8, 1, dram_info->num_channels); + int ipqdepth, ipqdepthpch = 16; + int maxdebw, peakbw; + int clperchgroup; + int num_groups = ARRAY_SIZE(display->bw.max); + int i, ret; + + ret = icl_get_qgv_points(display, dram_info, &qi); + if (ret) { + drm_dbg_kms(display->drm, + "Failed to get memory subsystem information, ignoring bandwidth limits"); + return ret; + } + + if (DISPLAY_VER(display) < 14 && + (dram_info->type == INTEL_DRAM_LPDDR4 || dram_info->type == INTEL_DRAM_LPDDR5)) + num_channels *= 2; + + if (num_channels < qi.max_numchannels && DISPLAY_VER(display) >= 12) + qi.deinterleave = max(qi.deinterleave / 2, 1); + + if (DISPLAY_VER(display) >= 12 && num_channels > qi.max_numchannels) + drm_warn(display->drm, "Number of channels exceeds max number of channels."); + if (qi.max_numchannels != 0) + num_channels = min_t(u8, num_channels, qi.max_numchannels); + + peakbw = tgl_peakbw(num_channels, qi.channel_width, icl_sagv_max_dclk(&qi)); + maxdebw = min(soc_bw_params->deprogbwlimit * 1000, peakbw * DEPROGBWPCLIMIT / 100); + + ipqdepth = min(ipqdepthpch, display_bw_params->displayrtids / num_channels); + /* + * clperchgroup = 4kpagespermempage * clperchperblock, + * clperchperblock = 8 / num_channels * interleave + */ + clperchgroup = 4 * (8 / num_channels) * qi.deinterleave; + + for (i = 0; i < num_groups; i++) { + struct intel_bw_info *bi = &display->bw.max[i]; + struct intel_bw_info *bi_next; + int clpchgroup; + int j; + + clpchgroup = (display_bw_params->deburst * qi.deinterleave / num_channels) << i; + + if (i < num_groups - 1) { + bi_next = &display->bw.max[i + 1]; + + if (clpchgroup < clperchgroup) + bi_next->num_planes = (ipqdepth - clpchgroup) / clpchgroup; + 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 = sp->dclk * clpchgroup * 32 * num_channels / ct; + + bi->deratedbw[j] = min(maxdebw, + bw * (100 - soc_bw_params->derating) / 100); + bi->peakbw[j] = tgl_peakbw(num_channels, qi.channel_width, sp->dclk); + + drm_dbg_kms(display->drm, + "BW%d / QGV %d: num_planes=%d deratedbw=%u peakbw: %u\n", + i, j, bi->num_planes, bi->deratedbw[j], + bi->peakbw[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(display->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) + display->sagv.status = I915_SAGV_NOT_CONTROLLED; + else + display->sagv.status = I915_SAGV_ENABLED; + + return 0; +} + +static void dg2_get_bw_info(struct intel_display *display) +{ + unsigned int deratedbw = display->platform.dg2_g11 ? 38000 : 50000; + int num_groups = ARRAY_SIZE(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 = &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; + } + + display->sagv.status = I915_SAGV_NOT_CONTROLLED; +} + +static int xe2_hpd_get_bw_info(struct intel_display *display, + const struct dram_info *dram_info, + const struct intel_soc_bw_params *soc_bw_params) +{ + struct intel_qgv_info qi = {}; + int num_channels = dram_info->num_channels; + int peakbw, maxdebw; + int ret, i; + + ret = icl_get_qgv_points(display, dram_info, &qi); + if (ret) { + drm_dbg_kms(display->drm, + "Failed to get memory subsystem information, ignoring bandwidth limits"); + return ret; + } + + peakbw = tgl_peakbw(num_channels, qi.channel_width, icl_sagv_max_dclk(&qi)); + maxdebw = min(soc_bw_params->deprogbwlimit * 1000, peakbw * DEPROGBWPCLIMIT / 100); + + for (i = 0; i < qi.num_points; i++) { + const struct intel_qgv_point *sp = &qi.points[i]; + int bw = tgl_peakbw(num_channels, qi.channel_width, sp->dclk); + + display->bw.max[0].deratedbw[i] = + min(maxdebw, (100 - soc_bw_params->derating) * bw / 100); + display->bw.max[0].peakbw[i] = bw; + + drm_dbg_kms(display->drm, "QGV %d: deratedbw=%u peakbw: %u\n", + i, display->bw.max[0].deratedbw[i], + display->bw.max[0].peakbw[i]); + } + + /* Bandwidth does not depend on # of planes; set all groups the same */ + display->bw.max[0].num_planes = 1; + display->bw.max[0].num_qgv_points = qi.num_points; + for (i = 1; i < ARRAY_SIZE(display->bw.max); i++) + memcpy(&display->bw.max[i], &display->bw.max[0], + sizeof(display->bw.max[0])); + + /* + * Xe2_HPD should always have exactly two QGV points representing + * battery and plugged-in operation. + */ + drm_WARN_ON(display->drm, qi.num_points != 2); + display->sagv.status = I915_SAGV_ENABLED; + + return 0; +} + +static unsigned int icl_max_bw_index(struct intel_display *display, + 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(display->bw.max); i++) { + const struct intel_bw_info *bi = + &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 i; + } + + return UINT_MAX; +} + +static unsigned int tgl_max_bw_index(struct intel_display *display, + int num_planes, int qgv_point) +{ + int i; + + for (i = ARRAY_SIZE(display->bw.max) - 1; i >= 0; i--) { + const struct intel_bw_info *bi = + &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 i; + } + + return 0; +} + +static unsigned int adl_psf_bw(struct intel_display *display, + int psf_gv_point) +{ + const struct intel_bw_info *bi = + &display->bw.max[0]; + + return bi->psf_bw[psf_gv_point]; +} + +static unsigned int icl_qgv_bw(struct intel_display *display, + int num_active_planes, int qgv_point) +{ + unsigned int idx; + + if (DISPLAY_VER(display) >= 12) + idx = tgl_max_bw_index(display, num_active_planes, qgv_point); + else + idx = icl_max_bw_index(display, num_active_planes, qgv_point); + + if (idx >= ARRAY_SIZE(display->bw.max)) + return 0; + + return display->bw.max[idx].deratedbw[qgv_point]; +} + +void intel_bw_init_hw(struct intel_display *display) +{ + const struct dram_info *dram_info; + const struct intel_soc_bw_params *soc_bw_params; + const struct intel_display_bw_params *display_bw_params; + + if (!HAS_DISPLAY(display)) + return; + + dram_info = intel_dram_info(display); + soc_bw_params = get_soc_bw_params(display, dram_info); + display_bw_params = get_display_bw_params(display); + + /* + * Starting with Xe3p_LPD, the hardware tells us whether memory has ECC + * enabled that would impact display bandwidth. However, so far there + * are no instructions in Bspec on how to handle that case. Let's + * complain if we ever find such a scenario. + */ + if (DISPLAY_VER(display) >= 35) + drm_WARN_ON(display->drm, dram_info->ecc_impacting_de_bw); + + if (DISPLAY_VERx100(display) >= 1401 && display->platform.dgfx) { + xe2_hpd_get_bw_info(display, dram_info, soc_bw_params); + } else if (display->platform.dg2) { + dg2_get_bw_info(display); + } else if (DISPLAY_VER(display) >= 12) { + tgl_get_bw_info(display, dram_info, soc_bw_params, display_bw_params); + } else if (DISPLAY_VER(display) == 11) { + icl_get_bw_info(display, dram_info, soc_bw_params, display_bw_params); + } +} + +static unsigned int intel_bw_num_active_planes(struct intel_display *display, + const struct intel_bw_state *bw_state) +{ + unsigned int num_active_planes = 0; + enum pipe pipe; + + for_each_pipe(display, pipe) + num_active_planes += bw_state->num_active_planes[pipe]; + + return num_active_planes; +} + +static unsigned int intel_bw_data_rate(struct intel_display *display, + const struct intel_bw_state *bw_state) +{ + unsigned int data_rate = 0; + enum pipe pipe; + + for_each_pipe(display, pipe) + data_rate += bw_state->data_rate[pipe]; + + if (DISPLAY_VER(display) >= 13 && intel_display_vtd_active(display)) + data_rate = DIV_ROUND_UP(data_rate * 105, 100); + + return data_rate; +} + +struct intel_bw_state *to_intel_bw_state(struct intel_global_state *obj_state) +{ + return container_of(obj_state, struct intel_bw_state, base); +} + +struct intel_bw_state * +intel_atomic_get_old_bw_state(struct intel_atomic_state *state) +{ + struct intel_display *display = to_intel_display(state); + struct intel_global_state *bw_state; + + bw_state = intel_atomic_get_old_global_obj_state(state, &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 intel_display *display = to_intel_display(state); + struct intel_global_state *bw_state; + + bw_state = intel_atomic_get_new_global_obj_state(state, &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 intel_display *display = to_intel_display(state); + struct intel_global_state *bw_state; + + bw_state = intel_atomic_get_global_obj_state(state, &display->bw.obj); + if (IS_ERR(bw_state)) + return ERR_CAST(bw_state); + + return to_intel_bw_state(bw_state); +} + +static unsigned int icl_max_bw_qgv_point_mask(struct intel_display *display, + int num_active_planes) +{ + unsigned int num_qgv_points = display->bw.max[0].num_qgv_points; + unsigned int max_bw_point = 0; + unsigned int max_bw = 0; + int i; + + for (i = 0; i < num_qgv_points; i++) { + unsigned int max_data_rate = + icl_qgv_bw(display, 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 = BIT(i); + max_bw = max_data_rate; + } + } + + return max_bw_point; +} + +static u16 icl_prepare_qgv_points_mask(struct intel_display *display, + unsigned int qgv_points, + unsigned int psf_points) +{ + return ~(ICL_PCODE_REQ_QGV_PT(qgv_points) | + ADLS_PCODE_REQ_PSF_PT(psf_points)) & icl_qgv_points_mask(display); +} + +static unsigned int icl_max_bw_psf_gv_point_mask(struct intel_display *display) +{ + unsigned int num_psf_gv_points = display->bw.max[0].num_psf_gv_points; + unsigned int max_bw_point_mask = 0; + unsigned int max_bw = 0; + int i; + + for (i = 0; i < num_psf_gv_points; i++) { + unsigned int max_data_rate = adl_psf_bw(display, i); + + if (max_data_rate > max_bw) { + max_bw_point_mask = BIT(i); + max_bw = max_data_rate; + } else if (max_data_rate == max_bw) { + max_bw_point_mask |= BIT(i); + } + } + + return max_bw_point_mask; +} + +static void icl_force_disable_sagv(struct intel_display *display, + struct intel_bw_state *bw_state) +{ + unsigned int qgv_points = icl_max_bw_qgv_point_mask(display, 0); + unsigned int psf_points = icl_max_bw_psf_gv_point_mask(display); + + bw_state->qgv_points_mask = icl_prepare_qgv_points_mask(display, + qgv_points, + psf_points); + + drm_dbg_kms(display->drm, "Forcing SAGV disable: mask 0x%x\n", + bw_state->qgv_points_mask); + + icl_pcode_restrict_qgv_points(display, bw_state->qgv_points_mask); +} + +void icl_sagv_pre_plane_update(struct intel_atomic_state *state) +{ + struct intel_display *display = to_intel_display(state); + const struct intel_bw_state *old_bw_state = + intel_atomic_get_old_bw_state(state); + const struct intel_bw_state *new_bw_state = + intel_atomic_get_new_bw_state(state); + u16 old_mask, new_mask; + + if (!new_bw_state) + return; + + old_mask = old_bw_state->qgv_points_mask; + new_mask = old_bw_state->qgv_points_mask | new_bw_state->qgv_points_mask; + + if (old_mask == new_mask) + return; + + WARN_ON(!new_bw_state->base.changed); + + drm_dbg_kms(display->drm, "Restricting QGV points: 0x%x -> 0x%x\n", + old_mask, new_mask); + + /* + * Restrict required qgv points before updating the configuration. + * According to BSpec we can't mask and unmask qgv points at the same + * time. Also masking should be done before updating the configuration + * and unmasking afterwards. + */ + icl_pcode_restrict_qgv_points(display, new_mask); +} + +void icl_sagv_post_plane_update(struct intel_atomic_state *state) +{ + struct intel_display *display = to_intel_display(state); + const struct intel_bw_state *old_bw_state = + intel_atomic_get_old_bw_state(state); + const struct intel_bw_state *new_bw_state = + intel_atomic_get_new_bw_state(state); + u16 old_mask, new_mask; + + if (!new_bw_state) + return; + + old_mask = old_bw_state->qgv_points_mask | new_bw_state->qgv_points_mask; + new_mask = new_bw_state->qgv_points_mask; + + if (old_mask == new_mask) + return; + + WARN_ON(!new_bw_state->base.changed); + + drm_dbg_kms(display->drm, "Relaxing QGV points: 0x%x -> 0x%x\n", + old_mask, new_mask); + + /* + * Allow required qgv points after updating the configuration. + * According to BSpec we can't mask and unmask qgv points at the same + * time. Also masking should be done before updating the configuration + * and unmasking afterwards. + */ + icl_pcode_restrict_qgv_points(display, new_mask); +} + +static int mtl_find_qgv_points(struct intel_display *display, + unsigned int data_rate, + unsigned int num_active_planes, + struct intel_bw_state *new_bw_state) +{ + unsigned int best_rate = UINT_MAX; + unsigned int num_qgv_points = display->bw.max[0].num_qgv_points; + unsigned int qgv_peak_bw = 0; + int i; + int ret; + + ret = intel_atomic_lock_global_state(&new_bw_state->base); + if (ret) + return ret; + + /* + * If SAGV cannot be enabled, disable the pcode SAGV by passing all 1's + * for qgv peak bw in PM Demand request. So assign UINT_MAX if SAGV is + * not enabled. PM Demand code will clamp the value for the register + */ + if (!intel_bw_can_enable_sagv(display, new_bw_state)) { + new_bw_state->qgv_point_peakbw = U16_MAX; + drm_dbg_kms(display->drm, "No SAGV, use UINT_MAX as peak bw."); + return 0; + } + + /* + * Find the best QGV point by comparing the data_rate with max data rate + * offered per plane group + */ + for (i = 0; i < num_qgv_points; i++) { + unsigned int bw_index = + tgl_max_bw_index(display, num_active_planes, i); + unsigned int max_data_rate; + + if (bw_index >= ARRAY_SIZE(display->bw.max)) + continue; + + max_data_rate = display->bw.max[bw_index].deratedbw[i]; + + if (max_data_rate < data_rate) + continue; + + if (max_data_rate - data_rate < best_rate) { + best_rate = max_data_rate - data_rate; + qgv_peak_bw = display->bw.max[bw_index].peakbw[i]; + } + + drm_dbg_kms(display->drm, "QGV point %d: max bw %d required %d qgv_peak_bw: %d\n", + i, max_data_rate, data_rate, qgv_peak_bw); + } + + drm_dbg_kms(display->drm, "Matching peaks QGV bw: %d for required data rate: %d\n", + qgv_peak_bw, data_rate); + + /* + * The display configuration cannot be supported if no QGV point + * satisfying the required data rate is found + */ + if (qgv_peak_bw == 0) { + drm_dbg_kms(display->drm, "No QGV points for bw %d for display configuration(%d active planes).\n", + data_rate, num_active_planes); + return -EINVAL; + } + + /* MTL PM DEMAND expects QGV BW parameter in multiples of 100 mbps */ + new_bw_state->qgv_point_peakbw = qgv_peak_bw / 100; + + return 0; +} + +static int icl_find_qgv_points(struct intel_display *display, + unsigned int data_rate, + unsigned int num_active_planes, + const struct intel_bw_state *old_bw_state, + struct intel_bw_state *new_bw_state) +{ + unsigned int num_psf_gv_points = display->bw.max[0].num_psf_gv_points; + unsigned int num_qgv_points = display->bw.max[0].num_qgv_points; + u16 psf_points = 0; + u16 qgv_points = 0; + int i; + int ret; + + ret = intel_atomic_lock_global_state(&new_bw_state->base); + if (ret) + return ret; + + for (i = 0; i < num_qgv_points; i++) { + unsigned int max_data_rate = icl_qgv_bw(display, + num_active_planes, i); + if (max_data_rate >= data_rate) + qgv_points |= BIT(i); + + drm_dbg_kms(display->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(display, i); + + if (max_data_rate >= data_rate) + psf_points |= BIT(i); + + drm_dbg_kms(display->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(display->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(display->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_bw_can_enable_sagv(display, new_bw_state)) { + qgv_points = icl_max_bw_qgv_point_mask(display, num_active_planes); + drm_dbg_kms(display->drm, "No SAGV, using single QGV point mask 0x%x\n", + qgv_points); + } + + /* + * 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_prepare_qgv_points_mask(display, + qgv_points, + psf_points); + /* + * 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 int intel_bw_check_qgv_points(struct intel_display *display, + const struct intel_bw_state *old_bw_state, + struct intel_bw_state *new_bw_state) +{ + unsigned int data_rate = intel_bw_data_rate(display, new_bw_state); + unsigned int num_active_planes = + intel_bw_num_active_planes(display, new_bw_state); + + data_rate = DIV_ROUND_UP(data_rate, 1000); + + if (DISPLAY_VER(display) >= 14) + return mtl_find_qgv_points(display, data_rate, num_active_planes, + new_bw_state); + else + return icl_find_qgv_points(display, data_rate, num_active_planes, + old_bw_state, new_bw_state); +} + +static int intel_bw_check_data_rate(struct intel_atomic_state *state, bool *changed) +{ + struct intel_display *display = to_intel_display(state); + const struct intel_crtc_state *new_crtc_state, *old_crtc_state; + struct intel_crtc *crtc; + + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state) { + unsigned int old_data_rate = + intel_crtc_bw_data_rate(old_crtc_state); + unsigned int new_data_rate = + intel_crtc_bw_data_rate(new_crtc_state); + unsigned int old_active_planes = + intel_crtc_bw_num_active_planes(old_crtc_state); + unsigned int new_active_planes = + intel_crtc_bw_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(display->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; +} + +static int intel_bw_check_sagv_mask(struct intel_atomic_state *state) +{ + struct intel_display *display = to_intel_display(state); + const struct intel_crtc_state *old_crtc_state; + const struct intel_crtc_state *new_crtc_state; + const struct intel_bw_state *old_bw_state = NULL; + struct intel_bw_state *new_bw_state = NULL; + struct intel_crtc *crtc; + int ret; + + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state) { + if (intel_crtc_can_enable_sagv(old_crtc_state) == + intel_crtc_can_enable_sagv(new_crtc_state)) + continue; + + 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); + + if (intel_crtc_can_enable_sagv(new_crtc_state)) + new_bw_state->pipe_sagv_reject &= ~BIT(crtc->pipe); + else + new_bw_state->pipe_sagv_reject |= BIT(crtc->pipe); + } + + if (!new_bw_state) + return 0; + + if (intel_bw_can_enable_sagv(display, new_bw_state) != + intel_bw_can_enable_sagv(display, old_bw_state)) { + ret = intel_atomic_serialize_global_state(&new_bw_state->base); + if (ret) + return ret; + } else if (new_bw_state->pipe_sagv_reject != old_bw_state->pipe_sagv_reject) { + ret = intel_atomic_lock_global_state(&new_bw_state->base); + if (ret) + return ret; + } + + return 0; +} + +int intel_bw_atomic_check(struct intel_atomic_state *state) +{ + struct intel_display *display = to_intel_display(state); + bool changed = false; + struct intel_bw_state *new_bw_state; + const struct intel_bw_state *old_bw_state; + int ret; + + if (DISPLAY_VER(display) < 9) + return 0; + + ret = intel_bw_check_sagv_mask(state); + if (ret) + return ret; + + /* FIXME earlier gens need some checks too */ + if (DISPLAY_VER(display) < 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_bw_can_enable_sagv(display, old_bw_state) != + intel_bw_can_enable_sagv(display, 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_bw_check_qgv_points(display, old_bw_state, new_bw_state); + if (ret) + return ret; + + return 0; +} + +static void intel_bw_crtc_update(struct intel_bw_state *bw_state, + const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + bw_state->data_rate[crtc->pipe] = + intel_crtc_bw_data_rate(crtc_state); + bw_state->num_active_planes[crtc->pipe] = + intel_crtc_bw_num_active_planes(crtc_state); + + drm_dbg_kms(display->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]); +} + +void intel_bw_update_hw_state(struct intel_display *display) +{ + struct intel_bw_state *bw_state = + to_intel_bw_state(display->bw.obj.state); + struct intel_crtc *crtc; + + if (DISPLAY_VER(display) < 9) + return; + + bw_state->pipe_sagv_reject = 0; + + for_each_intel_crtc(display, crtc) { + const struct intel_crtc_state *crtc_state = + to_intel_crtc_state(crtc->base.state); + enum pipe pipe = crtc->pipe; + + if (DISPLAY_VER(display) >= 11) + intel_bw_crtc_update(bw_state, crtc_state); + + /* initially SAGV has been forced off */ + bw_state->pipe_sagv_reject |= BIT(pipe); + } +} + +void intel_bw_crtc_disable_noatomic(struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + struct intel_bw_state *bw_state = + to_intel_bw_state(display->bw.obj.state); + enum pipe pipe = crtc->pipe; + + if (DISPLAY_VER(display) < 9) + return; + + bw_state->data_rate[pipe] = 0; + bw_state->num_active_planes[pipe] = 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 intel_display *display) +{ + struct intel_bw_state *state; + + state = kzalloc_obj(*state); + if (!state) + return -ENOMEM; + + intel_atomic_global_obj_init(display, &display->bw.obj, + &state->base, &intel_bw_funcs); + + /* + * Limit this only if we have SAGV. And for Display version 14 onwards + * sagv is handled though pmdemand requests + */ + if (intel_has_sagv(display) && IS_DISPLAY_VER(display, 11, 13)) + icl_force_disable_sagv(display, state); + + return 0; +} + +bool intel_bw_pmdemand_needs_update(struct intel_atomic_state *state) +{ + const struct intel_bw_state *new_bw_state, *old_bw_state; + + new_bw_state = intel_atomic_get_new_bw_state(state); + old_bw_state = intel_atomic_get_old_bw_state(state); + + if (new_bw_state && + new_bw_state->qgv_point_peakbw != old_bw_state->qgv_point_peakbw) + return true; + + return false; +} + +bool intel_bw_can_enable_sagv(struct intel_display *display, + const struct intel_bw_state *bw_state) +{ + return bw_state->pipe_sagv_reject == 0; +} + +int intel_bw_qgv_point_peakbw(const struct intel_bw_state *bw_state) +{ + return bw_state->qgv_point_peakbw; +} 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..99b447388 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_bw.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_BW_H__ +#define __INTEL_BW_H__ + +#include + +struct intel_atomic_state; +struct intel_bw_state; +struct intel_crtc; +struct intel_crtc_state; +struct intel_display; +struct intel_global_state; + +struct intel_bw_state *to_intel_bw_state(struct intel_global_state *obj_state); + +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 intel_display *display); +int intel_bw_init(struct intel_display *display); +int intel_bw_atomic_check(struct intel_atomic_state *state); +void intel_bw_update_hw_state(struct intel_display *display); +void intel_bw_crtc_disable_noatomic(struct intel_crtc *crtc); + +bool intel_bw_pmdemand_needs_update(struct intel_atomic_state *state); +bool intel_bw_can_enable_sagv(struct intel_display *display, + const struct intel_bw_state *bw_state); +void icl_sagv_pre_plane_update(struct intel_atomic_state *state); +void icl_sagv_post_plane_update(struct intel_atomic_state *state); +int intel_bw_qgv_point_peakbw(const struct intel_bw_state *bw_state); + +#endif /* __INTEL_BW_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_casf.c b/drivers/gpu/drm/i915/display/intel_casf.c new file mode 100644 index 000000000..cafd21a03 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_casf.c @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: MIT +/* Copyright © 2025 Intel Corporation */ + +#include + +#include "intel_casf.h" +#include "intel_casf_regs.h" +#include "intel_de.h" +#include "intel_display_regs.h" +#include "intel_display_types.h" +#include "skl_scaler.h" + +#define MAX_PIXELS_FOR_3_TAP_FILTER (1920 * 1080) +#define MAX_PIXELS_FOR_5_TAP_FILTER (3840 * 2160) + +#define FILTER_COEFF_0_125 125 +#define FILTER_COEFF_0_25 250 +#define FILTER_COEFF_0_5 500 +#define FILTER_COEFF_1_0 1000 +#define FILTER_COEFF_0_0 0 +#define SET_POSITIVE_SIGN(x) ((x) & (~SIGN)) + +/** + * DOC: Content Adaptive Sharpness Filter (CASF) + * + * Starting from LNL the display engine supports an + * adaptive sharpening filter, enhancing the image + * quality. The display hardware utilizes the second + * pipe scaler for implementing CASF. + * If sharpness is being enabled then pipe scaling + * cannot be used. + * This filter operates on a region of pixels based + * on the tap size. Coefficients are used to generate + * an alpha value which blends the sharpened image to + * original image. + */ + +/* Default LUT values to be loaded one time. */ +static const u16 sharpness_lut[] = { + 4095, 2047, 1364, 1022, 816, 678, 579, + 504, 444, 397, 357, 323, 293, 268, 244, 224, + 204, 187, 170, 154, 139, 125, 111, 98, 85, + 73, 60, 48, 36, 24, 12, 0 +}; + +const u16 filtercoeff_1[] = { + FILTER_COEFF_0_0, FILTER_COEFF_0_0, FILTER_COEFF_0_5, + FILTER_COEFF_1_0, FILTER_COEFF_0_5, FILTER_COEFF_0_0, + FILTER_COEFF_0_0, +}; + +const u16 filtercoeff_2[] = { + FILTER_COEFF_0_0, FILTER_COEFF_0_25, FILTER_COEFF_0_5, + FILTER_COEFF_1_0, FILTER_COEFF_0_5, FILTER_COEFF_0_25, + FILTER_COEFF_0_0, +}; + +const u16 filtercoeff_3[] = { + FILTER_COEFF_0_125, FILTER_COEFF_0_25, FILTER_COEFF_0_5, + FILTER_COEFF_1_0, FILTER_COEFF_0_5, FILTER_COEFF_0_25, + FILTER_COEFF_0_125, +}; + +static void intel_casf_filter_lut_load(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + int i; + + intel_de_write(display, SHRPLUT_INDEX(crtc->pipe), + INDEX_AUTO_INCR | INDEX_VALUE(0)); + + for (i = 0; i < ARRAY_SIZE(sharpness_lut); i++) + intel_de_write(display, SHRPLUT_DATA(crtc->pipe), + sharpness_lut[i]); +} + +static void intel_casf_compute_win_size(struct intel_crtc_state *crtc_state) +{ + const struct drm_display_mode *mode = &crtc_state->hw.adjusted_mode; + u32 total_pixels = mode->hdisplay * mode->vdisplay; + + if (total_pixels <= MAX_PIXELS_FOR_3_TAP_FILTER) + crtc_state->pch_pfit.casf.win_size = SHARPNESS_FILTER_SIZE_3X3; + else if (total_pixels <= MAX_PIXELS_FOR_5_TAP_FILTER) + crtc_state->pch_pfit.casf.win_size = SHARPNESS_FILTER_SIZE_5X5; + else + crtc_state->pch_pfit.casf.win_size = SHARPNESS_FILTER_SIZE_7X7; +} + +static void intel_casf_scaler_compute_coeff(struct intel_crtc_state *crtc_state); + +int intel_casf_compute_config(struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + if (crtc_state->hw.sharpness_strength == 0) + return 0; + + /* CASF with joiner not supported in hardware */ + if (crtc_state->joiner_pipes) { + drm_dbg_kms(display->drm, "CASF not supported with joiner\n"); + return -EINVAL; + } + + crtc_state->pch_pfit.casf.enable = true; + + /* + * HW takes a value in form (1.0 + strength) in 4.4 fixed format. + * Strength is from 0.0-14.9375 ie from 0-239. + * User can give value from 0-255 but is clamped to 239. + * Ex. User gives 85 which is 5.3125 and adding 1.0 gives 6.3125. + * 6.3125 in 4.4 format is b01100101 which is equal to 101. + * Also 85 + 16 = 101. + */ + crtc_state->pch_pfit.casf.strength = + min(crtc_state->hw.sharpness_strength, 0xEF) + 0x10; + + intel_casf_compute_win_size(crtc_state); + + intel_casf_scaler_compute_coeff(crtc_state); + + return 0; +} + +void intel_casf_sharpness_get_config(struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + u32 sharp; + + sharp = intel_de_read(display, SHARPNESS_CTL(crtc->pipe)); + if (sharp & FILTER_EN) { + if (drm_WARN_ON(display->drm, + REG_FIELD_GET(FILTER_STRENGTH_MASK, sharp) < 16)) + crtc_state->pch_pfit.casf.strength = 0; + else + crtc_state->pch_pfit.casf.strength = + REG_FIELD_GET(FILTER_STRENGTH_MASK, sharp); + crtc_state->pch_pfit.casf.enable = true; + crtc_state->pch_pfit.casf.win_size = + REG_FIELD_GET(FILTER_SIZE_MASK, sharp); + } +} + +static int casf_coeff_tap(int i) +{ + return i % SCALER_FILTER_NUM_TAPS; +} + +static u32 casf_coeff(const struct intel_crtc_state *crtc_state, int tap) +{ + struct scaler_filter_coeff value; + u32 coeff; + + value = crtc_state->pch_pfit.casf.coeff[tap]; + value.sign = 0; + + coeff = value.sign << 15 | value.exp << 12 | value.mantissa << 3; + return coeff; +} + +/* + * 17 phase of 7 taps requires 119 coefficients in 60 dwords per set. + * To enable casf: program scaler coefficients with the coefficients + * that are calculated and stored in pch_pfit.casf.coeff as per + * SCALER_COEFFICIENT_FORMAT + */ +static void intel_casf_write_coeff(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + int id = crtc_state->scaler_state.scaler_id; + int i; + + if (id != 1) { + drm_WARN(display->drm, 0, "Second scaler not enabled\n"); + return; + } + + intel_de_write_fw(display, GLK_PS_COEF_INDEX_SET(crtc->pipe, id, 0), + PS_COEF_INDEX_AUTO_INC); + + for (i = 0; i < 17 * SCALER_FILTER_NUM_TAPS; i += 2) { + u32 tmp; + int tap; + + tap = casf_coeff_tap(i); + tmp = casf_coeff(crtc_state, tap); + + tap = casf_coeff_tap(i + 1); + tmp |= casf_coeff(crtc_state, tap) << 16; + + intel_de_write_fw(display, GLK_PS_COEF_DATA_SET(crtc->pipe, id, 0), + tmp); + } +} + +static void convert_sharpness_coeff_binary(struct scaler_filter_coeff *coeff, + u16 coefficient) +{ + if (coefficient < 25) { + coeff->mantissa = (coefficient * 2048) / 100; + coeff->exp = 3; + } else if (coefficient < 50) { + coeff->mantissa = (coefficient * 1024) / 100; + coeff->exp = 2; + } else if (coefficient < 100) { + coeff->mantissa = (coefficient * 512) / 100; + coeff->exp = 1; + } else { + coeff->mantissa = (coefficient * 256) / 100; + coeff->exp = 0; + } +} + +static void intel_casf_scaler_compute_coeff(struct intel_crtc_state *crtc_state) +{ + const u16 *filtercoeff; + u16 filter_coeff[SCALER_FILTER_NUM_TAPS]; + u16 sum_coeff = 0; + int i; + + if (crtc_state->pch_pfit.casf.win_size == 0) + filtercoeff = filtercoeff_1; + else if (crtc_state->pch_pfit.casf.win_size == 1) + filtercoeff = filtercoeff_2; + else + filtercoeff = filtercoeff_3; + + for (i = 0; i < SCALER_FILTER_NUM_TAPS; i++) + sum_coeff += *(filtercoeff + i); + + for (i = 0; i < SCALER_FILTER_NUM_TAPS; i++) { + filter_coeff[i] = (*(filtercoeff + i) * 100 / sum_coeff); + convert_sharpness_coeff_binary(&crtc_state->pch_pfit.casf.coeff[i], + filter_coeff[i]); + } +} + +void intel_casf_setup(const struct intel_crtc_state *crtc_state) +{ + intel_casf_filter_lut_load(crtc_state); + intel_casf_write_coeff(crtc_state); +} diff --git a/drivers/gpu/drm/i915/display/intel_casf.h b/drivers/gpu/drm/i915/display/intel_casf.h new file mode 100644 index 000000000..3ebb7522a --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_casf.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2025 Intel Corporation + */ + +#ifndef __INTEL_CASF_H__ +#define __INTEL_CASF_H__ + +#include + +struct intel_crtc_state; + +int intel_casf_compute_config(struct intel_crtc_state *crtc_state); +void intel_casf_sharpness_get_config(struct intel_crtc_state *crtc_state); +void intel_casf_setup(const struct intel_crtc_state *crtc_state); + +#endif /* __INTEL_CASF_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_casf_regs.h b/drivers/gpu/drm/i915/display/intel_casf_regs.h new file mode 100644 index 000000000..87803cca5 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_casf_regs.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2025 Intel Corporation + */ + +#ifndef __INTEL_CASF_REGS_H__ +#define __INTEL_CASF_REGS_H__ + +#include "intel_display_reg_defs.h" + +#define _SHARPNESS_CTL_A 0x682B0 +#define _SHARPNESS_CTL_B 0x68AB0 +#define SHARPNESS_CTL(pipe) _MMIO_PIPE(pipe, _SHARPNESS_CTL_A, _SHARPNESS_CTL_B) +#define FILTER_EN REG_BIT(31) +#define FILTER_STRENGTH_MASK REG_GENMASK(15, 8) +#define FILTER_STRENGTH(x) REG_FIELD_PREP(FILTER_STRENGTH_MASK, (x)) +#define FILTER_SIZE_MASK REG_GENMASK(1, 0) +#define SHARPNESS_FILTER_SIZE_3X3 REG_FIELD_PREP(FILTER_SIZE_MASK, 0) +#define SHARPNESS_FILTER_SIZE_5X5 REG_FIELD_PREP(FILTER_SIZE_MASK, 1) +#define SHARPNESS_FILTER_SIZE_7X7 REG_FIELD_PREP(FILTER_SIZE_MASK, 2) + +#define _SHRPLUT_DATA_A 0x682B8 +#define _SHRPLUT_DATA_B 0x68AB8 +#define SHRPLUT_DATA(pipe) _MMIO_PIPE(pipe, _SHRPLUT_DATA_A, _SHRPLUT_DATA_B) + +#define _SHRPLUT_INDEX_A 0x682B4 +#define _SHRPLUT_INDEX_B 0x68AB4 +#define SHRPLUT_INDEX(pipe) _MMIO_PIPE(pipe, _SHRPLUT_INDEX_A, _SHRPLUT_INDEX_B) +#define INDEX_AUTO_INCR REG_BIT(10) +#define INDEX_VALUE_MASK REG_GENMASK(4, 0) +#define INDEX_VALUE(x) REG_FIELD_PREP(INDEX_VALUE_MASK, (x)) + +#endif /* __INTEL_CASF_REGS__ */ 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..189ae2d3c --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_cdclk.c @@ -0,0 +1,4261 @@ +/* + * 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 +#include + +#include +#include +#include +#include +#include + +#include "hsw_ips.h" +#include "intel_atomic.h" +#include "intel_audio.h" +#include "intel_cdclk.h" +#include "intel_crtc.h" +#include "intel_dbuf_bw.h" +#include "intel_de.h" +#include "intel_display_regs.h" +#include "intel_display_types.h" +#include "intel_display_utils.h" +#include "intel_display_wa.h" +#include "intel_dram.h" +#include "intel_mchbar.h" +#include "intel_parent.h" +#include "intel_plane.h" +#include "intel_psr.h" +#include "intel_vdsc.h" +#include "skl_watermark.h" +#include "skl_watermark_regs.h" +#include "vlv_clock.h" +#include "vlv_dsi.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. + * + * There are multiple components involved in the generation of the CDCLK + * frequency: + * + * - We have the CDCLK PLL, which generates an output clock based on a + * reference clock and a ratio parameter. + * - The CD2X Divider, which divides the output of the PLL based on a + * divisor selected from a set of pre-defined choices. + * - The CD2X Squasher, which further divides the output based on a + * waveform represented as a sequence of bits where each zero + * "squashes out" a clock cycle. + * - And, finally, a fixed divider that divides the output frequency by 2. + * + * As such, the resulting CDCLK frequency can be calculated with the + * following formula: + * + * cdclk = vco / cd2x_div / (sq_len / sq_div) / 2 + * + * , where vco is the frequency generated by the PLL; cd2x_div + * represents the CD2X Divider; sq_len and sq_div are the bit length + * and the number of high bits for the CD2X Squasher waveform, respectively; + * and 2 represents the fixed divider. + * + * Note that some older platforms do not contain the CD2X Divider + * and/or CD2X Squasher, in which case we can ignore their respective + * factors in the formula above. + * + * Several methods exist to change the CDCLK frequency, which ones are + * supported depends on the platform: + * + * - Full PLL disable + re-enable with new VCO frequency. Pipes must be inactive. + * - CD2X divider update. Single pipe can be active as the divider update + * can be synchronized with the pipe's start of vblank. + * - Crawl the PLL smoothly to the new VCO frequency. Pipes can be active. + * - Squash waveform update. Pipes can be active. + * - Crawl and squash can also be done back to back. Pipes can be active. + * + * 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_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 DBUF bandwidth requirements */ + int dbuf_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 enabled pipes */ + u8 enabled_pipes; + + /* bitmask of active pipes */ + u8 active_pipes; + + /* update cdclk with pipes disabled */ + bool disable_pipes; +}; + +struct intel_cdclk_funcs { + void (*get_cdclk)(struct intel_display *display, + struct intel_cdclk_config *cdclk_config); + void (*set_cdclk)(struct intel_display *display, + const struct intel_cdclk_config *cdclk_config, + enum pipe pipe); + int (*modeset_calc_cdclk)(struct intel_atomic_state *state); + u8 (*calc_voltage_level)(int cdclk); +}; + +void intel_cdclk_get_cdclk(struct intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + display->cdclk.funcs->get_cdclk(display, cdclk_config); +} + +static void intel_cdclk_set_cdclk(struct intel_display *display, + const struct intel_cdclk_config *cdclk_config, + enum pipe pipe) +{ + display->cdclk.funcs->set_cdclk(display, cdclk_config, pipe); +} + +static int intel_cdclk_modeset_calc_cdclk(struct intel_atomic_state *state) +{ + struct intel_display *display = to_intel_display(state); + + return display->cdclk.funcs->modeset_calc_cdclk(state); +} + +static u8 intel_cdclk_calc_voltage_level(struct intel_display *display, + int cdclk) +{ + return display->cdclk.funcs->calc_voltage_level(cdclk); +} + +static void fixed_133mhz_get_cdclk(struct intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + cdclk_config->cdclk = 133333; +} + +static void fixed_200mhz_get_cdclk(struct intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + cdclk_config->cdclk = 200000; +} + +static void fixed_266mhz_get_cdclk(struct intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + cdclk_config->cdclk = 266667; +} + +static void fixed_333mhz_get_cdclk(struct intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + cdclk_config->cdclk = 333333; +} + +static void fixed_400mhz_get_cdclk(struct intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + cdclk_config->cdclk = 400000; +} + +static void fixed_450mhz_get_cdclk(struct intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + cdclk_config->cdclk = 450000; +} + +static void i85x_get_cdclk(struct intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + struct pci_dev *pdev = to_pci_dev(display->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 intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + struct pci_dev *pdev = to_pci_dev(display->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 intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + struct pci_dev *pdev = to_pci_dev(display->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 intel_display *display) +{ + 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 (display->platform.gm45) + vco_table = ctg_vco; + else if (display->platform.g45) + vco_table = elk_vco; + else if (display->platform.i965gm) + vco_table = cl_vco; + else if (display->platform.pineview) + vco_table = pnv_vco; + else if (display->platform.g33) + vco_table = blb_vco; + else + return 0; + + tmp = intel_mchbar_read(display, display->platform.pineview || + display->platform.mobile ? HPLLVCO_MOBILE : HPLLVCO); + + vco = vco_table[tmp & 0x7]; + if (vco == 0) + drm_err(display->drm, "Bad HPLL VCO (HPLLVCO=0x%02x)\n", + tmp); + else + drm_dbg_kms(display->drm, "HPLL VCO %u kHz\n", vco); + + return vco; +} + +static void g33_get_cdclk(struct intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + struct pci_dev *pdev = to_pci_dev(display->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(display); + + 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(display->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 intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + struct pci_dev *pdev = to_pci_dev(display->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(display->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 intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + struct pci_dev *pdev = to_pci_dev(display->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(display); + + 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(display->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 intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + struct pci_dev *pdev = to_pci_dev(display->drm->dev); + unsigned int cdclk_sel; + u16 tmp = 0; + + cdclk_config->vco = intel_hpll_vco(display); + + 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(display->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 intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + u32 lcpll = intel_de_read(display, LCPLL_CTL); + u32 freq = lcpll & LCPLL_CLK_FREQ_MASK; + + if (lcpll & LCPLL_CD_SOURCE_FCLK) + cdclk_config->cdclk = 800000; + else if (intel_de_read(display, FUSE_STRAP) & HSW_CDCLK_LIMIT) + cdclk_config->cdclk = 450000; + else if (freq == LCPLL_CLK_FREQ_450) + cdclk_config->cdclk = 450000; + else if (display->platform.haswell_ult) + cdclk_config->cdclk = 337500; + else + cdclk_config->cdclk = 540000; +} + +static int vlv_calc_cdclk(struct intel_display *display, int min_cdclk) +{ + int freq_320 = (vlv_clock_get_hpll_vco(display->drm) << 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 (display->platform.valleyview && 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 intel_display *display, int cdclk) +{ + if (display->platform.valleyview) { + 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(vlv_clock_get_hpll_vco(display->drm) << 1, cdclk) - 1; + } +} + +static void vlv_get_cdclk(struct intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + u32 val; + + cdclk_config->vco = vlv_clock_get_hpll_vco(display->drm); + cdclk_config->cdclk = vlv_clock_get_cdclk(display->drm); + + vlv_punit_get(display); + val = vlv_punit_read(display, PUNIT_REG_DSPSSPM); + vlv_punit_put(display); + + if (display->platform.valleyview) + 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 intel_display *display) +{ + unsigned int credits, default_credits; + + if (display->platform.cherryview) + default_credits = PFI_CREDIT(12); + else + default_credits = PFI_CREDIT(8); + + if (display->cdclk.hw.cdclk >= vlv_clock_get_czclk(display->drm)) { + /* CHV suggested value is 31 or 63 */ + if (display->platform.cherryview) + 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(display, GCI_CONTROL, + VGA_FAST_MODE_DISABLE | default_credits); + + intel_de_write(display, 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(display->drm, + intel_de_read(display, GCI_CONTROL) & PFI_CREDIT_RESEND); +} + +static void vlv_set_cdclk(struct intel_display *display, + const struct intel_cdclk_config *cdclk_config, + enum pipe pipe) +{ + int cdclk = cdclk_config->cdclk; + u32 val, cmd = cdclk_config->voltage_level; + struct ref_tracker *wakeref; + int ret; + + 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(display, POWER_DOMAIN_DISPLAY_CORE); + + intel_parent_vlv_iosf_get(display, + BIT(VLV_IOSF_SB_CCK) | + BIT(VLV_IOSF_SB_BUNIT) | + BIT(VLV_IOSF_SB_PUNIT)); + + val = vlv_punit_read(display, PUNIT_REG_DSPSSPM); + val &= ~DSPFREQGUAR_MASK; + val |= (cmd << DSPFREQGUAR_SHIFT); + vlv_punit_write(display, PUNIT_REG_DSPSSPM, val); + + ret = poll_timeout_us(val = vlv_punit_read(display, PUNIT_REG_DSPSSPM), + (val & DSPFREQSTAT_MASK) == (cmd << DSPFREQSTAT_SHIFT), + 500, 50 * 1000, false); + if (ret) + drm_err(display->drm, "timed out waiting for CDCLK change\n"); + + if (cdclk == 400000) { + u32 divider; + + divider = DIV_ROUND_CLOSEST(vlv_clock_get_hpll_vco(display->drm) << 1, + cdclk) - 1; + + /* adjust cdclk divider */ + val = vlv_cck_read(display, CCK_DISPLAY_CLOCK_CONTROL); + val &= ~CCK_FREQUENCY_VALUES; + val |= divider; + vlv_cck_write(display, CCK_DISPLAY_CLOCK_CONTROL, val); + + ret = poll_timeout_us(val = vlv_cck_read(display, CCK_DISPLAY_CLOCK_CONTROL), + (val & CCK_FREQUENCY_STATUS) == (divider << CCK_FREQUENCY_STATUS_SHIFT), + 500, 50 * 1000, false); + if (ret) + drm_err(display->drm, "timed out waiting for CDCLK change\n"); + } + + /* adjust self-refresh exit latency value */ + val = vlv_bunit_read(display, 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(display, BUNIT_REG_BISOC, val); + + intel_parent_vlv_iosf_put(display, + BIT(VLV_IOSF_SB_CCK) | + BIT(VLV_IOSF_SB_BUNIT) | + BIT(VLV_IOSF_SB_PUNIT)); + + intel_update_cdclk(display); + + vlv_program_pfi_credits(display); + + intel_display_power_put(display, POWER_DOMAIN_DISPLAY_CORE, wakeref); +} + +static void chv_set_cdclk(struct intel_display *display, + const struct intel_cdclk_config *cdclk_config, + enum pipe pipe) +{ + int cdclk = cdclk_config->cdclk; + u32 val, cmd = cdclk_config->voltage_level; + struct ref_tracker *wakeref; + int ret; + + 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(display, POWER_DOMAIN_DISPLAY_CORE); + + vlv_punit_get(display); + val = vlv_punit_read(display, PUNIT_REG_DSPSSPM); + val &= ~DSPFREQGUAR_MASK_CHV; + val |= (cmd << DSPFREQGUAR_SHIFT_CHV); + vlv_punit_write(display, PUNIT_REG_DSPSSPM, val); + + ret = poll_timeout_us(val = vlv_punit_read(display, PUNIT_REG_DSPSSPM), + (val & DSPFREQSTAT_MASK_CHV) == (cmd << DSPFREQSTAT_SHIFT_CHV), + 500, 50 * 1000, false); + if (ret) + drm_err(display->drm, "timed out waiting for CDCLK change\n"); + + vlv_punit_put(display); + + intel_update_cdclk(display); + + vlv_program_pfi_credits(display); + + intel_display_power_put(display, 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 intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + u32 lcpll = intel_de_read(display, LCPLL_CTL); + u32 freq = lcpll & LCPLL_CLK_FREQ_MASK; + + if (lcpll & LCPLL_CD_SOURCE_FCLK) + cdclk_config->cdclk = 800000; + else if (intel_de_read(display, 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 intel_display *display, + const struct intel_cdclk_config *cdclk_config, + enum pipe pipe) +{ + int cdclk = cdclk_config->cdclk; + int ret; + + if (drm_WARN(display->drm, + (intel_de_read(display, 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 = intel_parent_pcode_write(display, BDW_PCODE_DISPLAY_FREQ_CHANGE_REQ, 0x0); + if (ret) { + drm_err(display->drm, + "failed to inform pcode about cdclk change\n"); + return; + } + + intel_de_rmw(display, 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. + */ + ret = intel_de_wait_for_set_us(display, LCPLL_CTL, + LCPLL_CD_SOURCE_FCLK_DONE, 100); + if (ret) + drm_err(display->drm, "Switching to FCLK failed\n"); + + intel_de_rmw(display, LCPLL_CTL, + LCPLL_CLK_FREQ_MASK, bdw_cdclk_freq_sel(cdclk)); + + intel_de_rmw(display, LCPLL_CTL, + LCPLL_CD_SOURCE_FCLK, 0); + + ret = intel_de_wait_for_clear_us(display, LCPLL_CTL, + LCPLL_CD_SOURCE_FCLK_DONE, 1); + if (ret) + drm_err(display->drm, "Switching back to LCPLL failed\n"); + + intel_parent_pcode_write(display, HSW_PCODE_DE_WRITE_FREQ_REQ, + cdclk_config->voltage_level); + + intel_de_write(display, CDCLK_FREQ, + DIV_ROUND_CLOSEST(cdclk, 1000) - 1); + + intel_update_cdclk(display); +} + +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 intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + u32 val; + + cdclk_config->ref = 24000; + cdclk_config->vco = 0; + + val = intel_de_read(display, LCPLL1_CTL); + if ((val & LCPLL_PLL_ENABLE) == 0) + return; + + if (drm_WARN_ON(display->drm, (val & LCPLL_PLL_LOCK) == 0)) + return; + + val = intel_de_read(display, DPLL_CTRL1); + + if (drm_WARN_ON(display->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 intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + u32 cdctl; + + skl_dpll0_update(display, cdclk_config); + + cdclk_config->cdclk = cdclk_config->bypass = cdclk_config->ref; + + if (cdclk_config->vco == 0) + goto out; + + cdctl = intel_de_read(display, 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 intel_display *display, int vco) +{ + bool changed = display->cdclk.skl_preferred_vco_freq != vco; + + display->cdclk.skl_preferred_vco_freq = vco; + + if (changed) + intel_update_max_cdclk(display); +} + +static u32 skl_dpll0_link_rate(struct intel_display *display, int vco) +{ + drm_WARN_ON(display->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 intel_display *display, int vco) +{ + intel_de_rmw(display, 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(display, vco)); + intel_de_posting_read(display, DPLL_CTRL1); + + intel_de_rmw(display, LCPLL1_CTL, + 0, LCPLL_PLL_ENABLE); + + if (intel_de_wait_for_set_ms(display, LCPLL1_CTL, LCPLL_PLL_LOCK, 5)) + drm_err(display->drm, "DPLL0 not locked\n"); + + display->cdclk.hw.vco = vco; + + /* We'll want to keep using the current vco from now on. */ + skl_set_preferred_cdclk_vco(display, vco); +} + +static void skl_dpll0_disable(struct intel_display *display) +{ + intel_de_rmw(display, LCPLL1_CTL, + LCPLL_PLL_ENABLE, 0); + + if (intel_de_wait_for_clear_ms(display, LCPLL1_CTL, LCPLL_PLL_LOCK, 1)) + drm_err(display->drm, "Couldn't disable DPLL0\n"); + + display->cdclk.hw.vco = 0; +} + +static u32 skl_cdclk_freq_sel(struct intel_display *display, + int cdclk, int vco) +{ + switch (cdclk) { + default: + drm_WARN_ON(display->drm, + cdclk != display->cdclk.hw.bypass); + drm_WARN_ON(display->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 intel_display *display, + 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(display->drm, + display->platform.skylake && vco == 8640000); + + ret = intel_parent_pcode_request(display, SKL_PCODE_CDCLK_CONTROL, + SKL_CDCLK_PREPARE_FOR_CHANGE, + SKL_CDCLK_READY_FOR_CHANGE, + SKL_CDCLK_READY_FOR_CHANGE, 3); + if (ret) { + drm_err(display->drm, + "Failed to inform PCU about cdclk change (%d)\n", ret); + return; + } + + freq_select = skl_cdclk_freq_sel(display, cdclk, vco); + + if (display->cdclk.hw.vco != 0 && + display->cdclk.hw.vco != vco) + skl_dpll0_disable(display); + + cdclk_ctl = intel_de_read(display, CDCLK_CTL); + + if (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(display, CDCLK_CTL, cdclk_ctl); + } + + /* Wa Display #1183: skl,kbl,cfl */ + cdclk_ctl |= CDCLK_DIVMUX_CD_OVERRIDE; + intel_de_write(display, CDCLK_CTL, cdclk_ctl); + intel_de_posting_read(display, CDCLK_CTL); + + if (display->cdclk.hw.vco != vco) + skl_dpll0_enable(display, vco); + + /* Wa Display #1183: skl,kbl,cfl */ + cdclk_ctl &= ~(CDCLK_FREQ_SEL_MASK | CDCLK_FREQ_DECIMAL_MASK); + intel_de_write(display, CDCLK_CTL, cdclk_ctl); + + cdclk_ctl |= freq_select | skl_cdclk_decimal(cdclk); + intel_de_write(display, CDCLK_CTL, cdclk_ctl); + + /* Wa Display #1183: skl,kbl,cfl */ + cdclk_ctl &= ~CDCLK_DIVMUX_CD_OVERRIDE; + intel_de_write(display, CDCLK_CTL, cdclk_ctl); + intel_de_posting_read(display, CDCLK_CTL); + + /* inform PCU of the change */ + intel_parent_pcode_write(display, SKL_PCODE_CDCLK_CONTROL, + cdclk_config->voltage_level); + + intel_update_cdclk(display); +} + +static void skl_sanitize_cdclk(struct intel_display *display) +{ + 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(display, SWF_ILK(0x18)) & 0x00FFFFFF) == 0) + goto sanitize; + + intel_update_cdclk(display); + intel_cdclk_dump_config(display, &display->cdclk.hw, "Current CDCLK"); + + /* Is PLL enabled and locked ? */ + if (display->cdclk.hw.vco == 0 || + display->cdclk.hw.cdclk == 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(display, CDCLK_CTL); + expected = (cdctl & CDCLK_FREQ_SEL_MASK) | + skl_cdclk_decimal(display->cdclk.hw.cdclk); + if (cdctl == expected) + /* All well; nothing to sanitize */ + return; + +sanitize: + drm_dbg_kms(display->drm, "Sanitizing cdclk programmed by pre-os\n"); + + /* force cdclk programming */ + display->cdclk.hw.cdclk = 0; + /* force full PLL disable + enable */ + display->cdclk.hw.vco = ~0; +} + +static void skl_cdclk_init_hw(struct intel_display *display) +{ + struct intel_cdclk_config cdclk_config; + + skl_sanitize_cdclk(display); + + if (display->cdclk.hw.cdclk != 0 && + display->cdclk.hw.vco != 0) { + /* + * Use the current vco as our initial + * guess as to what the preferred vco is. + */ + if (display->cdclk.skl_preferred_vco_freq == 0) + skl_set_preferred_cdclk_vco(display, + display->cdclk.hw.vco); + return; + } + + cdclk_config = display->cdclk.hw; + + cdclk_config.vco = display->cdclk.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(display, &cdclk_config, INVALID_PIPE); +} + +static void skl_cdclk_uninit_hw(struct intel_display *display) +{ + struct intel_cdclk_config cdclk_config = 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(display, &cdclk_config, INVALID_PIPE); +} + +struct intel_cdclk_vals { + u32 cdclk; + u16 refclk; + u16 waveform; + u8 ratio; +}; + +static const struct intel_cdclk_vals bxt_cdclk_table[] = { + { .refclk = 19200, .cdclk = 144000, .ratio = 60 }, + { .refclk = 19200, .cdclk = 288000, .ratio = 60 }, + { .refclk = 19200, .cdclk = 384000, .ratio = 60 }, + { .refclk = 19200, .cdclk = 576000, .ratio = 60 }, + { .refclk = 19200, .cdclk = 624000, .ratio = 65 }, + {} +}; + +static const struct intel_cdclk_vals glk_cdclk_table[] = { + { .refclk = 19200, .cdclk = 79200, .ratio = 33 }, + { .refclk = 19200, .cdclk = 158400, .ratio = 33 }, + { .refclk = 19200, .cdclk = 316800, .ratio = 33 }, + {} +}; + +static const struct intel_cdclk_vals icl_cdclk_table[] = { + { .refclk = 19200, .cdclk = 172800, .ratio = 18 }, + { .refclk = 19200, .cdclk = 192000, .ratio = 20 }, + { .refclk = 19200, .cdclk = 307200, .ratio = 32 }, + { .refclk = 19200, .cdclk = 326400, .ratio = 68 }, + { .refclk = 19200, .cdclk = 556800, .ratio = 58 }, + { .refclk = 19200, .cdclk = 652800, .ratio = 68 }, + + { .refclk = 24000, .cdclk = 180000, .ratio = 15 }, + { .refclk = 24000, .cdclk = 192000, .ratio = 16 }, + { .refclk = 24000, .cdclk = 312000, .ratio = 26 }, + { .refclk = 24000, .cdclk = 324000, .ratio = 54 }, + { .refclk = 24000, .cdclk = 552000, .ratio = 46 }, + { .refclk = 24000, .cdclk = 648000, .ratio = 54 }, + + { .refclk = 38400, .cdclk = 172800, .ratio = 9 }, + { .refclk = 38400, .cdclk = 192000, .ratio = 10 }, + { .refclk = 38400, .cdclk = 307200, .ratio = 16 }, + { .refclk = 38400, .cdclk = 326400, .ratio = 34 }, + { .refclk = 38400, .cdclk = 556800, .ratio = 29 }, + { .refclk = 38400, .cdclk = 652800, .ratio = 34 }, + {} +}; + +static const struct intel_cdclk_vals rkl_cdclk_table[] = { + { .refclk = 19200, .cdclk = 172800, .ratio = 36 }, + { .refclk = 19200, .cdclk = 192000, .ratio = 40 }, + { .refclk = 19200, .cdclk = 307200, .ratio = 64 }, + { .refclk = 19200, .cdclk = 326400, .ratio = 136 }, + { .refclk = 19200, .cdclk = 556800, .ratio = 116 }, + { .refclk = 19200, .cdclk = 652800, .ratio = 136 }, + + { .refclk = 24000, .cdclk = 180000, .ratio = 30 }, + { .refclk = 24000, .cdclk = 192000, .ratio = 32 }, + { .refclk = 24000, .cdclk = 312000, .ratio = 52 }, + { .refclk = 24000, .cdclk = 324000, .ratio = 108 }, + { .refclk = 24000, .cdclk = 552000, .ratio = 92 }, + { .refclk = 24000, .cdclk = 648000, .ratio = 108 }, + + { .refclk = 38400, .cdclk = 172800, .ratio = 18 }, + { .refclk = 38400, .cdclk = 192000, .ratio = 20 }, + { .refclk = 38400, .cdclk = 307200, .ratio = 32 }, + { .refclk = 38400, .cdclk = 326400, .ratio = 68 }, + { .refclk = 38400, .cdclk = 556800, .ratio = 58 }, + { .refclk = 38400, .cdclk = 652800, .ratio = 68 }, + {} +}; + +static const struct intel_cdclk_vals adlp_a_step_cdclk_table[] = { + { .refclk = 19200, .cdclk = 307200, .ratio = 32 }, + { .refclk = 19200, .cdclk = 556800, .ratio = 58 }, + { .refclk = 19200, .cdclk = 652800, .ratio = 68 }, + + { .refclk = 24000, .cdclk = 312000, .ratio = 26 }, + { .refclk = 24000, .cdclk = 552000, .ratio = 46 }, + { .refclk = 24400, .cdclk = 648000, .ratio = 54 }, + + { .refclk = 38400, .cdclk = 307200, .ratio = 16 }, + { .refclk = 38400, .cdclk = 556800, .ratio = 29 }, + { .refclk = 38400, .cdclk = 652800, .ratio = 34 }, + {} +}; + +static const struct intel_cdclk_vals adlp_cdclk_table[] = { + { .refclk = 19200, .cdclk = 172800, .ratio = 27 }, + { .refclk = 19200, .cdclk = 192000, .ratio = 20 }, + { .refclk = 19200, .cdclk = 307200, .ratio = 32 }, + { .refclk = 19200, .cdclk = 556800, .ratio = 58 }, + { .refclk = 19200, .cdclk = 652800, .ratio = 68 }, + + { .refclk = 24000, .cdclk = 176000, .ratio = 22 }, + { .refclk = 24000, .cdclk = 192000, .ratio = 16 }, + { .refclk = 24000, .cdclk = 312000, .ratio = 26 }, + { .refclk = 24000, .cdclk = 552000, .ratio = 46 }, + { .refclk = 24000, .cdclk = 648000, .ratio = 54 }, + + { .refclk = 38400, .cdclk = 179200, .ratio = 14 }, + { .refclk = 38400, .cdclk = 192000, .ratio = 10 }, + { .refclk = 38400, .cdclk = 307200, .ratio = 16 }, + { .refclk = 38400, .cdclk = 556800, .ratio = 29 }, + { .refclk = 38400, .cdclk = 652800, .ratio = 34 }, + {} +}; + +static const struct intel_cdclk_vals rplu_cdclk_table[] = { + { .refclk = 19200, .cdclk = 172800, .ratio = 27 }, + { .refclk = 19200, .cdclk = 192000, .ratio = 20 }, + { .refclk = 19200, .cdclk = 307200, .ratio = 32 }, + { .refclk = 19200, .cdclk = 480000, .ratio = 50 }, + { .refclk = 19200, .cdclk = 556800, .ratio = 58 }, + { .refclk = 19200, .cdclk = 652800, .ratio = 68 }, + + { .refclk = 24000, .cdclk = 176000, .ratio = 22 }, + { .refclk = 24000, .cdclk = 192000, .ratio = 16 }, + { .refclk = 24000, .cdclk = 312000, .ratio = 26 }, + { .refclk = 24000, .cdclk = 480000, .ratio = 40 }, + { .refclk = 24000, .cdclk = 552000, .ratio = 46 }, + { .refclk = 24000, .cdclk = 648000, .ratio = 54 }, + + { .refclk = 38400, .cdclk = 179200, .ratio = 14 }, + { .refclk = 38400, .cdclk = 192000, .ratio = 10 }, + { .refclk = 38400, .cdclk = 307200, .ratio = 16 }, + { .refclk = 38400, .cdclk = 480000, .ratio = 25 }, + { .refclk = 38400, .cdclk = 556800, .ratio = 29 }, + { .refclk = 38400, .cdclk = 652800, .ratio = 34 }, + {} +}; + +static const struct intel_cdclk_vals dg2_cdclk_table[] = { + { .refclk = 38400, .cdclk = 163200, .ratio = 34, .waveform = 0x8888 }, + { .refclk = 38400, .cdclk = 204000, .ratio = 34, .waveform = 0x9248 }, + { .refclk = 38400, .cdclk = 244800, .ratio = 34, .waveform = 0xa4a4 }, + { .refclk = 38400, .cdclk = 285600, .ratio = 34, .waveform = 0xa54a }, + { .refclk = 38400, .cdclk = 326400, .ratio = 34, .waveform = 0xaaaa }, + { .refclk = 38400, .cdclk = 367200, .ratio = 34, .waveform = 0xad5a }, + { .refclk = 38400, .cdclk = 408000, .ratio = 34, .waveform = 0xb6b6 }, + { .refclk = 38400, .cdclk = 448800, .ratio = 34, .waveform = 0xdbb6 }, + { .refclk = 38400, .cdclk = 489600, .ratio = 34, .waveform = 0xeeee }, + { .refclk = 38400, .cdclk = 530400, .ratio = 34, .waveform = 0xf7de }, + { .refclk = 38400, .cdclk = 571200, .ratio = 34, .waveform = 0xfefe }, + { .refclk = 38400, .cdclk = 612000, .ratio = 34, .waveform = 0xfffe }, + { .refclk = 38400, .cdclk = 652800, .ratio = 34, .waveform = 0xffff }, + {} +}; + +static const struct intel_cdclk_vals mtl_cdclk_table[] = { + { .refclk = 38400, .cdclk = 172800, .ratio = 16, .waveform = 0xad5a }, + { .refclk = 38400, .cdclk = 192000, .ratio = 16, .waveform = 0xb6b6 }, + { .refclk = 38400, .cdclk = 307200, .ratio = 16, .waveform = 0x0000 }, + { .refclk = 38400, .cdclk = 480000, .ratio = 25, .waveform = 0x0000 }, + { .refclk = 38400, .cdclk = 556800, .ratio = 29, .waveform = 0x0000 }, + { .refclk = 38400, .cdclk = 652800, .ratio = 34, .waveform = 0x0000 }, + {} +}; + +static const struct intel_cdclk_vals xe2lpd_cdclk_table[] = { + { .refclk = 38400, .cdclk = 153600, .ratio = 16, .waveform = 0xaaaa }, + { .refclk = 38400, .cdclk = 172800, .ratio = 16, .waveform = 0xad5a }, + { .refclk = 38400, .cdclk = 192000, .ratio = 16, .waveform = 0xb6b6 }, + { .refclk = 38400, .cdclk = 211200, .ratio = 16, .waveform = 0xdbb6 }, + { .refclk = 38400, .cdclk = 230400, .ratio = 16, .waveform = 0xeeee }, + { .refclk = 38400, .cdclk = 249600, .ratio = 16, .waveform = 0xf7de }, + { .refclk = 38400, .cdclk = 268800, .ratio = 16, .waveform = 0xfefe }, + { .refclk = 38400, .cdclk = 288000, .ratio = 16, .waveform = 0xfffe }, + { .refclk = 38400, .cdclk = 307200, .ratio = 16, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 330000, .ratio = 25, .waveform = 0xdbb6 }, + { .refclk = 38400, .cdclk = 360000, .ratio = 25, .waveform = 0xeeee }, + { .refclk = 38400, .cdclk = 390000, .ratio = 25, .waveform = 0xf7de }, + { .refclk = 38400, .cdclk = 420000, .ratio = 25, .waveform = 0xfefe }, + { .refclk = 38400, .cdclk = 450000, .ratio = 25, .waveform = 0xfffe }, + { .refclk = 38400, .cdclk = 480000, .ratio = 25, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 487200, .ratio = 29, .waveform = 0xfefe }, + { .refclk = 38400, .cdclk = 522000, .ratio = 29, .waveform = 0xfffe }, + { .refclk = 38400, .cdclk = 556800, .ratio = 29, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 571200, .ratio = 34, .waveform = 0xfefe }, + { .refclk = 38400, .cdclk = 612000, .ratio = 34, .waveform = 0xfffe }, + { .refclk = 38400, .cdclk = 652800, .ratio = 34, .waveform = 0xffff }, + {} +}; + +/* + * Xe2_HPD always uses the minimal cdclk table from Wa_15015413771 + */ +static const struct intel_cdclk_vals xe2hpd_cdclk_table[] = { + { .refclk = 38400, .cdclk = 652800, .ratio = 34, .waveform = 0xffff }, + {} +}; + +static const struct intel_cdclk_vals xe3lpd_cdclk_table[] = { + { .refclk = 38400, .cdclk = 153600, .ratio = 16, .waveform = 0xaaaa }, + { .refclk = 38400, .cdclk = 172800, .ratio = 16, .waveform = 0xad5a }, + { .refclk = 38400, .cdclk = 192000, .ratio = 16, .waveform = 0xb6b6 }, + { .refclk = 38400, .cdclk = 211200, .ratio = 16, .waveform = 0xdbb6 }, + { .refclk = 38400, .cdclk = 230400, .ratio = 16, .waveform = 0xeeee }, + { .refclk = 38400, .cdclk = 249600, .ratio = 16, .waveform = 0xf7de }, + { .refclk = 38400, .cdclk = 268800, .ratio = 16, .waveform = 0xfefe }, + { .refclk = 38400, .cdclk = 288000, .ratio = 16, .waveform = 0xfffe }, + { .refclk = 38400, .cdclk = 307200, .ratio = 16, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 326400, .ratio = 17, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 345600, .ratio = 18, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 364800, .ratio = 19, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 384000, .ratio = 20, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 403200, .ratio = 21, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 422400, .ratio = 22, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 441600, .ratio = 23, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 460800, .ratio = 24, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 480000, .ratio = 25, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 499200, .ratio = 26, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 518400, .ratio = 27, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 537600, .ratio = 28, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 556800, .ratio = 29, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 576000, .ratio = 30, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 595200, .ratio = 31, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 614400, .ratio = 32, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 633600, .ratio = 33, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 652800, .ratio = 34, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 672000, .ratio = 35, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 691200, .ratio = 36, .waveform = 0xffff }, + {} +}; + +static const struct intel_cdclk_vals xe3p_lpd_cdclk_table[] = { + { .refclk = 38400, .cdclk = 151200, .ratio = 21, .waveform = 0xa4a4 }, + { .refclk = 38400, .cdclk = 176400, .ratio = 21, .waveform = 0xaa54 }, + { .refclk = 38400, .cdclk = 201600, .ratio = 21, .waveform = 0xaaaa }, + { .refclk = 38400, .cdclk = 226800, .ratio = 21, .waveform = 0xad5a }, + { .refclk = 38400, .cdclk = 252000, .ratio = 21, .waveform = 0xb6b6 }, + { .refclk = 38400, .cdclk = 277200, .ratio = 21, .waveform = 0xdbb6 }, + { .refclk = 38400, .cdclk = 302400, .ratio = 21, .waveform = 0xeeee }, + { .refclk = 38400, .cdclk = 327600, .ratio = 21, .waveform = 0xf7de }, + { .refclk = 38400, .cdclk = 352800, .ratio = 21, .waveform = 0xfefe }, + { .refclk = 38400, .cdclk = 378000, .ratio = 21, .waveform = 0xfffe }, + { .refclk = 38400, .cdclk = 403200, .ratio = 21, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 422400, .ratio = 22, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 441600, .ratio = 23, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 460800, .ratio = 24, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 480000, .ratio = 25, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 499200, .ratio = 26, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 518400, .ratio = 27, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 537600, .ratio = 28, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 556800, .ratio = 29, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 576000, .ratio = 30, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 595200, .ratio = 31, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 614400, .ratio = 32, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 633600, .ratio = 33, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 652800, .ratio = 34, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 672000, .ratio = 35, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 691200, .ratio = 36, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 710400, .ratio = 37, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 729600, .ratio = 38, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 748800, .ratio = 39, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 768000, .ratio = 40, .waveform = 0xffff }, + { .refclk = 38400, .cdclk = 787200, .ratio = 41, .waveform = 0xffff }, + {} +}; + +static const int cdclk_squash_len = 16; + +static int cdclk_squash_divider(u16 waveform) +{ + return hweight16(waveform ?: 0xffff); +} + +static int cdclk_divider(int cdclk, int vco, u16 waveform) +{ + /* 2 * cd2x divider */ + return DIV_ROUND_CLOSEST(vco * cdclk_squash_divider(waveform), + cdclk * cdclk_squash_len); +} + +static int bxt_calc_cdclk(struct intel_display *display, int min_cdclk) +{ + const struct intel_cdclk_vals *table = display->cdclk.table; + int i; + + for (i = 0; table[i].refclk; i++) + if (table[i].refclk == display->cdclk.hw.ref && + table[i].cdclk >= min_cdclk) + return table[i].cdclk; + + drm_WARN(display->drm, 1, + "Cannot satisfy minimum cdclk %d with refclk %u\n", + min_cdclk, display->cdclk.hw.ref); + return display->cdclk.max_cdclk_freq; +} + +static int bxt_calc_cdclk_pll_vco(struct intel_display *display, int cdclk) +{ + const struct intel_cdclk_vals *table = display->cdclk.table; + int i; + + if (cdclk == display->cdclk.hw.bypass) + return 0; + + for (i = 0; table[i].refclk; i++) + if (table[i].refclk == display->cdclk.hw.ref && + table[i].cdclk == cdclk) + return display->cdclk.hw.ref * table[i].ratio; + + drm_WARN(display->drm, 1, "cdclk %d not valid for refclk %u\n", + cdclk, display->cdclk.hw.ref); + return 0; +} + +static u8 bxt_calc_voltage_level(int cdclk) +{ + return DIV_ROUND_UP(cdclk, 25000); +} + +static u8 calc_voltage_level(int cdclk, int num_voltage_levels, + const int voltage_level_max_cdclk[]) +{ + int voltage_level; + + for (voltage_level = 0; voltage_level < num_voltage_levels; voltage_level++) { + if (cdclk <= voltage_level_max_cdclk[voltage_level]) + return voltage_level; + } + + MISSING_CASE(cdclk); + return num_voltage_levels - 1; +} + +static u8 icl_calc_voltage_level(int cdclk) +{ + static const int icl_voltage_level_max_cdclk[] = { + [0] = 312000, + [1] = 556800, + [2] = 652800, + }; + + return calc_voltage_level(cdclk, + ARRAY_SIZE(icl_voltage_level_max_cdclk), + icl_voltage_level_max_cdclk); +} + +static u8 ehl_calc_voltage_level(int cdclk) +{ + static const int ehl_voltage_level_max_cdclk[] = { + [0] = 180000, + [1] = 312000, + [2] = 326400, + /* + * Bspec lists the limit as 556.8 MHz, but some JSL + * development boards (at least) boot with 652.8 MHz + */ + [3] = 652800, + }; + + return calc_voltage_level(cdclk, + ARRAY_SIZE(ehl_voltage_level_max_cdclk), + ehl_voltage_level_max_cdclk); +} + +static u8 tgl_calc_voltage_level(int cdclk) +{ + static const int tgl_voltage_level_max_cdclk[] = { + [0] = 312000, + [1] = 326400, + [2] = 556800, + [3] = 652800, + }; + + return calc_voltage_level(cdclk, + ARRAY_SIZE(tgl_voltage_level_max_cdclk), + tgl_voltage_level_max_cdclk); +} + +static u8 rplu_calc_voltage_level(int cdclk) +{ + static const int rplu_voltage_level_max_cdclk[] = { + [0] = 312000, + [1] = 480000, + [2] = 556800, + [3] = 652800, + }; + + return calc_voltage_level(cdclk, + ARRAY_SIZE(rplu_voltage_level_max_cdclk), + rplu_voltage_level_max_cdclk); +} + +static u8 xe3lpd_calc_voltage_level(int cdclk) +{ + /* + * Starting with xe3lpd power controller does not need the voltage + * index when doing the modeset update. This function is best left + * defined but returning 0 to the mask. + */ + return 0; +} + +static void icl_readout_refclk(struct intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + u32 dssm = intel_de_read(display, 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 intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + u32 val, ratio; + + if (display->platform.dg2) + cdclk_config->ref = 38400; + else if (DISPLAY_VER(display) >= 11) + icl_readout_refclk(display, cdclk_config); + else + cdclk_config->ref = 19200; + + val = intel_de_read(display, 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(display) >= 11) + ratio = val & ICL_CDCLK_PLL_RATIO_MASK; + else + ratio = intel_de_read(display, BXT_DE_PLL_CTL) & BXT_DE_PLL_RATIO_MASK; + + cdclk_config->vco = ratio * cdclk_config->ref; +} + +static void bxt_get_cdclk(struct intel_display *display, + struct intel_cdclk_config *cdclk_config) +{ + u32 squash_ctl = 0; + u32 divider; + int div; + + bxt_de_pll_readout(display, cdclk_config); + + if (DISPLAY_VER(display) >= 12) + cdclk_config->bypass = cdclk_config->ref / 2; + else if (DISPLAY_VER(display) >= 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(display, 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_SQUASH(display)) + squash_ctl = intel_de_read(display, 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: + if (DISPLAY_VER(display) >= 20) + cdclk_config->joined_mbus = intel_de_read(display, MBUS_CTL) & MBUS_JOIN; + /* + * 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(display, cdclk_config->cdclk); +} + +static void bxt_de_pll_disable(struct intel_display *display) +{ + intel_de_write(display, BXT_DE_PLL_ENABLE, 0); + + /* Timeout 200us */ + if (intel_de_wait_for_clear_ms(display, + BXT_DE_PLL_ENABLE, BXT_DE_PLL_LOCK, 1)) + drm_err(display->drm, "timeout waiting for DE PLL unlock\n"); + + display->cdclk.hw.vco = 0; +} + +static void bxt_de_pll_enable(struct intel_display *display, int vco) +{ + int ratio = DIV_ROUND_CLOSEST(vco, display->cdclk.hw.ref); + + intel_de_rmw(display, BXT_DE_PLL_CTL, + BXT_DE_PLL_RATIO_MASK, BXT_DE_PLL_RATIO(ratio)); + + intel_de_write(display, BXT_DE_PLL_ENABLE, BXT_DE_PLL_PLL_ENABLE); + + /* Timeout 200us */ + if (intel_de_wait_for_set_ms(display, + BXT_DE_PLL_ENABLE, BXT_DE_PLL_LOCK, 1)) + drm_err(display->drm, "timeout waiting for DE PLL lock\n"); + + display->cdclk.hw.vco = vco; +} + +static void icl_cdclk_pll_disable(struct intel_display *display) +{ + /* + * Wa_13012396614: + * Fixes: A sporadic race condition between MDCLK selection and PLL + * enabling. + * Workaround: + * Change programming of MDCLK source selection in CDCLK_CTL: + * - When disabling the CDCLK PLL, first set MDCLK source to be CD2XCLK. + * - When enabling the CDCLK PLL, update MDCLK source selection only + * after the PLL is enabled (which is already done as part of the + * normal flow of _bxt_set_cdclk()). + */ + if (intel_display_wa(display, INTEL_DISPLAY_WA_13012396614)) + intel_de_rmw(display, CDCLK_CTL, MDCLK_SOURCE_SEL_MASK, MDCLK_SOURCE_SEL_CD2XCLK); + + intel_de_rmw(display, BXT_DE_PLL_ENABLE, + BXT_DE_PLL_PLL_ENABLE, 0); + + /* Timeout 200us */ + if (intel_de_wait_for_clear_ms(display, BXT_DE_PLL_ENABLE, BXT_DE_PLL_LOCK, 1)) + drm_err(display->drm, "timeout waiting for CDCLK PLL unlock\n"); + + display->cdclk.hw.vco = 0; +} + +static void icl_cdclk_pll_enable(struct intel_display *display, int vco) +{ + int ratio = DIV_ROUND_CLOSEST(vco, display->cdclk.hw.ref); + u32 val; + + val = ICL_CDCLK_PLL_RATIO(ratio); + intel_de_write(display, BXT_DE_PLL_ENABLE, val); + + val |= BXT_DE_PLL_PLL_ENABLE; + intel_de_write(display, BXT_DE_PLL_ENABLE, val); + + /* Timeout 200us */ + if (intel_de_wait_for_set_ms(display, BXT_DE_PLL_ENABLE, BXT_DE_PLL_LOCK, 1)) + drm_err(display->drm, "timeout waiting for CDCLK PLL lock\n"); + + display->cdclk.hw.vco = vco; +} + +static void adlp_cdclk_pll_crawl(struct intel_display *display, int vco) +{ + int ratio = DIV_ROUND_CLOSEST(vco, 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(display, BXT_DE_PLL_ENABLE, val); + + /* Submit freq change request */ + val |= BXT_DE_PLL_FREQ_REQ; + intel_de_write(display, BXT_DE_PLL_ENABLE, val); + + /* Timeout 200us */ + if (intel_de_wait_for_set_ms(display, BXT_DE_PLL_ENABLE, + BXT_DE_PLL_LOCK | BXT_DE_PLL_FREQ_REQ_ACK, 1)) + drm_err(display->drm, "timeout waiting for FREQ change request ack\n"); + + val &= ~BXT_DE_PLL_FREQ_REQ; + intel_de_write(display, BXT_DE_PLL_ENABLE, val); + + display->cdclk.hw.vco = vco; +} + +static u32 bxt_cdclk_cd2x_pipe(struct intel_display *display, enum pipe pipe) +{ + if (DISPLAY_VER(display) >= 12) { + if (pipe == INVALID_PIPE) + return TGL_CDCLK_CD2X_PIPE_NONE; + else + return TGL_CDCLK_CD2X_PIPE(pipe); + } else if (DISPLAY_VER(display) >= 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 intel_display *display, + int cdclk, int vco, u16 waveform) +{ + u32 ret; + + /* cdclk = vco / 2 / div{1,1.5,2,4} */ + switch (cdclk_divider(cdclk, vco, waveform)) { + default: + drm_WARN_ON(display->drm, + cdclk != display->cdclk.hw.bypass); + drm_WARN_ON(display->drm, vco != 0); + fallthrough; + case 2: + ret = BXT_CDCLK_CD2X_DIV_SEL_1; + break; + case 3: + ret = BXT_CDCLK_CD2X_DIV_SEL_1_5; + break; + case 4: + ret = BXT_CDCLK_CD2X_DIV_SEL_2; + break; + case 8: + ret = BXT_CDCLK_CD2X_DIV_SEL_4; + break; + } + + /* + * On Xe3_LPD onward, the expectation is to always have + * BXT_CDCLK_CD2X_DIV_SEL_1 as the default. + */ + if (DISPLAY_VER(display) >= 30) + drm_WARN_ON(display->drm, ret != BXT_CDCLK_CD2X_DIV_SEL_1); + + return ret; +} + +static u16 cdclk_squash_waveform(struct intel_display *display, + int cdclk) +{ + const struct intel_cdclk_vals *table = display->cdclk.table; + int i; + + if (cdclk == display->cdclk.hw.bypass) + return 0; + + for (i = 0; table[i].refclk; i++) + if (table[i].refclk == display->cdclk.hw.ref && + table[i].cdclk == cdclk) + return table[i].waveform; + + drm_WARN(display->drm, 1, "cdclk %d not valid for refclk %u\n", + cdclk, display->cdclk.hw.ref); + + return 0xffff; +} + +static void icl_cdclk_pll_update(struct intel_display *display, int vco) +{ + if (display->cdclk.hw.vco != 0 && + display->cdclk.hw.vco != vco) + icl_cdclk_pll_disable(display); + + if (display->cdclk.hw.vco != vco) + icl_cdclk_pll_enable(display, vco); +} + +static void bxt_cdclk_pll_update(struct intel_display *display, int vco) +{ + if (display->cdclk.hw.vco != 0 && + display->cdclk.hw.vco != vco) + bxt_de_pll_disable(display); + + if (display->cdclk.hw.vco != vco) + bxt_de_pll_enable(display, vco); +} + +static void dg2_cdclk_squash_program(struct intel_display *display, + u16 waveform) +{ + u32 squash_ctl = 0; + + if (waveform) + squash_ctl = CDCLK_SQUASH_ENABLE | + CDCLK_SQUASH_WINDOW_SIZE(0xf) | waveform; + + intel_de_write(display, CDCLK_SQUASH_CTL, squash_ctl); +} + +static bool cdclk_pll_is_unknown(unsigned int vco) +{ + /* + * Ensure driver does not take the crawl path for the + * case when the vco is set to ~0 in the + * sanitize path. + */ + return vco == ~0; +} + +static bool mdclk_source_is_cdclk_pll(struct intel_display *display) +{ + return DISPLAY_VER(display) >= 20; +} + +static u32 xe2lpd_mdclk_source_sel(struct intel_display *display) +{ + if (mdclk_source_is_cdclk_pll(display)) + return MDCLK_SOURCE_SEL_CDCLK_PLL; + + return MDCLK_SOURCE_SEL_CD2XCLK; +} + +int intel_mdclk_cdclk_ratio(struct intel_display *display, + const struct intel_cdclk_config *cdclk_config) +{ + if (mdclk_source_is_cdclk_pll(display)) + return DIV_ROUND_UP(cdclk_config->vco, cdclk_config->cdclk); + + /* Otherwise, source for MDCLK is CD2XCLK. */ + return 2; +} + +static void xe2lpd_mdclk_cdclk_ratio_program(struct intel_display *display, + const struct intel_cdclk_config *cdclk_config) +{ + intel_dbuf_mdclk_cdclk_ratio_update(display, + intel_mdclk_cdclk_ratio(display, cdclk_config), + cdclk_config->joined_mbus); +} + +static bool cdclk_compute_crawl_and_squash_midpoint(struct intel_display *display, + const struct intel_cdclk_config *old_cdclk_config, + const struct intel_cdclk_config *new_cdclk_config, + struct intel_cdclk_config *mid_cdclk_config) +{ + u16 old_waveform, new_waveform, mid_waveform; + int old_div, new_div, mid_div; + + /* Return if PLL is in an unknown state, force a complete disable and re-enable. */ + if (cdclk_pll_is_unknown(old_cdclk_config->vco)) + return false; + + /* Return if both Squash and Crawl are not present */ + if (!HAS_CDCLK_CRAWL(display) || !HAS_CDCLK_SQUASH(display)) + return false; + + old_waveform = cdclk_squash_waveform(display, old_cdclk_config->cdclk); + new_waveform = cdclk_squash_waveform(display, new_cdclk_config->cdclk); + + /* Return if Squash only or Crawl only is the desired action */ + if (old_cdclk_config->vco == 0 || new_cdclk_config->vco == 0 || + old_cdclk_config->vco == new_cdclk_config->vco || + old_waveform == new_waveform) + return false; + + old_div = cdclk_divider(old_cdclk_config->cdclk, + old_cdclk_config->vco, old_waveform); + new_div = cdclk_divider(new_cdclk_config->cdclk, + new_cdclk_config->vco, new_waveform); + + /* + * Should not happen currently. We might need more midpoint + * transitions if we need to also change the cd2x divider. + */ + if (drm_WARN_ON(display->drm, old_div != new_div)) + return false; + + *mid_cdclk_config = *new_cdclk_config; + + /* + * Populate the mid_cdclk_config accordingly. + * - If moving to a higher cdclk, the desired action is squashing. + * The mid cdclk config should have the new (squash) waveform. + * - If moving to a lower cdclk, the desired action is crawling. + * The mid cdclk config should have the new vco. + */ + + if (cdclk_squash_divider(new_waveform) > cdclk_squash_divider(old_waveform)) { + mid_cdclk_config->vco = old_cdclk_config->vco; + mid_div = old_div; + mid_waveform = new_waveform; + } else { + mid_cdclk_config->vco = new_cdclk_config->vco; + mid_div = new_div; + mid_waveform = old_waveform; + } + + mid_cdclk_config->cdclk = DIV_ROUND_CLOSEST(cdclk_squash_divider(mid_waveform) * + mid_cdclk_config->vco, + cdclk_squash_len * mid_div); + + /* make sure the mid clock came out sane */ + + drm_WARN_ON(display->drm, mid_cdclk_config->cdclk < + min(old_cdclk_config->cdclk, new_cdclk_config->cdclk)); + drm_WARN_ON(display->drm, mid_cdclk_config->cdclk > + display->cdclk.max_cdclk_freq); + drm_WARN_ON(display->drm, cdclk_squash_waveform(display, mid_cdclk_config->cdclk) != + mid_waveform); + + return true; +} + +static bool pll_enable_wa_needed(struct intel_display *display) +{ + return (DISPLAY_VERx100(display) == 2000 || + DISPLAY_VERx100(display) == 1400 || + display->platform.dg2) && + display->cdclk.hw.vco > 0; +} + +static u32 bxt_cdclk_ctl(struct intel_display *display, + const struct intel_cdclk_config *cdclk_config, + enum pipe pipe) +{ + int cdclk = cdclk_config->cdclk; + int vco = cdclk_config->vco; + u16 waveform; + u32 val; + + waveform = cdclk_squash_waveform(display, cdclk); + + val = bxt_cdclk_cd2x_div_sel(display, cdclk, vco, waveform); + + if (DISPLAY_VER(display) < 30) + val |= bxt_cdclk_cd2x_pipe(display, pipe); + + /* + * Disable SSA Precharge when CD clock frequency < 500 MHz, + * enable otherwise. + */ + if ((display->platform.geminilake || display->platform.broxton) && + cdclk >= 500000) + val |= BXT_CDCLK_SSA_PRECHARGE_ENABLE; + + if (DISPLAY_VER(display) >= 20) { + /* + * Wa_13012396614 requires selecting CD2XCLK as MDCLK source + * prior to disabling the PLL, which is already handled by + * icl_cdclk_pll_disable(). Here we are just making sure + * we keep the expected value. + */ + if (intel_display_wa(display, INTEL_DISPLAY_WA_13012396614) && + vco == 0) + val |= MDCLK_SOURCE_SEL_CD2XCLK; + else + val |= xe2lpd_mdclk_source_sel(display); + } else { + val |= skl_cdclk_decimal(cdclk); + } + + return val; +} + +static void _bxt_set_cdclk(struct intel_display *display, + const struct intel_cdclk_config *cdclk_config, + enum pipe pipe) +{ + int cdclk = cdclk_config->cdclk; + int vco = cdclk_config->vco; + + if (HAS_CDCLK_CRAWL(display) && display->cdclk.hw.vco > 0 && vco > 0 && + !cdclk_pll_is_unknown(display->cdclk.hw.vco)) { + if (display->cdclk.hw.vco != vco) + adlp_cdclk_pll_crawl(display, vco); + } else if (DISPLAY_VER(display) >= 11) { + /* wa_15010685871: dg2, mtl */ + if (pll_enable_wa_needed(display)) + dg2_cdclk_squash_program(display, 0); + + icl_cdclk_pll_update(display, vco); + } else { + bxt_cdclk_pll_update(display, vco); + } + + if (HAS_CDCLK_SQUASH(display)) { + u16 waveform = cdclk_squash_waveform(display, cdclk); + + dg2_cdclk_squash_program(display, waveform); + } + + intel_de_write(display, CDCLK_CTL, bxt_cdclk_ctl(display, cdclk_config, pipe)); + + if (pipe != INVALID_PIPE) + intel_crtc_wait_for_next_vblank(intel_crtc_for_pipe(display, pipe)); +} + +static void bxt_set_cdclk(struct intel_display *display, + const struct intel_cdclk_config *cdclk_config, + enum pipe pipe) +{ + struct intel_cdclk_config mid_cdclk_config; + int cdclk = cdclk_config->cdclk; + int ret = 0; + + /* + * Inform power controller of upcoming frequency change. + * Display versions 14 and beyond do not follow the PUnit + * mailbox communication, skip + * this step. + */ + if (DISPLAY_VER(display) >= 14 || display->platform.dg2) + ; /* NOOP */ + else if (DISPLAY_VER(display) >= 11) + ret = intel_parent_pcode_request(display, 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 = intel_parent_pcode_write_timeout(display, + HSW_PCODE_DE_WRITE_FREQ_REQ, + 0x80000000, 2); + + if (ret) { + drm_err(display->drm, + "Failed to inform PCU about cdclk change (err %d, freq %d)\n", + ret, cdclk); + return; + } + + if (DISPLAY_VER(display) >= 20 && cdclk < display->cdclk.hw.cdclk) + xe2lpd_mdclk_cdclk_ratio_program(display, cdclk_config); + + if (cdclk_compute_crawl_and_squash_midpoint(display, &display->cdclk.hw, + cdclk_config, &mid_cdclk_config)) { + _bxt_set_cdclk(display, &mid_cdclk_config, pipe); + _bxt_set_cdclk(display, cdclk_config, pipe); + } else { + _bxt_set_cdclk(display, cdclk_config, pipe); + } + + if (DISPLAY_VER(display) >= 20 && cdclk > display->cdclk.hw.cdclk) + xe2lpd_mdclk_cdclk_ratio_program(display, cdclk_config); + + if (DISPLAY_VER(display) >= 14) + /* + * NOOP - No Pcode communication needed for + * Display versions 14 and beyond + */; + else if (DISPLAY_VER(display) >= 11 && !display->platform.dg2) + ret = intel_parent_pcode_write(display, SKL_PCODE_CDCLK_CONTROL, + cdclk_config->voltage_level); + if (DISPLAY_VER(display) < 11) { + /* + * 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 = intel_parent_pcode_write_timeout(display, + HSW_PCODE_DE_WRITE_FREQ_REQ, + cdclk_config->voltage_level, 2); + } + if (ret) { + drm_err(display->drm, + "PCode CDCLK freq set failed, (err %d, freq %d)\n", + ret, cdclk); + return; + } + + intel_update_cdclk(display); + + if (DISPLAY_VER(display) >= 11) + /* + * Can't read out the voltage level :( + * Let's just assume everything is as expected. + */ + display->cdclk.hw.voltage_level = cdclk_config->voltage_level; +} + +static void bxt_sanitize_cdclk(struct intel_display *display) +{ + u32 cdctl, expected; + int cdclk, vco; + + intel_update_cdclk(display); + intel_cdclk_dump_config(display, &display->cdclk.hw, "Current CDCLK"); + + if (display->cdclk.hw.vco == 0 || + display->cdclk.hw.cdclk == display->cdclk.hw.bypass) + goto sanitize; + + /* Make sure this is a legal cdclk value for the platform */ + cdclk = bxt_calc_cdclk(display, display->cdclk.hw.cdclk); + if (cdclk != display->cdclk.hw.cdclk) + goto sanitize; + + /* Make sure the VCO is correct for the cdclk */ + vco = bxt_calc_cdclk_pll_vco(display, cdclk); + if (vco != display->cdclk.hw.vco) + goto sanitize; + + /* + * 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(display, CDCLK_CTL); + expected = bxt_cdclk_ctl(display, &display->cdclk.hw, INVALID_PIPE); + + /* + * Let's ignore the pipe field, since BIOS could have configured the + * dividers both syncing to an active pipe, or asynchronously + * (PIPE_NONE). + */ + cdctl &= ~bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); + expected &= ~bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); + + if (cdctl == expected) + /* All well; nothing to sanitize */ + return; + +sanitize: + drm_dbg_kms(display->drm, "Sanitizing cdclk programmed by pre-os\n"); + + /* force cdclk programming */ + display->cdclk.hw.cdclk = 0; + + /* force full PLL disable + enable */ + display->cdclk.hw.vco = ~0; +} + +static void bxt_cdclk_init_hw(struct intel_display *display) +{ + struct intel_cdclk_config cdclk_config; + + bxt_sanitize_cdclk(display); + + if (display->cdclk.hw.cdclk != 0 && + display->cdclk.hw.vco != 0) + return; + + cdclk_config = 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(display, 0); + cdclk_config.vco = bxt_calc_cdclk_pll_vco(display, cdclk_config.cdclk); + cdclk_config.voltage_level = + intel_cdclk_calc_voltage_level(display, cdclk_config.cdclk); + + bxt_set_cdclk(display, &cdclk_config, INVALID_PIPE); +} + +static void bxt_cdclk_uninit_hw(struct intel_display *display) +{ + struct intel_cdclk_config cdclk_config = display->cdclk.hw; + + cdclk_config.cdclk = cdclk_config.bypass; + cdclk_config.vco = 0; + cdclk_config.voltage_level = + intel_cdclk_calc_voltage_level(display, cdclk_config.cdclk); + + bxt_set_cdclk(display, &cdclk_config, INVALID_PIPE); +} + +/** + * intel_cdclk_init_hw - Initialize CDCLK hardware + * @display: display instance + * + * Initialize CDCLK. This consists mainly of initializing 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 intel_display *display) +{ + if (DISPLAY_VER(display) >= 10 || display->platform.broxton) + bxt_cdclk_init_hw(display); + else if (DISPLAY_VER(display) == 9) + skl_cdclk_init_hw(display); +} + +/** + * intel_cdclk_uninit_hw - Uninitialize CDCLK hardware + * @display: display instance + * + * Uninitialize CDCLK. This is done only during the display core + * uninitialization sequence. + */ +void intel_cdclk_uninit_hw(struct intel_display *display) +{ + if (DISPLAY_VER(display) >= 10 || display->platform.broxton) + bxt_cdclk_uninit_hw(display); + else if (DISPLAY_VER(display) == 9) + skl_cdclk_uninit_hw(display); +} + +static bool intel_cdclk_can_crawl_and_squash(struct intel_display *display, + const struct intel_cdclk_config *a, + const struct intel_cdclk_config *b) +{ + u16 old_waveform; + u16 new_waveform; + + drm_WARN_ON(display->drm, cdclk_pll_is_unknown(a->vco)); + + if (a->vco == 0 || b->vco == 0) + return false; + + if (!HAS_CDCLK_CRAWL(display) || !HAS_CDCLK_SQUASH(display)) + return false; + + old_waveform = cdclk_squash_waveform(display, a->cdclk); + new_waveform = cdclk_squash_waveform(display, b->cdclk); + + return a->vco != b->vco && + old_waveform != new_waveform; +} + +static bool intel_cdclk_can_crawl(struct intel_display *display, + const struct intel_cdclk_config *a, + const struct intel_cdclk_config *b) +{ + int a_div, b_div; + + if (!HAS_CDCLK_CRAWL(display)) + 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 intel_display *display, + 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_SQUASH(display)) + return false; + + return a->cdclk != b->cdclk && + a->vco != 0 && + a->vco == b->vco && + a->ref == b->ref; +} + +/** + * intel_cdclk_clock_changed - Check whether the clock changed + * @a: first CDCLK configuration + * @b: second CDCLK configuration + * + * Returns: + * True if CDCLK changed in a way that requires re-programming and + * False otherwise. + */ +bool intel_cdclk_clock_changed(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 + * @display: display instance + * @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 intel_display *display, + const struct intel_cdclk_config *a, + const struct intel_cdclk_config *b) +{ + /* Older hw doesn't have the capability */ + if (DISPLAY_VER(display) < 10 && !display->platform.broxton) + 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_SQUASH(display)) + 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_clock_changed(a, b) || + a->voltage_level != b->voltage_level; +} + +void intel_cdclk_dump_config(struct intel_display *display, + const struct intel_cdclk_config *cdclk_config, + const char *context) +{ + drm_dbg_kms(display->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); +} + +static void intel_pcode_notify(struct intel_display *display, + u8 voltage_level, + u8 active_pipe_count, + u16 cdclk, + bool cdclk_update_valid, + bool pipe_count_update_valid) +{ + int ret; + u32 update_mask = 0; + + if (!display->platform.dg2) + return; + + update_mask = DISPLAY_TO_PCODE_UPDATE_MASK(cdclk, active_pipe_count, voltage_level); + + if (cdclk_update_valid) + update_mask |= DISPLAY_TO_PCODE_CDCLK_VALID; + + if (pipe_count_update_valid) + update_mask |= DISPLAY_TO_PCODE_PIPE_COUNT_VALID; + + ret = intel_parent_pcode_request(display, SKL_PCODE_CDCLK_CONTROL, + SKL_CDCLK_PREPARE_FOR_CHANGE | + update_mask, + SKL_CDCLK_READY_FOR_CHANGE, + SKL_CDCLK_READY_FOR_CHANGE, 3); + if (ret) + drm_err(display->drm, + "Failed to inform PCU about display config (err %d)\n", + ret); +} + +static void intel_set_cdclk(struct intel_display *display, + const struct intel_cdclk_config *cdclk_config, + enum pipe pipe, const char *context) +{ + struct intel_encoder *encoder; + + if (!intel_cdclk_changed(&display->cdclk.hw, cdclk_config)) + return; + + if (drm_WARN_ON_ONCE(display->drm, !display->cdclk.funcs->set_cdclk)) + return; + + intel_cdclk_dump_config(display, cdclk_config, context); + + for_each_intel_encoder_with_psr(display->drm, encoder) { + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + + intel_psr_pause(intel_dp); + } + + intel_audio_cdclk_change_pre(display); + + /* + * 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(&display->gmbus.mutex); + for_each_intel_dp(display->drm, encoder) { + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + + mutex_lock_nest_lock(&intel_dp->aux.hw_mutex, + &display->gmbus.mutex); + } + + intel_cdclk_set_cdclk(display, cdclk_config, pipe); + + for_each_intel_dp(display->drm, encoder) { + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + + mutex_unlock(&intel_dp->aux.hw_mutex); + } + mutex_unlock(&display->gmbus.mutex); + + for_each_intel_encoder_with_psr(display->drm, encoder) { + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + + intel_psr_resume(intel_dp); + } + + intel_audio_cdclk_change_post(display); + + if (drm_WARN(display->drm, + intel_cdclk_changed(&display->cdclk.hw, cdclk_config), + "cdclk state doesn't match!\n")) { + intel_cdclk_dump_config(display, &display->cdclk.hw, "[hw state]"); + intel_cdclk_dump_config(display, cdclk_config, "[sw state]"); + } +} + +static bool dg2_power_well_count(struct intel_display *display, + const struct intel_cdclk_state *cdclk_state) +{ + return display->platform.dg2 ? hweight8(cdclk_state->active_pipes) : 0; +} + +static void intel_cdclk_pcode_pre_notify(struct intel_atomic_state *state) +{ + struct intel_display *display = to_intel_display(state); + 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); + unsigned int cdclk = 0; u8 voltage_level, num_active_pipes = 0; + bool change_cdclk, update_pipe_count; + + if (!intel_cdclk_changed(&old_cdclk_state->actual, + &new_cdclk_state->actual) && + dg2_power_well_count(display, old_cdclk_state) == + dg2_power_well_count(display, new_cdclk_state)) + return; + + /* According to "Sequence Before Frequency Change", voltage level set to 0x3 */ + voltage_level = DISPLAY_TO_PCODE_VOLTAGE_MAX; + + change_cdclk = new_cdclk_state->actual.cdclk != old_cdclk_state->actual.cdclk; + update_pipe_count = dg2_power_well_count(display, new_cdclk_state) > + dg2_power_well_count(display, old_cdclk_state); + + /* + * According to "Sequence Before Frequency Change", + * if CDCLK is increasing, set bits 25:16 to upcoming CDCLK, + * if CDCLK is decreasing or not changing, set bits 25:16 to current CDCLK, + * which basically means we choose the maximum of old and new CDCLK, if we know both + */ + if (change_cdclk) + cdclk = max(new_cdclk_state->actual.cdclk, old_cdclk_state->actual.cdclk); + + /* + * According to "Sequence For Pipe Count Change", + * if pipe count is increasing, set bits 25:16 to upcoming pipe count + * (power well is enabled) + * no action if it is decreasing, before the change + */ + if (update_pipe_count) + num_active_pipes = dg2_power_well_count(display, new_cdclk_state); + + intel_pcode_notify(display, voltage_level, num_active_pipes, cdclk, + change_cdclk, update_pipe_count); +} + +static void intel_cdclk_pcode_post_notify(struct intel_atomic_state *state) +{ + struct intel_display *display = to_intel_display(state); + const struct intel_cdclk_state *new_cdclk_state = + intel_atomic_get_new_cdclk_state(state); + const struct intel_cdclk_state *old_cdclk_state = + intel_atomic_get_old_cdclk_state(state); + unsigned int cdclk = 0; u8 voltage_level, num_active_pipes = 0; + bool update_cdclk, update_pipe_count; + + /* According to "Sequence After Frequency Change", set voltage to used level */ + voltage_level = new_cdclk_state->actual.voltage_level; + + update_cdclk = new_cdclk_state->actual.cdclk != old_cdclk_state->actual.cdclk; + update_pipe_count = dg2_power_well_count(display, new_cdclk_state) < + dg2_power_well_count(display, old_cdclk_state); + + /* + * According to "Sequence After Frequency Change", + * set bits 25:16 to current CDCLK + */ + if (update_cdclk) + cdclk = new_cdclk_state->actual.cdclk; + + /* + * According to "Sequence For Pipe Count Change", + * if pipe count is decreasing, set bits 25:16 to current pipe count, + * after the change(power well is disabled) + * no action if it is increasing, after the change + */ + if (update_pipe_count) + num_active_pipes = dg2_power_well_count(display, new_cdclk_state); + + intel_pcode_notify(display, voltage_level, num_active_pipes, cdclk, + update_cdclk, update_pipe_count); +} + +bool intel_cdclk_is_decreasing_later(struct intel_atomic_state *state) +{ + 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); + + return new_cdclk_state && !new_cdclk_state->disable_pipes && + new_cdclk_state->actual.cdclk < old_cdclk_state->actual.cdclk; +} + +/** + * 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 intel_display *display = to_intel_display(state); + 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); + struct intel_cdclk_config cdclk_config; + enum pipe pipe; + + if (!new_cdclk_state) + return; + + if (!intel_cdclk_changed(&old_cdclk_state->actual, + &new_cdclk_state->actual)) + return; + + if (display->platform.dg2) + intel_cdclk_pcode_pre_notify(state); + + if (new_cdclk_state->disable_pipes) { + cdclk_config = new_cdclk_state->actual; + pipe = INVALID_PIPE; + } else { + if (new_cdclk_state->actual.cdclk >= old_cdclk_state->actual.cdclk) { + cdclk_config = new_cdclk_state->actual; + pipe = new_cdclk_state->pipe; + } else { + cdclk_config = old_cdclk_state->actual; + pipe = INVALID_PIPE; + } + + cdclk_config.voltage_level = max(new_cdclk_state->actual.voltage_level, + old_cdclk_state->actual.voltage_level); + } + + /* + * mbus joining will be changed later by + * intel_dbuf_mbus_{pre,post}_ddb_update() + */ + cdclk_config.joined_mbus = old_cdclk_state->actual.joined_mbus; + + drm_WARN_ON(display->drm, !new_cdclk_state->base.changed); + + intel_set_cdclk(display, &cdclk_config, pipe, + "Pre changing CDCLK to"); +} + +/** + * 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 intel_display *display = to_intel_display(state); + 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; + + if (!new_cdclk_state) + return; + + if (!intel_cdclk_changed(&old_cdclk_state->actual, + &new_cdclk_state->actual)) + return; + + if (display->platform.dg2) + intel_cdclk_pcode_post_notify(state); + + if (!new_cdclk_state->disable_pipes && + new_cdclk_state->actual.cdclk < old_cdclk_state->actual.cdclk) + pipe = new_cdclk_state->pipe; + else + pipe = INVALID_PIPE; + + drm_WARN_ON(display->drm, !new_cdclk_state->base.changed); + + intel_set_cdclk(display, &new_cdclk_state->actual, pipe, + "Post changing CDCLK to"); +} + +/* pixels per CDCLK */ +static int intel_cdclk_ppc(struct intel_display *display, bool double_wide) +{ + return DISPLAY_VER(display) >= 10 || double_wide ? 2 : 1; +} + +/* max pixel rate as % of CDCLK (not accounting for PPC) */ +static int intel_cdclk_guardband(struct intel_display *display) +{ + if (DISPLAY_VER(display) >= 9 || + display->platform.broadwell || display->platform.haswell) + return 100; + else if (display->platform.cherryview) + return 95; + else + return 90; +} + +static int _intel_pixel_rate_to_cdclk(const struct intel_crtc_state *crtc_state, int pixel_rate) +{ + struct intel_display *display = to_intel_display(crtc_state); + int ppc = intel_cdclk_ppc(display, crtc_state->double_wide); + int guardband = intel_cdclk_guardband(display); + + return DIV_ROUND_UP(pixel_rate * 100, guardband * ppc); +} + +static int intel_pixel_rate_to_cdclk(const struct intel_crtc_state *crtc_state) +{ + return _intel_pixel_rate_to_cdclk(crtc_state, crtc_state->pixel_rate); +} + +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 intel_display *display = to_intel_display(crtc); + struct intel_plane *plane; + int min_cdclk = 0; + + for_each_intel_plane_on_crtc(display->drm, crtc, plane) + min_cdclk = max(min_cdclk, crtc_state->plane_min_cdclk[plane->id]); + + return min_cdclk; +} + +int intel_crtc_min_cdclk(const struct intel_crtc_state *crtc_state) +{ + int min_cdclk; + + if (!crtc_state->hw.enable) + return 0; + + min_cdclk = intel_pixel_rate_to_cdclk(crtc_state); + min_cdclk = max(min_cdclk, intel_crtc_bw_min_cdclk(crtc_state)); + min_cdclk = max(min_cdclk, intel_fbc_min_cdclk(crtc_state)); + min_cdclk = max(min_cdclk, hsw_ips_min_cdclk(crtc_state)); + min_cdclk = max(min_cdclk, intel_audio_min_cdclk(crtc_state)); + min_cdclk = max(min_cdclk, vlv_dsi_min_cdclk(crtc_state)); + min_cdclk = max(min_cdclk, intel_planes_min_cdclk(crtc_state)); + min_cdclk = max(min_cdclk, intel_vdsc_min_cdclk(crtc_state)); + + return min_cdclk; +} + +static int intel_cdclk_update_crtc_min_cdclk(struct intel_atomic_state *state, + struct intel_crtc *crtc, + int old_min_cdclk, int new_min_cdclk, + bool *need_cdclk_calc) +{ + struct intel_display *display = to_intel_display(state); + struct intel_cdclk_state *cdclk_state; + bool allow_cdclk_decrease = intel_any_crtc_needs_modeset(state); + int ret; + + if (new_min_cdclk == old_min_cdclk) + return 0; + + if (!allow_cdclk_decrease && 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); + + old_min_cdclk = cdclk_state->min_cdclk[crtc->pipe]; + + if (new_min_cdclk == old_min_cdclk) + return 0; + + if (!allow_cdclk_decrease && new_min_cdclk < old_min_cdclk) + return 0; + + cdclk_state->min_cdclk[crtc->pipe] = new_min_cdclk; + + ret = intel_atomic_lock_global_state(&cdclk_state->base); + if (ret) + return ret; + + *need_cdclk_calc = true; + + drm_dbg_kms(display->drm, + "[CRTC:%d:%s] min cdclk: %d kHz -> %d kHz\n", + crtc->base.base.id, crtc->base.name, + old_min_cdclk, new_min_cdclk); + + return 0; +} + +static int intel_cdclk_update_crtc_min_voltage_level(struct intel_atomic_state *state, + struct intel_crtc *crtc, + u8 old_min_voltage_level, + u8 new_min_voltage_level, + bool *need_cdclk_calc) +{ + struct intel_display *display = to_intel_display(state); + struct intel_cdclk_state *cdclk_state; + bool allow_voltage_level_decrease = intel_any_crtc_needs_modeset(state); + int ret; + + if (new_min_voltage_level == old_min_voltage_level) + return 0; + + if (!allow_voltage_level_decrease && + new_min_voltage_level < old_min_voltage_level) + return 0; + + cdclk_state = intel_atomic_get_cdclk_state(state); + if (IS_ERR(cdclk_state)) + return PTR_ERR(cdclk_state); + + old_min_voltage_level = cdclk_state->min_voltage_level[crtc->pipe]; + + if (new_min_voltage_level == old_min_voltage_level) + return 0; + + if (!allow_voltage_level_decrease && + new_min_voltage_level < old_min_voltage_level) + return 0; + + cdclk_state->min_voltage_level[crtc->pipe] = new_min_voltage_level; + + ret = intel_atomic_lock_global_state(&cdclk_state->base); + if (ret) + return ret; + + *need_cdclk_calc = true; + + drm_dbg_kms(display->drm, + "[CRTC:%d:%s] min voltage level: %d -> %d\n", + crtc->base.base.id, crtc->base.name, + old_min_voltage_level, new_min_voltage_level); + + return 0; +} + +int intel_cdclk_update_dbuf_bw_min_cdclk(struct intel_atomic_state *state, + int old_min_cdclk, int new_min_cdclk, + bool *need_cdclk_calc) +{ + struct intel_display *display = to_intel_display(state); + struct intel_cdclk_state *cdclk_state; + bool allow_cdclk_decrease = intel_any_crtc_needs_modeset(state); + int ret; + + if (new_min_cdclk == old_min_cdclk) + return 0; + + if (!allow_cdclk_decrease && 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); + + old_min_cdclk = cdclk_state->dbuf_bw_min_cdclk; + + if (new_min_cdclk == old_min_cdclk) + return 0; + + if (!allow_cdclk_decrease && new_min_cdclk < old_min_cdclk) + return 0; + + cdclk_state->dbuf_bw_min_cdclk = new_min_cdclk; + + ret = intel_atomic_lock_global_state(&cdclk_state->base); + if (ret) + return ret; + + *need_cdclk_calc = true; + + drm_dbg_kms(display->drm, + "dbuf bandwidth min cdclk: %d kHz -> %d kHz\n", + old_min_cdclk, new_min_cdclk); + + return 0; +} + +static bool glk_cdclk_audio_wa_needed(struct intel_display *display, + const struct intel_cdclk_state *cdclk_state) +{ + return display->platform.geminilake && + cdclk_state->enabled_pipes && + !is_power_of_2(cdclk_state->enabled_pipes); +} + +static int intel_compute_min_cdclk(struct intel_atomic_state *state) +{ + struct intel_display *display = to_intel_display(state); + struct intel_cdclk_state *cdclk_state = + intel_atomic_get_new_cdclk_state(state); + enum pipe pipe; + int min_cdclk; + + min_cdclk = cdclk_state->force_min_cdclk; + min_cdclk = max(min_cdclk, cdclk_state->dbuf_bw_min_cdclk); + for_each_pipe(display, pipe) + min_cdclk = max(min_cdclk, cdclk_state->min_cdclk[pipe]); + + /* + * 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 (glk_cdclk_audio_wa_needed(display, cdclk_state)) + min_cdclk = max(min_cdclk, 2 * 96000); + + if (min_cdclk > display->cdclk.max_cdclk_freq) { + drm_dbg_kms(display->drm, + "required cdclk (%d kHz) exceeds max (%d kHz)\n", + min_cdclk, 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_atomic_state *state) +{ + struct intel_display *display = to_intel_display(state); + struct intel_cdclk_state *cdclk_state = + intel_atomic_get_new_cdclk_state(state); + struct intel_crtc *crtc; + struct intel_crtc_state *crtc_state; + u8 min_voltage_level; + enum pipe pipe; + + for_each_new_intel_crtc_in_state(state, crtc, crtc_state) { + 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(display, pipe) + min_voltage_level = max(min_voltage_level, + cdclk_state->min_voltage_level[pipe]); + + return min_voltage_level; +} + +static int vlv_modeset_calc_cdclk(struct intel_atomic_state *state) +{ + struct intel_display *display = to_intel_display(state); + struct intel_cdclk_state *cdclk_state = + intel_atomic_get_new_cdclk_state(state); + int min_cdclk, cdclk; + + min_cdclk = intel_compute_min_cdclk(state); + if (min_cdclk < 0) + return min_cdclk; + + cdclk = vlv_calc_cdclk(display, min_cdclk); + + cdclk_state->logical.cdclk = cdclk; + cdclk_state->logical.voltage_level = + vlv_calc_voltage_level(display, cdclk); + + if (!cdclk_state->active_pipes) { + cdclk = vlv_calc_cdclk(display, cdclk_state->force_min_cdclk); + + cdclk_state->actual.cdclk = cdclk; + cdclk_state->actual.voltage_level = + vlv_calc_voltage_level(display, cdclk); + } else { + cdclk_state->actual = cdclk_state->logical; + } + + return 0; +} + +static int bdw_modeset_calc_cdclk(struct intel_atomic_state *state) +{ + struct intel_cdclk_state *cdclk_state = + intel_atomic_get_new_cdclk_state(state); + int min_cdclk, cdclk; + + min_cdclk = intel_compute_min_cdclk(state); + if (min_cdclk < 0) + return min_cdclk; + + 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_atomic_state *state) +{ + struct intel_display *display = to_intel_display(state); + struct intel_cdclk_state *cdclk_state = + intel_atomic_get_new_cdclk_state(state); + struct intel_crtc *crtc; + struct intel_crtc_state *crtc_state; + int vco; + + vco = cdclk_state->logical.vco; + if (!vco) + vco = display->cdclk.skl_preferred_vco_freq; + + for_each_new_intel_crtc_in_state(state, crtc, crtc_state) { + 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_atomic_state *state) +{ + struct intel_cdclk_state *cdclk_state = + intel_atomic_get_new_cdclk_state(state); + int min_cdclk, cdclk, vco; + + min_cdclk = intel_compute_min_cdclk(state); + if (min_cdclk < 0) + return min_cdclk; + + vco = skl_dpll0_vco(state); + + 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_atomic_state *state) +{ + struct intel_display *display = to_intel_display(state); + struct intel_cdclk_state *cdclk_state = + intel_atomic_get_new_cdclk_state(state); + int min_cdclk, min_voltage_level, cdclk, vco; + + min_cdclk = intel_compute_min_cdclk(state); + if (min_cdclk < 0) + return min_cdclk; + + min_voltage_level = bxt_compute_min_voltage_level(state); + if (min_voltage_level < 0) + return min_voltage_level; + + cdclk = bxt_calc_cdclk(display, min_cdclk); + vco = bxt_calc_cdclk_pll_vco(display, 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(display, cdclk)); + + if (!cdclk_state->active_pipes) { + cdclk = bxt_calc_cdclk(display, cdclk_state->force_min_cdclk); + vco = bxt_calc_cdclk_pll_vco(display, cdclk); + + cdclk_state->actual.vco = vco; + cdclk_state->actual.cdclk = cdclk; + cdclk_state->actual.voltage_level = + intel_cdclk_calc_voltage_level(display, cdclk); + } else { + cdclk_state->actual = cdclk_state->logical; + } + + return 0; +} + +static int fixed_modeset_calc_cdclk(struct intel_atomic_state *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(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; + cdclk_state->disable_pipes = false; + + 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 intel_display *display = to_intel_display(state); + struct intel_global_state *cdclk_state; + + cdclk_state = intel_atomic_get_global_obj_state(state, &display->cdclk.obj); + if (IS_ERR(cdclk_state)) + return ERR_CAST(cdclk_state); + + return to_intel_cdclk_state(cdclk_state); +} + +static int intel_cdclk_modeset_checks(struct intel_atomic_state *state, + bool *need_cdclk_calc) +{ + struct intel_display *display = to_intel_display(state); + const struct intel_cdclk_state *old_cdclk_state; + struct intel_cdclk_state *new_cdclk_state; + int ret; + + if (!intel_any_crtc_enable_changed(state) && + !intel_any_crtc_active_changed(state)) + return 0; + + 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->enabled_pipes = + intel_calc_enabled_pipes(state, old_cdclk_state->enabled_pipes); + + new_cdclk_state->active_pipes = + intel_calc_active_pipes(state, old_cdclk_state->active_pipes); + + ret = intel_atomic_lock_global_state(&new_cdclk_state->base); + if (ret) + return ret; + + if (!old_cdclk_state->active_pipes != !new_cdclk_state->active_pipes) + *need_cdclk_calc = true; + + if (glk_cdclk_audio_wa_needed(display, old_cdclk_state) != + glk_cdclk_audio_wa_needed(display, new_cdclk_state)) + *need_cdclk_calc = true; + + if (dg2_power_well_count(display, old_cdclk_state) != + dg2_power_well_count(display, new_cdclk_state)) + *need_cdclk_calc = true; + + return 0; +} + +static int intel_crtcs_calc_min_cdclk(struct intel_atomic_state *state, + bool *need_cdclk_calc) +{ + const struct intel_crtc_state *old_crtc_state; + const struct intel_crtc_state *new_crtc_state; + struct intel_crtc *crtc; + int ret; + + for_each_oldnew_intel_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state) { + ret = intel_cdclk_update_crtc_min_cdclk(state, crtc, + old_crtc_state->min_cdclk, + new_crtc_state->min_cdclk, + need_cdclk_calc); + if (ret) + return ret; + + ret = intel_cdclk_update_crtc_min_voltage_level(state, crtc, + old_crtc_state->min_voltage_level, + new_crtc_state->min_voltage_level, + need_cdclk_calc); + if (ret) + return ret; + } + + return 0; +} + +int intel_cdclk_state_set_joined_mbus(struct intel_atomic_state *state, bool joined_mbus) +{ + struct intel_cdclk_state *cdclk_state; + + cdclk_state = intel_atomic_get_cdclk_state(state); + if (IS_ERR(cdclk_state)) + return PTR_ERR(cdclk_state); + + cdclk_state->actual.joined_mbus = joined_mbus; + cdclk_state->logical.joined_mbus = joined_mbus; + + return intel_atomic_lock_global_state(&cdclk_state->base); +} + +int intel_cdclk_init(struct intel_display *display) +{ + struct intel_cdclk_state *cdclk_state; + + cdclk_state = kzalloc_obj(*cdclk_state); + if (!cdclk_state) + return -ENOMEM; + + intel_atomic_global_obj_init(display, &display->cdclk.obj, + &cdclk_state->base, &intel_cdclk_funcs); + + return 0; +} + +static bool intel_cdclk_need_serialize(struct intel_display *display, + const struct intel_cdclk_state *old_cdclk_state, + const struct intel_cdclk_state *new_cdclk_state) +{ + /* + * We need to poke hw for DG2, because we notify PCode if + * pipe power well count changes. + */ + return intel_cdclk_changed(&old_cdclk_state->actual, + &new_cdclk_state->actual) || + dg2_power_well_count(display, old_cdclk_state) != + dg2_power_well_count(display, new_cdclk_state); +} + +static int intel_modeset_calc_cdclk(struct intel_atomic_state *state) +{ + struct intel_display *display = to_intel_display(state); + 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); + + ret = intel_cdclk_modeset_calc_cdclk(state); + if (ret) + return ret; + + if (intel_cdclk_need_serialize(display, old_cdclk_state, new_cdclk_state)) { + /* + * 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 (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(display, + &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(display, pipe); + + crtc_state = intel_atomic_get_crtc_state(&state->base, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + if (intel_crtc_needs_modeset(crtc_state)) + pipe = INVALID_PIPE; + } + + if (intel_cdclk_can_crawl_and_squash(display, + &old_cdclk_state->actual, + &new_cdclk_state->actual)) { + drm_dbg_kms(display->drm, + "Can change cdclk via crawling and squashing\n"); + } else if (intel_cdclk_can_squash(display, + &old_cdclk_state->actual, + &new_cdclk_state->actual)) { + drm_dbg_kms(display->drm, + "Can change cdclk via squashing\n"); + } else if (intel_cdclk_can_crawl(display, + &old_cdclk_state->actual, + &new_cdclk_state->actual)) { + drm_dbg_kms(display->drm, + "Can change cdclk via crawling\n"); + } else if (pipe != INVALID_PIPE) { + new_cdclk_state->pipe = pipe; + + drm_dbg_kms(display->drm, + "Can change cdclk cd2x divider with pipe %c active\n", + pipe_name(pipe)); + } else if (intel_cdclk_clock_changed(&old_cdclk_state->actual, + &new_cdclk_state->actual)) { + /* All pipes must be switched off while we change the cdclk. */ + ret = intel_modeset_all_pipes_late(state, "CDCLK change"); + if (ret) + return ret; + + new_cdclk_state->disable_pipes = true; + + drm_dbg_kms(display->drm, + "Modeset required for cdclk change\n"); + } + + if (intel_mdclk_cdclk_ratio(display, &old_cdclk_state->actual) != + intel_mdclk_cdclk_ratio(display, &new_cdclk_state->actual)) { + int ratio = intel_mdclk_cdclk_ratio(display, &new_cdclk_state->actual); + + ret = intel_dbuf_state_set_mdclk_cdclk_ratio(state, ratio); + if (ret) + return ret; + } + + drm_dbg_kms(display->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(display->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; +} + +int intel_cdclk_atomic_check(struct intel_atomic_state *state) +{ + const struct intel_cdclk_state *old_cdclk_state; + struct intel_cdclk_state *new_cdclk_state; + bool need_cdclk_calc = false; + int ret; + + ret = intel_cdclk_modeset_checks(state, &need_cdclk_calc); + if (ret) + return ret; + + ret = intel_crtcs_calc_min_cdclk(state, &need_cdclk_calc); + if (ret) + return ret; + + ret = intel_dbuf_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) { + ret = intel_atomic_lock_global_state(&new_cdclk_state->base); + if (ret) + return ret; + + need_cdclk_calc = true; + } + + if (need_cdclk_calc) { + ret = intel_modeset_calc_cdclk(state); + if (ret) + return ret; + } + + return 0; +} + +void intel_cdclk_update_hw_state(struct intel_display *display) +{ + const struct intel_dbuf_bw_state *dbuf_bw_state = + to_intel_dbuf_bw_state(display->dbuf_bw.obj.state); + struct intel_cdclk_state *cdclk_state = + to_intel_cdclk_state(display->cdclk.obj.state); + struct intel_crtc *crtc; + + cdclk_state->enabled_pipes = 0; + cdclk_state->active_pipes = 0; + + for_each_intel_crtc(display, crtc) { + const struct intel_crtc_state *crtc_state = + to_intel_crtc_state(crtc->base.state); + enum pipe pipe = crtc->pipe; + + if (crtc_state->hw.enable) + cdclk_state->enabled_pipes |= BIT(pipe); + if (crtc_state->hw.active) + cdclk_state->active_pipes |= BIT(pipe); + + cdclk_state->min_cdclk[pipe] = crtc_state->min_cdclk; + cdclk_state->min_voltage_level[pipe] = crtc_state->min_voltage_level; + } + + cdclk_state->dbuf_bw_min_cdclk = intel_dbuf_bw_min_cdclk(display, dbuf_bw_state); +} + +void intel_cdclk_crtc_disable_noatomic(struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + + intel_cdclk_update_hw_state(display); +} + +static int intel_compute_max_dotclk(struct intel_display *display) +{ + int ppc = intel_cdclk_ppc(display, HAS_DOUBLE_WIDE(display)); + int guardband = intel_cdclk_guardband(display); + int max_cdclk_freq = display->cdclk.max_cdclk_freq; + + return ppc * max_cdclk_freq * guardband / 100; +} + +/** + * intel_update_max_cdclk - Determine the maximum support CDCLK frequency + * @display: display instance + * + * 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 intel_display *display) +{ + if (DISPLAY_VER(display) >= 35) { + display->cdclk.max_cdclk_freq = 787200; + } else if (DISPLAY_VERx100(display) >= 3002) { + display->cdclk.max_cdclk_freq = 480000; + } else if (DISPLAY_VER(display) >= 30) { + display->cdclk.max_cdclk_freq = 691200; + } else if (display->platform.jasperlake || display->platform.elkhartlake) { + if (display->cdclk.hw.ref == 24000) + display->cdclk.max_cdclk_freq = 552000; + else + display->cdclk.max_cdclk_freq = 556800; + } else if (DISPLAY_VER(display) >= 11) { + if (display->cdclk.hw.ref == 24000) + display->cdclk.max_cdclk_freq = 648000; + else + display->cdclk.max_cdclk_freq = 652800; + } else if (display->platform.geminilake) { + display->cdclk.max_cdclk_freq = 316800; + } else if (display->platform.broxton) { + display->cdclk.max_cdclk_freq = 624000; + } else if (DISPLAY_VER(display) == 9) { + u32 limit = intel_de_read(display, SKL_DFSM) & SKL_DFSM_CDCLK_LIMIT_MASK; + int max_cdclk, vco; + + vco = display->cdclk.skl_preferred_vco_freq; + drm_WARN_ON(display->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; + + display->cdclk.max_cdclk_freq = skl_calc_cdclk(max_cdclk, vco); + } else if (display->platform.broadwell) { + /* + * 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(display, FUSE_STRAP) & HSW_CDCLK_LIMIT) + display->cdclk.max_cdclk_freq = 450000; + else if (display->platform.broadwell_ulx) + display->cdclk.max_cdclk_freq = 450000; + else if (display->platform.broadwell_ult) + display->cdclk.max_cdclk_freq = 540000; + else + display->cdclk.max_cdclk_freq = 675000; + } else if (display->platform.cherryview) { + display->cdclk.max_cdclk_freq = 320000; + } else if (display->platform.valleyview) { + display->cdclk.max_cdclk_freq = 400000; + } else { + /* otherwise assume cdclk is fixed */ + display->cdclk.max_cdclk_freq = display->cdclk.hw.cdclk; + } + + display->cdclk.max_dotclk_freq = intel_compute_max_dotclk(display); + + drm_dbg(display->drm, "Max CD clock rate: %d kHz\n", + display->cdclk.max_cdclk_freq); + + drm_dbg(display->drm, "Max dotclock rate: %d kHz\n", + display->cdclk.max_dotclk_freq); +} + +/** + * intel_update_cdclk - Determine the current CDCLK frequency + * @display: display instance + * + * Determine the current CDCLK frequency. + */ +void intel_update_cdclk(struct intel_display *display) +{ + intel_cdclk_get_cdclk(display, &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 (display->platform.valleyview || display->platform.cherryview) + intel_de_write(display, GMBUSFREQ_VLV, + DIV_ROUND_UP(display->cdclk.hw.cdclk, 1000)); +} + +static int dg1_rawclk(struct intel_display *display) +{ + /* + * DG1 always uses a 38.4 MHz rawclk. The bspec tells us + * "Program Numerator=2, Denominator=4, Divider=37 decimal." + */ + intel_de_write(display, PCH_RAWCLK_FREQ, + CNP_RAWCLK_DEN(4) | CNP_RAWCLK_DIV(37) | ICP_RAWCLK_NUM(2)); + + return 38400; +} + +static int cnp_rawclk(struct intel_display *display) +{ + int divider, fraction; + u32 rawclk; + + if (intel_de_read(display, 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(display) >= PCH_ICP) + rawclk |= ICP_RAWCLK_NUM(numerator); + } + + intel_de_write(display, PCH_RAWCLK_FREQ, rawclk); + return divider + fraction; +} + +static int pch_rawclk(struct intel_display *display) +{ + return (intel_de_read(display, PCH_RAWCLK_FREQ) & RAWCLK_FREQ_MASK) * 1000; +} + +static int i9xx_hrawclk(struct intel_display *display) +{ + /* hrawclock is 1/4 the FSB frequency */ + return DIV_ROUND_CLOSEST(intel_fsb_freq(display), 4); +} + +/** + * intel_read_rawclk - Determine the current RAWCLK frequency + * @display: display instance + * + * Determine the current RAWCLK frequency. RAWCLK is a fixed + * frequency clock so this needs to done only once. + */ +u32 intel_read_rawclk(struct intel_display *display) +{ + u32 freq; + + if (INTEL_PCH_TYPE(display) >= PCH_MTL) + /* + * 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(display) >= PCH_DG1) + freq = dg1_rawclk(display); + else if (INTEL_PCH_TYPE(display) >= PCH_CNP) + freq = cnp_rawclk(display); + else if (HAS_PCH_SPLIT(display)) + freq = pch_rawclk(display); + else if (display->platform.valleyview || display->platform.cherryview) + freq = vlv_clock_get_hrawclk(display->drm); + else if (DISPLAY_VER(display) >= 3) + freq = i9xx_hrawclk(display); + else + /* no rawclk on other platforms, or no need to know it */ + return 0; + + return freq; +} + +static int i915_cdclk_info_show(struct seq_file *m, void *unused) +{ + struct intel_display *display = m->private; + + seq_printf(m, "Current CD clock frequency: %d kHz\n", display->cdclk.hw.cdclk); + seq_printf(m, "Max CD clock frequency: %d kHz\n", display->cdclk.max_cdclk_freq); + seq_printf(m, "Max pixel clock frequency: %d kHz\n", display->cdclk.max_dotclk_freq); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(i915_cdclk_info); + +void intel_cdclk_debugfs_register(struct intel_display *display) +{ + debugfs_create_file("i915_cdclk_info", 0444, display->drm->debugfs_root, + display, &i915_cdclk_info_fops); +} + +static const struct intel_cdclk_funcs xe3lpd_cdclk_funcs = { + .get_cdclk = bxt_get_cdclk, + .set_cdclk = bxt_set_cdclk, + .modeset_calc_cdclk = bxt_modeset_calc_cdclk, + .calc_voltage_level = xe3lpd_calc_voltage_level, +}; + +static const struct intel_cdclk_funcs rplu_cdclk_funcs = { + .get_cdclk = bxt_get_cdclk, + .set_cdclk = bxt_set_cdclk, + .modeset_calc_cdclk = bxt_modeset_calc_cdclk, + .calc_voltage_level = rplu_calc_voltage_level, +}; + +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 + * @display: display instance + */ +void intel_init_cdclk_hooks(struct intel_display *display) +{ + if (DISPLAY_VER(display) >= 35) { + display->cdclk.funcs = &xe3lpd_cdclk_funcs; + display->cdclk.table = xe3p_lpd_cdclk_table; + } else if (DISPLAY_VER(display) >= 30) { + display->cdclk.funcs = &xe3lpd_cdclk_funcs; + display->cdclk.table = xe3lpd_cdclk_table; + } else if (DISPLAY_VER(display) >= 20) { + display->cdclk.funcs = &rplu_cdclk_funcs; + display->cdclk.table = xe2lpd_cdclk_table; + } else if (DISPLAY_VERx100(display) >= 1401) { + display->cdclk.funcs = &rplu_cdclk_funcs; + display->cdclk.table = xe2hpd_cdclk_table; + } else if (DISPLAY_VER(display) >= 14) { + display->cdclk.funcs = &rplu_cdclk_funcs; + display->cdclk.table = mtl_cdclk_table; + } else if (display->platform.dg2) { + display->cdclk.funcs = &tgl_cdclk_funcs; + display->cdclk.table = dg2_cdclk_table; + } else if (display->platform.alderlake_p) { + /* Wa_22011320316:adl-p[a0] */ + if (intel_display_wa(display, INTEL_DISPLAY_WA_22011320316)) { + display->cdclk.table = adlp_a_step_cdclk_table; + display->cdclk.funcs = &tgl_cdclk_funcs; + } else if (display->platform.alderlake_p_raptorlake_u) { + display->cdclk.table = rplu_cdclk_table; + display->cdclk.funcs = &rplu_cdclk_funcs; + } else { + display->cdclk.table = adlp_cdclk_table; + display->cdclk.funcs = &tgl_cdclk_funcs; + } + } else if (display->platform.rocketlake) { + display->cdclk.funcs = &tgl_cdclk_funcs; + display->cdclk.table = rkl_cdclk_table; + } else if (DISPLAY_VER(display) >= 12) { + display->cdclk.funcs = &tgl_cdclk_funcs; + display->cdclk.table = icl_cdclk_table; + } else if (display->platform.jasperlake || display->platform.elkhartlake) { + display->cdclk.funcs = &ehl_cdclk_funcs; + display->cdclk.table = icl_cdclk_table; + } else if (DISPLAY_VER(display) >= 11) { + display->cdclk.funcs = &icl_cdclk_funcs; + display->cdclk.table = icl_cdclk_table; + } else if (display->platform.geminilake || display->platform.broxton) { + display->cdclk.funcs = &bxt_cdclk_funcs; + if (display->platform.geminilake) + display->cdclk.table = glk_cdclk_table; + else + display->cdclk.table = bxt_cdclk_table; + } else if (DISPLAY_VER(display) == 9) { + display->cdclk.funcs = &skl_cdclk_funcs; + } else if (display->platform.broadwell) { + display->cdclk.funcs = &bdw_cdclk_funcs; + } else if (display->platform.haswell) { + display->cdclk.funcs = &hsw_cdclk_funcs; + } else if (display->platform.cherryview) { + display->cdclk.funcs = &chv_cdclk_funcs; + } else if (display->platform.valleyview) { + display->cdclk.funcs = &vlv_cdclk_funcs; + } else if (display->platform.sandybridge || display->platform.ivybridge) { + display->cdclk.funcs = &fixed_400mhz_cdclk_funcs; + } else if (display->platform.ironlake) { + display->cdclk.funcs = &ilk_cdclk_funcs; + } else if (display->platform.gm45) { + display->cdclk.funcs = &gm45_cdclk_funcs; + } else if (display->platform.g45) { + display->cdclk.funcs = &g33_cdclk_funcs; + } else if (display->platform.i965gm) { + display->cdclk.funcs = &i965gm_cdclk_funcs; + } else if (display->platform.i965g) { + display->cdclk.funcs = &fixed_400mhz_cdclk_funcs; + } else if (display->platform.pineview) { + display->cdclk.funcs = &pnv_cdclk_funcs; + } else if (display->platform.g33) { + display->cdclk.funcs = &g33_cdclk_funcs; + } else if (display->platform.i945gm) { + display->cdclk.funcs = &i945gm_cdclk_funcs; + } else if (display->platform.i945g) { + display->cdclk.funcs = &fixed_400mhz_cdclk_funcs; + } else if (display->platform.i915gm) { + display->cdclk.funcs = &i915gm_cdclk_funcs; + } else if (display->platform.i915g) { + display->cdclk.funcs = &i915g_cdclk_funcs; + } else if (display->platform.i865g) { + display->cdclk.funcs = &i865g_cdclk_funcs; + } else if (display->platform.i85x) { + display->cdclk.funcs = &i85x_cdclk_funcs; + } else if (display->platform.i845g) { + display->cdclk.funcs = &i845g_cdclk_funcs; + } else if (display->platform.i830) { + display->cdclk.funcs = &i830_cdclk_funcs; + } + + if (drm_WARN(display->drm, !display->cdclk.funcs, + "Unknown platform. Assuming i830\n")) + display->cdclk.funcs = &i830_cdclk_funcs; +} + +int intel_cdclk_logical(const struct intel_cdclk_state *cdclk_state) +{ + return cdclk_state->logical.cdclk; +} + +int intel_cdclk_actual(const struct intel_cdclk_state *cdclk_state) +{ + return cdclk_state->actual.cdclk; +} + +int intel_cdclk_actual_voltage_level(const struct intel_cdclk_state *cdclk_state) +{ + return cdclk_state->actual.voltage_level; +} + +int intel_cdclk_min_cdclk(const struct intel_cdclk_state *cdclk_state, enum pipe pipe) +{ + return cdclk_state->min_cdclk[pipe]; +} + +bool intel_cdclk_pmdemand_needs_update(struct intel_atomic_state *state) +{ + const struct intel_cdclk_state *new_cdclk_state, *old_cdclk_state; + + new_cdclk_state = intel_atomic_get_new_cdclk_state(state); + old_cdclk_state = intel_atomic_get_old_cdclk_state(state); + + if (new_cdclk_state && + (new_cdclk_state->actual.cdclk != old_cdclk_state->actual.cdclk || + new_cdclk_state->actual.voltage_level != old_cdclk_state->actual.voltage_level)) + return true; + + return false; +} + +void intel_cdclk_force_min_cdclk(struct intel_cdclk_state *cdclk_state, int force_min_cdclk) +{ + cdclk_state->force_min_cdclk = force_min_cdclk; +} + +void intel_cdclk_read_hw(struct intel_display *display) +{ + struct intel_cdclk_state *cdclk_state; + + cdclk_state = to_intel_cdclk_state(display->cdclk.obj.state); + + intel_update_cdclk(display); + intel_cdclk_dump_config(display, &display->cdclk.hw, "Current CDCLK"); + cdclk_state->actual = display->cdclk.hw; + cdclk_state->logical = display->cdclk.hw; +} + +static int calc_cdclk(const struct intel_crtc_state *crtc_state, int min_cdclk) +{ + struct intel_display *display = to_intel_display(crtc_state); + + if (DISPLAY_VER(display) >= 10 || display->platform.broxton) { + return bxt_calc_cdclk(display, min_cdclk); + } else if (DISPLAY_VER(display) == 9) { + int vco; + + vco = display->cdclk.skl_preferred_vco_freq; + if (vco == 0) + vco = 8100000; + + return skl_calc_cdclk(min_cdclk, vco); + } else if (display->platform.broadwell) { + return bdw_calc_cdclk(min_cdclk); + } else if (display->platform.cherryview || display->platform.valleyview) { + return vlv_calc_cdclk(display, min_cdclk); + } else { + return display->cdclk.max_cdclk_freq; + } +} + +static unsigned int _intel_cdclk_prefill_adj(const struct intel_crtc_state *crtc_state, + int clock, int min_cdclk) +{ + struct intel_display *display = to_intel_display(crtc_state); + int ppc = intel_cdclk_ppc(display, crtc_state->double_wide); + int cdclk = calc_cdclk(crtc_state, min_cdclk); + + return min(0x10000, DIV_ROUND_UP_ULL((u64)clock << 16, ppc * cdclk)); +} + +unsigned int intel_cdclk_prefill_adjustment(const struct intel_crtc_state *crtc_state) +{ + /* FIXME use the actual min_cdclk for the pipe here */ + return intel_cdclk_prefill_adjustment_worst(crtc_state); +} + +unsigned int intel_cdclk_prefill_adjustment_worst(const struct intel_crtc_state *crtc_state) +{ + int clock = crtc_state->hw.pipe_mode.crtc_clock; + int min_cdclk; + + /* + * FIXME could perhaps consider a few more of the factors + * that go the per-crtc min_cdclk. Namely anything that + * only changes during full modesets. + * + * FIXME this assumes 1:1 scaling, but the other _worst() stuff + * assumes max downscaling, so the final result will be + * unrealistically bad. Figure out where the actual maximum value + * lies and use that to compute a more realistic worst case + * estimate... + */ + min_cdclk = _intel_pixel_rate_to_cdclk(crtc_state, clock); + + return _intel_cdclk_prefill_adj(crtc_state, clock, min_cdclk); +} + +int intel_cdclk_min_cdclk_for_prefill(const struct intel_crtc_state *crtc_state, + unsigned int prefill_lines_unadjusted, + unsigned int prefill_lines_available) +{ + struct intel_display *display = to_intel_display(crtc_state); + const struct drm_display_mode *pipe_mode = &crtc_state->hw.pipe_mode; + int ppc = intel_cdclk_ppc(display, crtc_state->double_wide); + + return DIV_ROUND_UP_ULL(mul_u32_u32(pipe_mode->crtc_clock, prefill_lines_unadjusted), + ppc * prefill_lines_available); +} 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..1ff7d078b --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_cdclk.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_CDCLK_H__ +#define __INTEL_CDCLK_H__ + +#include + +enum pipe; +struct intel_atomic_state; +struct intel_cdclk_state; +struct intel_crtc; +struct intel_crtc_state; +struct intel_display; + +struct intel_cdclk_config { + unsigned int cdclk, vco, ref, bypass; + u8 voltage_level; + /* This field is only valid for Xe2LPD and above. */ + bool joined_mbus; +}; + +void intel_cdclk_init_hw(struct intel_display *display); +void intel_cdclk_uninit_hw(struct intel_display *display); +void intel_init_cdclk_hooks(struct intel_display *display); +void intel_update_max_cdclk(struct intel_display *display); +void intel_update_cdclk(struct intel_display *display); +u32 intel_read_rawclk(struct intel_display *display); +bool intel_cdclk_clock_changed(const struct intel_cdclk_config *a, + const struct intel_cdclk_config *b); +int intel_mdclk_cdclk_ratio(struct intel_display *display, + const struct intel_cdclk_config *cdclk_config); +bool intel_cdclk_is_decreasing_later(struct intel_atomic_state *state); +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 intel_display *display, + const struct intel_cdclk_config *cdclk_config, + const char *context); +void intel_cdclk_get_cdclk(struct intel_display *display, + struct intel_cdclk_config *cdclk_config); +int intel_cdclk_atomic_check(struct intel_atomic_state *state); +int intel_cdclk_state_set_joined_mbus(struct intel_atomic_state *state, bool joined_mbus); +struct intel_cdclk_state * +intel_atomic_get_cdclk_state(struct intel_atomic_state *state); +void intel_cdclk_update_hw_state(struct intel_display *display); +void intel_cdclk_crtc_disable_noatomic(struct intel_crtc *crtc); +int intel_cdclk_update_dbuf_bw_min_cdclk(struct intel_atomic_state *state, + int old_min_cdclk, int new_min_cdclk, + bool *need_cdclk_calc); + +#define to_intel_cdclk_state(global_state) \ + container_of_const((global_state), 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_intel_display(state)->cdclk.obj)) +#define intel_atomic_get_new_cdclk_state(state) \ + to_intel_cdclk_state(intel_atomic_get_new_global_obj_state(state, &to_intel_display(state)->cdclk.obj)) + +int intel_cdclk_init(struct intel_display *display); +void intel_cdclk_debugfs_register(struct intel_display *display); + +int intel_cdclk_logical(const struct intel_cdclk_state *cdclk_state); +int intel_cdclk_actual(const struct intel_cdclk_state *cdclk_state); +int intel_cdclk_actual_voltage_level(const struct intel_cdclk_state *cdclk_state); +int intel_cdclk_min_cdclk(const struct intel_cdclk_state *cdclk_state, enum pipe pipe); +bool intel_cdclk_pmdemand_needs_update(struct intel_atomic_state *state); +void intel_cdclk_force_min_cdclk(struct intel_cdclk_state *cdclk_state, int force_min_cdclk); +void intel_cdclk_read_hw(struct intel_display *display); + +unsigned int intel_cdclk_prefill_adjustment(const struct intel_crtc_state *crtc_state); +unsigned int intel_cdclk_prefill_adjustment_worst(const struct intel_crtc_state *crtc_state); +int intel_cdclk_min_cdclk_for_prefill(const struct intel_crtc_state *crtc_state, + unsigned int prefill_lines_unadjusted, + unsigned int prefill_lines_available); + +int intel_crtc_min_cdclk(const struct intel_crtc_state *crtc_state); + +#endif /* __INTEL_CDCLK_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_cmtg.c b/drivers/gpu/drm/i915/display/intel_cmtg.c new file mode 100644 index 000000000..e1fdc6fe9 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_cmtg.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2025 Intel Corporation + */ + +#include +#include + +#include +#include + +#include "intel_cmtg.h" +#include "intel_cmtg_regs.h" +#include "intel_crtc.h" +#include "intel_de.h" +#include "intel_display_device.h" +#include "intel_display_power.h" +#include "intel_display_regs.h" + +/** + * DOC: Common Primary Timing Generator (CMTG) + * + * The CMTG is a timing generator that runs in parallel to transcoders timing + * generators (TG) to provide a synchronization mechanism where CMTG acts as + * primary and transcoders TGs act as secondary to the CMTG. The CMTG outputs + * its TG start and frame sync signals to the transcoders that are configured + * as secondary, which use those signals to synchronize their own timing with + * the CMTG's. + * + * The CMTG can be used only with eDP or MIPI command mode and supports the + * following use cases: + * + * - Dual eDP: The CMTG can be used to keep two eDP TGs in sync when on a + * dual eDP configuration (with or without PSR/PSR2 enabled). + * + * - Single eDP as secondary: It is also possible to use a single eDP + * configuration with the transcoder TG as secondary to the CMTG. That would + * allow a flow that would not require a modeset on the existing eDP when a + * new eDP is added for a dual eDP configuration with CMTG. + * + * - DC6v: In DC6v, the transcoder might be off but the CMTG keeps running to + * maintain frame timings. When exiting DC6v, the transcoder TG then is + * synced back the CMTG. + * + * Currently, the driver does not use the CMTG, but we need to make sure that + * we disable it in case we inherit a display configuration with it enabled. + */ + +/* + * We describe here only the minimum data required to allow us to properly + * disable the CMTG if necessary. + */ +struct intel_cmtg_config { + bool cmtg_a_enable; + /* + * Xe2_LPD adds a second CMTG that can be used for dual eDP async mode. + */ + bool cmtg_b_enable; + bool trans_a_secondary; + bool trans_b_secondary; +}; + +static bool intel_cmtg_has_cmtg_b(struct intel_display *display) +{ + return DISPLAY_VER(display) >= 20; +} + +static bool intel_cmtg_has_clock_sel(struct intel_display *display) +{ + return DISPLAY_VER(display) >= 14; +} + +static void intel_cmtg_dump_config(struct intel_display *display, + struct intel_cmtg_config *cmtg_config) +{ + drm_dbg_kms(display->drm, + "CMTG readout: CMTG A: %s, CMTG B: %s, Transcoder A secondary: %s, Transcoder B secondary: %s\n", + str_enabled_disabled(cmtg_config->cmtg_a_enable), + intel_cmtg_has_cmtg_b(display) ? str_enabled_disabled(cmtg_config->cmtg_b_enable) : "n/a", + str_yes_no(cmtg_config->trans_a_secondary), + str_yes_no(cmtg_config->trans_b_secondary)); +} + +static bool intel_cmtg_transcoder_is_secondary(struct intel_display *display, + enum transcoder trans) +{ + enum intel_display_power_domain power_domain; + u32 val = 0; + + if (!HAS_TRANSCODER(display, trans)) + return false; + + power_domain = POWER_DOMAIN_TRANSCODER(trans); + + with_intel_display_power_if_enabled(display, power_domain) + val = intel_de_read(display, TRANS_DDI_FUNC_CTL2(display, trans)); + + return val & CMTG_SECONDARY_MODE; +} + +static void intel_cmtg_get_config(struct intel_display *display, + struct intel_cmtg_config *cmtg_config) +{ + u32 val; + + val = intel_de_read(display, TRANS_CMTG_CTL_A); + cmtg_config->cmtg_a_enable = val & CMTG_ENABLE; + + if (intel_cmtg_has_cmtg_b(display)) { + val = intel_de_read(display, TRANS_CMTG_CTL_B); + cmtg_config->cmtg_b_enable = val & CMTG_ENABLE; + } + + cmtg_config->trans_a_secondary = intel_cmtg_transcoder_is_secondary(display, TRANSCODER_A); + cmtg_config->trans_b_secondary = intel_cmtg_transcoder_is_secondary(display, TRANSCODER_B); +} + +static bool intel_cmtg_disable_requires_modeset(struct intel_display *display, + struct intel_cmtg_config *cmtg_config) +{ + if (DISPLAY_VER(display) >= 20) + return false; + + return cmtg_config->trans_a_secondary || cmtg_config->trans_b_secondary; +} + +static void intel_cmtg_disable(struct intel_display *display, + struct intel_cmtg_config *cmtg_config) +{ + u32 clk_sel_clr = 0; + u32 clk_sel_set = 0; + + if (cmtg_config->trans_a_secondary) + intel_de_rmw(display, TRANS_DDI_FUNC_CTL2(display, TRANSCODER_A), + CMTG_SECONDARY_MODE, 0); + + if (cmtg_config->trans_b_secondary) + intel_de_rmw(display, TRANS_DDI_FUNC_CTL2(display, TRANSCODER_B), + CMTG_SECONDARY_MODE, 0); + + if (cmtg_config->cmtg_a_enable) { + drm_dbg_kms(display->drm, "Disabling CMTG A\n"); + intel_de_rmw(display, TRANS_CMTG_CTL_A, CMTG_ENABLE, 0); + clk_sel_clr |= CMTG_CLK_SEL_A_MASK; + clk_sel_set |= CMTG_CLK_SEL_A_DISABLED; + } + + if (cmtg_config->cmtg_b_enable) { + drm_dbg_kms(display->drm, "Disabling CMTG B\n"); + intel_de_rmw(display, TRANS_CMTG_CTL_B, CMTG_ENABLE, 0); + clk_sel_clr |= CMTG_CLK_SEL_B_MASK; + clk_sel_set |= CMTG_CLK_SEL_B_DISABLED; + } + + if (intel_cmtg_has_clock_sel(display) && clk_sel_clr) + intel_de_rmw(display, CMTG_CLK_SEL, clk_sel_clr, clk_sel_set); +} + +/* + * Read out CMTG configuration and, on platforms that allow disabling it without + * a modeset, do it. + * + * This function must be called before any port PLL is disabled in the general + * sanitization process, because we need whatever port PLL that is providing the + * clock for CMTG to be on before accessing CMTG registers. + */ +void intel_cmtg_sanitize(struct intel_display *display) +{ + struct intel_cmtg_config cmtg_config = {}; + + if (!HAS_CMTG(display)) + return; + + intel_cmtg_get_config(display, &cmtg_config); + intel_cmtg_dump_config(display, &cmtg_config); + + /* + * FIXME: The driver is not prepared to handle cases where a modeset is + * required for disabling the CMTG: we need a proper way of tracking + * CMTG state and do the right syncronization with respect to triggering + * the modeset as part of the disable sequence. + */ + if (intel_cmtg_disable_requires_modeset(display, &cmtg_config)) + return; + + intel_cmtg_disable(display, &cmtg_config); +} diff --git a/drivers/gpu/drm/i915/display/intel_cmtg.h b/drivers/gpu/drm/i915/display/intel_cmtg.h new file mode 100644 index 000000000..ba62199ad --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_cmtg.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (C) 2025 Intel Corporation + */ + +#ifndef __INTEL_CMTG_H__ +#define __INTEL_CMTG_H__ + +struct intel_display; + +void intel_cmtg_sanitize(struct intel_display *display); + +#endif /* __INTEL_CMTG_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_cmtg_regs.h b/drivers/gpu/drm/i915/display/intel_cmtg_regs.h new file mode 100644 index 000000000..945a35578 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_cmtg_regs.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (C) 2025 Intel Corporation + */ + +#ifndef __INTEL_CMTG_REGS_H__ +#define __INTEL_CMTG_REGS_H__ + +#include "intel_display_reg_defs.h" + +#define CMTG_CLK_SEL _MMIO(0x46160) +#define CMTG_CLK_SEL_A_MASK REG_GENMASK(31, 29) +#define CMTG_CLK_SEL_A_DISABLED REG_FIELD_PREP(CMTG_CLK_SEL_A_MASK, 0) +#define CMTG_CLK_SEL_B_MASK REG_GENMASK(15, 13) +#define CMTG_CLK_SEL_B_DISABLED REG_FIELD_PREP(CMTG_CLK_SEL_B_MASK, 0) + +#define TRANS_CMTG_CTL_A _MMIO(0x6fa88) +#define TRANS_CMTG_CTL_B _MMIO(0x6fb88) +#define CMTG_ENABLE REG_BIT(31) + +#endif /* __INTEL_CMTG_REGS_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..7ef870cd9 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_color.c @@ -0,0 +1,4389 @@ +/* + * 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 + +#include "i9xx_plane_regs.h" +#include "intel_color.h" +#include "intel_color_regs.h" +#include "intel_de.h" +#include "intel_display_types.h" +#include "intel_display_utils.h" +#include "intel_dsb.h" +#include "intel_vrr.h" +#include "skl_universal_plane.h" +#include "skl_universal_plane_regs.h" + +struct intel_color_funcs { + int (*color_check)(struct intel_atomic_state *state, + struct intel_crtc *crtc); + /* + * 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)(struct intel_dsb *dsb, + 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)(struct intel_dsb *dsb, + const struct intel_crtc_state *crtc_state); + /* + * Perform any extra tasks needed after all the + * double buffered registers have been latched. + */ + void (*color_post_update)(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); + /* + * Read out the LUTs from the hardware into the software state. + * Used by eg. the hardware state checker. + */ + void (*read_luts)(struct intel_crtc_state *crtc_state); + /* + * Compare the LUTs + */ + bool (*lut_equal)(const struct intel_crtc_state *crtc_state, + const struct drm_property_blob *blob1, + const struct drm_property_blob *blob2, + bool is_pre_csc_lut); + /* + * Read out the CSCs (if any) from the hardware into the + * software state. Used by eg. the hardware state checker. + */ + void (*read_csc)(struct intel_crtc_state *crtc_state); + /* + * Read config other than LUTs and CSCs, before them. Optional. + */ + void (*get_config)(struct intel_crtc_state *crtc_state); + + /* Plane CSC*/ + void (*load_plane_csc_matrix)(struct intel_dsb *dsb, + const struct intel_plane_state *plane_state); + + /* Plane Pre/Post CSC */ + void (*load_plane_luts)(struct intel_dsb *dsb, + const struct intel_plane_state *plane_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_1_0 0x7800 +#define ILK_CSC_COEFF_LIMITED_RANGE ((235 - 16) << (12 - 8)) /* exponent 0 */ +#define ILK_CSC_POSTOFF_LIMITED_RANGE (16 << (12 - 8)) + +static const struct intel_csc_matrix ilk_csc_matrix_identity = { + .preoff = {}, + .coeff = { + ILK_CSC_COEFF_1_0, 0, 0, + 0, ILK_CSC_COEFF_1_0, 0, + 0, 0, ILK_CSC_COEFF_1_0, + }, + .postoff = {}, +}; + +/* Full range RGB -> limited range RGB matrix */ +static const struct intel_csc_matrix ilk_csc_matrix_limited_range = { + .preoff = {}, + .coeff = { + ILK_CSC_COEFF_LIMITED_RANGE, 0, 0, + 0, ILK_CSC_COEFF_LIMITED_RANGE, 0, + 0, 0, ILK_CSC_COEFF_LIMITED_RANGE, + }, + .postoff = { + ILK_CSC_POSTOFF_LIMITED_RANGE, + ILK_CSC_POSTOFF_LIMITED_RANGE, + ILK_CSC_POSTOFF_LIMITED_RANGE, + }, +}; + +/* BT.709 full range RGB -> limited range YCbCr matrix */ +static const struct intel_csc_matrix ilk_csc_matrix_rgb_to_ycbcr = { + .preoff = {}, + .coeff = { + 0x1e08, 0x9cc0, 0xb528, + 0x2ba8, 0x09d8, 0x37e8, + 0xbce8, 0x9ad8, 0x1e08, + }, + .postoff = { + 0x0800, 0x0100, 0x0800, + }, +}; + +static void intel_csc_clear(struct intel_csc_matrix *csc) +{ + memset(csc, 0, sizeof(*csc)); +} + +static bool lut_is_legacy(const struct drm_property_blob *lut) +{ + return lut && drm_color_lut_size(lut) == LEGACY_LUT_LENGTH; +} + +/* + * 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_dsb *dsb, + struct intel_crtc *crtc, + const struct intel_csc_matrix *csc) +{ + struct intel_display *display = to_intel_display(crtc->base.dev); + enum pipe pipe = crtc->pipe; + + intel_de_write_dsb(display, dsb, PIPE_CSC_PREOFF_HI(pipe), + csc->preoff[0]); + intel_de_write_dsb(display, dsb, PIPE_CSC_PREOFF_ME(pipe), + csc->preoff[1]); + intel_de_write_dsb(display, dsb, PIPE_CSC_PREOFF_LO(pipe), + csc->preoff[2]); + + intel_de_write_dsb(display, dsb, PIPE_CSC_COEFF_RY_GY(pipe), + csc->coeff[0] << 16 | csc->coeff[1]); + intel_de_write_dsb(display, dsb, PIPE_CSC_COEFF_BY(pipe), + csc->coeff[2] << 16); + + intel_de_write_dsb(display, dsb, PIPE_CSC_COEFF_RU_GU(pipe), + csc->coeff[3] << 16 | csc->coeff[4]); + intel_de_write_dsb(display, dsb, PIPE_CSC_COEFF_BU(pipe), + csc->coeff[5] << 16); + + intel_de_write_dsb(display, dsb, PIPE_CSC_COEFF_RV_GV(pipe), + csc->coeff[6] << 16 | csc->coeff[7]); + intel_de_write_dsb(display, dsb, PIPE_CSC_COEFF_BV(pipe), + csc->coeff[8] << 16); + + if (DISPLAY_VER(display) < 7) + return; + + intel_de_write_dsb(display, dsb, PIPE_CSC_POSTOFF_HI(pipe), + csc->postoff[0]); + intel_de_write_dsb(display, dsb, PIPE_CSC_POSTOFF_ME(pipe), + csc->postoff[1]); + intel_de_write_dsb(display, dsb, PIPE_CSC_POSTOFF_LO(pipe), + csc->postoff[2]); +} + +static void ilk_read_pipe_csc(struct intel_crtc *crtc, + struct intel_csc_matrix *csc) +{ + struct intel_display *display = to_intel_display(crtc); + enum pipe pipe = crtc->pipe; + u32 tmp; + + csc->preoff[0] = intel_de_read_fw(display, PIPE_CSC_PREOFF_HI(pipe)); + csc->preoff[1] = intel_de_read_fw(display, PIPE_CSC_PREOFF_ME(pipe)); + csc->preoff[2] = intel_de_read_fw(display, PIPE_CSC_PREOFF_LO(pipe)); + + tmp = intel_de_read_fw(display, PIPE_CSC_COEFF_RY_GY(pipe)); + csc->coeff[0] = tmp >> 16; + csc->coeff[1] = tmp & 0xffff; + tmp = intel_de_read_fw(display, PIPE_CSC_COEFF_BY(pipe)); + csc->coeff[2] = tmp >> 16; + + tmp = intel_de_read_fw(display, PIPE_CSC_COEFF_RU_GU(pipe)); + csc->coeff[3] = tmp >> 16; + csc->coeff[4] = tmp & 0xffff; + tmp = intel_de_read_fw(display, PIPE_CSC_COEFF_BU(pipe)); + csc->coeff[5] = tmp >> 16; + + tmp = intel_de_read_fw(display, PIPE_CSC_COEFF_RV_GV(pipe)); + csc->coeff[6] = tmp >> 16; + csc->coeff[7] = tmp & 0xffff; + tmp = intel_de_read_fw(display, PIPE_CSC_COEFF_BV(pipe)); + csc->coeff[8] = tmp >> 16; + + if (DISPLAY_VER(display) < 7) + return; + + csc->postoff[0] = intel_de_read_fw(display, PIPE_CSC_POSTOFF_HI(pipe)); + csc->postoff[1] = intel_de_read_fw(display, PIPE_CSC_POSTOFF_ME(pipe)); + csc->postoff[2] = intel_de_read_fw(display, PIPE_CSC_POSTOFF_LO(pipe)); +} + +static void ilk_read_csc(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + if (crtc_state->csc_enable) + ilk_read_pipe_csc(crtc, &crtc_state->csc); +} + +static void skl_read_csc(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + /* + * Display WA #1184: skl,glk + * Wa_1406463849: icl + * + * Danger! On SKL-ICL *reads* from the CSC coeff/offset registers + * will disarm an already armed CSC double buffer update. + * So this must not be called while armed. Fortunately the state checker + * readout happens only after the update has been already been latched. + * + * On earlier and later platforms only writes to said registers will + * disarm the update. This is considered normal behavior and also + * happens with various other hardware units. + */ + if (crtc_state->csc_enable) + ilk_read_pipe_csc(crtc, &crtc_state->csc); +} + +static void icl_update_output_csc(struct intel_dsb *dsb, + struct intel_crtc *crtc, + const struct intel_csc_matrix *csc) +{ + struct intel_display *display = to_intel_display(crtc->base.dev); + enum pipe pipe = crtc->pipe; + + intel_de_write_dsb(display, dsb, PIPE_CSC_OUTPUT_PREOFF_HI(pipe), + csc->preoff[0]); + intel_de_write_dsb(display, dsb, PIPE_CSC_OUTPUT_PREOFF_ME(pipe), + csc->preoff[1]); + intel_de_write_dsb(display, dsb, PIPE_CSC_OUTPUT_PREOFF_LO(pipe), + csc->preoff[2]); + + intel_de_write_dsb(display, dsb, PIPE_CSC_OUTPUT_COEFF_RY_GY(pipe), + csc->coeff[0] << 16 | csc->coeff[1]); + intel_de_write_dsb(display, dsb, PIPE_CSC_OUTPUT_COEFF_BY(pipe), + csc->coeff[2] << 16); + + intel_de_write_dsb(display, dsb, PIPE_CSC_OUTPUT_COEFF_RU_GU(pipe), + csc->coeff[3] << 16 | csc->coeff[4]); + intel_de_write_dsb(display, dsb, PIPE_CSC_OUTPUT_COEFF_BU(pipe), + csc->coeff[5] << 16); + + intel_de_write_dsb(display, dsb, PIPE_CSC_OUTPUT_COEFF_RV_GV(pipe), + csc->coeff[6] << 16 | csc->coeff[7]); + intel_de_write_dsb(display, dsb, PIPE_CSC_OUTPUT_COEFF_BV(pipe), + csc->coeff[8] << 16); + + intel_de_write_dsb(display, dsb, PIPE_CSC_OUTPUT_POSTOFF_HI(pipe), + csc->postoff[0]); + intel_de_write_dsb(display, dsb, PIPE_CSC_OUTPUT_POSTOFF_ME(pipe), + csc->postoff[1]); + intel_de_write_dsb(display, dsb, PIPE_CSC_OUTPUT_POSTOFF_LO(pipe), + csc->postoff[2]); +} + +static void icl_read_output_csc(struct intel_crtc *crtc, + struct intel_csc_matrix *csc) +{ + struct intel_display *display = to_intel_display(crtc); + enum pipe pipe = crtc->pipe; + u32 tmp; + + csc->preoff[0] = intel_de_read_fw(display, PIPE_CSC_OUTPUT_PREOFF_HI(pipe)); + csc->preoff[1] = intel_de_read_fw(display, PIPE_CSC_OUTPUT_PREOFF_ME(pipe)); + csc->preoff[2] = intel_de_read_fw(display, PIPE_CSC_OUTPUT_PREOFF_LO(pipe)); + + tmp = intel_de_read_fw(display, PIPE_CSC_OUTPUT_COEFF_RY_GY(pipe)); + csc->coeff[0] = tmp >> 16; + csc->coeff[1] = tmp & 0xffff; + tmp = intel_de_read_fw(display, PIPE_CSC_OUTPUT_COEFF_BY(pipe)); + csc->coeff[2] = tmp >> 16; + + tmp = intel_de_read_fw(display, PIPE_CSC_OUTPUT_COEFF_RU_GU(pipe)); + csc->coeff[3] = tmp >> 16; + csc->coeff[4] = tmp & 0xffff; + tmp = intel_de_read_fw(display, PIPE_CSC_OUTPUT_COEFF_BU(pipe)); + csc->coeff[5] = tmp >> 16; + + tmp = intel_de_read_fw(display, PIPE_CSC_OUTPUT_COEFF_RV_GV(pipe)); + csc->coeff[6] = tmp >> 16; + csc->coeff[7] = tmp & 0xffff; + tmp = intel_de_read_fw(display, PIPE_CSC_OUTPUT_COEFF_BV(pipe)); + csc->coeff[8] = tmp >> 16; + + csc->postoff[0] = intel_de_read_fw(display, PIPE_CSC_OUTPUT_POSTOFF_HI(pipe)); + csc->postoff[1] = intel_de_read_fw(display, PIPE_CSC_OUTPUT_POSTOFF_ME(pipe)); + csc->postoff[2] = intel_de_read_fw(display, PIPE_CSC_OUTPUT_POSTOFF_LO(pipe)); +} + +static void icl_read_csc(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + /* + * Wa_1406463849: icl + * + * See skl_read_csc() + */ + if (crtc_state->csc_mode & ICL_CSC_ENABLE) + ilk_read_pipe_csc(crtc, &crtc_state->csc); + + if (crtc_state->csc_mode & ICL_OUTPUT_CSC_ENABLE) + icl_read_output_csc(crtc, &crtc_state->output_csc); +} + +static bool ilk_limited_range(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + /* icl+ have dedicated output CSC */ + if (DISPLAY_VER(display) >= 11) + return false; + + /* pre-hsw have TRANSCONF_COLOR_RANGE_SELECT */ + if (DISPLAY_VER(display) < 7 || display->platform.ivybridge) + return false; + + return crtc_state->limited_color_range; +} + +static bool ilk_lut_limited_range(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + if (!ilk_limited_range(crtc_state)) + return false; + + if (crtc_state->c8_planes) + return false; + + if (DISPLAY_VER(display) == 10) + return crtc_state->hw.gamma_lut; + else + return crtc_state->hw.gamma_lut && + (crtc_state->hw.degamma_lut || crtc_state->hw.ctm); +} + +static bool ilk_csc_limited_range(const struct intel_crtc_state *crtc_state) +{ + if (!ilk_limited_range(crtc_state)) + return false; + + return !ilk_lut_limited_range(crtc_state); +} + +static void ilk_csc_copy(struct intel_display *display, + struct intel_csc_matrix *dst, + const struct intel_csc_matrix *src) +{ + *dst = *src; + + if (DISPLAY_VER(display) < 7) + memset(dst->postoff, 0, sizeof(dst->postoff)); +} + +static void ilk_csc_convert_ctm(const struct intel_crtc_state *crtc_state, + struct intel_csc_matrix *csc, + bool limited_color_range) +{ + struct intel_display *display = to_intel_display(crtc_state); + const struct drm_color_ctm *ctm = crtc_state->hw.ctm->data; + const u64 *input; + u64 temp[9]; + int i; + + /* for preoff/postoff */ + if (limited_color_range) + ilk_csc_copy(display, csc, &ilk_csc_matrix_limited_range); + else + ilk_csc_copy(display, csc, &ilk_csc_matrix_identity); + + if (limited_color_range) + 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); + + csc->coeff[i] = 0; + + /* sign bit */ + if (CTM_COEFF_NEGATIVE(input[i])) + csc->coeff[i] |= 1 << 15; + + if (abs_coeff < CTM_COEFF_0_125) + csc->coeff[i] |= (3 << 12) | + ILK_CSC_COEFF_FP(abs_coeff, 12); + else if (abs_coeff < CTM_COEFF_0_25) + csc->coeff[i] |= (2 << 12) | + ILK_CSC_COEFF_FP(abs_coeff, 11); + else if (abs_coeff < CTM_COEFF_0_5) + csc->coeff[i] |= (1 << 12) | + ILK_CSC_COEFF_FP(abs_coeff, 10); + else if (abs_coeff < CTM_COEFF_1_0) + csc->coeff[i] |= ILK_CSC_COEFF_FP(abs_coeff, 9); + else if (abs_coeff < CTM_COEFF_2_0) + csc->coeff[i] |= (7 << 12) | + ILK_CSC_COEFF_FP(abs_coeff, 8); + else + csc->coeff[i] |= (6 << 12) | + ILK_CSC_COEFF_FP(abs_coeff, 7); + } +} + +static void ilk_assign_csc(struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + bool limited_color_range = ilk_csc_limited_range(crtc_state); + + if (crtc_state->hw.ctm) { + drm_WARN_ON(display->drm, !crtc_state->csc_enable); + + ilk_csc_convert_ctm(crtc_state, &crtc_state->csc, limited_color_range); + } else if (crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB) { + drm_WARN_ON(display->drm, !crtc_state->csc_enable); + + ilk_csc_copy(display, &crtc_state->csc, &ilk_csc_matrix_rgb_to_ycbcr); + } else if (limited_color_range) { + drm_WARN_ON(display->drm, !crtc_state->csc_enable); + + ilk_csc_copy(display, &crtc_state->csc, &ilk_csc_matrix_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(display->drm, !display->platform.geminilake); + + ilk_csc_copy(display, &crtc_state->csc, &ilk_csc_matrix_identity); + } else { + intel_csc_clear(&crtc_state->csc); + } +} + +static void ilk_load_csc_matrix(struct intel_dsb *dsb, + const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + if (crtc_state->csc_enable) + ilk_update_pipe_csc(dsb, crtc, &crtc_state->csc); +} + +static void icl_assign_csc(struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + if (crtc_state->hw.ctm) { + drm_WARN_ON(display->drm, (crtc_state->csc_mode & ICL_CSC_ENABLE) == 0); + + ilk_csc_convert_ctm(crtc_state, &crtc_state->csc, false); + } else { + drm_WARN_ON(display->drm, (crtc_state->csc_mode & ICL_CSC_ENABLE) != 0); + + intel_csc_clear(&crtc_state->csc); + } + + if (crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB) { + drm_WARN_ON(display->drm, (crtc_state->csc_mode & ICL_OUTPUT_CSC_ENABLE) == 0); + + ilk_csc_copy(display, &crtc_state->output_csc, &ilk_csc_matrix_rgb_to_ycbcr); + } else if (crtc_state->limited_color_range) { + drm_WARN_ON(display->drm, (crtc_state->csc_mode & ICL_OUTPUT_CSC_ENABLE) == 0); + + ilk_csc_copy(display, &crtc_state->output_csc, &ilk_csc_matrix_limited_range); + } else { + drm_WARN_ON(display->drm, (crtc_state->csc_mode & ICL_OUTPUT_CSC_ENABLE) != 0); + + intel_csc_clear(&crtc_state->output_csc); + } +} + +static void icl_load_csc_matrix(struct intel_dsb *dsb, + const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + if (crtc_state->csc_mode & ICL_CSC_ENABLE) + ilk_update_pipe_csc(dsb, crtc, &crtc_state->csc); + + if (crtc_state->csc_mode & ICL_OUTPUT_CSC_ENABLE) + icl_update_output_csc(dsb, crtc, &crtc_state->output_csc); +} + +static u16 ctm_to_twos_complement(u64 coeff, int int_bits, int frac_bits) +{ + s64 c = CTM_COEFF_ABS(coeff); + + /* leave an extra bit for rounding */ + c >>= 32 - frac_bits - 1; + + /* round and drop the extra bit */ + c = (c + 1) >> 1; + + if (CTM_COEFF_NEGATIVE(coeff)) + c = -c; + + int_bits = max(int_bits, 1); + + c = clamp(c, -(s64)BIT(int_bits + frac_bits - 1), + (s64)(BIT(int_bits + frac_bits - 1) - 1)); + + return c & (BIT(int_bits + frac_bits) - 1); +} + +/* + * VLV/CHV Wide Gamut Color Correction (WGC) CSC + * |r| | c0 c1 c2 | |r| + * |g| = | c3 c4 c5 | x |g| + * |b| | c6 c7 c8 | |b| + * + * Coefficients are two's complement s2.10. + */ +static void vlv_wgc_csc_convert_ctm(const struct intel_crtc_state *crtc_state, + struct intel_csc_matrix *csc) +{ + const struct drm_color_ctm *ctm = crtc_state->hw.ctm->data; + int i; + + for (i = 0; i < 9; i++) + csc->coeff[i] = ctm_to_twos_complement(ctm->matrix[i], 2, 10); +} + +static void vlv_load_wgc_csc(struct intel_crtc *crtc, + const struct intel_csc_matrix *csc) +{ + struct intel_display *display = to_intel_display(crtc); + enum pipe pipe = crtc->pipe; + + intel_de_write_fw(display, PIPE_WGC_C01_C00(display, pipe), + csc->coeff[1] << 16 | csc->coeff[0]); + intel_de_write_fw(display, PIPE_WGC_C02(display, pipe), + csc->coeff[2]); + + intel_de_write_fw(display, PIPE_WGC_C11_C10(display, pipe), + csc->coeff[4] << 16 | csc->coeff[3]); + intel_de_write_fw(display, PIPE_WGC_C12(display, pipe), + csc->coeff[5]); + + intel_de_write_fw(display, PIPE_WGC_C21_C20(display, pipe), + csc->coeff[7] << 16 | csc->coeff[6]); + intel_de_write_fw(display, PIPE_WGC_C22(display, pipe), + csc->coeff[8]); +} + +static void vlv_read_wgc_csc(struct intel_crtc *crtc, + struct intel_csc_matrix *csc) +{ + struct intel_display *display = to_intel_display(crtc); + enum pipe pipe = crtc->pipe; + u32 tmp; + + tmp = intel_de_read_fw(display, PIPE_WGC_C01_C00(display, pipe)); + csc->coeff[0] = tmp & 0xffff; + csc->coeff[1] = tmp >> 16; + + tmp = intel_de_read_fw(display, PIPE_WGC_C02(display, pipe)); + csc->coeff[2] = tmp & 0xffff; + + tmp = intel_de_read_fw(display, PIPE_WGC_C11_C10(display, pipe)); + csc->coeff[3] = tmp & 0xffff; + csc->coeff[4] = tmp >> 16; + + tmp = intel_de_read_fw(display, PIPE_WGC_C12(display, pipe)); + csc->coeff[5] = tmp & 0xffff; + + tmp = intel_de_read_fw(display, PIPE_WGC_C21_C20(display, pipe)); + csc->coeff[6] = tmp & 0xffff; + csc->coeff[7] = tmp >> 16; + + tmp = intel_de_read_fw(display, PIPE_WGC_C22(display, pipe)); + csc->coeff[8] = tmp & 0xffff; +} + +static void vlv_read_csc(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + if (crtc_state->wgc_enable) + vlv_read_wgc_csc(crtc, &crtc_state->csc); +} + +static void vlv_assign_csc(struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + if (crtc_state->hw.ctm) { + drm_WARN_ON(display->drm, !crtc_state->wgc_enable); + + vlv_wgc_csc_convert_ctm(crtc_state, &crtc_state->csc); + } else { + drm_WARN_ON(display->drm, crtc_state->wgc_enable); + + intel_csc_clear(&crtc_state->csc); + } +} + +/* + * CHV Color Gamut Mapping (CGM) CSC + * |r| | c0 c1 c2 | |r| + * |g| = | c3 c4 c5 | x |g| + * |b| | c6 c7 c8 | |b| + * + * Coefficients are two's complement s4.12. + */ +static void chv_cgm_csc_convert_ctm(const struct intel_crtc_state *crtc_state, + struct intel_csc_matrix *csc) +{ + const struct drm_color_ctm *ctm = crtc_state->hw.ctm->data; + int i; + + for (i = 0; i < 9; i++) + csc->coeff[i] = ctm_to_twos_complement(ctm->matrix[i], 4, 12); +} + +#define CHV_CGM_CSC_COEFF_1_0 (1 << 12) + +static const struct intel_csc_matrix chv_cgm_csc_matrix_identity = { + .coeff = { + CHV_CGM_CSC_COEFF_1_0, 0, 0, + 0, CHV_CGM_CSC_COEFF_1_0, 0, + 0, 0, CHV_CGM_CSC_COEFF_1_0, + }, +}; + +static void chv_load_cgm_csc(struct intel_crtc *crtc, + const struct intel_csc_matrix *csc) +{ + struct intel_display *display = to_intel_display(crtc); + enum pipe pipe = crtc->pipe; + + intel_de_write_fw(display, CGM_PIPE_CSC_COEFF01(pipe), + csc->coeff[1] << 16 | csc->coeff[0]); + intel_de_write_fw(display, CGM_PIPE_CSC_COEFF23(pipe), + csc->coeff[3] << 16 | csc->coeff[2]); + intel_de_write_fw(display, CGM_PIPE_CSC_COEFF45(pipe), + csc->coeff[5] << 16 | csc->coeff[4]); + intel_de_write_fw(display, CGM_PIPE_CSC_COEFF67(pipe), + csc->coeff[7] << 16 | csc->coeff[6]); + intel_de_write_fw(display, CGM_PIPE_CSC_COEFF8(pipe), + csc->coeff[8]); +} + +static void chv_read_cgm_csc(struct intel_crtc *crtc, + struct intel_csc_matrix *csc) +{ + struct intel_display *display = to_intel_display(crtc); + enum pipe pipe = crtc->pipe; + u32 tmp; + + tmp = intel_de_read_fw(display, CGM_PIPE_CSC_COEFF01(pipe)); + csc->coeff[0] = tmp & 0xffff; + csc->coeff[1] = tmp >> 16; + + tmp = intel_de_read_fw(display, CGM_PIPE_CSC_COEFF23(pipe)); + csc->coeff[2] = tmp & 0xffff; + csc->coeff[3] = tmp >> 16; + + tmp = intel_de_read_fw(display, CGM_PIPE_CSC_COEFF45(pipe)); + csc->coeff[4] = tmp & 0xffff; + csc->coeff[5] = tmp >> 16; + + tmp = intel_de_read_fw(display, CGM_PIPE_CSC_COEFF67(pipe)); + csc->coeff[6] = tmp & 0xffff; + csc->coeff[7] = tmp >> 16; + + tmp = intel_de_read_fw(display, CGM_PIPE_CSC_COEFF8(pipe)); + csc->coeff[8] = tmp & 0xffff; +} + +static void chv_read_csc(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_CSC) + chv_read_cgm_csc(crtc, &crtc_state->csc); +} + +static void chv_assign_csc(struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + drm_WARN_ON(display->drm, crtc_state->wgc_enable); + + if (crtc_state->hw.ctm) { + drm_WARN_ON(display->drm, (crtc_state->cgm_mode & CGM_PIPE_MODE_CSC) == 0); + + chv_cgm_csc_convert_ctm(crtc_state, &crtc_state->csc); + } else { + drm_WARN_ON(display->drm, (crtc_state->cgm_mode & CGM_PIPE_MODE_CSC) == 0); + + crtc_state->csc = chv_cgm_csc_matrix_identity; + } +} + +/* convert hw value with given bit_precision to lut property val */ +static u32 intel_color_lut_pack(u32 val, int bit_precision) +{ + if (bit_precision > 16) + return DIV_ROUND_CLOSEST_ULL(mul_u32_u32(val, (1 << 16) - 1), + (1 << bit_precision) - 1); + else + return DIV_ROUND_CLOSEST(val * ((1 << 16) - 1), + (1 << bit_precision) - 1); +} + +static u32 i9xx_lut_8(const struct drm_color_lut *color) +{ + return REG_FIELD_PREP(PALETTE_RED_MASK, drm_color_lut_extract(color->red, 8)) | + REG_FIELD_PREP(PALETTE_GREEN_MASK, drm_color_lut_extract(color->green, 8)) | + REG_FIELD_PREP(PALETTE_BLUE_MASK, 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(PALETTE_RED_MASK, val), 8); + entry->green = intel_color_lut_pack(REG_FIELD_GET(PALETTE_GREEN_MASK, val), 8); + entry->blue = intel_color_lut_pack(REG_FIELD_GET(PALETTE_BLUE_MASK, val), 8); +} + +/* i8xx/i9xx+ 10bit slope format "even DW" (low 8 bits) */ +static u32 _i9xx_lut_10_ldw(u16 a) +{ + return drm_color_lut_extract(a, 10) & 0xff; +} + +static u32 i9xx_lut_10_ldw(const struct drm_color_lut *color) +{ + return REG_FIELD_PREP(PALETTE_RED_MASK, _i9xx_lut_10_ldw(color[0].red)) | + REG_FIELD_PREP(PALETTE_GREEN_MASK, _i9xx_lut_10_ldw(color[0].green)) | + REG_FIELD_PREP(PALETTE_BLUE_MASK, _i9xx_lut_10_ldw(color[0].blue)); +} + +/* i8xx/i9xx+ 10bit slope format "odd DW" (high 2 bits + slope) */ +static u32 _i9xx_lut_10_udw(u16 a, u16 b) +{ + unsigned int mantissa, exponent; + + a = drm_color_lut_extract(a, 10); + b = drm_color_lut_extract(b, 10); + + /* b = a + 8 * m * 2 ^ -e */ + mantissa = clamp(b - a, 0, 0x7f); + exponent = 3; + while (mantissa > 0xf) { + mantissa >>= 1; + exponent--; + } + + return (exponent << 6) | + (mantissa << 2) | + (a >> 8); +} + +static u32 i9xx_lut_10_udw(const struct drm_color_lut *color) +{ + return REG_FIELD_PREP(PALETTE_RED_MASK, _i9xx_lut_10_udw(color[0].red, color[1].red)) | + REG_FIELD_PREP(PALETTE_GREEN_MASK, _i9xx_lut_10_udw(color[0].green, color[1].green)) | + REG_FIELD_PREP(PALETTE_BLUE_MASK, _i9xx_lut_10_udw(color[0].blue, color[1].blue)); +} + +static void i9xx_lut_10_pack(struct drm_color_lut *color, + u32 ldw, u32 udw) +{ + u16 red = REG_FIELD_GET(PALETTE_10BIT_RED_LDW_MASK, ldw) | + REG_FIELD_GET(PALETTE_10BIT_RED_UDW_MASK, udw) << 8; + u16 green = REG_FIELD_GET(PALETTE_10BIT_GREEN_LDW_MASK, ldw) | + REG_FIELD_GET(PALETTE_10BIT_GREEN_UDW_MASK, udw) << 8; + u16 blue = REG_FIELD_GET(PALETTE_10BIT_BLUE_LDW_MASK, ldw) | + REG_FIELD_GET(PALETTE_10BIT_BLUE_UDW_MASK, udw) << 8; + + color->red = intel_color_lut_pack(red, 10); + color->green = intel_color_lut_pack(green, 10); + color->blue = intel_color_lut_pack(blue, 10); +} + +static void i9xx_lut_10_pack_slope(struct drm_color_lut *color, + u32 ldw, u32 udw) +{ + int r_exp = REG_FIELD_GET(PALETTE_10BIT_RED_EXP_MASK, udw); + int r_mant = REG_FIELD_GET(PALETTE_10BIT_RED_MANT_MASK, udw); + int g_exp = REG_FIELD_GET(PALETTE_10BIT_GREEN_EXP_MASK, udw); + int g_mant = REG_FIELD_GET(PALETTE_10BIT_GREEN_MANT_MASK, udw); + int b_exp = REG_FIELD_GET(PALETTE_10BIT_BLUE_EXP_MASK, udw); + int b_mant = REG_FIELD_GET(PALETTE_10BIT_BLUE_MANT_MASK, udw); + + i9xx_lut_10_pack(color, ldw, udw); + + color->red += r_mant << (3 - r_exp); + color->green += g_mant << (3 - g_exp); + color->blue += b_mant << (3 - b_exp); +} + +/* i965+ "10.6" bit interpolated format "even DW" (low 8 bits) */ +static u32 i965_lut_10p6_ldw(const struct drm_color_lut *color) +{ + return REG_FIELD_PREP(PALETTE_RED_MASK, color->red & 0xff) | + REG_FIELD_PREP(PALETTE_GREEN_MASK, color->green & 0xff) | + REG_FIELD_PREP(PALETTE_BLUE_MASK, 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 REG_FIELD_PREP(PALETTE_RED_MASK, color->red >> 8) | + REG_FIELD_PREP(PALETTE_GREEN_MASK, color->green >> 8) | + REG_FIELD_PREP(PALETTE_BLUE_MASK, 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 min(val, 0xffffu); +} + +static u32 ilk_lut_10(const struct drm_color_lut *color) +{ + return REG_FIELD_PREP(PREC_PALETTE_10_RED_MASK, drm_color_lut_extract(color->red, 10)) | + REG_FIELD_PREP(PREC_PALETTE_10_GREEN_MASK, drm_color_lut_extract(color->green, 10)) | + REG_FIELD_PREP(PREC_PALETTE_10_BLUE_MASK, 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_10_RED_MASK, val), 10); + entry->green = intel_color_lut_pack(REG_FIELD_GET(PREC_PALETTE_10_GREEN_MASK, val), 10); + entry->blue = intel_color_lut_pack(REG_FIELD_GET(PREC_PALETTE_10_BLUE_MASK, val), 10); +} + +/* ilk+ "12.4" interpolated format (low 6 bits) */ +static u32 ilk_lut_12p4_ldw(const struct drm_color_lut *color) +{ + return REG_FIELD_PREP(PREC_PALETTE_12P4_RED_LDW_MASK, color->red & 0x3f) | + REG_FIELD_PREP(PREC_PALETTE_12P4_GREEN_LDW_MASK, color->green & 0x3f) | + REG_FIELD_PREP(PREC_PALETTE_12P4_BLUE_LDW_MASK, color->blue & 0x3f); +} + +/* ilk+ "12.4" interpolated format (high 10 bits) */ +static u32 ilk_lut_12p4_udw(const struct drm_color_lut *color) +{ + return REG_FIELD_PREP(PREC_PALETTE_12P4_RED_UDW_MASK, color->red >> 6) | + REG_FIELD_PREP(PREC_PALETTE_12P4_GREEN_UDW_MASK, color->green >> 6) | + REG_FIELD_PREP(PREC_PALETTE_12P4_BLUE_UDW_MASK, color->blue >> 6); +} + +static void ilk_lut_12p4_pack(struct drm_color_lut *entry, u32 ldw, u32 udw) +{ + entry->red = REG_FIELD_GET(PREC_PALETTE_12P4_RED_UDW_MASK, udw) << 6 | + REG_FIELD_GET(PREC_PALETTE_12P4_RED_LDW_MASK, ldw); + entry->green = REG_FIELD_GET(PREC_PALETTE_12P4_GREEN_UDW_MASK, udw) << 6 | + REG_FIELD_GET(PREC_PALETTE_12P4_GREEN_LDW_MASK, ldw); + entry->blue = REG_FIELD_GET(PREC_PALETTE_12P4_BLUE_UDW_MASK, udw) << 6 | + REG_FIELD_GET(PREC_PALETTE_12P4_BLUE_LDW_MASK, ldw); +} + +static void icl_color_commit_noarm(struct intel_dsb *dsb, + const struct intel_crtc_state *crtc_state) +{ + /* + * Despite Wa_1406463849, ICL no longer suffers from the SKL + * DC5/PSR CSC black screen issue (see skl_color_commit_noarm()). + * Possibly due to the extra sticky CSC arming + * (see icl_color_post_update()). + * + * On TGL+ all CSC arming issues have been properly fixed. + */ + icl_load_csc_matrix(dsb, crtc_state); +} + +static void skl_color_commit_noarm(struct intel_dsb *dsb, + 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 performed from skl_color_commit_arm() + * which is called after PSR exit. + */ + if (!crtc_state->has_psr) + ilk_load_csc_matrix(dsb, crtc_state); +} + +static void ilk_color_commit_noarm(struct intel_dsb *dsb, + const struct intel_crtc_state *crtc_state) +{ + ilk_load_csc_matrix(dsb, crtc_state); +} + +static void i9xx_color_commit_arm(struct intel_dsb *dsb, + const struct intel_crtc_state *crtc_state) +{ + /* update TRANSCONF GAMMA_MODE */ + i9xx_set_pipeconf(crtc_state); +} + +static void ilk_color_commit_arm(struct intel_dsb *dsb, + const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct intel_display *display = to_intel_display(crtc); + + /* update TRANSCONF GAMMA_MODE */ + ilk_set_pipeconf(crtc_state); + + intel_de_write_fw(display, PIPE_CSC_MODE(crtc->pipe), + crtc_state->csc_mode); +} + +static void hsw_color_commit_arm(struct intel_dsb *dsb, + const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct intel_display *display = to_intel_display(crtc); + + intel_de_write(display, GAMMA_MODE(crtc->pipe), + crtc_state->gamma_mode); + + intel_de_write_fw(display, PIPE_CSC_MODE(crtc->pipe), + crtc_state->csc_mode); +} + +static u32 hsw_read_gamma_mode(struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + + return intel_de_read(display, GAMMA_MODE(crtc->pipe)); +} + +static u32 ilk_read_csc_mode(struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + + return intel_de_read(display, PIPE_CSC_MODE(crtc->pipe)); +} + +static void i9xx_get_config(struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct intel_plane *plane = to_intel_plane(crtc->base.primary); + enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; + u32 tmp; + + tmp = intel_de_read(display, DSPCNTR(display, i9xx_plane)); + + if (tmp & DISP_PIPE_GAMMA_ENABLE) + crtc_state->gamma_enable = true; + + if (!HAS_GMCH(display) && tmp & DISP_PIPE_CSC_ENABLE) + crtc_state->csc_enable = true; +} + +static void hsw_get_config(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + crtc_state->gamma_mode = hsw_read_gamma_mode(crtc); + crtc_state->csc_mode = ilk_read_csc_mode(crtc); + + i9xx_get_config(crtc_state); +} + +static void skl_get_config(struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + u32 color; + + crtc_state->gamma_mode = hsw_read_gamma_mode(crtc); + crtc_state->csc_mode = ilk_read_csc_mode(crtc); + + color = intel_de_read(display, SKL_BOTTOM_COLOR(crtc->pipe)); + if (DISPLAY_VER(display) < 35) { + if (color & SKL_BOTTOM_COLOR_GAMMA_ENABLE) + crtc_state->gamma_enable = true; + + if (color & SKL_BOTTOM_COLOR_CSC_ENABLE) + crtc_state->csc_enable = true; + } + + crtc_state->hw.background_color = color & GENMASK(29, 0); +} + +u32 intel_color_background_color_drm_to_hw(u64 drm_background_color) +{ + return (DRM_ARGB64_GETR_BPC(drm_background_color, 10) << 20) | + (DRM_ARGB64_GETG_BPC(drm_background_color, 10) << 10) | + (DRM_ARGB64_GETB_BPC(drm_background_color, 10)); +} + +u64 intel_color_background_color_hw_to_drm(u32 hw_background_color) +{ + u16 r = (hw_background_color >> 20) & 0x3ff; + u16 g = (hw_background_color >> 10) & 0x3ff; + u16 b = hw_background_color & 0x3ff; + + return DRM_ARGB64_PREP_BPC(0x3ff, r, g, b, 10); +} + +static void skl_color_commit_arm(struct intel_dsb *dsb, + const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + enum pipe pipe = crtc->pipe; + u32 val = crtc_state->hw.background_color; + + if (crtc_state->has_psr) + ilk_load_csc_matrix(dsb, crtc_state); + + 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_dsb(display, dsb, SKL_BOTTOM_COLOR(pipe), val); + + intel_de_write_dsb(display, dsb, GAMMA_MODE(crtc->pipe), crtc_state->gamma_mode); + + intel_de_write_dsb(display, dsb, PIPE_CSC_MODE(crtc->pipe), crtc_state->csc_mode); +} + +static void icl_color_commit_arm(struct intel_dsb *dsb, + const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + enum pipe pipe = crtc->pipe; + + intel_de_write_dsb(display, dsb, SKL_BOTTOM_COLOR(pipe), crtc_state->hw.background_color); + + intel_de_write_dsb(display, dsb, GAMMA_MODE(crtc->pipe), crtc_state->gamma_mode); + + intel_de_write_dsb(display, dsb, PIPE_CSC_MODE(crtc->pipe), crtc_state->csc_mode); +} + +static void icl_color_post_update(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + /* + * Despite Wa_1406463849, ICL CSC is no longer disarmed by + * coeff/offset register *writes*. Instead, once CSC_MODE + * is armed it stays armed, even after it has been latched. + * Afterwards the coeff/offset registers become effectively + * self-arming. That self-arming must be disabled before the + * next icl_color_commit_noarm() tries to write the next set + * of coeff/offset registers. Fortunately register *reads* + * do still disarm the CSC. Naturally this must not be done + * until the previously written CSC registers have actually + * been latched. + * + * TGL+ no longer need this workaround. + */ + intel_de_read_fw(display, PIPE_CSC_PREOFF_HI(crtc->pipe)); +} + +static struct drm_property_blob * +create_linear_lut(struct intel_display *display, int lut_size) +{ + struct drm_property_blob *blob; + struct drm_color_lut *lut; + int i; + + blob = drm_property_create_blob(display->drm, + sizeof(lut[0]) * lut_size, + NULL); + if (IS_ERR(blob)) + return blob; + + lut = blob->data; + + for (i = 0; i < lut_size; i++) { + u16 val = 0xffff * i / (lut_size - 1); + + lut[i].red = val; + lut[i].green = val; + lut[i].blue = val; + } + + return blob; +} + +static u16 lut_limited_range(unsigned int value) +{ + unsigned int min = 16 << 8; + unsigned int max = 235 << 8; + + return value * (max - min) / 0xffff + min; +} + +static struct drm_property_blob * +create_resized_lut(struct intel_display *display, + const struct drm_property_blob *blob_in, int lut_out_size, + bool limited_color_range) +{ + int i, lut_in_size = drm_color_lut_size(blob_in); + struct drm_property_blob *blob_out; + const struct drm_color_lut *lut_in; + struct drm_color_lut *lut_out; + + blob_out = drm_property_create_blob(display->drm, + sizeof(lut_out[0]) * lut_out_size, + NULL); + if (IS_ERR(blob_out)) + return blob_out; + + lut_in = blob_in->data; + lut_out = blob_out->data; + + for (i = 0; i < lut_out_size; i++) { + const struct drm_color_lut *entry = + &lut_in[i * (lut_in_size - 1) / (lut_out_size - 1)]; + + if (limited_color_range) { + lut_out[i].red = lut_limited_range(entry->red); + lut_out[i].green = lut_limited_range(entry->green); + lut_out[i].blue = lut_limited_range(entry->blue); + } else { + lut_out[i] = *entry; + } + } + + return blob_out; +} + +static void i9xx_load_lut_8(struct intel_crtc *crtc, + const struct drm_property_blob *blob) +{ + struct intel_display *display = to_intel_display(crtc); + 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(display, PALETTE(display, pipe, i), + i9xx_lut_8(&lut[i])); +} + +static void i9xx_load_lut_10(struct intel_crtc *crtc, + const struct drm_property_blob *blob) +{ + struct intel_display *display = to_intel_display(crtc); + 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(display, + PALETTE(display, pipe, 2 * i + 0), + i9xx_lut_10_ldw(&lut[i])); + intel_de_write_fw(display, + PALETTE(display, pipe, 2 * i + 1), + i9xx_lut_10_udw(&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); + const struct drm_property_blob *post_csc_lut = crtc_state->post_csc_lut; + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + i9xx_load_lut_8(crtc, post_csc_lut); + break; + case GAMMA_MODE_MODE_10BIT: + i9xx_load_lut_10(crtc, post_csc_lut); + break; + default: + MISSING_CASE(crtc_state->gamma_mode); + break; + } +} + +static void i965_load_lut_10p6(struct intel_crtc *crtc, + const struct drm_property_blob *blob) +{ + struct intel_display *display = to_intel_display(crtc); + 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(display, + PALETTE(display, pipe, 2 * i + 0), + i965_lut_10p6_ldw(&lut[i])); + intel_de_write_fw(display, + PALETTE(display, pipe, 2 * i + 1), + i965_lut_10p6_udw(&lut[i])); + } + + intel_de_write_fw(display, PIPEGCMAX(display, pipe, 0), lut[i].red); + intel_de_write_fw(display, PIPEGCMAX(display, pipe, 1), lut[i].green); + intel_de_write_fw(display, PIPEGCMAX(display, 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); + const struct drm_property_blob *post_csc_lut = crtc_state->post_csc_lut; + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + i9xx_load_lut_8(crtc, post_csc_lut); + break; + case GAMMA_MODE_MODE_10BIT: + i965_load_lut_10p6(crtc, post_csc_lut); + break; + default: + MISSING_CASE(crtc_state->gamma_mode); + break; + } +} + +static void ilk_lut_write(const struct intel_crtc_state *crtc_state, + intel_reg_t reg, u32 val) +{ + struct intel_display *display = to_intel_display(crtc_state); + + if (crtc_state->dsb_color) + intel_dsb_reg_write(crtc_state->dsb_color, reg, val); + else + intel_de_write_fw(display, reg, val); +} + +static void ilk_lut_write_indexed(const struct intel_crtc_state *crtc_state, + intel_reg_t reg, u32 val) +{ + struct intel_display *display = to_intel_display(crtc_state); + + if (crtc_state->dsb_color) + intel_dsb_reg_write_indexed(crtc_state->dsb_color, reg, val); + else + intel_de_write_fw(display, reg, val); +} + +static void ilk_load_lut_8(const struct intel_crtc_state *crtc_state, + const struct drm_property_blob *blob) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + const struct drm_color_lut *lut; + enum pipe pipe = crtc->pipe; + int i; + + if (!blob) + return; + + lut = blob->data; + + /* + * DSB fails to correctly load the legacy LUT unless + * we either write each entry twice when using posted + * writes, or we use non-posted writes. + * + * If palette anti-collision is active during LUT + * register writes: + * - posted writes simply get dropped and thus the LUT + * contents may not be correctly updated + * - non-posted writes are blocked and thus the LUT + * contents are always correct, but simultaneous CPU + * MMIO access will start to fail + * + * Choose the lesser of two evils and use posted writes. + * Using posted writes is also faster, even when having + * to write each register twice. + */ + for (i = 0; i < 256; i++) { + ilk_lut_write(crtc_state, LGC_PALETTE(pipe, i), + i9xx_lut_8(&lut[i])); + if (crtc_state->dsb_color) + ilk_lut_write(crtc_state, LGC_PALETTE(pipe, i), + i9xx_lut_8(&lut[i])); + } +} + +static void ilk_load_lut_10(const struct intel_crtc_state *crtc_state, + const struct drm_property_blob *blob) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + 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++) + ilk_lut_write(crtc_state, PREC_PALETTE(pipe, i), + ilk_lut_10(&lut[i])); +} + +static void ilk_load_luts(const struct intel_crtc_state *crtc_state) +{ + const struct drm_property_blob *post_csc_lut = crtc_state->post_csc_lut; + const struct drm_property_blob *pre_csc_lut = crtc_state->pre_csc_lut; + const struct drm_property_blob *blob = post_csc_lut ?: pre_csc_lut; + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + ilk_load_lut_8(crtc_state, blob); + break; + case GAMMA_MODE_MODE_10BIT: + ilk_load_lut_10(crtc_state, blob); + 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(const struct intel_crtc_state *crtc_state, + const struct drm_property_blob *blob, + u32 prec_index) +{ + const struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + 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++) { + ilk_lut_write(crtc_state, PREC_PAL_INDEX(pipe), + prec_index + i); + ilk_lut_write(crtc_state, PREC_PAL_DATA(pipe), + ilk_lut_10(&lut[i])); + } + + /* + * Reset the index, otherwise it prevents the legacy palette to be + * written properly. + */ + ilk_lut_write(crtc_state, PREC_PAL_INDEX(pipe), + PAL_PREC_INDEX_VALUE(0)); +} + +/* On BDW+ the index auto increment mode actually works */ +static void bdw_load_lut_10(const struct intel_crtc_state *crtc_state, + const struct drm_property_blob *blob, + u32 prec_index) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + const struct drm_color_lut *lut = blob->data; + int i, lut_size = drm_color_lut_size(blob); + enum pipe pipe = crtc->pipe; + + ilk_lut_write(crtc_state, PREC_PAL_INDEX(pipe), + prec_index); + ilk_lut_write(crtc_state, PREC_PAL_INDEX(pipe), + PAL_PREC_AUTO_INCREMENT | + prec_index); + + for (i = 0; i < lut_size; i++) + ilk_lut_write_indexed(crtc_state, PREC_PAL_DATA(pipe), + ilk_lut_10(&lut[i])); + + /* + * Reset the index, otherwise it prevents the legacy palette to be + * written properly. + */ + ilk_lut_write(crtc_state, PREC_PAL_INDEX(pipe), + PAL_PREC_INDEX_VALUE(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); + enum pipe pipe = crtc->pipe; + + /* Program the max register to clamp values > 1.0. */ + ilk_lut_write(crtc_state, PREC_PAL_EXT_GC_MAX(pipe, 0), 1 << 16); + ilk_lut_write(crtc_state, PREC_PAL_EXT_GC_MAX(pipe, 1), 1 << 16); + ilk_lut_write(crtc_state, PREC_PAL_EXT_GC_MAX(pipe, 2), 1 << 16); +} + +static void glk_load_lut_ext2_max(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + enum pipe pipe = crtc->pipe; + + /* Program the max register to clamp values > 1.0. */ + ilk_lut_write(crtc_state, PREC_PAL_EXT2_GC_MAX(pipe, 0), 1 << 16); + ilk_lut_write(crtc_state, PREC_PAL_EXT2_GC_MAX(pipe, 1), 1 << 16); + ilk_lut_write(crtc_state, PREC_PAL_EXT2_GC_MAX(pipe, 2), 1 << 16); +} + +static void ivb_load_luts(const struct intel_crtc_state *crtc_state) +{ + const struct drm_property_blob *post_csc_lut = crtc_state->post_csc_lut; + const struct drm_property_blob *pre_csc_lut = crtc_state->pre_csc_lut; + const struct drm_property_blob *blob = post_csc_lut ?: pre_csc_lut; + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + ilk_load_lut_8(crtc_state, blob); + break; + case GAMMA_MODE_MODE_SPLIT: + ivb_load_lut_10(crtc_state, pre_csc_lut, PAL_PREC_SPLIT_MODE | + PAL_PREC_INDEX_VALUE(0)); + ivb_load_lut_ext_max(crtc_state); + ivb_load_lut_10(crtc_state, post_csc_lut, PAL_PREC_SPLIT_MODE | + PAL_PREC_INDEX_VALUE(512)); + break; + case GAMMA_MODE_MODE_10BIT: + ivb_load_lut_10(crtc_state, 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) +{ + const struct drm_property_blob *post_csc_lut = crtc_state->post_csc_lut; + const struct drm_property_blob *pre_csc_lut = crtc_state->pre_csc_lut; + const struct drm_property_blob *blob = post_csc_lut ?: pre_csc_lut; + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + ilk_load_lut_8(crtc_state, blob); + break; + case GAMMA_MODE_MODE_SPLIT: + bdw_load_lut_10(crtc_state, pre_csc_lut, PAL_PREC_SPLIT_MODE | + PAL_PREC_INDEX_VALUE(0)); + ivb_load_lut_ext_max(crtc_state); + bdw_load_lut_10(crtc_state, post_csc_lut, PAL_PREC_SPLIT_MODE | + PAL_PREC_INDEX_VALUE(512)); + break; + case GAMMA_MODE_MODE_10BIT: + bdw_load_lut_10(crtc_state, 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 intel_display *display) +{ + if (DISPLAY_VER(display) >= 13) + return 131; + else + return 35; +} + +static u32 glk_degamma_lut(const struct drm_color_lut *color) +{ + return color->green; +} + +static void glk_degamma_lut_pack(struct drm_color_lut *entry, u32 val) +{ + /* PRE_CSC_GAMC_DATA is 3.16, clamp to 0.16 */ + entry->red = entry->green = entry->blue = min(val, 0xffffu); +} + +static u32 mtl_degamma_lut(const struct drm_color_lut *color) +{ + return drm_color_lut_extract(color->green, 24); +} + +static void mtl_degamma_lut_pack(struct drm_color_lut *entry, u32 val) +{ + /* PRE_CSC_GAMC_DATA is 3.24, clamp to 0.16 */ + entry->red = entry->green = entry->blue = + intel_color_lut_pack(min(val, 0xffffffu), 24); +} + +static void glk_load_degamma_lut(const struct intel_crtc_state *crtc_state, + const struct drm_property_blob *blob) +{ + struct intel_display *display = to_intel_display(crtc_state); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + const struct drm_color_lut *lut = blob->data; + int i, lut_size = drm_color_lut_size(blob); + enum pipe pipe = crtc->pipe; + + /* + * When setting the auto-increment bit, the hardware seems to + * ignore the index bits, so we need to reset it to index 0 + * separately. + */ + ilk_lut_write(crtc_state, PRE_CSC_GAMC_INDEX(pipe), + PRE_CSC_GAMC_INDEX_VALUE(0)); + ilk_lut_write(crtc_state, PRE_CSC_GAMC_INDEX(pipe), + PRE_CSC_GAMC_AUTO_INCREMENT | + PRE_CSC_GAMC_INDEX_VALUE(0)); + + 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. + */ + ilk_lut_write_indexed(crtc_state, PRE_CSC_GAMC_DATA(pipe), + DISPLAY_VER(display) >= 14 ? + mtl_degamma_lut(&lut[i]) : glk_degamma_lut(&lut[i])); + } + + /* Clamp values > 1.0. */ + while (i++ < glk_degamma_lut_size(display)) + ilk_lut_write_indexed(crtc_state, PRE_CSC_GAMC_DATA(pipe), + DISPLAY_VER(display) >= 14 ? + 1 << 24 : 1 << 16); + + ilk_lut_write(crtc_state, PRE_CSC_GAMC_INDEX(pipe), 0); +} + +static void glk_load_luts(const struct intel_crtc_state *crtc_state) +{ + const struct drm_property_blob *pre_csc_lut = crtc_state->pre_csc_lut; + const struct drm_property_blob *post_csc_lut = crtc_state->post_csc_lut; + + if (pre_csc_lut) + glk_load_degamma_lut(crtc_state, pre_csc_lut); + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + ilk_load_lut_8(crtc_state, post_csc_lut); + break; + case GAMMA_MODE_MODE_10BIT: + bdw_load_lut_10(crtc_state, post_csc_lut, PAL_PREC_INDEX_VALUE(0)); + ivb_load_lut_ext_max(crtc_state); + glk_load_lut_ext2_max(crtc_state); + break; + default: + MISSING_CASE(crtc_state->gamma_mode); + break; + } +} + +static void +ivb_load_lut_max(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 */ + ilk_lut_write(crtc_state, PREC_PAL_GC_MAX(pipe, 0), color->red); + ilk_lut_write(crtc_state, PREC_PAL_GC_MAX(pipe, 1), color->green); + ilk_lut_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->post_csc_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). + */ + ilk_lut_write(crtc_state, PREC_PAL_MULTI_SEG_INDEX(pipe), + PAL_PREC_MULTI_SEG_INDEX_VALUE(0)); + ilk_lut_write(crtc_state, PREC_PAL_MULTI_SEG_INDEX(pipe), + PAL_PREC_AUTO_INCREMENT | + PAL_PREC_MULTI_SEG_INDEX_VALUE(0)); + + for (i = 0; i < 9; i++) { + const struct drm_color_lut *entry = &lut[i]; + + ilk_lut_write_indexed(crtc_state, PREC_PAL_MULTI_SEG_DATA(pipe), + ilk_lut_12p4_ldw(entry)); + ilk_lut_write_indexed(crtc_state, PREC_PAL_MULTI_SEG_DATA(pipe), + ilk_lut_12p4_udw(entry)); + } + + ilk_lut_write(crtc_state, PREC_PAL_MULTI_SEG_INDEX(pipe), + PAL_PREC_MULTI_SEG_INDEX_VALUE(0)); +} + +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->post_csc_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. + */ + ilk_lut_write(crtc_state, PREC_PAL_INDEX(pipe), + PAL_PREC_INDEX_VALUE(0)); + ilk_lut_write(crtc_state, PREC_PAL_INDEX(pipe), + PAL_PREC_AUTO_INCREMENT | + PAL_PREC_INDEX_VALUE(0)); + + for (i = 1; i < 257; i++) { + entry = &lut[i * 8]; + + ilk_lut_write_indexed(crtc_state, PREC_PAL_DATA(pipe), + ilk_lut_12p4_ldw(entry)); + ilk_lut_write_indexed(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]; + + ilk_lut_write_indexed(crtc_state, PREC_PAL_DATA(pipe), + ilk_lut_12p4_ldw(entry)); + ilk_lut_write_indexed(crtc_state, PREC_PAL_DATA(pipe), + ilk_lut_12p4_udw(entry)); + } + + ilk_lut_write(crtc_state, PREC_PAL_INDEX(pipe), + PAL_PREC_INDEX_VALUE(0)); + + /* The last entry in the LUT is to be programmed in GCMAX */ + entry = &lut[256 * 8 * 128]; + ivb_load_lut_max(crtc_state, entry); +} + +static void icl_load_luts(const struct intel_crtc_state *crtc_state) +{ + const struct drm_property_blob *pre_csc_lut = crtc_state->pre_csc_lut; + const struct drm_property_blob *post_csc_lut = crtc_state->post_csc_lut; + + if (pre_csc_lut) + glk_load_degamma_lut(crtc_state, pre_csc_lut); + + switch (crtc_state->gamma_mode & GAMMA_MODE_MODE_MASK) { + case GAMMA_MODE_MODE_8BIT: + ilk_load_lut_8(crtc_state, post_csc_lut); + break; + case GAMMA_MODE_MODE_12BIT_MULTI_SEG: + icl_program_gamma_superfine_segment(crtc_state); + icl_program_gamma_multi_segment(crtc_state); + ivb_load_lut_ext_max(crtc_state); + glk_load_lut_ext2_max(crtc_state); + break; + case GAMMA_MODE_MODE_10BIT: + bdw_load_lut_10(crtc_state, post_csc_lut, PAL_PREC_INDEX_VALUE(0)); + ivb_load_lut_ext_max(crtc_state); + glk_load_lut_ext2_max(crtc_state); + break; + default: + MISSING_CASE(crtc_state->gamma_mode); + break; + } +} + +static void vlv_load_luts(const struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + if (crtc_state->wgc_enable) + vlv_load_wgc_csc(crtc, &crtc_state->csc); + + i965_load_luts(crtc_state); +} + +static u32 chv_cgm_degamma_ldw(const struct drm_color_lut *color) +{ + return REG_FIELD_PREP(CGM_PIPE_DEGAMMA_GREEN_LDW_MASK, drm_color_lut_extract(color->green, 14)) | + REG_FIELD_PREP(CGM_PIPE_DEGAMMA_BLUE_LDW_MASK, drm_color_lut_extract(color->blue, 14)); +} + +static u32 chv_cgm_degamma_udw(const struct drm_color_lut *color) +{ + return REG_FIELD_PREP(CGM_PIPE_DEGAMMA_RED_UDW_MASK, drm_color_lut_extract(color->red, 14)); +} + +static void chv_cgm_degamma_pack(struct drm_color_lut *entry, u32 ldw, u32 udw) +{ + entry->green = intel_color_lut_pack(REG_FIELD_GET(CGM_PIPE_DEGAMMA_GREEN_LDW_MASK, ldw), 14); + entry->blue = intel_color_lut_pack(REG_FIELD_GET(CGM_PIPE_DEGAMMA_BLUE_LDW_MASK, ldw), 14); + entry->red = intel_color_lut_pack(REG_FIELD_GET(CGM_PIPE_DEGAMMA_RED_UDW_MASK, udw), 14); +} + +static void chv_load_cgm_degamma(struct intel_crtc *crtc, + const struct drm_property_blob *blob) +{ + struct intel_display *display = to_intel_display(crtc); + 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(display, CGM_PIPE_DEGAMMA(pipe, i, 0), + chv_cgm_degamma_ldw(&lut[i])); + intel_de_write_fw(display, 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 REG_FIELD_PREP(CGM_PIPE_GAMMA_GREEN_LDW_MASK, drm_color_lut_extract(color->green, 10)) | + REG_FIELD_PREP(CGM_PIPE_GAMMA_BLUE_LDW_MASK, drm_color_lut_extract(color->blue, 10)); +} + +static u32 chv_cgm_gamma_udw(const struct drm_color_lut *color) +{ + return REG_FIELD_PREP(CGM_PIPE_GAMMA_RED_UDW_MASK, 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_LDW_MASK, ldw), 10); + entry->blue = intel_color_lut_pack(REG_FIELD_GET(CGM_PIPE_GAMMA_BLUE_LDW_MASK, ldw), 10); + entry->red = intel_color_lut_pack(REG_FIELD_GET(CGM_PIPE_GAMMA_RED_UDW_MASK, udw), 10); +} + +static void chv_load_cgm_gamma(struct intel_crtc *crtc, + const struct drm_property_blob *blob) +{ + struct intel_display *display = to_intel_display(crtc); + 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(display, CGM_PIPE_GAMMA(pipe, i, 0), + chv_cgm_gamma_ldw(&lut[i])); + intel_de_write_fw(display, 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_display *display = to_intel_display(crtc_state); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + const struct drm_property_blob *pre_csc_lut = crtc_state->pre_csc_lut; + const struct drm_property_blob *post_csc_lut = crtc_state->post_csc_lut; + + if (crtc_state->cgm_mode & CGM_PIPE_MODE_CSC) + chv_load_cgm_csc(crtc, &crtc_state->csc); + + if (crtc_state->cgm_mode & CGM_PIPE_MODE_DEGAMMA) + chv_load_cgm_degamma(crtc, pre_csc_lut); + + if (crtc_state->cgm_mode & CGM_PIPE_MODE_GAMMA) + chv_load_cgm_gamma(crtc, post_csc_lut); + else + i965_load_luts(crtc_state); + + intel_de_write_fw(display, CGM_PIPE_MODE(crtc->pipe), + crtc_state->cgm_mode); +} + +void intel_color_load_luts(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + if (crtc_state->dsb_color) + return; + + display->color.funcs->load_luts(crtc_state); +} + +void intel_color_commit_noarm(struct intel_dsb *dsb, + const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + if (display->color.funcs->color_commit_noarm) + display->color.funcs->color_commit_noarm(dsb, crtc_state); +} + +void intel_color_commit_arm(struct intel_dsb *dsb, + const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + display->color.funcs->color_commit_arm(dsb, crtc_state); +} + +void intel_color_post_update(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + if (display->color.funcs->color_post_update) + display->color.funcs->color_post_update(crtc_state); +} + +void intel_color_modeset(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + intel_color_load_luts(crtc_state); + intel_color_commit_noarm(NULL, crtc_state); + intel_color_commit_arm(NULL, crtc_state); + + if (DISPLAY_VER(display) < 9) { + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct intel_plane *plane = to_intel_plane(crtc->base.primary); + + /* update DSPCNTR to configure gamma/csc for pipe bottom color */ + plane->disable_arm(NULL, plane, crtc_state); + } +} + +bool intel_color_uses_dsb(const struct intel_crtc_state *crtc_state) +{ + return crtc_state->dsb_color; +} + +bool intel_color_uses_chained_dsb(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + return crtc_state->dsb_color && !HAS_DOUBLE_BUFFERED_LUT(display); +} + +bool intel_color_uses_gosub_dsb(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + return crtc_state->dsb_color && HAS_DOUBLE_BUFFERED_LUT(display); +} + +void intel_color_prepare_commit(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(state); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + + if (!crtc_state->hw.active || + intel_crtc_needs_modeset(crtc_state)) + return; + + if (!intel_crtc_needs_color_update(crtc_state)) + return; + + if (!crtc_state->pre_csc_lut && !crtc_state->post_csc_lut) + return; + + if (HAS_DOUBLE_BUFFERED_LUT(display)) + crtc_state->dsb_color = intel_dsb_prepare(state, crtc, INTEL_DSB_0, 1024); + else + crtc_state->dsb_color = intel_dsb_prepare(state, crtc, INTEL_DSB_1, 1024); + + if (!intel_color_uses_dsb(crtc_state)) + return; + + display->color.funcs->load_luts(crtc_state); + + if (crtc_state->use_dsb && intel_color_uses_chained_dsb(crtc_state)) { + intel_vrr_send_push(crtc_state->dsb_color, crtc_state); + intel_dsb_wait_for_delayed_vblank(state, crtc_state->dsb_color); + intel_vrr_check_push_sent(crtc_state->dsb_color, crtc_state); + intel_dsb_interrupt(crtc_state->dsb_color); + } + + if (intel_color_uses_gosub_dsb(crtc_state)) + intel_dsb_gosub_finish(crtc_state->dsb_color); + else + intel_dsb_finish(crtc_state->dsb_color); +} + +void intel_color_cleanup_commit(struct intel_crtc_state *crtc_state) +{ + if (crtc_state->dsb_color) { + intel_dsb_cleanup(crtc_state->dsb_color); + crtc_state->dsb_color = NULL; + } +} + +void intel_color_wait_commit(const struct intel_crtc_state *crtc_state) +{ + if (crtc_state->dsb_color) + intel_dsb_wait(crtc_state->dsb_color); +} + +static bool intel_can_preload_luts(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(state); + const struct intel_crtc_state *old_crtc_state = + intel_atomic_get_old_crtc_state(state, crtc); + + if (HAS_DOUBLE_BUFFERED_LUT(display)) + return false; + + return !old_crtc_state->post_csc_lut && + !old_crtc_state->pre_csc_lut; +} + +static bool vlv_can_preload_luts(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); + + return !old_crtc_state->wgc_enable && + !old_crtc_state->post_csc_lut; +} + +static bool chv_can_preload_luts(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); + + /* + * 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 vlv_can_preload_luts(state, crtc); +} + +int intel_color_check(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(state); + 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); + + /* + * May need to update pipe gamma enable bits + * when C8 planes are getting enabled/disabled. + */ + if (!old_crtc_state->c8_planes != !new_crtc_state->c8_planes || + old_crtc_state->hw.background_color != new_crtc_state->hw.background_color) + new_crtc_state->uapi.color_mgmt_changed = true; + + if (DRM_ARGB64_GETA(new_crtc_state->uapi.background_color) != 0xffff) { + drm_dbg_kms(display->drm, "New background not completely opaque\n"); + return -EINVAL; + } + + if (!intel_crtc_needs_color_update(new_crtc_state)) + return 0; + + return display->color.funcs->color_check(state, crtc); +} + +void intel_color_get_config(struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + display->color.funcs->get_config(crtc_state); + + display->color.funcs->read_luts(crtc_state); + + if (display->color.funcs->read_csc) + display->color.funcs->read_csc(crtc_state); +} + +bool intel_color_lut_equal(const struct intel_crtc_state *crtc_state, + const struct drm_property_blob *blob1, + const struct drm_property_blob *blob2, + bool is_pre_csc_lut) +{ + struct intel_display *display = to_intel_display(crtc_state); + + /* + * FIXME c8_planes readout missing thus + * .read_luts() doesn't read out post_csc_lut. + */ + if (!is_pre_csc_lut && crtc_state->c8_planes) + return true; + + return display->color.funcs->lut_equal(crtc_state, blob1, blob2, + is_pre_csc_lut); +} + +static bool need_plane_update(struct intel_plane *plane, + const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(plane); + + /* + * 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(display) < 9 && plane->id == PLANE_PRIMARY); +} + +static int +intel_color_add_affected_planes(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(state); + 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); + struct intel_plane *plane; + + if (!new_crtc_state->hw.active || + intel_crtc_needs_modeset(new_crtc_state)) + 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(display->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); + new_crtc_state->async_flip_planes = 0; + new_crtc_state->do_async_flip = false; + + /* plane control register changes blocked by CxSR */ + if (HAS_GMCH(display)) + new_crtc_state->disable_cxsr = true; + } + + return 0; +} + +static u32 intel_gamma_lut_tests(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + const struct drm_property_blob *gamma_lut = crtc_state->hw.gamma_lut; + + if (lut_is_legacy(gamma_lut)) + return 0; + + return DISPLAY_INFO(display)->color.gamma_lut_tests; +} + +static u32 intel_degamma_lut_tests(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + return DISPLAY_INFO(display)->color.degamma_lut_tests; +} + +static int intel_gamma_lut_size(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + const struct drm_property_blob *gamma_lut = crtc_state->hw.gamma_lut; + + if (lut_is_legacy(gamma_lut)) + return LEGACY_LUT_LENGTH; + + return DISPLAY_INFO(display)->color.gamma_lut_size; +} + +static u32 intel_degamma_lut_size(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + return DISPLAY_INFO(display)->color.degamma_lut_size; +} + +static int check_lut_size(struct intel_crtc *crtc, const char *lut_name, + const struct drm_property_blob *lut, int expected) +{ + struct intel_display *display = to_intel_display(crtc); + int len; + + if (!lut) + return 0; + + len = drm_color_lut_size(lut); + if (len != expected) { + drm_dbg_kms(display->drm, + "[CRTC:%d:%s] Invalid %s LUT size; got %d, expected %d\n", + crtc->base.base.id, crtc->base.name, lut_name, len, expected); + return -EINVAL; + } + + return 0; +} + +static int _check_luts(const struct intel_crtc_state *crtc_state, + u32 degamma_tests, u32 gamma_tests) +{ + struct intel_display *display = to_intel_display(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; + int gamma_length, degamma_length; + + /* C8 relies on its palette being stored in the legacy LUT */ + if (crtc_state->c8_planes && !lut_is_legacy(crtc_state->hw.gamma_lut)) { + drm_dbg_kms(display->drm, + "[CRTC:%d:%s] C8 pixelformat requires the legacy LUT\n", + crtc->base.base.id, crtc->base.name); + return -EINVAL; + } + + degamma_length = intel_degamma_lut_size(crtc_state); + gamma_length = intel_gamma_lut_size(crtc_state); + + if (check_lut_size(crtc, "degamma", degamma_lut, degamma_length) || + check_lut_size(crtc, "gamma", 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 int check_luts(const struct intel_crtc_state *crtc_state) +{ + return _check_luts(crtc_state, + intel_degamma_lut_tests(crtc_state), + intel_gamma_lut_tests(crtc_state)); +} + +static u32 i9xx_gamma_mode(struct intel_crtc_state *crtc_state) +{ + if (!crtc_state->gamma_enable || + lut_is_legacy(crtc_state->hw.gamma_lut)) + return GAMMA_MODE_MODE_8BIT; + else + return GAMMA_MODE_MODE_10BIT; +} + +static int i9xx_lut_10_diff(u16 a, u16 b) +{ + return drm_color_lut_extract(a, 10) - + drm_color_lut_extract(b, 10); +} + +static int i9xx_check_lut_10(struct intel_crtc *crtc, + const struct drm_property_blob *blob) +{ + struct intel_display *display = to_intel_display(crtc); + const struct drm_color_lut *lut = blob->data; + int lut_size = drm_color_lut_size(blob); + const struct drm_color_lut *a = &lut[lut_size - 2]; + const struct drm_color_lut *b = &lut[lut_size - 1]; + + if (i9xx_lut_10_diff(b->red, a->red) > 0x7f || + i9xx_lut_10_diff(b->green, a->green) > 0x7f || + i9xx_lut_10_diff(b->blue, a->blue) > 0x7f) { + drm_dbg_kms(display->drm, + "[CRTC:%d:%s] Last gamma LUT entry exceeds max slope\n", + crtc->base.base.id, crtc->base.name); + return -EINVAL; + } + + return 0; +} + +void intel_color_assert_luts(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + /* make sure {pre,post}_csc_lut were correctly assigned */ + if (DISPLAY_VER(display) >= 11 || HAS_GMCH(display)) { + drm_WARN_ON(display->drm, + crtc_state->pre_csc_lut != crtc_state->hw.degamma_lut); + drm_WARN_ON(display->drm, + crtc_state->post_csc_lut != crtc_state->hw.gamma_lut); + } else if (DISPLAY_VER(display) == 10) { + drm_WARN_ON(display->drm, + crtc_state->post_csc_lut == crtc_state->hw.gamma_lut && + crtc_state->pre_csc_lut != crtc_state->hw.degamma_lut && + crtc_state->pre_csc_lut != display->color.glk_linear_degamma_lut); + drm_WARN_ON(display->drm, + !ilk_lut_limited_range(crtc_state) && + crtc_state->post_csc_lut != NULL && + crtc_state->post_csc_lut != crtc_state->hw.gamma_lut); + } else if (crtc_state->gamma_mode != GAMMA_MODE_MODE_SPLIT) { + drm_WARN_ON(display->drm, + crtc_state->pre_csc_lut != crtc_state->hw.degamma_lut && + crtc_state->pre_csc_lut != crtc_state->hw.gamma_lut); + drm_WARN_ON(display->drm, + !ilk_lut_limited_range(crtc_state) && + crtc_state->post_csc_lut != crtc_state->hw.degamma_lut && + crtc_state->post_csc_lut != crtc_state->hw.gamma_lut); + } +} + +static void intel_assign_luts(struct intel_crtc_state *crtc_state) +{ + drm_property_replace_blob(&crtc_state->pre_csc_lut, + crtc_state->hw.degamma_lut); + drm_property_replace_blob(&crtc_state->post_csc_lut, + crtc_state->hw.gamma_lut); +} + +static int i9xx_color_check(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(state); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + 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); + + if (DISPLAY_VER(display) < 4 && + crtc_state->gamma_mode == GAMMA_MODE_MODE_10BIT) { + ret = i9xx_check_lut_10(crtc, crtc_state->hw.gamma_lut); + if (ret) + return ret; + } + + ret = intel_color_add_affected_planes(state, crtc); + if (ret) + return ret; + + intel_assign_luts(crtc_state); + + crtc_state->preload_luts = intel_can_preload_luts(state, crtc); + + return 0; +} + +/* + * VLV color pipeline: + * u0.10 -> WGC csc -> u0.10 -> pipe gamma -> u0.10 + */ +static int vlv_color_check(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 = 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); + + crtc_state->wgc_enable = crtc_state->hw.ctm; + + ret = intel_color_add_affected_planes(state, crtc); + if (ret) + return ret; + + intel_assign_luts(crtc_state); + + vlv_assign_csc(crtc_state); + + crtc_state->preload_luts = vlv_can_preload_luts(state, crtc); + + return 0; +} + +static u32 chv_cgm_mode(const struct intel_crtc_state *crtc_state) +{ + u32 cgm_mode = 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 && + !lut_is_legacy(crtc_state->hw.gamma_lut)) + cgm_mode |= CGM_PIPE_MODE_GAMMA; + + /* + * Toggling the CGM CSC on/off outside of the tiny window + * between start of vblank and frame start causes underruns. + * Always enable the CGM CSC as a workaround. + */ + cgm_mode |= CGM_PIPE_MODE_CSC; + + 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_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + 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 = + lut_is_legacy(crtc_state->hw.gamma_lut) && + !crtc_state->c8_planes; + + crtc_state->gamma_mode = GAMMA_MODE_MODE_8BIT; + + crtc_state->cgm_mode = chv_cgm_mode(crtc_state); + + /* + * We always bypass the WGC CSC and use the CGM CSC + * instead since it has degamma and better precision. + */ + crtc_state->wgc_enable = false; + + ret = intel_color_add_affected_planes(state, crtc); + if (ret) + return ret; + + intel_assign_luts(crtc_state); + + chv_assign_csc(crtc_state); + + crtc_state->preload_luts = chv_can_preload_luts(state, crtc); + + return 0; +} + +static bool ilk_gamma_enable(const struct intel_crtc_state *crtc_state) +{ + return (crtc_state->hw.gamma_lut || + crtc_state->hw.degamma_lut) && + !crtc_state->c8_planes; +} + +static bool ilk_csc_enable(const struct intel_crtc_state *crtc_state) +{ + return crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB || + ilk_csc_limited_range(crtc_state) || + crtc_state->hw.ctm; +} + +static u32 ilk_gamma_mode(const struct intel_crtc_state *crtc_state) +{ + if (!crtc_state->gamma_enable || + lut_is_legacy(crtc_state->hw.gamma_lut)) + 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; + + if (crtc_state->hw.degamma_lut) + return CSC_MODE_YUV_TO_RGB; + + return CSC_MODE_YUV_TO_RGB | + CSC_POSITION_BEFORE_GAMMA; +} + +static int ilk_assign_luts(struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + if (ilk_lut_limited_range(crtc_state)) { + struct drm_property_blob *gamma_lut; + + gamma_lut = create_resized_lut(display, crtc_state->hw.gamma_lut, + drm_color_lut_size(crtc_state->hw.gamma_lut), + true); + if (IS_ERR(gamma_lut)) + return PTR_ERR(gamma_lut); + + drm_property_replace_blob(&crtc_state->post_csc_lut, gamma_lut); + + drm_property_blob_put(gamma_lut); + + drm_property_replace_blob(&crtc_state->pre_csc_lut, crtc_state->hw.degamma_lut); + + return 0; + } + + if (crtc_state->hw.degamma_lut || + crtc_state->csc_mode & CSC_POSITION_BEFORE_GAMMA) { + drm_property_replace_blob(&crtc_state->pre_csc_lut, + crtc_state->hw.degamma_lut); + drm_property_replace_blob(&crtc_state->post_csc_lut, + crtc_state->hw.gamma_lut); + } else { + drm_property_replace_blob(&crtc_state->pre_csc_lut, + crtc_state->hw.gamma_lut); + drm_property_replace_blob(&crtc_state->post_csc_lut, + NULL); + } + + return 0; +} + +static int ilk_color_check(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(state); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + int ret; + + ret = check_luts(crtc_state); + if (ret) + return ret; + + if (crtc_state->hw.degamma_lut && crtc_state->hw.gamma_lut) { + drm_dbg_kms(display->drm, + "[CRTC:%d:%s] Degamma and gamma together are not possible\n", + crtc->base.base.id, crtc->base.name); + return -EINVAL; + } + + if (crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB && + crtc_state->hw.ctm) { + drm_dbg_kms(display->drm, + "[CRTC:%d:%s] YCbCr and CTM together are not possible\n", + crtc->base.base.id, crtc->base.name); + return -EINVAL; + } + + crtc_state->gamma_enable = ilk_gamma_enable(crtc_state); + + crtc_state->csc_enable = ilk_csc_enable(crtc_state); + + crtc_state->gamma_mode = ilk_gamma_mode(crtc_state); + + crtc_state->csc_mode = ilk_csc_mode(crtc_state); + + ret = intel_color_add_affected_planes(state, crtc); + if (ret) + return ret; + + ret = ilk_assign_luts(crtc_state); + if (ret) + return ret; + + ilk_assign_csc(crtc_state); + + crtc_state->preload_luts = intel_can_preload_luts(state, crtc); + + return 0; +} + +static u32 ivb_gamma_mode(const struct intel_crtc_state *crtc_state) +{ + if (crtc_state->hw.degamma_lut && crtc_state->hw.gamma_lut) + return GAMMA_MODE_MODE_SPLIT; + + return ilk_gamma_mode(crtc_state); +} + +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_assign_luts(struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + struct drm_property_blob *degamma_lut, *gamma_lut; + + if (crtc_state->gamma_mode != GAMMA_MODE_MODE_SPLIT) + return ilk_assign_luts(crtc_state); + + drm_WARN_ON(display->drm, drm_color_lut_size(crtc_state->hw.degamma_lut) != 1024); + drm_WARN_ON(display->drm, drm_color_lut_size(crtc_state->hw.gamma_lut) != 1024); + + degamma_lut = create_resized_lut(display, crtc_state->hw.degamma_lut, 512, + false); + if (IS_ERR(degamma_lut)) + return PTR_ERR(degamma_lut); + + gamma_lut = create_resized_lut(display, crtc_state->hw.gamma_lut, 512, + ilk_lut_limited_range(crtc_state)); + if (IS_ERR(gamma_lut)) { + drm_property_blob_put(degamma_lut); + return PTR_ERR(gamma_lut); + } + + drm_property_replace_blob(&crtc_state->pre_csc_lut, degamma_lut); + drm_property_replace_blob(&crtc_state->post_csc_lut, gamma_lut); + + drm_property_blob_put(degamma_lut); + drm_property_blob_put(gamma_lut); + + return 0; +} + +static int ivb_color_check(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(state); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + int ret; + + ret = check_luts(crtc_state); + if (ret) + return ret; + + if (crtc_state->c8_planes && crtc_state->hw.degamma_lut) { + drm_dbg_kms(display->drm, + "[CRTC:%d:%s] C8 pixelformat and degamma together are not possible\n", + crtc->base.base.id, crtc->base.name); + return -EINVAL; + } + + if (crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB && + crtc_state->hw.ctm) { + drm_dbg_kms(display->drm, + "[CRTC:%d:%s] YCbCr and CTM together are not possible\n", + crtc->base.base.id, crtc->base.name); + return -EINVAL; + } + + if (crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB && + crtc_state->hw.degamma_lut && crtc_state->hw.gamma_lut) { + drm_dbg_kms(display->drm, + "[CRTC:%d:%s] YCbCr and degamma+gamma together are not possible\n", + crtc->base.base.id, crtc->base.name); + return -EINVAL; + } + + crtc_state->gamma_enable = ilk_gamma_enable(crtc_state); + + crtc_state->csc_enable = ilk_csc_enable(crtc_state); + + crtc_state->gamma_mode = ivb_gamma_mode(crtc_state); + + crtc_state->csc_mode = ivb_csc_mode(crtc_state); + + ret = intel_color_add_affected_planes(state, crtc); + if (ret) + return ret; + + ret = ivb_assign_luts(crtc_state); + if (ret) + return ret; + + ilk_assign_csc(crtc_state); + + crtc_state->preload_luts = intel_can_preload_luts(state, crtc); + + return 0; +} + +static u32 glk_gamma_mode(const struct intel_crtc_state *crtc_state) +{ + if (!crtc_state->gamma_enable || + lut_is_legacy(crtc_state->hw.gamma_lut)) + return GAMMA_MODE_MODE_8BIT; + else + return GAMMA_MODE_MODE_10BIT; +} + +static bool glk_use_pre_csc_lut_for_gamma(const struct intel_crtc_state *crtc_state) +{ + return crtc_state->hw.gamma_lut && + !crtc_state->c8_planes && + crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB; +} + +static int glk_assign_luts(struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + + if (glk_use_pre_csc_lut_for_gamma(crtc_state)) { + struct drm_property_blob *gamma_lut; + + gamma_lut = create_resized_lut(display, crtc_state->hw.gamma_lut, + DISPLAY_INFO(display)->color.degamma_lut_size, + false); + if (IS_ERR(gamma_lut)) + return PTR_ERR(gamma_lut); + + drm_property_replace_blob(&crtc_state->pre_csc_lut, gamma_lut); + drm_property_replace_blob(&crtc_state->post_csc_lut, NULL); + + drm_property_blob_put(gamma_lut); + + return 0; + } + + if (ilk_lut_limited_range(crtc_state)) { + struct drm_property_blob *gamma_lut; + + gamma_lut = create_resized_lut(display, crtc_state->hw.gamma_lut, + drm_color_lut_size(crtc_state->hw.gamma_lut), + true); + if (IS_ERR(gamma_lut)) + return PTR_ERR(gamma_lut); + + drm_property_replace_blob(&crtc_state->post_csc_lut, gamma_lut); + + drm_property_blob_put(gamma_lut); + } else { + drm_property_replace_blob(&crtc_state->post_csc_lut, crtc_state->hw.gamma_lut); + } + + drm_property_replace_blob(&crtc_state->pre_csc_lut, crtc_state->hw.degamma_lut); + + /* + * 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. + */ + if (crtc_state->csc_enable && !crtc_state->pre_csc_lut) + drm_property_replace_blob(&crtc_state->pre_csc_lut, + display->color.glk_linear_degamma_lut); + + return 0; +} + +static int glk_check_luts(const struct intel_crtc_state *crtc_state) +{ + u32 degamma_tests = intel_degamma_lut_tests(crtc_state); + u32 gamma_tests = intel_gamma_lut_tests(crtc_state); + + if (glk_use_pre_csc_lut_for_gamma(crtc_state)) + gamma_tests |= degamma_tests; + + return _check_luts(crtc_state, degamma_tests, gamma_tests); +} + +static int glk_color_check(struct intel_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(state); + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + int ret; + + ret = glk_check_luts(crtc_state); + if (ret) + return ret; + + if (crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB && + crtc_state->hw.ctm) { + drm_dbg_kms(display->drm, + "[CRTC:%d:%s] YCbCr and CTM together are not possible\n", + crtc->base.base.id, crtc->base.name); + return -EINVAL; + } + + if (crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB && + crtc_state->hw.degamma_lut && crtc_state->hw.gamma_lut) { + drm_dbg_kms(display->drm, + "[CRTC:%d:%s] YCbCr and degamma+gamma together are not possible\n", + crtc->base.base.id, crtc->base.name); + return -EINVAL; + } + + crtc_state->gamma_enable = + !glk_use_pre_csc_lut_for_gamma(crtc_state) && + crtc_state->hw.gamma_lut && + !crtc_state->c8_planes; + + /* On GLK+ degamma LUT is controlled by csc_enable */ + crtc_state->csc_enable = + glk_use_pre_csc_lut_for_gamma(crtc_state) || + crtc_state->hw.degamma_lut || + crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB || + crtc_state->hw.ctm || ilk_csc_limited_range(crtc_state); + + crtc_state->gamma_mode = glk_gamma_mode(crtc_state); + + crtc_state->csc_mode = 0; + + ret = intel_color_add_affected_planes(state, crtc); + if (ret) + return ret; + + ret = glk_assign_luts(crtc_state); + if (ret) + return ret; + + ilk_assign_csc(crtc_state); + + crtc_state->preload_luts = intel_can_preload_luts(state, crtc); + + return 0; +} + +static u32 icl_gamma_mode(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + 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 || + lut_is_legacy(crtc_state->hw.gamma_lut)) + 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(display) >= 13) + gamma_mode |= GAMMA_MODE_MODE_10BIT; + else + gamma_mode |= GAMMA_MODE_MODE_12BIT_MULTI_SEG; + + 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_atomic_state *state, + struct intel_crtc *crtc) +{ + struct intel_crtc_state *crtc_state = + intel_atomic_get_new_crtc_state(state, crtc); + 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); + + intel_assign_luts(crtc_state); + + icl_assign_csc(crtc_state); + + crtc_state->preload_luts = intel_can_preload_luts(state, crtc); + + return 0; +} + +static int i9xx_post_csc_lut_precision(const struct intel_crtc_state *crtc_state) +{ + if (!crtc_state->gamma_enable && !crtc_state->c8_planes) + 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 i9xx_pre_csc_lut_precision(const struct intel_crtc_state *crtc_state) +{ + return 0; +} + +static int i965_post_csc_lut_precision(const struct intel_crtc_state *crtc_state) +{ + if (!crtc_state->gamma_enable && !crtc_state->c8_planes) + 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_mode_precision(u32 gamma_mode) +{ + switch (gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + return 8; + case GAMMA_MODE_MODE_10BIT: + return 10; + default: + MISSING_CASE(gamma_mode); + return 0; + } +} + +static bool ilk_has_post_csc_lut(const struct intel_crtc_state *crtc_state) +{ + if (crtc_state->c8_planes) + return true; + + return crtc_state->gamma_enable && + (crtc_state->csc_mode & CSC_POSITION_BEFORE_GAMMA) != 0; +} + +static bool ilk_has_pre_csc_lut(const struct intel_crtc_state *crtc_state) +{ + return crtc_state->gamma_enable && + (crtc_state->csc_mode & CSC_POSITION_BEFORE_GAMMA) == 0; +} + +static int ilk_post_csc_lut_precision(const struct intel_crtc_state *crtc_state) +{ + if (!ilk_has_post_csc_lut(crtc_state)) + return 0; + + return ilk_gamma_mode_precision(crtc_state->gamma_mode); +} + +static int ilk_pre_csc_lut_precision(const struct intel_crtc_state *crtc_state) +{ + if (!ilk_has_pre_csc_lut(crtc_state)) + return 0; + + return ilk_gamma_mode_precision(crtc_state->gamma_mode); +} + +static int ivb_post_csc_lut_precision(const struct intel_crtc_state *crtc_state) +{ + if (crtc_state->gamma_enable && + crtc_state->gamma_mode == GAMMA_MODE_MODE_SPLIT) + return 10; + + return ilk_post_csc_lut_precision(crtc_state); +} + +static int ivb_pre_csc_lut_precision(const struct intel_crtc_state *crtc_state) +{ + if (crtc_state->gamma_enable && + crtc_state->gamma_mode == GAMMA_MODE_MODE_SPLIT) + return 10; + + return ilk_pre_csc_lut_precision(crtc_state); +} + +static int chv_post_csc_lut_precision(const struct intel_crtc_state *crtc_state) +{ + if (crtc_state->cgm_mode & CGM_PIPE_MODE_GAMMA) + return 10; + + return i965_post_csc_lut_precision(crtc_state); +} + +static int chv_pre_csc_lut_precision(const struct intel_crtc_state *crtc_state) +{ + if (crtc_state->cgm_mode & CGM_PIPE_MODE_DEGAMMA) + return 14; + + return 0; +} + +static int glk_post_csc_lut_precision(const struct intel_crtc_state *crtc_state) +{ + if (!crtc_state->gamma_enable && !crtc_state->c8_planes) + return 0; + + return ilk_gamma_mode_precision(crtc_state->gamma_mode); +} + +static int glk_pre_csc_lut_precision(const struct intel_crtc_state *crtc_state) +{ + if (!crtc_state->csc_enable) + return 0; + + return 16; +} + +static bool icl_has_post_csc_lut(const struct intel_crtc_state *crtc_state) +{ + if (crtc_state->c8_planes) + return true; + + return crtc_state->gamma_mode & POST_CSC_GAMMA_ENABLE; +} + +static bool icl_has_pre_csc_lut(const struct intel_crtc_state *crtc_state) +{ + return crtc_state->gamma_mode & PRE_CSC_GAMMA_ENABLE; +} + +static int icl_post_csc_lut_precision(const struct intel_crtc_state *crtc_state) +{ + if (!icl_has_post_csc_lut(crtc_state)) + 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_SEG: + return 16; + default: + MISSING_CASE(crtc_state->gamma_mode); + return 0; + } +} + +static int icl_pre_csc_lut_precision(const struct intel_crtc_state *crtc_state) +{ + if (!icl_has_pre_csc_lut(crtc_state)) + return 0; + + return 16; +} + +static bool err_check(const struct drm_color_lut *lut1, + const 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_lut_entries_equal(const struct drm_color_lut *lut1, + const 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; +} + +static bool intel_lut_equal(const struct drm_property_blob *blob1, + const struct drm_property_blob *blob2, + int check_size, int precision) +{ + const struct drm_color_lut *lut1, *lut2; + int lut_size1, lut_size2; + u32 err; + + if (!blob1 != !blob2) + return false; + + if (!blob1 != !precision) + return false; + + if (!blob1) + return true; + + lut_size1 = drm_color_lut_size(blob1); + lut_size2 = drm_color_lut_size(blob2); + + if (lut_size1 != lut_size2) + return false; + + if (check_size > lut_size1) + return false; + + lut1 = blob1->data; + lut2 = blob2->data; + + err = 0xffff >> precision; + + if (!check_size) + check_size = lut_size1; + + return intel_lut_entries_equal(lut1, lut2, check_size, err); +} + +static bool i9xx_lut_equal(const struct intel_crtc_state *crtc_state, + const struct drm_property_blob *blob1, + const struct drm_property_blob *blob2, + bool is_pre_csc_lut) +{ + int check_size = 0; + + if (is_pre_csc_lut) + return intel_lut_equal(blob1, blob2, 0, + i9xx_pre_csc_lut_precision(crtc_state)); + + /* 10bit mode last entry is implicit, just skip it */ + if (crtc_state->gamma_mode == GAMMA_MODE_MODE_10BIT) + check_size = 128; + + return intel_lut_equal(blob1, blob2, check_size, + i9xx_post_csc_lut_precision(crtc_state)); +} + +static bool i965_lut_equal(const struct intel_crtc_state *crtc_state, + const struct drm_property_blob *blob1, + const struct drm_property_blob *blob2, + bool is_pre_csc_lut) +{ + if (is_pre_csc_lut) + return intel_lut_equal(blob1, blob2, 0, + i9xx_pre_csc_lut_precision(crtc_state)); + else + return intel_lut_equal(blob1, blob2, 0, + i965_post_csc_lut_precision(crtc_state)); +} + +static bool chv_lut_equal(const struct intel_crtc_state *crtc_state, + const struct drm_property_blob *blob1, + const struct drm_property_blob *blob2, + bool is_pre_csc_lut) +{ + if (is_pre_csc_lut) + return intel_lut_equal(blob1, blob2, 0, + chv_pre_csc_lut_precision(crtc_state)); + else + return intel_lut_equal(blob1, blob2, 0, + chv_post_csc_lut_precision(crtc_state)); +} + +static bool ilk_lut_equal(const struct intel_crtc_state *crtc_state, + const struct drm_property_blob *blob1, + const struct drm_property_blob *blob2, + bool is_pre_csc_lut) +{ + if (is_pre_csc_lut) + return intel_lut_equal(blob1, blob2, 0, + ilk_pre_csc_lut_precision(crtc_state)); + else + return intel_lut_equal(blob1, blob2, 0, + ilk_post_csc_lut_precision(crtc_state)); +} + +static bool ivb_lut_equal(const struct intel_crtc_state *crtc_state, + const struct drm_property_blob *blob1, + const struct drm_property_blob *blob2, + bool is_pre_csc_lut) +{ + if (is_pre_csc_lut) + return intel_lut_equal(blob1, blob2, 0, + ivb_pre_csc_lut_precision(crtc_state)); + else + return intel_lut_equal(blob1, blob2, 0, + ivb_post_csc_lut_precision(crtc_state)); +} + +static bool glk_lut_equal(const struct intel_crtc_state *crtc_state, + const struct drm_property_blob *blob1, + const struct drm_property_blob *blob2, + bool is_pre_csc_lut) +{ + if (is_pre_csc_lut) + return intel_lut_equal(blob1, blob2, 0, + glk_pre_csc_lut_precision(crtc_state)); + else + return intel_lut_equal(blob1, blob2, 0, + glk_post_csc_lut_precision(crtc_state)); +} + +static bool icl_lut_equal(const struct intel_crtc_state *crtc_state, + const struct drm_property_blob *blob1, + const struct drm_property_blob *blob2, + bool is_pre_csc_lut) +{ + int check_size = 0; + + if (is_pre_csc_lut) + return intel_lut_equal(blob1, blob2, 0, + icl_pre_csc_lut_precision(crtc_state)); + + /* hw readout broken except for the super fine segment :( */ + if ((crtc_state->gamma_mode & GAMMA_MODE_MODE_MASK) == + GAMMA_MODE_MODE_12BIT_MULTI_SEG) + check_size = 9; + + return intel_lut_equal(blob1, blob2, check_size, + icl_post_csc_lut_precision(crtc_state)); +} + +static struct drm_property_blob *i9xx_read_lut_8(struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + enum pipe pipe = crtc->pipe; + struct drm_property_blob *blob; + struct drm_color_lut *lut; + int i; + + blob = drm_property_create_blob(display->drm, + sizeof(lut[0]) * 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(display, + PALETTE(display, pipe, i)); + + i9xx_lut_8_pack(&lut[i], val); + } + + return blob; +} + +static struct drm_property_blob *i9xx_read_lut_10(struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + u32 lut_size = DISPLAY_INFO(display)->color.gamma_lut_size; + enum pipe pipe = crtc->pipe; + struct drm_property_blob *blob; + struct drm_color_lut *lut; + u32 ldw, udw; + int i; + + blob = drm_property_create_blob(display->drm, + lut_size * sizeof(lut[0]), NULL); + if (IS_ERR(blob)) + return NULL; + + lut = blob->data; + + for (i = 0; i < lut_size - 1; i++) { + ldw = intel_de_read_fw(display, + PALETTE(display, pipe, 2 * i + 0)); + udw = intel_de_read_fw(display, + PALETTE(display, pipe, 2 * i + 1)); + + i9xx_lut_10_pack(&lut[i], ldw, udw); + } + + i9xx_lut_10_pack_slope(&lut[i], ldw, udw); + + 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 && !crtc_state->c8_planes) + return; + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + crtc_state->post_csc_lut = i9xx_read_lut_8(crtc); + break; + case GAMMA_MODE_MODE_10BIT: + crtc_state->post_csc_lut = i9xx_read_lut_10(crtc); + break; + default: + MISSING_CASE(crtc_state->gamma_mode); + break; + } +} + +static struct drm_property_blob *i965_read_lut_10p6(struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + int i, lut_size = DISPLAY_INFO(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(display->drm, + sizeof(lut[0]) * 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(display, + PALETTE(display, pipe, 2 * i + 0)); + u32 udw = intel_de_read_fw(display, + PALETTE(display, pipe, 2 * i + 1)); + + i965_lut_10p6_pack(&lut[i], ldw, udw); + } + + lut[i].red = i965_lut_11p6_max_pack(intel_de_read_fw(display, PIPEGCMAX(display, pipe, 0))); + lut[i].green = i965_lut_11p6_max_pack(intel_de_read_fw(display, PIPEGCMAX(display, pipe, 1))); + lut[i].blue = i965_lut_11p6_max_pack(intel_de_read_fw(display, PIPEGCMAX(display, 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 && !crtc_state->c8_planes) + return; + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + crtc_state->post_csc_lut = i9xx_read_lut_8(crtc); + break; + case GAMMA_MODE_MODE_10BIT: + crtc_state->post_csc_lut = i965_read_lut_10p6(crtc); + break; + default: + MISSING_CASE(crtc_state->gamma_mode); + break; + } +} + +static struct drm_property_blob *chv_read_cgm_degamma(struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + int i, lut_size = DISPLAY_INFO(display)->color.degamma_lut_size; + enum pipe pipe = crtc->pipe; + struct drm_property_blob *blob; + struct drm_color_lut *lut; + + blob = drm_property_create_blob(display->drm, + sizeof(lut[0]) * 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(display, CGM_PIPE_DEGAMMA(pipe, i, 0)); + u32 udw = intel_de_read_fw(display, CGM_PIPE_DEGAMMA(pipe, i, 1)); + + chv_cgm_degamma_pack(&lut[i], ldw, udw); + } + + return blob; +} + +static struct drm_property_blob *chv_read_cgm_gamma(struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + int i, lut_size = DISPLAY_INFO(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(display->drm, + sizeof(lut[0]) * 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(display, CGM_PIPE_GAMMA(pipe, i, 0)); + u32 udw = intel_de_read_fw(display, CGM_PIPE_GAMMA(pipe, i, 1)); + + chv_cgm_gamma_pack(&lut[i], ldw, udw); + } + + return blob; +} + +static void chv_get_config(struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + crtc_state->cgm_mode = intel_de_read(display, CGM_PIPE_MODE(crtc->pipe)); + + i9xx_get_config(crtc_state); +} + +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_DEGAMMA) + crtc_state->pre_csc_lut = chv_read_cgm_degamma(crtc); + + if (crtc_state->cgm_mode & CGM_PIPE_MODE_GAMMA) + crtc_state->post_csc_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 intel_display *display = to_intel_display(crtc); + enum pipe pipe = crtc->pipe; + struct drm_property_blob *blob; + struct drm_color_lut *lut; + int i; + + blob = drm_property_create_blob(display->drm, + sizeof(lut[0]) * 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(display, 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 intel_display *display = to_intel_display(crtc); + int i, lut_size = DISPLAY_INFO(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(display->drm, + sizeof(lut[0]) * 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(display, PREC_PALETTE(pipe, i)); + + ilk_lut_10_pack(&lut[i], val); + } + + return blob; +} + +static void ilk_get_config(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + + crtc_state->csc_mode = ilk_read_csc_mode(crtc); + + i9xx_get_config(crtc_state); +} + +static void ilk_read_luts(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_property_blob **blob = + ilk_has_post_csc_lut(crtc_state) ? + &crtc_state->post_csc_lut : &crtc_state->pre_csc_lut; + + if (!crtc_state->gamma_enable && !crtc_state->c8_planes) + return; + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + *blob = ilk_read_lut_8(crtc); + break; + case GAMMA_MODE_MODE_10BIT: + *blob = ilk_read_lut_10(crtc); + break; + default: + MISSING_CASE(crtc_state->gamma_mode); + break; + } +} + +/* + * IVB/HSW Bspec / PAL_PREC_INDEX: + * "Restriction : Index auto increment mode is not + * supported and must not be enabled." + */ +static struct drm_property_blob *ivb_read_lut_10(struct intel_crtc *crtc, + u32 prec_index) +{ + struct intel_display *display = to_intel_display(crtc); + int i, lut_size = ivb_lut_10_size(prec_index); + enum pipe pipe = crtc->pipe; + struct drm_property_blob *blob; + struct drm_color_lut *lut; + + blob = drm_property_create_blob(display->drm, + sizeof(lut[0]) * lut_size, + NULL); + if (IS_ERR(blob)) + return NULL; + + lut = blob->data; + + for (i = 0; i < lut_size; i++) { + u32 val; + + intel_de_write_fw(display, PREC_PAL_INDEX(pipe), + prec_index + i); + val = intel_de_read_fw(display, PREC_PAL_DATA(pipe)); + + ilk_lut_10_pack(&lut[i], val); + } + + intel_de_write_fw(display, PREC_PAL_INDEX(pipe), + PAL_PREC_INDEX_VALUE(0)); + + return blob; +} + +static void ivb_read_luts(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_property_blob **blob = + ilk_has_post_csc_lut(crtc_state) ? + &crtc_state->post_csc_lut : &crtc_state->pre_csc_lut; + + if (!crtc_state->gamma_enable && !crtc_state->c8_planes) + return; + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + *blob = ilk_read_lut_8(crtc); + break; + case GAMMA_MODE_MODE_SPLIT: + crtc_state->pre_csc_lut = + ivb_read_lut_10(crtc, PAL_PREC_SPLIT_MODE | + PAL_PREC_INDEX_VALUE(0)); + crtc_state->post_csc_lut = + ivb_read_lut_10(crtc, PAL_PREC_SPLIT_MODE | + PAL_PREC_INDEX_VALUE(512)); + break; + case GAMMA_MODE_MODE_10BIT: + *blob = ivb_read_lut_10(crtc, PAL_PREC_INDEX_VALUE(0)); + 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 intel_display *display = to_intel_display(crtc); + int i, lut_size = ivb_lut_10_size(prec_index); + enum pipe pipe = crtc->pipe; + struct drm_property_blob *blob; + struct drm_color_lut *lut; + + blob = drm_property_create_blob(display->drm, + sizeof(lut[0]) * lut_size, + NULL); + if (IS_ERR(blob)) + return NULL; + + lut = blob->data; + + intel_de_write_fw(display, PREC_PAL_INDEX(pipe), + prec_index); + intel_de_write_fw(display, PREC_PAL_INDEX(pipe), + PAL_PREC_AUTO_INCREMENT | + prec_index); + + for (i = 0; i < lut_size; i++) { + u32 val = intel_de_read_fw(display, PREC_PAL_DATA(pipe)); + + ilk_lut_10_pack(&lut[i], val); + } + + intel_de_write_fw(display, PREC_PAL_INDEX(pipe), + PAL_PREC_INDEX_VALUE(0)); + + return blob; +} + +static void bdw_read_luts(struct intel_crtc_state *crtc_state) +{ + struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc); + struct drm_property_blob **blob = + ilk_has_post_csc_lut(crtc_state) ? + &crtc_state->post_csc_lut : &crtc_state->pre_csc_lut; + + if (!crtc_state->gamma_enable && !crtc_state->c8_planes) + return; + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + *blob = ilk_read_lut_8(crtc); + break; + case GAMMA_MODE_MODE_SPLIT: + crtc_state->pre_csc_lut = + bdw_read_lut_10(crtc, PAL_PREC_SPLIT_MODE | + PAL_PREC_INDEX_VALUE(0)); + crtc_state->post_csc_lut = + bdw_read_lut_10(crtc, PAL_PREC_SPLIT_MODE | + PAL_PREC_INDEX_VALUE(512)); + break; + case GAMMA_MODE_MODE_10BIT: + *blob = bdw_read_lut_10(crtc, PAL_PREC_INDEX_VALUE(0)); + break; + default: + MISSING_CASE(crtc_state->gamma_mode); + break; + } +} + +static struct drm_property_blob *glk_read_degamma_lut(struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + int i, lut_size = DISPLAY_INFO(display)->color.degamma_lut_size; + enum pipe pipe = crtc->pipe; + struct drm_property_blob *blob; + struct drm_color_lut *lut; + + blob = drm_property_create_blob(display->drm, + sizeof(lut[0]) * lut_size, + NULL); + if (IS_ERR(blob)) + return NULL; + + lut = blob->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(display, PRE_CSC_GAMC_INDEX(pipe), + PRE_CSC_GAMC_INDEX_VALUE(0)); + intel_de_write_fw(display, PRE_CSC_GAMC_INDEX(pipe), + PRE_CSC_GAMC_AUTO_INCREMENT | + PRE_CSC_GAMC_INDEX_VALUE(0)); + + for (i = 0; i < lut_size; i++) { + u32 val = intel_de_read_fw(display, PRE_CSC_GAMC_DATA(pipe)); + + if (DISPLAY_VER(display) >= 14) + mtl_degamma_lut_pack(&lut[i], val); + else + glk_degamma_lut_pack(&lut[i], val); + } + + intel_de_write_fw(display, PRE_CSC_GAMC_INDEX(pipe), + PRE_CSC_GAMC_INDEX_VALUE(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->csc_enable) + crtc_state->pre_csc_lut = glk_read_degamma_lut(crtc); + + if (!crtc_state->gamma_enable && !crtc_state->c8_planes) + return; + + switch (crtc_state->gamma_mode) { + case GAMMA_MODE_MODE_8BIT: + crtc_state->post_csc_lut = ilk_read_lut_8(crtc); + break; + case GAMMA_MODE_MODE_10BIT: + crtc_state->post_csc_lut = bdw_read_lut_10(crtc, PAL_PREC_INDEX_VALUE(0)); + break; + default: + MISSING_CASE(crtc_state->gamma_mode); + break; + } +} + +static struct drm_property_blob * +icl_read_lut_multi_segment(struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + int i, lut_size = DISPLAY_INFO(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(display->drm, + sizeof(lut[0]) * lut_size, + NULL); + if (IS_ERR(blob)) + return NULL; + + lut = blob->data; + + intel_de_write_fw(display, PREC_PAL_MULTI_SEG_INDEX(pipe), + PAL_PREC_MULTI_SEG_INDEX_VALUE(0)); + intel_de_write_fw(display, PREC_PAL_MULTI_SEG_INDEX(pipe), + PAL_PREC_MULTI_SEG_AUTO_INCREMENT | + PAL_PREC_MULTI_SEG_INDEX_VALUE(0)); + + for (i = 0; i < 9; i++) { + u32 ldw = intel_de_read_fw(display, PREC_PAL_MULTI_SEG_DATA(pipe)); + u32 udw = intel_de_read_fw(display, PREC_PAL_MULTI_SEG_DATA(pipe)); + + ilk_lut_12p4_pack(&lut[i], ldw, udw); + } + + intel_de_write_fw(display, PREC_PAL_MULTI_SEG_INDEX(pipe), + PAL_PREC_MULTI_SEG_INDEX_VALUE(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 (icl_has_pre_csc_lut(crtc_state)) + crtc_state->pre_csc_lut = glk_read_degamma_lut(crtc); + + if (!icl_has_post_csc_lut(crtc_state)) + return; + + switch (crtc_state->gamma_mode & GAMMA_MODE_MODE_MASK) { + case GAMMA_MODE_MODE_8BIT: + crtc_state->post_csc_lut = ilk_read_lut_8(crtc); + break; + case GAMMA_MODE_MODE_10BIT: + crtc_state->post_csc_lut = bdw_read_lut_10(crtc, PAL_PREC_INDEX_VALUE(0)); + break; + case GAMMA_MODE_MODE_12BIT_MULTI_SEG: + crtc_state->post_csc_lut = icl_read_lut_multi_segment(crtc); + break; + default: + MISSING_CASE(crtc_state->gamma_mode); + break; + } +} + +static void +xelpd_load_plane_csc_matrix(struct intel_dsb *dsb, + const struct intel_plane_state *plane_state) +{ + struct intel_display *display = to_intel_display(plane_state); + const struct drm_plane_state *state = &plane_state->uapi; + enum pipe pipe = to_intel_plane(state->plane)->pipe; + enum plane_id plane = to_intel_plane(state->plane)->id; + const struct drm_property_blob *blob = plane_state->hw.ctm; + struct drm_color_ctm_3x4 *ctm; + const u64 *input; + u16 coeffs[9] = {}; + int i, j; + + if (!icl_is_hdr_plane(display, plane) || !blob) + return; + + ctm = blob->data; + input = ctm->matrix; + + /* + * Convert fixed point S31.32 input to format supported by the + * hardware. + */ + for (i = 0, j = 0; i < ARRAY_SIZE(coeffs); i++) { + u64 abs_coeff = ((1ULL << 63) - 1) & input[j]; + + /* + * Clamp input value to min/max supported by + * hardware. + */ + abs_coeff = clamp_val(abs_coeff, 0, CTM_COEFF_4_0 - 1); + + /* sign bit */ + if (CTM_COEFF_NEGATIVE(input[j])) + 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); + + /* Skip postoffs */ + if (!((j + 2) % 4)) + j += 2; + else + j++; + } + + intel_de_write_dsb(display, dsb, PLANE_CSC_COEFF(pipe, plane, 0), + coeffs[0] << 16 | coeffs[1]); + intel_de_write_dsb(display, dsb, PLANE_CSC_COEFF(pipe, plane, 1), + coeffs[2] << 16); + + intel_de_write_dsb(display, dsb, PLANE_CSC_COEFF(pipe, plane, 2), + coeffs[3] << 16 | coeffs[4]); + intel_de_write_dsb(display, dsb, PLANE_CSC_COEFF(pipe, plane, 3), + coeffs[5] << 16); + + intel_de_write_dsb(display, dsb, PLANE_CSC_COEFF(pipe, plane, 4), + coeffs[6] << 16 | coeffs[7]); + intel_de_write_dsb(display, dsb, PLANE_CSC_COEFF(pipe, plane, 5), + coeffs[8] << 16); + + intel_de_write_dsb(display, dsb, PLANE_CSC_PREOFF(pipe, plane, 0), 0); + intel_de_write_dsb(display, dsb, PLANE_CSC_PREOFF(pipe, plane, 1), 0); + intel_de_write_dsb(display, dsb, PLANE_CSC_PREOFF(pipe, plane, 2), 0); + + /* + * Conversion from S31.32 to S0.12. BIT[12] is the signed bit + */ + intel_de_write_dsb(display, dsb, + PLANE_CSC_POSTOFF(pipe, plane, 0), + ctm_to_twos_complement(input[3], 0, 12)); + intel_de_write_dsb(display, dsb, + PLANE_CSC_POSTOFF(pipe, plane, 1), + ctm_to_twos_complement(input[7], 0, 12)); + intel_de_write_dsb(display, dsb, + PLANE_CSC_POSTOFF(pipe, plane, 2), + ctm_to_twos_complement(input[11], 0, 12)); +} + +static void +xelpd_program_plane_pre_csc_lut(struct intel_dsb *dsb, + const struct intel_plane_state *plane_state) +{ + struct intel_display *display = to_intel_display(plane_state); + const struct drm_plane_state *state = &plane_state->uapi; + enum pipe pipe = to_intel_plane(state->plane)->pipe; + enum plane_id plane = to_intel_plane(state->plane)->id; + const struct drm_color_lut32 *pre_csc_lut = plane_state->hw.degamma_lut->data; + u32 i, lut_size; + + if (icl_is_hdr_plane(display, plane)) { + lut_size = 128; + + intel_de_write_dsb(display, dsb, + PLANE_PRE_CSC_GAMC_INDEX_ENH(pipe, plane, 0), + PLANE_PAL_PREC_AUTO_INCREMENT); + + if (pre_csc_lut) { + for (i = 0; i < lut_size; i++) { + u32 lut_val = drm_color_lut32_extract(pre_csc_lut[i].green, 24); + + intel_de_write_dsb(display, dsb, + PLANE_PRE_CSC_GAMC_DATA_ENH(pipe, plane, 0), + lut_val); + } + + /* Program the max register to clamp values > 1.0. */ + /* TODO: Restrict to 0x7ffffff */ + do { + intel_de_write_dsb(display, dsb, + PLANE_PRE_CSC_GAMC_DATA_ENH(pipe, plane, 0), + (1 << 24)); + } while (i++ < 130); + } else { + for (i = 0; i < lut_size; i++) { + u32 v = (i * ((1 << 24) - 1)) / (lut_size - 1); + + intel_de_write_dsb(display, dsb, + PLANE_PRE_CSC_GAMC_DATA_ENH(pipe, plane, 0), v); + } + + do { + intel_de_write_dsb(display, dsb, + PLANE_PRE_CSC_GAMC_DATA_ENH(pipe, plane, 0), + 1 << 24); + } while (i++ < 130); + } + + intel_de_write_dsb(display, dsb, PLANE_PRE_CSC_GAMC_INDEX_ENH(pipe, plane, 0), 0); + } +} + +static void +xelpd_program_plane_post_csc_lut(struct intel_dsb *dsb, + const struct intel_plane_state *plane_state) +{ + struct intel_display *display = to_intel_display(plane_state); + const struct drm_plane_state *state = &plane_state->uapi; + enum pipe pipe = to_intel_plane(state->plane)->pipe; + enum plane_id plane = to_intel_plane(state->plane)->id; + const struct drm_color_lut32 *post_csc_lut = plane_state->hw.gamma_lut->data; + u32 i, lut_size, lut_val; + + if (icl_is_hdr_plane(display, plane)) { + intel_de_write_dsb(display, dsb, PLANE_POST_CSC_GAMC_INDEX_ENH(pipe, plane, 0), + PLANE_PAL_PREC_AUTO_INCREMENT); + /* TODO: Add macro */ + intel_de_write_dsb(display, dsb, PLANE_POST_CSC_GAMC_SEG0_INDEX_ENH(pipe, plane, 0), + PLANE_PAL_PREC_AUTO_INCREMENT); + if (post_csc_lut) { + lut_size = 32; + for (i = 0; i < lut_size; i++) { + lut_val = drm_color_lut32_extract(post_csc_lut[i].green, 24); + + intel_de_write_dsb(display, dsb, + PLANE_POST_CSC_GAMC_DATA_ENH(pipe, plane, 0), + lut_val); + } + + /* Segment 2 */ + do { + intel_de_write_dsb(display, dsb, + PLANE_POST_CSC_GAMC_DATA_ENH(pipe, plane, 0), + (1 << 24)); + } while (i++ < 34); + } else { + /*TODO: Add for segment 0 */ + lut_size = 32; + for (i = 0; i < lut_size; i++) { + u32 v = (i * ((1 << 24) - 1)) / (lut_size - 1); + + intel_de_write_dsb(display, dsb, + PLANE_POST_CSC_GAMC_DATA_ENH(pipe, plane, 0), v); + } + + do { + intel_de_write_dsb(display, dsb, + PLANE_POST_CSC_GAMC_DATA_ENH(pipe, plane, 0), + 1 << 24); + } while (i++ < 34); + } + + intel_de_write_dsb(display, dsb, PLANE_POST_CSC_GAMC_INDEX_ENH(pipe, plane, 0), 0); + intel_de_write_dsb(display, dsb, + PLANE_POST_CSC_GAMC_SEG0_INDEX_ENH(pipe, plane, 0), 0); + } +} + +static void +xelpd_plane_load_luts(struct intel_dsb *dsb, const struct intel_plane_state *plane_state) +{ + if (plane_state->hw.degamma_lut) + xelpd_program_plane_pre_csc_lut(dsb, plane_state); + + if (plane_state->hw.gamma_lut) + xelpd_program_plane_post_csc_lut(dsb, plane_state); +} + +static u32 glk_3dlut_10(const struct drm_color_lut32 *color) +{ + return REG_FIELD_PREP(LUT_3D_DATA_RED_MASK, drm_color_lut32_extract(color->red, 10)) | + REG_FIELD_PREP(LUT_3D_DATA_GREEN_MASK, drm_color_lut32_extract(color->green, 10)) | + REG_FIELD_PREP(LUT_3D_DATA_BLUE_MASK, drm_color_lut32_extract(color->blue, 10)); +} + +static void glk_load_lut_3d(struct intel_dsb *dsb, + struct intel_crtc *crtc, + const struct drm_property_blob *blob) +{ + struct intel_display *display = to_intel_display(crtc->base.dev); + const struct drm_color_lut32 *lut = blob->data; + int i, lut_size = drm_color_lut32_size(blob); + enum pipe pipe = crtc->pipe; + + if (!dsb && intel_de_read(display, LUT_3D_CTL(pipe)) & LUT_3D_READY) { + drm_err(display->drm, "[CRTC:%d:%s] 3D LUT not ready, not loading LUTs\n", + crtc->base.base.id, crtc->base.name); + return; + } + + intel_de_write_dsb(display, dsb, LUT_3D_INDEX(pipe), LUT_3D_AUTO_INCREMENT); + for (i = 0; i < lut_size; i++) + intel_de_write_dsb(display, dsb, LUT_3D_DATA(pipe), glk_3dlut_10(&lut[i])); + intel_de_write_dsb(display, dsb, LUT_3D_INDEX(pipe), 0); +} + +static void glk_lut_3d_commit(struct intel_dsb *dsb, struct intel_crtc *crtc, bool enable) +{ + struct intel_display *display = to_intel_display(crtc); + enum pipe pipe = crtc->pipe; + u32 val = 0; + + if (!dsb && intel_de_read(display, LUT_3D_CTL(pipe)) & LUT_3D_READY) { + drm_err(display->drm, "[CRTC:%d:%s] 3D LUT not ready, not committing change\n", + crtc->base.base.id, crtc->base.name); + return; + } + + if (enable) + val = LUT_3D_ENABLE | LUT_3D_READY | LUT_3D_BIND_PLANE_1; + + intel_de_write_dsb(display, dsb, LUT_3D_CTL(pipe), val); +} + +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, + .lut_equal = chv_lut_equal, + .read_csc = chv_read_csc, + .get_config = chv_get_config, +}; + +static const struct intel_color_funcs vlv_color_funcs = { + .color_check = vlv_color_check, + .color_commit_arm = i9xx_color_commit_arm, + .load_luts = vlv_load_luts, + .read_luts = i965_read_luts, + .lut_equal = i965_lut_equal, + .read_csc = vlv_read_csc, + .get_config = i9xx_get_config, +}; + +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, + .lut_equal = i965_lut_equal, + .get_config = i9xx_get_config, +}; + +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, + .lut_equal = i9xx_lut_equal, + .get_config = i9xx_get_config, +}; + +static const struct intel_color_funcs tgl_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, + .lut_equal = icl_lut_equal, + .read_csc = icl_read_csc, + .get_config = skl_get_config, + .load_plane_csc_matrix = xelpd_load_plane_csc_matrix, + .load_plane_luts = xelpd_plane_load_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, + .color_post_update = icl_color_post_update, + .load_luts = icl_load_luts, + .read_luts = icl_read_luts, + .lut_equal = icl_lut_equal, + .read_csc = icl_read_csc, + .get_config = skl_get_config, +}; + +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, + .lut_equal = glk_lut_equal, + .read_csc = skl_read_csc, + .get_config = skl_get_config, +}; + +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 = bdw_read_luts, + .lut_equal = ivb_lut_equal, + .read_csc = skl_read_csc, + .get_config = skl_get_config, +}; + +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 = bdw_read_luts, + .lut_equal = ivb_lut_equal, + .read_csc = ilk_read_csc, + .get_config = hsw_get_config, +}; + +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 = ivb_read_luts, + .lut_equal = ivb_lut_equal, + .read_csc = ilk_read_csc, + .get_config = hsw_get_config, +}; + +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 = ivb_read_luts, + .lut_equal = ivb_lut_equal, + .read_csc = ilk_read_csc, + .get_config = ilk_get_config, +}; + +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, + .lut_equal = ilk_lut_equal, + .read_csc = ilk_read_csc, + .get_config = ilk_get_config, +}; + +void intel_color_plane_commit_arm(struct intel_dsb *dsb, + const struct intel_plane_state *plane_state) +{ + struct intel_display *display = to_intel_display(plane_state); + struct intel_crtc *crtc = to_intel_crtc(plane_state->uapi.crtc); + + if (crtc && intel_color_crtc_has_3dlut(display, crtc->pipe)) + glk_lut_3d_commit(dsb, crtc, !!plane_state->hw.lut_3d); +} + +static void +intel_color_load_plane_csc_matrix(struct intel_dsb *dsb, + const struct intel_plane_state *plane_state) +{ + struct intel_display *display = to_intel_display(plane_state); + + if (display->color.funcs->load_plane_csc_matrix) + display->color.funcs->load_plane_csc_matrix(dsb, plane_state); +} + +static void +intel_color_load_plane_luts(struct intel_dsb *dsb, + const struct intel_plane_state *plane_state) +{ + struct intel_display *display = to_intel_display(plane_state); + + if (display->color.funcs->load_plane_luts) + display->color.funcs->load_plane_luts(dsb, plane_state); +} + +bool +intel_color_crtc_has_3dlut(struct intel_display *display, enum pipe pipe) +{ + if (DISPLAY_VER(display) >= 12) + return pipe == PIPE_A || pipe == PIPE_B; + else + return false; +} + +static void +intel_color_load_3dlut(struct intel_dsb *dsb, + const struct intel_plane_state *plane_state) +{ + struct intel_display *display = to_intel_display(plane_state); + struct intel_crtc *crtc = to_intel_crtc(plane_state->uapi.crtc); + + if (crtc && intel_color_crtc_has_3dlut(display, crtc->pipe)) + glk_load_lut_3d(dsb, crtc, plane_state->hw.lut_3d); +} + +void intel_color_plane_program_pipeline(struct intel_dsb *dsb, + const struct intel_plane_state *plane_state) +{ + if (plane_state->hw.ctm) + intel_color_load_plane_csc_matrix(dsb, plane_state); + if (plane_state->hw.degamma_lut || plane_state->hw.gamma_lut) + intel_color_load_plane_luts(dsb, plane_state); + if (plane_state->hw.lut_3d) + intel_color_load_3dlut(dsb, plane_state); +} + +void intel_color_crtc_init(struct intel_crtc *crtc) +{ + struct intel_display *display = to_intel_display(crtc); + int degamma_lut_size, gamma_lut_size; + bool has_ctm; + + drm_mode_crtc_set_gamma_size(&crtc->base, 256); + + gamma_lut_size = DISPLAY_INFO(display)->color.gamma_lut_size; + degamma_lut_size = DISPLAY_INFO(display)->color.degamma_lut_size; + has_ctm = DISPLAY_VER(display) >= 5; + + /* + * "DPALETTE_A: NOTE: The 8-bit (non-10-bit) mode is the + * only mode supported by Alviso and Grantsdale." + * + * Actually looks like this affects all of gen3. + * Confirmed on alv,cst,pnv. Mobile gen2 parts (alm,mgm) + * are confirmed not to suffer from this restriction. + */ + if (DISPLAY_VER(display) == 3 && crtc->pipe == PIPE_A) + gamma_lut_size = 256; + + drm_crtc_enable_color_mgmt(&crtc->base, degamma_lut_size, + has_ctm, gamma_lut_size); +} + +int intel_color_init(struct intel_display *display) +{ + struct drm_property_blob *blob; + + if (DISPLAY_VER(display) != 10) + return 0; + + blob = create_linear_lut(display, + DISPLAY_INFO(display)->color.degamma_lut_size); + if (IS_ERR(blob)) + return PTR_ERR(blob); + + display->color.glk_linear_degamma_lut = blob; + + return 0; +} + +void intel_color_init_hooks(struct intel_display *display) +{ + if (HAS_GMCH(display)) { + if (display->platform.cherryview) + display->color.funcs = &chv_color_funcs; + else if (display->platform.valleyview) + display->color.funcs = &vlv_color_funcs; + else if (DISPLAY_VER(display) >= 4) + display->color.funcs = &i965_color_funcs; + else + display->color.funcs = &i9xx_color_funcs; + } else { + if (DISPLAY_VER(display) >= 12) + display->color.funcs = &tgl_color_funcs; + else if (DISPLAY_VER(display) == 11) + display->color.funcs = &icl_color_funcs; + else if (DISPLAY_VER(display) == 10) + display->color.funcs = &glk_color_funcs; + else if (DISPLAY_VER(display) == 9) + display->color.funcs = &skl_color_funcs; + else if (DISPLAY_VER(display) == 8) + display->color.funcs = &bdw_color_funcs; + else if (display->platform.haswell) + display->color.funcs = &hsw_color_funcs; + else if (DISPLAY_VER(display) == 7) + display->color.funcs = &ivb_color_funcs; + else + display->color.funcs = &ilk_color_funcs; + } +} 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..f3963a8d1 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_color.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_COLOR_H__ +#define __INTEL_COLOR_H__ + +#include + +struct intel_atomic_state; +struct intel_crtc_state; +struct intel_crtc; +struct intel_display; +struct intel_dsb; +struct intel_plane_state; +struct drm_property_blob; +enum pipe; + +void intel_color_init_hooks(struct intel_display *display); +int intel_color_init(struct intel_display *display); +void intel_color_crtc_init(struct intel_crtc *crtc); +int intel_color_check(struct intel_atomic_state *state, + struct intel_crtc *crtc); +void intel_color_prepare_commit(struct intel_atomic_state *state, + struct intel_crtc *crtc); +void intel_color_cleanup_commit(struct intel_crtc_state *crtc_state); +bool intel_color_uses_dsb(const struct intel_crtc_state *crtc_state); +bool intel_color_uses_chained_dsb(const struct intel_crtc_state *crtc_state); +bool intel_color_uses_gosub_dsb(const struct intel_crtc_state *crtc_state); +void intel_color_wait_commit(const struct intel_crtc_state *crtc_state); +void intel_color_commit_noarm(struct intel_dsb *dsb, + const struct intel_crtc_state *crtc_state); +void intel_color_commit_arm(struct intel_dsb *dsb, + const struct intel_crtc_state *crtc_state); +void intel_color_post_update(const struct intel_crtc_state *crtc_state); +void intel_color_load_luts(const struct intel_crtc_state *crtc_state); +void intel_color_modeset(const struct intel_crtc_state *crtc_state); +void intel_color_get_config(struct intel_crtc_state *crtc_state); +bool intel_color_lut_equal(const struct intel_crtc_state *crtc_state, + const struct drm_property_blob *blob1, + const struct drm_property_blob *blob2, + bool is_pre_csc_lut); +void intel_color_assert_luts(const struct intel_crtc_state *crtc_state); +void intel_color_plane_program_pipeline(struct intel_dsb *dsb, + const struct intel_plane_state *plane_state); +void intel_color_plane_commit_arm(struct intel_dsb *dsb, + const struct intel_plane_state *plane_state); +bool intel_color_crtc_has_3dlut(struct intel_display *display, enum pipe pipe); +u32 intel_color_background_color_drm_to_hw(u64 drm_background_color); +u64 intel_color_background_color_hw_to_drm(u32 hw_background_color); + +#endif /* __INTEL_COLOR_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_color_pipeline.c b/drivers/gpu/drm/i915/display/intel_color_pipeline.c new file mode 100644 index 000000000..6cf8080ee --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_color_pipeline.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2025 Intel Corporation + */ +#include + +#include "intel_color.h" +#include "intel_colorop.h" +#include "intel_color_pipeline.h" +#include "intel_de.h" +#include "intel_display_types.h" +#include "skl_universal_plane.h" + +#define MAX_COLOR_PIPELINES 1 +#define MAX_COLOROP 4 +#define PLANE_DEGAMMA_SIZE 128 +#define PLANE_GAMMA_SIZE 32 + +static const struct drm_colorop_funcs intel_colorop_funcs = { + .destroy = intel_colorop_destroy, +}; + +/* + * 3DLUT can be bound to all three HDR planes. However, even with the latest + * color pipeline UAPI, there is no good way to represent a HW block which + * can be shared/attached at different stages of the pipeline. So right now, + * we expose 3DLUT only attached with the primary plane. + * + * That way we don't confuse the userspace with opaque commit failures + * on trying to enable it on multiple planes which would otherwise make + * the pipeline totally unusable. + */ +static const enum intel_color_block xe3plpd_primary_plane_pipeline[] = { + INTEL_PLANE_CB_PRE_CSC_LUT, + INTEL_PLANE_CB_CSC, + INTEL_PLANE_CB_3DLUT, + INTEL_PLANE_CB_POST_CSC_LUT, +}; + +static const enum intel_color_block hdr_plane_pipeline[] = { + INTEL_PLANE_CB_PRE_CSC_LUT, + INTEL_PLANE_CB_CSC, + INTEL_PLANE_CB_POST_CSC_LUT, +}; + +static bool plane_has_3dlut(struct intel_display *display, enum pipe pipe, + struct drm_plane *plane) +{ + return (DISPLAY_VER(display) >= 35 && + intel_color_crtc_has_3dlut(display, pipe) && + plane->type == DRM_PLANE_TYPE_PRIMARY); +} + +static +struct intel_colorop *intel_color_pipeline_plane_add_colorop(struct drm_plane *plane, + struct intel_colorop *prev, + enum intel_color_block id) +{ + struct drm_device *dev = plane->dev; + struct intel_colorop *colorop; + int ret; + + colorop = intel_colorop_create(id); + + if (IS_ERR(colorop)) + return colorop; + + switch (id) { + case INTEL_PLANE_CB_PRE_CSC_LUT: + ret = drm_plane_colorop_curve_1d_lut_init(dev, + &colorop->base, plane, + &intel_colorop_funcs, + PLANE_DEGAMMA_SIZE, + DRM_COLOROP_LUT1D_INTERPOLATION_LINEAR, + DRM_COLOROP_FLAG_ALLOW_BYPASS); + break; + case INTEL_PLANE_CB_CSC: + ret = drm_plane_colorop_ctm_3x4_init(dev, &colorop->base, plane, + &intel_colorop_funcs, + DRM_COLOROP_FLAG_ALLOW_BYPASS); + break; + case INTEL_PLANE_CB_3DLUT: + ret = drm_plane_colorop_3dlut_init(dev, &colorop->base, plane, + &intel_colorop_funcs, 17, + DRM_COLOROP_LUT3D_INTERPOLATION_TETRAHEDRAL, + true); + break; + case INTEL_PLANE_CB_POST_CSC_LUT: + ret = drm_plane_colorop_curve_1d_lut_init(dev, &colorop->base, plane, + &intel_colorop_funcs, + PLANE_GAMMA_SIZE, + DRM_COLOROP_LUT1D_INTERPOLATION_LINEAR, + DRM_COLOROP_FLAG_ALLOW_BYPASS); + break; + default: + drm_err(plane->dev, "Invalid colorop id [%d]", id); + ret = -EINVAL; + } + + if (ret) + goto cleanup; + + if (prev) + drm_colorop_set_next_property(&prev->base, &colorop->base); + + return colorop; + +cleanup: + intel_colorop_destroy(&colorop->base); + return ERR_PTR(ret); +} + +static +int _intel_color_pipeline_plane_init(struct drm_plane *plane, struct drm_prop_enum_list *list, + enum pipe pipe) +{ + struct drm_device *dev = plane->dev; + struct intel_display *display = to_intel_display(dev); + struct intel_colorop *colorop[MAX_COLOROP]; + struct intel_colorop *prev = NULL; + const enum intel_color_block *pipeline; + int pipeline_len; + int ret = 0; + int i; + + if (plane_has_3dlut(display, pipe, plane)) { + pipeline = xe3plpd_primary_plane_pipeline; + pipeline_len = ARRAY_SIZE(xe3plpd_primary_plane_pipeline); + } else { + pipeline = hdr_plane_pipeline; + pipeline_len = ARRAY_SIZE(hdr_plane_pipeline); + } + + for (i = 0; i < pipeline_len; i++) { + colorop[i] = intel_color_pipeline_plane_add_colorop(plane, prev, + pipeline[i]); + if (IS_ERR(colorop[i])) { + ret = PTR_ERR(colorop[i]); + goto cleanup; + } + + prev = colorop[i]; + } + + list->type = colorop[0]->base.base.id; + list->name = kasprintf(GFP_KERNEL, "Color Pipeline %d", colorop[0]->base.base.id); + + return 0; + +cleanup: + while (--i >= 0) + intel_colorop_destroy(&colorop[i]->base); + return ret; +} + +int intel_color_pipeline_plane_init(struct drm_plane *plane, enum pipe pipe) +{ + struct drm_device *dev = plane->dev; + struct intel_display *display = to_intel_display(dev); + struct drm_prop_enum_list pipelines[MAX_COLOR_PIPELINES] = {}; + int len = 0; + int ret = 0; + int i; + + /* Currently expose pipeline only for HDR planes */ + if (!icl_is_hdr_plane(display, to_intel_plane(plane)->id)) + return 0; + + /* Add pipeline consisting of transfer functions */ + ret = _intel_color_pipeline_plane_init(plane, &pipelines[len], pipe); + if (ret) + goto out; + len++; + + ret = drm_plane_create_color_pipeline_property(plane, pipelines, len); + + for (i = 0; i < len; i++) + kfree(pipelines[i].name); + +out: + return ret; +} diff --git a/drivers/gpu/drm/i915/display/intel_color_pipeline.h b/drivers/gpu/drm/i915/display/intel_color_pipeline.h new file mode 100644 index 000000000..a457d306d --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_color_pipeline.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2025 Intel Corporation + */ + +#ifndef __INTEL_COLOR_PIPELINE_H__ +#define __INTEL_COLOR_PIPELINE_H__ + +struct drm_plane; +enum pipe; + +int intel_color_pipeline_plane_init(struct drm_plane *plane, enum pipe pipe); + +#endif /* __INTEL_COLOR_PIPELINE_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_color_regs.h b/drivers/gpu/drm/i915/display/intel_color_regs.h new file mode 100644 index 000000000..c370b6029 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_color_regs.h @@ -0,0 +1,348 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2023 Intel Corporation + */ + +#ifndef __INTEL_COLOR_REGS_H__ +#define __INTEL_COLOR_REGS_H__ + +#include "intel_display_reg_defs.h" + +/* GMCH palette */ +#define _PALETTE_A 0xa000 +#define _PALETTE_B 0xa800 +#define _CHV_PALETTE_C 0xc000 +/* 8bit mode / i965+ 10.6 interpolated mode ldw/udw */ +#define PALETTE_RED_MASK REG_GENMASK(23, 16) +#define PALETTE_GREEN_MASK REG_GENMASK(15, 8) +#define PALETTE_BLUE_MASK REG_GENMASK(7, 0) +/* pre-i965 10bit interpolated mode ldw */ +#define PALETTE_10BIT_RED_LDW_MASK REG_GENMASK(23, 16) +#define PALETTE_10BIT_GREEN_LDW_MASK REG_GENMASK(15, 8) +#define PALETTE_10BIT_BLUE_LDW_MASK REG_GENMASK(7, 0) +/* pre-i965 10bit interpolated mode udw */ +#define PALETTE_10BIT_RED_EXP_MASK REG_GENMASK(23, 22) +#define PALETTE_10BIT_RED_MANT_MASK REG_GENMASK(21, 18) +#define PALETTE_10BIT_RED_UDW_MASK REG_GENMASK(17, 16) +#define PALETTE_10BIT_GREEN_EXP_MASK REG_GENMASK(15, 14) +#define PALETTE_10BIT_GREEN_MANT_MASK REG_GENMASK(13, 10) +#define PALETTE_10BIT_GREEN_UDW_MASK REG_GENMASK(9, 8) +#define PALETTE_10BIT_BLUE_EXP_MASK REG_GENMASK(7, 6) +#define PALETTE_10BIT_BLUE_MANT_MASK REG_GENMASK(5, 2) +#define PALETTE_10BIT_BLUE_UDW_MASK REG_GENMASK(1, 0) +#define PALETTE(dev_priv, pipe, i) _MMIO(DISPLAY_MMIO_BASE(dev_priv) + \ + _PICK_EVEN_2RANGES(pipe, 2, \ + _PALETTE_A, _PALETTE_B, \ + _CHV_PALETTE_C, _CHV_PALETTE_C) + \ + (i) * 4) + +/* i965/g4x/vlv/chv */ +#define _PIPEAGCMAX 0x70010 +#define _PIPEBGCMAX 0x71010 +#define PIPEGCMAX(dev_priv, pipe, i) _MMIO_PIPE2(dev_priv, pipe, _PIPEAGCMAX + (i) * 4) /* u1.16 */ + +/* ilk+ palette */ +#define _LGC_PALETTE_A 0x4a000 +#define _LGC_PALETTE_B 0x4a800 +/* see PALETTE_* for the bits */ +#define LGC_PALETTE(pipe, i) _MMIO(_PIPE(pipe, _LGC_PALETTE_A, _LGC_PALETTE_B) + (i) * 4) + +/* ilk/snb precision palette */ +#define _PREC_PALETTE_A 0x4b000 +#define _PREC_PALETTE_B 0x4c000 +/* 10bit mode */ +#define PREC_PALETTE_10_RED_MASK REG_GENMASK(29, 20) +#define PREC_PALETTE_10_GREEN_MASK REG_GENMASK(19, 10) +#define PREC_PALETTE_10_BLUE_MASK REG_GENMASK(9, 0) +/* 12.4 interpolated mode ldw */ +#define PREC_PALETTE_12P4_RED_LDW_MASK REG_GENMASK(29, 24) +#define PREC_PALETTE_12P4_GREEN_LDW_MASK REG_GENMASK(19, 14) +#define PREC_PALETTE_12P4_BLUE_LDW_MASK REG_GENMASK(9, 4) +/* 12.4 interpolated mode udw */ +#define PREC_PALETTE_12P4_RED_UDW_MASK REG_GENMASK(29, 20) +#define PREC_PALETTE_12P4_GREEN_UDW_MASK REG_GENMASK(19, 10) +#define PREC_PALETTE_12P4_BLUE_UDW_MASK REG_GENMASK(9, 0) +#define PREC_PALETTE(pipe, i) _MMIO(_PIPE(pipe, _PREC_PALETTE_A, _PREC_PALETTE_B) + (i) * 4) + +#define _PREC_PIPEAGCMAX 0x4d000 +#define _PREC_PIPEBGCMAX 0x4d010 +#define PREC_PIPEGCMAX(pipe, i) _MMIO(_PIPE(pipe, _PIPEAGCMAX, _PIPEBGCMAX) + (i) * 4) /* u1.16 */ + +#define _GAMMA_MODE_A 0x4a480 +#define _GAMMA_MODE_B 0x4ac80 +#define GAMMA_MODE(pipe) _MMIO_PIPE(pipe, _GAMMA_MODE_A, _GAMMA_MODE_B) +#define PRE_CSC_GAMMA_ENABLE REG_BIT(31) /* icl+ */ +#define POST_CSC_GAMMA_ENABLE REG_BIT(30) /* icl+ */ +#define PALETTE_ANTICOL_DISABLE REG_BIT(15) /* skl+ */ +#define GAMMA_MODE_MODE_MASK REG_GENMASK(1, 0) +#define GAMMA_MODE_MODE_8BIT REG_FIELD_PREP(GAMMA_MODE_MODE_MASK, 0) +#define GAMMA_MODE_MODE_10BIT REG_FIELD_PREP(GAMMA_MODE_MODE_MASK, 1) +#define GAMMA_MODE_MODE_12BIT REG_FIELD_PREP(GAMMA_MODE_MODE_MASK, 2) +#define GAMMA_MODE_MODE_SPLIT REG_FIELD_PREP(GAMMA_MODE_MODE_MASK, 3) /* ivb-bdw */ +#define GAMMA_MODE_MODE_12BIT_MULTI_SEG REG_FIELD_PREP(GAMMA_MODE_MODE_MASK, 3) /* icl-tgl */ + +/* pipe CSC */ +#define _PIPE_A_CSC_COEFF_RY_GY 0x49010 +#define _PIPE_A_CSC_COEFF_BY 0x49014 +#define _PIPE_A_CSC_COEFF_RU_GU 0x49018 +#define _PIPE_A_CSC_COEFF_BU 0x4901c +#define _PIPE_A_CSC_COEFF_RV_GV 0x49020 +#define _PIPE_A_CSC_COEFF_BV 0x49024 + +#define _PIPE_A_CSC_MODE 0x49028 +#define ICL_CSC_ENABLE (1 << 31) /* icl+ */ +#define ICL_OUTPUT_CSC_ENABLE (1 << 30) /* icl+ */ +#define CSC_BLACK_SCREEN_OFFSET (1 << 2) /* ilk/snb */ +#define CSC_POSITION_BEFORE_GAMMA (1 << 1) /* pre-glk */ +#define CSC_MODE_YUV_TO_RGB (1 << 0) /* ilk/snb */ + +#define _PIPE_A_CSC_PREOFF_HI 0x49030 +#define _PIPE_A_CSC_PREOFF_ME 0x49034 +#define _PIPE_A_CSC_PREOFF_LO 0x49038 +#define _PIPE_A_CSC_POSTOFF_HI 0x49040 +#define _PIPE_A_CSC_POSTOFF_ME 0x49044 +#define _PIPE_A_CSC_POSTOFF_LO 0x49048 + +#define _PIPE_B_CSC_COEFF_RY_GY 0x49110 +#define _PIPE_B_CSC_COEFF_BY 0x49114 +#define _PIPE_B_CSC_COEFF_RU_GU 0x49118 +#define _PIPE_B_CSC_COEFF_BU 0x4911c +#define _PIPE_B_CSC_COEFF_RV_GV 0x49120 +#define _PIPE_B_CSC_COEFF_BV 0x49124 +#define _PIPE_B_CSC_MODE 0x49128 +#define _PIPE_B_CSC_PREOFF_HI 0x49130 +#define _PIPE_B_CSC_PREOFF_ME 0x49134 +#define _PIPE_B_CSC_PREOFF_LO 0x49138 +#define _PIPE_B_CSC_POSTOFF_HI 0x49140 +#define _PIPE_B_CSC_POSTOFF_ME 0x49144 +#define _PIPE_B_CSC_POSTOFF_LO 0x49148 + +#define PIPE_CSC_COEFF_RY_GY(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_COEFF_RY_GY, _PIPE_B_CSC_COEFF_RY_GY) +#define PIPE_CSC_COEFF_BY(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_COEFF_BY, _PIPE_B_CSC_COEFF_BY) +#define PIPE_CSC_COEFF_RU_GU(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_COEFF_RU_GU, _PIPE_B_CSC_COEFF_RU_GU) +#define PIPE_CSC_COEFF_BU(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_COEFF_BU, _PIPE_B_CSC_COEFF_BU) +#define PIPE_CSC_COEFF_RV_GV(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_COEFF_RV_GV, _PIPE_B_CSC_COEFF_RV_GV) +#define PIPE_CSC_COEFF_BV(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_COEFF_BV, _PIPE_B_CSC_COEFF_BV) +#define PIPE_CSC_MODE(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_MODE, _PIPE_B_CSC_MODE) +#define PIPE_CSC_PREOFF_HI(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_PREOFF_HI, _PIPE_B_CSC_PREOFF_HI) +#define PIPE_CSC_PREOFF_ME(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_PREOFF_ME, _PIPE_B_CSC_PREOFF_ME) +#define PIPE_CSC_PREOFF_LO(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_PREOFF_LO, _PIPE_B_CSC_PREOFF_LO) +#define PIPE_CSC_POSTOFF_HI(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_POSTOFF_HI, _PIPE_B_CSC_POSTOFF_HI) +#define PIPE_CSC_POSTOFF_ME(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_POSTOFF_ME, _PIPE_B_CSC_POSTOFF_ME) +#define PIPE_CSC_POSTOFF_LO(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_POSTOFF_LO, _PIPE_B_CSC_POSTOFF_LO) + +/* Pipe Output CSC */ +#define _PIPE_A_OUTPUT_CSC_COEFF_RY_GY 0x49050 +#define _PIPE_A_OUTPUT_CSC_COEFF_BY 0x49054 +#define _PIPE_A_OUTPUT_CSC_COEFF_RU_GU 0x49058 +#define _PIPE_A_OUTPUT_CSC_COEFF_BU 0x4905c +#define _PIPE_A_OUTPUT_CSC_COEFF_RV_GV 0x49060 +#define _PIPE_A_OUTPUT_CSC_COEFF_BV 0x49064 +#define _PIPE_A_OUTPUT_CSC_PREOFF_HI 0x49068 +#define _PIPE_A_OUTPUT_CSC_PREOFF_ME 0x4906c +#define _PIPE_A_OUTPUT_CSC_PREOFF_LO 0x49070 +#define _PIPE_A_OUTPUT_CSC_POSTOFF_HI 0x49074 +#define _PIPE_A_OUTPUT_CSC_POSTOFF_ME 0x49078 +#define _PIPE_A_OUTPUT_CSC_POSTOFF_LO 0x4907c + +#define _PIPE_B_OUTPUT_CSC_COEFF_RY_GY 0x49150 +#define _PIPE_B_OUTPUT_CSC_COEFF_BY 0x49154 +#define _PIPE_B_OUTPUT_CSC_COEFF_RU_GU 0x49158 +#define _PIPE_B_OUTPUT_CSC_COEFF_BU 0x4915c +#define _PIPE_B_OUTPUT_CSC_COEFF_RV_GV 0x49160 +#define _PIPE_B_OUTPUT_CSC_COEFF_BV 0x49164 +#define _PIPE_B_OUTPUT_CSC_PREOFF_HI 0x49168 +#define _PIPE_B_OUTPUT_CSC_PREOFF_ME 0x4916c +#define _PIPE_B_OUTPUT_CSC_PREOFF_LO 0x49170 +#define _PIPE_B_OUTPUT_CSC_POSTOFF_HI 0x49174 +#define _PIPE_B_OUTPUT_CSC_POSTOFF_ME 0x49178 +#define _PIPE_B_OUTPUT_CSC_POSTOFF_LO 0x4917c + +#define PIPE_CSC_OUTPUT_COEFF_RY_GY(pipe) _MMIO_PIPE(pipe,\ + _PIPE_A_OUTPUT_CSC_COEFF_RY_GY,\ + _PIPE_B_OUTPUT_CSC_COEFF_RY_GY) +#define PIPE_CSC_OUTPUT_COEFF_BY(pipe) _MMIO_PIPE(pipe, \ + _PIPE_A_OUTPUT_CSC_COEFF_BY, \ + _PIPE_B_OUTPUT_CSC_COEFF_BY) +#define PIPE_CSC_OUTPUT_COEFF_RU_GU(pipe) _MMIO_PIPE(pipe, \ + _PIPE_A_OUTPUT_CSC_COEFF_RU_GU, \ + _PIPE_B_OUTPUT_CSC_COEFF_RU_GU) +#define PIPE_CSC_OUTPUT_COEFF_BU(pipe) _MMIO_PIPE(pipe, \ + _PIPE_A_OUTPUT_CSC_COEFF_BU, \ + _PIPE_B_OUTPUT_CSC_COEFF_BU) +#define PIPE_CSC_OUTPUT_COEFF_RV_GV(pipe) _MMIO_PIPE(pipe, \ + _PIPE_A_OUTPUT_CSC_COEFF_RV_GV, \ + _PIPE_B_OUTPUT_CSC_COEFF_RV_GV) +#define PIPE_CSC_OUTPUT_COEFF_BV(pipe) _MMIO_PIPE(pipe, \ + _PIPE_A_OUTPUT_CSC_COEFF_BV, \ + _PIPE_B_OUTPUT_CSC_COEFF_BV) +#define PIPE_CSC_OUTPUT_PREOFF_HI(pipe) _MMIO_PIPE(pipe, \ + _PIPE_A_OUTPUT_CSC_PREOFF_HI, \ + _PIPE_B_OUTPUT_CSC_PREOFF_HI) +#define PIPE_CSC_OUTPUT_PREOFF_ME(pipe) _MMIO_PIPE(pipe, \ + _PIPE_A_OUTPUT_CSC_PREOFF_ME, \ + _PIPE_B_OUTPUT_CSC_PREOFF_ME) +#define PIPE_CSC_OUTPUT_PREOFF_LO(pipe) _MMIO_PIPE(pipe, \ + _PIPE_A_OUTPUT_CSC_PREOFF_LO, \ + _PIPE_B_OUTPUT_CSC_PREOFF_LO) +#define PIPE_CSC_OUTPUT_POSTOFF_HI(pipe) _MMIO_PIPE(pipe, \ + _PIPE_A_OUTPUT_CSC_POSTOFF_HI, \ + _PIPE_B_OUTPUT_CSC_POSTOFF_HI) +#define PIPE_CSC_OUTPUT_POSTOFF_ME(pipe) _MMIO_PIPE(pipe, \ + _PIPE_A_OUTPUT_CSC_POSTOFF_ME, \ + _PIPE_B_OUTPUT_CSC_POSTOFF_ME) +#define PIPE_CSC_OUTPUT_POSTOFF_LO(pipe) _MMIO_PIPE(pipe, \ + _PIPE_A_OUTPUT_CSC_POSTOFF_LO, \ + _PIPE_B_OUTPUT_CSC_POSTOFF_LO) + +/* pipe degamma/gamma LUTs on IVB+ */ +#define _PAL_PREC_INDEX_A 0x4A400 +#define _PAL_PREC_INDEX_B 0x4AC00 +#define _PAL_PREC_INDEX_C 0x4B400 +#define PAL_PREC_SPLIT_MODE REG_BIT(31) +#define PAL_PREC_AUTO_INCREMENT REG_BIT(15) +#define PAL_PREC_INDEX_VALUE_MASK REG_GENMASK(9, 0) +#define PAL_PREC_INDEX_VALUE(x) REG_FIELD_PREP(PAL_PREC_INDEX_VALUE_MASK, (x)) +#define _PAL_PREC_DATA_A 0x4A404 +#define _PAL_PREC_DATA_B 0x4AC04 +#define _PAL_PREC_DATA_C 0x4B404 +/* see PREC_PALETTE_* for the bits */ +#define _PAL_PREC_GC_MAX_A 0x4A410 +#define _PAL_PREC_GC_MAX_B 0x4AC10 +#define _PAL_PREC_GC_MAX_C 0x4B410 +#define _PAL_PREC_EXT_GC_MAX_A 0x4A420 +#define _PAL_PREC_EXT_GC_MAX_B 0x4AC20 +#define _PAL_PREC_EXT_GC_MAX_C 0x4B420 +#define _PAL_PREC_EXT2_GC_MAX_A 0x4A430 +#define _PAL_PREC_EXT2_GC_MAX_B 0x4AC30 +#define _PAL_PREC_EXT2_GC_MAX_C 0x4B430 + +#define PREC_PAL_INDEX(pipe) _MMIO_PIPE(pipe, _PAL_PREC_INDEX_A, _PAL_PREC_INDEX_B) +#define PREC_PAL_DATA(pipe) _MMIO_PIPE(pipe, _PAL_PREC_DATA_A, _PAL_PREC_DATA_B) +#define PREC_PAL_GC_MAX(pipe, i) _MMIO(_PIPE(pipe, _PAL_PREC_GC_MAX_A, _PAL_PREC_GC_MAX_B) + (i) * 4) /* u1.16 */ +#define PREC_PAL_EXT_GC_MAX(pipe, i) _MMIO(_PIPE(pipe, _PAL_PREC_EXT_GC_MAX_A, _PAL_PREC_EXT_GC_MAX_B) + (i) * 4) /* u3.16 */ +#define PREC_PAL_EXT2_GC_MAX(pipe, i) _MMIO(_PIPE(pipe, _PAL_PREC_EXT2_GC_MAX_A, _PAL_PREC_EXT2_GC_MAX_B) + (i) * 4) /* glk+, u3.16 */ + +#define _PRE_CSC_GAMC_INDEX_A 0x4A484 +#define _PRE_CSC_GAMC_INDEX_B 0x4AC84 +#define _PRE_CSC_GAMC_INDEX_C 0x4B484 +#define PRE_CSC_GAMC_AUTO_INCREMENT REG_BIT(10) +#define PRE_CSC_GAMC_INDEX_VALUE_MASK REG_GENMASK(7, 0) +#define PRE_CSC_GAMC_INDEX_VALUE(x) REG_FIELD_PREP(PRE_CSC_GAMC_INDEX_VALUE_MASK, (x)) +#define _PRE_CSC_GAMC_DATA_A 0x4A488 +#define _PRE_CSC_GAMC_DATA_B 0x4AC88 +#define _PRE_CSC_GAMC_DATA_C 0x4B488 + +#define PRE_CSC_GAMC_INDEX(pipe) _MMIO_PIPE(pipe, _PRE_CSC_GAMC_INDEX_A, _PRE_CSC_GAMC_INDEX_B) +#define PRE_CSC_GAMC_DATA(pipe) _MMIO_PIPE(pipe, _PRE_CSC_GAMC_DATA_A, _PRE_CSC_GAMC_DATA_B) + +/* ICL Multi segmented gamma */ +#define _PAL_PREC_MULTI_SEG_INDEX_A 0x4A408 +#define _PAL_PREC_MULTI_SEG_INDEX_B 0x4AC08 +#define PAL_PREC_MULTI_SEG_AUTO_INCREMENT REG_BIT(15) +#define PAL_PREC_MULTI_SEG_INDEX_VALUE_MASK REG_GENMASK(4, 0) +#define PAL_PREC_MULTI_SEG_INDEX_VALUE(x) REG_FIELD_PREP(PAL_PREC_MULTI_SEG_INDEX_VALUE_MASK, (x)) + +#define _PAL_PREC_MULTI_SEG_DATA_A 0x4A40C +#define _PAL_PREC_MULTI_SEG_DATA_B 0x4AC0C +/* see PREC_PALETTE_12P4_* for the bits */ + +#define PREC_PAL_MULTI_SEG_INDEX(pipe) _MMIO_PIPE(pipe, \ + _PAL_PREC_MULTI_SEG_INDEX_A, \ + _PAL_PREC_MULTI_SEG_INDEX_B) +#define PREC_PAL_MULTI_SEG_DATA(pipe) _MMIO_PIPE(pipe, \ + _PAL_PREC_MULTI_SEG_DATA_A, \ + _PAL_PREC_MULTI_SEG_DATA_B) + +#define _PIPE_A_WGC_C01_C00 0x600B0 /* s2.10 */ +#define _PIPE_A_WGC_C02 0x600B4 /* s2.10 */ +#define _PIPE_A_WGC_C11_C10 0x600B8 /* s2.10 */ +#define _PIPE_A_WGC_C12 0x600BC /* s2.10 */ +#define _PIPE_A_WGC_C21_C20 0x600C0 /* s2.10 */ +#define _PIPE_A_WGC_C22 0x600C4 /* s2.10 */ + +#define PIPE_WGC_C01_C00(dev_priv, pipe) _MMIO_TRANS2(dev_priv, pipe, _PIPE_A_WGC_C01_C00) +#define PIPE_WGC_C02(dev_priv, pipe) _MMIO_TRANS2(dev_priv, pipe, _PIPE_A_WGC_C02) +#define PIPE_WGC_C11_C10(dev_priv, pipe) _MMIO_TRANS2(dev_priv, pipe, _PIPE_A_WGC_C11_C10) +#define PIPE_WGC_C12(dev_priv, pipe) _MMIO_TRANS2(dev_priv, pipe, _PIPE_A_WGC_C12) +#define PIPE_WGC_C21_C20(dev_priv, pipe) _MMIO_TRANS2(dev_priv, pipe, _PIPE_A_WGC_C21_C20) +#define PIPE_WGC_C22(dev_priv, pipe) _MMIO_TRANS2(dev_priv, pipe, _PIPE_A_WGC_C22) + +/* pipe CSC & degamma/gamma LUTs on CHV */ +#define _CGM_PIPE_A_CSC_COEFF01 (VLV_DISPLAY_BASE + 0x67900) +#define _CGM_PIPE_A_CSC_COEFF23 (VLV_DISPLAY_BASE + 0x67904) +#define _CGM_PIPE_A_CSC_COEFF45 (VLV_DISPLAY_BASE + 0x67908) +#define _CGM_PIPE_A_CSC_COEFF67 (VLV_DISPLAY_BASE + 0x6790C) +#define _CGM_PIPE_A_CSC_COEFF8 (VLV_DISPLAY_BASE + 0x67910) +#define _CGM_PIPE_A_DEGAMMA (VLV_DISPLAY_BASE + 0x66000) +/* cgm degamma ldw */ +#define CGM_PIPE_DEGAMMA_GREEN_LDW_MASK REG_GENMASK(29, 16) +#define CGM_PIPE_DEGAMMA_BLUE_LDW_MASK REG_GENMASK(13, 0) +/* cgm degamma udw */ +#define CGM_PIPE_DEGAMMA_RED_UDW_MASK REG_GENMASK(13, 0) +#define _CGM_PIPE_A_GAMMA (VLV_DISPLAY_BASE + 0x67000) +/* cgm gamma ldw */ +#define CGM_PIPE_GAMMA_GREEN_LDW_MASK REG_GENMASK(25, 16) +#define CGM_PIPE_GAMMA_BLUE_LDW_MASK REG_GENMASK(9, 0) +/* cgm gamma udw */ +#define CGM_PIPE_GAMMA_RED_UDW_MASK REG_GENMASK(9, 0) +#define _CGM_PIPE_A_MODE (VLV_DISPLAY_BASE + 0x67A00) +#define CGM_PIPE_MODE_GAMMA (1 << 2) +#define CGM_PIPE_MODE_CSC (1 << 1) +#define CGM_PIPE_MODE_DEGAMMA (1 << 0) + +#define _CGM_PIPE_B_CSC_COEFF01 (VLV_DISPLAY_BASE + 0x69900) +#define _CGM_PIPE_B_CSC_COEFF23 (VLV_DISPLAY_BASE + 0x69904) +#define _CGM_PIPE_B_CSC_COEFF45 (VLV_DISPLAY_BASE + 0x69908) +#define _CGM_PIPE_B_CSC_COEFF67 (VLV_DISPLAY_BASE + 0x6990C) +#define _CGM_PIPE_B_CSC_COEFF8 (VLV_DISPLAY_BASE + 0x69910) +#define _CGM_PIPE_B_DEGAMMA (VLV_DISPLAY_BASE + 0x68000) +#define _CGM_PIPE_B_GAMMA (VLV_DISPLAY_BASE + 0x69000) +#define _CGM_PIPE_B_MODE (VLV_DISPLAY_BASE + 0x69A00) + +#define CGM_PIPE_CSC_COEFF01(pipe) _MMIO_PIPE(pipe, _CGM_PIPE_A_CSC_COEFF01, _CGM_PIPE_B_CSC_COEFF01) +#define CGM_PIPE_CSC_COEFF23(pipe) _MMIO_PIPE(pipe, _CGM_PIPE_A_CSC_COEFF23, _CGM_PIPE_B_CSC_COEFF23) +#define CGM_PIPE_CSC_COEFF45(pipe) _MMIO_PIPE(pipe, _CGM_PIPE_A_CSC_COEFF45, _CGM_PIPE_B_CSC_COEFF45) +#define CGM_PIPE_CSC_COEFF67(pipe) _MMIO_PIPE(pipe, _CGM_PIPE_A_CSC_COEFF67, _CGM_PIPE_B_CSC_COEFF67) +#define CGM_PIPE_CSC_COEFF8(pipe) _MMIO_PIPE(pipe, _CGM_PIPE_A_CSC_COEFF8, _CGM_PIPE_B_CSC_COEFF8) +#define CGM_PIPE_DEGAMMA(pipe, i, w) _MMIO(_PIPE(pipe, _CGM_PIPE_A_DEGAMMA, _CGM_PIPE_B_DEGAMMA) + (i) * 8 + (w) * 4) +#define CGM_PIPE_GAMMA(pipe, i, w) _MMIO(_PIPE(pipe, _CGM_PIPE_A_GAMMA, _CGM_PIPE_B_GAMMA) + (i) * 8 + (w) * 4) +#define CGM_PIPE_MODE(pipe) _MMIO_PIPE(pipe, _CGM_PIPE_A_MODE, _CGM_PIPE_B_MODE) + +/* Skylake+ pipe bottom (background) color */ +#define _SKL_BOTTOM_COLOR_A 0x70034 +#define _SKL_BOTTOM_COLOR_B 0x71034 +#define SKL_BOTTOM_COLOR_GAMMA_ENABLE REG_BIT(31) +#define SKL_BOTTOM_COLOR_CSC_ENABLE REG_BIT(30) +#define SKL_BOTTOM_COLOR(pipe) _MMIO_PIPE(pipe, _SKL_BOTTOM_COLOR_A, _SKL_BOTTOM_COLOR_B) + +/* 3D LUT */ +#define _LUT_3D_CTL_A 0x490A4 +#define _LUT_3D_CTL_B 0x491A4 +#define LUT_3D_CTL(pipe) _MMIO_PIPE(pipe, _LUT_3D_CTL_A, _LUT_3D_CTL_B) +#define LUT_3D_ENABLE REG_BIT(31) +#define LUT_3D_READY REG_BIT(30) +#define LUT_3D_BINDING_MASK REG_GENMASK(23, 22) +#define LUT_3D_BIND_PIPE REG_FIELD_PREP(LUT_3D_BINDING_MASK, 0) +#define LUT_3D_BIND_PLANE_1 REG_FIELD_PREP(LUT_3D_BINDING_MASK, 1) +#define LUT_3D_BIND_PLANE_2 REG_FIELD_PREP(LUT_3D_BINDING_MASK, 2) +#define LUT_3D_BIND_PLANE_3 REG_FIELD_PREP(LUT_3D_BINDING_MASK, 3) + +#define _LUT_3D_INDEX_A 0x490A8 +#define _LUT_3D_INDEX_B 0x491A8 +#define LUT_3D_INDEX(pipe) _MMIO_PIPE(pipe, _LUT_3D_INDEX_A, _LUT_3D_INDEX_B) +#define LUT_3D_AUTO_INCREMENT REG_BIT(13) +#define LUT_3D_INDEX_VALUE_MASK REG_GENMASK(12, 0) +#define LUT_3D_INDEX_VALUE(x) REG_FIELD_PREP(LUT_3D_INDEX_VALUE_MASK, (x)) + +#define _LUT_3D_DATA_A 0x490AC +#define _LUT_3D_DATA_B 0x491AC +#define LUT_3D_DATA(pipe) _MMIO_PIPE(pipe, _LUT_3D_DATA_A, _LUT_3D_DATA_B) +#define LUT_3D_DATA_RED_MASK REG_GENMASK(29, 20) +#define LUT_3D_DATA_GREEN_MASK REG_GENMASK(19, 10) +#define LUT_3D_DATA_BLUE_MASK REG_GENMASK(9, 0) +#define LUT_3D_DATA_RED(x) REG_FIELD_PREP(LUT_3D_DATA_RED_MASK, (x)) +#define LUT_3D_DATA_GREEN(x) REG_FIELD_PREP(LUT_3D_DATA_GREEN_MASK, (x)) +#define LUT_3D_DATA_BLUE(x) REG_FIELD_PREP(LUT_3D_DATA_BLUE_MASK, (x)) + +#endif /* __INTEL_COLOR_REGS_H__ */ diff --git a/drivers/gpu/drm/i915/display/intel_colorop.c b/drivers/gpu/drm/i915/display/intel_colorop.c new file mode 100644 index 000000000..2ec10ceba --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_colorop.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2025 Intel Corporation + */ + +#include "intel_colorop.h" +#include "intel_display_types.h" + +struct intel_colorop *to_intel_colorop(struct drm_colorop *colorop) +{ + return container_of(colorop, struct intel_colorop, base); +} + +struct intel_colorop *intel_colorop_alloc(void) +{ + struct intel_colorop *colorop; + + colorop = kzalloc_obj(*colorop); + if (!colorop) + return ERR_PTR(-ENOMEM); + + return colorop; +} + +struct intel_colorop *intel_colorop_create(enum intel_color_block id) +{ + struct intel_colorop *colorop; + + colorop = intel_colorop_alloc(); + + if (IS_ERR(colorop)) + return colorop; + + colorop->id = id; + + return colorop; +} + +void intel_colorop_destroy(struct drm_colorop *colorop) +{ + drm_colorop_cleanup(colorop); + kfree(to_intel_colorop(colorop)); +} diff --git a/drivers/gpu/drm/i915/display/intel_colorop.h b/drivers/gpu/drm/i915/display/intel_colorop.h new file mode 100644 index 000000000..638baf67d --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_colorop.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2025 Intel Corporation + */ + +#ifndef __INTEL_COLOROP_H__ +#define __INTEL_COLOROP_H__ + +enum intel_color_block; +struct drm_colorop; +struct intel_colorop; + +struct intel_colorop *to_intel_colorop(struct drm_colorop *colorop); +struct intel_colorop *intel_colorop_alloc(void); +struct intel_colorop *intel_colorop_create(enum intel_color_block id); +void intel_colorop_destroy(struct drm_colorop *colorop); + +#endif /* __INTEL_COLOROP_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..9b4ecfbbe --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_combo_phy.c @@ -0,0 +1,417 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2018 Intel Corporation + */ + +#include + +#include "intel_combo_phy.h" +#include "intel_combo_phy_regs.h" +#include "intel_de.h" +#include "intel_display_regs.h" +#include "intel_display_types.h" +#include "intel_display_utils.h" + +#define for_each_combo_phy(__display, __phy) \ + for ((__phy) = PHY_A; (__phy) < I915_MAX_PHYS; (__phy)++) \ + for_each_if(intel_phy_is_combo(__display, __phy)) + +#define for_each_combo_phy_reverse(__display, __phy) \ + for ((__phy) = I915_MAX_PHYS; (__phy)-- > PHY_A;) \ + for_each_if(intel_phy_is_combo(__display, __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 intel_display *display, enum phy phy) +{ + u32 val; + + val = intel_de_read(display, 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: + return &icl_procmon_values[PROCMON_0_85V_DOT_0]; + case VOLTAGE_INFO_0_95V | PROCESS_INFO_DOT_0: + return &icl_procmon_values[PROCMON_0_95V_DOT_0]; + case VOLTAGE_INFO_0_95V | PROCESS_INFO_DOT_1: + return &icl_procmon_values[PROCMON_0_95V_DOT_1]; + case VOLTAGE_INFO_1_05V | PROCESS_INFO_DOT_0: + return &icl_procmon_values[PROCMON_1_05V_DOT_0]; + case VOLTAGE_INFO_1_05V | PROCESS_INFO_DOT_1: + return &icl_procmon_values[PROCMON_1_05V_DOT_1]; + } +} + +static void icl_set_procmon_ref_values(struct intel_display *display, + enum phy phy) +{ + const struct icl_procmon *procmon; + + procmon = icl_get_procmon_ref_values(display, phy); + + intel_de_rmw(display, ICL_PORT_COMP_DW1(phy), + (0xff << 16) | 0xff, procmon->dw1); + + intel_de_write(display, ICL_PORT_COMP_DW9(phy), procmon->dw9); + intel_de_write(display, ICL_PORT_COMP_DW10(phy), procmon->dw10); +} + +static bool check_phy_reg(struct intel_display *display, + enum phy phy, intel_reg_t reg, u32 mask, + u32 expected_val) +{ + u32 val = intel_de_read(display, reg); + + if ((val & mask) != expected_val) { + drm_dbg_kms(display->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 intel_display *display, + enum phy phy) +{ + const struct icl_procmon *procmon; + bool ret; + + procmon = icl_get_procmon_ref_values(display, phy); + + ret = check_phy_reg(display, phy, ICL_PORT_COMP_DW1(phy), + (0xff << 16) | 0xff, procmon->dw1); + ret &= check_phy_reg(display, phy, ICL_PORT_COMP_DW9(phy), + -1U, procmon->dw9); + ret &= check_phy_reg(display, phy, ICL_PORT_COMP_DW10(phy), + -1U, procmon->dw10); + + return ret; +} + +static bool has_phy_misc(struct intel_display *display, 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 (display->platform.alderlake_s) + return phy == PHY_A; + else if ((display->platform.jasperlake || display->platform.elkhartlake) || + display->platform.rocketlake || + display->platform.dg1) + return phy < PHY_C; + + return true; +} + +static bool icl_combo_phy_enabled(struct intel_display *display, + enum phy phy) +{ + /* The PHY C added by EHL has no PHY_MISC register */ + if (!has_phy_misc(display, phy)) + return intel_de_read(display, ICL_PORT_COMP_DW0(phy)) & COMP_INIT; + else + return !(intel_de_read(display, ICL_PHY_MISC(phy)) & + ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN) && + (intel_de_read(display, ICL_PORT_COMP_DW0(phy)) & COMP_INIT); +} + +static bool ehl_vbt_ddi_d_present(struct intel_display *display) +{ + bool ddi_a_present = intel_bios_is_port_present(display, PORT_A); + bool ddi_d_present = intel_bios_is_port_present(display, PORT_D); + bool dsi_present = intel_bios_is_dsi_present(display, 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(display->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 intel_display *display, 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 (display->platform.alderlake_s) + return phy == PHY_D; + else if (display->platform.dg1 || display->platform.rocketlake) + return phy == PHY_C; + + return false; +} + +static bool icl_combo_phy_verify_state(struct intel_display *display, + enum phy phy) +{ + bool ret = true; + u32 expected_val = 0; + + if (!icl_combo_phy_enabled(display, phy)) + return false; + + if (DISPLAY_VER(display) >= 12) { + ret &= check_phy_reg(display, 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(display, phy, ICL_PORT_PCS_DW1_LN(0, phy), + DCC_MODE_SELECT_MASK, RUN_DCC_ONCE); + } + + ret &= icl_verify_procmon_ref_values(display, phy); + + if (phy_is_master(display, phy)) { + ret &= check_phy_reg(display, phy, ICL_PORT_COMP_DW8(phy), + IREFGEN, IREFGEN); + + if (display->platform.jasperlake || display->platform.elkhartlake) { + if (ehl_vbt_ddi_d_present(display)) + expected_val = ICL_PHY_MISC_MUX_DDID; + + ret &= check_phy_reg(display, phy, ICL_PHY_MISC(phy), + ICL_PHY_MISC_MUX_DDID, + expected_val); + } + } + + ret &= check_phy_reg(display, phy, ICL_PORT_CL_DW5(phy), + CL_POWER_DOWN_ENABLE, CL_POWER_DOWN_ENABLE); + + return ret; +} + +void intel_combo_phy_power_up_lanes(struct intel_display *display, + enum phy phy, bool is_dsi, + int lane_count, bool lane_reversal) +{ + u8 lane_mask; + + if (is_dsi) { + drm_WARN_ON(display->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; + } + } + + intel_de_rmw(display, ICL_PORT_CL_DW10(phy), + PWR_DOWN_LN_MASK, lane_mask); +} + +static void icl_combo_phys_init(struct intel_display *display) +{ + enum phy phy; + + for_each_combo_phy(display, phy) { + const struct icl_procmon *procmon; + u32 val; + + if (icl_combo_phy_verify_state(display, phy)) + continue; + + procmon = icl_get_procmon_ref_values(display, phy); + + drm_dbg_kms(display->drm, + "Initializing combo PHY %c (Voltage/Process Info : %s)\n", + phy_name(phy), procmon->name); + + if (!has_phy_misc(display, 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(display, ICL_PHY_MISC(phy)); + if ((display->platform.jasperlake || display->platform.elkhartlake) && + phy == PHY_A) { + val &= ~ICL_PHY_MISC_MUX_DDID; + + if (ehl_vbt_ddi_d_present(display)) + val |= ICL_PHY_MISC_MUX_DDID; + } + + val &= ~ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN; + intel_de_write(display, ICL_PHY_MISC(phy), val); + +skip_phy_misc: + if (DISPLAY_VER(display) >= 12) { + val = intel_de_read(display, 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(display, ICL_PORT_TX_DW8_GRP(phy), val); + + val = intel_de_read(display, ICL_PORT_PCS_DW1_LN(0, phy)); + val &= ~DCC_MODE_SELECT_MASK; + val |= RUN_DCC_ONCE; + intel_de_write(display, ICL_PORT_PCS_DW1_GRP(phy), val); + } + + icl_set_procmon_ref_values(display, phy); + + if (phy_is_master(display, phy)) + intel_de_rmw(display, ICL_PORT_COMP_DW8(phy), + 0, IREFGEN); + + intel_de_rmw(display, ICL_PORT_COMP_DW0(phy), 0, COMP_INIT); + intel_de_rmw(display, ICL_PORT_CL_DW5(phy), + 0, CL_POWER_DOWN_ENABLE); + } +} + +static void icl_combo_phys_uninit(struct intel_display *display) +{ + enum phy phy; + + for_each_combo_phy_reverse(display, phy) { + if (phy == PHY_A && + !icl_combo_phy_verify_state(display, phy)) { + if (display->platform.tigerlake || display->platform.dg1) { + /* + * A known problem with old ifwi: + * https://gitlab.freedesktop.org/drm/intel/-/issues/2411 + * Suppress the warning for CI. Remove ASAP! + */ + drm_dbg_kms(display->drm, + "Combo PHY %c HW state changed unexpectedly\n", + phy_name(phy)); + } else { + drm_warn(display->drm, + "Combo PHY %c HW state changed unexpectedly\n", + phy_name(phy)); + } + } + + if (!has_phy_misc(display, phy)) + goto skip_phy_misc; + + intel_de_rmw(display, ICL_PHY_MISC(phy), 0, + ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN); + +skip_phy_misc: + intel_de_rmw(display, ICL_PORT_COMP_DW0(phy), COMP_INIT, 0); + } +} + +void intel_combo_phy_init(struct intel_display *display) +{ + icl_combo_phys_init(display); +} + +void intel_combo_phy_uninit(struct intel_display *display) +{ + icl_combo_phys_uninit(display); +} 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..3f5dba78e --- /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 + +enum phy; +struct intel_display; + +void intel_combo_phy_init(struct intel_display *display); +void intel_combo_phy_uninit(struct intel_display *display); +void intel_combo_phy_power_up_lanes(struct intel_display *display, + 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..3694f9537 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_combo_phy_regs.h @@ -0,0 +1,167 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2022 Intel Corporation + */ + +#ifndef __INTEL_COMBO_PHY_REGS__ +#define __INTEL_COMBO_PHY_REGS__ + +#include "intel_display_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 REG_BIT(4) +#define SUS_CLOCK_CONFIG REG_GENMASK(1, 0) + +#define ICL_PORT_CL_DW10(phy) _MMIO(_ICL_PORT_CL_DW(10, phy)) +#define PG_SEQ_DELAY_OVERRIDE_MASK REG_GENMASK(26, 25) +#define PG_SEQ_DELAY_OVERRIDE_ENABLE REG_BIT(24) +#define PWR_DOWN_LN_MASK REG_GENMASK(7, 4) +#define PWR_UP_ALL_LANES REG_FIELD_PREP(PWR_DOWN_LN_MASK, 0x0) +#define PWR_DOWN_LN_3_2_1 REG_FIELD_PREP(PWR_DOWN_LN_MASK, 0xe) +#define PWR_DOWN_LN_3_2 REG_FIELD_PREP(PWR_DOWN_LN_MASK, 0xc) +#define PWR_DOWN_LN_3 REG_FIELD_PREP(PWR_DOWN_LN_MASK, 0x8) +#define PWR_DOWN_LN_2_1_0 REG_FIELD_PREP(PWR_DOWN_LN_MASK, 0x7) +#define PWR_DOWN_LN_1_0 REG_FIELD_PREP(PWR_DOWN_LN_MASK, 0x3) +#define PWR_DOWN_LN_3_1 REG_FIELD_PREP(PWR_DOWN_LN_MASK, 0xa) +#define PWR_DOWN_LN_3_1_0 REG_FIELD_PREP(PWR_DOWN_LN_MASK, 0xb) +#define EDP4K2K_MODE_OVRD_EN REG_BIT(3) +#define EDP4K2K_MODE_OVRD_OPTIMIZED REG_BIT(2) + +#define ICL_PORT_CL_DW12(phy) _MMIO(_ICL_PORT_CL_DW(12, phy)) +#define ICL_LANE_ENABLE_AUX REG_BIT(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 REG_BIT(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_MASK REG_GENMASK(28, 26) +#define PROCESS_INFO_DOT_0 REG_FIELD_PREP(PROCESS_INFO_MASK, 0) +#define PROCESS_INFO_DOT_1 REG_FIELD_PREP(PROCESS_INFO_MASK, 1) +#define PROCESS_INFO_DOT_4 REG_FIELD_PREP(PROCESS_INFO_MASK, 2) +#define VOLTAGE_INFO_MASK REG_GENMASK(25, 24) +#define VOLTAGE_INFO_0_85V REG_FIELD_PREP(VOLTAGE_INFO_MASK, 0) +#define VOLTAGE_INFO_0_95V REG_FIELD_PREP(VOLTAGE_INFO_MASK, 1) +#define VOLTAGE_INFO_1_05V REG_FIELD_PREP(VOLTAGE_INFO_MASK, 2) + +#define ICL_PORT_COMP_DW8(phy) _MMIO(_ICL_PORT_COMP_DW(8, phy)) +#define IREFGEN REG_BIT(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 REG_GENMASK(21, 20) +#define RUN_DCC_ONCE REG_FIELD_PREP(DCC_MODE_SELECT_MASK, 0) +#define COMMON_KEEPER_EN REG_BIT(26) +#define LATENCY_OPTIM_MASK REG_GENMASK(3, 2) +#define LATENCY_OPTIM_VAL(x) REG_FIELD_PREP(LATENCY_OPTIM_MASK, (x)) + +/* 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_MASK REG_BIT(15) +#define SWING_SEL_UPPER(x) REG_FIELD_PREP(SWING_SEL_UPPER_MASK, (x) >> 3) +#define SWING_SEL_LOWER_MASK REG_GENMASK(13, 11) +#define SWING_SEL_LOWER(x) REG_FIELD_PREP(SWING_SEL_LOWER_MASK, (x) & 0x7) +#define FRC_LATENCY_OPTIM_MASK REG_GENMASK(10, 8) +#define FRC_LATENCY_OPTIM_VAL(x) REG_FIELD_PREP(FRC_LATENCY_OPTIM_MASK, (x)) +#define RCOMP_SCALAR_MASK REG_GENMASK(7, 0) +#define RCOMP_SCALAR(x) REG_FIELD_PREP(RCOMP_SCALAR_MASK, (x)) + +#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 REG_BIT(31) +#define POST_CURSOR_1_MASK REG_GENMASK(17, 12) +#define POST_CURSOR_1(x) REG_FIELD_PREP(POST_CURSOR_1_MASK, (x)) +#define POST_CURSOR_2_MASK REG_GENMASK(11, 6) +#define POST_CURSOR_2(x) REG_FIELD_PREP(POST_CURSOR_2_MASK, (x)) +#define CURSOR_COEFF_MASK REG_GENMASK(5, 0) +#define CURSOR_COEFF(x) REG_FIELD_PREP(CURSOR_COEFF_MASK, (x)) + +#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 REG_BIT(31) +#define TAP2_DISABLE REG_BIT(30) +#define TAP3_DISABLE REG_BIT(29) +#define CURSOR_PROGRAM REG_BIT(26) +#define COEFF_POLARITY REG_BIT(25) +#define SCALING_MODE_SEL_MASK REG_GENMASK(20, 18) +#define SCALING_MODE_SEL(x) REG_FIELD_PREP(SCALING_MODE_SEL_MASK, (x)) +#define RTERM_SELECT_MASK REG_GENMASK(5, 3) +#define RTERM_SELECT(x) REG_FIELD_PREP(RTERM_SELECT_MASK, (x)) + +#define ICL_PORT_TX_DW6_AUX(phy) _MMIO(_ICL_PORT_TX_DW_AUX(6, phy)) +#define ICL_PORT_TX_DW6_GRP(phy) _MMIO(_ICL_PORT_TX_DW_GRP(6, phy)) +#define ICL_PORT_TX_DW6_LN(ln, phy) _MMIO(_ICL_PORT_TX_DW_LN(6, ln, phy)) +#define O_FUNC_OVRD_EN REG_BIT(7) +#define O_LDO_REF_SEL_CRI REG_GENMASK(6, 1) +#define O_LDO_BYPASS_CRI REG_BIT(0) + +#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_MASK REG_GENMASK(30, 24) +#define N_SCALAR(x) REG_FIELD_PREP(N_SCALAR_MASK, (x)) + +#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..7ef9338d6 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_connector.c @@ -0,0 +1,340 @@ +/* + * 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 +#include + +#include "intel_connector.h" +#include "intel_display_core.h" +#include "intel_display_debugfs.h" +#include "intel_display_types.h" +#include "intel_hdcp.h" +#include "intel_panel.h" + +static void intel_connector_modeset_retry_work_fn(struct work_struct *work) +{ + struct intel_connector *connector = container_of(work, typeof(*connector), + modeset_retry_work); + struct intel_display *display = to_intel_display(connector); + + drm_dbg_kms(display->drm, "[CONNECTOR:%d:%s]\n", connector->base.base.id, + connector->base.name); + + /* Grab the locks before changing connector property*/ + mutex_lock(&display->drm->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->base, + DRM_MODE_LINK_STATUS_BAD); + mutex_unlock(&display->drm->mode_config.mutex); + /* Send Hotplug uevent so userspace can reprobe */ + drm_kms_helper_connector_hotplug_event(&connector->base); + + drm_connector_put(&connector->base); +} + +void intel_connector_queue_modeset_retry_work(struct intel_connector *connector) +{ + struct intel_display *display = to_intel_display(connector); + + drm_connector_get(&connector->base); + if (!queue_work(display->wq.unordered, &connector->modeset_retry_work)) + drm_connector_put(&connector->base); +} + +void intel_connector_cancel_modeset_retry_work(struct intel_connector *connector) +{ + if (cancel_work_sync(&connector->modeset_retry_work)) + drm_connector_put(&connector->base); +} + +static 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_obj(*conn_state); + if (!conn_state) + return -ENOMEM; + + __drm_atomic_helper_connector_reset(&connector->base, + &conn_state->base); + + intel_panel_init_alloc(connector); + + INIT_WORK(&connector->modeset_retry_work, + intel_connector_modeset_retry_work_fn); + + return 0; +} + +struct intel_connector *intel_connector_alloc(void) +{ + struct intel_connector *connector; + + connector = kzalloc_obj(*connector); + 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); + + drm_edid_free(intel_connector->detect_edid); + + intel_hdcp_cleanup(intel_connector); + + intel_panel_fini(intel_connector); + + drm_connector_cleanup(connector); + + if (intel_connector->mst.port) + drm_dp_mst_put_port_malloc(intel_connector->mst.port); + + kfree(connector); +} + +int intel_connector_register(struct drm_connector *_connector) +{ + struct intel_connector *connector = to_intel_connector(_connector); + int ret; + + ret = intel_panel_register(connector); + if (ret) + return ret; + + intel_connector_debugfs_add(connector); + + return 0; +} +ALLOW_ERROR_INJECTION(intel_connector_register, ERRNO); + +void intel_connector_unregister(struct drm_connector *_connector) +{ + struct intel_connector *connector = to_intel_connector(_connector); + + intel_panel_unregister(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 intel_display *display = to_intel_display(connector); + + drm_modeset_lock_assert_held(&display->drm->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 + * @drm_edid: previously read EDID information + */ +int intel_connector_update_modes(struct drm_connector *connector, + const struct drm_edid *drm_edid) +{ + int ret; + + drm_edid_connector_update(connector, drm_edid); + ret = drm_edid_connector_add_modes(connector); + + return ret; +} + +/** + * intel_ddc_get_modes - get modelist from monitor + * @connector: DRM connector device to use + * @ddc: DDC bus i2c adapter + * + * Fetch the EDID information from @connector using the DDC bus. + */ +int intel_ddc_get_modes(struct drm_connector *connector, + struct i2c_adapter *ddc) +{ + const struct drm_edid *drm_edid; + int ret; + + drm_edid = drm_edid_read_ddc(connector, ddc); + if (!drm_edid) + return 0; + + ret = intel_connector_update_modes(connector, drm_edid); + drm_edid_free(drm_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 intel_display *display = to_intel_display(connector->dev); + struct drm_property *prop; + + prop = display->properties.force_audio; + if (prop == NULL) { + prop = drm_property_create_enum(display->drm, 0, + "audio", + force_audio_names, + ARRAY_SIZE(force_audio_names)); + if (prop == NULL) + return; + + 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 intel_display *display = to_intel_display(connector->dev); + struct drm_property *prop; + + prop = display->properties.broadcast_rgb; + if (prop == NULL) { + prop = drm_property_create_enum(display->drm, DRM_MODE_PROP_ENUM, + "Broadcast RGB", + broadcast_rgb_names, + ARRAY_SIZE(broadcast_rgb_names)); + if (prop == NULL) + return; + + 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, 0)) + drm_connector_attach_colorspace_property(connector); +} + +void +intel_attach_dp_colorspace_property(struct drm_connector *connector) +{ + if (!drm_mode_create_dp_colorspace_property(connector, 0)) + drm_connector_attach_colorspace_property(connector); +} + +void +intel_attach_scaling_mode_property(struct drm_connector *connector) +{ + struct intel_display *display = to_intel_display(connector->dev); + u32 scaling_modes; + + scaling_modes = BIT(DRM_MODE_SCALE_ASPECT) | + BIT(DRM_MODE_SCALE_FULLSCREEN); + + /* On GMCH platforms borders are only possible on the LVDS port */ + if (!HAS_GMCH(display) || connector->connector_type == DRM_MODE_CONNECTOR_LVDS) + scaling_modes |= BIT(DRM_MODE_SCALE_CENTER); + + drm_connector_attach_scaling_mode_property(connector, scaling_modes); + + connector->state->scaling_mode = DRM_MODE_SCALE_ASPECT; +} 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..0aa86626e --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_connector.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2019 Intel Corporation + */ + +#ifndef __INTEL_CONNECTOR_H__ +#define __INTEL_CONNECTOR_H__ + +#include + +struct drm_connector; +struct drm_edid; +struct i2c_adapter; +struct intel_connector; +struct intel_encoder; + +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, + const struct drm_edid *drm_edid); +int intel_ddc_get_modes(struct drm_connector *c, struct i2c_adapter *ddc); +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); +void intel_attach_scaling_mode_property(struct drm_connector *connector); +void intel_connector_queue_modeset_retry_work(struct intel_connector *connector); +void intel_connector_cancel_modeset_retry_work(struct intel_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..243e332be --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_crt.c @@ -0,0 +1,1139 @@ +/* + * 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