From a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 Mon Sep 17 00:00:00 2001 From: Jinke Han Date: Tue, 8 Sep 2026 15:37:42 +0800 Subject: x86/kprobes: Fix crash when probing CS CALL instructions When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) Signed-off-by: Jinke Han Signed-off-by: Ingo Molnar Reviewed-by: Masami Hiramatsu (Google) Acked-by: Yafang Shao Acked-by: Borislav Petkov Cc: Peter Zijlstra Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000 --- drivers/gpu/drm/i915/.gitignore | 2 + drivers/gpu/drm/i915/.kunitconfig | 9 + drivers/gpu/drm/i915/Kconfig | 185 + drivers/gpu/drm/i915/Kconfig.debug | 290 + drivers/gpu/drm/i915/Kconfig.profile | 139 + drivers/gpu/drm/i915/Makefile | 462 ++ 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 | 1437 ++++ 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 | 1275 +++ 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 | 2044 +++++ 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 | 650 ++ drivers/gpu/drm/i915/display/intel_alpm.h | 42 + drivers/gpu/drm/i915/display/intel_atomic.c | 374 + drivers/gpu/drm/i915/display/intel_atomic.h | 54 + drivers/gpu/drm/i915/display/intel_audio.c | 1441 ++++ 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 | 3881 +++++++++ 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 | 1552 ++++ 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 | 4306 ++++++++++ drivers/gpu/drm/i915/display/intel_cdclk.h | 81 + drivers/gpu/drm/i915/display/intel_cmtg.c | 451 ++ drivers/gpu/drm/i915/display/intel_cmtg.h | 30 + drivers/gpu/drm/i915/display/intel_cmtg_regs.h | 41 + drivers/gpu/drm/i915/display/intel_color.c | 4362 ++++++++++ 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 | 1142 +++ 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 | 1123 +++ 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 | 3871 +++++++++ drivers/gpu/drm/i915/display/intel_cx0_phy.h | 88 + 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 | 5553 +++++++++++++ drivers/gpu/drm/i915/display/intel_ddi.h | 89 + 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 | 164 + drivers/gpu/drm/i915/display/intel_de.h | 183 + drivers/gpu/drm/i915/display/intel_display.c | 8536 ++++++++++++++++++++ drivers/gpu/drm/i915/display/intel_display.h | 528 ++ .../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 | 654 ++ .../gpu/drm/i915/display/intel_display_debugfs.c | 1403 ++++ .../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 | 2027 +++++ .../gpu/drm/i915/display/intel_display_device.h | 356 + .../gpu/drm/i915/display/intel_display_driver.c | 960 +++ .../gpu/drm/i915/display/intel_display_driver.h | 55 + drivers/gpu/drm/i915/display/intel_display_irq.c | 2687 ++++++ drivers/gpu/drm/i915/display/intel_display_irq.h | 87 + .../gpu/drm/i915/display/intel_display_jiffies.h | 43 + .../gpu/drm/i915/display/intel_display_limits.h | 178 + .../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 | 2752 +++++++ drivers/gpu/drm/i915/display/intel_display_power.h | 355 + .../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 | 2144 +++++ .../drm/i915/display/intel_display_power_well.h | 180 + .../gpu/drm/i915/display/intel_display_reg_defs.h | 77 + drivers/gpu/drm/i915/display/intel_display_regs.h | 3268 ++++++++ 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 | 2316 ++++++ 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 | 166 + drivers/gpu/drm/i915/display/intel_display_wa.h | 73 + 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 | 1790 ++++ drivers/gpu/drm/i915/display/intel_dmc.h | 56 + drivers/gpu/drm/i915/display/intel_dmc_regs.h | 649 ++ 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 | 7449 +++++++++++++++++ drivers/gpu/drm/i915/display/intel_dp.h | 245 + 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 | 756 ++ .../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 + drivers/gpu/drm/i915/display/intel_dp_link_caps.c | 1312 +++ drivers/gpu/drm/i915/display/intel_dp_link_caps.h | 195 + .../gpu/drm/i915/display/intel_dp_link_training.c | 2842 +++++++ .../gpu/drm/i915/display/intel_dp_link_training.h | 105 + drivers/gpu/drm/i915/display/intel_dp_mst.c | 2243 +++++ drivers/gpu/drm/i915/display/intel_dp_mst.h | 39 + drivers/gpu/drm/i915/display/intel_dp_test.c | 836 ++ drivers/gpu/drm/i915/display/intel_dp_test.h | 24 + drivers/gpu/drm/i915/display/intel_dp_tunnel.c | 924 +++ drivers/gpu/drm/i915/display/intel_dp_tunnel.h | 159 + 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 | 867 ++ 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 | 1151 +++ 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 | 560 ++ 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 | 2491 ++++++ 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 | 587 ++ 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 | 1042 +++ 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 | 2897 +++++++ 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 | 3422 ++++++++ drivers/gpu/drm/i915/display/intel_hdmi.h | 76 + 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 | 2359 ++++++ 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 | 1016 +++ 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 | 1011 +++ 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 | 695 ++ drivers/gpu/drm/i915/display/intel_panel.h | 63 + drivers/gpu/drm/i915/display/intel_parent.c | 457 ++ drivers/gpu/drm/i915/display/intel_parent.h | 161 + 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 | 1991 +++++ drivers/gpu/drm/i915/display/intel_plane.h | 103 + 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 | 4640 +++++++++++ drivers/gpu/drm/i915/display/intel_psr.h | 92 + drivers/gpu/drm/i915/display/intel_psr_regs.h | 343 + 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 | 3524 ++++++++ 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 | 1740 ++++ 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 | 2022 +++++ 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 | 1233 +++ drivers/gpu/drm/i915/display/intel_vrr.h | 59 + 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 | 3250 ++++++++ drivers/gpu/drm/i915/display/skl_universal_plane.h | 55 + .../drm/i915/display/skl_universal_plane_regs.h | 585 ++ drivers/gpu/drm/i915/display/skl_watermark.c | 4100 ++++++++++ drivers/gpu/drm/i915/display/skl_watermark.h | 86 + drivers/gpu/drm/i915/display/skl_watermark_regs.h | 87 + drivers/gpu/drm/i915/display/tests/Makefile | 7 + .../drm/i915/display/tests/intel_dp_link_test.c | 1408 ++++ 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 | 2058 +++++ 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 | 2728 +++++++ 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 | 867 ++ .../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 | 677 ++ drivers/gpu/drm/i915/gem/i915_gem_panic.c | 147 + drivers/gpu/drm/i915/gem/i915_gem_panic.h | 11 + 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 | 4177 ++++++++++ .../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 | 1197 +++ 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 | 1792 ++++ 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_params.c | 224 + drivers/gpu/drm/i915/i915_params.h | 80 + drivers/gpu/drm/i915/i915_pci.c | 1045 +++ 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 | 641 ++ 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 + 915 files changed, 424833 insertions(+) create mode 100644 drivers/gpu/drm/i915/.gitignore create mode 100644 drivers/gpu/drm/i915/.kunitconfig 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_caps.c create mode 100644 drivers/gpu/drm/i915/display/intel_dp_link_caps.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/tests/Makefile create mode 100644 drivers/gpu/drm/i915/display/tests/intel_dp_link_test.c 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_panic.c create mode 100644 drivers/gpu/drm/i915/gem/i915_gem_panic.h 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_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/.kunitconfig b/drivers/gpu/drm/i915/.kunitconfig new file mode 100644 index 000000000..1b47fe62b --- /dev/null +++ b/drivers/gpu/drm/i915/.kunitconfig @@ -0,0 +1,9 @@ +CONFIG_EXPERT=y +CONFIG_MODULES=y +CONFIG_KUNIT=y +CONFIG_PCI=y +CONFIG_DEBUG_FS=y +CONFIG_MAGIC_SYSRQ=y +CONFIG_DRM=y +CONFIG_DRM_I915=y +CONFIG_DRM_I915_KUNIT_TEST=y 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..dc43dcfba --- /dev/null +++ b/drivers/gpu/drm/i915/Kconfig.debug @@ -0,0 +1,290 @@ +# 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_KUNIT_TEST + tristate "KUnit tests for the drm i915 driver" if !KUNIT_ALL_TESTS + depends on DRM_I915 && KUNIT && DEBUG_FS + default KUNIT_ALL_TESTS + help + Choose this option to allow the driver to perform selftests under + the kunit framework + + Recommended for 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..a83fa8be0 --- /dev/null +++ b/drivers/gpu/drm/i915/Makefile @@ -0,0 +1,462 @@ +# 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 + +# "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_panic.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_caps.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 + +obj-$(CONFIG_DRM_I915_KUNIT_TEST) += display/tests/ + +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..b867443ff --- /dev/null +++ b/drivers/gpu/drm/i915/display/g4x_dp.c @@ -0,0 +1,1437 @@ +// 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_atomic_state *state, + 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(state, 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) +{ + struct intel_digital_port *dig_port = enc_to_dig_port(to_intel_encoder(encoder)); + + intel_dp_encoder_flush_work(encoder); + + drm_encoder_cleanup(encoder); + intel_dp_link_cleanup(&dig_port->dp); + kfree(dig_port); +} + +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 (intel_dp_link_init(&dig_port->dp) != 0) + goto err_dp_init; + + 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: + intel_dp_link_cleanup(&dig_port->dp); +err_dp_init: + 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..4c33aa1d1 --- /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_atomic_state *state, + 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_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..f828ec962 --- /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_cdclk * 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..df6da1c6a --- /dev/null +++ b/drivers/gpu/drm/i915/display/i9xx_plane.c @@ -0,0 +1,1275 @@ +// 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_cdclk 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_cdclk; + + 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); + + if (display->platform.valleyview || display->platform.cherryview) + drm_plane_create_blend_mode_property(&plane->base, + BIT(DRM_MODE_BLEND_PREMULTI)); + + 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..ea0cdb782 --- /dev/null +++ b/drivers/gpu/drm/i915/display/icl_dsi.c @@ -0,0 +1,2044 @@ +/* + * 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_atomic_state *state, + 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(state, pipe_config, intel_connector); + 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..f1383764b --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_alpm.c @@ -0,0 +1,650 @@ +// 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 (crtc_state->has_psr || + !intel_vrr_is_fixed_rr(crtc_state) || + !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 (!intel_vrr_always_use_vrr_tg(display)) + 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; + + if (intel_display_power_dc3co_allowed(display)) + pr_alpm_ctl |= PR_ALPM_CTL_USE_DC3CO_IDLE_PROTOCOL; + else + pr_alpm_ctl &= ~PR_ALPM_CTL_USE_DC3CO_IDLE_PROTOCOL; + + 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..9d0d47c79 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_atomic.c @@ -0,0 +1,374 @@ +/* + * 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); + + crtc_state->hw.degamma_lut = NULL; + crtc_state->hw.gamma_lut = NULL; + crtc_state->hw.ctm = NULL; + crtc_state->pre_csc_lut = NULL; + crtc_state->post_csc_lut = NULL; +} + +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..eae76e961 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_audio.c @@ -0,0 +1,1441 @@ +/* + * 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_dp.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); +} + +static +bool intel_audio_needs_cpu_transcoder_id(const struct intel_crtc_state *crtc_state) +{ + return intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST) || + intel_dp_is_uhbr(crtc_state); +} + +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; + audio_state->needs_cpu_transcoder_id = + intel_audio_needs_cpu_transcoder_id(crtc_state); + 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/HBR cases. MST and UHBR SST are addressed by + * a real cpu_transcoder. + */ + if (!intel_audio_needs_cpu_transcoder_id(crtc_state)) + 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; + audio_state->needs_cpu_transcoder_id = false; + 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/HBR cases. MST and UHBR SST are addressed by + * a real cpu_transcoder. + */ + if (!intel_audio_needs_cpu_transcoder_id(old_crtc_state)) + 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 for a given (port, cpu_transcoder). + * + * Streams are addressed either by a real cpu_transcoder (DP MST and UHBR SST, + * i.e. entries whose stored needs_cpu_transcoder_id is true) or by port alone + * (legacy SST). Both the signalling side (pin_eld_notify()) and the lookup + * side use the same predicate, so the two are symmetric. + * + * cpu_transcoder >= 0 & needs_cpu_transcoder_id: return audio.state[cpu_transcoder] + * when the port matches. + * cpu_transcoder < 0 & !needs_cpu_transcoder_id: return the first port-matching + * entry. + * cpu_transcoder = 0 & !needs_cpu_transcoder_id: falls to the port-only + * loop so the first device entry of a legacy SST port is still found. + */ +static struct intel_audio_state *find_audio_state(struct intel_display *display, + int port, int cpu_transcoder) +{ + /* MST, or UHBR SST. */ + 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 && + audio_state->needs_cpu_transcoder_id) + return audio_state; + } + + /* Legacy SST. */ + 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 && + !audio_state->needs_cpu_transcoder_id) + 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..97cbae2e5 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_bios.c @@ -0,0 +1,3881 @@ +/* + * 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 bool is_panel_type_valid(int panel_type) +{ + return panel_type >= 0 && panel_type < 16; +} + +static bool is_panel_type_pnp(int panel_type) +{ + return panel_type == 0xff; +} + +static bool is_panel_type_valid_or_pnp(int panel_type) +{ + return is_panel_type_valid(panel_type) || is_panel_type_pnp(panel_type); +} + +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 (!is_panel_type_valid_or_pnp(lfp_options->panel_type)) { + 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) { + if (!is_panel_type_valid_or_pnp(lfp_options->panel_type2)) { + drm_dbg_kms(display->drm, "Invalid VBT panel type 2 0x%x\n", + lfp_options->panel_type2); + return -1; + } + + 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 (is_panel_type_valid(panel_types[PANEL_TYPE_OPREGION].panel_type)) + i = PANEL_TYPE_OPREGION; + else if (is_panel_type_pnp(panel_types[PANEL_TYPE_VBT].panel_type) && + is_panel_type_valid(panel_types[PANEL_TYPE_PNPID].panel_type)) + i = PANEL_TYPE_PNPID; + else if (is_panel_type_valid(panel_types[PANEL_TYPE_VBT].panel_type)) + 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] = DDC_BUS_DDI_B, + [GMBUS_PIN_2] = DDC_BUS_DDI_C, + [GMBUS_PIN_4] = DDC_BUS_DDI_D, /* sic */ + [GMBUS_PIN_3] = DDC_BUS_DDI_F, /* sic */ +}; + +static const u8 icp_ddc_pin_map[] = { + [GMBUS_PIN_1] = ICL_DDC_BUS_DDI_A, + [GMBUS_PIN_2] = ICL_DDC_BUS_DDI_B, + [GMBUS_PIN_3] = TGL_DDC_BUS_DDI_C, + [GMBUS_PIN_9_TC1] = ICL_DDC_BUS_PORT_1, + [GMBUS_PIN_10_TC2] = ICL_DDC_BUS_PORT_2, + [GMBUS_PIN_11_TC3] = ICL_DDC_BUS_PORT_3, + [GMBUS_PIN_12_TC4] = ICL_DDC_BUS_PORT_4, + [GMBUS_PIN_13_TC5] = TGL_DDC_BUS_PORT_5, + [GMBUS_PIN_14_TC6] = TGL_DDC_BUS_PORT_6, +}; + +static const u8 rkl_pch_tgp_ddc_pin_map[] = { + [GMBUS_PIN_1] = ICL_DDC_BUS_DDI_A, + [GMBUS_PIN_2] = ICL_DDC_BUS_DDI_B, + [GMBUS_PIN_9_TC1] = RKL_DDC_BUS_DDI_D, + [GMBUS_PIN_10_TC2] = RKL_DDC_BUS_DDI_E, +}; + +static const u8 adls_ddc_pin_map[] = { + [GMBUS_PIN_1] = ICL_DDC_BUS_DDI_A, + [GMBUS_PIN_9_TC1] = ADLS_DDC_BUS_PORT_TC1, + [GMBUS_PIN_10_TC2] = ADLS_DDC_BUS_PORT_TC2, + [GMBUS_PIN_11_TC3] = ADLS_DDC_BUS_PORT_TC3, + [GMBUS_PIN_12_TC4] = ADLS_DDC_BUS_PORT_TC4, +}; + +static const u8 gen9bc_tgp_ddc_pin_map[] = { + [GMBUS_PIN_2] = DDC_BUS_DDI_B, + [GMBUS_PIN_9_TC1] = DDC_BUS_DDI_C, + [GMBUS_PIN_10_TC2] = DDC_BUS_DDI_D, +}; + +static const u8 adlp_ddc_pin_map[] = { + [GMBUS_PIN_1] = ICL_DDC_BUS_DDI_A, + [GMBUS_PIN_2] = ICL_DDC_BUS_DDI_B, + [GMBUS_PIN_9_TC1] = ADLP_DDC_BUS_PORT_TC1, + [GMBUS_PIN_10_TC2] = ADLP_DDC_BUS_PORT_TC2, + [GMBUS_PIN_11_TC3] = ADLP_DDC_BUS_PORT_TC3, + [GMBUS_PIN_12_TC4] = 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..99fd84a5a --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_bw.c @@ -0,0 +1,1552 @@ +// 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_display_wa.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 + +#define PEAK_BW_THRESHOLD 20000 + +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_qgv_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.num_psf_gv_points; + unsigned int num_qgv_points = display->bw.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 (HAS_PMDEMAND(display)) + 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_init_qgv_info(struct intel_display *display, + const struct dram_info *dram_info, + struct intel_qgv_info *qi) +{ + qi->num_qgv_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; + /* + * Wa_16030862157 + * Xe3p supports a fully-populated 16-channel LPDDR + * config (4 memory controllers x 4 channels); earlier + * D14+ platforms top out at 8. + */ + qi->max_numchannels = + intel_display_wa(display, INTEL_DISPLAY_WA_16030862157) ? 16 : 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; + } + + return 0; +} + +static int icl_get_qgv_points(struct intel_display *display, + const struct dram_info *dram_info, + struct intel_qgv_info *qi) +{ + int i, ret; + + if (icl_init_qgv_info(display, dram_info, qi)) + return -EINVAL; + + if (drm_WARN_ON(display->drm, + qi->num_qgv_points > ARRAY_SIZE(qi->points))) + qi->num_qgv_points = ARRAY_SIZE(qi->points); + + for (i = 0; i < qi->num_qgv_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_qgv_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 void update_sagv_status(struct intel_display *display, int qgv_points) +{ + /* + * 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 (qgv_points == 1) + display->sagv.status = I915_SAGV_NOT_CONTROLLED; + else + display->sagv.status = I915_SAGV_ENABLED; +} + +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); + + display->bw.num_qgv_points = qi.num_qgv_points; + display->bw.num_psf_gv_points = qi.num_psf_points; + + 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; + + for (j = 0; j < qi.num_qgv_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]); + } + } + + return 0; +} + +static int tgl_peakbw(int num_channels, int channel_width, int dclk) +{ + return num_channels * (channel_width / 8) * dclk; +} + +static void xe3_add_peakbw_threshold(struct intel_display *display) +{ + u8 qgv_points = display->bw.num_qgv_points; + + if (!HAS_PEAK_BW_THRESHOLD(display)) + return; + + if (qgv_points >= I915_NUM_QGV_POINTS) { + drm_dbg_kms(display->drm, "QGV points maxed out; skipping peak bandwidth threshold.\n"); + return; + } + + if (qgv_points <= 1) + return; + + display->bw.num_qgv_points++; + + display->bw.peakbw[qgv_points] = PEAK_BW_THRESHOLD; + + for (int i = 0; i < ARRAY_SIZE(display->bw.max); i++) + display->bw.max[i].deratedbw[qgv_points] = PEAK_BW_THRESHOLD; + + drm_dbg_kms(display->drm, "An extra QGV point %d added for Peak bw threshold of %d\n", + qgv_points, PEAK_BW_THRESHOLD); +} + +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); + /* + * Wa_16030862157 + * clperchgroup = 4kpagespermempage * clperchperblock, + * clperchperblock = max(8 / num_channels, 1) * interleave + * + * The 8 / num_channels truncating divide collapses to 0 for + * >8-channel configs (16-channel: 8 / 16 = 0); the max(..., 1) floor + * keeps clperchperblock >= 1 there while preserving the literal + * truncating divide for <=8-channel configs. + */ + clperchgroup = 4 * max(8 / num_channels, 1) * qi.deinterleave; + + display->bw.num_qgv_points = qi.num_qgv_points; + display->bw.num_psf_gv_points = qi.num_psf_points; + + display->bw.max[0].num_planes = U8_MAX; + + 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; + + if (i < num_groups - 1) { + struct intel_bw_info *bi_next = &display->bw.max[i + 1]; + + if (clpchgroup < clperchgroup) + bi_next->num_planes = (ipqdepth - clpchgroup) / clpchgroup; + else + bi_next->num_planes = 0; + } + + for (j = 0; j < qi.num_qgv_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]); + } + } + + for (i = 0; i < qi.num_qgv_points; i++) { + const struct intel_qgv_point *sp = &qi.points[i]; + + display->bw.peakbw[i] = tgl_peakbw(num_channels, qi.channel_width, sp->dclk); + + drm_dbg_kms(display->drm, "QGV %d: peakbw=%u\n", i, display->bw.peakbw[i]); + } + + /* For xe3 cases add an extra qgv point for Peak bw threshold */ + xe3_add_peakbw_threshold(display); + + for (i = 0; i < qi.num_psf_points; i++) { + const struct intel_psf_gv_point *sp = &qi.psf_points[i]; + + display->bw.psf_bw[i] = adl_calc_psf_bw(sp->clk); + + drm_dbg_kms(display->drm, "PSF GV %d: bw=%u\n", i, display->bw.psf_bw[i]); + } + + return 0; +} + +static void dg2_get_bw_info(struct intel_display *display) +{ + int i; + + display->bw.num_qgv_points = 1; + + display->bw.max[0].num_planes = U8_MAX; + display->bw.max[0].deratedbw[0] = display->platform.dg2_g11 ? 38000 : 50000; + + drm_dbg_kms(display->drm, + "QGV 0: deratedbw=%u\n", + display->bw.max[0].deratedbw[0]); + + /* Bandwidth does not depend on # of planes; set all groups the same */ + for (i = 1; i < ARRAY_SIZE(display->bw.max); i++) + display->bw.max[i] = display->bw.max[0]; +} + +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); + + display->bw.num_qgv_points = qi.num_qgv_points; + + display->bw.max[0].num_planes = U8_MAX; + + for (i = 0; i < qi.num_qgv_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.peakbw[i] = bw; + + drm_dbg_kms(display->drm, "QGV %d: deratedbw=%u peakbw=%u\n", + i, display->bw.max[0].deratedbw[i], display->bw.peakbw[i]); + } + + /* Bandwidth does not depend on # of planes; set all groups the same */ + for (i = 1; i < ARRAY_SIZE(display->bw.max); i++) + display->bw.max[i] = 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_qgv_points != 2); + + return 0; +} + +static unsigned int icl_max_bw_index(struct intel_display *display, + int num_planes, int qgv_point) +{ + int i; + + if (qgv_point >= display->bw.num_qgv_points) + return UINT_MAX; + + /* + * 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]; + + 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; + + if (qgv_point >= display->bw.num_qgv_points) + return UINT_MAX; + + for (i = ARRAY_SIZE(display->bw.max) - 1; i >= 0; i--) { + const struct intel_bw_info *bi = + &display->bw.max[i]; + + if (num_planes <= bi->num_planes) + return i; + } + + return UINT_MAX; +} + +static unsigned int adl_psf_bw(struct intel_display *display, + int psf_gv_point) +{ + return display->bw.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); + } + + update_sagv_status(display, display->bw.num_qgv_points); +} + +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.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.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.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 max_data_rate = + icl_qgv_bw(display, num_active_planes, i); + + if (max_data_rate < data_rate) + continue; + + if (max_data_rate < best_rate) { + best_rate = max_data_rate; + qgv_peak_bw = display->bw.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.num_psf_gv_points; + unsigned int num_qgv_points = display->bw.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 (HAS_PMDEMAND(display)) + 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..a1a572099 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_cdclk.c @@ -0,0 +1,4306 @@ +/* + * 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, swf18; + + /* + * 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 + */ + swf18 = intel_de_read(display, SWF_ILK(0x18)); + if ((swf18 & 0x00FFFFFF) == 0) { + drm_dbg_kms(display->drm, "Sanitizing CDCLK due to SWF18 0x%x\n", swf18); + 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) { + drm_dbg_kms(display->drm, "Sanitizing CDCLK due to PLL not enabled/locked\n"); + 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) { + cdctl &= ~CDCLK_FREQ_DECIMAL_MASK; + cdctl |= expected & CDCLK_FREQ_DECIMAL_MASK; + + if (cdctl != expected) { + drm_dbg_kms(display->drm, "Sanitizing CDCLK due to CDCLK_CTL 0x%x, expected 0x%x)\n", + intel_de_read(display, CDCLK_CTL), expected); + goto sanitize; + } + + drm_dbg_kms(display->drm, "Sanitizing CDCLK decimal divider (CDCLK_CTL 0x%x, expected 0x%x)\n", + intel_de_read(display, CDCLK_CTL), expected); + + intel_de_write(display, CDCLK_CTL, expected); + } + + /* All well; nothing to sanitize */ + return; + +sanitize: + /* 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_mask(struct intel_display *display) +{ + if (DISPLAY_VER(display) >= 11) + return ICL_CDCLK_CD2X_PIPE_MASK; + else + return BXT_CDCLK_CD2X_PIPE_MASK; +} + +static u32 bxt_cdclk_cd2x_pipe(struct intel_display *display, enum pipe pipe) +{ + 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) { + drm_dbg_kms(display->drm, "Sanitizing CDCLK due to PLL not enabled/locked\n"); + 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) { + drm_dbg_kms(display->drm, "Sanitizing CDCLK due to bad CDCLK frequency\n"); + 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) { + drm_dbg_kms(display->drm, "Sanitizing CDCLK due to bad VCO frequency\n"); + 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). + */ + if (DISPLAY_VER(display) < 30) { + cdctl &= ~bxt_cdclk_cd2x_pipe_mask(display); + cdctl |= bxt_cdclk_cd2x_pipe(display, INVALID_PIPE); + } + + if (cdctl != expected) { + if (DISPLAY_VER(display) < 20) { + cdctl &= ~CDCLK_FREQ_DECIMAL_MASK; + cdctl |= expected & CDCLK_FREQ_DECIMAL_MASK; + } + + if (cdctl != expected) { + drm_dbg_kms(display->drm, "Sanitizing CDCLK due to CDCLK_CTL 0x%x, expected 0x%x\n", + intel_de_read(display, CDCLK_CTL), expected); + goto sanitize; + } + + drm_dbg_kms(display->drm, "Sanitizing CDCLK decimal divider (CDCLK_CTL 0x%x, expected 0x%x)\n", + intel_de_read(display, CDCLK_CTL), expected); + + intel_de_write(display, CDCLK_CTL, expected); + } + + /* All well; nothing to sanitize */ + return; + +sanitize: + /* 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 int 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 */ +int intel_cdclk_ppc(struct intel_display *display, bool double_wide) +{ + return HAS_2PPC(display) || 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_cdclk); +} + +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..a60cbf745 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_cdclk.h @@ -0,0 +1,81 @@ +/* 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; +}; + +int intel_cdclk_ppc(struct intel_display *display, bool double_wide); +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..c8e0f90af --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_cmtg.c @@ -0,0 +1,451 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright (C) 2025 Intel Corporation + */ + +#include + +#include +#include + +#include "intel_cmtg.h" +#include "intel_cmtg_regs.h" +#include "intel_crtc.h" +#include "intel_de.h" +#include "intel_display.h" +#include "intel_display_device.h" +#include "intel_display_irq.h" +#include "intel_display_power.h" +#include "intel_display_regs.h" +#include "intel_display_types.h" +#include "intel_vrr.h" +#include "intel_vrr_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 inline enum transcoder to_cmtg_transcoder(enum transcoder cpu_transcoder) +{ + switch (cpu_transcoder) { + case TRANSCODER_A: + return TRANSCODER_CMTG0; + case TRANSCODER_B: + return TRANSCODER_CMTG1; + default: + return INVALID_TRANSCODER; + } +} + +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(TRANSCODER_A)); + cmtg_config->cmtg_a_enable = val & CMTG_ENABLE; + + if (intel_cmtg_has_cmtg_b(display)) { + val = intel_de_read(display, TRANS_CMTG_CTL(TRANSCODER_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_all(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(TRANSCODER_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(TRANSCODER_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); +} + +void intel_cmtg_disable(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 transcoder cpu_transcoder = crtc_state->cpu_transcoder; + enum transcoder cmtg_transcoder = to_cmtg_transcoder(crtc_state->cpu_transcoder); + u32 clk_sel_clr = 0, interrupt_mask = 0; + + if (!crtc->cmtg.enabled) + return; + + if (drm_WARN_ON(display->drm, cmtg_transcoder == INVALID_TRANSCODER)) + return; + + crtc->cmtg.enabled = false; + intel_de_rmw(display, TRANS_VRR_CTL(display, cmtg_transcoder), + VRR_CTL_VRR_ENABLE | VRR_CTL_FLIP_LINE_EN, 0); + + /* + * Use cpu_transcoder for: + * 1. Exclusive CMTG registers that do not use the standard transcoder offset + * (e.g., TRANS_CMTG_CTL, CMTG_CLK_SEL). + * 2. Registers shared between the eDP and CMTG transcoders. + * (e.g., TRANS_DDI_FUNC_CTL2). + */ + + intel_de_rmw(display, TRANS_DDI_FUNC_CTL2(display, cpu_transcoder), + CMTG_SECONDARY_MODE, 0); + intel_de_rmw(display, CMTG_SCANLINE_GB1(cpu_transcoder), CMTG_HW_GB_ENABLE, 0); + + intel_de_rmw(display, TRANS_CMTG_CTL(cpu_transcoder), CMTG_ENABLE, 0); + + if (intel_de_wait_for_clear_ms(display, TRANS_CMTG_CTL(cpu_transcoder), CMTG_STATE, 50)) { + drm_WARN(display->drm, 1, "CMTG: %s disable timeout\n", + transcoder_name(cpu_transcoder)); + return; + } + + clk_sel_clr = cpu_transcoder == TRANSCODER_A ? CMTG_CLK_SEL_A_MASK : CMTG_CLK_SEL_B_MASK; + intel_de_rmw(display, CMTG_CLK_SEL, clk_sel_clr, 0); + + drm_dbg_kms(display->drm, "CMTG: %s disabled\n", transcoder_name(cpu_transcoder)); + + if (cpu_transcoder == TRANSCODER_A) + interrupt_mask = CMTG_VBLANK_A; + else if (cpu_transcoder == TRANSCODER_B) + interrupt_mask = CMTG_VBLANK_B; + + intel_display_irq_port_interrupt_mask(display, interrupt_mask, true); +} + +/* + * 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_all(display, &cmtg_config); +} + +bool intel_cmtg_is_allowed(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + + if ((cpu_transcoder == TRANSCODER_A || cpu_transcoder == TRANSCODER_B) && + DISPLAY_VER(display) == 35 && intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP)) + return true; + + return false; +} + +void intel_cmtg_set_clk_select(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + u32 clk_sel_clr = 0; + u32 clk_sel_set = 0; + + if (!intel_cmtg_is_allowed(crtc_state)) + return; + + if (cpu_transcoder == TRANSCODER_A) { + clk_sel_clr = CMTG_CLK_SEL_A_MASK; + clk_sel_set = CMTG_CLK_SELECT_PHYA_ENABLE; + } else if (cpu_transcoder == TRANSCODER_B) { + clk_sel_clr = CMTG_CLK_SEL_B_MASK; + clk_sel_set = CMTG_CLK_SELECT_PHYB_ENABLE; + } + + if (clk_sel_set) + intel_de_rmw(display, CMTG_CLK_SEL, clk_sel_clr, clk_sel_set); +} + +void intel_cmtg_set_timings(const struct intel_crtc_state *crtc_state, enum set_timing_type type) +{ + enum transcoder cmtg_transcoder = to_cmtg_transcoder(crtc_state->cpu_transcoder); + + if (cmtg_transcoder == INVALID_TRANSCODER) + return; + + if (!intel_cmtg_is_allowed(crtc_state)) + return; + + if (type == LRR) + intel_set_transcoder_timings_lrr(crtc_state, cmtg_transcoder); + else + intel_set_transcoder_timings(crtc_state, cmtg_transcoder); +} + +void intel_cmtg_set_vrr_timings(const struct intel_crtc_state *crtc_state) +{ + enum transcoder cmtg_transcoder = to_cmtg_transcoder(crtc_state->cpu_transcoder); + + if (!intel_cmtg_is_allowed(crtc_state)) + return; + + intel_vrr_set_fixed_rr_timings(crtc_state, cmtg_transcoder); +} + +void intel_cmtg_set_vrr_ctl(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + enum transcoder cmtg_transcoder = to_cmtg_transcoder(crtc_state->cpu_transcoder); + u32 vrr_ctl; + + if (!intel_cmtg_is_allowed(crtc_state)) + return; + + vrr_ctl = VRR_CTL_VRR_ENABLE | VRR_CTL_FLIP_LINE_EN | + XELPD_VRR_CTL_VRR_GUARDBAND(crtc_state->vrr.guardband); + + /* TODO: The code below may need to be revisited once CMRR is enabled */ + if (crtc_state->cmrr.enable) + vrr_ctl |= VRR_CTL_CMRR_ENABLE; + + intel_de_write(display, TRANS_VRR_CTL(display, cmtg_transcoder), vrr_ctl); +} + +void intel_cmtg_set_m_n(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + enum transcoder cmtg_transcoder = to_cmtg_transcoder(crtc_state->cpu_transcoder); + const struct intel_link_m_n *m_n = &crtc_state->dp_m_n; + + if (!intel_cmtg_is_allowed(crtc_state)) + return; + + intel_de_write(display, PIPE_LINK_M1(display, cmtg_transcoder), m_n->link_m); + intel_de_write(display, PIPE_LINK_N1(display, cmtg_transcoder), m_n->link_n); +} + +static void intel_cmtg_enable_sync(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + u32 cmtg_ctl; + + cmtg_ctl = CMTG_SYNC_TO_PORT | CMTG_ENABLE; + + intel_de_rmw(display, TRANS_CMTG_CTL(cpu_transcoder), 0, cmtg_ctl); + if (intel_de_wait_for_clear_ms(display, TRANS_CMTG_CTL(cpu_transcoder), + CMTG_SYNC_TO_PORT, 50)) { + drm_WARN(display->drm, 1, "CMTG: %s enable timeout\n", + transcoder_name(cpu_transcoder)); + } +} + +static void intel_cmtg_enable_ddi(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 transcoder cpu_transcoder = crtc_state->cpu_transcoder; + u32 interrupt_mask = 0; + + intel_de_rmw(display, TRANS_DDI_FUNC_CTL2(display, cpu_transcoder), 0, CMTG_SECONDARY_MODE); + intel_de_rmw(display, CMTG_SCANLINE_GB1(cpu_transcoder), 0, CMTG_HW_GB_ENABLE); + crtc->cmtg.enabled = true; + drm_dbg_kms(display->drm, "CMTG: %s enabled\n", transcoder_name(cpu_transcoder)); + + /* + * TODO: Currently cmtg is enabled along with eDP transcoder so cmtg + * interrupt is not enabled through IER, need to do some fine + * tuning in future. + */ + + if (cpu_transcoder == TRANSCODER_A) + interrupt_mask = CMTG_VBLANK_A; + else if (cpu_transcoder == TRANSCODER_B) + interrupt_mask = CMTG_VBLANK_B; + + intel_display_irq_port_interrupt_mask(display, interrupt_mask, false); +} + +/* Bspec: 75253 */ +#define DC3CO_ENTRY_LATENCY_US 55 +#define DC3CO_EXIT_LATENCY_US 40 + +static void intel_cmtg_set_hwgb(const struct intel_crtc_state *crtc_state) +{ + struct intel_display *display = to_intel_display(crtc_state); + enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; + u32 breakeven_gb; + u32 dc5_exit_latency; + u32 line_time_us = 75; /* Max default initialization value */ + u32 val; + + if (crtc_state->linetime) + line_time_us = DIV_ROUND_UP(crtc_state->linetime, 8); + + /* Break Even Guardband - DC3co Entry Latency / linetime */ + breakeven_gb = DIV_ROUND_UP(DC3CO_ENTRY_LATENCY_US, line_time_us); + + /* DC5 Exit Latency - DC3co Exit Latency / linetime */ + dc5_exit_latency = DIV_ROUND_UP(DC3CO_EXIT_LATENCY_US, line_time_us); + + val = REG_FIELD_PREP(CMTG_HW_GB_BREAKEVEN_MASK, breakeven_gb) | + REG_FIELD_PREP(CMTG_HW_GB_DC5_EXIT_LATENCY_MASK, dc5_exit_latency) | + REG_FIELD_PREP(CMTG_HW_GB_UP_LW_BG_DIFF_MASK, 1); + + intel_de_write(display, CMTG_HW_GB(cpu_transcoder), val); +} + +static void intel_cmtg_restore(const struct intel_crtc_state *crtc_state) +{ + intel_cmtg_set_clk_select(crtc_state); + intel_cmtg_set_timings(crtc_state, MODESET); + intel_cmtg_set_vrr_timings(crtc_state); + intel_cmtg_set_vrr_ctl(crtc_state); + intel_cmtg_set_m_n(crtc_state); +} + +void intel_cmtg_program(struct intel_atomic_state *state) +{ + struct intel_display *display = to_intel_display(state); + struct intel_crtc *crtc; + struct intel_crtc_state *new_crtc_state; + bool dc3co_to_dc6 = intel_display_power_get_and_reset_dc3co_to_dc6(display); + + for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state) { + bool modeset = intel_crtc_needs_modeset(new_crtc_state); + + if (!intel_cmtg_is_allowed(new_crtc_state)) + continue; + + if ((modeset || dc3co_to_dc6) && + new_crtc_state->hw.active && !crtc->cmtg.enabled) { + if (dc3co_to_dc6) + intel_cmtg_restore(new_crtc_state); + + intel_cmtg_enable_sync(new_crtc_state); + intel_cmtg_set_hwgb(new_crtc_state); + intel_cmtg_enable_ddi(new_crtc_state); + } + } +} 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..a08cb2dce --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_cmtg.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (C) 2025 Intel Corporation + */ + +#ifndef __INTEL_CMTG_H__ +#define __INTEL_CMTG_H__ + +#include + +struct intel_atomic_state; +struct intel_display; +struct intel_crtc_state; + +enum set_timing_type { + MODESET = 0, + LRR +}; + +void intel_cmtg_disable(const struct intel_crtc_state *crtc_state); +void intel_cmtg_set_m_n(const struct intel_crtc_state *crtc_state); +void intel_cmtg_set_vrr_timings(const struct intel_crtc_state *crtc_state); +void intel_cmtg_set_vrr_ctl(const struct intel_crtc_state *crtc_state); +void intel_cmtg_set_timings(const struct intel_crtc_state *crtc_state, enum set_timing_type type); +void intel_cmtg_set_clk_select(const struct intel_crtc_state *crtc_state); +void intel_cmtg_sanitize(struct intel_display *display); +bool intel_cmtg_is_allowed(const struct intel_crtc_state *crtc_state); +void intel_cmtg_program(struct intel_atomic_state *state); + +#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..18dcb665d --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_cmtg_regs.h @@ -0,0 +1,41 @@ +/* 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_SELECT_PHYA_ENABLE REG_FIELD_PREP(CMTG_CLK_SEL_A_MASK, 0x4) +#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_SELECT_PHYB_ENABLE REG_FIELD_PREP(CMTG_CLK_SEL_B_MASK, 0x6) +#define CMTG_CLK_SEL_B_DISABLED REG_FIELD_PREP(CMTG_CLK_SEL_B_MASK, 0) + +#define _TRANS_CMTG_CTL_A 0x6fa88 +#define _TRANS_CMTG_CTL_B 0x6fb88 +#define TRANS_CMTG_CTL(trans) _MMIO_TRANS((trans), \ + _TRANS_CMTG_CTL_A, _TRANS_CMTG_CTL_B) +#define CMTG_ENABLE REG_BIT(31) +#define CMTG_SYNC_TO_PORT REG_BIT(29) +#define CMTG_STATE REG_BIT(23) + +#define _CMTG_HW_GB_A 0x6fa8c +#define _CMTG_HW_GB_B 0x6fb8c +#define CMTG_HW_GB(trans) _MMIO_TRANS((trans), \ + _CMTG_HW_GB_A, _CMTG_HW_GB_B) +#define CMTG_HW_GB_BREAKEVEN_MASK REG_GENMASK(11, 0) +#define CMTG_HW_GB_DC5_EXIT_LATENCY_MASK REG_GENMASK(27, 16) +#define CMTG_HW_GB_UP_LW_BG_DIFF_MASK REG_GENMASK(31, 28) + +#define _CMTG_SCANLINE_GB1_A 0x456A0 +#define _CMTG_SCANLINE_GB1_B 0x456C0 +#define CMTG_SCANLINE_GB1(trans) _MMIO_TRANS((trans), \ + _CMTG_SCANLINE_GB1_A, _CMTG_SCANLINE_GB1_B) +#define CMTG_HW_GB_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..87ced9f6f --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_color.c @@ -0,0 +1,4362 @@ +/* + * 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; + int i, lut_size = 128; + u32 lut_val; + + if (!icl_is_hdr_plane(display, plane)) + return; + + intel_de_write_dsb(display, dsb, + PLANE_PRE_CSC_GAMC_INDEX_ENH(pipe, plane, 0), + PLANE_PAL_PREC_AUTO_INCREMENT); + + for (i = 0; i < lut_size + 3; i++) { + if (pre_csc_lut) { + if (i < lut_size) + lut_val = drm_color_lut32_extract(pre_csc_lut[i].green, 24); + /* else duplicate last lut_val */ + } else { + if (i < lut_size) + lut_val = (i * ((1 << 24) - 1)) / (lut_size - 1); + else + lut_val = 1 << 24; + } + + intel_de_write_dsb(display, dsb, + PLANE_PRE_CSC_GAMC_DATA_ENH(pipe, plane, 0), + lut_val); + } + + 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; + int i, lut_size = 32; + u32 lut_val; + + if (!icl_is_hdr_plane(display, plane)) + return; + + 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); + + for (i = 0; i < lut_size + 3; i++) { + if (post_csc_lut) { + if (i < lut_size) + lut_val = drm_color_lut32_extract(post_csc_lut[i].green, 24); + /* else clamp to the last LUT value to prevent step discontinuity */ + } else { + if (i < lut_size) + lut_val = (i * ((1 << 24) - 1)) / (lut_size - 1); + else + lut_val = 1 << 24; + } + + intel_de_write_dsb(display, dsb, + PLANE_POST_CSC_GAMC_DATA_ENH(pipe, plane, 0), + lut_val); + } + + 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..5b8968197 --- /dev/null +++ b/drivers/gpu/drm/i915/display/intel_crt.c @@ -0,0 +1,1142 @@ +/* + * 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