From 4e38a206c290c0a4b4263276e850ecc3ff32dc21 Mon Sep 17 00:00:00 2001 From: Steve Cohen Date: Wed, 11 Oct 2017 11:59:44 -0400 Subject: [PATCH] drm/msm: Add snapshot of SDE and supporting drivers Snapshot of SDE driver. This includes DRM SDE, DP, and DSI drivers, v4l2 rotator driver, and clk driver for display plls. Upstream changes to drm/msm have been merged but require further fixes and validation before enabling this driver. Snapshot was taken from msm-4.9 as of: commit f54b6540b397 ("wil6210: add proper locking in cfg suspend") Change-Id: Ied16c0c33d1321c96f080bc7dafe0ab302657861 Signed-off-by: Steve Cohen --- .../devicetree/bindings/display/msm/dsi.txt | 16 + .../bindings/display/msm/sde-rsc.txt | 96 + .../devicetree/bindings/display/msm/sde.txt | 736 +++ .../bindings/drm/msm/mdss-dsi-panel.txt | 772 +++ .../devicetree/bindings/drm/msm/sde-dp.txt | 217 + .../devicetree/bindings/drm/msm/sde-dsi.txt | 102 + .../devicetree/bindings/drm/msm/sde-wb.txt | 23 + .../devicetree/bindings/fb/mdss-dsi-panel.txt | 742 +++ .../devicetree/bindings/fb/mdss-pll.txt | 103 + .../bindings/media/video/msm-sde-rotator.txt | 223 + .../devicetree/bindings/msm_hdcp/msm_hdcp.txt | 14 + drivers/clk/qcom/Kconfig | 2 + drivers/clk/qcom/Makefile | 2 + drivers/clk/qcom/clk-rcg2.c | 5 +- drivers/clk/qcom/mdss/Kconfig | 7 + drivers/clk/qcom/mdss/Makefile | 6 + drivers/clk/qcom/mdss/mdss-dp-pll-10nm-util.c | 766 +++ drivers/clk/qcom/mdss/mdss-dp-pll-10nm.c | 310 + drivers/clk/qcom/mdss/mdss-dp-pll-10nm.h | 59 + drivers/clk/qcom/mdss/mdss-dp-pll.h | 33 + .../clk/qcom/mdss/mdss-dsi-20nm-pll-util.c | 1011 +++ drivers/clk/qcom/mdss/mdss-dsi-pll-10nm.c | 1519 +++++ drivers/clk/qcom/mdss/mdss-dsi-pll-20nm.c | 608 ++ drivers/clk/qcom/mdss/mdss-dsi-pll-28hpm.c | 335 + drivers/clk/qcom/mdss/mdss-dsi-pll-28lpm.c | 306 + .../clk/qcom/mdss/mdss-dsi-pll-8996-util.c | 1137 ++++ drivers/clk/qcom/mdss/mdss-dsi-pll-8996.c | 566 ++ drivers/clk/qcom/mdss/mdss-dsi-pll-8996.h | 221 + drivers/clk/qcom/mdss/mdss-dsi-pll-util.c | 587 ++ drivers/clk/qcom/mdss/mdss-dsi-pll.h | 57 + drivers/clk/qcom/mdss/mdss-edp-pll-28hpm.c | 593 ++ drivers/clk/qcom/mdss/mdss-hdmi-pll-20nm.c | 983 +++ drivers/clk/qcom/mdss/mdss-hdmi-pll-28hpm.c | 1104 ++++ drivers/clk/qcom/mdss/mdss-hdmi-pll-8996.c | 2685 ++++++++ drivers/clk/qcom/mdss/mdss-hdmi-pll-8998.c | 841 +++ drivers/clk/qcom/mdss/mdss-hdmi-pll.h | 61 + drivers/clk/qcom/mdss/mdss-pll-util.c | 437 ++ drivers/clk/qcom/mdss/mdss-pll.c | 421 ++ drivers/clk/qcom/mdss/mdss-pll.h | 240 + drivers/gpu/drm/Kconfig | 3 +- drivers/gpu/drm/drm_dp_helper.c | 13 +- drivers/gpu/drm/drm_edid.c | 79 +- drivers/gpu/drm/drm_framebuffer.c | 3 +- drivers/gpu/drm/drm_mipi_dsi.c | 12 +- drivers/gpu/drm/msm/Kconfig | 94 +- drivers/gpu/drm/msm/Makefile | 202 +- drivers/gpu/drm/msm/dp/dp_audio.c | 796 +++ drivers/gpu/drm/msm/dp/dp_audio.h | 81 + drivers/gpu/drm/msm/dp/dp_aux.c | 570 ++ drivers/gpu/drm/msm/dp/dp_aux.h | 44 + drivers/gpu/drm/msm/dp/dp_catalog.c | 1320 ++++ drivers/gpu/drm/msm/dp/dp_catalog.h | 163 + drivers/gpu/drm/msm/dp/dp_ctrl.c | 1474 +++++ drivers/gpu/drm/msm/dp/dp_ctrl.h | 50 + drivers/gpu/drm/msm/dp/dp_debug.c | 503 ++ drivers/gpu/drm/msm/dp/dp_debug.h | 60 + drivers/gpu/drm/msm/dp/dp_display.c | 1263 ++++ drivers/gpu/drm/msm/dp/dp_display.h | 52 + drivers/gpu/drm/msm/dp/dp_drm.c | 538 ++ drivers/gpu/drm/msm/dp/dp_drm.h | 96 + drivers/gpu/drm/msm/dp/dp_hdcp2p2.c | 927 +++ drivers/gpu/drm/msm/dp/dp_link.c | 1548 +++++ drivers/gpu/drm/msm/dp/dp_link.h | 184 + drivers/gpu/drm/msm/dp/dp_panel.c | 531 ++ drivers/gpu/drm/msm/dp/dp_panel.h | 116 + drivers/gpu/drm/msm/dp/dp_parser.c | 645 ++ drivers/gpu/drm/msm/dp/dp_parser.h | 200 + drivers/gpu/drm/msm/dp/dp_power.c | 623 ++ drivers/gpu/drm/msm/dp/dp_power.h | 58 + drivers/gpu/drm/msm/dp/dp_reg.h | 231 + drivers/gpu/drm/msm/dp/dp_usbpd.c | 491 ++ drivers/gpu/drm/msm/dp/dp_usbpd.h | 101 + drivers/gpu/drm/msm/dsi-staging/dsi_catalog.c | 241 + drivers/gpu/drm/msm/dsi-staging/dsi_catalog.h | 201 + drivers/gpu/drm/msm/dsi-staging/dsi_clk.h | 276 + .../gpu/drm/msm/dsi-staging/dsi_clk_manager.c | 1226 ++++ drivers/gpu/drm/msm/dsi-staging/dsi_ctrl.c | 2859 +++++++++ drivers/gpu/drm/msm/dsi-staging/dsi_ctrl.h | 635 ++ drivers/gpu/drm/msm/dsi-staging/dsi_ctrl_hw.h | 752 +++ .../gpu/drm/msm/dsi-staging/dsi_ctrl_hw_1_4.c | 480 ++ .../gpu/drm/msm/dsi-staging/dsi_ctrl_hw_2_0.c | 234 + .../gpu/drm/msm/dsi-staging/dsi_ctrl_hw_2_2.c | 42 + .../gpu/drm/msm/dsi-staging/dsi_ctrl_hw_cmn.c | 1312 ++++ .../gpu/drm/msm/dsi-staging/dsi_ctrl_reg.h | 196 + drivers/gpu/drm/msm/dsi-staging/dsi_defs.h | 581 ++ drivers/gpu/drm/msm/dsi-staging/dsi_display.c | 4325 +++++++++++++ drivers/gpu/drm/msm/dsi-staging/dsi_display.h | 538 ++ .../drm/msm/dsi-staging/dsi_display_test.c | 114 + .../drm/msm/dsi-staging/dsi_display_test.h | 31 + drivers/gpu/drm/msm/dsi-staging/dsi_drm.c | 708 +++ drivers/gpu/drm/msm/dsi-staging/dsi_drm.h | 129 + drivers/gpu/drm/msm/dsi-staging/dsi_hw.h | 48 + drivers/gpu/drm/msm/dsi-staging/dsi_panel.c | 3521 +++++++++++ drivers/gpu/drm/msm/dsi-staging/dsi_panel.h | 262 + drivers/gpu/drm/msm/dsi-staging/dsi_phy.c | 935 +++ drivers/gpu/drm/msm/dsi-staging/dsi_phy.h | 235 + drivers/gpu/drm/msm/dsi-staging/dsi_phy_hw.h | 260 + .../gpu/drm/msm/dsi-staging/dsi_phy_hw_v2_0.c | 252 + .../gpu/drm/msm/dsi-staging/dsi_phy_hw_v3_0.c | 447 ++ .../drm/msm/dsi-staging/dsi_phy_timing_calc.c | 676 ++ .../drm/msm/dsi-staging/dsi_phy_timing_calc.h | 144 + .../drm/msm/dsi-staging/dsi_phy_timing_v2_0.c | 126 + .../drm/msm/dsi-staging/dsi_phy_timing_v3_0.c | 107 + drivers/gpu/drm/msm/dsi-staging/dsi_pwr.c | 365 ++ drivers/gpu/drm/msm/dsi-staging/dsi_pwr.h | 93 + drivers/gpu/drm/msm/dsi/phy/dsi_phy_14nm.c | 2 +- drivers/gpu/drm/msm/hdmi/hdmi.c | 2 +- drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c | 26 +- drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.h | 11 + drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c | 4 +- drivers/gpu/drm/msm/mdp/mdp_format.c | 8 +- drivers/gpu/drm/msm/mdp/mdp_kms.h | 4 +- drivers/gpu/drm/msm/msm_atomic.c | 478 +- drivers/gpu/drm/msm/msm_drv.c | 1059 +++- drivers/gpu/drm/msm/msm_drv.h | 570 +- drivers/gpu/drm/msm/msm_fb.c | 146 +- drivers/gpu/drm/msm/msm_gem.c | 127 +- drivers/gpu/drm/msm/msm_gem.h | 44 + drivers/gpu/drm/msm/msm_gem_vma.c | 211 + drivers/gpu/drm/msm/msm_kms.h | 105 +- drivers/gpu/drm/msm/msm_mmu.h | 30 +- drivers/gpu/drm/msm/msm_prop.c | 694 +++ drivers/gpu/drm/msm/msm_prop.h | 434 ++ drivers/gpu/drm/msm/msm_rd.c | 78 +- drivers/gpu/drm/msm/msm_smmu.c | 576 ++ drivers/gpu/drm/msm/sde/sde_ad4.h | 99 + .../gpu/drm/msm/sde/sde_color_processing.c | 1520 +++++ .../gpu/drm/msm/sde/sde_color_processing.h | 120 + drivers/gpu/drm/msm/sde/sde_connector.c | 1406 +++++ drivers/gpu/drm/msm/sde/sde_connector.h | 583 ++ drivers/gpu/drm/msm/sde/sde_core_irq.c | 640 ++ drivers/gpu/drm/msm/sde/sde_core_irq.h | 192 + drivers/gpu/drm/msm/sde/sde_core_perf.c | 815 +++ drivers/gpu/drm/msm/sde/sde_core_perf.h | 144 + drivers/gpu/drm/msm/sde/sde_crtc.c | 5480 +++++++++++++++++ drivers/gpu/drm/msm/sde/sde_crtc.h | 661 ++ drivers/gpu/drm/msm/sde/sde_encoder.c | 3835 ++++++++++++ drivers/gpu/drm/msm/sde/sde_encoder.h | 220 + drivers/gpu/drm/msm/sde/sde_encoder_phys.h | 572 ++ .../gpu/drm/msm/sde/sde_encoder_phys_cmd.c | 1365 ++++ .../gpu/drm/msm/sde/sde_encoder_phys_vid.c | 1045 ++++ drivers/gpu/drm/msm/sde/sde_encoder_phys_wb.c | 1389 +++++ drivers/gpu/drm/msm/sde/sde_fence.c | 404 ++ drivers/gpu/drm/msm/sde/sde_fence.h | 190 + drivers/gpu/drm/msm/sde/sde_formats.c | 1360 ++++ drivers/gpu/drm/msm/sde/sde_formats.h | 158 + drivers/gpu/drm/msm/sde/sde_hw_ad4.c | 1443 +++++ drivers/gpu/drm/msm/sde/sde_hw_blk.c | 155 + drivers/gpu/drm/msm/sde/sde_hw_blk.h | 53 + drivers/gpu/drm/msm/sde/sde_hw_catalog.c | 3277 ++++++++++ drivers/gpu/drm/msm/sde/sde_hw_catalog.h | 1047 ++++ .../gpu/drm/msm/sde/sde_hw_catalog_format.h | 182 + drivers/gpu/drm/msm/sde/sde_hw_cdm.c | 328 + drivers/gpu/drm/msm/sde/sde_hw_cdm.h | 139 + .../drm/msm/sde/sde_hw_color_proc_common_v4.h | 69 + .../gpu/drm/msm/sde/sde_hw_color_proc_v4.c | 242 + .../gpu/drm/msm/sde/sde_hw_color_proc_v4.h | 40 + .../gpu/drm/msm/sde/sde_hw_color_processing.h | 20 + .../msm/sde/sde_hw_color_processing_v1_7.c | 565 ++ .../msm/sde/sde_hw_color_processing_v1_7.h | 92 + drivers/gpu/drm/msm/sde/sde_hw_ctl.c | 657 ++ drivers/gpu/drm/msm/sde/sde_hw_ctl.h | 275 + drivers/gpu/drm/msm/sde/sde_hw_ds.c | 149 + drivers/gpu/drm/msm/sde/sde_hw_ds.h | 111 + drivers/gpu/drm/msm/sde/sde_hw_dsc.c | 252 + drivers/gpu/drm/msm/sde/sde_hw_dsc.h | 100 + drivers/gpu/drm/msm/sde/sde_hw_dspp.c | 209 + drivers/gpu/drm/msm/sde/sde_hw_dspp.h | 220 + drivers/gpu/drm/msm/sde/sde_hw_interrupts.c | 1235 ++++ drivers/gpu/drm/msm/sde/sde_hw_interrupts.h | 289 + drivers/gpu/drm/msm/sde/sde_hw_intf.c | 373 ++ drivers/gpu/drm/msm/sde/sde_hw_intf.h | 132 + drivers/gpu/drm/msm/sde/sde_hw_lm.c | 331 + drivers/gpu/drm/msm/sde/sde_hw_lm.h | 136 + drivers/gpu/drm/msm/sde/sde_hw_mdss.h | 538 ++ drivers/gpu/drm/msm/sde/sde_hw_pingpong.c | 409 ++ drivers/gpu/drm/msm/sde/sde_hw_pingpong.h | 182 + drivers/gpu/drm/msm/sde/sde_hw_reg_dma_v1.c | 817 +++ drivers/gpu/drm/msm/sde/sde_hw_reg_dma_v1.h | 27 + .../msm/sde/sde_hw_reg_dma_v1_color_proc.c | 943 +++ .../msm/sde/sde_hw_reg_dma_v1_color_proc.h | 75 + drivers/gpu/drm/msm/sde/sde_hw_rot.c | 962 +++ drivers/gpu/drm/msm/sde/sde_hw_rot.h | 192 + drivers/gpu/drm/msm/sde/sde_hw_sspp.c | 1058 ++++ drivers/gpu/drm/msm/sde/sde_hw_sspp.h | 574 ++ drivers/gpu/drm/msm/sde/sde_hw_top.c | 461 ++ drivers/gpu/drm/msm/sde/sde_hw_top.h | 227 + drivers/gpu/drm/msm/sde/sde_hw_util.c | 453 ++ drivers/gpu/drm/msm/sde/sde_hw_util.h | 202 + drivers/gpu/drm/msm/sde/sde_hw_vbif.c | 275 + drivers/gpu/drm/msm/sde/sde_hw_vbif.h | 128 + drivers/gpu/drm/msm/sde/sde_hw_wb.c | 324 + drivers/gpu/drm/msm/sde/sde_hw_wb.h | 187 + drivers/gpu/drm/msm/sde/sde_hwio.h | 60 + drivers/gpu/drm/msm/sde/sde_irq.c | 112 + drivers/gpu/drm/msm/sde/sde_irq.h | 59 + drivers/gpu/drm/msm/sde/sde_kms.c | 2298 +++++++ drivers/gpu/drm/msm/sde/sde_kms.h | 557 ++ drivers/gpu/drm/msm/sde/sde_kms_utils.c | 220 + drivers/gpu/drm/msm/sde/sde_plane.c | 4800 +++++++++++++++ drivers/gpu/drm/msm/sde/sde_plane.h | 296 + drivers/gpu/drm/msm/sde/sde_reg_dma.c | 139 + drivers/gpu/drm/msm/sde/sde_reg_dma.h | 310 + drivers/gpu/drm/msm/sde/sde_rm.c | 1388 +++++ drivers/gpu/drm/msm/sde/sde_rm.h | 200 + drivers/gpu/drm/msm/sde/sde_trace.h | 245 + drivers/gpu/drm/msm/sde/sde_vbif.c | 385 ++ drivers/gpu/drm/msm/sde/sde_vbif.h | 94 + drivers/gpu/drm/msm/sde/sde_wb.c | 782 +++ drivers/gpu/drm/msm/sde/sde_wb.h | 334 + drivers/gpu/drm/msm/sde_dbg.c | 3325 ++++++++++ drivers/gpu/drm/msm/sde_dbg.h | 421 ++ drivers/gpu/drm/msm/sde_dbg_evtlog.c | 306 + drivers/gpu/drm/msm/sde_edid_parser.c | 656 ++ drivers/gpu/drm/msm/sde_edid_parser.h | 166 + drivers/gpu/drm/msm/sde_hdcp.h | 75 + drivers/gpu/drm/msm/sde_hdcp_1x.c | 1579 +++++ drivers/gpu/drm/msm/sde_io_util.c | 506 ++ drivers/gpu/drm/msm/sde_power_handle.c | 1127 ++++ drivers/gpu/drm/msm/sde_power_handle.h | 334 + drivers/gpu/drm/msm/sde_rsc.c | 1367 ++++ drivers/gpu/drm/msm/sde_rsc_hw.c | 818 +++ drivers/gpu/drm/msm/sde_rsc_priv.h | 191 + drivers/media/platform/msm/Kconfig | 1 + drivers/media/platform/msm/Makefile | 5 + drivers/media/platform/msm/sde/Kconfig | 21 + drivers/media/platform/msm/sde/Makefile | 1 + .../media/platform/msm/sde/rotator/Makefile | 27 + .../msm/sde/rotator/sde_rotator_base.c | 846 +++ .../msm/sde/rotator/sde_rotator_base.h | 295 + .../msm/sde/rotator/sde_rotator_core.c | 3442 +++++++++++ .../msm/sde/rotator/sde_rotator_core.h | 789 +++ .../msm/sde/rotator/sde_rotator_debug.c | 1517 +++++ .../msm/sde/rotator/sde_rotator_debug.h | 80 + .../msm/sde/rotator/sde_rotator_dev.c | 3495 +++++++++++ .../msm/sde/rotator/sde_rotator_dev.h | 249 + .../msm/sde/rotator/sde_rotator_formats.c | 926 +++ .../msm/sde/rotator/sde_rotator_formats.h | 204 + .../msm/sde/rotator/sde_rotator_hwio.h | 77 + .../msm/sde/rotator/sde_rotator_inline.h | 117 + .../msm/sde/rotator/sde_rotator_io_util.c | 430 ++ .../msm/sde/rotator/sde_rotator_io_util.h | 105 + .../platform/msm/sde/rotator/sde_rotator_r1.c | 746 +++ .../platform/msm/sde/rotator/sde_rotator_r1.h | 22 + .../msm/sde/rotator/sde_rotator_r1_ctl.c | 267 + .../msm/sde/rotator/sde_rotator_r1_debug.c | 45 + .../msm/sde/rotator/sde_rotator_r1_debug.h | 32 + .../msm/sde/rotator/sde_rotator_r1_hwio.h | 149 + .../msm/sde/rotator/sde_rotator_r1_internal.h | 172 + .../msm/sde/rotator/sde_rotator_r1_pipe.c | 430 ++ .../msm/sde/rotator/sde_rotator_r1_wb.c | 531 ++ .../platform/msm/sde/rotator/sde_rotator_r3.c | 3478 +++++++++++ .../platform/msm/sde/rotator/sde_rotator_r3.h | 20 + .../msm/sde/rotator/sde_rotator_r3_debug.c | 84 + .../msm/sde/rotator/sde_rotator_r3_debug.h | 32 + .../msm/sde/rotator/sde_rotator_r3_hwio.h | 304 + .../msm/sde/rotator/sde_rotator_r3_internal.h | 434 ++ .../msm/sde/rotator/sde_rotator_smmu.c | 716 +++ .../msm/sde/rotator/sde_rotator_smmu.h | 50 + .../msm/sde/rotator/sde_rotator_sync.c | 441 ++ .../msm/sde/rotator/sde_rotator_sync.h | 115 + .../msm/sde/rotator/sde_rotator_trace.h | 337 + .../msm/sde/rotator/sde_rotator_util.c | 1105 ++++ .../msm/sde/rotator/sde_rotator_util.h | 207 + drivers/media/v4l2-core/v4l2-ioctl.c | 80 + drivers/misc/Kconfig | 9 + drivers/misc/Makefile | 1 + drivers/misc/hdcp.c | 2549 ++++++++ drivers/platform/msm/Kconfig | 9 + drivers/platform/msm/Makefile | 1 + drivers/platform/msm/msm_ext_display.c | 525 ++ include/drm/drm_connector.h | 19 + include/drm/drm_dp_helper.h | 24 + include/drm/drm_mipi_dsi.h | 4 + include/drm/drm_mode_object.h | 2 +- include/dt-bindings/clock/mdss-10nm-pll-clk.h | 47 + include/linux/hdcp_qseecom.h | 224 + include/linux/msm_ext_display.h | 182 + include/linux/sde_io_util.h | 113 + include/linux/sde_rsc.h | 301 + include/media/msm_sde_rotator.h | 17 + include/uapi/drm/drm_fourcc.h | 32 + include/uapi/drm/drm_mode.h | 5 + include/uapi/drm/msm_drm.h | 79 + include/uapi/drm/msm_drm_pp.h | 345 ++ include/uapi/drm/sde_drm.h | 449 ++ include/uapi/linux/mdss_rotator.h | 144 + include/uapi/linux/msm_mdp.h | 1461 +++++ include/uapi/linux/msm_mdp_ext.h | 688 +++ include/uapi/linux/msm_rotator.h | 60 + include/uapi/linux/videodev2.h | 81 + include/uapi/media/msm_media_info.h | 1376 +++++ include/uapi/media/msm_sde_rotator.h | 115 + include/uapi/video/msm_hdmi_hdcp_mgr.h | 54 + include/uapi/video/msm_hdmi_modes.h | 559 ++ include/video/mipi_display.h | 4 + 296 files changed, 155127 insertions(+), 256 deletions(-) create mode 100644 Documentation/devicetree/bindings/display/msm/sde-rsc.txt create mode 100644 Documentation/devicetree/bindings/display/msm/sde.txt create mode 100644 Documentation/devicetree/bindings/drm/msm/mdss-dsi-panel.txt create mode 100644 Documentation/devicetree/bindings/drm/msm/sde-dp.txt create mode 100644 Documentation/devicetree/bindings/drm/msm/sde-dsi.txt create mode 100644 Documentation/devicetree/bindings/drm/msm/sde-wb.txt create mode 100644 Documentation/devicetree/bindings/fb/mdss-dsi-panel.txt create mode 100644 Documentation/devicetree/bindings/fb/mdss-pll.txt create mode 100644 Documentation/devicetree/bindings/media/video/msm-sde-rotator.txt create mode 100644 Documentation/devicetree/bindings/msm_hdcp/msm_hdcp.txt create mode 100644 drivers/clk/qcom/mdss/Kconfig create mode 100644 drivers/clk/qcom/mdss/Makefile create mode 100644 drivers/clk/qcom/mdss/mdss-dp-pll-10nm-util.c create mode 100644 drivers/clk/qcom/mdss/mdss-dp-pll-10nm.c create mode 100644 drivers/clk/qcom/mdss/mdss-dp-pll-10nm.h create mode 100644 drivers/clk/qcom/mdss/mdss-dp-pll.h create mode 100644 drivers/clk/qcom/mdss/mdss-dsi-20nm-pll-util.c create mode 100644 drivers/clk/qcom/mdss/mdss-dsi-pll-10nm.c create mode 100644 drivers/clk/qcom/mdss/mdss-dsi-pll-20nm.c create mode 100644 drivers/clk/qcom/mdss/mdss-dsi-pll-28hpm.c create mode 100644 drivers/clk/qcom/mdss/mdss-dsi-pll-28lpm.c create mode 100644 drivers/clk/qcom/mdss/mdss-dsi-pll-8996-util.c create mode 100644 drivers/clk/qcom/mdss/mdss-dsi-pll-8996.c create mode 100644 drivers/clk/qcom/mdss/mdss-dsi-pll-8996.h create mode 100644 drivers/clk/qcom/mdss/mdss-dsi-pll-util.c create mode 100644 drivers/clk/qcom/mdss/mdss-dsi-pll.h create mode 100644 drivers/clk/qcom/mdss/mdss-edp-pll-28hpm.c create mode 100644 drivers/clk/qcom/mdss/mdss-hdmi-pll-20nm.c create mode 100644 drivers/clk/qcom/mdss/mdss-hdmi-pll-28hpm.c create mode 100644 drivers/clk/qcom/mdss/mdss-hdmi-pll-8996.c create mode 100644 drivers/clk/qcom/mdss/mdss-hdmi-pll-8998.c create mode 100644 drivers/clk/qcom/mdss/mdss-hdmi-pll.h create mode 100644 drivers/clk/qcom/mdss/mdss-pll-util.c create mode 100644 drivers/clk/qcom/mdss/mdss-pll.c create mode 100644 drivers/clk/qcom/mdss/mdss-pll.h create mode 100644 drivers/gpu/drm/msm/dp/dp_audio.c create mode 100644 drivers/gpu/drm/msm/dp/dp_audio.h create mode 100644 drivers/gpu/drm/msm/dp/dp_aux.c create mode 100644 drivers/gpu/drm/msm/dp/dp_aux.h create mode 100644 drivers/gpu/drm/msm/dp/dp_catalog.c create mode 100644 drivers/gpu/drm/msm/dp/dp_catalog.h create mode 100644 drivers/gpu/drm/msm/dp/dp_ctrl.c create mode 100644 drivers/gpu/drm/msm/dp/dp_ctrl.h create mode 100644 drivers/gpu/drm/msm/dp/dp_debug.c create mode 100644 drivers/gpu/drm/msm/dp/dp_debug.h create mode 100644 drivers/gpu/drm/msm/dp/dp_display.c create mode 100644 drivers/gpu/drm/msm/dp/dp_display.h create mode 100644 drivers/gpu/drm/msm/dp/dp_drm.c create mode 100644 drivers/gpu/drm/msm/dp/dp_drm.h create mode 100644 drivers/gpu/drm/msm/dp/dp_hdcp2p2.c create mode 100644 drivers/gpu/drm/msm/dp/dp_link.c create mode 100644 drivers/gpu/drm/msm/dp/dp_link.h create mode 100644 drivers/gpu/drm/msm/dp/dp_panel.c create mode 100644 drivers/gpu/drm/msm/dp/dp_panel.h create mode 100644 drivers/gpu/drm/msm/dp/dp_parser.c create mode 100644 drivers/gpu/drm/msm/dp/dp_parser.h create mode 100644 drivers/gpu/drm/msm/dp/dp_power.c create mode 100644 drivers/gpu/drm/msm/dp/dp_power.h create mode 100644 drivers/gpu/drm/msm/dp/dp_reg.h create mode 100644 drivers/gpu/drm/msm/dp/dp_usbpd.c create mode 100644 drivers/gpu/drm/msm/dp/dp_usbpd.h create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_catalog.c create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_catalog.h create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_clk.h create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_clk_manager.c create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_ctrl.c create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_ctrl.h create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_ctrl_hw.h create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_ctrl_hw_1_4.c create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_ctrl_hw_2_0.c create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_ctrl_hw_2_2.c create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_ctrl_hw_cmn.c create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_ctrl_reg.h create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_defs.h create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_display.c create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_display.h create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_display_test.c create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_display_test.h create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_drm.c create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_drm.h create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_hw.h create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_panel.c create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_panel.h create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_phy.c create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_phy.h create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_phy_hw.h create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_phy_hw_v2_0.c create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_phy_hw_v3_0.c create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_phy_timing_calc.c create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_phy_timing_calc.h create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_phy_timing_v2_0.c create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_phy_timing_v3_0.c create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_pwr.c create mode 100644 drivers/gpu/drm/msm/dsi-staging/dsi_pwr.h create mode 100644 drivers/gpu/drm/msm/msm_prop.c create mode 100644 drivers/gpu/drm/msm/msm_prop.h create mode 100644 drivers/gpu/drm/msm/msm_smmu.c create mode 100644 drivers/gpu/drm/msm/sde/sde_ad4.h create mode 100644 drivers/gpu/drm/msm/sde/sde_color_processing.c create mode 100644 drivers/gpu/drm/msm/sde/sde_color_processing.h create mode 100644 drivers/gpu/drm/msm/sde/sde_connector.c create mode 100644 drivers/gpu/drm/msm/sde/sde_connector.h create mode 100644 drivers/gpu/drm/msm/sde/sde_core_irq.c create mode 100644 drivers/gpu/drm/msm/sde/sde_core_irq.h create mode 100644 drivers/gpu/drm/msm/sde/sde_core_perf.c create mode 100644 drivers/gpu/drm/msm/sde/sde_core_perf.h create mode 100644 drivers/gpu/drm/msm/sde/sde_crtc.c create mode 100644 drivers/gpu/drm/msm/sde/sde_crtc.h create mode 100644 drivers/gpu/drm/msm/sde/sde_encoder.c create mode 100644 drivers/gpu/drm/msm/sde/sde_encoder.h create mode 100644 drivers/gpu/drm/msm/sde/sde_encoder_phys.h create mode 100644 drivers/gpu/drm/msm/sde/sde_encoder_phys_cmd.c create mode 100644 drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c create mode 100644 drivers/gpu/drm/msm/sde/sde_encoder_phys_wb.c create mode 100644 drivers/gpu/drm/msm/sde/sde_fence.c create mode 100644 drivers/gpu/drm/msm/sde/sde_fence.h create mode 100644 drivers/gpu/drm/msm/sde/sde_formats.c create mode 100644 drivers/gpu/drm/msm/sde/sde_formats.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_ad4.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_blk.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_blk.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_catalog.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_catalog.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_catalog_format.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_cdm.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_cdm.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_color_proc_common_v4.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_color_proc_v4.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_color_proc_v4.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_color_processing.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_color_processing_v1_7.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_color_processing_v1_7.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_ctl.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_ctl.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_ds.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_ds.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_dsc.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_dsc.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_dspp.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_dspp.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_interrupts.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_interrupts.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_intf.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_intf.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_lm.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_lm.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_mdss.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_pingpong.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_pingpong.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_reg_dma_v1.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_reg_dma_v1.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_reg_dma_v1_color_proc.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_reg_dma_v1_color_proc.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_rot.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_rot.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_sspp.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_sspp.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_top.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_top.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_util.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_util.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_vbif.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_vbif.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_wb.c create mode 100644 drivers/gpu/drm/msm/sde/sde_hw_wb.h create mode 100644 drivers/gpu/drm/msm/sde/sde_hwio.h create mode 100644 drivers/gpu/drm/msm/sde/sde_irq.c create mode 100644 drivers/gpu/drm/msm/sde/sde_irq.h create mode 100644 drivers/gpu/drm/msm/sde/sde_kms.c create mode 100644 drivers/gpu/drm/msm/sde/sde_kms.h create mode 100644 drivers/gpu/drm/msm/sde/sde_kms_utils.c create mode 100644 drivers/gpu/drm/msm/sde/sde_plane.c create mode 100644 drivers/gpu/drm/msm/sde/sde_plane.h create mode 100644 drivers/gpu/drm/msm/sde/sde_reg_dma.c create mode 100644 drivers/gpu/drm/msm/sde/sde_reg_dma.h create mode 100644 drivers/gpu/drm/msm/sde/sde_rm.c create mode 100644 drivers/gpu/drm/msm/sde/sde_rm.h create mode 100644 drivers/gpu/drm/msm/sde/sde_trace.h create mode 100644 drivers/gpu/drm/msm/sde/sde_vbif.c create mode 100644 drivers/gpu/drm/msm/sde/sde_vbif.h create mode 100644 drivers/gpu/drm/msm/sde/sde_wb.c create mode 100644 drivers/gpu/drm/msm/sde/sde_wb.h create mode 100644 drivers/gpu/drm/msm/sde_dbg.c create mode 100644 drivers/gpu/drm/msm/sde_dbg.h create mode 100644 drivers/gpu/drm/msm/sde_dbg_evtlog.c create mode 100644 drivers/gpu/drm/msm/sde_edid_parser.c create mode 100644 drivers/gpu/drm/msm/sde_edid_parser.h create mode 100644 drivers/gpu/drm/msm/sde_hdcp.h create mode 100644 drivers/gpu/drm/msm/sde_hdcp_1x.c create mode 100644 drivers/gpu/drm/msm/sde_io_util.c create mode 100644 drivers/gpu/drm/msm/sde_power_handle.c create mode 100644 drivers/gpu/drm/msm/sde_power_handle.h create mode 100644 drivers/gpu/drm/msm/sde_rsc.c create mode 100644 drivers/gpu/drm/msm/sde_rsc_hw.c create mode 100644 drivers/gpu/drm/msm/sde_rsc_priv.h create mode 100644 drivers/media/platform/msm/Kconfig create mode 100644 drivers/media/platform/msm/Makefile create mode 100644 drivers/media/platform/msm/sde/Kconfig create mode 100644 drivers/media/platform/msm/sde/Makefile create mode 100644 drivers/media/platform/msm/sde/rotator/Makefile create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_base.c create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_base.h create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_core.c create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_core.h create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_debug.c create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_debug.h create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_dev.c create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_dev.h create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_formats.c create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_formats.h create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_hwio.h create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_inline.h create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_io_util.c create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_io_util.h create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_r1.c create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_r1.h create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_r1_ctl.c create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_r1_debug.c create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_r1_debug.h create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_r1_hwio.h create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_r1_internal.h create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_r1_pipe.c create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_r1_wb.c create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_r3.c create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_r3.h create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_r3_debug.c create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_r3_debug.h create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_r3_hwio.h create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_r3_internal.h create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_smmu.c create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_smmu.h create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_sync.c create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_sync.h create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_trace.h create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_util.c create mode 100644 drivers/media/platform/msm/sde/rotator/sde_rotator_util.h create mode 100644 drivers/misc/hdcp.c create mode 100644 drivers/platform/msm/msm_ext_display.c create mode 100644 include/dt-bindings/clock/mdss-10nm-pll-clk.h create mode 100644 include/linux/hdcp_qseecom.h create mode 100644 include/linux/msm_ext_display.h create mode 100644 include/linux/sde_io_util.h create mode 100644 include/linux/sde_rsc.h create mode 100644 include/media/msm_sde_rotator.h create mode 100644 include/uapi/drm/msm_drm_pp.h create mode 100644 include/uapi/drm/sde_drm.h create mode 100644 include/uapi/linux/mdss_rotator.h create mode 100644 include/uapi/linux/msm_mdp.h create mode 100644 include/uapi/linux/msm_mdp_ext.h create mode 100644 include/uapi/linux/msm_rotator.h create mode 100644 include/uapi/media/msm_media_info.h create mode 100644 include/uapi/media/msm_sde_rotator.h create mode 100644 include/uapi/video/msm_hdmi_hdcp_mgr.h create mode 100644 include/uapi/video/msm_hdmi_modes.h diff --git a/Documentation/devicetree/bindings/display/msm/dsi.txt b/Documentation/devicetree/bindings/display/msm/dsi.txt index fa00e62e1cf6..8334828a58e3 100644 --- a/Documentation/devicetree/bindings/display/msm/dsi.txt +++ b/Documentation/devicetree/bindings/display/msm/dsi.txt @@ -107,6 +107,20 @@ Required properties: Optional properties: - qcom,dsi-phy-regulator-ldo-mode: Boolean value indicating if the LDO mode PHY regulator is wanted. +- qcom,mdss-mdp-transfer-time-us: Specifies the dsi transfer time for command mode + panels in microseconds. Driver uses this number to adjust + the clock rate according to the expected transfer time. + Increasing this value would slow down the mdp processing + and can result in slower performance. + Decreasing this value can speed up the mdp processing, + but this can also impact power consumption. + As a rule this time should not be higher than the time + that would be expected with the processing at the + dsi link rate since anyways this would be the maximum + transfer time that could be achieved. + If ping pong split is enabled, this time should not be higher + than two times the dsi link rate time. + If the property is not specified, then the default value is 14000 us. [1] Documentation/devicetree/bindings/clock/clock-bindings.txt [2] Documentation/devicetree/bindings/graph.txt @@ -157,6 +171,8 @@ Example: qcom,master-dsi; qcom,sync-dual-dsi; + qcom,mdss-mdp-transfer-time-us = <12000>; + pinctrl-names = "default", "sleep"; pinctrl-0 = <&dsi_active>; pinctrl-1 = <&dsi_suspend>; diff --git a/Documentation/devicetree/bindings/display/msm/sde-rsc.txt b/Documentation/devicetree/bindings/display/msm/sde-rsc.txt new file mode 100644 index 000000000000..55d18cf6fe32 --- /dev/null +++ b/Documentation/devicetree/bindings/display/msm/sde-rsc.txt @@ -0,0 +1,96 @@ +Qualcomm Technologies, Inc. SDE RSC + +Snapdragon Display Engine implements display rsc to driver +display core to different modes for power saving + +Required properties +- compatible: Must be "qcom,sde-rsc" +- reg: Offset and length of the register set for + the device. +- reg-names: Names to refer to register sets related + to this device + +Optional properties: +- clocks: List of phandles for clock device nodes + needed by the device. +- clock-names: List of clock names needed by the device. +- vdd-supply: phandle for vdd regulator device node. +- qcom,sde-rsc-version: U32 property represents the rsc version. It helps to + select correct sequence for sde rsc based on version. +- qcom,sde-dram-channels: U32 property represents the number of channels in the + Bus memory controller. +- qcom,sde-num-nrt-paths: U32 property represents the number of non-realtime + paths in each Bus Scaling Usecase. This value depends on + number of AXI ports that are dedicated to non-realtime VBIF + for particular chipset. + These paths must be defined after rt-paths in + "qcom,msm-bus,vectors-KBps" vector request. + +Bus Scaling Subnodes: +- qcom,sde-data-bus: Property to provide Bus scaling for data bus access for + sde blocks. +- qcom,sde-llcc-bus: Property to provide Bus scaling for data bus access for + mnoc to llcc. +- qcom,sde-ebi-bus: Property to provide Bus scaling for data bus access for + llcc to ebi. + +Bus Scaling Data: +- qcom,msm-bus,name: String property describing client name. +- qcom,msm-bus,active-only: Boolean context flag for requests in active or + dual (active & sleep) contex +- qcom,msm-bus,num-cases: This is the number of Bus Scaling use cases + defined in the vectors property. +- qcom,msm-bus,num-paths: This represents the number of paths in each + Bus Scaling Usecase. +- qcom,msm-bus,vectors-KBps: * A series of 4 cell properties, with a format + of (src, dst, ab, ib) which is defined at + Documentation/devicetree/bindings/arm/msm/msm_bus.txt + * Current values of src & dst are defined at + include/linux/msm-bus-board.h +Example: + sde_rscc { + cell-index = <0>; + compatible = "qcom,sde-rsc"; + reg = <0xaf20000 0x1c44>, + <0xaf30000 0x3fd4>; + reg-names = "drv", "wrapper"; + clocks = <&clock_mmss clk_mdss_ahb_clk>, + <&clock_mmss clk_mdss_axi_clk>; + clock-names = "iface_clk", "bus_clk"; + vdd-supply = <&gdsc_mdss>; + + qcom,sde-rsc-version = <1>; + qcom,sde-dram-channels = <2>; + qcom,sde-num-nrt-paths = <1>; + + qcom,sde-data-bus { + qcom,msm-bus,name = "sde_rsc"; + qcom,msm-bus,active-only; + qcom,msm-bus,num-cases = <3>; + qcom,msm-bus,num-paths = <2>; + qcom,msm-bus,vectors-KBps = + <22 512 0 0>, <23 512 0 0>, + <22 512 0 6400000>, <23 512 0 6400000>, + <22 512 0 6400000>, <23 512 0 6400000>; + }; + qcom,sde-llcc-bus { + qcom,msm-bus,name = "sde_rsc_llcc"; + qcom,msm-bus,active-only; + qcom,msm-bus,num-cases = <3>; + qcom,msm-bus,num-paths = <1>; + qcom,msm-bus,vectors-KBps = + <20001 20513 0 0>, + <20001 20513 0 6400000>, + <20001 20513 0 6400000>; + }; + qcom,sde-ebi-bus { + qcom,msm-bus,name = "sde_rsc_ebi"; + qcom,msm-bus,active-only; + qcom,msm-bus,num-cases = <3>; + qcom,msm-bus,num-paths = <1>; + qcom,msm-bus,vectors-KBps = + <20000 20512 0 0>, + <20000 20512 0 6400000>, + <20000 20512 0 6400000>; + }; + }; diff --git a/Documentation/devicetree/bindings/display/msm/sde.txt b/Documentation/devicetree/bindings/display/msm/sde.txt new file mode 100644 index 000000000000..b570448ebadc --- /dev/null +++ b/Documentation/devicetree/bindings/display/msm/sde.txt @@ -0,0 +1,736 @@ +Qualcomm Technologies, Inc. SDE KMS + +Snapdragon Display Engine implements Linux DRM/KMS APIs to drive user +interface to different panel interfaces. SDE driver is the core of +display subsystem which manage all data paths to different panel interfaces. + +Required properties +- compatible: Must be "qcom,sde-kms" +- compatible: "msm-hdmi-audio-codec-rx"; +- reg: Offset and length of the register set for the device. +- reg-names : Names to refer to register sets related to this device +- clocks: List of Phandles for clock device nodes + needed by the device. +- clock-names: List of clock names needed by the device. +- mmagic-supply: Phandle for mmagic mdss supply regulator device node. +- vdd-supply: Phandle for vdd regulator device node. +- interrupt-parent: Must be core interrupt controller. +- interrupts: Interrupt associated with MDSS. +- interrupt-controller: Mark the device node as an interrupt controller. +- #interrupt-cells: Should be one. The first cell is interrupt number. +- iommus: Specifies the SID's used by this context bank. +- qcom,sde-sspp-type: Array of strings for SDE source surface pipes type information. + A source pipe can be "vig", "rgb", "dma" or "cursor" type. + Number of xin ids defined should match the number of offsets + defined in property: qcom,sde-sspp-off. +- qcom,sde-sspp-off: Array of offset for SDE source surface pipes. The offsets + are calculated from register "mdp_phys" defined in + reg property + "sde-off". The number of offsets defined here should + reflect the amount of pipes that can be active in SDE for + this configuration. +- qcom,sde-sspp-xin-id: Array of VBIF clients ids (xins) corresponding + to the respective source pipes. Number of xin ids + defined should match the number of offsets + defined in property: qcom,sde-sspp-off. +- qcom,sde-ctl-off: Array of offset addresses for the available ctl + hw blocks within SDE, these offsets are + calculated from register "mdp_phys" defined in + reg property. The number of ctl offsets defined + here should reflect the number of control paths + that can be configured concurrently on SDE for + this configuration. +- qcom,sde-wb-off: Array of offset addresses for the programmable + writeback blocks within SDE. +- qcom,sde-wb-xin-id: Array of VBIF clients ids (xins) corresponding + to the respective writeback. Number of xin ids + defined should match the number of offsets + defined in property: qcom,sde-wb-off. +- qcom,sde-mixer-off: Array of offset addresses for the available + mixer blocks that can drive data to panel + interfaces. These offsets are be calculated from + register "mdp_phys" defined in reg property. + The number of offsets defined should reflect the + amount of mixers that can drive data to a panel + interface. +- qcom,sde-dspp-top-off: Offset address for the dspp top block. + The offset is calculated from register "mdp_phys" + defined in reg property. +- qcom,sde-dspp-off: Array of offset addresses for the available dspp + blocks. These offsets are calculated from + register "mdp_phys" defined in reg property. +- qcom,sde-pp-off: Array of offset addresses for the available + pingpong blocks. These offsets are calculated + from register "mdp_phys" defined in reg property. +- qcom,sde-pp-slave: Array of flags indicating whether each ping pong + block may be configured as a pp slave. +- qcom,sde-intf-off: Array of offset addresses for the available SDE + interface blocks that can drive data to a + panel controller. The offsets are calculated + from "mdp_phys" defined in reg property. The number + of offsets defined should reflect the number of + programmable interface blocks available in hardware. +- qcom,sde-mixer-blend-op-off Array of offset addresses for the available + blending stages. The offsets are relative to + qcom,sde-mixer-off. +- qcom,sde-mixer-pair-mask Array of mixer numbers that can be paired with + mixer number corresponding to the array index. + +Optional properties: +- clock-rate: List of clock rates in Hz. +- clock-max-rate: List of maximum clock rate in Hz that this device supports. +- qcom,platform-supply-entries: A node that lists the elements of the supply. There + can be more than one instance of this binding, + in which case the entry would be appended with + the supply entry index. + e.g. qcom,platform-supply-entry@0 + -- reg: offset and length of the register set for the device. + -- qcom,supply-name: name of the supply (vdd/vdda/vddio) + -- qcom,supply-min-voltage: minimum voltage level (uV) + -- qcom,supply-max-voltage: maximum voltage level (uV) + -- qcom,supply-enable-load: load drawn (uA) from enabled supply + -- qcom,supply-disable-load: load drawn (uA) from disabled supply + -- qcom,supply-pre-on-sleep: time to sleep (ms) before turning on + -- qcom,supply-post-on-sleep: time to sleep (ms) after turning on + -- qcom,supply-pre-off-sleep: time to sleep (ms) before turning off + -- qcom,supply-post-off-sleep: time to sleep (ms) after turning off +- qcom,sde-sspp-src-size: A u32 value indicates the address range for each sspp. +- qcom,sde-mixer-size: A u32 value indicates the address range for each mixer. +- qcom,sde-ctl-size: A u32 value indicates the address range for each ctl. +- qcom,sde-dspp-size: A u32 value indicates the address range for each dspp. +- qcom,sde-intf-size: A u32 value indicates the address range for each intf. +- qcom,sde-dsc-size: A u32 value indicates the address range for each dsc. +- qcom,sde-cdm-size: A u32 value indicates the address range for each cdm. +- qcom,sde-pp-size: A u32 value indicates the address range for each pingpong. +- qcom,sde-wb-size: A u32 value indicates the address range for each writeback. +- qcom,sde-len: A u32 entry for SDE address range. +- qcom,sde-intf-max-prefetch-lines: Array of u32 values for max prefetch lines on + each interface. +- qcom,sde-sspp-linewidth: A u32 value indicates the max sspp line width. +- qcom,sde-mixer-linewidth: A u32 value indicates the max mixer line width. +- qcom,sde-wb-linewidth: A u32 value indicates the max writeback line width. +- qcom,sde-sspp-scale-size: A u32 value indicates the scaling block size on sspp. +- qcom,sde-mixer-blendstages: A u32 value indicates the max mixer blend stages for + alpha blending. +- qcom,sde-qseed-type: A string entry indiates qseed support on sspp and wb. + It supports "qssedv3" and "qseedv2" entries for qseed + type. By default "qseedv2" is used if this optional property + is not defined. +- qcom,sde-csc-type: A string entry indicates csc support on sspp and wb. + It supports "csc" and "csc-10bit" entries for csc + type. +- qcom,sde-highest-bank-bit: A u32 property to indicate GPU/Camera/Video highest memory + bank bit used for tile format buffers. +- qcom,sde-ubwc-version: Property to specify the UBWC feature version. +- qcom,sde-ubwc-static: Property to specify the default UBWC static + configuration value. +- qcom,sde-ubwc-swizzle: Property to specify the default UBWC swizzle + configuration value. +- qcom,sde-panic-per-pipe: Boolean property to indicate if panic signal + control feature is available on each source pipe. +- qcom,sde-has-src-split: Boolean property to indicate if source split + feature is available or not. +- qcom,sde-has-dim-layer: Boolean property to indicate if mixer has dim layer + feature is available or not. +- qcom,sde-has-idle-pc: Boolean property to indicate if target has idle + power collapse feature available or not. +- qcom,sde-has-mixer-gc: Boolean property to indicate if mixer has gamma correction + feature available or not. +- qcom,sde-has-dest-scaler: Boolean property to indicate if destination scaler + feature is available or not. +- qcom,sde-max-dest-scaler-input-linewidth: A u32 value indicates the + maximum input line width to destination scaler. +- qcom,sde-max-dest-scaler-output-linewidth: A u32 value indicates the + maximum output line width of destination scaler. +- qcom,sde-dest-scaler-top-off: A u32 value provides the + offset from mdp base to destination scaler block. +- qcom,sde-dest-scaler-top-size: A u32 value indicates the address range for ds top +- qcom,sde-dest-scaler-off: Array of u32 offsets indicate the qseed3 scaler blocks + offset from destination scaler top offset. +- qcom,sde-dest-scaler-size: A u32 value indicates the address range for each scaler block +- qcom,sde-sspp-clk-ctrl: Array of offsets describing clk control + offsets for dynamic clock gating. 1st value + in the array represents offset of the control + register. 2nd value represents bit offset within + control register. Number of offsets defined should + match the number of offsets defined in + property: qcom,sde-sspp-off +- qcom,sde-sspp-clk-status: Array of offsets describing clk status + offsets for dynamic clock gating. 1st value + in the array represents offset of the status + register. 2nd value represents bit offset within + control register. Number of offsets defined should + match the number of offsets defined in + property: qcom,sde-sspp-off. +- qcom,sde-sspp-excl-rect: Array of u32 values indicating exclusion rectangle + support on each sspp. +- qcom,sde-sspp-smart-dma-priority: Array of u32 values indicating hw pipe + priority of secondary rectangles when smart dma + is supported. Number of priority values should + match the number of offsets defined in + qcom,sde-sspp-off node. Zero indicates no support + for smart dma for the sspp. +- qcom,sde-smart-dma-rev: A string entry indicating the smart dma version + supported on the device. Supported entries are + "smart_dma_v1" and "smart_dma_v2". +- qcom,sde-intf-type: Array of string provides the interface type information. + Possible string values + "dsi" - dsi display interface + "dp" - Display Port interface + "hdmi" - HDMI display interface + An interface is considered as "none" if interface type + is not defined. +- qcom,sde-off: SDE offset from "mdp_phys" defined in reg property. +- qcom,sde-cdm-off: Array of offset addresses for the available + cdm blocks. These offsets will be calculated from + register "mdp_phys" defined in reg property. +- qcom,sde-vbif-off: Array of offset addresses for the available + vbif blocks. These offsets will be calculated from + register "vbif_phys" defined in reg property. +- qcom,sde-vbif-size: A u32 value indicates the vbif block address range. +- qcom,sde-te-off: A u32 offset indicates the te block offset on pingpong. + This offset is 0x0 by default. +- qcom,sde-te2-off: A u32 offset indicates the te2 block offset on pingpong. +- qcom,sde-te-size: A u32 value indicates the te block address range. +- qcom,sde-te2-size: A u32 value indicates the te2 block address range. +- qcom,sde-dsc-off: A u32 offset indicates the dsc block offset on pingpong. +- qcom,sde-dither-off: A u32 offset indicates the dither block offset on pingpong. +- qcom,sde-dither-version: A u32 value indicates the dither block version. +- qcom,sde-dither-size: A u32 value indicates the dither block address range. +- qcom,sde-sspp-vig-blocks: A node that lists the blocks inside the VIG hardware. The + block entries will contain the offset and version (if needed) + of each feature block. The presence of a block entry + indicates that the SSPP VIG contains that feature hardware. + e.g. qcom,sde-sspp-vig-blocks + -- qcom,sde-vig-csc-off: offset of CSC hardware + -- qcom,sde-vig-qseed-off: offset of QSEED hardware + -- qcom,sde-vig-qseed-size: A u32 address range for qseed scaler. + -- qcom,sde-vig-pcc: offset and version of PCC hardware + -- qcom,sde-vig-hsic: offset and version of global PA adjustment + -- qcom,sde-vig-memcolor: offset and version of PA memcolor hardware +- qcom,sde-sspp-rgb-blocks: A node that lists the blocks inside the RGB hardware. The + block entries will contain the offset and version (if needed) + of each feature block. The presence of a block entry + indicates that the SSPP RGB contains that feature hardware. + e.g. qcom,sde-sspp-vig-blocks + -- qcom,sde-rgb-scaler-off: offset of RGB scaler hardware + -- qcom,sde-rgb-scaler-size: A u32 address range for scaler. + -- qcom,sde-rgb-pcc: offset and version of PCC hardware +- qcom,sde-dspp-blocks: A node that lists the blocks inside the DSPP hardware. The + block entries will contain the offset and version of each + feature block. The presence of a block entry indicates that + the DSPP contains that feature hardware. + e.g. qcom,sde-dspp-blocks + -- qcom,sde-dspp-pcc: offset and version of PCC hardware + -- qcom,sde-dspp-gc: offset and version of GC hardware + -- qcom,sde-dspp-igc: offset and version of IGC hardware + -- qcom,sde-dspp-hsic: offset and version of global PA adjustment + -- qcom,sde-dspp-memcolor: offset and version of PA memcolor hardware + -- qcom,sde-dspp-sixzone: offset and version of PA sixzone hardware + -- qcom,sde-dspp-gamut: offset and version of Gamut mapping hardware + -- qcom,sde-dspp-dither: offset and version of dither hardware + -- qcom,sde-dspp-hist: offset and version of histogram hardware + -- qcom,sde-dspp-vlut: offset and version of PA vLUT hardware +- qcom,sde-mixer-blocks: A node that lists the blocks inside the layer mixer hardware. The + block entries will contain the offset and version (if needed) + of each feature block. The presence of a block entry + indicates that the layer mixer contains that feature hardware. + e.g. qcom,sde-mixer-blocks + -- qcom,sde-mixer-gc: offset and version of mixer GC hardware +- qcom,sde-dspp-ad-off: Array of u32 offsets indicate the ad block offset from the + DSPP offset. Since AD hardware is represented as part of + DSPP block, the AD offsets must be offset from the + corresponding DSPP base. +- qcom,sde-dspp-ad-version A u32 value indicating the version of the AD hardware +- qcom,sde-vbif-id: Array of vbif ids corresponding to the + offsets defined in property: qcom,sde-vbif-off. +- qcom,sde-vbif-default-ot-rd-limit: A u32 value indicates the default read OT limit +- qcom,sde-vbif-default-ot-wr-limit: A u32 value indicates the default write OT limit +- qcom,sde-vbif-dynamic-ot-rd-limit: A series of 2 cell property, with a format + of (pps, OT limit), where pps is pixel per second and + OT limit is the read limit to apply if the given + pps is not exceeded. +- qcom,sde-vbif-dynamic-ot-wr-limit: A series of 2 cell property, with a format + of (pps, OT limit), where pps is pixel per second and + OT limit is the write limit to apply if the given + pps is not exceeded. +- qcom,sde-vbif-memtype-0: Array of u32 vbif memory type settings, group 0 +- qcom,sde-vbif-memtype-1: Array of u32 vbif memory type settings, group 1 +- qcom,sde-wb-id: Array of writeback ids corresponding to the + offsets defined in property: qcom,sde-wb-off. +- qcom,sde-wb-clk-ctrl: Array of 2 cell property describing clk control + offsets for dynamic clock gating. 1st value + in the array represents offset of the control + register. 2nd value represents bit offset within + control register. Number of offsets defined should + match the number of offsets defined in + property: qcom,sde-wb-off +- qcom,sde-reg-dma-off: Offset of the register dma hardware block from + "regdma_phys" defined in reg property. +- qcom,sde-reg-dma-version: Version of the reg dma hardware block. +- qcom,sde-reg-dma-trigger-off: Offset of the lut dma trigger reg from "mdp_phys" + defined in reg property. +- qcom,sde-dram-channels: This represents the number of channels in the + Bus memory controller. +- qcom,sde-num-nrt-paths: Integer property represents the number of non-realtime + paths in each Bus Scaling Usecase. This value depends on + number of AXI ports that are dedicated to non-realtime VBIF + for particular chipset. + These paths must be defined after rt-paths in + "qcom,msm-bus,vectors-KBps" vector request. +- qcom,sde-max-bw-low-kbps: This value indicates the max bandwidth in Kbps + that can be supported without underflow. + This is a low bandwidth threshold which should + be applied in most scenarios to be safe from + underflows when unable to satisfy bandwidth + requirements. +- qcom,sde-max-bw-high-kbps: This value indicates the max bandwidth in Kbps + that can be supported without underflow. + This is a high bandwidth threshold which can be + applied in scenarios where panel interface can + be more tolerant to memory latency such as + command mode panels. +- qcom,sde-core-ib-ff: A string entry indicating the fudge factor for + core ib calculation. +- qcom,sde-core-clk-ff: A string entry indicating the fudge factor for + core clock calculation. +- qcom,sde-min-core-ib-kbps: This u32 value indicates the minimum mnoc ib + vote in Kbps that can be reduced without hitting underflow. + BW calculation logic will choose the IB bandwidth requirement + based on usecase if this floor value is not defined. +- qcom,sde-min-llcc-ib-kbps: This u32 value indicates the minimum llcc ib + vote in Kbps that can be reduced without hitting underflow. + BW calculation logic will choose the IB bandwidth requirement + based on usecase if this floor value is not defined. +- qcom,sde-min-dram-ib-kbps: This u32 value indicates the minimum dram ib + vote in Kbps that can be reduced without hitting underflow. + BW calculation logic will choose the IB bandwidth requirement + based on usecase if this floor value is not defined. +- qcom,sde-comp-ratio-rt: A string entry indicating the compression ratio + for each supported compressed format on realtime interface. + The string is composed of one or more of + /// + separated with spaces. +- qcom,sde-comp-ratio-nrt: A string entry indicating the compression ratio + for each supported compressed format on non-realtime interface. + The string is composed of one or more of + /// + separated with spaces. +- qcom,sde-undersized-prefill-lines: A u32 value indicates the size of undersized prefill in lines. +- qcom,sde-xtra-prefill-lines: A u32 value indicates the extra prefill in lines. +- qcom,sde-dest-scale-prefill-lines: A u32 value indicates the latency of destination scaler in lines. +- qcom,sde-macrotile-prefill-lines: A u32 value indicates the latency of macrotile in lines. +- qcom,sde-yuv-nv12-prefill-lines: A u32 value indicates the latency of yuv/nv12 in lines. +- qcom,sde-linear-prefill-lines: A u32 value indicates the latency of linear in lines. +- qcom,sde-downscaling-prefill-lines: A u32 value indicates the latency of downscaling in lines. +- qcom,sde-max-per-pipe-bw-kbps: Array of u32 value indicates the max per pipe bandwidth in Kbps. +- qcom,sde-amortizable-threshold: This value indicates the min for traffic shaping in lines. +- qcom,sde-vbif-qos-rt-remap: This array is used to program vbif qos remapper register + priority for realtime clients. +- qcom,sde-vbif-qos-nrt-remap: This array is used to program vbif qos remapper register + priority for non-realtime clients. +- qcom,sde-danger-lut: A 4 cell property, with a format of , + indicating the danger luts on sspp. +- qcom,sde-safe-lut: A 4 cell property, with a format of , + indicating the safe luts on sspp. +- qcom,sde-qos-lut-linear: Array of 3 cell property, with a format of + in ascending fill level + indicating the qos luts for linear format on sspp. + Zero fill level on the last entry identifies the default lut. +- qcom,sde-qos-lut-macrotile: Array of 3 cell property, with a format of + in ascending fill level + indicating the qos luts for macrotile format on sspp. + Zero fill level on the last entry identifies the default lut. +- qcom,sde-qos-lut-nrt: Array of 3 cell property, with a format of + in ascending fill level + indicating the qos luts for nrt (e.g wfd) on sspp. + Zero fill level on the last entry identifies the default lut. +- qcom,sde-qos-lut-cwb: Array of 3 cell property, with a format of + in ascending fill level + indicating the qos luts for cwb on sspp. + Zero fill level on the last entry identifies the default lut. +- qcom,sde-cdp-setting: Array of 2 cell property, with a format of + for cdp use cases in + order of , and . +- qcom,sde-inline-rot-xin: An integer array of xin-ids related to inline + rotation. +- qcom,sde-inline-rot-xin-type: A string array indicating the type of xin, + namely sspp or wb. Number of entries should match + the number of xin-ids defined in + property: qcom,sde-inline-rot-xin +- qcom,sde-inline-rot-clk-ctrl: Array of offsets describing clk control + offsets for dynamic clock gating. 1st value + in the array represents offset of the control + register. 2nd value represents bit offset within + control register. Number of offsets defined should + match the number of xin-ids defined in + property: qcom,sde-inline-rot-xin + +Bus Scaling Subnodes: +- qcom,sde-reg-bus: Property to provide Bus scaling for register access for + mdss blocks. +- qcom,sde-data-bus: Property to provide Bus scaling for data bus access for + mdss blocks. +- qcom,sde-llcc-bus: Property to provide Bus scaling for data bus access for + mnoc to llcc. +- qcom,sde-ebi-bus: Property to provide Bus scaling for data bus access for + llcc to ebi. + +- qcom,sde-inline-rotator: A 2 cell property, with format of (rotator phandle, + instance id), of inline rotator device. + +Bus Scaling Data: +- qcom,msm-bus,name: String property describing client name. +- qcom,msm-bus,num-cases: This is the number of Bus Scaling use cases + defined in the vectors property. +- qcom,msm-bus,num-paths: This represents the number of paths in each + Bus Scaling Usecase. +- qcom,msm-bus,vectors-KBps: * A series of 4 cell properties, with a format + of (src, dst, ab, ib) which is defined at + Documentation/devicetree/bindings/arm/msm/msm_bus.txt + * Current values of src & dst are defined at + include/linux/msm-bus-board.h + +SMMU Subnodes: +- smmu_sde_****: Child nodes representing sde smmu virtual + devices + +Subnode properties: +- compatible: Compatible names used for smmu devices. + names should be: + "qcom,smmu_sde_unsec": smmu context bank device + for unsecure sde real time domain. + "qcom,smmu_sde_sec": smmu context bank device + for secure sde real time domain. + "qcom,smmu_sde_nrt_unsec": smmu context bank device + for unsecure sde non-real time domain. + "qcom,smmu_sde_nrt_sec": smmu context bank device + for secure sde non-real time domain. + + +Please refer to ../../interrupt-controller/interrupts.txt for a general +description of interrupt bindings. + +Example: + mdss_mdp: qcom,mdss_mdp@900000 { + compatible = "qcom,sde-kms"; + reg = <0x00900000 0x90000>, + <0x009b0000 0x1040>, + <0x009b8000 0x1040>, + <0x0aeac000 0x00f0>; + reg-names = "mdp_phys", + "vbif_phys", + "vbif_nrt_phys", + "regdma_phys"; + clocks = <&clock_mmss clk_mdss_ahb_clk>, + <&clock_mmss clk_mdss_axi_clk>, + <&clock_mmss clk_mdp_clk_src>, + <&clock_mmss clk_mdss_mdp_vote_clk>, + <&clock_mmss clk_smmu_mdp_axi_clk>, + <&clock_mmss clk_mmagic_mdss_axi_clk>, + <&clock_mmss clk_mdss_vsync_clk>; + clock-names = "iface_clk", + "bus_clk", + "core_clk_src", + "core_clk", + "iommu_clk", + "mmagic_clk", + "vsync_clk"; + clock-rate = <0>, <0>, <0>; + clock-max-rate= <0 320000000 0>; + mmagic-supply = <&gdsc_mmagic_mdss>; + vdd-supply = <&gdsc_mdss>; + interrupt-parent = <&intc>; + interrupts = <0 83 0>; + interrupt-controller; + #interrupt-cells = <1>; + iommus = <&mdp_smmu 0>; + + qcom,sde-off = <0x1000>; + qcom,sde-ctl-off = <0x00002000 0x00002200 0x00002400 + 0x00002600 0x00002800>; + qcom,sde-mixer-off = <0x00045000 0x00046000 + 0x00047000 0x0004a000>; + qcom,sde-dspp-top-off = <0x1300>; + qcom,sde-dspp-off = <0x00055000 0x00057000>; + qcom,sde-dspp-ad-off = <0x24000 0x22800>; + qcom,sde-dspp-ad-version = <0x00030000>; + qcom,sde-dest-scaler-top-off = <0x00061000>; + qcom,sde-dest-scaler-off = <0x800 0x1000>; + qcom,sde-wb-off = <0x00066000>; + qcom,sde-wb-xin-id = <6>; + qcom,sde-intf-off = <0x0006b000 0x0006b800 + 0x0006c000 0x0006c800>; + qcom,sde-intf-type = "none", "dsi", "dsi", "hdmi"; + qcom,sde-pp-off = <0x00071000 0x00071800 + 0x00072000 0x00072800>; + qcom,sde-pp-slave = <0x0 0x0 0x0 0x0>; + qcom,sde-cdm-off = <0x0007a200>; + qcom,sde-dsc-off = <0x00081000 0x00081400>; + qcom,sde-intf-max-prefetch-lines = <0x15 0x15 0x15 0x15>; + + qcom,sde-mixer-pair-mask = <2 1 6 0 0 3>; + qcom,sde-mixer-blend-op-off = <0x20 0x38 0x50 0x68 0x80 0x98 + 0xb0 0xc8 0xe0 0xf8 0x110>; + + + qcom,sde-sspp-type = "vig", "vig", "vig", + "vig", "rgb", "rgb", + "rgb", "rgb", "dma", + "dma", "cursor", "cursor"; + + qcom,sde-sspp-off = <0x00005000 0x00007000 0x00009000 + 0x0000b000 0x00015000 0x00017000 + 0x00019000 0x0001b000 0x00025000 + 0x00027000 0x00035000 0x00037000>; + + qcom,sde-sspp-xin-id = <0 4 8 + 12 1 5 + 9 13 2 + 10 7 7>; + + /* offsets are relative to "mdp_phys + qcom,sde-off */ + qcom,sde-sspp-clk-ctrl = <0x2ac 0>, <0x2b4 0>, <0x2bc 0>, + <0x2c4 0>, <0x2ac 4>, <0x2b4 4>, <0x2bc 4>, + <0x2c4 4>, <0x2ac 8>, <0x2b4 8>, <0x3a8 16>, + <0x3b0 16>; + qcom,sde-sspp-clk-status = <0x2ac 0>, <0x2b4 0>, <0x2bc 0>, + <0x2c4 0>, <0x2ac 4>, <0x2b4 4>, <0x2bc 4>, + <0x2c4 4>, <0x2ac 8>, <0x2b4 8>, <0x3a8 16>, + <0x3b0 16>; + qcom,sde-mixer-linewidth = <2560>; + qcom,sde-sspp-linewidth = <2560>; + qcom,sde-mixer-blendstages = <0x7>; + qcom,sde-highest-bank-bit = <0x2>; + qcom,sde-ubwc-version = <0x100>; + qcom,sde-ubwc-static = <0x100>; + qcom,sde-ubwc-swizzle = <0>; + qcom,sde-panic-per-pipe; + qcom,sde-has-src-split; + qcom,sde-has-dim-layer; + qcom,sde-sspp-src-size = <0x100>; + qcom,sde-mixer-size = <0x100>; + qcom,sde-ctl-size = <0x100>; + qcom,sde-dspp-top-size = <0xc>; + qcom,sde-dspp-size = <0x100>; + qcom,sde-intf-size = <0x100>; + qcom,sde-dsc-size = <0x100>; + qcom,sde-cdm-size = <0x100>; + qcom,sde-pp-size = <0x100>; + qcom,sde-wb-size = <0x100>; + qcom,sde-dest-scaler-top-size = <0xc>; + qcom,sde-dest-scaler-size = <0x800>; + qcom,sde-len = <0x100>; + qcom,sde-wb-linewidth = <2560>; + qcom,sde-sspp-scale-size = <0x100>; + qcom,sde-mixer-blendstages = <0x8>; + qcom,sde-qseed-type = "qseedv2"; + qcom,sde-csc-type = "csc-10bit"; + qcom,sde-highest-bank-bit = <15>; + qcom,sde-has-mixer-gc; + qcom,sde-has-idle-pc; + qcom,sde-has-dest-scaler; + qcom,sde-max-dest-scaler-input-linewidth = <2048>; + qcom,sde-max-dest-scaler-output-linewidth = <2560>; + qcom,sde-sspp-max-rects = <1 1 1 1 + 1 1 1 1 + 1 1 + 1 1>; + qcom,sde-sspp-excl-rect = <1 1 1 1 + 1 1 1 1 + 1 1 + 1 1>; + qcom,sde-sspp-smart-dma-priority = <0 0 0 0 + 0 0 0 0 + 0 0 + 1 2>; + qcom,sde-smart-dma-rev = "smart_dma_v2"; + qcom,sde-te-off = <0x100>; + qcom,sde-te2-off = <0x100>; + qcom,sde-te-size = <0xffff>; + qcom,sde-te2-size = <0xffff>; + + qcom,sde-wb-id = <2>; + qcom,sde-wb-clk-ctrl = <0x2bc 16>; + + qcom,sde-danger-lut = <0x0000000f 0x0000ffff 0x00000000 + 0x00000000>; + qcom,sde-safe-lut = <0xfffc 0xff00 0xffff 0xffff>; + qcom,sde-qos-lut-linear = + <4 0x00000000 0x00000357>, + <5 0x00000000 0x00003357>, + <6 0x00000000 0x00023357>, + <7 0x00000000 0x00223357>, + <8 0x00000000 0x02223357>, + <9 0x00000000 0x22223357>, + <10 0x00000002 0x22223357>, + <11 0x00000022 0x22223357>, + <12 0x00000222 0x22223357>, + <13 0x00002222 0x22223357>, + <14 0x00012222 0x22223357>, + <0 0x00112222 0x22223357>; + qcom,sde-qos-lut-macrotile = + <10 0x00000003 0x44556677>, + <11 0x00000033 0x44556677>, + <12 0x00000233 0x44556677>, + <13 0x00002233 0x44556677>, + <14 0x00012233 0x44556677>, + <0 0x00112233 0x44556677>; + qcom,sde-qos-lut-nrt = + <0 0x00000000 0x00000000>; + qcom,sde-qos-lut-cwb = + <0 0x75300000 0x00000000>; + + qcom,sde-cdp-setting = <1 1>, <1 0>; + + qcom,sde-vbif-off = <0 0>; + qcom,sde-vbif-id = <0 1>; + qcom,sde-vbif-default-ot-rd-limit = <32>; + qcom,sde-vbif-default-ot-wr-limit = <16>; + qcom,sde-vbif-dynamic-ot-rd-limit = <62208000 2>, + <124416000 4>, <248832000 16>; + qcom,sde-vbif-dynamic-ot-wr-limit = <62208000 2>, + <124416000 4>, <248832000 16>; + qcom,sde-vbif-memtype-0 = <3 3 3 3 3 3 3 3>; + qcom,sde-vbif-memtype-1 = <3 3 3 3 3 3>; + + qcom,sde-dram-channels = <2>; + qcom,sde-num-nrt-paths = <1>; + + qcom,sde-max-bw-high-kbps = <9000000>; + qcom,sde-max-bw-low-kbps = <9000000>; + + qcom,sde-core-ib-ff = "1.1"; + qcom,sde-core-clk-ff = "1.0"; + qcom,sde-min-core-ib-kbps = <2400000>; + qcom,sde-min-llcc-ib-kbps = <800000>; + qcom,sde-min-dram-ib-kbps = <800000>; + qcom,sde-comp-ratio-rt = "NV12/5/1/1.1 AB24/5/1/1.2 XB24/5/1/1.3"; + qcom,sde-comp-ratio-nrt = "NV12/5/1/1.1 AB24/5/1/1.2 XB24/5/1/1.3"; + qcom,sde-undersized-prefill-lines = <4>; + qcom,sde-xtra-prefill-lines = <5>; + qcom,sde-dest-scale-prefill-lines = <6>; + qcom,sde-macrotile-prefill-lines = <7>; + qcom,sde-yuv-nv12-prefill-lines = <8>; + qcom,sde-linear-prefill-lines = <9>; + qcom,sde-downscaling-prefill-lines = <10>; + qcom,sde-max-per-pipe-bw-kbps = <2400000 2400000 2400000 2400000 + 2400000 2400000 2400000 2400000>; + qcom,sde-amortizable-threshold = <11>; + + qcom,sde-vbif-qos-rt-remap = <3 3 4 4 5 5 6 6>; + qcom,sde-vbif-qos-nrt-remap = <3 3 3 3 3 3 3 3>; + + qcom,sde-sspp-vig-blocks { + qcom,sde-vig-csc-off = <0x320>; + qcom,sde-vig-qseed-off = <0x200>; + qcom,sde-vig-qseed-size = <0x74>; + /* Offset from vig top, version of HSIC */ + qcom,sde-vig-hsic = <0x200 0x00010000>; + qcom,sde-vig-memcolor = <0x200 0x00010000>; + qcom,sde-vig-pcc = <0x1780 0x00010000>; + }; + + qcom,sde-sspp-rgb-blocks { + qcom,sde-rgb-scaler-off = <0x200>; + qcom,sde-rgb-scaler-size = <0x74>; + qcom,sde-rgb-pcc = <0x380 0x00010000>; + }; + + qcom,sde-dspp-blocks { + qcom,sde-dspp-igc = <0x0 0x00010000>; + qcom,sde-dspp-pcc = <0x1700 0x00010000>; + qcom,sde-dspp-gc = <0x17c0 0x00010000>; + qcom,sde-dspp-hsic = <0x0 0x00010000>; + qcom,sde-dspp-memcolor = <0x0 0x00010000>; + qcom,sde-dspp-sixzone = <0x0 0x00010000>; + qcom,sde-dspp-gamut = <0x1600 0x00010000>; + qcom,sde-dspp-dither = <0x0 0x00010000>; + qcom,sde-dspp-hist = <0x0 0x00010000>; + qcom,sde-dspp-vlut = <0x0 0x00010000>; + }; + + qcom,sde-mixer-blocks { + qcom,sde-mixer-gc = <0x3c0 0x00010000>; + }; + + qcom,msm-hdmi-audio-rx { + compatible = "qcom,msm-hdmi-audio-codec-rx"; + }; + + qcom,sde-inline-rotator = <&mdss_rotator 0>; + qcom,sde-inline-rot-xin = <10 11>; + qcom,sde-inline-rot-xin-type = "sspp", "wb"; + qcom,sde-inline-rot-clk-ctrl = <0x2bc 0x8>, <0x2bc 0xc>; + + qcom,platform-supply-entries { + #address-cells = <1>; + #size-cells = <0>; + qcom,platform-supply-entry@0 { + reg = <0>; + qcom,supply-name = "vdd"; + qcom,supply-min-voltage = <0>; + qcom,supply-max-voltage = <0>; + qcom,supply-enable-load = <0>; + qcom,supply-disable-load = <0>; + qcom,supply-pre-on-sleep = <0>; + qcom,supply-post-on-sleep = <0>; + qcom,supply-pre-off-sleep = <0>; + qcom,supply-post-off-sleep = <0>; + }; + }; + + qcom,sde-data-bus { + qcom,msm-bus,name = "mdss_sde"; + qcom,msm-bus,num-cases = <3>; + qcom,msm-bus,num-paths = <3>; + qcom,msm-bus,vectors-KBps = + <22 512 0 0>, <23 512 0 0>, <25 512 0 0>, + <22 512 0 6400000>, <23 512 0 6400000>, + <25 512 0 6400000>, + <22 512 0 6400000>, <23 512 0 6400000>, + <25 512 0 6400000>; + }; + qcom,sde-llcc-bus { + qcom,msm-bus,name = "mdss_sde_llcc"; + qcom,msm-bus,num-cases = <3>; + qcom,msm-bus,num-paths = <1>; + qcom,msm-bus,vectors-KBps = + <132 770 0 0>, + <132 770 0 6400000>, + <132 770 0 6400000>; + }; + qcom,sde-ebi-bus { + qcom,msm-bus,name = "mdss_sde_ebi"; + qcom,msm-bus,num-cases = <3>; + qcom,msm-bus,num-paths = <1>; + qcom,msm-bus,vectors-KBps = + <129 512 0 0>, + <129 512 0 6400000>, + <129 512 0 6400000>; + }; + + qcom,sde-reg-bus { + /* Reg Bus Scale Settings */ + qcom,msm-bus,name = "mdss_reg"; + qcom,msm-bus,num-cases = <4>; + qcom,msm-bus,num-paths = <1>; + qcom,msm-bus,active-only; + qcom,msm-bus,vectors-KBps = + <1 590 0 0>, + <1 590 0 76800>, + <1 590 0 160000>, + <1 590 0 320000>; + }; + + smmu_kms_unsec: qcom,smmu_kms_unsec_cb { + compatible = "qcom,smmu_sde_unsec"; + iommus = <&mmss_smmu 0>; + }; + + smmu_kms_sec: qcom,smmu_kms_sec_cb { + compatible = "qcom,smmu_sde_sec"; + iommus = <&mmss_smmu 1>; + }; + }; diff --git a/Documentation/devicetree/bindings/drm/msm/mdss-dsi-panel.txt b/Documentation/devicetree/bindings/drm/msm/mdss-dsi-panel.txt new file mode 100644 index 000000000000..a965e0e2105e --- /dev/null +++ b/Documentation/devicetree/bindings/drm/msm/mdss-dsi-panel.txt @@ -0,0 +1,772 @@ +QTI mdss-dsi-panel + +mdss-dsi-panel is a dsi panel device which supports panels that +are compatible with MIPI display serial interface specification. + +Required properties: +- compatible: This property applies to DSI V2 panels only. + This property should not be added for panels + that work based on version "V6.0" + DSI panels that are of different versions + are initialized by the drivers for dsi controller. + This property specifies the version + for DSI HW that this panel will work with + "qcom,dsi-panel-v2" = DSI V2.0 +- status: This property applies to DSI V2 panels only. + This property should not be added for panels + that work based on version "V6.0" + DSI panels that are of different versions + are initialized by the drivers for dsi controller. + A string that has to be set to "okay/ok" + to enable the panel driver. By default this property + will be set to "disable". Will be set to "ok/okay" + status for specific platforms. +- qcom,mdss-dsi-panel-controller: Specifies the phandle for the DSI controller that + this panel will be mapped to. +- qcom,mdss-dsi-panel-width: Specifies panel width in pixels. +- qcom,mdss-dsi-panel-height: Specifies panel height in pixels. +- qcom,mdss-dsi-bpp: Specifies the panel bits per pixel. + 3 = for rgb111 + 8 = for rgb332 + 12 = for rgb444 + 16 = for rgb565 + 18 = for rgb666 + 24 = for rgb888 +- qcom,mdss-dsi-panel-destination: A string that specifies the destination display for the panel. + "display_1" = DISPLAY_1 + "display_2" = DISPLAY_2 +- qcom,mdss-dsi-panel-timings: An array of length 12 that specifies the PHY + timing settings for the panel. +- qcom,mdss-dsi-panel-timings-8996: An array of length 40 char that specifies the 8996 PHY lane + timing settings for the panel. +- qcom,mdss-dsi-on-command: A byte stream formed by multiple dcs packets base on + qcom dsi controller protocol. + byte 0: dcs data type + byte 1: set to indicate this is an individual packet + (no chain) + byte 2: virtual channel number + byte 3: expect ack from client (dcs read command) + byte 4: wait number of specified ms after dcs command + transmitted + byte 5, 6: 16 bits length in network byte order + byte 7 and beyond: number byte of payload +- qcom,mdss-dsi-off-command: A byte stream formed by multiple dcs packets base on + qcom dsi controller protocol. + byte 0: dcs data type + byte 1: set to indicate this is an individual packet + (no chain) + byte 2: virtual channel number + byte 3: expect ack from client (dcs read command) + byte 4: wait number of specified ms after dcs command + transmitted + byte 5, 6: 16 bits length in network byte order + byte 7 and beyond: number byte of payload +- qcom,mdss-dsi-post-panel-on-command: same as "qcom,mdss-dsi-on-command" except commands are + sent after displaying an image. + +Note, if a short DCS packet(i.e packet with Byte 0:dcs data type as 05) mentioned in +qcom,mdss-dsi-on-command/qcom,mdss-dsi-off-command stream fails to transmit, +then 3 options can be tried. + 1. Send the packet as a long packet instead + Byte 0: dcs data type = 05 (DCS short Packet) + Byte 0: dcs data type = 29 (DCS long Packet) + 2. Send the packet in one burst by prepending with the next packet in packet stream + Byte 1 = 01 (indicates this is an individual packet) + Byte 1 = 00 (indicates this will be appended to the next + individual packet in the packet stream) + 3. Prepend a NULL packet to the short packet and send both in one burst instead of + combining multiple short packets and sending them in one burst. + +Optional properties: +- qcom,mdss-dsi-panel-name: A string used as a descriptive name of the panel +- qcom,mdss-dsi-panel-phy-timings: An array of length 'n' char that specifies the DSI PHY lane + timing settings for the panel. This is specific to SDE DRM driver. + The value of 'n' depends on the DSI PHY h/w revision and parsing this + property properly will be taken care in the DSI PHY DRM driver. +- qcom,cmd-sync-wait-broadcast: Boolean used to broadcast dcs command to panels. +- qcom,mdss-dsi-fbc-enable: Boolean used to enable frame buffer compression mode. +- qcom,mdss-dsi-fbc-slice-height: Slice height(in lines) of compressed block. + Expressed as power of 2. To set as 128 lines, + this should be set to 7. +- qcom,mdss-dsi-fbc-2d-pred-mode: Boolean to enable 2D map prediction. +- qcom,mdss-dsi-fbc-ver2-mode: Boolean to enable FBC 2.0 that supports 1/3 + compression. +- qcom,mdss-dsi-fbc-bpp: Compressed bpp supported by the panel. + Specified color order is used as default value. +- qcom,mdss-dsi-fbc-packing: Component packing. + 0 = default value. +- qcom,mdss-dsi-fbc-quant-error: Boolean used to enable quantization error calculation. +- qcom,mdss-dsi-fbc-bias: Bias for CD. + 0 = default value. +- qcom,mdss-dsi-fbc-pat-mode: Boolean used to enable PAT mode. +- qcom,mdss-dsi-fbc-vlc-mode: Boolean used to enable VLC mode. +- qcom,mdss-dsi-fbc-bflc-mode: Boolean used to enable BFLC mode. +- qcom,mdss-dsi-fbc-h-line-budget: Per line extra budget. + 0 = default value. +- qcom,mdss-dsi-fbc-budget-ctrl: Extra budget level. + 0 = default value. +- qcom,mdss-dsi-fbc-block-budget: Per block budget. + 0 = default value. +- qcom,mdss-dsi-fbc-lossless-threshold: Lossless mode threshold. + 0 = default value. +- qcom,mdss-dsi-fbc-lossy-threshold: Lossy mode threshold. + 0 = default value. +- qcom,mdss-dsi-fbc-rgb-threshold: Lossy RGB threshold. + 0 = default value. +- qcom,mdss-dsi-fbc-lossy-mode-idx: Lossy mode index value. + 0 = default value. +- qcom,mdss-dsi-fbc-max-pred-err: Max quantization prediction error. + 0 = default value +- qcom,mdss-dsi-h-back-porch: Horizontal back porch value in pixel. + 6 = default value. +- qcom,mdss-dsi-h-front-porch: Horizontal front porch value in pixel. + 6 = default value. +- qcom,mdss-dsi-h-pulse-width: Horizontal pulse width. + 2 = default value. +- qcom,mdss-dsi-h-sync-skew: Horizontal sync skew value. + 0 = default value. +- qcom,mdss-dsi-v-back-porch: Vertical back porch value in pixel. + 6 = default value. +- qcom,mdss-dsi-v-front-porch: Vertical front porch value in pixel. + 6 = default value. +- qcom,mdss-dsi-v-pulse-width: Vertical pulse width. + 2 = default value. +- qcom,mdss-dsi-h-left-border: Horizontal left border in pixel. + 0 = default value +- qcom,mdss-dsi-h-right-border: Horizontal right border in pixel. + 0 = default value +- qcom,mdss-dsi-v-top-border: Vertical top border in pixel. + 0 = default value +- qcom,mdss-dsi-v-bottom-border: Vertical bottom border in pixel. + 0 = default value +- qcom,mdss-dsi-underflow-color: Specifies the controller settings for the + panel under flow color. + 0xff = default value. +- qcom,mdss-dsi-border-color: Defines the border color value if border is present. + 0 = default value. +- qcom,mdss-dsi-panel-jitter: Panel jitter value is expressed in terms of numerator + and denominator. It contains two u32 values - numerator + followed by denominator. The jitter configurition causes + the early wakeup if panel needs to adjust before vsync. + Default jitter value is 2.0%. Max allowed value is 10%. +- qcom,mdss-dsi-panel-prefill-lines: An integer value defines the panel prefill lines required to + calculate the backoff time of rsc. + Default value is 16 lines. Max allowed value is vtotal. +- qcom,mdss-dsi-pan-enable-dynamic-fps: Boolean used to enable change in frame rate dynamically. +- qcom,mdss-dsi-pan-fps-update: A string that specifies when to change the frame rate. + "dfps_suspend_resume_mode"= FPS change request is + implemented during suspend/resume. + "dfps_immediate_clk_mode" = FPS change request is + implemented immediately using DSI clocks. + "dfps_immediate_porch_mode_hfp" = FPS change request is + implemented immediately by changing panel horizontal + front porch values. + "dfps_immediate_porch_mode_vfp" = FPS change request is + implemented immediately by changing panel vertical + front porch values. +- qcom,min-refresh-rate: Minimum refresh rate supported by the panel. +- qcom,max-refresh-rate: Maximum refresh rate supported by the panel. If max refresh + rate is not specified, then the frame rate of the panel in + qcom,mdss-dsi-panel-framerate is used. +- qcom,mdss-dsi-bl-pmic-control-type: A string that specifies the implementation of backlight + control for this panel. + "bl_ctrl_pwm" = Backlight controlled by PWM gpio. + "bl_ctrl_wled" = Backlight controlled by WLED. + "bl_ctrl_dcs" = Backlight controlled by DCS commands. + other: Unknown backlight control. (default) +- qcom,mdss-dsi-bl-pwm-pmi: Boolean to indicate that PWM control is through second pmic chip. +- qcom,mdss-dsi-bl-pmic-bank-select: LPG channel for backlight. + Required if backlight pmic control type is PWM +- qcom,mdss-dsi-bl-pmic-pwm-frequency: PWM period in microseconds. + Required if backlight pmic control type is PWM +- qcom,mdss-dsi-pwm-gpio: PMIC gpio binding to backlight. + Required if backlight pmic control type is PWM +- qcom,mdss-dsi-bl-min-level: Specifies the min backlight level supported by the panel. + 0 = default value. +- qcom,mdss-dsi-bl-max-level: Specifies the max backlight level supported by the panel. + 255 = default value. +- qcom,mdss-brightness-max-level: Specifies the max brightness level supported. + 255 = default value. +- qcom,mdss-dsi-interleave-mode: Specifies interleave mode. + 0 = default value. +- qcom,mdss-dsi-panel-type: Specifies the panel operating mode. + "dsi_video_mode" = enable video mode (default). + "dsi_cmd_mode" = enable command mode. +- qcom,5v-boost-gpio: Specifies the panel gpio for display 5v boost. +- qcom,mdss-dsi-te-check-enable: Boolean to enable Tear Check configuration. +- qcom,mdss-dsi-te-using-wd: Boolean entry enables the watchdog timer support to generate the vsync signal + for command mode panel. By default, panel TE will be used to generate the vsync. +- qcom,mdss-dsi-te-using-te-pin: Boolean to specify whether using hardware vsync. +- qcom,mdss-dsi-te-pin-select: Specifies TE operating mode. + 0 = TE through embedded dcs command + 1 = TE through TE gpio pin. (default) +- qcom,mdss-dsi-te-dcs-command: Inserts the dcs command. + 1 = default value. +- qcom,mdss-dsi-wr-mem-start: DCS command for write_memory_start. + 0x2c = default value. +- qcom,mdss-dsi-wr-mem-continue: DCS command for write_memory_continue. + 0x3c = default value. +- qcom,mdss-dsi-h-sync-pulse: Specifies the pulse mode option for the panel. + 0 = Don't send hsa/he following vs/ve packet(default) + 1 = Send hsa/he following vs/ve packet +- qcom,mdss-dsi-hfp-power-mode: Boolean to determine DSI lane state during + horizontal front porch (HFP) blanking period. +- qcom,mdss-dsi-hbp-power-mode: Boolean to determine DSI lane state during + horizontal back porch (HBP) blanking period. +- qcom,mdss-dsi-hsa-power-mode: Boolean to determine DSI lane state during + horizontal sync active (HSA) mode. +- qcom,mdss-dsi-last-line-interleave Boolean to determine if last line + interleave flag needs to be enabled. +- qcom,mdss-dsi-bllp-eof-power-mode: Boolean to determine DSI lane state during + blanking low power period (BLLP) EOF mode. +- qcom,mdss-dsi-bllp-power-mode: Boolean to determine DSI lane state during + blanking low power period (BLLP) mode. +- qcom,mdss-dsi-traffic-mode: Specifies the panel traffic mode. + "non_burst_sync_pulse" = non burst with sync pulses (default). + "non_burst_sync_event" = non burst with sync start event. + "burst_mode" = burst mode. +- qcom,mdss-dsi-pixel-packing: Specifies if pixel packing is used (in case of RGB666). + "tight" = Tight packing (default value). + "loose" = Loose packing. +- qcom,mdss-dsi-virtual-channel-id: Specifies the virtual channel identefier. + 0 = default value. +- qcom,mdss-dsi-color-order: Specifies the R, G and B channel ordering. + "rgb_swap_rgb" = DSI_RGB_SWAP_RGB (default value) + "rgb_swap_rbg" = DSI_RGB_SWAP_RBG + "rgb_swap_brg" = DSI_RGB_SWAP_BRG + "rgb_swap_grb" = DSI_RGB_SWAP_GRB + "rgb_swap_gbr" = DSI_RGB_SWAP_GBR +- qcom,mdss-dsi-lane-0-state: Boolean that specifies whether data lane 0 is enabled. +- qcom,mdss-dsi-lane-1-state: Boolean that specifies whether data lane 1 is enabled. +- qcom,mdss-dsi-lane-2-state: Boolean that specifies whether data lane 2 is enabled. +- qcom,mdss-dsi-lane-3-state: Boolean that specifies whether data lane 3 is enabled. +- qcom,mdss-dsi-t-clk-post: Specifies the byte clock cycles after mode switch. + 0x03 = default value. +- qcom,mdss-dsi-t-clk-pre: Specifies the byte clock cycles before mode switch. + 0x24 = default value. +- qcom,mdss-dsi-stream: Specifies the packet stream to be used. + 0 = stream 0 (default) + 1 = stream 1 +- qcom,mdss-dsi-mdp-trigger: Specifies the trigger mechanism to be used for MDP path. + "none" = no trigger + "trigger_te" = Tear check signal line used for trigger + "trigger_sw" = Triggered by software (default) + "trigger_sw_te" = Software trigger and TE +- qcom,mdss-dsi-dma-trigger: Specifies the trigger mechanism to be used for DMA path. + "none" = no trigger + "trigger_te" = Tear check signal line used for trigger + "trigger_sw" = Triggered by software (default) + "trigger_sw_seof" = Software trigger and start/end of frame trigger. + "trigger_sw_te" = Software trigger and TE +- qcom,mdss-dsi-panel-framerate: Specifies the frame rate for the panel. + 60 = 60 frames per second (default) +- qcom,mdss-dsi-panel-clockrate: A 64 bit value specifies the panel clock speed in Hz. + 0 = default value. +- qcom,mdss-mdp-transfer-time-us: Specifies the dsi transfer time for command mode + panels in microseconds. Driver uses this number to adjust + the clock rate according to the expected transfer time. + Increasing this value would slow down the mdp processing + and can result in slower performance. + Decreasing this value can speed up the mdp processing, + but this can also impact power consumption. + As a rule this time should not be higher than the time + that would be expected with the processing at the + dsi link rate since anyways this would be the maximum + transfer time that could be achieved. + If ping pong split enabled, this time should not be higher + than two times the dsi link rate time. + 14000 = default value. +- qcom,mdss-dsi-on-command-state: String that specifies the ctrl state for sending ON commands. + "dsi_lp_mode" = DSI low power mode (default) + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-dsi-off-command-state: String that specifies the ctrl state for sending OFF commands. + "dsi_lp_mode" = DSI low power mode (default) + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-dsi-post-mode-switch-on-command-state: String that specifies the ctrl state for sending ON commands post mode switch. + "dsi_lp_mode" = DSI low power mode (default) + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-pan-physical-width-dimension: Specifies panel physical width in mm which corresponds + to the physical width in the framebuffer information. +- qcom,mdss-pan-physical-height-dimension: Specifies panel physical height in mm which corresponds + to the physical height in the framebuffer information. +- qcom,mdss-dsi-mode-sel-gpio-state: String that specifies the lcd mode for panel + (such as single-port/dual-port), if qcom,panel-mode-gpio + binding is defined in dsi controller. + "dual_port" = Set GPIO to LOW + "single_port" = Set GPIO to HIGH + "high" = Set GPIO to HIGH + "low" = Set GPIO to LOW + The default value is "dual_port". +- qcom,mdss-tear-check-disable: Boolean to disable mdp tear check. Tear check is enabled by default to avoid + tearing. Other tear-check properties are ignored if this property is present. + The below tear check configuration properties can be individually tuned if + tear check is enabled. +- qcom,mdss-tear-check-sync-cfg-height: Specifies the vertical total number of lines. + The default value is 0xfff0. +- qcom,mdss-tear-check-sync-init-val: Specifies the init value at which the read pointer gets loaded + at vsync edge. The reader pointer refers to the line number of + panel buffer that is currently being updated. + The default value is panel height. +- qcom,mdss-tear-check-sync-threshold-start: + Allows the first ROI line write to an panel when read pointer is + between the range of ROI start line and ROI start line plus this + setting. + The default value is 4. +- qcom,mdss-tear-check-sync-threshold-continue: + The minimum number of lines the write pointer needs to be + above the read pointer so that it is safe to write to the panel. + (This check is not done for the first ROI line write of an update) + The default value is 4. +- qcom,mdss-tear-check-start-pos: Specify the y position from which the start_threshold value is + added and write is kicked off if the read pointer falls within that + region. + The default value is panel height. +- qcom,mdss-tear-check-rd-ptr-trigger-intr: + Specify the read pointer value at which an interrupt has to be + generated. + The default value is panel height + 1. +- qcom,mdss-tear-check-frame-rate: Specify the value to be a real frame rate(fps) x 100 factor to tune the + timing of TE simulation with more precision. + The default value is 6000 with 60 fps. +- qcom,mdss-dsi-reset-sequence: An array that lists the + sequence of reset gpio values and sleeps + Each command will have the format defined + as below: + --> Reset GPIO value + --> Sleep value (in ms) +- qcom,partial-update-enabled: String used to enable partial + panel update for command mode panels. + "none": partial update is disabled + "single_roi": default enable mode, only single roi is sent to panel + "dual_roi": two rois are merged into one big roi. Panel ddic should be able + to process two roi's along with the DCS command to send two rois. + disabled if property is not specified. +- qcom,mdss-dsi-horizontal-line-idle: List of width ranges (EC - SC) in pixels indicating + additional idle time in dsi clock cycles that is needed + to compensate for smaller line width. +- qcom,partial-update-roi-merge: Boolean indicates roi combination is need + and function has been provided for dcs + 2A/2B command. +- qcom,dcs-cmd-by-left: Boolean to indicate that dcs command are sent + through the left DSI controller only in a dual-dsi configuration +- qcom,mdss-dsi-panel-hdr-enabled: Boolean to indicate HDR support in panel. +- qcom,mdss-dsi-panel-hdr-color-primaries: + Array of 8 unsigned integers denoting chromaticity of panel.These + values are specified in nits units. The value range is 0 through 50000. + To obtain real chromacity, these values should be divided by factor of + 50000. The structure of array is defined in below order + value 1: x value of white chromaticity of display panel + value 2: y value of white chromaticity of display panel + value 3: x value of red chromaticity of display panel + value 4: y value of red chromaticity of display panel + value 5: x value of green chromaticity of display panel + value 6: y value of green chromaticity of display panel + value 7: x value of blue chromaticity of display panel + value 8: y value of blue chromaticity of display panel +- qcom,mdss-dsi-panel-peak-brightness: Maximum brightness supported by panel.In absence of maximum value + typical value becomes peak brightness. Value is specified in nits units. + To obtain real peak brightness, this value should be divided by factor of + 10000. +- qcom,mdss-dsi-panel-blackness-level: Blackness level supported by panel. Blackness level is defined as + ratio of peak brightness to contrast. Value is specified in nits units. + To obtain real blackness level, this value should be divided by factor of + 10000. +- qcom,mdss-dsi-lp11-init: Boolean used to enable the DSI clocks and data lanes (low power 11) + before issuing hardware reset line. +- qcom,mdss-dsi-init-delay-us: Delay in microseconds(us) before performing any DSI activity in lp11 + mode. This master delay (t_init_delay as per DSI spec) should be sum + of DSI internal delay to reach fuctional after power up and minimum + delay required by panel to reach functional. +- qcom,mdss-dsi-rx-eot-ignore: Boolean used to enable ignoring end of transmission packets. +- qcom,mdss-dsi-tx-eot-append: Boolean used to enable appending end of transmission packets. +- qcom,ulps-enabled: Boolean to enable support for Ultra Low Power State (ULPS) mode. +- qcom,suspend-ulps-enabled: Boolean to enable support for ULPS mode for panels during suspend state. +- qcom,panel-roi-alignment: Specifies the panel ROI alignment restrictions on its + left, top, width, height alignments and minimum width and + height values +- qcom,esd-check-enabled: Boolean used to enable ESD recovery feature. +- qcom,mdss-dsi-panel-status-command: A byte stream formed by multiple dcs packets based on + qcom dsi controller protocol, to read the panel status. + This value is used to kick in the ESD recovery. + byte 0: dcs data type + byte 1: set to indicate this is an individual packet + (no chain) + byte 2: virtual channel number + byte 3: expect ack from client (dcs read command) + byte 4: wait number of specified ms after dcs command + transmitted + byte 5, 6: 16 bits length in network byte order + byte 7 and beyond: number byte of payload +- qcom,mdss-dsi-panel-status-command-mode: + String that specifies the ctrl state for reading the panel status. + "dsi_lp_mode" = DSI low power mode + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-dsi-lp1-command: An optional byte stream to request low + power mode on a panel +- qcom,mdss-dsi-lp1-command-mode: String that specifies the ctrl state for + setting the panel power mode. + "dsi_lp_mode" = DSI low power mode + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-dsi-lp2-command: An optional byte stream to request ultra + low power mode on a panel +- qcom,mdss-dsi-lp2-command-mode: String that specifies the ctrl state for + setting the panel power mode. + "dsi_lp_mode" = DSI low power mode + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-dsi-nolp-command: An optional byte stream to disable low + power and ultra low power panel modes +- qcom,mdss-dsi-nolp-command-mode: String that specifies the ctrl state for + setting the panel power mode. + "dsi_lp_mode" = DSI low power mode + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-dsi-panel-status-check-mode:Specifies the panel status check method for ESD recovery. + "bta_check" = Uses BTA to check the panel status + "reg_read" = Reads panel status register to check the panel status + "reg_read_nt35596" = Reads panel status register to check the panel + status for NT35596 panel. + "te_signal_check" = Uses TE signal behaviour to check the panel status +- qcom,mdss-dsi-panel-status-read-length: Integer array that specify the expected read-back length of values + for each of panel registers. Each length is corresponding to number of + returned parameters of register introduced in specification. +- qcom,mdss-dsi-panel-status-valid-params: Integer array that specify the valid returned values which need to check + for each of register. + Some panel need only check the first few values returned from panel. + So: if this property is the same to qcom,mdss-dsi-panel-status-read-length, + then just ignore this one. +- qcom,mdss-dsi-panel-status-value: Multiple integer arrays, each specifies the values of the panel status register + which is used to check the panel status. The size of each array is the sum of + length specified in qcom,mdss-dsi-panel-status-read-length, and must be equal. + This can cover that Some panel may return several alternative values. +- qcom,mdss-dsi-panel-max-error-count: Integer value that specifies the maximum number of errors from register + read that can be ignored before treating that the panel has gone bad. +- qcom,dynamic-mode-switch-enabled: Boolean used to mention whether panel supports + dynamic switching from video mode to command mode + and vice versa. +- qcom,dynamic-mode-switch-type: A string specifies how to perform dynamic mode switch. + If qcom,dynamic-mode-switch-enabled is set and no string specified, default value is + dynamic-switch-suspend-resume. + "dynamic-switch-suspend-resume"= Switch using suspend/resume. Panel will + go blank during transition. + "dynamic-switch-immediate"= Switch on next frame update. Panel will + not go blank for this transition. + "dynamic-resolution-switch-immediate"= Switch the panel resolution. Panel will + not go blank for this transition. +- qcom,mdss-dsi-post-mode-switch-on-command: Multiple dcs packets used for turning on DSI panel + after panel has switch modes. + Refer to "qcom,mdss-dsi-on-command" section for adding commands. +- qcom,video-to-cmd-mode-switch-commands: List of commands that need to be sent + to panel in order to switch from video mode to command mode dynamically. + Refer to "qcom,mdss-dsi-on-command" section for adding commands. +- qcom,cmd-to-video-mode-switch-commands: List of commands that need to be sent + to panel in order to switch from command mode to video mode dynamically. + Refer to "qcom,mdss-dsi-on-command" section for adding commands. +- qcom,send-pps-before-switch: Boolean propety to indicate when PPS commands should be sent, + either before or after switch commands during dynamic resolution + switch in DSC panels. If the property is not present, the default + behavior is to send PPS commands after the switch commands. +- qcom,mdss-dsi-panel-orientation: String used to indicate orientation of panel + "180" = panel is flipped in both horizontal and vertical directions + "hflip" = panel is flipped in horizontal direction + "vflip" = panel is flipped in vertical direction +- qcom,panel-ack-disabled: A boolean property to indicate, whether we need to wait for any ACK from the panel + for any commands that we send. +- qcom,mdss-dsi-force-clock-lane-hs: Boolean to force dsi clock lanes to HS mode always. + +- qcom,compression-mode: Select compression mode for panel. + "fbc" - frame buffer compression + "dsc" - display stream compression. + If "dsc" compression is used then config subnodes needs to be defined. +- qcom,panel-supply-entries: A node that lists the elements of the supply used to + power the DSI panel. There can be more than one instance + of this binding, in which case the entry would be appended + with the supply entry index. For a detailed description of + fields in the supply entry, refer to the qcom,ctrl-supply-entries + binding above. +- qcom,mdss-dsc-version: An 8 bit value indicates the DSC version supported by panel. Bits[0.3] + provides information about minor version while Bits[4.7] provides + major version information. It supports only DSC rev 1(Major).1(Minor) + right now. +- qcom,mdss-dsc-scr-version: Each DSC version can have multiple SCR. This 8 bit value indicates + current SCR revision information supported by panel. +- qcom,mdss-dsc-encoders: An integer value indicating how many DSC encoders should be used + to drive data stream to DSI. + Default value is 1 and max value is 2. + 2 encoder should be used only if qcom,mdss-lm-split or + qcom,split-mode with pingpong-split is used. +- qcom,mdss-dsc-slice-height: An integer value indicates the dsc slice height. +- qcom,mdss-dsc-slice-width: An integer value indicates the dsc slice width. + Multiple of slice width should be equal to panel-width. + Maximum 2 slices per DSC encoder can be used so if 2 DSC encoders + are used then minimum slice width is equal to panel-width/4. +- qcom,mdss-dsc-slice-per-pkt: An integer value indicates the slice per dsi packet. +- qcom,mdss-dsc-bit-per-component: An integer value indicates the bits per component before compression. +- qcom,mdss-dsc-bit-per-pixel: An integer value indicates the bits per pixel after compression. +- qcom,mdss-dsc-block-prediction-enable: A boolean value to enable/disable the block prediction at decoder. +- qcom,mdss-dsc-config-by-manufacture-cmd: A boolean to indicates panel use manufacture command to setup pps + instead of standard dcs type 0x0A. +- qcom,display-topology: Array of u32 values which specifies the list of topologies available + for the display. A display topology is defined by a + set of 3 values in the order: + - number of mixers + - number of compression encoders + - number of interfaces + Therefore, the array should always contain a tuple of 3 elements. +- qcom,default-topology-index: An u32 value which indexes the topology set + specified by the node "qcom,display-topology" + to identify the default topology for the + display. The first set is indexed by the + value 0. + +Required properties for sub-nodes: None +Optional properties: +- qcom,dba-panel: Indicates whether the current panel is used as a display bridge + to a non-DSI interface. +- qcom,bridge-name: A string to indicate the name of the bridge chip connected to DSI. qcom,bridge-name + is required if qcom,dba-panel is defined for the panel. +- qcom,adjust-timer-wakeup-ms: An integer value to indicate the timer delay(in ms) to accommodate + s/w delay while configuring the event timer wakeup logic. + +- qcom,mdss-dsi-display-timings: Parent node that lists the different resolutions that the panel supports. + Each child represents timings settings for a specific resolution. +- qcom,mdss-dsi-post-init-delay: Specifies required number of frames to wait so that panel can be functional + to show proper display. + +Additional properties added to the second level nodes that represent timings properties: +- qcom,mdss-dsi-timing-default: Property that specifies the current child as the default + timing configuration that will be used. +- qcom,mdss-dsi-timing-switch-command: List of commands that need to be sent + to panel when the resolution/timing switch happens dynamically. + Refer to "qcom,mdss-dsi-on-command" section for adding commands. +- qcom,mdss-dsi-timing-switch-command-state: String that specifies the ctrl state for sending resolution switch + commands. + "dsi_lp_mode" = DSI low power mode (default) + "dsi_hs_mode" = DSI high speed mode + +Note, if a given optional qcom,* binding is not present, then the driver will configure +the default values specified. + +Example: +&mdss_mdp { + dsi_sim_vid: qcom,mdss_dsi_sim_video { + qcom,mdss-dsi-panel-name = "simulator video mode dsi panel"; + qcom,mdss-dsi-panel-controller = <&mdss_dsi0>; + qcom,mdss-dsi-panel-height = <1280>; + qcom,mdss-dsi-panel-width = <720>; + qcom,mdss-dsi-bpp = <24>; + qcom,mdss-dsi-pixel-packing = <0>; + qcom,mdss-dsi-panel-destination = "display_1"; + qcom,cmd-sync-wait-broadcast; + qcom,mdss-dsi-fbc-enable; + qcom,mdss-dsi-fbc-slice-height = <5>; + qcom,mdss-dsi-fbc-2d-pred-mode; + qcom,mdss-dsi-fbc-ver2-mode; + qcom,mdss-dsi-fbc-bpp = <0>; + qcom,mdss-dsi-fbc-packing = <0>; + qcom,mdss-dsi-fbc-quant-error; + qcom,mdss-dsi-fbc-bias = <0>; + qcom,mdss-dsi-fbc-pat-mode; + qcom,mdss-dsi-fbc-vlc-mode; + qcom,mdss-dsi-fbc-bflc-mode; + qcom,mdss-dsi-fbc-h-line-budget = <0>; + qcom,mdss-dsi-fbc-budget-ctrl = <0>; + qcom,mdss-dsi-fbc-block-budget = <0>; + qcom,mdss-dsi-fbc-lossless-threshold = <0>; + qcom,mdss-dsi-fbc-lossy-threshold = <0>; + qcom,mdss-dsi-fbc-rgb-threshold = <0>; + qcom,mdss-dsi-fbc-lossy-mode-idx = <0>; + qcom,mdss-dsi-fbc-max-pred-err = <2>; + qcom,mdss-dsi-h-front-porch = <140>; + qcom,mdss-dsi-h-back-porch = <164>; + qcom,mdss-dsi-h-pulse-width = <8>; + qcom,mdss-dsi-h-sync-skew = <0>; + qcom,mdss-dsi-v-back-porch = <6>; + qcom,mdss-dsi-v-front-porch = <1>; + qcom,mdss-dsi-v-pulse-width = <1>; + qcom,mdss-dsi-h-left-border = <0>; + qcom,mdss-dsi-h-right-border = <0>; + qcom,mdss-dsi-v-top-border = <0>; + qcom,mdss-dsi-v-bottom-border = <0>; + qcom,mdss-dsi-border-color = <0>; + qcom,mdss-dsi-underflow-color = <0xff>; + qcom,mdss-dsi-bl-min-level = <1>; + qcom,mdss-dsi-bl-max-level = < 15>; + qcom,mdss-brightness-max-level = <255>; + qcom,mdss-dsi-interleave-mode = <0>; + qcom,mdss-dsi-panel-type = "dsi_video_mode"; + qcom,mdss-dsi-te-check-enable; + qcom,mdss-dsi-te-using-wd; + qcom,mdss-dsi-te-using-te-pin; + qcom,mdss-dsi-te-dcs-command = <1>; + qcom,mdss-dsi-wr-mem-continue = <0x3c>; + qcom,mdss-dsi-wr-mem-start = <0x2c>; + qcom,mdss-dsi-te-pin-select = <1>; + qcom,mdss-dsi-h-sync-pulse = <1>; + qcom,mdss-dsi-hfp-power-mode; + qcom,mdss-dsi-hbp-power-mode; + qcom,mdss-dsi-hsa-power-mode; + qcom,mdss-dsi-bllp-eof-power-mode; + qcom,mdss-dsi-bllp-power-mode; + qcom,mdss-dsi-last-line-interleave; + qcom,mdss-dsi-traffic-mode = <0>; + qcom,mdss-dsi-virtual-channel-id = <0>; + qcom,mdss-dsi-color-order = <0>; + qcom,mdss-dsi-lane-0-state; + qcom,mdss-dsi-lane-1-state; + qcom,mdss-dsi-lane-2-state; + qcom,mdss-dsi-lane-3-state; + qcom,mdss-dsi-t-clk-post = <0x20>; + qcom,mdss-dsi-t-clk-pre = <0x2c>; + qcom,mdss-dsi-stream = <0>; + qcom,mdss-dsi-mdp-trigger = <0>; + qcom,mdss-dsi-dma-trigger = <0>; + qcom,mdss-dsi-panel-framerate = <60>; + qcom,mdss-dsi-panel-clockrate = <424000000>; + qcom,mdss-mdp-transfer-time-us = <12500>; + qcom,mdss-dsi-panel-timings = [7d 25 1d 00 37 33 + 22 27 1e 03 04 00]; + qcom,mdss-dsi-panel-timings-8996 = [23 20 06 09 05 03 04 a0 + 23 20 06 09 05 03 04 a0 + 23 20 06 09 05 03 04 a0 + 23 20 06 09 05 03 04 a0 + 23 2e 06 08 05 03 04 a0]; + qcom,mdss-dsi-on-command = [32 01 00 00 00 00 02 00 00 + 29 01 00 00 10 00 02 FF 99]; + qcom,mdss-dsi-on-command-state = "dsi_lp_mode"; + qcom,mdss-dsi-off-command = [22 01 00 00 00 00 00]; + qcom,mdss-dsi-off-command-state = "dsi_hs_mode"; + qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_wled"; + qcom,mdss-dsi-pan-enable-dynamic-fps; + qcom,mdss-dsi-pan-fps-update = "dfps_suspend_resume_mode"; + qcom,min-refresh-rate = <30>; + qcom,max-refresh-rate = <60>; + qcom,mdss-dsi-bl-pmic-bank-select = <0>; + qcom,mdss-dsi-bl-pmic-pwm-frequency = <0>; + qcom,mdss-dsi-pwm-gpio = <&pm8941_mpps 5 0>; + qcom,5v-boost-gpio = <&pm8994_gpios 14 0>; + qcom,mdss-pan-physical-width-dimension = <60>; + qcom,mdss-pan-physical-height-dimension = <140>; + qcom,mdss-dsi-mode-sel-gpio-state = "dsc_mode"; + qcom,mdss-tear-check-sync-cfg-height = <0xfff0>; + qcom,mdss-tear-check-sync-init-val = <1280>; + qcom,mdss-tear-check-sync-threshold-start = <4>; + qcom,mdss-tear-check-sync-threshold-continue = <4>; + qcom,mdss-tear-check-start-pos = <1280>; + qcom,mdss-tear-check-rd-ptr-trigger-intr = <1281>; + qcom,mdss-tear-check-frame-rate = <6000>; + qcom,mdss-dsi-reset-sequence = <1 2>, <0 10>, <1 10>; + qcom,partial-update-enabled = "single_roi"; + qcom,dcs-cmd-by-left; + qcom,mdss-dsi-lp11-init; + qcom,mdss-dsi-init-delay-us = <100>; + mdss-dsi-rx-eot-ignore; + mdss-dsi-tx-eot-append; + qcom,ulps-enabled; + qcom,suspend-ulps-enabled; + qcom,panel-roi-alignment = <4 4 2 2 20 20>; + qcom,esd-check-enabled; + qcom,mdss-dsi-panel-status-command = [06 01 00 01 05 00 02 0A 08]; + qcom,mdss-dsi-panel-status-command-state = "dsi_lp_mode"; + qcom,mdss-dsi-panel-status-check-mode = "reg_read"; + qcom,mdss-dsi-panel-status-read-length = <8>; + qcom,mdss-dsi-panel-max-error-count = <3>; + qcom,mdss-dsi-panel-status-value = <0x1c 0x00 0x05 0x02 0x40 0x84 0x06 0x01>; + qcom,dynamic-mode-switch-enabled; + qcom,dynamic-mode-switch-type = "dynamic-switch-immediate"; + qcom,mdss-dsi-post-mode-switch-on-command = [32 01 00 00 00 00 02 00 00 + 29 01 00 00 10 00 02 B0 03]; + qcom,video-to-cmd-mode-switch-commands = [15 01 00 00 00 00 02 C2 0B + 15 01 00 00 00 00 02 C2 08]; + qcom,cmd-to-video-mode-switch-commands = [15 01 00 00 00 00 02 C2 03]; + qcom,send-pps-before-switch; + qcom,panel-ack-disabled; + qcom,mdss-dsi-horizontal-line-idle = <0 40 256>, + <40 120 128>, + <128 240 64>; + qcom,mdss-dsi-panel-orientation = "180" + qcom,mdss-dsi-panel-jitter = <0x8 0x10>; + qcom,mdss-dsi-panel-prefill-lines = <0x10>; + qcom,mdss-dsi-force-clock-lane-hs; + qcom,compression-mode = "dsc"; + qcom,adjust-timer-wakeup-ms = <1>; + qcom,mdss-dsi-display-timings { + wqhd { + qcom,mdss-dsi-timing-default; + qcom,mdss-dsi-panel-width = <720>; + qcom,mdss-dsi-panel-height = <2560>; + qcom,mdss-dsi-h-front-porch = <20>; + qcom,mdss-dsi-h-back-porch = <8>; + qcom,mdss-dsi-h-pulse-width = <8>; + qcom,mdss-dsi-h-sync-skew = <0>; + qcom,mdss-dsi-v-back-porch = <4>; + qcom,mdss-dsi-v-front-porch = <728>; + qcom,mdss-dsi-v-pulse-width = <4>; + qcom,mdss-dsi-panel-framerate = <60>; + qcom,mdss-dsi-panel-timings = [E6 38 26 00 68 6E 2A 3C 2C 03 04 00]; + qcom,mdss-dsi-t-clk-post = <0x02>; + qcom,mdss-dsi-t-clk-pre = <0x2a>; + qcom,mdss-dsi-on-command = [05 01 00 00 a0 00 02 11 00 + 05 01 00 00 02 00 02 29 00]; + qcom,mdss-dsi-on-command-state = "dsi_lp_mode"; + qcom,mdss-dsi-timing-switch-command = [ + 29 00 00 00 00 00 02 B0 04 + 29 00 00 00 00 00 02 F1 00]; + qcom,mdss-dsi-timing-switch-command-state = "dsi_lp_mode"; + + qcom,mdss-dsc-slice-height = <16>; + qcom,mdss-dsc-slice-width = <360>; + qcom,mdss-dsc-slice-per-pkt = <2>; + qcom,mdss-dsc-bit-per-component = <8>; + qcom,mdss-dsc-bit-per-pixel = <8>; + qcom,mdss-dsc-block-prediction-enable; + qcom,mdss-dsc-config-by-manufacture-cmd; + qcom,display-topology = <1 1 1>; + qcom,default-topology-index = <0>; + }; + }; + qcom,panel-supply-entries { + #address-cells = <1>; + #size-cells = <0>; + + qcom,panel-supply-entry@0 { + reg = <0>; + qcom,supply-name = "vdd"; + qcom,supply-min-voltage = <2800000>; + qcom,supply-max-voltage = <2800000>; + qcom,supply-enable-load = <100000>; + qcom,supply-disable-load = <100>; + qcom,supply-pre-on-sleep = <0>; + qcom,supply-post-on-sleep = <0>; + qcom,supply-pre-off-sleep = <0>; + qcom,supply-post-off-sleep = <0>; + }; + + qcom,panel-supply-entry@1 { + reg = <1>; + qcom,supply-name = "vddio"; + qcom,supply-min-voltage = <1800000>; + qcom,supply-max-voltage = <1800000>; + qcom,supply-enable-load = <100000>; + qcom,supply-disable-load = <100>; + qcom,supply-pre-on-sleep = <0>; + qcom,supply-post-on-sleep = <0>; + qcom,supply-pre-off-sleep = <0>; + qcom,supply-post-off-sleep = <0>; + }; + }; + + qcom,dba-panel; + qcom,bridge-name = "adv7533"; + qcom,mdss-dsc-version = <0x11>; + qcom,mdss-dsc-scr-version = <0x1>; + qcom,mdss-dsc-slice-height = <16>; + qcom,mdss-dsc-slice-width = <360>; + qcom,mdss-dsc-slice-per-pkt = <2>; + qcom,mdss-dsc-bit-per-component = <8>; + qcom,mdss-dsc-bit-per-pixel = <8>; + qcom,mdss-dsc-block-prediction-enable; + qcom,mdss-dsc-config-by-manufacture-cmd; + qcom,display-topology = <1 1 1>, + <2 2 1>; + qcom,default-topology-index = <0>; + }; +}; diff --git a/Documentation/devicetree/bindings/drm/msm/sde-dp.txt b/Documentation/devicetree/bindings/drm/msm/sde-dp.txt new file mode 100644 index 000000000000..ada2eab37e3e --- /dev/null +++ b/Documentation/devicetree/bindings/drm/msm/sde-dp.txt @@ -0,0 +1,217 @@ +Qualcomm Technologies, Inc. +sde-dp is the master Display Port device which supports DP host controllers that are compatible with VESA Display Port interface specification. +DP Controller: Required properties: +- compatible: Should be "qcom,dp-display". +- reg: Base address and length of DP hardware's memory mapped regions. +- reg-names: A list of strings that name the list of regs. "dp_ctrl" - DP controller memory region. + "dp_phy" - DP PHY memory region. + "dp_ln_tx0" - USB3 DP PHY combo TX-0 lane memory region. + "dp_ln_tx1" - USB3 DP PHY combo TX-1 lane memory region. + "dp_mmss_cc" - Display Clock Control memory region. + "qfprom_physical" - QFPROM Phys memory region. + "dp_pll" - USB3 DP combo PLL memory region. + "usb3_dp_com" - USB3 DP PHY combo memory region. + "hdcp_physical" - DP HDCP memory region. +- cell-index: Specifies the controller instance. +- clocks: Clocks required for Display Port operation. +- clock-names: Names of the clocks corresponding to handles. Following clocks are required: + "core_aux_clk", "core_usb_ref_clk_src","core_usb_ref_clk", "core_usb_cfg_ahb_clk", + "core_usb_pipe_clk", "ctrl_link_clk", "ctrl_link_iface_clk", "ctrl_crypto_clk", + "ctrl_pixel_clk", "pixel_clk_rcg", "pixel_parent". +- gdsc-supply: phandle to gdsc regulator node. +- vdda-1p2-supply: phandle to vdda 1.2V regulator node. +- vdda-0p9-supply: phandle to vdda 0.9V regulator node. +- interrupt-parent phandle to the interrupt parent device node. +- interrupts: The interrupt signal from the DSI block. +- qcom,aux-en-gpio: Specifies the aux-channel enable gpio. +- qcom,aux-sel-gpio: Specifies the aux-channel select gpio. +- qcom,usbplug-cc-gpio: Specifies the usbplug orientation gpio. +- qcom,aux-cfg0-settings: Specifies the DP AUX configuration 0 settings. The first + entry in this array corresponds to the register offset + within DP AUX, while the remaining entries indicate the + programmable values. +- qcom,aux-cfg1-settings: Specifies the DP AUX configuration 1 settings. The first + entry in this array corresponds to the register offset + within DP AUX, while the remaining entries indicate the + programmable values. +- qcom,aux-cfg2-settings: Specifies the DP AUX configuration 2 settings. The first + entry in this array corresponds to the register offset + within DP AUX, while the remaining entries indicate the + programmable values. +- qcom,aux-cfg3-settings: Specifies the DP AUX configuration 3 settings. The first + entry in this array corresponds to the register offset + within DP AUX, while the remaining entries indicate the + programmable values. +- qcom,aux-cfg4-settings: Specifies the DP AUX configuration 4 settings. The first + entry in this array corresponds to the register offset + within DP AUX, while the remaining entries indicate the + programmable values. +- qcom,aux-cfg5-settings: Specifies the DP AUX configuration 5 settings. The first + entry in this array corresponds to the register offset + within DP AUX, while the remaining entries indicate the + programmable values. +- qcom,aux-cfg6-settings: Specifies the DP AUX configuration 6 settings. The first + entry in this array corresponds to the register offset + within DP AUX, while the remaining entries indicate the + programmable values. +- qcom,aux-cfg7-settings: Specifies the DP AUX configuration 7 settings. The first + entry in this array corresponds to the register offset + within DP AUX, while the remaining entries indicate the + programmable values. +- qcom,aux-cfg8-settings: Specifies the DP AUX configuration 8 settings. The first + entry in this array corresponds to the register offset + within DP AUX, while the remaining entries indicate the + programmable values. +- qcom,aux-cfg9-settings: Specifies the DP AUX configuration 9 settings. The first + entry in this array corresponds to the register offset + within DP AUX, while the remaining entries indicate the + programmable values. +- qcom,max-pclk-frequency-khz: An integer specifying the max. pixel clock in KHz supported by Display Port. +- qcom,dp-usbpd-detection: Phandle for the PMI regulator node for USB PHY PD detection. +- qcom,-supply-entries: A node that lists the elements of the supply used by the a particular "type" of DSI module. The module "types" + can be "core", "ctrl", and "phy". Within the same type, + there can be more than one instance of this binding, + in which case the entry would be appended with the + supply entry index. + e.g. qcom,ctrl-supply-entry@0 + -- qcom,supply-name: name of the supply (vdd/vdda/vddio) + -- qcom,supply-min-voltage: minimum voltage level (uV) + -- qcom,supply-max-voltage: maximum voltage level (uV) + -- qcom,supply-enable-load: load drawn (uA) from enabled supply + -- qcom,supply-disable-load: load drawn (uA) from disabled supply + -- qcom,supply-pre-on-sleep: time to sleep (ms) before turning on + -- qcom,supply-post-on-sleep: time to sleep (ms) after turning on + -- qcom,supply-pre-off-sleep: time to sleep (ms) before turning off + -- qcom,supply-post-off-sleep: time to sleep (ms) after turning off +- pinctrl-names: List of names to assign mdss pin states defined in pinctrl device node + Refer to pinctrl-bindings.txt +- pinctrl-<0..n>: Lists phandles each pointing to the pin configuration node within a pin + controller. These pin configurations are installed in the pinctrl + device node. Refer to pinctrl-bindings.txt + +msm_ext_disp is a device which manages the interaction between external +display interfaces, e.g. Display Port, and the audio subsystem. + +Optional properties: +- qcom,ext-disp: phandle for msm-ext-display module +- compatible: Must be "qcom,msm-ext-disp" + +[Optional child nodes]: These nodes are for devices which are +dependent on msm_ext_disp. If msm_ext_disp is disabled then +these devices will be disabled as well. Ex. Audio Codec device. + +- ext_disp_audio_codec: Node for Audio Codec. +- compatible : "qcom,msm-ext-disp-audio-codec-rx"; + +Example: + ext_disp: qcom,msm-ext-disp { + compatible = "qcom,msm-ext-disp"; + ext_disp_audio_codec: qcom,msm-ext-disp-audio-codec-rx { + compatible = "qcom,msm-ext-disp-audio-codec-rx"; + }; + }; + + sde_dp: qcom,dp_display@0{ + cell-index = <0>; + compatible = "qcom,dp-display"; + + gdsc-supply = <&mdss_core_gdsc>; + vdda-1p2-supply = <&pm8998_l26>; + vdda-0p9-supply = <&pm8998_l1>; + + reg = <0xae90000 0xa84>, + <0x88eaa00 0x200>, + <0x88ea200 0x200>, + <0x88ea600 0x200>, + <0xaf02000 0x1a0>, + <0x780000 0x621c>, + <0x88ea030 0x10>, + <0x88e8000 0x621c>, + <0x0aee1000 0x034>; + reg-names = "dp_ctrl", "dp_phy", "dp_ln_tx0", "dp_ln_tx1", + "dp_mmss_cc", "qfprom_physical", "dp_pll", + "usb3_dp_com", "hdcp_physical"; + + interrupt-parent = <&mdss_mdp>; + interrupts = <12 0>; + + clocks = <&clock_dispcc DISP_CC_MDSS_DP_AUX_CLK>, + <&clock_rpmh RPMH_CXO_CLK>, + <&clock_gcc GCC_USB3_PRIM_CLKREF_CLK>, + <&clock_gcc GCC_USB_PHY_CFG_AHB2PHY_CLK>, + <&clock_gcc GCC_USB3_PRIM_PHY_PIPE_CLK>, + <&clock_dispcc DISP_CC_MDSS_DP_LINK_CLK>, + <&clock_dispcc DISP_CC_MDSS_DP_LINK_INTF_CLK>, + <&clock_dispcc DISP_CC_MDSS_DP_CRYPTO_CLK>, + <&clock_dispcc DISP_CC_MDSS_DP_PIXEL_CLK>, + <&clock_dispcc DISP_CC_MDSS_DP_PIXEL_CLK_SRC>, + <&mdss_dp_pll DP_VCO_DIVIDED_CLK_SRC_MUX>; + clock-names = "core_aux_clk", "core_usb_ref_clk_src", + "core_usb_ref_clk", "core_usb_cfg_ahb_clk", + "core_usb_pipe_clk", "ctrl_link_clk", + "ctrl_link_iface_clk", "ctrl_crypto_clk", + "ctrl_pixel_clk", "pixel_clk_rcg", "pixel_parent"; + + qcom,dp-usbpd-detection = <&pmi8998_pdphy>; + qcom,ext-disp = <&ext_disp>; + + qcom,aux-cfg0-settings = [1c 00]; + qcom,aux-cfg1-settings = [20 13 23 1d]; + qcom,aux-cfg2-settings = [24 00]; + qcom,aux-cfg3-settings = [28 00]; + qcom,aux-cfg4-settings = [2c 0a]; + qcom,aux-cfg5-settings = [30 26]; + qcom,aux-cfg6-settings = [34 0a]; + qcom,aux-cfg7-settings = [38 03]; + qcom,aux-cfg8-settings = [3c bb]; + qcom,aux-cfg9-settings = [40 03]; + qcom,max-pclk-frequency-khz = <593470>; + pinctrl-names = "mdss_dp_active", "mdss_dp_sleep"; + pinctrl-0 = <&sde_dp_aux_active &sde_dp_usbplug_cc_active>; + pinctrl-1 = <&sde_dp_aux_suspend &sde_dp_usbplug_cc_suspend>; + qcom,aux-en-gpio = <&tlmm 43 0>; + qcom,aux-sel-gpio = <&tlmm 51 0>; + qcom,usbplug-cc-gpio = <&tlmm 38 0>; + qcom,core-supply-entries { + #address-cells = <1>; + #size-cells = <0>; + + qcom,core-supply-entry@0 { + reg = <0>; + qcom,supply-name = "gdsc"; + qcom,supply-min-voltage = <0>; + qcom,supply-max-voltage = <0>; + qcom,supply-enable-load = <0>; + qcom,supply-disable-load = <0>; + }; + }; + + qcom,ctrl-supply-entries { + #address-cells = <1>; + #size-cells = <0>; + + qcom,ctrl-supply-entry@0 { + reg = <0>; + qcom,supply-name = "vdda-1p2"; + qcom,supply-min-voltage = <1200000>; + qcom,supply-max-voltage = <1200000>; + qcom,supply-enable-load = <21800>; + qcom,supply-disable-load = <4>; + }; + }; + + qcom,phy-supply-entries { + #address-cells = <1>; + #size-cells = <0>; + + qcom,phy-supply-entry@0 { + reg = <0>; + qcom,supply-name = "vdda-0p9"; + qcom,supply-min-voltage = <880000>; + qcom,supply-max-voltage = <880000>; + qcom,supply-enable-load = <36000>; + qcom,supply-disable-load = <32>; + }; + }; + }; +}; diff --git a/Documentation/devicetree/bindings/drm/msm/sde-dsi.txt b/Documentation/devicetree/bindings/drm/msm/sde-dsi.txt new file mode 100644 index 000000000000..641cc26fc07e --- /dev/null +++ b/Documentation/devicetree/bindings/drm/msm/sde-dsi.txt @@ -0,0 +1,102 @@ +Qualcomm Technologies, Inc. + +mdss-dsi is the master DSI device which supports multiple DSI host controllers +that are compatible with MIPI display serial interface specification. + +DSI Controller: +Required properties: +- compatible: Should be "qcom,dsi-ctrl-hw-v". Supported + versions include 1.4, 2.0 and 2.2. + eg: qcom,dsi-ctrl-hw-v1.4, qcom,dsi-ctrl-hw-v2.0, + qcom,dsi-ctrl-hw-v2.2 + And for dsi phy driver: + qcom,dsi-phy-v0.0-hpm, qcom,dsi-phy-v0.0-lpm, + qcom,dsi-phy-v1.0, qcom,dsi-phy-v2.0, + qcom,dsi-phy-v3.0 +- reg: Base address and length of DSI controller's memory + mapped regions. +- reg-names: A list of strings that name the list of regs. + "dsi_ctrl" - DSI controller memory region. + "mmss_misc" - MMSS misc memory region. +- cell-index: Specifies the controller instance. +- clocks: Clocks required for DSI controller operation. +- clock-names: Names of the clocks corresponding to handles. Following + clocks are required: + "mdp_core_clk" + "iface_clk" + "core_mmss_clk" + "bus_clk" + "byte_clk" + "pixel_clk" + "core_clk" + "byte_clk_rcg" + "pixel_clk_rcg" +- gdsc-supply: phandle to gdsc regulator node. +- vdda-supply: phandle to vdda regulator node. +- vcca-supply: phandle to vcca regulator node. +- interrupt-parent phandle to the interrupt parent device node. +- interrupts: The interrupt signal from the DSI block. + +Bus Scaling Data: +- qcom,msm-bus,name: String property describing MDSS client. +- qcom,msm-bus,num-cases: This is the number of bus scaling use cases + defined in the vectors property. This must be + set to <2> for MDSS DSI driver where use-case 0 + is used to remove BW votes from the system. Use + case 1 is used to generate bandwidth requestes + when sending command packets. +- qcom,msm-bus,num-paths: This represents number of paths in each bus + scaling usecase. This value depends on number of + AXI master ports dedicated to MDSS for + particular chipset. +- qcom,msm-bus,vectors-KBps: A series of 4 cell properties, with a format + of (src, dst, ab, ib) which is defined at + Documentation/devicetree/bindings/arm/msm/msm_bus.txt. + DSI driver should always set average bandwidth + (ab) to 0 and always use instantaneous + bandwidth(ib) values. + +Optional properties: +- label: String to describe controller. +- qcom,platform-te-gpio: Specifies the gpio used for TE. +- qcom,dsi-display-active: Current active display +- qcom,dsi-ctrl: handle to dsi controller device +- qcom,dsi-phy: handle to dsi phy device +- qcom,dsi-manager: Specifies dsi manager is present +- qcom,dsi-display: Specifies dsi display is present +- qcom,hdmi-display: Specifies hdmi is present +- qcom,dp-display: Specified dp is present +- qcom,-supply-entries: A node that lists the elements of the supply used by the + a particular "type" of DSI module. The module "types" + can be "core", "ctrl", and "phy". Within the same type, + there can be more than one instance of this binding, + in which case the entry would be appended with the + supply entry index. + e.g. qcom,ctrl-supply-entry@0 + -- qcom,supply-name: name of the supply (vdd/vdda/vddio) + -- qcom,supply-min-voltage: minimum voltage level (uV) + -- qcom,supply-max-voltage: maximum voltage level (uV) + -- qcom,supply-enable-load: load drawn (uA) from enabled supply + -- qcom,supply-disable-load: load drawn (uA) from disabled supply + -- qcom,supply-pre-on-sleep: time to sleep (ms) before turning on + -- qcom,supply-post-on-sleep: time to sleep (ms) after turning on + -- qcom,supply-pre-off-sleep: time to sleep (ms) before turning off + -- qcom,supply-post-off-sleep: time to sleep (ms) after turning off +- qcom,mdss-mdp-transfer-time-us: Specifies the dsi transfer time for command mode + panels in microseconds. Driver uses this number to adjust + the clock rate according to the expected transfer time. + Increasing this value would slow down the mdp processing + and can result in slower performance. + Decreasing this value can speed up the mdp processing, + but this can also impact power consumption. + As a rule this time should not be higher than the time + that would be expected with the processing at the + dsi link rate since anyways this would be the maximum + transfer time that could be achieved. + If ping pong split enabled, this time should not be higher + than two times the dsi link rate time. + If the property is not specified, then the default value is 14000 us. +- qcom,dsi-phy-isolation-enabled: A boolean property enables the phy isolation from dsi + controller. This must be enabled for debugging purpose + only with simulator panel. It should not be enabled for + normal DSI panels. diff --git a/Documentation/devicetree/bindings/drm/msm/sde-wb.txt b/Documentation/devicetree/bindings/drm/msm/sde-wb.txt new file mode 100644 index 000000000000..863b334e438a --- /dev/null +++ b/Documentation/devicetree/bindings/drm/msm/sde-wb.txt @@ -0,0 +1,23 @@ +QTI Snapdragon Display Engine (SDE) writeback display + +Required properties: +- compatible: "qcom,wb-display" + +Optional properties: +- cell-index: Index of writeback device instance. + Default to 0 if not specified. +- label: String to describe this writeback display. + Default to "unknown" if not specified. + +Example: + +/ { + ... + + sde_wb: qcom,wb-display { + compatible = "qcom,wb-display"; + cell-index = <2>; + label = "wb_display"; + }; + +}; diff --git a/Documentation/devicetree/bindings/fb/mdss-dsi-panel.txt b/Documentation/devicetree/bindings/fb/mdss-dsi-panel.txt new file mode 100644 index 000000000000..608b4260a0ab --- /dev/null +++ b/Documentation/devicetree/bindings/fb/mdss-dsi-panel.txt @@ -0,0 +1,742 @@ +Qualcomm mdss-dsi-panel + +mdss-dsi-panel is a dsi panel device which supports panels that +are compatible with MIPI display serial interface specification. + +Required properties: +- compatible: This property applies to DSI V2 panels only. + This property should not be added for panels + that work based on version "V6.0" + DSI panels that are of different versions + are initialized by the drivers for dsi controller. + This property specifies the version + for DSI HW that this panel will work with + "qcom,dsi-panel-v2" = DSI V2.0 +- status: This property applies to DSI V2 panels only. + This property should not be added for panels + that work based on version "V6.0" + DSI panels that are of different versions + are initialized by the drivers for dsi controller. + A string that has to be set to "okay/ok" + to enable the panel driver. By default this property + will be set to "disable". Will be set to "ok/okay" + status for specific platforms. +- qcom,mdss-dsi-panel-controller: Specifies the phandle for the DSI controller that + this panel will be mapped to. +- qcom,mdss-dsi-panel-width: Specifies panel width in pixels. +- qcom,mdss-dsi-panel-height: Specifies panel height in pixels. +- qcom,mdss-dsi-bpp: Specifies the panel bits per pixel. + 3 = for rgb111 + 8 = for rgb332 + 12 = for rgb444 + 16 = for rgb565 + 18 = for rgb666 + 24 = for rgb888 +- qcom,mdss-dsi-panel-destination: A string that specifies the destination display for the panel. + "display_1" = DISPLAY_1 + "display_2" = DISPLAY_2 +- qcom,mdss-dsi-panel-timings: An array of length 12 that specifies the PHY + timing settings for the panel. +- qcom,mdss-dsi-panel-timings-8996: An array of length 40 char that specifies the 8996 PHY lane + timing settings for the panel. +- qcom,mdss-dsi-on-command: A byte stream formed by multiple dcs packets base on + qcom dsi controller protocol. + byte 0: dcs data type + byte 1: set to indicate this is an individual packet + (no chain) + byte 2: virtual channel number + byte 3: expect ack from client (dcs read command) + byte 4: wait number of specified ms after dcs command + transmitted + byte 5, 6: 16 bits length in network byte order + byte 7 and beyond: number byte of payload +- qcom,mdss-dsi-off-command: A byte stream formed by multiple dcs packets base on + qcom dsi controller protocol. + byte 0: dcs data type + byte 1: set to indicate this is an individual packet + (no chain) + byte 2: virtual channel number + byte 3: expect ack from client (dcs read command) + byte 4: wait number of specified ms after dcs command + transmitted + byte 5, 6: 16 bits length in network byte order + byte 7 and beyond: number byte of payload +- qcom,mdss-dsi-post-panel-on-command: same as "qcom,mdss-dsi-on-command" except commands are + sent after displaying an image. + +Note, if a short DCS packet(i.e packet with Byte 0:dcs data type as 05) mentioned in +qcom,mdss-dsi-on-command/qcom,mdss-dsi-off-command stream fails to transmit, +then 3 options can be tried. + 1. Send the packet as a long packet instead + Byte 0: dcs data type = 05 (DCS short Packet) + Byte 0: dcs data type = 29 (DCS long Packet) + 2. Send the packet in one burst by prepending with the next packet in packet stream + Byte 1 = 01 (indicates this is an individual packet) + Byte 1 = 00 (indicates this will be appended to the next + individual packet in the packet stream) + 3. Prepend a NULL packet to the short packet and send both in one burst instead of + combining multiple short packets and sending them in one burst. + +Optional properties: +- qcom,mdss-dsi-panel-name: A string used as a descriptive name of the panel +- qcom,cmd-sync-wait-broadcast: Boolean used to broadcast dcs command to panels. +- qcom,mdss-dsi-fbc-enable: Boolean used to enable frame buffer compression mode. +- qcom,mdss-dsi-fbc-slice-height: Slice height(in lines) of compressed block. + Expressed as power of 2. To set as 128 lines, + this should be set to 7. +- qcom,mdss-dsi-fbc-2d-pred-mode: Boolean to enable 2D map prediction. +- qcom,mdss-dsi-fbc-ver2-mode: Boolean to enable FBC 2.0 that supports 1/3 + compression. +- qcom,mdss-dsi-fbc-bpp: Compressed bpp supported by the panel. + Specified color order is used as default value. +- qcom,mdss-dsi-fbc-packing: Component packing. + 0 = default value. +- qcom,mdss-dsi-fbc-quant-error: Boolean used to enable quantization error calculation. +- qcom,mdss-dsi-fbc-bias: Bias for CD. + 0 = default value. +- qcom,mdss-dsi-fbc-pat-mode: Boolean used to enable PAT mode. +- qcom,mdss-dsi-fbc-vlc-mode: Boolean used to enable VLC mode. +- qcom,mdss-dsi-fbc-bflc-mode: Boolean used to enable BFLC mode. +- qcom,mdss-dsi-fbc-h-line-budget: Per line extra budget. + 0 = default value. +- qcom,mdss-dsi-fbc-budget-ctrl: Extra budget level. + 0 = default value. +- qcom,mdss-dsi-fbc-block-budget: Per block budget. + 0 = default value. +- qcom,mdss-dsi-fbc-lossless-threshold: Lossless mode threshold. + 0 = default value. +- qcom,mdss-dsi-fbc-lossy-threshold: Lossy mode threshold. + 0 = default value. +- qcom,mdss-dsi-fbc-rgb-threshold: Lossy RGB threshold. + 0 = default value. +- qcom,mdss-dsi-fbc-lossy-mode-idx: Lossy mode index value. + 0 = default value. +- qcom,mdss-dsi-fbc-max-pred-err: Max quantization prediction error. + 0 = default value +- qcom,mdss-dsi-h-back-porch: Horizontal back porch value in pixel. + 6 = default value. +- qcom,mdss-dsi-h-front-porch: Horizontal front porch value in pixel. + 6 = default value. +- qcom,mdss-dsi-h-pulse-width: Horizontal pulse width. + 2 = default value. +- qcom,mdss-dsi-h-sync-skew: Horizontal sync skew value. + 0 = default value. +- qcom,mdss-dsi-v-back-porch: Vertical back porch value in pixel. + 6 = default value. +- qcom,mdss-dsi-v-front-porch: Vertical front porch value in pixel. + 6 = default value. +- qcom,mdss-dsi-v-pulse-width: Vertical pulse width. + 2 = default value. +- qcom,mdss-dsi-h-left-border: Horizontal left border in pixel. + 0 = default value +- qcom,mdss-dsi-h-right-border: Horizontal right border in pixel. + 0 = default value +- qcom,mdss-dsi-v-top-border: Vertical top border in pixel. + 0 = default value +- qcom,mdss-dsi-v-bottom-border: Vertical bottom border in pixel. + 0 = default value +- qcom,mdss-dsi-underflow-color: Specifies the controller settings for the + panel under flow color. + 0xff = default value. +- qcom,mdss-dsi-border-color: Defines the border color value if border is present. + 0 = default value. +- qcom,mdss-dsi-pan-enable-dynamic-fps: Boolean used to enable change in frame rate dynamically. +- qcom,mdss-dsi-pan-fps-update: A string that specifies when to change the frame rate. + "dfps_suspend_resume_mode"= FPS change request is + implemented during suspend/resume. + "dfps_immediate_clk_mode" = FPS change request is + implemented immediately using DSI clocks. + "dfps_immediate_porch_mode_hfp" = FPS change request is + implemented immediately by changing panel horizontal + front porch values. + "dfps_immediate_porch_mode_vfp" = FPS change request is + implemented immediately by changing panel vertical + front porch values. +- qcom,min-refresh-rate: Minimum refresh rate supported by the panel. +- qcom,max-refresh-rate: Maximum refresh rate supported by the panel. If max refresh + rate is not specified, then the frame rate of the panel in + qcom,mdss-dsi-panel-framerate is used. +- qcom,mdss-dsi-bl-pmic-control-type: A string that specifies the implementation of backlight + control for this panel. + "bl_ctrl_pwm" = Backlight controlled by PWM gpio. + "bl_ctrl_wled" = Backlight controlled by WLED. + "bl_ctrl_dcs" = Backlight controlled by DCS commands. + other: Unknown backlight control. (default) +- qcom,mdss-dsi-bl-pwm-pmi: Boolean to indicate that PWM control is through second pmic chip. +- qcom,mdss-dsi-bl-pmic-bank-select: LPG channel for backlight. + Required if blpmiccontroltype is PWM +- qcom,mdss-dsi-bl-pmic-pwm-frequency: PWM period in microseconds. + Required if blpmiccontroltype is PWM +- qcom,mdss-dsi-pwm-gpio: PMIC gpio binding to backlight. + Required if blpmiccontroltype is PWM +- qcom,mdss-dsi-bl-min-level: Specifies the min backlight level supported by the panel. + 0 = default value. +- qcom,mdss-dsi-bl-max-level: Specifies the max backlight level supported by the panel. + 255 = default value. +- qcom,mdss-brightness-max-level: Specifies the max brightness level supported. + 255 = default value. +- qcom,mdss-dsi-interleave-mode: Specifies interleave mode. + 0 = default value. +- qcom,mdss-dsi-panel-type: Specifies the panel operating mode. + "dsi_video_mode" = enable video mode (default). + "dsi_cmd_mode" = enable command mode. +- qcom,5v-boost-gpio: Specifies the panel gpio for display 5v boost. +- qcom,mdss-dsi-te-check-enable: Boolean to enable Tear Check configuration. +- qcom,mdss-dsi-te-using-te-pin: Boolean to specify whether using hardware vsync. +- qcom,mdss-dsi-te-pin-select: Specifies TE operating mode. + 0 = TE through embedded dcs command + 1 = TE through TE gpio pin. (default) +- qcom,mdss-dsi-te-dcs-command: Inserts the dcs command. + 1 = default value. +- qcom,mdss-dsi-wr-mem-start: DCS command for write_memory_start. + 0x2c = default value. +- qcom,mdss-dsi-wr-mem-continue: DCS command for write_memory_continue. + 0x3c = default value. +- qcom,mdss-dsi-h-sync-pulse: Specifies the pulse mode option for the panel. + 0 = Don't send hsa/he following vs/ve packet(default) + 1 = Send hsa/he following vs/ve packet +- qcom,mdss-dsi-hfp-power-mode: Boolean to determine DSI lane state during + horizontal front porch (HFP) blanking period. +- qcom,mdss-dsi-hbp-power-mode: Boolean to determine DSI lane state during + horizontal back porch (HBP) blanking period. +- qcom,mdss-dsi-hsa-power-mode: Boolean to determine DSI lane state during + horizontal sync active (HSA) mode. +- qcom,mdss-dsi-last-line-interleave Boolean to determine if last line + interleave flag needs to be enabled. +- qcom,mdss-dsi-bllp-eof-power-mode: Boolean to determine DSI lane state during + blanking low power period (BLLP) EOF mode. +- qcom,mdss-dsi-bllp-power-mode: Boolean to determine DSI lane state during + blanking low power period (BLLP) mode. +- qcom,mdss-dsi-traffic-mode: Specifies the panel traffic mode. + "non_burst_sync_pulse" = non burst with sync pulses (default). + "non_burst_sync_event" = non burst with sync start event. + "burst_mode" = burst mode. +- qcom,mdss-dsi-pixel-packing: Specifies if pixel packing is used (in case of RGB666). + "tight" = Tight packing (default value). + "loose" = Loose packing. +- qcom,mdss-dsi-virtual-channel-id: Specifies the virtual channel identefier. + 0 = default value. +- qcom,mdss-dsi-color-order: Specifies the R, G and B channel ordering. + "rgb_swap_rgb" = DSI_RGB_SWAP_RGB (default value) + "rgb_swap_rbg" = DSI_RGB_SWAP_RBG + "rgb_swap_brg" = DSI_RGB_SWAP_BRG + "rgb_swap_grb" = DSI_RGB_SWAP_GRB + "rgb_swap_gbr" = DSI_RGB_SWAP_GBR +- qcom,mdss-dsi-lane-0-state: Boolean that specifies whether data lane 0 is enabled. +- qcom,mdss-dsi-lane-1-state: Boolean that specifies whether data lane 1 is enabled. +- qcom,mdss-dsi-lane-2-state: Boolean that specifies whether data lane 2 is enabled. +- qcom,mdss-dsi-lane-3-state: Boolean that specifies whether data lane 3 is enabled. +- qcom,mdss-dsi-t-clk-post: Specifies the byte clock cycles after mode switch. + 0x03 = default value. +- qcom,mdss-dsi-t-clk-pre: Specifies the byte clock cycles before mode switch. + 0x24 = default value. +- qcom,mdss-dsi-stream: Specifies the packet stream to be used. + 0 = stream 0 (default) + 1 = stream 1 +- qcom,mdss-dsi-mdp-trigger: Specifies the trigger mechanism to be used for MDP path. + "none" = no trigger + "trigger_te" = Tear check signal line used for trigger + "trigger_sw" = Triggered by software (default) + "trigger_sw_te" = Software trigger and TE +- qcom,mdss-dsi-dma-trigger: Specifies the trigger mechanism to be used for DMA path. + "none" = no trigger + "trigger_te" = Tear check signal line used for trigger + "trigger_sw" = Triggered by software (default) + "trigger_sw_seof" = Software trigger and start/end of frame trigger. + "trigger_sw_te" = Software trigger and TE +- qcom,mdss-dsi-panel-framerate: Specifies the frame rate for the panel. + 60 = 60 frames per second (default) +- qcom,mdss-dsi-panel-clockrate: A 64 bit value specifies the panel clock speed in Hz. + 0 = default value. +- qcom,mdss-mdp-transfer-time-us: Specifies the dsi transfer time for command mode + panels in microseconds. Driver uses this number to adjust + the clock rate according to the expected transfer time. + Increasing this value would slow down the mdp processing + and can result in slower performance. + Decreasing this value can speed up the mdp processing, + but this can also impact power consumption. + As a rule this time should not be higher than the time + that would be expected with the processing at the + dsi link rate since anyways this would be the maximum + transfer time that could be achieved. + If ping pong split enabled, this time should not be higher + than two times the dsi link rate time. + 14000 = default value. +- qcom,mdss-dsi-on-command-state: String that specifies the ctrl state for sending ON commands. + "dsi_lp_mode" = DSI low power mode (default) + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-dsi-off-command-state: String that specifies the ctrl state for sending OFF commands. + "dsi_lp_mode" = DSI low power mode (default) + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-dsi-post-mode-switch-on-command-state: String that specifies the ctrl state for sending ON commands post mode switch. + "dsi_lp_mode" = DSI low power mode (default) + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-pan-physical-width-dimension: Specifies panel physical width in mm which corresponds + to the physical width in the framebuffer information. +- qcom,mdss-pan-physical-height-dimension: Specifies panel physical height in mm which corresponds + to the physical height in the framebuffer information. +- qcom,mdss-dsi-mode-sel-gpio-state: String that specifies the lcd mode for panel + (such as single-port/dual-port), if qcom,panel-mode-gpio + binding is defined in dsi controller. + "dual_port" = Set GPIO to LOW + "single_port" = Set GPIO to HIGH + "high" = Set GPIO to HIGH + "low" = Set GPIO to LOW + The default value is "dual_port". +- qcom,mdss-tear-check-disable: Boolean to disable mdp tear check. Tear check is enabled by default to avoid + tearing. Other tear-check properties are ignored if this property is present. + The below tear check configuration properties can be individually tuned if + tear check is enabled. +- qcom,mdss-tear-check-sync-cfg-height: Specifies the vertical total number of lines. + The default value is 0xfff0. +- qcom,mdss-tear-check-sync-init-val: Specifies the init value at which the read pointer gets loaded + at vsync edge. The reader pointer refers to the line number of + panel buffer that is currently being updated. + The default value is panel height. +- qcom,mdss-tear-check-sync-threshold-start: + Allows the first ROI line write to an panel when read pointer is + between the range of ROI start line and ROI start line plus this + setting. + The default value is 4. +- qcom,mdss-tear-check-sync-threshold-continue: + The minimum number of lines the write pointer needs to be + above the read pointer so that it is safe to write to the panel. + (This check is not done for the first ROI line write of an update) + The default value is 4. +- qcom,mdss-tear-check-start-pos: Specify the y position from which the start_threshold value is + added and write is kicked off if the read pointer falls within that + region. + The default value is panel height. +- qcom,mdss-tear-check-rd-ptr-trigger-intr: + Specify the read pointer value at which an interrupt has to be + generated. + The default value is panel height + 1. +- qcom,mdss-tear-check-frame-rate: Specify the value to be a real frame rate(fps) x 100 factor to tune the + timing of TE simulation with more precision. + The default value is 6000 with 60 fps. +- qcom,mdss-dsi-reset-sequence: An array that lists the + sequence of reset gpio values and sleeps + Each command will have the format defined + as below: + --> Reset GPIO value + --> Sleep value (in ms) +- qcom,partial-update-enabled: Boolean used to enable partial + panel update for command mode panels. +- qcom,mdss-dsi-horizontal-line-idle: List of width ranges (EC - SC) in pixels indicating + additional idle time in dsi clock cycles that is needed + to compensate for smaller line width. +- qcom,partial-update-roi-merge: Boolean indicates roi combination is need + and function has been provided for dcs + 2A/2B command. +- qcom,dcs-cmd-by-left: Boolean to indicate that dcs command are sent + through the left DSI controller only in a dual-dsi configuration +- qcom,mdss-dsi-lp11-init: Boolean used to enable the DSI clocks and data lanes (low power 11) + before issuing hardware reset line. +- qcom,mdss-dsi-init-delay-us: Delay in microseconds(us) before performing any DSI activity in lp11 + mode. This master delay (t_init_delay as per DSI spec) should be sum + of DSI internal delay to reach fuctional after power up and minimum + delay required by panel to reach functional. +- qcom,mdss-dsi-rx-eot-ignore: Boolean used to enable ignoring end of transmission packets. +- qcom,mdss-dsi-tx-eot-append: Boolean used to enable appending end of transmission packets. +- qcom,ulps-enabled: Boolean to enable support for Ultra Low Power State (ULPS) mode. +- qcom,suspend-ulps-enabled: Boolean to enable support for ULPS mode for panels during suspend state. +- qcom,panel-roi-alignment: Specifies the panel ROI alignment restrictions on its + left, top, width, height alignments and minimum width and + height values +- qcom,esd-check-enabled: Boolean used to enable ESD recovery feature. +- qcom,mdss-dsi-panel-status-command: A byte stream formed by multiple dcs packets based on + qcom dsi controller protocol, to read the panel status. + This value is used to kick in the ESD recovery. + byte 0: dcs data type + byte 1: set to indicate this is an individual packet + (no chain) + byte 2: virtual channel number + byte 3: expect ack from client (dcs read command) + byte 4: wait number of specified ms after dcs command + transmitted + byte 5, 6: 16 bits length in network byte order + byte 7 and beyond: number byte of payload +- qcom,mdss-dsi-panel-status-command-mode: + String that specifies the ctrl state for reading the panel status. + "dsi_lp_mode" = DSI low power mode + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-dsi-panel-status-check-mode:Specifies the panel status check method for ESD recovery. + "bta_check" = Uses BTA to check the panel status + "reg_read" = Reads panel status register to check the panel status + "reg_read_nt35596" = Reads panel status register to check the panel + status for NT35596 panel. + "te_signal_check" = Uses TE signal behaviour to check the panel status +- qcom,mdss-dsi-panel-status-read-length: Integer array that specify the expected read-back length of values + for each of panel registers. Each length is corresponding to number of + returned parameters of register introduced in specification. +- qcom,mdss-dsi-panel-status-valid-params: Integer array that specify the valid returned values which need to check + for each of register. + Some panel need only check the first few values returned from panel. + So: if this property is the same to qcom,mdss-dsi-panel-status-read-length, + then just ignore this one. +- qcom,mdss-dsi-panel-status-value: Multiple integer arrays, each specifies the values of the panel status register + which is used to check the panel status. The size of each array is the sum of + length specified in qcom,mdss-dsi-panel-status-read-length, and must be equal. + This can cover that Some panel may return several alternative values. +- qcom,mdss-dsi-panel-max-error-count: Integer value that specifies the maximum number of errors from register + read that can be ignored before treating that the panel has gone bad. +- qcom,dynamic-mode-switch-enabled: Boolean used to mention whether panel supports + dynamic switching from video mode to command mode + and vice versa. +- qcom,dynamic-mode-switch-type: A string specifies how to perform dynamic mode switch. + If qcom,dynamic-mode-switch-enabled is set and no string specified, default value is + dynamic-switch-suspend-resume. + "dynamic-switch-suspend-resume"= Switch using suspend/resume. Panel will + go blank during transition. + "dynamic-switch-immediate"= Switch on next frame update. Panel will + not go blank for this transition. + "dynamic-resolution-switch-immediate"= Switch the panel resolution. Panel will + not go blank for this transition. +- qcom,mdss-dsi-post-mode-switch-on-command: Multiple dcs packets used for turning on DSI panel + after panel has switch modes. + Refer to "qcom,mdss-dsi-on-command" section for adding commands. +- qcom,video-to-cmd-mode-switch-commands: List of commands that need to be sent + to panel in order to switch from video mode to command mode dynamically. + Refer to "qcom,mdss-dsi-on-command" section for adding commands. +- qcom,cmd-to-video-mode-switch-commands: List of commands that need to be sent + to panel in order to switch from command mode to video mode dynamically. + Refer to "qcom,mdss-dsi-on-command" section for adding commands. +- qcom,send-pps-before-switch: Boolean propety to indicate when PPS commands should be sent, + either before or after switch commands during dynamic resolution + switch in DSC panels. If the property is not present, the default + behavior is to send PPS commands after the switch commands. +- qcom,mdss-dsi-panel-orientation: String used to indicate orientation of panel + "180" = panel is flipped in both horizontal and vertical directions + "hflip" = panel is flipped in horizontal direction + "vflip" = panel is flipped in vertical direction +- qcom,panel-ack-disabled: A boolean property to indicate, whether we need to wait for any ACK from the panel + for any commands that we send. +- qcom,mdss-dsi-force-clock-lane-hs: Boolean to force dsi clock lanes to HS mode always. + +- qcom,compression-mode: Select compression mode for panel. + "fbc" - frame buffer compression + "dsc" - display stream compression. + If "dsc" compression is used then config subnodes needs to be defined. +- qcom,panel-supply-entries: A node that lists the elements of the supply used to + power the DSI panel. There can be more than one instance + of this binding, in which case the entry would be appended + with the supply entry index. For a detailed description of + fields in the supply entry, refer to the qcom,ctrl-supply-entries + binding above. +- qcom,config-select: Optional property to select default configuration. + +[[Optional config sub-nodes]] These subnodes provide different configurations for a given same panel. + Default configuration can be chosen by specifying phandle of the + selected subnode in the qcom,config-select. +Required properties for sub-nodes: None +Optional properites: +- qcom,lm-split: An array of two values indicating MDP should use two layer + mixers to reduce power. + Ex: Normally 1080x1920 display uses single DSI and thus one layer + mixer. But if we use two layer mixers then mux the output of + those two mixers into single stream and route it to single DSI + then we can lower the clock requirements of MDP. To use this + configuration we need two fill this array with <540 540>. + Both values doesn't have to be same, but recommended, however sum of + both values has to be equal to the panel-width. + By default two mixer streams are merged using 2D mux, however if + 2 DSC encoders are used then merge is performed within compression + engine. +- qcom,split-mode: String property indicating which split mode MDP should use. Valid + entries are "pingpong-split" and "dualctl-split". + This property is mutually exclusive with qcom,lm-split. +- qcom,mdss-dsc-version: An 8 bit value indicates the DSC version supported by panel. Bits[0.3] + provides information about minor version while Bits[4.7] provides + major version information. It supports only DSC rev 1(Major).1(Minor) + right now. +- qcom,mdss-dsc-scr-version: Each DSC version can have multiple SCR. This 8 bit value indicates + current SCR revision information supported by panel. +- qcom,mdss-dsc-encoders: An integer value indicating how many DSC encoders should be used + to drive data stream to DSI. + Default value is 1 and max value is 2. + 2 encoder should be used only if qcom,mdss-lm-split or + qcom,split-mode with pingpong-split is used. +- qcom,mdss-dsc-slice-height: An integer value indicates the dsc slice height. +- qcom,mdss-dsc-slice-width: An integer value indicates the dsc slice width. + Multiple of slice width should be equal to panel-width. + Maximum 2 slices per DSC encoder can be used so if 2 DSC encoders + are used then minimum slice width is equal to panel-width/4. +- qcom,mdss-dsc-slice-per-pkt: An integer value indicates the slice per dsi packet. +- qcom,mdss-dsc-bit-per-component: An integer value indicates the bits per component before compression. +- qcom,mdss-dsc-bit-per-pixel: An integer value indicates the bits per pixel after compression. +- qcom,mdss-dsc-block-prediction-enable: A boolean value to enable/disable the block prediction at decoder. +- qcom,mdss-dsc-config-by-manufacture-cmd: A boolean to indicates panel use manufacture command to setup pps + instead of standard dcs type 0x0A. +- qcom,dba-panel: Indicates whether the current panel is used as a display bridge + to a non-DSI interface. +- qcom,bridge-name: A string to indicate the name of the bridge chip connected to DSI. qcom,bridge-name + is required if qcom,dba-panel is defined for the panel. +- qcom,adjust-timer-wakeup-ms: An integer value to indicate the timer delay(in ms) to accommodate + s/w delay while configuring the event timer wakeup logic. + +- qcom,mdss-dsi-display-timings: Parent node that lists the different resolutions that the panel supports. + Each child represents timings settings for a specific resolution. +- qcom,mdss-dsi-post-init-delay: Specifies required number of frames to wait so that panel can be functional + to show proper display. + +Additional properties added to the second level nodes that represent timings properties: +- qcom,mdss-dsi-timing-default: Property that specifies the current child as the default + timing configuration that will be used. +- qcom,mdss-dsi-timing-switch-command: List of commands that need to be sent + to panel when the resolution/timing switch happens dynamically. + Refer to "qcom,mdss-dsi-on-command" section for adding commands. +- qcom,mdss-dsi-timing-switch-command-state: String that specifies the ctrl state for sending resolution switch + commands. + "dsi_lp_mode" = DSI low power mode (default) + "dsi_hs_mode" = DSI high speed mode + +Note, if a given optional qcom,* binding is not present, then the driver will configure +the default values specified. + +Example: +&mdss_mdp { + dsi_sim_vid: qcom,mdss_dsi_sim_video { + qcom,mdss-dsi-panel-name = "simulator video mode dsi panel"; + qcom,mdss-dsi-panel-controller = <&mdss_dsi0>; + qcom,mdss-dsi-panel-height = <1280>; + qcom,mdss-dsi-panel-width = <720>; + qcom,mdss-dsi-bpp = <24>; + qcom,mdss-dsi-pixel-packing = <0>; + qcom,mdss-dsi-panel-destination = "display_1"; + qcom,cmd-sync-wait-broadcast; + qcom,mdss-dsi-fbc-enable; + qcom,mdss-dsi-fbc-slice-height = <5>; + qcom,mdss-dsi-fbc-2d-pred-mode; + qcom,mdss-dsi-fbc-ver2-mode; + qcom,mdss-dsi-fbc-bpp = <0>; + qcom,mdss-dsi-fbc-packing = <0>; + qcom,mdss-dsi-fbc-quant-error; + qcom,mdss-dsi-fbc-bias = <0>; + qcom,mdss-dsi-fbc-pat-mode; + qcom,mdss-dsi-fbc-vlc-mode; + qcom,mdss-dsi-fbc-bflc-mode; + qcom,mdss-dsi-fbc-h-line-budget = <0>; + qcom,mdss-dsi-fbc-budget-ctrl = <0>; + qcom,mdss-dsi-fbc-block-budget = <0>; + qcom,mdss-dsi-fbc-lossless-threshold = <0>; + qcom,mdss-dsi-fbc-lossy-threshold = <0>; + qcom,mdss-dsi-fbc-rgb-threshold = <0>; + qcom,mdss-dsi-fbc-lossy-mode-idx = <0>; + qcom,mdss-dsi-fbc-max-pred-err = <2>; + qcom,mdss-dsi-h-front-porch = <140>; + qcom,mdss-dsi-h-back-porch = <164>; + qcom,mdss-dsi-h-pulse-width = <8>; + qcom,mdss-dsi-h-sync-skew = <0>; + qcom,mdss-dsi-v-back-porch = <6>; + qcom,mdss-dsi-v-front-porch = <1>; + qcom,mdss-dsi-v-pulse-width = <1>; + qcom,mdss-dsi-h-left-border = <0>; + qcom,mdss-dsi-h-right-border = <0>; + qcom,mdss-dsi-v-top-border = <0>; + qcom,mdss-dsi-v-bottom-border = <0>; + qcom,mdss-dsi-border-color = <0>; + qcom,mdss-dsi-underflow-color = <0xff>; + qcom,mdss-dsi-bl-min-level = <1>; + qcom,mdss-dsi-bl-max-level = < 15>; + qcom,mdss-brightness-max-level = <255>; + qcom,mdss-dsi-interleave-mode = <0>; + qcom,mdss-dsi-panel-type = "dsi_video_mode"; + qcom,mdss-dsi-te-check-enable; + qcom,mdss-dsi-te-using-te-pin; + qcom,mdss-dsi-te-dcs-command = <1>; + qcom,mdss-dsi-wr-mem-continue = <0x3c>; + qcom,mdss-dsi-wr-mem-start = <0x2c>; + qcom,mdss-dsi-te-pin-select = <1>; + qcom,mdss-dsi-h-sync-pulse = <1>; + qcom,mdss-dsi-hfp-power-mode; + qcom,mdss-dsi-hbp-power-mode; + qcom,mdss-dsi-hsa-power-mode; + qcom,mdss-dsi-bllp-eof-power-mode; + qcom,mdss-dsi-bllp-power-mode; + qcom,mdss-dsi-last-line-interleave; + qcom,mdss-dsi-traffic-mode = <0>; + qcom,mdss-dsi-virtual-channel-id = <0>; + qcom,mdss-dsi-color-order = <0>; + qcom,mdss-dsi-lane-0-state; + qcom,mdss-dsi-lane-1-state; + qcom,mdss-dsi-lane-2-state; + qcom,mdss-dsi-lane-3-state; + qcom,mdss-dsi-t-clk-post = <0x20>; + qcom,mdss-dsi-t-clk-pre = <0x2c>; + qcom,mdss-dsi-stream = <0>; + qcom,mdss-dsi-mdp-trigger = <0>; + qcom,mdss-dsi-dma-trigger = <0>; + qcom,mdss-dsi-panel-framerate = <60>; + qcom,mdss-dsi-panel-clockrate = <424000000>; + qcom,mdss-mdp-transfer-time-us = <12500>; + qcom,mdss-dsi-panel-timings = [7d 25 1d 00 37 33 + 22 27 1e 03 04 00]; + qcom,mdss-dsi-panel-timings-8996 = [23 20 06 09 05 03 04 a0 + 23 20 06 09 05 03 04 a0 + 23 20 06 09 05 03 04 a0 + 23 20 06 09 05 03 04 a0 + 23 2e 06 08 05 03 04 a0]; + qcom,mdss-dsi-on-command = [32 01 00 00 00 00 02 00 00 + 29 01 00 00 10 00 02 FF 99]; + qcom,mdss-dsi-on-command-state = "dsi_lp_mode"; + qcom,mdss-dsi-off-command = [22 01 00 00 00 00 00]; + qcom,mdss-dsi-off-command-state = "dsi_hs_mode"; + qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_wled"; + qcom,mdss-dsi-pan-enable-dynamic-fps; + qcom,mdss-dsi-pan-fps-update = "dfps_suspend_resume_mode"; + qcom,min-refresh-rate = <30>; + qcom,max-refresh-rate = <60>; + qcom,mdss-dsi-bl-pmic-bank-select = <0>; + qcom,mdss-dsi-bl-pmic-pwm-frequency = <0>; + qcom,mdss-dsi-pwm-gpio = <&pm8941_mpps 5 0>; + qcom,5v-boost-gpio = <&pm8994_gpios 14 0>; + qcom,mdss-pan-physical-width-dimension = <60>; + qcom,mdss-pan-physical-height-dimension = <140>; + qcom,mdss-dsi-mode-sel-gpio-state = "dsc_mode"; + qcom,mdss-tear-check-sync-cfg-height = <0xfff0>; + qcom,mdss-tear-check-sync-init-val = <1280>; + qcom,mdss-tear-check-sync-threshold-start = <4>; + qcom,mdss-tear-check-sync-threshold-continue = <4>; + qcom,mdss-tear-check-start-pos = <1280>; + qcom,mdss-tear-check-rd-ptr-trigger-intr = <1281>; + qcom,mdss-tear-check-frame-rate = <6000>; + qcom,mdss-dsi-reset-sequence = <1 2>, <0 10>, <1 10>; + qcom,partial-update-enabled; + qcom,dcs-cmd-by-left; + qcom,mdss-dsi-lp11-init; + qcom,mdss-dsi-init-delay-us = <100>; + mdss-dsi-rx-eot-ignore; + mdss-dsi-tx-eot-append; + qcom,ulps-enabled; + qcom,suspend-ulps-enabled; + qcom,panel-roi-alignment = <4 4 2 2 20 20>; + qcom,esd-check-enabled; + qcom,mdss-dsi-panel-status-command = [06 01 00 01 05 00 02 0A 08]; + qcom,mdss-dsi-panel-status-command-state = "dsi_lp_mode"; + qcom,mdss-dsi-panel-status-check-mode = "reg_read"; + qcom,mdss-dsi-panel-status-read-length = <8>; + qcom,mdss-dsi-panel-max-error-count = <3>; + qcom,mdss-dsi-panel-status-value = <0x1c 0x00 0x05 0x02 0x40 0x84 0x06 0x01>; + qcom,dynamic-mode-switch-enabled; + qcom,dynamic-mode-switch-type = "dynamic-switch-immediate"; + qcom,mdss-dsi-post-mode-switch-on-command = [32 01 00 00 00 00 02 00 00 + 29 01 00 00 10 00 02 B0 03]; + qcom,video-to-cmd-mode-switch-commands = [15 01 00 00 00 00 02 C2 0B + 15 01 00 00 00 00 02 C2 08]; + qcom,cmd-to-video-mode-switch-commands = [15 01 00 00 00 00 02 C2 03]; + qcom,send-pps-before-switch; + qcom,panel-ack-disabled; + qcom,mdss-dsi-horizontal-line-idle = <0 40 256>, + <40 120 128>, + <128 240 64>; + qcom,mdss-dsi-panel-orientation = "180" + qcom,mdss-dsi-force-clock-lane-hs; + qcom,compression-mode = "dsc"; + qcom,adjust-timer-wakeup-ms = <1>; + qcom,mdss-dsi-display-timings { + wqhd { + qcom,mdss-dsi-timing-default; + qcom,mdss-dsi-panel-width = <720>; + qcom,mdss-dsi-panel-height = <2560>; + qcom,mdss-dsi-h-front-porch = <20>; + qcom,mdss-dsi-h-back-porch = <8>; + qcom,mdss-dsi-h-pulse-width = <8>; + qcom,mdss-dsi-h-sync-skew = <0>; + qcom,mdss-dsi-v-back-porch = <4>; + qcom,mdss-dsi-v-front-porch = <728>; + qcom,mdss-dsi-v-pulse-width = <4>; + qcom,mdss-dsi-panel-framerate = <60>; + qcom,mdss-dsi-panel-timings = [E6 38 26 00 68 6E 2A 3C 2C 03 04 00]; + qcom,mdss-dsi-t-clk-post = <0x02>; + qcom,mdss-dsi-t-clk-pre = <0x2a>; + qcom,mdss-dsi-on-command = [05 01 00 00 a0 00 02 11 00 + 05 01 00 00 02 00 02 29 00]; + qcom,mdss-dsi-on-command-state = "dsi_lp_mode"; + qcom,mdss-dsi-timing-switch-command = [ + 29 00 00 00 00 00 02 B0 04 + 29 00 00 00 00 00 02 F1 00]; + qcom,mdss-dsi-timing-switch-command-state = "dsi_lp_mode"; + + qcom,config-select = <&dsi_sim_vid_config0>; + dsi_sim_vid_config0: config0 { + qcom,lm-split = <360 360>; + qcom,mdss-dsc-encoders = <2>; + qcom,mdss-dsc-slice-height = <16>; + qcom,mdss-dsc-slice-width = <360>; + qcom,mdss-dsc-slice-per-pkt = <2>; + qcom,mdss-dsc-bit-per-component = <8>; + qcom,mdss-dsc-bit-per-pixel = <8>; + qcom,mdss-dsc-block-prediction-enable; + qcom,mdss-dsc-config-by-manufacture-cmd; + }; + }; + }; + qcom,panel-supply-entries { + #address-cells = <1>; + #size-cells = <0>; + + qcom,panel-supply-entry@0 { + reg = <0>; + qcom,supply-name = "vdd"; + qcom,supply-min-voltage = <2800000>; + qcom,supply-max-voltage = <2800000>; + qcom,supply-enable-load = <100000>; + qcom,supply-disable-load = <100>; + qcom,supply-pre-on-sleep = <0>; + qcom,supply-post-on-sleep = <0>; + qcom,supply-pre-off-sleep = <0>; + qcom,supply-post-off-sleep = <0>; + }; + + qcom,panel-supply-entry@1 { + reg = <1>; + qcom,supply-name = "vddio"; + qcom,supply-min-voltage = <1800000>; + qcom,supply-max-voltage = <1800000>; + qcom,supply-enable-load = <100000>; + qcom,supply-disable-load = <100>; + qcom,supply-pre-on-sleep = <0>; + qcom,supply-post-on-sleep = <0>; + qcom,supply-pre-off-sleep = <0>; + qcom,supply-post-off-sleep = <0>; + }; + }; + + qcom,config-select = <&dsi_sim_vid_config0>; + qcom,dba-panel; + qcom,bridge-name = "adv7533"; + qcom,mdss-dsc-version = <0x11>; + qcom,mdss-dsc-scr-version = <0x1>; + + dsi_sim_vid_config0: config0 { + qcom,lm-split = <360 360>; + qcom,mdss-dsc-encoders = <2>; + qcom,mdss-dsc-slice-height = <16>; + qcom,mdss-dsc-slice-width = <360>; + qcom,mdss-dsc-slice-per-pkt = <2>; + qcom,mdss-dsc-bit-per-component = <8>; + qcom,mdss-dsc-bit-per-pixel = <8>; + qcom,mdss-dsc-block-prediction-enable; + qcom,mdss-dsc-config-by-manufacture-cmd; + }; + + dsi_sim_vid_config1: config1 { + qcom,mdss-dsc-encoders = <1>; + qcom,mdss-dsc-slice-height = <16>; + qcom,mdss-dsc-slice-width = <360>; + qcom,mdss-dsc-slice-per-pkt = <2>; + qcom,mdss-dsc-bit-per-component = <8>; + qcom,mdss-dsc-bit-per-pixel = <8>; + qcom,mdss-dsc-block-prediction-enable; + qcom,mdss-dsc-config-by-manufacture-cmd; + }; + + dsi_sim_vid_config2: config2 { + qcom,split-mode = "dualctl-split"; + }; + + dsi_sim_vid_config3: config3 { + qcom,split-mode = "pingpong-split"; + }; + }; +}; diff --git a/Documentation/devicetree/bindings/fb/mdss-pll.txt b/Documentation/devicetree/bindings/fb/mdss-pll.txt new file mode 100644 index 000000000000..59fa6a0b80b4 --- /dev/null +++ b/Documentation/devicetree/bindings/fb/mdss-pll.txt @@ -0,0 +1,103 @@ +Qualcomm Technologies MDSS pll for DSI/EDP/HDMI + +mdss-pll is a pll controller device which supports pll devices that +are compatible with MIPI display serial interface specification, +HDMI and edp. + +Required properties: +- compatible: Compatible name used in the driver + "qcom,mdss_dsi_pll_8916", "qcom,mdss_dsi_pll_8939", + "qcom,mdss_dsi_pll_8974", "qcom,mdss_dsi_pll_8994", + "qcom,mdss_dsi_pll_8994", "qcom,mdss_dsi_pll_8909", + "qcom,mdss_hdmi_pll", "qcom,mdss_hdmi_pll_8994", + "qcom,mdss_dsi_pll_8992", "qcom,mdss_hdmi_pll_8992", + "qcom,mdss_dsi_pll_8996", "qcom,mdss_hdmi_pll_8996", + "qcom,mdss_hdmi_pll_8996_v2", "qcom,mdss_dsi_pll_8996_v2", + "qcom,mdss_hdmi_pll_8996_v3", "qcom,mdss_hdmi_pll_8996_v3_1p8", + "qcom,mdss_edp_pll_8996_v3", "qcom,mdss_edp_pll_8996_v3_1p8", + "qcom,mdss_dsi_pll_10nm", "qcom,mdss_dp_pll_8998", + "qcom,mdss_hdmi_pll_8998", "qcom,mdss_dp_pll_10nm". +- cell-index: Specifies the controller used +- reg: offset and length of the register set for the device. +- reg-names : names to refer to register sets related to this device +- gdsc-supply: Phandle for gdsc regulator device node. +- vddio-supply: Phandle for vddio regulator device node. +- clocks: List of Phandles for clock device nodes + needed by the device. +- clock-names: List of clock names needed by the device. +- clock-rate: List of clock rates in Hz. + +Optional properties: +- label: A string used to describe the driver used. +- vcca-supply: Phandle for vcca regulator device node. + + +- qcom,dsi-pll-ssc-en: Boolean property to indicate that ssc is enabled. +- qcom,dsi-pll-ssc-mode: Spread-spectrum clocking. It can be either "down-spread" + or "center-spread". Default is "down-spread" if it is not specified. +- qcom,ssc-frequency-hz: Integer property to specify the spread frequency + to be programmed for the SSC. +- qcom,ssc-ppm: Integer property to specify the Parts per Million + value of SSC. + +- qcom,platform-supply-entries: A node that lists the elements of the supply. There + can be more than one instance of this binding, + in which case the entry would be appended with + the supply entry index. + e.g. qcom,platform-supply-entry@0 + - reg: offset and length of the register set for the device. + -- qcom,supply-name: name of the supply (vdd/vdda/vddio) + -- qcom,supply-min-voltage: minimum voltage level (uV) + -- qcom,supply-max-voltage: maximum voltage level (uV) + -- qcom,supply-enable-load: load drawn (uA) from enabled supply + -- qcom,supply-disable-load: load drawn (uA) from disabled supply + -- qcom,supply-pre-on-sleep: time to sleep (ms) before turning on + -- qcom,supply-post-on-sleep: time to sleep (ms) after turning on + -- qcom,supply-pre-off-sleep: time to sleep (ms) before turning off + -- qcom,supply-post-off-sleep: time to sleep (ms) after turning off + +Example: + mdss_dsi0_pll: qcom,mdss_dsi_pll@fd922A00 { + compatible = "qcom,mdss_dsi_pll_8974"; + label = "MDSS DSI 0 PLL"; + cell-index = <0>; + + reg = <0xfd922A00 0xD4>, + <0xfd922900 0x64>, + <0xfd8c2300 0x8>; + reg-names = "pll_base", "dynamic_pll_base", "gdsc_base"; + gdsc-supply = <&gdsc_mdss>; + vddio-supply = <&pm8941_l12>; + vcca-supply = <&pm8941_l28>; + + clocks = <&clock_gcc clk_gcc_mdss_mdp_clk>, + <&clock_gcc clk_gcc_mdss_ahb_clk>, + <&clock_gcc clk_gcc_mdss_axi_clk>; + clock-names = "mdp_core_clk", "iface_clk", "bus_clk"; + clock-rate = <0>, <0>, <0>; + + qcom,dsi-pll-slave; + qcom,dsi-pll-ssc-en; + qcom,dsi-pll-ssc-mode = "down-spread"; + qcom,ssc-frequency-hz = <30000>; + qcom,ssc-ppm = <5000>; + + qcom,platform-supply-entries { + #address-cells = <1>; + #size-cells = <0>; + + qcom,platform-supply-entry@0 { + reg = <0>; + qcom,supply-name = "vddio"; + qcom,supply-min-voltage = <1800000>; + qcom,supply-max-voltage = <1800000>; + qcom,supply-enable-load = <100000>; + qcom,supply-disable-load = <100>; + qcom,supply-pre-on-sleep = <0>; + qcom,supply-post-on-sleep = <20>; + qcom,supply-pre-off-sleep = <0>; + qcom,supply-post-off-sleep = <0>; + }; + }; + }; + diff --git a/Documentation/devicetree/bindings/media/video/msm-sde-rotator.txt b/Documentation/devicetree/bindings/media/video/msm-sde-rotator.txt new file mode 100644 index 000000000000..46649afcc37e --- /dev/null +++ b/Documentation/devicetree/bindings/media/video/msm-sde-rotator.txt @@ -0,0 +1,223 @@ +SDE Rotator + +SDE rotator is a v4l2 rotator driver, which manages the rotator hw +block inside the Snapdragon Display Engine (or Mobile Display Subsystem) + +Required properties +- compatible: Must be "qcom,sde-rotator". +- reg: offset and length of the register set for the device. +- reg-names: names to refer to register sets related to this device +- interrupt-parent: phandle for the interrupt controller that + services interrupts for this device. +- interrupts: Interrupt associated with rotator. +- -supply: Phandle for regulator device node. +- qcom,supply-names: names to refer to regulator device node. +- clocks: List of Phandles for clock device nodes + needed by the device. +- clock-names: List of clock names needed by the device. +- #list-cells: Number of rotator cells, must be 1 + +Bus Scaling Data: +- qcom,msm-bus,name: String property describing rotator client. +- qcom,msm-bus,num-cases: This is the the number of Bus Scaling use cases + defined in the vectors property. This must be + set to <3> for rotator driver where use-case 0 is + used to take off rotator BW votes from the system. + And use-case 1 & 2 are used in ping-pong fashion + to generate run-time BW requests. +- qcom,msm-bus,num-paths: This represents the number of paths in each + Bus Scaling Usecase. This value depends on + how many number of AXI master ports are + dedicated to rotator for particular chipset. +- qcom,msm-bus,vectors-KBps: * A series of 4 cell properties, with a format + of (src, dst, ab, ib) which is defined at + Documentation/devicetree/bindings/arm/msm/msm_bus.txt + * Current values of src & dst are defined at + include/linux/msm-bus-board.h + src values allowed for rotator are: + 25 = MSM_BUS_MASTER_ROTATOR + dst values allowed for rotator are: + 512 = MSM_BUS_SLAVE_EBI_CH0 + ab: Represents aggregated bandwidth. + ib: Represents instantaneous bandwidth. + * Total number of 4 cell properties will be + (number of use-cases * number of paths). + * These values will be overridden by the driver + based on the run-time requirements. So initial + ab and ib values defined here are random and + bare no logic except for the use-case 0 where ab + and ib values needs to be 0. + * Define realtime vector properties followed by + non-realtime vector properties. + +Optional properties +- qcom,rot-vbif-settings: Array with key-value pairs of constant VBIF register + settings used to setup MDSS QoS for optimum performance. + The key used should be offset from "rot_vbif_phys" register + defined in reg property. +- qcom,mdss-rot-block-size: This integer value indicates the size of a memory block + (in pixels) to be used by the rotator. If this property + is not specified, then a default value of 128 pixels + would be used. +- qcom,mdss-highest-bank-bit: This integer value indicate tile format as opposed to usual + linear format. The value tells the GPU highest memory + bank bit used. +- qcom,mdss-default-ot-wr-limit: This integer value indicates maximum number of pending + writes that can be allowed on each WR xin. + This value can be used to reduce the pending writes + limit and can be tuned to match performance + requirements depending upon system state. + Some platforms require a dynamic ot limiting in + some cases. Setting this default ot write limit + will enable this dynamic limiting for the write + operations in the platforms that require these + limits. +- qcom,mdss-default-ot-rd-limit: This integer value indicates the default number of pending + reads that can be allowed on each RD xin. + Some platforms require a dynamic ot limiting in + some cases. Setting this default ot read limit + will enable this dynamic limiting for the read + operations in the platforms that require these + limits. +- qcom,mdss-rot-vbif-qos-setting: This array is used to program vbif qos remapper register + priority for rotator clients. +- qcom,mdss-rot-vbif-memtype: Array of u32 vbif memory type settings for each xin port. +- qcom,mdss-rot-cdp-setting: Integer array of size two, to indicate client driven + prefetch is available or not. Index 0 represents + if CDP is enabled for read and index 1, if CDP + is enabled for write operation. +- qcom,mdss-rot-qos-lut A 4 cell property with the format of indicating the qos + lut settings for the rotator sspp and writeback + client. +- qcom,mdss-rot-danger-lut A two cell property with the format of indicating the danger lut settings for + the rotator sspp and writeback client. +- qcom,mdss-rot-safe-lut A two cell property with the format of indicating the safe lut settings for the + rotator sspp and writeback client. +- qcom,mdss-inline-rot-qos-lut: A 4 cell property with the format of indicating the qos + lut settings for the inline rotator sspp and + writeback client. +- qcom,mdss-inline-rot-danger-lut: A two cell property with the format of + indicating the danger lut + settings for the inline rotator sspp and + writeback client. +- qcom,mdss-inline-rot-safe-lut: A two cell property with the format of + indicating the safe lut + settings for the inline rotator sspp and + writeback client. +- qcom,mdss-rot-mode: This is integer value indicates operation mode + of the rotator device +- qcom,mdss-sbuf-headroom: This integer value indicates stream buffer headroom in lines. +- qcom,mdss-rot-linewidth: This integer value indicates rotator line width supported in pixels. +- cache-slice-names: A set of names that identify the usecase names of a client that uses + cache slice. These strings are used to look up the cache slice + entries by name. +- cache-slices: The tuple has phandle to llcc device as the first argument and the + second argument is the usecase id of the client. +- qcom,sde-ubwc-malsize: A u32 property to specify the default UBWC + minimum allowable length configuration value. +- qcom,sde-ubwc-swizzle: A u32 property to specify the default UBWC + swizzle configuration value. +- qcom,rot-reg-bus: Property to provide Bus scaling for register + access for rotator blocks. + +Subnode properties: +- compatible: Compatible name used in smmu v2. + smmu_v2 names should be: + "qcom,smmu_sde_rot_unsec"- smmu context bank device for + unsecure rotation domain. + "qcom,smmu_sde_rot_sec" - smmu context bank device for + secure rotation domain. +- iommus: specifies the SID's used by this context bank +- gdsc-mdss-supply: Phandle for mdss supply regulator device node. +- clocks: List of Phandles for clock device nodes + needed by the device. +- clock-names: List of clock names needed by the device. + + +Example: + mdss_rotator: qcom,mdss_rotator { + compatible = "qcom,sde_rotator"; + reg = <0xfd900000 0x22100>, + <0xfd925000 0x1000>; + reg-names = "mdp_phys", "rot_vbif_phys"; + + #list-cells = <1>; + + interrupt-parent = <&mdss_mdp>; + interrupts = <2 0>; + + qcom,mdss-mdp-reg-offset = <0x00001000>; + + rot-vdd-supply = <&gdsc_mdss>; + qcom,supply-names = "rot-vdd"; + + clocks = <&clock_mmss clk_mmss_mdss_ahb_clk>, + <&clock_mmss clk_mmss_mdss_rot_clk>; + clock-names = "iface_clk", "rot_core_clk"; + + qcom,mdss-highest-bank-bit = <0x2>; + qcom,sde-ubwc-malsize = <0>; + qcom,sde-ubwc-swizzle = <1>; + + /* Bus Scale Settings */ + qcom,msm-bus,name = "mdss_rotator"; + qcom,msm-bus,num-cases = <3>; + qcom,msm-bus,num-paths = <1>; + qcom,msm-bus,vectors-KBps = + <25 512 0 0>, + <25 512 0 6400000>, + <25 512 0 6400000>; + + /* VBIF QoS remapper settings*/ + qcom,mdss-rot-vbif-qos-setting = <1 1 1 1>; + qcom,mdss-rot-vbif-memtype = <3 3>; + + com,mdss-rot-cdp-setting = <1 1>; + + qcom,mdss-default-ot-rd-limit = <8>; + qcom,mdss-default-ot-wr-limit = <16>; + + qcom,mdss-rot-qos-lut = <0x0 0x0 0x0 0x0>; + qcom,mdss-rot-danger-lut = <0x0 0x0>; + qcom,mdss-rot-safe-lut = <0x0000ffff 0x0>; + + qcom,mdss-inline-rot-qos-lut = <0x0 0x0 0x00112233 0x44556677>; + qcom,mdss-inline-rot-danger-lut = <0x0 0x0000ffff>; + qcom,mdss-inline-rot-safe-lut = <0x0 0x0000ff00>; + + qcom,mdss-sbuf-headroom = <20>; + cache-slice-names = "rotator"; + cache-slices = <&llcc 4>; + + rot_reg: qcom,rot-reg-bus { + qcom,msm-bus,name = "mdss_rot_reg"; + qcom,msm-bus,num-cases = <2>; + qcom,msm-bus,num-paths = <1>; + qcom,msm-bus,active-only; + qcom,msm-bus,vectors-KBps = + <1 590 0 0>, + <1 590 0 76800>; + }; + + smmu_rot_unsec: qcom,smmu_rot_unsec_cb { + compatible = "qcom,smmu_sde_rot_unsec"; + iommus = <&mdp_smmu 0xe00>; + gdsc-mdss-supply = <&gdsc_bimc_smmu>; + clocks = <&clock_mmss clk_bimc_smmu_ahb_clk>, + <&clock_mmss clk_bimc_smmu_axi_clk>; + clock-names = "rot_ahb_clk", "rot_axi_clk"; + }; + + smmu_sde_rot_sec: qcom,smmu_sde_rot_sec_cb { + compatible = "qcom,smmu_sde_rot_sec"; + iommus = <&mmss_smmu 0xe01>; + gdsc-mdss-supply = <&gdsc_bimc_smmu>; + clocks = <&clock_mmss clk_bimc_smmu_ahb_clk>, + <&clock_mmss clk_bimc_smmu_axi_clk>; + clock-names = "rot_ahb_clk", "rot_axi_clk"; + }; + }; diff --git a/Documentation/devicetree/bindings/msm_hdcp/msm_hdcp.txt b/Documentation/devicetree/bindings/msm_hdcp/msm_hdcp.txt new file mode 100644 index 000000000000..8d5f55d7a8ca --- /dev/null +++ b/Documentation/devicetree/bindings/msm_hdcp/msm_hdcp.txt @@ -0,0 +1,14 @@ +MSM HDCP driver + +Standalone driver managing HDCP related communications +between TZ and HLOS for MSM chipset. + +Required properties: + +compatible = "qcom,msm-hdcp"; + +Example: + +qcom_msmhdcp: qcom,msm_hdcp { + compatible = "qcom,msm-hdcp"; +}; diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 229d42509c1e..603e09381198 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -1,3 +1,5 @@ +source "drivers/clk/qcom/mdss/Kconfig" + config QCOM_GDSC bool select PM_GENERIC_DOMAINS if PM diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 3f2034cc5420..891977f3d94a 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -42,3 +42,5 @@ obj-$(CONFIG_MSM_NPUCC_SDM855) += npucc-sdm855.o obj-$(CONFIG_MSM_VIDEOCC_SDM855) += videocc-sdm855.o obj-$(CONFIG_QCOM_CLK_RPM) += clk-rpm.o obj-$(CONFIG_QCOM_CLK_SMD_RPM) += clk-smd-rpm.o + +obj-y += mdss/ diff --git a/drivers/clk/qcom/clk-rcg2.c b/drivers/clk/qcom/clk-rcg2.c index 9a72862c7384..7e05f58f2e37 100644 --- a/drivers/clk/qcom/clk-rcg2.c +++ b/drivers/clk/qcom/clk-rcg2.c @@ -989,10 +989,11 @@ const struct clk_ops clk_byte2_ops = { EXPORT_SYMBOL_GPL(clk_byte2_ops); static const struct frac_entry frac_table_pixel[] = { + { 1, 1 }, + { 2, 3 }, + { 4, 9 }, { 3, 8 }, { 2, 9 }, - { 4, 9 }, - { 1, 1 }, { } }; diff --git a/drivers/clk/qcom/mdss/Kconfig b/drivers/clk/qcom/mdss/Kconfig new file mode 100644 index 000000000000..7213e375f1ef --- /dev/null +++ b/drivers/clk/qcom/mdss/Kconfig @@ -0,0 +1,7 @@ +config QCOM_MDSS_PLL + bool "MDSS pll programming" + depends on COMMON_CLK_QCOM + ---help--- + It provides support for DSI, eDP and HDMI interface pll programming on MDSS + hardware. It also handles the pll specific resources and turn them on/off when + mdss pll client tries to enable/disable pll clocks. diff --git a/drivers/clk/qcom/mdss/Makefile b/drivers/clk/qcom/mdss/Makefile new file mode 100644 index 000000000000..87feee6f282d --- /dev/null +++ b/drivers/clk/qcom/mdss/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_QCOM_MDSS_PLL) += mdss-pll-util.o +obj-$(CONFIG_QCOM_MDSS_PLL) += mdss-pll.o +obj-$(CONFIG_QCOM_MDSS_PLL) += mdss-dsi-pll-10nm.o +obj-$(CONFIG_QCOM_MDSS_PLL) += mdss-dp-pll-10nm.o +obj-$(CONFIG_QCOM_MDSS_PLL) += mdss-dp-pll-10nm-util.o + diff --git a/drivers/clk/qcom/mdss/mdss-dp-pll-10nm-util.c b/drivers/clk/qcom/mdss/mdss-dp-pll-10nm-util.c new file mode 100644 index 000000000000..eb2092a9e102 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dp-pll-10nm-util.c @@ -0,0 +1,766 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dp-pll.h" +#include "mdss-dp-pll-10nm.h" + +#define DP_PHY_REVISION_ID0 0x0000 +#define DP_PHY_REVISION_ID1 0x0004 +#define DP_PHY_REVISION_ID2 0x0008 +#define DP_PHY_REVISION_ID3 0x000C + +#define DP_PHY_CFG 0x0010 +#define DP_PHY_PD_CTL 0x0018 +#define DP_PHY_MODE 0x001C + +#define DP_PHY_AUX_CFG0 0x0020 +#define DP_PHY_AUX_CFG1 0x0024 +#define DP_PHY_AUX_CFG2 0x0028 +#define DP_PHY_AUX_CFG3 0x002C +#define DP_PHY_AUX_CFG4 0x0030 +#define DP_PHY_AUX_CFG5 0x0034 +#define DP_PHY_AUX_CFG6 0x0038 +#define DP_PHY_AUX_CFG7 0x003C +#define DP_PHY_AUX_CFG8 0x0040 +#define DP_PHY_AUX_CFG9 0x0044 +#define DP_PHY_AUX_INTERRUPT_MASK 0x0048 +#define DP_PHY_AUX_INTERRUPT_CLEAR 0x004C +#define DP_PHY_AUX_BIST_CFG 0x0050 + +#define DP_PHY_VCO_DIV 0x0064 +#define DP_PHY_TX0_TX1_LANE_CTL 0x006C +#define DP_PHY_TX2_TX3_LANE_CTL 0x0088 + +#define DP_PHY_SPARE0 0x00AC +#define DP_PHY_STATUS 0x00C0 + +/* Tx registers */ +#define TXn_BIST_MODE_LANENO 0x0000 +#define TXn_CLKBUF_ENABLE 0x0008 +#define TXn_TX_EMP_POST1_LVL 0x000C + +#define TXn_TX_DRV_LVL 0x001C + +#define TXn_RESET_TSYNC_EN 0x0024 +#define TXn_PRE_STALL_LDO_BOOST_EN 0x0028 +#define TXn_TX_BAND 0x002C +#define TXn_SLEW_CNTL 0x0030 +#define TXn_INTERFACE_SELECT 0x0034 + +#define TXn_RES_CODE_LANE_TX 0x003C +#define TXn_RES_CODE_LANE_RX 0x0040 +#define TXn_RES_CODE_LANE_OFFSET_TX 0x0044 +#define TXn_RES_CODE_LANE_OFFSET_RX 0x0048 + +#define TXn_DEBUG_BUS_SEL 0x0058 +#define TXn_TRANSCEIVER_BIAS_EN 0x005C +#define TXn_HIGHZ_DRVR_EN 0x0060 +#define TXn_TX_POL_INV 0x0064 +#define TXn_PARRATE_REC_DETECT_IDLE_EN 0x0068 + +#define TXn_LANE_MODE_1 0x008C + +#define TXn_TRAN_DRVR_EMP_EN 0x00C0 +#define TXn_TX_INTERFACE_MODE 0x00C4 + +#define TXn_VMODE_CTRL1 0x00F0 + +/* PLL register offset */ +#define QSERDES_COM_ATB_SEL1 0x0000 +#define QSERDES_COM_ATB_SEL2 0x0004 +#define QSERDES_COM_FREQ_UPDATE 0x0008 +#define QSERDES_COM_BG_TIMER 0x000C +#define QSERDES_COM_SSC_EN_CENTER 0x0010 +#define QSERDES_COM_SSC_ADJ_PER1 0x0014 +#define QSERDES_COM_SSC_ADJ_PER2 0x0018 +#define QSERDES_COM_SSC_PER1 0x001C +#define QSERDES_COM_SSC_PER2 0x0020 +#define QSERDES_COM_SSC_STEP_SIZE1 0x0024 +#define QSERDES_COM_SSC_STEP_SIZE2 0x0028 +#define QSERDES_COM_POST_DIV 0x002C +#define QSERDES_COM_POST_DIV_MUX 0x0030 +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN 0x0034 +#define QSERDES_COM_CLK_ENABLE1 0x0038 +#define QSERDES_COM_SYS_CLK_CTRL 0x003C +#define QSERDES_COM_SYSCLK_BUF_ENABLE 0x0040 +#define QSERDES_COM_PLL_EN 0x0044 +#define QSERDES_COM_PLL_IVCO 0x0048 +#define QSERDES_COM_CMN_IETRIM 0x004C +#define QSERDES_COM_CMN_IPTRIM 0x0050 + +#define QSERDES_COM_CP_CTRL_MODE0 0x0060 +#define QSERDES_COM_CP_CTRL_MODE1 0x0064 +#define QSERDES_COM_PLL_RCTRL_MODE0 0x0068 +#define QSERDES_COM_PLL_RCTRL_MODE1 0x006C +#define QSERDES_COM_PLL_CCTRL_MODE0 0x0070 +#define QSERDES_COM_PLL_CCTRL_MODE1 0x0074 +#define QSERDES_COM_PLL_CNTRL 0x0078 +#define QSERDES_COM_BIAS_EN_CTRL_BY_PSM 0x007C +#define QSERDES_COM_SYSCLK_EN_SEL 0x0080 +#define QSERDES_COM_CML_SYSCLK_SEL 0x0084 +#define QSERDES_COM_RESETSM_CNTRL 0x0088 +#define QSERDES_COM_RESETSM_CNTRL2 0x008C +#define QSERDES_COM_LOCK_CMP_EN 0x0090 +#define QSERDES_COM_LOCK_CMP_CFG 0x0094 +#define QSERDES_COM_LOCK_CMP1_MODE0 0x0098 +#define QSERDES_COM_LOCK_CMP2_MODE0 0x009C +#define QSERDES_COM_LOCK_CMP3_MODE0 0x00A0 + +#define QSERDES_COM_DEC_START_MODE0 0x00B0 +#define QSERDES_COM_DEC_START_MODE1 0x00B4 +#define QSERDES_COM_DIV_FRAC_START1_MODE0 0x00B8 +#define QSERDES_COM_DIV_FRAC_START2_MODE0 0x00BC +#define QSERDES_COM_DIV_FRAC_START3_MODE0 0x00C0 +#define QSERDES_COM_DIV_FRAC_START1_MODE1 0x00C4 +#define QSERDES_COM_DIV_FRAC_START2_MODE1 0x00C8 +#define QSERDES_COM_DIV_FRAC_START3_MODE1 0x00CC +#define QSERDES_COM_INTEGLOOP_INITVAL 0x00D0 +#define QSERDES_COM_INTEGLOOP_EN 0x00D4 +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 0x00D8 +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 0x00DC +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1 0x00E0 +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1 0x00E4 +#define QSERDES_COM_VCOCAL_DEADMAN_CTRL 0x00E8 +#define QSERDES_COM_VCO_TUNE_CTRL 0x00EC +#define QSERDES_COM_VCO_TUNE_MAP 0x00F0 + +#define QSERDES_COM_CMN_STATUS 0x0124 +#define QSERDES_COM_RESET_SM_STATUS 0x0128 + +#define QSERDES_COM_CLK_SEL 0x0138 +#define QSERDES_COM_HSCLK_SEL 0x013C + +#define QSERDES_COM_CORECLK_DIV_MODE0 0x0148 + +#define QSERDES_COM_SW_RESET 0x0150 +#define QSERDES_COM_CORE_CLK_EN 0x0154 +#define QSERDES_COM_C_READY_STATUS 0x0158 +#define QSERDES_COM_CMN_CONFIG 0x015C + +#define QSERDES_COM_SVS_MODE_CLK_SEL 0x0164 + +#define DP_PHY_PLL_POLL_SLEEP_US 500 +#define DP_PHY_PLL_POLL_TIMEOUT_US 10000 + +#define DP_VCO_RATE_8100MHZDIV1000 8100000UL +#define DP_VCO_RATE_9720MHZDIV1000 9720000UL +#define DP_VCO_RATE_10800MHZDIV1000 10800000UL + +int dp_mux_set_parent_10nm(void *context, unsigned int reg, unsigned int val) +{ + struct mdss_pll_resources *dp_res = context; + int rc; + u32 auxclk_div; + + rc = mdss_pll_resource_enable(dp_res, true); + if (rc) { + pr_err("Failed to enable mdss DP PLL resources\n"); + return rc; + } + + auxclk_div = MDSS_PLL_REG_R(dp_res->phy_base, DP_PHY_VCO_DIV); + auxclk_div &= ~0x03; /* bits 0 to 1 */ + + if (val == 0) /* mux parent index = 0 */ + auxclk_div |= 1; + else if (val == 1) /* mux parent index = 1 */ + auxclk_div |= 2; + else if (val == 2) /* mux parent index = 2 */ + auxclk_div |= 0; + + MDSS_PLL_REG_W(dp_res->phy_base, + DP_PHY_VCO_DIV, auxclk_div); + /* Make sure the PHY registers writes are done */ + wmb(); + pr_debug("%s: mux=%d auxclk_div=%x\n", __func__, val, auxclk_div); + + mdss_pll_resource_enable(dp_res, false); + + return 0; +} + +int dp_mux_get_parent_10nm(void *context, unsigned int reg, unsigned int *val) +{ + int rc; + u32 auxclk_div = 0; + struct mdss_pll_resources *dp_res = context; + + rc = mdss_pll_resource_enable(dp_res, true); + if (rc) { + pr_err("Failed to enable dp_res resources\n"); + return rc; + } + + auxclk_div = MDSS_PLL_REG_R(dp_res->phy_base, DP_PHY_VCO_DIV); + auxclk_div &= 0x03; + + if (auxclk_div == 1) /* Default divider */ + *val = 0; + else if (auxclk_div == 2) + *val = 1; + else if (auxclk_div == 0) + *val = 2; + + mdss_pll_resource_enable(dp_res, false); + + pr_debug("%s: auxclk_div=%d, val=%d\n", __func__, auxclk_div, *val); + + return 0; +} + +static int dp_vco_pll_init_db_10nm(struct dp_pll_db *pdb, + unsigned long rate) +{ + struct mdss_pll_resources *dp_res = pdb->pll; + u32 spare_value = 0; + + spare_value = MDSS_PLL_REG_R(dp_res->phy_base, DP_PHY_SPARE0); + pdb->lane_cnt = spare_value & 0x0F; + pdb->orientation = (spare_value & 0xF0) >> 4; + + pr_debug("%s: spare_value=0x%x, ln_cnt=0x%x, orientation=0x%x\n", + __func__, spare_value, pdb->lane_cnt, pdb->orientation); + + switch (rate) { + case DP_VCO_HSCLK_RATE_1620MHZDIV1000: + pr_debug("%s: VCO rate: %ld\n", __func__, + DP_VCO_RATE_9720MHZDIV1000); + pdb->hsclk_sel = 0x0c; + pdb->dec_start_mode0 = 0x69; + pdb->div_frac_start1_mode0 = 0x00; + pdb->div_frac_start2_mode0 = 0x80; + pdb->div_frac_start3_mode0 = 0x07; + pdb->integloop_gain0_mode0 = 0x3f; + pdb->integloop_gain1_mode0 = 0x00; + pdb->vco_tune_map = 0x00; + pdb->lock_cmp1_mode0 = 0x6f; + pdb->lock_cmp2_mode0 = 0x08; + pdb->lock_cmp3_mode0 = 0x00; + pdb->phy_vco_div = 0x1; + pdb->lock_cmp_en = 0x00; + break; + case DP_VCO_HSCLK_RATE_2700MHZDIV1000: + pr_debug("%s: VCO rate: %ld\n", __func__, + DP_VCO_RATE_10800MHZDIV1000); + pdb->hsclk_sel = 0x04; + pdb->dec_start_mode0 = 0x69; + pdb->div_frac_start1_mode0 = 0x00; + pdb->div_frac_start2_mode0 = 0x80; + pdb->div_frac_start3_mode0 = 0x07; + pdb->integloop_gain0_mode0 = 0x3f; + pdb->integloop_gain1_mode0 = 0x00; + pdb->vco_tune_map = 0x00; + pdb->lock_cmp1_mode0 = 0x0f; + pdb->lock_cmp2_mode0 = 0x0e; + pdb->lock_cmp3_mode0 = 0x00; + pdb->phy_vco_div = 0x1; + pdb->lock_cmp_en = 0x00; + break; + case DP_VCO_HSCLK_RATE_5400MHZDIV1000: + pr_debug("%s: VCO rate: %ld\n", __func__, + DP_VCO_RATE_10800MHZDIV1000); + pdb->hsclk_sel = 0x00; + pdb->dec_start_mode0 = 0x8c; + pdb->div_frac_start1_mode0 = 0x00; + pdb->div_frac_start2_mode0 = 0x00; + pdb->div_frac_start3_mode0 = 0x0a; + pdb->integloop_gain0_mode0 = 0x3f; + pdb->integloop_gain1_mode0 = 0x00; + pdb->vco_tune_map = 0x00; + pdb->lock_cmp1_mode0 = 0x1f; + pdb->lock_cmp2_mode0 = 0x1c; + pdb->lock_cmp3_mode0 = 0x00; + pdb->phy_vco_div = 0x2; + pdb->lock_cmp_en = 0x00; + break; + case DP_VCO_HSCLK_RATE_8100MHZDIV1000: + pr_debug("%s: VCO rate: %ld\n", __func__, + DP_VCO_RATE_8100MHZDIV1000); + pdb->hsclk_sel = 0x03; + pdb->dec_start_mode0 = 0x69; + pdb->div_frac_start1_mode0 = 0x00; + pdb->div_frac_start2_mode0 = 0x80; + pdb->div_frac_start3_mode0 = 0x07; + pdb->integloop_gain0_mode0 = 0x3f; + pdb->integloop_gain1_mode0 = 0x00; + pdb->vco_tune_map = 0x00; + pdb->lock_cmp1_mode0 = 0x2f; + pdb->lock_cmp2_mode0 = 0x2a; + pdb->lock_cmp3_mode0 = 0x00; + pdb->phy_vco_div = 0x0; + pdb->lock_cmp_en = 0x08; + break; + default: + return -EINVAL; + } + return 0; +} + +static int dp_config_vco_rate_10nm(struct dp_pll_vco_clk *vco, + unsigned long rate) +{ + u32 res = 0; + struct mdss_pll_resources *dp_res = vco->priv; + struct dp_pll_db *pdb = (struct dp_pll_db *)dp_res->priv; + + res = dp_vco_pll_init_db_10nm(pdb, rate); + if (res) { + pr_err("VCO Init DB failed\n"); + return res; + } + + if (pdb->lane_cnt != 4) { + if (pdb->orientation == ORIENTATION_CC2) + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_PD_CTL, 0x6d); + else + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_PD_CTL, 0x75); + } else { + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_PD_CTL, 0x7d); + } + + /* Make sure the PHY register writes are done */ + wmb(); + + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_SVS_MODE_CLK_SEL, 0x01); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_SYSCLK_EN_SEL, 0x37); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_SYS_CLK_CTRL, 0x02); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_CLK_ENABLE1, 0x0e); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_SYSCLK_BUF_ENABLE, 0x06); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_CLK_SEL, 0x30); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_CMN_CONFIG, 0x02); + + /* Different for each clock rates */ + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_HSCLK_SEL, pdb->hsclk_sel); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_DEC_START_MODE0, pdb->dec_start_mode0); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_DIV_FRAC_START1_MODE0, pdb->div_frac_start1_mode0); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_DIV_FRAC_START2_MODE0, pdb->div_frac_start2_mode0); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_DIV_FRAC_START3_MODE0, pdb->div_frac_start3_mode0); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_INTEGLOOP_GAIN0_MODE0, pdb->integloop_gain0_mode0); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_INTEGLOOP_GAIN1_MODE0, pdb->integloop_gain1_mode0); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_VCO_TUNE_MAP, pdb->vco_tune_map); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_LOCK_CMP1_MODE0, pdb->lock_cmp1_mode0); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_LOCK_CMP2_MODE0, pdb->lock_cmp2_mode0); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_LOCK_CMP3_MODE0, pdb->lock_cmp3_mode0); + /* Make sure the PLL register writes are done */ + wmb(); + + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_BG_TIMER, 0x0a); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_CORECLK_DIV_MODE0, 0x0a); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_VCO_TUNE_CTRL, 0x00); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3f); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_CORE_CLK_EN, 0x1f); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_PLL_IVCO, 0x07); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_LOCK_CMP_EN, pdb->lock_cmp_en); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_PLL_CCTRL_MODE0, 0x36); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_PLL_RCTRL_MODE0, 0x16); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_CP_CTRL_MODE0, 0x06); + /* Make sure the PHY register writes are done */ + wmb(); + + if (pdb->orientation == ORIENTATION_CC2) + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_MODE, 0x4c); + else + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_MODE, 0x5c); + /* Make sure the PLL register writes are done */ + wmb(); + + /* TX Lane configuration */ + MDSS_PLL_REG_W(dp_res->phy_base, + DP_PHY_TX0_TX1_LANE_CTL, 0x05); + MDSS_PLL_REG_W(dp_res->phy_base, + DP_PHY_TX2_TX3_LANE_CTL, 0x05); + + /* TX-0 register configuration */ + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_TRANSCEIVER_BIAS_EN, 0x1a); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_VMODE_CTRL1, 0x40); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_PRE_STALL_LDO_BOOST_EN, 0x30); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_INTERFACE_SELECT, 0x3d); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_CLKBUF_ENABLE, 0x0f); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_RESET_TSYNC_EN, 0x03); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_TRAN_DRVR_EMP_EN, 0x03); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, + TXn_PARRATE_REC_DETECT_IDLE_EN, 0x00); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_TX_INTERFACE_MODE, 0x00); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_TX_BAND, 0x4); + + /* TX-1 register configuration */ + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_TRANSCEIVER_BIAS_EN, 0x1a); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_VMODE_CTRL1, 0x40); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_PRE_STALL_LDO_BOOST_EN, 0x30); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_INTERFACE_SELECT, 0x3d); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_CLKBUF_ENABLE, 0x0f); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_RESET_TSYNC_EN, 0x03); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_TRAN_DRVR_EMP_EN, 0x03); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, + TXn_PARRATE_REC_DETECT_IDLE_EN, 0x00); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_TX_INTERFACE_MODE, 0x00); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_TX_BAND, 0x4); + /* Make sure the PHY register writes are done */ + wmb(); + + /* dependent on the vco frequency */ + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_VCO_DIV, pdb->phy_vco_div); + + return res; +} + +static bool dp_10nm_pll_lock_status(struct mdss_pll_resources *dp_res) +{ + u32 status; + bool pll_locked; + + /* poll for PLL lock status */ + if (readl_poll_timeout_atomic((dp_res->pll_base + + QSERDES_COM_C_READY_STATUS), + status, + ((status & BIT(0)) > 0), + DP_PHY_PLL_POLL_SLEEP_US, + DP_PHY_PLL_POLL_TIMEOUT_US)) { + pr_err("%s: C_READY status is not high. Status=%x\n", + __func__, status); + pll_locked = false; + } else { + pll_locked = true; + } + + return pll_locked; +} + +static bool dp_10nm_phy_rdy_status(struct mdss_pll_resources *dp_res) +{ + u32 status; + bool phy_ready = true; + + /* poll for PHY ready status */ + if (readl_poll_timeout_atomic((dp_res->phy_base + + DP_PHY_STATUS), + status, + ((status & (BIT(1))) > 0), + DP_PHY_PLL_POLL_SLEEP_US, + DP_PHY_PLL_POLL_TIMEOUT_US)) { + pr_err("%s: Phy_ready is not high. Status=%x\n", + __func__, status); + phy_ready = false; + } + + return phy_ready; +} + +static int dp_pll_enable_10nm(struct clk_hw *hw) +{ + int rc = 0; + struct dp_pll_vco_clk *vco = to_dp_vco_hw(hw); + struct mdss_pll_resources *dp_res = vco->priv; + struct dp_pll_db *pdb = (struct dp_pll_db *)dp_res->priv; + u32 bias_en, drvr_en; + + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_AUX_CFG2, 0x04); + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_CFG, 0x01); + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_CFG, 0x05); + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_CFG, 0x01); + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_CFG, 0x09); + wmb(); /* Make sure the PHY register writes are done */ + + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_RESETSM_CNTRL, 0x20); + wmb(); /* Make sure the PLL register writes are done */ + + if (!dp_10nm_pll_lock_status(dp_res)) { + rc = -EINVAL; + goto lock_err; + } + + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_CFG, 0x19); + /* Make sure the PHY register writes are done */ + wmb(); + /* poll for PHY ready status */ + if (!dp_10nm_phy_rdy_status(dp_res)) { + rc = -EINVAL; + goto lock_err; + } + + pr_debug("%s: PLL is locked\n", __func__); + + if (pdb->lane_cnt == 1) { + bias_en = 0x3e; + drvr_en = 0x13; + } else { + bias_en = 0x3f; + drvr_en = 0x10; + } + + if (pdb->lane_cnt != 4) { + if (pdb->orientation == ORIENTATION_CC1) { + MDSS_PLL_REG_W(dp_res->ln_tx1_base, + TXn_HIGHZ_DRVR_EN, drvr_en); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, + TXn_TRANSCEIVER_BIAS_EN, bias_en); + } else { + MDSS_PLL_REG_W(dp_res->ln_tx0_base, + TXn_HIGHZ_DRVR_EN, drvr_en); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, + TXn_TRANSCEIVER_BIAS_EN, bias_en); + } + } else { + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_HIGHZ_DRVR_EN, drvr_en); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, + TXn_TRANSCEIVER_BIAS_EN, bias_en); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_HIGHZ_DRVR_EN, drvr_en); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, + TXn_TRANSCEIVER_BIAS_EN, bias_en); + } + + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_TX_POL_INV, 0x0a); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_TX_POL_INV, 0x0a); + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_CFG, 0x18); + udelay(2000); + + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_CFG, 0x19); + + /* + * Make sure all the register writes are completed before + * doing any other operation + */ + wmb(); + + /* poll for PHY ready status */ + if (!dp_10nm_phy_rdy_status(dp_res)) { + rc = -EINVAL; + goto lock_err; + } + + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_TX_DRV_LVL, 0x38); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_TX_DRV_LVL, 0x38); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_TX_EMP_POST1_LVL, 0x20); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_TX_EMP_POST1_LVL, 0x20); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_RES_CODE_LANE_OFFSET_TX, 0x06); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_RES_CODE_LANE_OFFSET_TX, 0x06); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_RES_CODE_LANE_OFFSET_RX, 0x07); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_RES_CODE_LANE_OFFSET_RX, 0x07); + /* Make sure the PHY register writes are done */ + wmb(); + +lock_err: + return rc; +} + +static int dp_pll_disable_10nm(struct clk_hw *hw) +{ + int rc = 0; + struct dp_pll_vco_clk *vco = to_dp_vco_hw(hw); + struct mdss_pll_resources *dp_res = vco->priv; + + /* Assert DP PHY power down */ + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_PD_CTL, 0x2); + /* + * Make sure all the register writes to disable PLL are + * completed before doing any other operation + */ + wmb(); + + return rc; +} + + +int dp_vco_prepare_10nm(struct clk_hw *hw) +{ + int rc = 0; + struct dp_pll_vco_clk *vco = to_dp_vco_hw(hw); + struct mdss_pll_resources *dp_res = vco->priv; + + pr_debug("rate=%ld\n", vco->rate); + rc = mdss_pll_resource_enable(dp_res, true); + if (rc) { + pr_err("Failed to enable mdss DP pll resources\n"); + goto error; + } + + if ((dp_res->vco_cached_rate != 0) + && (dp_res->vco_cached_rate == vco->rate)) { + rc = vco->hw.init->ops->set_rate(hw, + dp_res->vco_cached_rate, dp_res->vco_cached_rate); + if (rc) { + pr_err("index=%d vco_set_rate failed. rc=%d\n", + rc, dp_res->index); + mdss_pll_resource_enable(dp_res, false); + goto error; + } + } + + rc = dp_pll_enable_10nm(hw); + if (rc) { + mdss_pll_resource_enable(dp_res, false); + pr_err("ndx=%d failed to enable dp pll\n", + dp_res->index); + goto error; + } + + mdss_pll_resource_enable(dp_res, false); +error: + return rc; +} + +void dp_vco_unprepare_10nm(struct clk_hw *hw) +{ + struct dp_pll_vco_clk *vco = to_dp_vco_hw(hw); + struct mdss_pll_resources *dp_res = vco->priv; + + if (!dp_res) { + pr_err("Invalid input parameter\n"); + return; + } + + if (!dp_res->pll_on && + mdss_pll_resource_enable(dp_res, true)) { + pr_err("pll resource can't be enabled\n"); + return; + } + dp_res->vco_cached_rate = vco->rate; + dp_pll_disable_10nm(hw); + + dp_res->handoff_resources = false; + mdss_pll_resource_enable(dp_res, false); + dp_res->pll_on = false; +} + +int dp_vco_set_rate_10nm(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct dp_pll_vco_clk *vco = to_dp_vco_hw(hw); + struct mdss_pll_resources *dp_res = vco->priv; + int rc; + + rc = mdss_pll_resource_enable(dp_res, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + pr_debug("DP lane CLK rate=%ld\n", rate); + + rc = dp_config_vco_rate_10nm(vco, rate); + if (rc) + pr_err("%s: Failed to set clk rate\n", __func__); + + mdss_pll_resource_enable(dp_res, false); + + vco->rate = rate; + + return 0; +} + +unsigned long dp_vco_recalc_rate_10nm(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct dp_pll_vco_clk *vco = to_dp_vco_hw(hw); + int rc; + u32 div, hsclk_div, link_clk_div = 0; + u64 vco_rate; + struct mdss_pll_resources *dp_res = vco->priv; + + rc = mdss_pll_resource_enable(dp_res, true); + if (rc) { + pr_err("Failed to enable mdss DP pll=%d\n", dp_res->index); + return rc; + } + + div = MDSS_PLL_REG_R(dp_res->pll_base, QSERDES_COM_HSCLK_SEL); + div &= 0x0f; + + if (div == 12) + hsclk_div = 6; /* Default */ + else if (div == 4) + hsclk_div = 4; + else if (div == 0) + hsclk_div = 2; + else if (div == 3) + hsclk_div = 1; + else { + pr_debug("unknown divider. forcing to default\n"); + hsclk_div = 5; + } + + div = MDSS_PLL_REG_R(dp_res->phy_base, DP_PHY_AUX_CFG2); + div >>= 2; + + if ((div & 0x3) == 0) + link_clk_div = 5; + else if ((div & 0x3) == 1) + link_clk_div = 10; + else if ((div & 0x3) == 2) + link_clk_div = 20; + else + pr_err("%s: unsupported div. Phy_mode: %d\n", __func__, div); + + if (link_clk_div == 20) { + vco_rate = DP_VCO_HSCLK_RATE_2700MHZDIV1000; + } else { + if (hsclk_div == 6) + vco_rate = DP_VCO_HSCLK_RATE_1620MHZDIV1000; + else if (hsclk_div == 4) + vco_rate = DP_VCO_HSCLK_RATE_2700MHZDIV1000; + else if (hsclk_div == 2) + vco_rate = DP_VCO_HSCLK_RATE_5400MHZDIV1000; + else + vco_rate = DP_VCO_HSCLK_RATE_8100MHZDIV1000; + } + + pr_debug("returning vco rate = %lu\n", (unsigned long)vco_rate); + + mdss_pll_resource_enable(dp_res, false); + + dp_res->vco_cached_rate = vco->rate = vco_rate; + return (unsigned long)vco_rate; +} + +long dp_vco_round_rate_10nm(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + unsigned long rrate = rate; + struct dp_pll_vco_clk *vco = to_dp_vco_hw(hw); + + if (rate <= vco->min_rate) + rrate = vco->min_rate; + else if (rate <= DP_VCO_HSCLK_RATE_2700MHZDIV1000) + rrate = DP_VCO_HSCLK_RATE_2700MHZDIV1000; + else if (rate <= DP_VCO_HSCLK_RATE_5400MHZDIV1000) + rrate = DP_VCO_HSCLK_RATE_5400MHZDIV1000; + else + rrate = vco->max_rate; + + pr_debug("%s: rrate=%ld\n", __func__, rrate); + + *parent_rate = rrate; + return rrate; +} + diff --git a/drivers/clk/qcom/mdss/mdss-dp-pll-10nm.c b/drivers/clk/qcom/mdss/mdss-dp-pll-10nm.c new file mode 100644 index 000000000000..e30ef8251903 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dp-pll-10nm.c @@ -0,0 +1,310 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * Display Port PLL driver block diagram for branch clocks + * + * +------------------------------+ + * | DP_VCO_CLK | + * | | + * | +-------------------+ | + * | | (DP PLL/VCO) | | + * | +---------+---------+ | + * | v | + * | +----------+-----------+ | + * | | hsclk_divsel_clk_src | | + * | +----------+-----------+ | + * +------------------------------+ + * | + * +------------<---------v------------>----------+ + * | | + * +-----v------------+ | + * | dp_link_clk_src | | + * | divsel_ten | | + * +---------+--------+ | + * | | + * | | + * v v + * Input to DISPCC block | + * for link clk, crypto clk | + * and interface clock | + * | + * | + * +--------<------------+-----------------+---<---+ + * | | | + * +-------v------+ +--------v-----+ +--------v------+ + * | vco_divided | | vco_divided | | vco_divided | + * | _clk_src | | _clk_src | | _clk_src | + * | | | | | | + * |divsel_six | | divsel_two | | divsel_four | + * +-------+------+ +-----+--------+ +--------+------+ + * | | | + * v------->----------v-------------<------v + * | + * +----------+---------+ + * | vco_divided_clk | + * | _src_mux | + * +---------+----------+ + * | + * v + * Input to DISPCC block + * for DP pixel clock + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dp-pll.h" +#include "mdss-dp-pll-10nm.h" + +static struct dp_pll_db dp_pdb; +static struct clk_ops mux_clk_ops; + +static struct regmap_config dp_pll_10nm_cfg = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x910, +}; + +static struct regmap_bus dp_pixel_mux_regmap_ops = { + .reg_write = dp_mux_set_parent_10nm, + .reg_read = dp_mux_get_parent_10nm, +}; + +/* Op structures */ +static const struct clk_ops dp_10nm_vco_clk_ops = { + .recalc_rate = dp_vco_recalc_rate_10nm, + .set_rate = dp_vco_set_rate_10nm, + .round_rate = dp_vco_round_rate_10nm, + .prepare = dp_vco_prepare_10nm, + .unprepare = dp_vco_unprepare_10nm, +}; + +static struct dp_pll_vco_clk dp_vco_clk = { + .min_rate = DP_VCO_HSCLK_RATE_1620MHZDIV1000, + .max_rate = DP_VCO_HSCLK_RATE_8100MHZDIV1000, + .hw.init = &(struct clk_init_data){ + .name = "dp_vco_clk", + .parent_names = (const char *[]){ "xo_board" }, + .num_parents = 1, + .ops = &dp_10nm_vco_clk_ops, + }, +}; + +static struct clk_fixed_factor dp_link_clk_divsel_ten = { + .div = 10, + .mult = 1, + + .hw.init = &(struct clk_init_data){ + .name = "dp_link_clk_divsel_ten", + .parent_names = + (const char *[]){ "dp_vco_clk" }, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dp_vco_divsel_two_clk_src = { + .div = 2, + .mult = 1, + + .hw.init = &(struct clk_init_data){ + .name = "dp_vco_divsel_two_clk_src", + .parent_names = + (const char *[]){ "dp_vco_clk" }, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dp_vco_divsel_four_clk_src = { + .div = 4, + .mult = 1, + + .hw.init = &(struct clk_init_data){ + .name = "dp_vco_divsel_four_clk_src", + .parent_names = + (const char *[]){ "dp_vco_clk" }, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dp_vco_divsel_six_clk_src = { + .div = 6, + .mult = 1, + + .hw.init = &(struct clk_init_data){ + .name = "dp_vco_divsel_six_clk_src", + .parent_names = + (const char *[]){ "dp_vco_clk" }, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE), + .ops = &clk_fixed_factor_ops, + }, +}; + + +static int clk_mux_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + int ret = 0; + + ret = __clk_mux_determine_rate_closest(hw, req); + if (ret) + return ret; + + /* Set the new parent of mux if there is a new valid parent */ + if (hw->clk && req->best_parent_hw->clk) + clk_set_parent(hw->clk, req->best_parent_hw->clk); + + return 0; +} + +static unsigned long mux_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk *div_clk = NULL, *vco_clk = NULL; + struct dp_pll_vco_clk *vco = NULL; + + div_clk = clk_get_parent(hw->clk); + if (!div_clk) + return 0; + + vco_clk = clk_get_parent(div_clk); + if (!vco_clk) + return 0; + + vco = to_dp_vco_hw(__clk_get_hw(vco_clk)); + if (!vco) + return 0; + + if (vco->rate == DP_VCO_HSCLK_RATE_8100MHZDIV1000) + return (vco->rate / 6); + else if (vco->rate == DP_VCO_HSCLK_RATE_5400MHZDIV1000) + return (vco->rate / 4); + else + return (vco->rate / 2); +} + +static struct clk_regmap_mux dp_vco_divided_clk_src_mux = { + .reg = 0x64, + .shift = 0, + .width = 2, + + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dp_vco_divided_clk_src_mux", + .parent_names = + (const char *[]){"dp_vco_divsel_two_clk_src", + "dp_vco_divsel_four_clk_src", + "dp_vco_divsel_six_clk_src"}, + .num_parents = 3, + .ops = &mux_clk_ops, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + }, + }, +}; + +static struct clk_hw *mdss_dp_pllcc_10nm[] = { + [DP_VCO_CLK] = &dp_vco_clk.hw, + [DP_LINK_CLK_DIVSEL_TEN] = &dp_link_clk_divsel_ten.hw, + [DP_VCO_DIVIDED_TWO_CLK_SRC] = &dp_vco_divsel_two_clk_src.hw, + [DP_VCO_DIVIDED_FOUR_CLK_SRC] = &dp_vco_divsel_four_clk_src.hw, + [DP_VCO_DIVIDED_SIX_CLK_SRC] = &dp_vco_divsel_six_clk_src.hw, + [DP_VCO_DIVIDED_CLK_SRC_MUX] = &dp_vco_divided_clk_src_mux.clkr.hw, +}; + +int dp_pll_clock_register_10nm(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = -ENOTSUPP, i = 0; + struct clk_onecell_data *clk_data; + struct clk *clk; + struct regmap *regmap; + int num_clks = ARRAY_SIZE(mdss_dp_pllcc_10nm); + + if (!pdev || !pdev->dev.of_node) { + pr_err("Invalid input parameters\n"); + return -EINVAL; + } + + if (!pll_res || !pll_res->pll_base || !pll_res->phy_base || + !pll_res->ln_tx0_base || !pll_res->ln_tx1_base) { + pr_err("%s: Invalid input parameters\n", __func__); + return -EINVAL; + } + + clk_data = devm_kzalloc(&pdev->dev, sizeof(struct clk_onecell_data), + GFP_KERNEL); + if (!clk_data) + return -ENOMEM; + + clk_data->clks = devm_kzalloc(&pdev->dev, (num_clks * + sizeof(struct clk *)), GFP_KERNEL); + if (!clk_data->clks) { + devm_kfree(&pdev->dev, clk_data); + return -ENOMEM; + } + clk_data->clk_num = num_clks; + + pll_res->priv = &dp_pdb; + dp_pdb.pll = pll_res; + + /* Set client data for vco, mux and div clocks */ + regmap = devm_regmap_init(&pdev->dev, &dp_pixel_mux_regmap_ops, + pll_res, &dp_pll_10nm_cfg); + dp_vco_divided_clk_src_mux.clkr.regmap = regmap; + mux_clk_ops = clk_regmap_mux_closest_ops; + mux_clk_ops.determine_rate = clk_mux_determine_rate; + mux_clk_ops.recalc_rate = mux_recalc_rate; + + dp_vco_clk.priv = pll_res; + + for (i = DP_VCO_CLK; i <= DP_VCO_DIVIDED_CLK_SRC_MUX; i++) { + pr_debug("reg clk: %d index: %d\n", i, pll_res->index); + clk = devm_clk_register(&pdev->dev, + mdss_dp_pllcc_10nm[i]); + if (IS_ERR(clk)) { + pr_err("clk registration failed for DP: %d\n", + pll_res->index); + rc = -EINVAL; + goto clk_reg_fail; + } + clk_data->clks[i] = clk; + } + + rc = of_clk_add_provider(pdev->dev.of_node, + of_clk_src_onecell_get, clk_data); + if (rc) { + pr_err("%s: Clock register failed rc=%d\n", __func__, rc); + rc = -EPROBE_DEFER; + } else { + pr_debug("%s SUCCESS\n", __func__); + } + return 0; +clk_reg_fail: + devm_kfree(&pdev->dev, clk_data->clks); + devm_kfree(&pdev->dev, clk_data); + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-dp-pll-10nm.h b/drivers/clk/qcom/mdss/mdss-dp-pll-10nm.h new file mode 100644 index 000000000000..c3b5635a55ae --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dp-pll-10nm.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MDSS_DP_PLL_10NM_H +#define __MDSS_DP_PLL_10NM_H + +#define DP_VCO_HSCLK_RATE_1620MHZDIV1000 1620000UL +#define DP_VCO_HSCLK_RATE_2700MHZDIV1000 2700000UL +#define DP_VCO_HSCLK_RATE_5400MHZDIV1000 5400000UL +#define DP_VCO_HSCLK_RATE_8100MHZDIV1000 8100000UL + +struct dp_pll_db { + struct mdss_pll_resources *pll; + + /* lane and orientation settings */ + u8 lane_cnt; + u8 orientation; + + /* COM PHY settings */ + u32 hsclk_sel; + u32 dec_start_mode0; + u32 div_frac_start1_mode0; + u32 div_frac_start2_mode0; + u32 div_frac_start3_mode0; + u32 integloop_gain0_mode0; + u32 integloop_gain1_mode0; + u32 vco_tune_map; + u32 lock_cmp1_mode0; + u32 lock_cmp2_mode0; + u32 lock_cmp3_mode0; + u32 lock_cmp_en; + + /* PHY vco divider */ + u32 phy_vco_div; +}; + +int dp_vco_set_rate_10nm(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate); +unsigned long dp_vco_recalc_rate_10nm(struct clk_hw *hw, + unsigned long parent_rate); +long dp_vco_round_rate_10nm(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate); +int dp_vco_prepare_10nm(struct clk_hw *hw); +void dp_vco_unprepare_10nm(struct clk_hw *hw); +int dp_mux_set_parent_10nm(void *context, + unsigned int reg, unsigned int val); +int dp_mux_get_parent_10nm(void *context, + unsigned int reg, unsigned int *val); +#endif /* __MDSS_DP_PLL_10NM_H */ diff --git a/drivers/clk/qcom/mdss/mdss-dp-pll.h b/drivers/clk/qcom/mdss/mdss-dp-pll.h new file mode 100644 index 000000000000..2b1d70ea8f2a --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dp-pll.h @@ -0,0 +1,33 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MDSS_DP_PLL_H +#define __MDSS_DP_PLL_H + +struct dp_pll_vco_clk { + struct clk_hw hw; + unsigned long rate; /* current vco rate */ + u64 min_rate; /* min vco rate */ + u64 max_rate; /* max vco rate */ + void *priv; +}; + +static inline struct dp_pll_vco_clk *to_dp_vco_hw(struct clk_hw *hw) +{ + return container_of(hw, struct dp_pll_vco_clk, hw); +} + +int dp_pll_clock_register_10nm(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); + +#endif /* __MDSS_DP_PLL_H */ diff --git a/drivers/clk/qcom/mdss/mdss-dsi-20nm-pll-util.c b/drivers/clk/qcom/mdss/mdss-dsi-20nm-pll-util.c new file mode 100644 index 000000000000..bc775b19b3d9 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-20nm-pll-util.c @@ -0,0 +1,1011 @@ +/* Copyright (c) 2014-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dsi-pll.h" + +#define MMSS_DSI_PHY_PLL_SYS_CLK_CTRL 0x0000 +#define MMSS_DSI_PHY_PLL_PLL_VCOTAIL_EN 0x0004 +#define MMSS_DSI_PHY_PLL_CMN_MODE 0x0008 +#define MMSS_DSI_PHY_PLL_IE_TRIM 0x000C +#define MMSS_DSI_PHY_PLL_IP_TRIM 0x0010 + +#define MMSS_DSI_PHY_PLL_PLL_PHSEL_CONTROL 0x0018 +#define MMSS_DSI_PHY_PLL_IPTAT_TRIM_VCCA_TX_SEL 0x001C +#define MMSS_DSI_PHY_PLL_PLL_PHSEL_DC 0x0020 +#define MMSS_DSI_PHY_PLL_PLL_IP_SETI 0x0024 +#define MMSS_DSI_PHY_PLL_CORE_CLK_IN_SYNC_SEL 0x0028 + +#define MMSS_DSI_PHY_PLL_BIAS_EN_CLKBUFLR_EN 0x0030 +#define MMSS_DSI_PHY_PLL_PLL_CP_SETI 0x0034 +#define MMSS_DSI_PHY_PLL_PLL_IP_SETP 0x0038 +#define MMSS_DSI_PHY_PLL_PLL_CP_SETP 0x003C +#define MMSS_DSI_PHY_PLL_ATB_SEL1 0x0040 +#define MMSS_DSI_PHY_PLL_ATB_SEL2 0x0044 +#define MMSS_DSI_PHY_PLL_SYSCLK_EN_SEL_TXBAND 0x0048 +#define MMSS_DSI_PHY_PLL_RESETSM_CNTRL 0x004C +#define MMSS_DSI_PHY_PLL_RESETSM_CNTRL2 0x0050 +#define MMSS_DSI_PHY_PLL_RESETSM_CNTRL3 0x0054 +#define MMSS_DSI_PHY_PLL_RESETSM_PLL_CAL_COUNT1 0x0058 +#define MMSS_DSI_PHY_PLL_RESETSM_PLL_CAL_COUNT2 0x005C +#define MMSS_DSI_PHY_PLL_DIV_REF1 0x0060 +#define MMSS_DSI_PHY_PLL_DIV_REF2 0x0064 +#define MMSS_DSI_PHY_PLL_KVCO_COUNT1 0x0068 +#define MMSS_DSI_PHY_PLL_KVCO_COUNT2 0x006C +#define MMSS_DSI_PHY_PLL_KVCO_CAL_CNTRL 0x0070 +#define MMSS_DSI_PHY_PLL_KVCO_CODE 0x0074 +#define MMSS_DSI_PHY_PLL_VREF_CFG1 0x0078 +#define MMSS_DSI_PHY_PLL_VREF_CFG2 0x007C +#define MMSS_DSI_PHY_PLL_VREF_CFG3 0x0080 +#define MMSS_DSI_PHY_PLL_VREF_CFG4 0x0084 +#define MMSS_DSI_PHY_PLL_VREF_CFG5 0x0088 +#define MMSS_DSI_PHY_PLL_VREF_CFG6 0x008C +#define MMSS_DSI_PHY_PLL_PLLLOCK_CMP1 0x0090 +#define MMSS_DSI_PHY_PLL_PLLLOCK_CMP2 0x0094 +#define MMSS_DSI_PHY_PLL_PLLLOCK_CMP3 0x0098 + +#define MMSS_DSI_PHY_PLL_BGTC 0x00A0 +#define MMSS_DSI_PHY_PLL_PLL_TEST_UPDN 0x00A4 +#define MMSS_DSI_PHY_PLL_PLL_VCO_TUNE 0x00A8 +#define MMSS_DSI_PHY_PLL_DEC_START1 0x00AC +#define MMSS_DSI_PHY_PLL_PLL_AMP_OS 0x00B0 +#define MMSS_DSI_PHY_PLL_SSC_EN_CENTER 0x00B4 +#define MMSS_DSI_PHY_PLL_SSC_ADJ_PER1 0x00B8 +#define MMSS_DSI_PHY_PLL_SSC_ADJ_PER2 0x00BC +#define MMSS_DSI_PHY_PLL_SSC_PER1 0x00C0 +#define MMSS_DSI_PHY_PLL_SSC_PER2 0x00C4 +#define MMSS_DSI_PHY_PLL_SSC_STEP_SIZE1 0x00C8 +#define MMSS_DSI_PHY_PLL_SSC_STEP_SIZE2 0x00CC +#define MMSS_DSI_PHY_PLL_RES_CODE_UP 0x00D0 +#define MMSS_DSI_PHY_PLL_RES_CODE_DN 0x00D4 +#define MMSS_DSI_PHY_PLL_RES_CODE_UP_OFFSET 0x00D8 +#define MMSS_DSI_PHY_PLL_RES_CODE_DN_OFFSET 0x00DC +#define MMSS_DSI_PHY_PLL_RES_CODE_START_SEG1 0x00E0 +#define MMSS_DSI_PHY_PLL_RES_CODE_START_SEG2 0x00E4 +#define MMSS_DSI_PHY_PLL_RES_CODE_CAL_CSR 0x00E8 +#define MMSS_DSI_PHY_PLL_RES_CODE 0x00EC +#define MMSS_DSI_PHY_PLL_RES_TRIM_CONTROL 0x00F0 +#define MMSS_DSI_PHY_PLL_RES_TRIM_CONTROL2 0x00F4 +#define MMSS_DSI_PHY_PLL_RES_TRIM_EN_VCOCALDONE 0x00F8 +#define MMSS_DSI_PHY_PLL_FAUX_EN 0x00FC + +#define MMSS_DSI_PHY_PLL_DIV_FRAC_START1 0x0100 +#define MMSS_DSI_PHY_PLL_DIV_FRAC_START2 0x0104 +#define MMSS_DSI_PHY_PLL_DIV_FRAC_START3 0x0108 +#define MMSS_DSI_PHY_PLL_DEC_START2 0x010C +#define MMSS_DSI_PHY_PLL_PLL_RXTXEPCLK_EN 0x0110 +#define MMSS_DSI_PHY_PLL_PLL_CRCTRL 0x0114 +#define MMSS_DSI_PHY_PLL_LOW_POWER_RO_CONTROL 0x013C +#define MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL 0x0140 +#define MMSS_DSI_PHY_PLL_HR_OCLK2_DIVIDER 0x0144 +#define MMSS_DSI_PHY_PLL_HR_OCLK3_DIVIDER 0x0148 +#define MMSS_DSI_PHY_PLL_PLL_VCO_HIGH 0x014C +#define MMSS_DSI_PHY_PLL_RESET_SM 0x0150 +#define MMSS_DSI_PHY_PLL_MUXVAL 0x0154 +#define MMSS_DSI_PHY_PLL_CORE_RES_CODE_DN 0x0158 +#define MMSS_DSI_PHY_PLL_CORE_RES_CODE_UP 0x015C +#define MMSS_DSI_PHY_PLL_CORE_VCO_TUNE 0x0160 +#define MMSS_DSI_PHY_PLL_CORE_VCO_TAIL 0x0164 +#define MMSS_DSI_PHY_PLL_CORE_KVCO_CODE 0x0168 + +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL0 0x014 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL1 0x018 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL2 0x01C +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL3 0x020 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL4 0x024 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL5 0x028 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL6 0x02C +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL7 0x030 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL8 0x034 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL9 0x038 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL10 0x03C +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL11 0x040 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL12 0x044 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL13 0x048 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL14 0x04C +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL15 0x050 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL16 0x054 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL17 0x058 + +#define DSI_PLL_POLL_DELAY_US 1000 +#define DSI_PLL_POLL_TIMEOUT_US 15000 + +int set_mdss_byte_mux_sel(struct mux_clk *clk, int sel) +{ + return 0; +} + +int get_mdss_byte_mux_sel(struct mux_clk *clk) +{ + return 0; +} + +int set_mdss_pixel_mux_sel(struct mux_clk *clk, int sel) +{ + return 0; +} + +int get_mdss_pixel_mux_sel(struct mux_clk *clk) +{ + return 0; +} + +static void pll_20nm_cache_trim_codes(struct mdss_pll_resources *dsi_pll_res) +{ + int rc; + + if (dsi_pll_res->reg_upd) + return; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return; + } + + dsi_pll_res->cache_pll_trim_codes[0] = + MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_CORE_KVCO_CODE); + dsi_pll_res->cache_pll_trim_codes[1] = + MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_CORE_VCO_TUNE); + + pr_debug("core_kvco_code=0x%x core_vco_turn=0x%x\n", + dsi_pll_res->cache_pll_trim_codes[0], + dsi_pll_res->cache_pll_trim_codes[1]); + + mdss_pll_resource_enable(dsi_pll_res, false); + + dsi_pll_res->reg_upd = true; +} + +static void pll_20nm_override_trim_codes(struct mdss_pll_resources *dsi_pll_res) +{ + u32 reg_data; + void __iomem *pll_base = dsi_pll_res->pll_base; + + /* + * Override mux config for all cached trim codes from + * saved config except for VCO Tune + */ + reg_data = (dsi_pll_res->cache_pll_trim_codes[0] & 0x3f) | BIT(5); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_KVCO_CODE, reg_data); + + reg_data = (dsi_pll_res->cache_pll_trim_codes[1] & 0x7f) | BIT(7); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_VCO_TUNE, reg_data); +} + + +int set_bypass_lp_div_mux_sel(struct mux_clk *clk, int sel) +{ + struct mdss_pll_resources *dsi_pll_res = clk->priv; + int reg_data; + + pr_debug("bypass_lp_div mux set to %s mode\n", + sel ? "indirect" : "direct"); + + pr_debug("POST_DIVIDER_CONTROL = 0x%x\n", + MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL)); + + reg_data = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL); + reg_data |= BIT(7); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL, + reg_data | (sel << 5)); + pr_debug("POST_DIVIDER_CONTROL = 0x%x\n", + MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL)); + + return 0; +} + +int set_shadow_bypass_lp_div_mux_sel(struct mux_clk *clk, int sel) +{ + struct mdss_pll_resources *dsi_pll_res = clk->priv; + int reg_data, rem; + + if (!dsi_pll_res->resource_enable) { + pr_err("PLL resources disabled. Dynamic fps invalid\n"); + return -EINVAL; + } + + reg_data = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL); + reg_data |= BIT(7); + + pr_debug("%d: reg_data = %x\n", __LINE__, reg_data); + + /* Repeat POST DIVIDER 2 times (4 writes)*/ + for (rem = 0; rem < 2; rem++) + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL16 + (4 * rem), + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL, + (reg_data | (sel << 5)), (reg_data | (sel << 5))); + + return 0; +} + +int get_bypass_lp_div_mux_sel(struct mux_clk *clk) +{ + int mux_mode, rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + mux_mode = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL) & BIT(5); + + pr_debug("bypass_lp_div mux mode = %s", + mux_mode ? "indirect" : "direct"); + mdss_pll_resource_enable(dsi_pll_res, false); + + return !!mux_mode; +} + +int ndiv_set_div(struct div_clk *clk, int div) +{ + int rc, reg_data; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + reg_data = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL, + reg_data | div); + + pr_debug("POST_DIVIDER_CONTROL = 0x%x\n", + MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL)); + + mdss_pll_resource_enable(dsi_pll_res, false); + return rc; +} + +int shadow_ndiv_set_div(struct div_clk *clk, int div) +{ + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (!dsi_pll_res->resource_enable) { + pr_err("PLL resources disabled. Dynamic fps invalid\n"); + return -EINVAL; + } + + pr_debug("%d div=%i\n", __LINE__, div); + + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL14, + MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL, + 0x07, (0xB | div)); + + return 0; +} + +int ndiv_get_div(struct div_clk *clk) +{ + int div = 0, rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(clk->priv, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + div = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL) & 0x0F; + + mdss_pll_resource_enable(dsi_pll_res, false); + + return div; +} + +int fixed_hr_oclk2_set_div(struct div_clk *clk, int div) +{ + int rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_HR_OCLK2_DIVIDER, + (div - 1)); + + mdss_pll_resource_enable(dsi_pll_res, false); + return rc; +} + +int shadow_fixed_hr_oclk2_set_div(struct div_clk *clk, int div) +{ + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (!dsi_pll_res->resource_enable) { + pr_err("PLL resources disabled. Dynamic fps invalid\n"); + return -EINVAL; + } + pr_debug("%d div = %d\n", __LINE__, div); + + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL5, + MMSS_DSI_PHY_PLL_HR_OCLK2_DIVIDER, + MMSS_DSI_PHY_PLL_HR_OCLK2_DIVIDER, + (div - 1), (div - 1)); + + return 0; +} + +int fixed_hr_oclk2_get_div(struct div_clk *clk) +{ + int div = 0, rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + div = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_HR_OCLK2_DIVIDER); + + mdss_pll_resource_enable(dsi_pll_res, false); + return div + 1; +} + +int hr_oclk3_set_div(struct div_clk *clk, int div) +{ + int rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + pr_debug("%d div = %d\n", __LINE__, div); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_HR_OCLK3_DIVIDER, + (div - 1)); + pr_debug("%s: HR_OCLK3_DIVIDER = 0x%x\n", __func__, + MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_HR_OCLK3_DIVIDER)); + + mdss_pll_resource_enable(dsi_pll_res, false); + return rc; +} + +int shadow_hr_oclk3_set_div(struct div_clk *clk, int div) +{ + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (!dsi_pll_res->resource_enable) { + pr_err("PLL resources disabled. Dynamic fps invalid\n"); + return -EINVAL; + } + + pr_debug("%d div = %d\n", __LINE__, div); + + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL6, + MMSS_DSI_PHY_PLL_HR_OCLK3_DIVIDER, + MMSS_DSI_PHY_PLL_HR_OCLK3_DIVIDER, + (div - 1), (div - 1)); + + return 0; +} + +int hr_oclk3_get_div(struct div_clk *clk) +{ + int div = 0, rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + div = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_HR_OCLK3_DIVIDER); + + mdss_pll_resource_enable(dsi_pll_res, false); + return div + 1; +} + +static bool pll_20nm_is_pll_locked(struct mdss_pll_resources *dsi_pll_res) +{ + u32 status; + bool pll_locked; + + /* poll for PLL ready status */ + if (readl_poll_timeout_atomic((dsi_pll_res->pll_base + + MMSS_DSI_PHY_PLL_RESET_SM), + status, + ((status & BIT(5)) > 0), + DSI_PLL_POLL_DELAY_US, + DSI_PLL_POLL_TIMEOUT_US)) { + pr_debug("DSI PLL status=%x failed to Lock\n", status); + pll_locked = false; + } else if (readl_poll_timeout_atomic((dsi_pll_res->pll_base + + MMSS_DSI_PHY_PLL_RESET_SM), + status, + ((status & BIT(6)) > 0), + DSI_PLL_POLL_DELAY_US, + DSI_PLL_POLL_TIMEOUT_US)) { + pr_debug("DSI PLL status=%x PLl not ready\n", status); + pll_locked = false; + } else { + pll_locked = true; + } + + return pll_locked; +} + +void __dsi_pll_disable(void __iomem *pll_base) +{ + if (!pll_base) { + pr_err("Invalid pll base\n"); + return; + } + pr_debug("Disabling PHY PLL for PLL_BASE=%p\n", pll_base); + + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_VCOTAIL_EN, 0x02); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, 0x06); +} + +static void pll_20nm_config_powerdown(void __iomem *pll_base) +{ + if (!pll_base) { + pr_err("Invalid pll base.\n"); + return; + } + + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_SYS_CLK_CTRL, 0x00); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_CMN_MODE, 0x01); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_VCOTAIL_EN, 0x82); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_BIAS_EN_CLKBUFLR_EN, 0x02); +} + +static int dsi_pll_enable(struct clk *c) +{ + int i, rc; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + /* Try all enable sequences until one succeeds */ + for (i = 0; i < vco->pll_en_seq_cnt; i++) { + rc = vco->pll_enable_seqs[i](dsi_pll_res); + pr_debug("DSI PLL %s after sequence #%d\n", + rc ? "unlocked" : "locked", i + 1); + if (!rc) + break; + } + /* Disable PLL1 to avoid current leakage while toggling MDSS GDSC */ + if (dsi_pll_res->pll_1_base) + pll_20nm_config_powerdown(dsi_pll_res->pll_1_base); + + if (rc) { + mdss_pll_resource_enable(dsi_pll_res, false); + pr_err("DSI PLL failed to lock\n"); + } + dsi_pll_res->pll_on = true; + + return rc; +} + +static void dsi_pll_disable(struct clk *c) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (!dsi_pll_res->pll_on && + mdss_pll_resource_enable(dsi_pll_res, true)) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return; + } + + dsi_pll_res->handoff_resources = false; + + __dsi_pll_disable(dsi_pll_res->pll_base); + + /* Disable PLL1 to avoid current leakage while toggling MDSS GDSC */ + if (dsi_pll_res->pll_1_base) + pll_20nm_config_powerdown(dsi_pll_res->pll_1_base); + + pll_20nm_config_powerdown(dsi_pll_res->pll_base); + + mdss_pll_resource_enable(dsi_pll_res, false); + + dsi_pll_res->pll_on = false; + + pr_debug("DSI PLL Disabled\n"); +} + +static void pll_20nm_config_common_block_1(void __iomem *pll_base) +{ + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_VCOTAIL_EN, 0x82); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_BIAS_EN_CLKBUFLR_EN, 0x2a); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_BIAS_EN_CLKBUFLR_EN, 0x2b); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, 0x02); +} + +static void pll_20nm_config_common_block_2(void __iomem *pll_base) +{ + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_SYS_CLK_CTRL, 0x40); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_IE_TRIM, 0x0F); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_IP_TRIM, 0x0F); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_PHSEL_CONTROL, 0x08); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_IPTAT_TRIM_VCCA_TX_SEL, 0x0E); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_BKG_KVCO_CAL_EN, 0x08); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_SYSCLK_EN_SEL_TXBAND, 0x4A); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_DIV_REF1, 0x00); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_DIV_REF2, 0x01); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_CNTRL, 0x07); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_KVCO_CAL_CNTRL, 0x1f); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_KVCO_COUNT1, 0x8A); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_VREF_CFG3, 0x10); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_SSC_EN_CENTER, 0x00); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_FAUX_EN, 0x0C); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_RXTXEPCLK_EN, 0x0a); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_LOW_POWER_RO_CONTROL, 0x0f); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_CMN_MODE, 0x00); +} + +static void pll_20nm_config_loop_bw(void __iomem *pll_base) +{ + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_IP_SETI, 0x03); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_CP_SETI, 0x3F); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_IP_SETP, 0x03); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_CP_SETP, 0x1F); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_CRCTRL, 0x77); +} + +static void pll_20nm_vco_rate_calc(struct mdss_pll_vco_calc *vco_calc, + s64 vco_clk_rate, s64 ref_clk_rate) +{ + s64 multiplier = (1 << 20); + s64 duration = 1024, pll_comp_val; + s64 dec_start_multiple, dec_start; + s32 div_frac_start; + s64 dec_start1, dec_start2; + s32 div_frac_start1, div_frac_start2, div_frac_start3; + s64 pll_plllock_cmp1, pll_plllock_cmp2, pll_plllock_cmp3; + + memset(vco_calc, 0, sizeof(*vco_calc)); + pr_debug("vco_clk_rate=%lld ref_clk_rate=%lld\n", vco_clk_rate, + ref_clk_rate); + + dec_start_multiple = div_s64(vco_clk_rate * multiplier, + 2 * ref_clk_rate); + div_s64_rem(dec_start_multiple, + multiplier, &div_frac_start); + + dec_start = div_s64(dec_start_multiple, multiplier); + dec_start1 = (dec_start & 0x7f) | BIT(7); + dec_start2 = ((dec_start & 0x80) >> 7) | BIT(1); + div_frac_start1 = (div_frac_start & 0x7f) | BIT(7); + div_frac_start2 = ((div_frac_start >> 7) & 0x7f) | BIT(7); + div_frac_start3 = ((div_frac_start >> 14) & 0x3f) | BIT(6); + pll_comp_val = (div_s64(dec_start_multiple * 2 * duration, + 10 * multiplier)) - 1; + pll_plllock_cmp1 = pll_comp_val & 0xff; + pll_plllock_cmp2 = (pll_comp_val >> 8) & 0xff; + pll_plllock_cmp3 = (pll_comp_val >> 16) & 0xff; + + pr_debug("dec_start_multiple = 0x%llx\n", dec_start_multiple); + pr_debug("dec_start = 0x%llx, div_frac_start = 0x%x\n", + dec_start, div_frac_start); + pr_debug("dec_start1 = 0x%llx, dec_start2 = 0x%llx\n", + dec_start1, dec_start2); + pr_debug("div_frac_start1 = 0x%x, div_frac_start2 = 0x%x\n", + div_frac_start1, div_frac_start2); + pr_debug("div_frac_start3 = 0x%x\n", div_frac_start3); + pr_debug("pll_comp_val = 0x%llx\n", pll_comp_val); + pr_debug("pll_plllock_cmp1 = 0x%llx, pll_plllock_cmp2 =%llx\n", + pll_plllock_cmp1, pll_plllock_cmp2); + pr_debug("pll_plllock_cmp3 = 0x%llx\n", pll_plllock_cmp3); + + /* Assign to vco struct */ + vco_calc->div_frac_start1 = div_frac_start1; + vco_calc->div_frac_start2 = div_frac_start2; + vco_calc->div_frac_start3 = div_frac_start3; + vco_calc->dec_start1 = dec_start1; + vco_calc->dec_start2 = dec_start2; + vco_calc->pll_plllock_cmp1 = pll_plllock_cmp1; + vco_calc->pll_plllock_cmp2 = pll_plllock_cmp2; + vco_calc->pll_plllock_cmp3 = pll_plllock_cmp3; +} + +static void pll_20nm_config_vco_rate(void __iomem *pll_base, + struct mdss_pll_vco_calc *vco_calc) +{ + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_DIV_FRAC_START1, + vco_calc->div_frac_start1); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_DIV_FRAC_START2, + vco_calc->div_frac_start2); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_DIV_FRAC_START3, + vco_calc->div_frac_start3); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_DEC_START1, + vco_calc->dec_start1); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_DEC_START2, + vco_calc->dec_start2); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLLLOCK_CMP1, + vco_calc->pll_plllock_cmp1); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLLLOCK_CMP2, + vco_calc->pll_plllock_cmp2); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLLLOCK_CMP3, + vco_calc->pll_plllock_cmp3); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLLLOCK_CMP_EN, 0x01); +} + +int pll_20nm_vco_set_rate(struct dsi_pll_vco_clk *vco, unsigned long rate) +{ + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + dsi_pll_res->vco_current_rate = rate; + dsi_pll_res->vco_ref_clk_rate = vco->ref_clk_rate; + + return 0; +} + +int shadow_pll_20nm_vco_set_rate(struct dsi_pll_vco_clk *vco, + unsigned long rate) +{ + struct mdss_pll_resources *dsi_pll_res = vco->priv; + struct mdss_pll_vco_calc vco_calc; + s64 vco_clk_rate = rate; + u32 rem; + + if (!dsi_pll_res->resource_enable) { + pr_err("PLL resources disabled. Dynamic fps invalid\n"); + return -EINVAL; + } + + pr_debug("req vco set rate: %lld\n", vco_clk_rate); + + pll_20nm_override_trim_codes(dsi_pll_res); + + /* div fraction, start and comp calculations */ + pll_20nm_vco_rate_calc(&vco_calc, vco_clk_rate, + dsi_pll_res->vco_ref_clk_rate); + + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL0, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL, + MMSS_DSI_PHY_PLL_PLLLOCK_CMP_EN, + 0xB1, 0); + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL1, + MMSS_DSI_PHY_PLL_PLLLOCK_CMP1, + MMSS_DSI_PHY_PLL_PLLLOCK_CMP2, + vco_calc.pll_plllock_cmp1, vco_calc.pll_plllock_cmp2); + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL2, + MMSS_DSI_PHY_PLL_PLLLOCK_CMP3, + MMSS_DSI_PHY_PLL_DEC_START1, + vco_calc.pll_plllock_cmp3, vco_calc.dec_start1); + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL3, + MMSS_DSI_PHY_PLL_DEC_START2, + MMSS_DSI_PHY_PLL_DIV_FRAC_START1, + vco_calc.dec_start2, vco_calc.div_frac_start1); + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL4, + MMSS_DSI_PHY_PLL_DIV_FRAC_START2, + MMSS_DSI_PHY_PLL_DIV_FRAC_START3, + vco_calc.div_frac_start2, vco_calc.div_frac_start3); + /* Method 2 - Auto PLL calibration */ + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL7, + MMSS_DSI_PHY_PLL_PLL_VCO_TUNE, + MMSS_DSI_PHY_PLL_PLLLOCK_CMP_EN, + 0, 0x0D); + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL8, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL, + MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, + 0xF0, 0x07); + + /* + * RESETSM_CTRL3 has to be set for 12 times (6 reg writes), + * Each register setting write 2 times, running in loop for 5 + * times (5 reg writes) and other two iterations are taken + * care (one above and other in shadow_bypass + */ + for (rem = 0; rem < 5; rem++) { + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL9 + (4 * rem), + MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, + MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, + 0x07, 0x07); + } + + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL15, + MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, + MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, + 0x03, 0x03); + + /* memory barrier */ + wmb(); + return 0; +} + +unsigned long pll_20nm_vco_get_rate(struct clk *c) +{ + u64 vco_rate, multiplier = (1 << 20); + s32 div_frac_start; + u32 dec_start; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + u64 ref_clk = vco->ref_clk_rate; + int rc; + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + dec_start = (MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_DEC_START2) & BIT(0)) << 7; + dec_start |= (MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_DEC_START1) & 0x7f); + pr_debug("dec_start = 0x%x\n", dec_start); + + div_frac_start = (MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_DIV_FRAC_START3) & 0x3f) << 14; + div_frac_start |= (MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_DIV_FRAC_START2) & 0x7f) << 7; + div_frac_start |= MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_DIV_FRAC_START1) & 0x7f; + pr_debug("div_frac_start = 0x%x\n", div_frac_start); + + vco_rate = ref_clk * 2 * dec_start; + vco_rate += ((ref_clk * 2 * div_frac_start) / multiplier); + pr_debug("returning vco rate = %lu\n", (unsigned long)vco_rate); + + mdss_pll_resource_enable(dsi_pll_res, false); + + return (unsigned long)vco_rate; +} +long pll_20nm_vco_round_rate(struct clk *c, unsigned long rate) +{ + unsigned long rrate = rate; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + + if (rate < vco->min_rate) + rrate = vco->min_rate; + if (rate > vco->max_rate) + rrate = vco->max_rate; + + return rrate; +} + +enum handoff pll_20nm_vco_handoff(struct clk *c) +{ + int rc; + enum handoff ret = HANDOFF_DISABLED_CLK; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return HANDOFF_DISABLED_CLK; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return ret; + } + + if (pll_20nm_is_pll_locked(dsi_pll_res)) { + dsi_pll_res->handoff_resources = true; + dsi_pll_res->pll_on = true; + c->rate = pll_20nm_vco_get_rate(c); + ret = HANDOFF_ENABLED_CLK; + dsi_pll_res->vco_locking_rate = c->rate; + dsi_pll_res->is_init_locked = true; + pll_20nm_cache_trim_codes(dsi_pll_res); + pr_debug("handoff vco_locking_rate=%llu\n", + dsi_pll_res->vco_locking_rate); + } else { + mdss_pll_resource_enable(dsi_pll_res, false); + dsi_pll_res->vco_locking_rate = 0; + dsi_pll_res->is_init_locked = false; + } + + return ret; +} + +int pll_20nm_vco_prepare(struct clk *c) +{ + int rc = 0; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (!dsi_pll_res) { + pr_err("Dsi pll resources are not available\n"); + return -EINVAL; + } + + if ((dsi_pll_res->vco_cached_rate != 0) + && (dsi_pll_res->vco_cached_rate == c->rate)) { + rc = c->ops->set_rate(c, dsi_pll_res->vco_cached_rate); + if (rc) { + pr_err("vco_set_rate failed. rc=%d\n", rc); + goto error; + } + } + + rc = dsi_pll_enable(c); + +error: + return rc; +} + +void pll_20nm_vco_unprepare(struct clk *c) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (!dsi_pll_res) { + pr_err("Dsi pll resources are not available\n"); + return; + } + + dsi_pll_res->vco_cached_rate = c->rate; + dsi_pll_disable(c); +} + +static void pll_20nm_config_resetsm(void __iomem *pll_base) +{ + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_RESETSM_CNTRL, 0x24); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_RESETSM_CNTRL2, 0x07); +} + +static void pll_20nm_config_vco_start(void __iomem *pll_base) +{ + + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_VCOTAIL_EN, 0x03); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, 0x02); + udelay(10); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, 0x03); +} + +static void pll_20nm_config_bypass_cal(void __iomem *pll_base) +{ + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_RESETSM_CNTRL, 0xac); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_BKG_KVCO_CAL_EN, 0x28); +} + +static int pll_20nm_vco_relock(struct mdss_pll_resources *dsi_pll_res) +{ + int rc = 0; + + pll_20nm_override_trim_codes(dsi_pll_res); + pll_20nm_config_bypass_cal(dsi_pll_res->pll_base); + pll_20nm_config_vco_start(dsi_pll_res->pll_base); + + if (!pll_20nm_is_pll_locked(dsi_pll_res)) { + pr_err("DSI PLL re-lock failed\n"); + rc = -EINVAL; + } + + return rc; +} + +static int pll_20nm_vco_init_lock(struct mdss_pll_resources *dsi_pll_res) +{ + int rc = 0; + + pll_20nm_config_resetsm(dsi_pll_res->pll_base); + pll_20nm_config_vco_start(dsi_pll_res->pll_base); + + if (!pll_20nm_is_pll_locked(dsi_pll_res)) { + pr_err("DSI PLL init lock failed\n"); + rc = -EINVAL; + goto init_lock_err; + } + + pll_20nm_cache_trim_codes(dsi_pll_res); + +init_lock_err: + return rc; +} + +int pll_20nm_vco_enable_seq(struct mdss_pll_resources *dsi_pll_res) +{ + int rc = 0; + struct mdss_pll_vco_calc vco_calc; + + if (!dsi_pll_res) { + pr_err("Invalid PLL resources\n"); + return -EINVAL; + } + + pll_20nm_config_common_block_1(dsi_pll_res->pll_1_base); + pll_20nm_config_common_block_1(dsi_pll_res->pll_base); + pll_20nm_config_common_block_2(dsi_pll_res->pll_base); + pll_20nm_config_loop_bw(dsi_pll_res->pll_base); + + pll_20nm_vco_rate_calc(&vco_calc, dsi_pll_res->vco_current_rate, + dsi_pll_res->vco_ref_clk_rate); + pll_20nm_config_vco_rate(dsi_pll_res->pll_base, &vco_calc); + + pr_debug("init lock=%d prev vco_rate=%llu, new vco_rate=%llu\n", + dsi_pll_res->is_init_locked, dsi_pll_res->vco_locking_rate, + dsi_pll_res->vco_current_rate); + /* + * Run auto-lock sequence if it is either bootup initial + * locking or when the vco rate is changed. Otherwise, just + * use stored codes and bypass caliberation. + */ + if (!dsi_pll_res->is_init_locked || (dsi_pll_res->vco_locking_rate != + dsi_pll_res->vco_current_rate)) { + rc = pll_20nm_vco_init_lock(dsi_pll_res); + dsi_pll_res->is_init_locked = (rc) ? false : true; + } else { + rc = pll_20nm_vco_relock(dsi_pll_res); + } + + dsi_pll_res->vco_locking_rate = (rc) ? 0 : + dsi_pll_res->vco_current_rate; + + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-10nm.c b/drivers/clk/qcom/mdss/mdss-dsi-pll-10nm.c new file mode 100644 index 000000000000..1cd6c9cb8e17 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-10nm.c @@ -0,0 +1,1519 @@ +/* + * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include "mdss-dsi-pll.h" +#include "mdss-pll.h" +#include + +#define VCO_DELAY_USEC 1 + +#define MHZ_250 250000000UL +#define MHZ_500 500000000UL +#define MHZ_1000 1000000000UL +#define MHZ_1100 1100000000UL +#define MHZ_1900 1900000000UL +#define MHZ_3000 3000000000UL + +/* Register Offsets from PLL base address */ +#define PLL_ANALOG_CONTROLS_ONE 0x000 +#define PLL_ANALOG_CONTROLS_TWO 0x004 +#define PLL_ANALOG_CONTROLS_THREE 0x010 +#define PLL_DSM_DIVIDER 0x01c +#define PLL_FEEDBACK_DIVIDER 0x020 +#define PLL_SYSTEM_MUXES 0x024 +#define PLL_CMODE 0x02c +#define PLL_CALIBRATION_SETTINGS 0x030 +#define PLL_BAND_SEL_CAL_SETTINGS_THREE 0x054 +#define PLL_FREQ_DETECT_SETTINGS_ONE 0x064 +#define PLL_PFILT 0x07c +#define PLL_IFILT 0x080 +#define PLL_OUTDIV 0x094 +#define PLL_CORE_OVERRIDE 0x0a4 +#define PLL_CORE_INPUT_OVERRIDE 0x0a8 +#define PLL_PLL_DIGITAL_TIMERS_TWO 0x0b4 +#define PLL_DECIMAL_DIV_START_1 0x0cc +#define PLL_FRAC_DIV_START_LOW_1 0x0d0 +#define PLL_FRAC_DIV_START_MID_1 0x0d4 +#define PLL_FRAC_DIV_START_HIGH_1 0x0d8 +#define PLL_SSC_STEPSIZE_LOW_1 0x10c +#define PLL_SSC_STEPSIZE_HIGH_1 0x110 +#define PLL_SSC_DIV_PER_LOW_1 0x114 +#define PLL_SSC_DIV_PER_HIGH_1 0x118 +#define PLL_SSC_DIV_ADJPER_LOW_1 0x11c +#define PLL_SSC_DIV_ADJPER_HIGH_1 0x120 +#define PLL_SSC_CONTROL 0x13c +#define PLL_PLL_OUTDIV_RATE 0x140 +#define PLL_PLL_LOCKDET_RATE_1 0x144 +#define PLL_PLL_PROP_GAIN_RATE_1 0x14c +#define PLL_PLL_BAND_SET_RATE_1 0x154 +#define PLL_PLL_INT_GAIN_IFILT_BAND_1 0x15c +#define PLL_PLL_FL_INT_GAIN_PFILT_BAND_1 0x164 +#define PLL_PLL_LOCK_OVERRIDE 0x180 +#define PLL_PLL_LOCK_DELAY 0x184 +#define PLL_CLOCK_INVERTERS 0x18c +#define PLL_COMMON_STATUS_ONE 0x1a0 + +/* Register Offsets from PHY base address */ +#define PHY_CMN_CLK_CFG0 0x010 +#define PHY_CMN_CLK_CFG1 0x014 +#define PHY_CMN_RBUF_CTRL 0x01c +#define PHY_CMN_PLL_CNTRL 0x038 +#define PHY_CMN_CTRL_0 0x024 + +/* Bit definition of SSC control registers */ +#define SSC_CENTER BIT(0) +#define SSC_EN BIT(1) +#define SSC_FREQ_UPDATE BIT(2) +#define SSC_FREQ_UPDATE_MUX BIT(3) +#define SSC_UPDATE_SSC BIT(4) +#define SSC_UPDATE_SSC_MUX BIT(5) +#define SSC_START BIT(6) +#define SSC_START_MUX BIT(7) + +enum { + DSI_PLL_0, + DSI_PLL_1, + DSI_PLL_MAX +}; + +struct dsi_pll_regs { + u32 pll_prop_gain_rate; + u32 pll_lockdet_rate; + u32 decimal_div_start; + u32 frac_div_start_low; + u32 frac_div_start_mid; + u32 frac_div_start_high; + u32 pll_clock_inverters; + u32 ssc_stepsize_low; + u32 ssc_stepsize_high; + u32 ssc_div_per_low; + u32 ssc_div_per_high; + u32 ssc_adjper_low; + u32 ssc_adjper_high; + u32 ssc_control; +}; + +struct dsi_pll_config { + u32 ref_freq; + bool div_override; + u32 output_div; + bool ignore_frac; + bool disable_prescaler; + bool enable_ssc; + bool ssc_center; + u32 dec_bits; + u32 frac_bits; + u32 lock_timer; + u32 ssc_freq; + u32 ssc_offset; + u32 ssc_adj_per; + u32 thresh_cycles; + u32 refclk_cycles; +}; + +struct dsi_pll_10nm { + struct mdss_pll_resources *rsc; + struct dsi_pll_config pll_configuration; + struct dsi_pll_regs reg_setup; +}; + +static inline int pll_reg_read(void *context, unsigned int reg, + unsigned int *val) +{ + int rc = 0; + struct mdss_pll_resources *rsc = context; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + *val = MDSS_PLL_REG_R(rsc->pll_base, reg); + (void)mdss_pll_resource_enable(rsc, false); + + return rc; +} + +static inline int pll_reg_write(void *context, unsigned int reg, + unsigned int val) +{ + int rc = 0; + struct mdss_pll_resources *rsc = context; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + MDSS_PLL_REG_W(rsc->pll_base, reg, val); + (void)mdss_pll_resource_enable(rsc, false); + + return rc; +} + +static inline int phy_reg_read(void *context, unsigned int reg, + unsigned int *val) +{ + int rc = 0; + struct mdss_pll_resources *rsc = context; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + *val = MDSS_PLL_REG_R(rsc->phy_base, reg); + (void)mdss_pll_resource_enable(rsc, false); + + return rc; +} + +static inline int phy_reg_write(void *context, unsigned int reg, + unsigned int val) +{ + int rc = 0; + struct mdss_pll_resources *rsc = context; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + MDSS_PLL_REG_W(rsc->phy_base, reg, val); + (void)mdss_pll_resource_enable(rsc, false); + + return rc; +} + +static inline int phy_reg_update_bits_sub(struct mdss_pll_resources *rsc, + unsigned int reg, unsigned int mask, unsigned int val) +{ + u32 reg_val; + int rc = 0; + + reg_val = MDSS_PLL_REG_R(rsc->phy_base, reg); + reg_val &= ~mask; + reg_val |= (val & mask); + MDSS_PLL_REG_W(rsc->phy_base, reg, reg_val); + + return rc; +} + +static inline int phy_reg_update_bits(void *context, unsigned int reg, + unsigned int mask, unsigned int val) +{ + int rc = 0; + struct mdss_pll_resources *rsc = context; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + rc = phy_reg_update_bits_sub(rsc, reg, mask, val); + if (!rc && rsc->slave) + rc = phy_reg_update_bits_sub(rsc->slave, reg, mask, val); + (void)mdss_pll_resource_enable(rsc, false); + + return rc; +} + +static inline int pclk_mux_read_sel(void *context, unsigned int reg, + unsigned int *val) +{ + int rc = 0; + struct mdss_pll_resources *rsc = context; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + else + *val = (MDSS_PLL_REG_R(rsc->pll_base, reg) & 0x3); + + (void)mdss_pll_resource_enable(rsc, false); + return rc; +} + + +static inline int pclk_mux_write_sel_sub(struct mdss_pll_resources *rsc, + unsigned int reg, unsigned int val) +{ + u32 reg_val; + int rc = 0; + + reg_val = MDSS_PLL_REG_R(rsc->phy_base, reg); + reg_val &= ~0x03; + reg_val |= val; + + MDSS_PLL_REG_W(rsc->phy_base, reg, reg_val); + + return rc; +} + +static inline int pclk_mux_write_sel(void *context, unsigned int reg, + unsigned int val) +{ + int rc = 0; + struct mdss_pll_resources *rsc = context; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + rc = pclk_mux_write_sel_sub(rsc, reg, val); + if (!rc && rsc->slave) + rc = pclk_mux_write_sel_sub(rsc->slave, reg, val); + + (void)mdss_pll_resource_enable(rsc, false); + + return rc; +} + +static struct mdss_pll_resources *pll_rsc_db[DSI_PLL_MAX]; +static struct dsi_pll_10nm plls[DSI_PLL_MAX]; + +static void dsi_pll_config_slave(struct mdss_pll_resources *rsc) +{ + u32 reg; + struct mdss_pll_resources *orsc = pll_rsc_db[DSI_PLL_1]; + + if (!rsc) + return; + + /* Only DSI PLL0 can act as a master */ + if (rsc->index != DSI_PLL_0) + return; + + /* default configuration: source is either internal or ref clock */ + rsc->slave = NULL; + + if (!orsc) { + pr_warn("slave PLL unavilable, assuming standalone config\n"); + return; + } + + /* check to see if the source of DSI1 PLL bitclk is set to external */ + reg = MDSS_PLL_REG_R(orsc->phy_base, PHY_CMN_CLK_CFG1); + reg &= (BIT(2) | BIT(3)); + if (reg == 0x04) + rsc->slave = pll_rsc_db[DSI_PLL_1]; /* external source */ + + pr_debug("Slave PLL %s\n", rsc->slave ? "configured" : "absent"); +} + +static void dsi_pll_setup_config(struct dsi_pll_10nm *pll, + struct mdss_pll_resources *rsc) +{ + struct dsi_pll_config *config = &pll->pll_configuration; + + config->ref_freq = 19200000; + config->output_div = 1; + config->dec_bits = 8; + config->frac_bits = 18; + config->lock_timer = 64; + config->ssc_freq = 31500; + config->ssc_offset = 5000; + config->ssc_adj_per = 2; + config->thresh_cycles = 32; + config->refclk_cycles = 256; + + config->div_override = false; + config->ignore_frac = false; + config->disable_prescaler = false; + config->enable_ssc = rsc->ssc_en; + config->ssc_center = rsc->ssc_center; + + if (config->enable_ssc) { + if (rsc->ssc_freq) + config->ssc_freq = rsc->ssc_freq; + if (rsc->ssc_ppm) + config->ssc_offset = rsc->ssc_ppm; + } + + dsi_pll_config_slave(rsc); +} + +static void dsi_pll_calc_dec_frac(struct dsi_pll_10nm *pll, + struct mdss_pll_resources *rsc) +{ + struct dsi_pll_config *config = &pll->pll_configuration; + struct dsi_pll_regs *regs = &pll->reg_setup; + u64 fref = rsc->vco_ref_clk_rate; + u64 pll_freq; + u64 divider; + u64 dec, dec_multiple; + u32 frac; + u64 multiplier; + + pll_freq = rsc->vco_current_rate; + + if (config->disable_prescaler) + divider = fref; + else + divider = fref * 2; + + multiplier = 1 << config->frac_bits; + dec_multiple = div_u64(pll_freq * multiplier, divider); + div_u64_rem(dec_multiple, multiplier, &frac); + + dec = div_u64(dec_multiple, multiplier); + + if (pll_freq <= MHZ_1900) + regs->pll_prop_gain_rate = 8; + else if (pll_freq <= MHZ_3000) + regs->pll_prop_gain_rate = 10; + else + regs->pll_prop_gain_rate = 12; + if (pll_freq < MHZ_1100) + regs->pll_clock_inverters = 8; + else + regs->pll_clock_inverters = 0; + + regs->pll_lockdet_rate = config->lock_timer; + regs->decimal_div_start = dec; + regs->frac_div_start_low = (frac & 0xff); + regs->frac_div_start_mid = (frac & 0xff00) >> 8; + regs->frac_div_start_high = (frac & 0x30000) >> 16; +} + +static void dsi_pll_calc_ssc(struct dsi_pll_10nm *pll, + struct mdss_pll_resources *rsc) +{ + struct dsi_pll_config *config = &pll->pll_configuration; + struct dsi_pll_regs *regs = &pll->reg_setup; + u32 ssc_per; + u32 ssc_mod; + u64 ssc_step_size; + u64 frac; + + if (!config->enable_ssc) { + pr_debug("SSC not enabled\n"); + return; + } + + ssc_per = DIV_ROUND_CLOSEST(config->ref_freq, config->ssc_freq) / 2 - 1; + ssc_mod = (ssc_per + 1) % (config->ssc_adj_per + 1); + ssc_per -= ssc_mod; + + frac = regs->frac_div_start_low | + (regs->frac_div_start_mid << 8) | + (regs->frac_div_start_high << 16); + ssc_step_size = regs->decimal_div_start; + ssc_step_size *= (1 << config->frac_bits); + ssc_step_size += frac; + ssc_step_size *= config->ssc_offset; + ssc_step_size *= (config->ssc_adj_per + 1); + ssc_step_size = div_u64(ssc_step_size, (ssc_per + 1)); + ssc_step_size = DIV_ROUND_CLOSEST_ULL(ssc_step_size, 1000000); + + regs->ssc_div_per_low = ssc_per & 0xFF; + regs->ssc_div_per_high = (ssc_per & 0xFF00) >> 8; + regs->ssc_stepsize_low = (u32)(ssc_step_size & 0xFF); + regs->ssc_stepsize_high = (u32)((ssc_step_size & 0xFF00) >> 8); + regs->ssc_adjper_low = config->ssc_adj_per & 0xFF; + regs->ssc_adjper_high = (config->ssc_adj_per & 0xFF00) >> 8; + + regs->ssc_control = config->ssc_center ? SSC_CENTER : 0; + + pr_debug("SCC: Dec:%d, frac:%llu, frac_bits:%d\n", + regs->decimal_div_start, frac, config->frac_bits); + pr_debug("SSC: div_per:0x%X, stepsize:0x%X, adjper:0x%X\n", + ssc_per, (u32)ssc_step_size, config->ssc_adj_per); +} + +static void dsi_pll_ssc_commit(struct dsi_pll_10nm *pll, + struct mdss_pll_resources *rsc) +{ + void __iomem *pll_base = rsc->pll_base; + struct dsi_pll_regs *regs = &pll->reg_setup; + + if (pll->pll_configuration.enable_ssc) { + pr_debug("SSC is enabled\n"); + + MDSS_PLL_REG_W(pll_base, PLL_SSC_STEPSIZE_LOW_1, + regs->ssc_stepsize_low); + MDSS_PLL_REG_W(pll_base, PLL_SSC_STEPSIZE_HIGH_1, + regs->ssc_stepsize_high); + MDSS_PLL_REG_W(pll_base, PLL_SSC_DIV_PER_LOW_1, + regs->ssc_div_per_low); + MDSS_PLL_REG_W(pll_base, PLL_SSC_DIV_PER_HIGH_1, + regs->ssc_div_per_high); + MDSS_PLL_REG_W(pll_base, PLL_SSC_DIV_ADJPER_LOW_1, + regs->ssc_adjper_low); + MDSS_PLL_REG_W(pll_base, PLL_SSC_DIV_ADJPER_HIGH_1, + regs->ssc_adjper_high); + MDSS_PLL_REG_W(pll_base, PLL_SSC_CONTROL, + SSC_EN | regs->ssc_control); + } +} + +static void dsi_pll_config_hzindep_reg(struct dsi_pll_10nm *pll, + struct mdss_pll_resources *rsc) +{ + void __iomem *pll_base = rsc->pll_base; + + MDSS_PLL_REG_W(pll_base, PLL_ANALOG_CONTROLS_ONE, 0x80); + MDSS_PLL_REG_W(pll_base, PLL_ANALOG_CONTROLS_TWO, 0x03); + MDSS_PLL_REG_W(pll_base, PLL_ANALOG_CONTROLS_THREE, 0x00); + MDSS_PLL_REG_W(pll_base, PLL_DSM_DIVIDER, 0x00); + MDSS_PLL_REG_W(pll_base, PLL_FEEDBACK_DIVIDER, 0x4e); + MDSS_PLL_REG_W(pll_base, PLL_CALIBRATION_SETTINGS, 0x40); + MDSS_PLL_REG_W(pll_base, PLL_BAND_SEL_CAL_SETTINGS_THREE, 0xba); + MDSS_PLL_REG_W(pll_base, PLL_FREQ_DETECT_SETTINGS_ONE, 0x0c); + MDSS_PLL_REG_W(pll_base, PLL_OUTDIV, 0x00); + MDSS_PLL_REG_W(pll_base, PLL_CORE_OVERRIDE, 0x00); + MDSS_PLL_REG_W(pll_base, PLL_PLL_DIGITAL_TIMERS_TWO, 0x08); + MDSS_PLL_REG_W(pll_base, PLL_PLL_PROP_GAIN_RATE_1, 0x08); + MDSS_PLL_REG_W(pll_base, PLL_PLL_BAND_SET_RATE_1, 0xc0); + MDSS_PLL_REG_W(pll_base, PLL_PLL_INT_GAIN_IFILT_BAND_1, 0xfa); + MDSS_PLL_REG_W(pll_base, PLL_PLL_FL_INT_GAIN_PFILT_BAND_1, 0x4c); + MDSS_PLL_REG_W(pll_base, PLL_PLL_LOCK_OVERRIDE, 0x80); + MDSS_PLL_REG_W(pll_base, PLL_PFILT, 0x29); + MDSS_PLL_REG_W(pll_base, PLL_IFILT, 0x3f); +} + +static void dsi_pll_commit(struct dsi_pll_10nm *pll, + struct mdss_pll_resources *rsc) +{ + void __iomem *pll_base = rsc->pll_base; + struct dsi_pll_regs *reg = &pll->reg_setup; + + MDSS_PLL_REG_W(pll_base, PLL_CORE_INPUT_OVERRIDE, 0x12); + MDSS_PLL_REG_W(pll_base, PLL_DECIMAL_DIV_START_1, + reg->decimal_div_start); + MDSS_PLL_REG_W(pll_base, PLL_FRAC_DIV_START_LOW_1, + reg->frac_div_start_low); + MDSS_PLL_REG_W(pll_base, PLL_FRAC_DIV_START_MID_1, + reg->frac_div_start_mid); + MDSS_PLL_REG_W(pll_base, PLL_FRAC_DIV_START_HIGH_1, + reg->frac_div_start_high); + MDSS_PLL_REG_W(pll_base, PLL_PLL_LOCKDET_RATE_1, 0x40); + MDSS_PLL_REG_W(pll_base, PLL_PLL_LOCK_DELAY, 0x06); + MDSS_PLL_REG_W(pll_base, PLL_CMODE, 0x10); + MDSS_PLL_REG_W(pll_base, PLL_CLOCK_INVERTERS, reg->pll_clock_inverters); + +} + +static int vco_10nm_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + int rc; + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + struct mdss_pll_resources *rsc = vco->priv; + struct dsi_pll_10nm *pll; + + if (!rsc) { + pr_err("pll resource not found\n"); + return -EINVAL; + } + + if (rsc->pll_on) + return 0; + + pll = rsc->priv; + if (!pll) { + pr_err("pll configuration not found\n"); + return -EINVAL; + } + + pr_debug("ndx=%d, rate=%lu\n", rsc->index, rate); + + rsc->vco_current_rate = rate; + rsc->vco_ref_clk_rate = vco->ref_clk_rate; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("failed to enable mdss dsi pll(%d), rc=%d\n", + rsc->index, rc); + return rc; + } + + dsi_pll_setup_config(pll, rsc); + + dsi_pll_calc_dec_frac(pll, rsc); + + dsi_pll_calc_ssc(pll, rsc); + + dsi_pll_commit(pll, rsc); + + dsi_pll_config_hzindep_reg(pll, rsc); + + dsi_pll_ssc_commit(pll, rsc); + + /* flush, ensure all register writes are done*/ + wmb(); + + mdss_pll_resource_enable(rsc, false); + + return 0; +} + +static int dsi_pll_10nm_lock_status(struct mdss_pll_resources *pll) +{ + int rc; + u32 status; + u32 const delay_us = 100; + u32 const timeout_us = 5000; + + rc = readl_poll_timeout_atomic(pll->pll_base + PLL_COMMON_STATUS_ONE, + status, + ((status & BIT(0)) > 0), + delay_us, + timeout_us); + if (rc) + pr_err("DSI PLL(%d) lock failed, status=0x%08x\n", + pll->index, status); + + return rc; +} + +static void dsi_pll_disable_pll_bias(struct mdss_pll_resources *rsc) +{ + u32 data = MDSS_PLL_REG_R(rsc->phy_base, PHY_CMN_CTRL_0); + + MDSS_PLL_REG_W(rsc->pll_base, PLL_SYSTEM_MUXES, 0); + MDSS_PLL_REG_W(rsc->phy_base, PHY_CMN_CTRL_0, data & ~BIT(5)); + ndelay(250); +} + +static void dsi_pll_enable_pll_bias(struct mdss_pll_resources *rsc) +{ + u32 data = MDSS_PLL_REG_R(rsc->phy_base, PHY_CMN_CTRL_0); + + MDSS_PLL_REG_W(rsc->phy_base, PHY_CMN_CTRL_0, data | BIT(5)); + MDSS_PLL_REG_W(rsc->pll_base, PLL_SYSTEM_MUXES, 0xc0); + ndelay(250); +} + +static void dsi_pll_disable_global_clk(struct mdss_pll_resources *rsc) +{ + u32 data; + + data = MDSS_PLL_REG_R(rsc->phy_base, PHY_CMN_CLK_CFG1); + MDSS_PLL_REG_W(rsc->phy_base, PHY_CMN_CLK_CFG1, (data & ~BIT(5))); +} + +static void dsi_pll_enable_global_clk(struct mdss_pll_resources *rsc) +{ + u32 data; + + data = MDSS_PLL_REG_R(rsc->phy_base, PHY_CMN_CLK_CFG1); + MDSS_PLL_REG_W(rsc->phy_base, PHY_CMN_CLK_CFG1, (data | BIT(5))); +} + +static int dsi_pll_enable(struct dsi_pll_vco_clk *vco) +{ + int rc; + struct mdss_pll_resources *rsc = vco->priv; + + dsi_pll_enable_pll_bias(rsc); + if (rsc->slave) + dsi_pll_enable_pll_bias(rsc->slave); + + /* Start PLL */ + MDSS_PLL_REG_W(rsc->phy_base, PHY_CMN_PLL_CNTRL, 0x01); + + /* + * ensure all PLL configurations are written prior to checking + * for PLL lock. + */ + wmb(); + + /* Check for PLL lock */ + rc = dsi_pll_10nm_lock_status(rsc); + if (rc) { + pr_err("PLL(%d) lock failed\n", rsc->index); + goto error; + } + + rsc->pll_on = true; + + dsi_pll_enable_global_clk(rsc); + if (rsc->slave) + dsi_pll_enable_global_clk(rsc->slave); + + MDSS_PLL_REG_W(rsc->phy_base, PHY_CMN_RBUF_CTRL, 0x01); + if (rsc->slave) + MDSS_PLL_REG_W(rsc->slave->phy_base, PHY_CMN_RBUF_CTRL, 0x01); + +error: + return rc; +} + +static void dsi_pll_disable_sub(struct mdss_pll_resources *rsc) +{ + MDSS_PLL_REG_W(rsc->phy_base, PHY_CMN_RBUF_CTRL, 0); + dsi_pll_disable_pll_bias(rsc); +} + +static void dsi_pll_disable(struct dsi_pll_vco_clk *vco) +{ + struct mdss_pll_resources *rsc = vco->priv; + + if (!rsc->pll_on && + mdss_pll_resource_enable(rsc, true)) { + pr_err("failed to enable pll (%d) resources\n", rsc->index); + return; + } + + rsc->handoff_resources = false; + + pr_debug("stop PLL (%d)\n", rsc->index); + + /* + * To avoid any stray glitches while + * abruptly powering down the PLL + * make sure to gate the clock using + * the clock enable bit before powering + * down the PLL + */ + dsi_pll_disable_global_clk(rsc); + MDSS_PLL_REG_W(rsc->phy_base, PHY_CMN_PLL_CNTRL, 0); + dsi_pll_disable_sub(rsc); + if (rsc->slave) { + dsi_pll_disable_global_clk(rsc->slave); + dsi_pll_disable_sub(rsc->slave); + } + /* flush, ensure all register writes are done*/ + wmb(); + rsc->pll_on = false; +} + +long vco_10nm_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + unsigned long rrate = rate; + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + + if (rate < vco->min_rate) + rrate = vco->min_rate; + if (rate > vco->max_rate) + rrate = vco->max_rate; + + *parent_rate = rrate; + + return rrate; +} + +static void vco_10nm_unprepare(struct clk_hw *hw) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + struct mdss_pll_resources *pll = vco->priv; + + if (!pll) { + pr_err("dsi pll resources not available\n"); + return; + } + pll->cached_cfg0 = MDSS_PLL_REG_R(pll->phy_base, PHY_CMN_CLK_CFG0); + pll->cached_cfg1 = MDSS_PLL_REG_R(pll->phy_base, PHY_CMN_CLK_CFG1); + pll->cached_outdiv = MDSS_PLL_REG_R(pll->pll_base, PLL_PLL_OUTDIV_RATE); + pr_debug("cfg0=%d,cfg1=%d, outdiv=%d\n", pll->cached_cfg0, + pll->cached_cfg1, pll->cached_outdiv); + + pll->vco_cached_rate = clk_hw_get_rate(hw); + dsi_pll_disable(vco); + mdss_pll_resource_enable(pll, false); +} + +static int vco_10nm_prepare(struct clk_hw *hw) +{ + int rc = 0; + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + struct mdss_pll_resources *pll = vco->priv; + + if (!pll) { + pr_err("dsi pll resources are not available\n"); + return -EINVAL; + } + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("failed to enable pll (%d) resource, rc=%d\n", + pll->index, rc); + return rc; + } + + if ((pll->vco_cached_rate != 0) && + (pll->vco_cached_rate == clk_hw_get_rate(hw))) { + rc = hw->init->ops->set_rate(hw, pll->vco_cached_rate, + pll->vco_cached_rate); + if (rc) { + pr_err("pll(%d) set_rate failed, rc=%d\n", + pll->index, rc); + mdss_pll_resource_enable(pll, false); + return rc; + } + pr_debug("cfg0=%d, cfg1=%d\n", pll->cached_cfg0, + pll->cached_cfg1); + MDSS_PLL_REG_W(pll->phy_base, PHY_CMN_CLK_CFG0, + pll->cached_cfg0); + MDSS_PLL_REG_W(pll->phy_base, PHY_CMN_CLK_CFG1, + pll->cached_cfg1); + MDSS_PLL_REG_W(pll->pll_base, PLL_PLL_OUTDIV_RATE, + pll->cached_outdiv); + } + + rc = dsi_pll_enable(vco); + if (rc) { + mdss_pll_resource_enable(pll, false); + pr_err("pll(%d) enable failed, rc=%d\n", pll->index, rc); + return rc; + } + + return rc; +} + +static unsigned long vco_10nm_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + struct mdss_pll_resources *pll = vco->priv; + int rc; + u64 ref_clk = vco->ref_clk_rate; + u64 vco_rate; + u64 multiplier; + u32 frac; + u32 dec; + u32 outdiv; + u64 pll_freq, tmp64; + + if (!vco->priv) + pr_err("vco priv is null\n"); + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("failed to enable pll(%d) resource, rc=%d\n", + pll->index, rc); + return 0; + } + + dec = MDSS_PLL_REG_R(pll->pll_base, PLL_DECIMAL_DIV_START_1); + dec &= 0xFF; + + frac = MDSS_PLL_REG_R(pll->pll_base, PLL_FRAC_DIV_START_LOW_1); + frac |= ((MDSS_PLL_REG_R(pll->pll_base, PLL_FRAC_DIV_START_MID_1) & + 0xFF) << + 8); + frac |= ((MDSS_PLL_REG_R(pll->pll_base, PLL_FRAC_DIV_START_HIGH_1) & + 0x3) << + 16); + + /* OUTDIV_1:0 field is (log(outdiv, 2)) */ + outdiv = MDSS_PLL_REG_R(pll->pll_base, PLL_PLL_OUTDIV_RATE); + outdiv &= 0x3; + outdiv = 1 << outdiv; + + /* + * TODO: + * 1. Assumes prescaler is disabled + * 2. Multiplier is 2^18. it should be 2^(num_of_frac_bits) + **/ + multiplier = 1 << 18; + pll_freq = dec * (ref_clk * 2); + tmp64 = (ref_clk * 2 * frac); + pll_freq += div_u64(tmp64, multiplier); + + vco_rate = div_u64(pll_freq, outdiv); + + pr_debug("dec=0x%x, frac=0x%x, outdiv=%d, vco=%llu\n", + dec, frac, outdiv, vco_rate); + + (void)mdss_pll_resource_enable(pll, false); + + return (unsigned long)vco_rate; +} + +static int pixel_clk_get_div(void *context, unsigned int reg, unsigned int *div) +{ + int rc; + struct mdss_pll_resources *pll = context; + u32 reg_val; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + reg_val = MDSS_PLL_REG_R(pll->phy_base, PHY_CMN_CLK_CFG0); + *div = (reg_val & 0xF0) >> 4; + + /** + * Common clock framework the divider value is interpreted as one less + * hence we return one less for all dividers except when zero + */ + if (*div != 0) + *div -= 1; + + (void)mdss_pll_resource_enable(pll, false); + + return rc; +} + +static void pixel_clk_set_div_sub(struct mdss_pll_resources *pll, int div) +{ + u32 reg_val; + + reg_val = MDSS_PLL_REG_R(pll->phy_base, PHY_CMN_CLK_CFG0); + reg_val &= ~0xF0; + reg_val |= (div << 4); + MDSS_PLL_REG_W(pll->phy_base, PHY_CMN_CLK_CFG0, reg_val); +} + +static int pixel_clk_set_div(void *context, unsigned int reg, unsigned int div) +{ + int rc; + struct mdss_pll_resources *pll = context; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + /** + * In common clock framework the divider value provided is one less and + * and hence adjusting the divider value by one prior to writing it to + * hardware + */ + div++; + pixel_clk_set_div_sub(pll, div); + if (pll->slave) + pixel_clk_set_div_sub(pll->slave, div); + (void)mdss_pll_resource_enable(pll, false); + + return 0; +} + +static int bit_clk_get_div(void *context, unsigned int reg, unsigned int *div) +{ + int rc; + struct mdss_pll_resources *pll = context; + u32 reg_val; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + reg_val = MDSS_PLL_REG_R(pll->phy_base, PHY_CMN_CLK_CFG0); + *div = (reg_val & 0x0F); + + /** + *Common clock framework the divider value is interpreted as one less + * hence we return one less for all dividers except when zero + */ + if (*div != 0) + *div -= 1; + (void)mdss_pll_resource_enable(pll, false); + + return rc; +} + +static void bit_clk_set_div_sub(struct mdss_pll_resources *rsc, int div) +{ + u32 reg_val; + + reg_val = MDSS_PLL_REG_R(rsc->phy_base, PHY_CMN_CLK_CFG0); + reg_val &= ~0x0F; + reg_val |= div; + MDSS_PLL_REG_W(rsc->phy_base, PHY_CMN_CLK_CFG0, reg_val); +} + +static int bit_clk_set_div(void *context, unsigned int reg, unsigned int div) +{ + int rc; + struct mdss_pll_resources *rsc = context; + struct dsi_pll_8998 *pll; + + if (!rsc) { + pr_err("pll resource not found\n"); + return -EINVAL; + } + + pll = rsc->priv; + if (!pll) { + pr_err("pll configuration not found\n"); + return -EINVAL; + } + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + /** + * In common clock framework the divider value provided is one less and + * and hence adjusting the divider value by one prior to writing it to + * hardware + */ + div++; + + bit_clk_set_div_sub(rsc, div); + /* For slave PLL, this divider always should be set to 1 */ + if (rsc->slave) + bit_clk_set_div_sub(rsc->slave, 1); + + (void)mdss_pll_resource_enable(rsc, false); + + return rc; +} + +static struct regmap_config dsi_pll_10nm_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x7c0, +}; + +static struct regmap_bus pll_regmap_bus = { + .reg_write = pll_reg_write, + .reg_read = pll_reg_read, +}; + +static struct regmap_bus pclk_mux_regmap_bus = { + .reg_read = phy_reg_read, + .reg_write = pclk_mux_write_sel, +}; + +static struct regmap_bus pclk_src_regmap_bus = { + .reg_write = pixel_clk_set_div, + .reg_read = pixel_clk_get_div, +}; + +static struct regmap_bus bitclk_src_regmap_bus = { + .reg_write = bit_clk_set_div, + .reg_read = bit_clk_get_div, +}; + +static const struct clk_ops clk_ops_vco_10nm = { + .recalc_rate = vco_10nm_recalc_rate, + .set_rate = vco_10nm_set_rate, + .round_rate = vco_10nm_round_rate, + .prepare = vco_10nm_prepare, + .unprepare = vco_10nm_unprepare, +}; + +static struct regmap_bus mdss_mux_regmap_bus = { + .reg_write = mdss_set_mux_sel, + .reg_read = mdss_get_mux_sel, +}; + +/* + * Clock tree for generating DSI byte and pixel clocks. + * + * + * +---------------+ + * | vco_clk | + * +-------+-------+ + * | + * | + * +---------------+ + * | pll_out_div | + * | DIV(1,2,4,8) | + * +-------+-------+ + * | + * +-----------------------------+--------+ + * | | | + * +-------v-------+ | | + * | bitclk_src | | | + * | DIV(1..15) | | | + * +-------+-------+ | | + * | | | + * +----------+---------+ | | + * Shadow Path | | | | | + * + +-------v-------+ | +------v------+ | +------v-------+ + * | | byteclk_src | | |post_bit_div | | |post_vco_div | + * | | DIV(8) | | |DIV (2) | | |DIV(4) | + * | +-------+-------+ | +------+------+ | +------+-------+ + * | | | | | | | + * | | | +------+ | | + * | | +-------------+ | | +----+ + * | +--------+ | | | | + * | | +-v--v-v---v------+ + * +-v---------v----+ \ pclk_src_mux / + * \ byteclk_mux / \ / + * \ / +-----+-----+ + * +----+-----+ | Shadow Path + * | | + + * v +-----v------+ | + * dsi_byte_clk | pclk_src | | + * | DIV(1..15) | | + * +-----+------+ | + * | | + * | | + * +--------+ | + * | | + * +---v----v----+ + * \ pclk_mux / + * \ / + * +---+---+ + * | + * | + * v + * dsi_pclk + * + */ + +static struct dsi_pll_vco_clk dsi0pll_vco_clk = { + .ref_clk_rate = 19200000UL, + .min_rate = 1000000000UL, + .max_rate = 3500000000UL, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_vco_clk", + .parent_names = (const char *[]){"bi_tcxo"}, + .num_parents = 1, + .ops = &clk_ops_vco_10nm, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct dsi_pll_vco_clk dsi1pll_vco_clk = { + .ref_clk_rate = 19200000UL, + .min_rate = 1000000000UL, + .max_rate = 3500000000UL, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_vco_clk", + .parent_names = (const char *[]){"bi_tcxo"}, + .num_parents = 1, + .ops = &clk_ops_vco_10nm, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_regmap_div dsi0pll_pll_out_div = { + .reg = PLL_PLL_OUTDIV_RATE, + .shift = 0, + .width = 2, + .flags = CLK_DIVIDER_POWER_OF_TWO, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_pll_out_div", + .parent_names = (const char *[]){"dsi0pll_vco_clk"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_div_ops, + }, + }, +}; + +static struct clk_regmap_div dsi1pll_pll_out_div = { + .reg = PLL_PLL_OUTDIV_RATE, + .shift = 0, + .width = 2, + .flags = CLK_DIVIDER_POWER_OF_TWO, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_pll_out_div", + .parent_names = (const char *[]){"dsi1pll_vco_clk"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_div_ops, + }, + }, +}; + +static struct clk_regmap_div dsi0pll_bitclk_src = { + .shift = 0, + .width = 4, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_bitclk_src", + .parent_names = (const char *[]){"dsi0pll_pll_out_div"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_div_ops, + }, + }, +}; + +static struct clk_regmap_div dsi1pll_bitclk_src = { + .shift = 0, + .width = 4, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_bitclk_src", + .parent_names = (const char *[]){"dsi1pll_pll_out_div"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_div_ops, + }, + }, +}; + +static struct clk_fixed_factor dsi0pll_post_vco_div = { + .div = 4, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_post_vco_div", + .parent_names = (const char *[]){"dsi0pll_pll_out_div"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi1pll_post_vco_div = { + .div = 4, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_post_vco_div", + .parent_names = (const char *[]){"dsi1pll_pll_out_div"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi0pll_byteclk_src = { + .div = 8, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_byteclk_src", + .parent_names = (const char *[]){"dsi0pll_bitclk_src"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi1pll_byteclk_src = { + .div = 8, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_byteclk_src", + .parent_names = (const char *[]){"dsi1pll_bitclk_src"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi0pll_post_bit_div = { + .div = 2, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_post_bit_div", + .parent_names = (const char *[]){"dsi0pll_bitclk_src"}, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi1pll_post_bit_div = { + .div = 2, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_post_bit_div", + .parent_names = (const char *[]){"dsi1pll_bitclk_src"}, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_regmap_mux dsi0pll_byteclk_mux = { + .shift = 0, + .width = 1, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi0_phy_pll_out_byteclk", + .parent_names = (const char *[]){"dsi0pll_byteclk_src"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_mux_closest_ops, + }, + }, +}; + +static struct clk_regmap_mux dsi1pll_byteclk_mux = { + .shift = 0, + .width = 1, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi1_phy_pll_out_byteclk", + .parent_names = (const char *[]){"dsi1pll_byteclk_src"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_mux_closest_ops, + }, + }, +}; + +static struct clk_regmap_mux dsi0pll_pclk_src_mux = { + .reg = PHY_CMN_CLK_CFG1, + .shift = 0, + .width = 2, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_pclk_src_mux", + .parent_names = (const char *[]){"dsi0pll_bitclk_src", + "dsi0pll_post_bit_div", + "dsi0pll_pll_out_div", + "dsi0pll_post_vco_div"}, + .num_parents = 4, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_regmap_mux_closest_ops, + }, + }, +}; + +static struct clk_regmap_mux dsi1pll_pclk_src_mux = { + .reg = PHY_CMN_CLK_CFG1, + .shift = 0, + .width = 2, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_pclk_src_mux", + .parent_names = (const char *[]){"dsi1pll_bitclk_src", + "dsi1pll_post_bit_div", + "dsi1pll_pll_out_div", + "dsi1pll_post_vco_div"}, + .num_parents = 4, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_regmap_mux_closest_ops, + }, + }, +}; + +static struct clk_regmap_div dsi0pll_pclk_src = { + .shift = 0, + .width = 4, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_pclk_src", + .parent_names = (const char *[]){ + "dsi0pll_pclk_src_mux"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_div_ops, + }, + }, +}; + +static struct clk_regmap_div dsi1pll_pclk_src = { + .shift = 0, + .width = 4, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_pclk_src", + .parent_names = (const char *[]){ + "dsi1pll_pclk_src_mux"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_div_ops, + }, + }, +}; + +static struct clk_regmap_mux dsi0pll_pclk_mux = { + .shift = 0, + .width = 1, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi0_phy_pll_out_dsiclk", + .parent_names = (const char *[]){"dsi0pll_pclk_src"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_mux_closest_ops, + }, + }, +}; + +static struct clk_regmap_mux dsi1pll_pclk_mux = { + .shift = 0, + .width = 1, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi1_phy_pll_out_dsiclk", + .parent_names = (const char *[]){"dsi1pll_pclk_src"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_mux_closest_ops, + }, + }, +}; + +static struct clk_hw *mdss_dsi_pllcc_10nm[] = { + [VCO_CLK_0] = &dsi0pll_vco_clk.hw, + [PLL_OUT_DIV_0_CLK] = &dsi0pll_pll_out_div.clkr.hw, + [BITCLK_SRC_0_CLK] = &dsi0pll_bitclk_src.clkr.hw, + [BYTECLK_SRC_0_CLK] = &dsi0pll_byteclk_src.hw, + [POST_BIT_DIV_0_CLK] = &dsi0pll_post_bit_div.hw, + [POST_VCO_DIV_0_CLK] = &dsi0pll_post_vco_div.hw, + [BYTECLK_MUX_0_CLK] = &dsi0pll_byteclk_mux.clkr.hw, + [PCLK_SRC_MUX_0_CLK] = &dsi0pll_pclk_src_mux.clkr.hw, + [PCLK_SRC_0_CLK] = &dsi0pll_pclk_src.clkr.hw, + [PCLK_MUX_0_CLK] = &dsi0pll_pclk_mux.clkr.hw, + [VCO_CLK_1] = &dsi1pll_vco_clk.hw, + [PLL_OUT_DIV_1_CLK] = &dsi1pll_pll_out_div.clkr.hw, + [BITCLK_SRC_1_CLK] = &dsi1pll_bitclk_src.clkr.hw, + [BYTECLK_SRC_1_CLK] = &dsi1pll_byteclk_src.hw, + [POST_BIT_DIV_1_CLK] = &dsi1pll_post_bit_div.hw, + [POST_VCO_DIV_1_CLK] = &dsi1pll_post_vco_div.hw, + [BYTECLK_MUX_1_CLK] = &dsi1pll_byteclk_mux.clkr.hw, + [PCLK_SRC_MUX_1_CLK] = &dsi1pll_pclk_src_mux.clkr.hw, + [PCLK_SRC_1_CLK] = &dsi1pll_pclk_src.clkr.hw, + [PCLK_MUX_1_CLK] = &dsi1pll_pclk_mux.clkr.hw, +}; + +int dsi_pll_clock_register_10nm(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = 0, ndx, i; + struct clk *clk; + struct clk_onecell_data *clk_data; + int num_clks = ARRAY_SIZE(mdss_dsi_pllcc_10nm); + struct regmap *rmap; + + if (!pdev || !pdev->dev.of_node || + !pll_res || !pll_res->pll_base || !pll_res->phy_base) { + pr_err("Invalid params\n"); + return -EINVAL; + } + + ndx = pll_res->index; + + if (ndx >= DSI_PLL_MAX) { + pr_err("pll index(%d) NOT supported\n", ndx); + return -EINVAL; + } + + pll_rsc_db[ndx] = pll_res; + plls[ndx].rsc = pll_res; + pll_res->priv = &plls[ndx]; + pll_res->vco_delay = VCO_DELAY_USEC; + + clk_data = devm_kzalloc(&pdev->dev, sizeof(struct clk_onecell_data), + GFP_KERNEL); + if (!clk_data) + return -ENOMEM; + + clk_data->clks = devm_kzalloc(&pdev->dev, (num_clks * + sizeof(struct clk *)), GFP_KERNEL); + if (!clk_data->clks) { + devm_kfree(&pdev->dev, clk_data); + return -ENOMEM; + } + clk_data->clk_num = num_clks; + + /* Establish client data */ + if (ndx == 0) { + + rmap = devm_regmap_init(&pdev->dev, &pll_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi0pll_pll_out_div.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &bitclk_src_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi0pll_bitclk_src.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &pclk_src_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi0pll_pclk_src.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &mdss_mux_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi0pll_pclk_mux.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &pclk_mux_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi0pll_pclk_src_mux.clkr.regmap = rmap; + rmap = devm_regmap_init(&pdev->dev, &mdss_mux_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi0pll_byteclk_mux.clkr.regmap = rmap; + + dsi0pll_vco_clk.priv = pll_res; + for (i = VCO_CLK_0; i <= PCLK_MUX_0_CLK; i++) { + clk = devm_clk_register(&pdev->dev, + mdss_dsi_pllcc_10nm[i]); + if (IS_ERR(clk)) { + pr_err("clk registration failed for DSI clock:%d\n", + pll_res->index); + rc = -EINVAL; + goto clk_register_fail; + } + clk_data->clks[i] = clk; + + } + + rc = of_clk_add_provider(pdev->dev.of_node, + of_clk_src_onecell_get, clk_data); + + + } else { + rmap = devm_regmap_init(&pdev->dev, &pll_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi1pll_pll_out_div.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &bitclk_src_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi1pll_bitclk_src.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &pclk_src_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi1pll_pclk_src.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &pclk_mux_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi1pll_pclk_mux.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &mdss_mux_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi1pll_pclk_src_mux.clkr.regmap = rmap; + rmap = devm_regmap_init(&pdev->dev, &mdss_mux_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi1pll_byteclk_mux.clkr.regmap = rmap; + dsi1pll_vco_clk.priv = pll_res; + + for (i = VCO_CLK_1; i <= PCLK_MUX_1_CLK; i++) { + clk = devm_clk_register(&pdev->dev, + mdss_dsi_pllcc_10nm[i]); + if (IS_ERR(clk)) { + pr_err("clk registration failed for DSI clock:%d\n", + pll_res->index); + rc = -EINVAL; + goto clk_register_fail; + } + clk_data->clks[i] = clk; + + } + + rc = of_clk_add_provider(pdev->dev.of_node, + of_clk_src_onecell_get, clk_data); + } + if (!rc) { + pr_info("Registered DSI PLL ndx=%d, clocks successfully", ndx); + + return rc; + } +clk_register_fail: + devm_kfree(&pdev->dev, clk_data->clks); + devm_kfree(&pdev->dev, clk_data); + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-20nm.c b/drivers/clk/qcom/mdss/mdss-dsi-pll-20nm.c new file mode 100644 index 000000000000..787c93b5c890 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-20nm.c @@ -0,0 +1,608 @@ +/* Copyright (c) 2014-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dsi-pll.h" + +#define VCO_DELAY_USEC 1 + +static const struct clk_ops bypass_lp_div_mux_clk_ops; +static const struct clk_ops pixel_clk_src_ops; +static const struct clk_ops byte_clk_src_ops; +static const struct clk_ops ndiv_clk_ops; + +static const struct clk_ops shadow_pixel_clk_src_ops; +static const struct clk_ops shadow_byte_clk_src_ops; +static const struct clk_ops clk_ops_gen_mux_dsi; + +static int vco_set_rate_20nm(struct clk *c, unsigned long rate) +{ + int rc; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + pr_debug("Cancel pending pll off work\n"); + cancel_work_sync(&dsi_pll_res->pll_off); + rc = pll_20nm_vco_set_rate(vco, rate); + + mdss_pll_resource_enable(dsi_pll_res, false); + return rc; +} + +static int pll1_vco_set_rate_20nm(struct clk *c, unsigned long rate) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *pll_res = vco->priv; + + mdss_pll_resource_enable(pll_res, true); + __dsi_pll_disable(pll_res->pll_base); + mdss_pll_resource_enable(pll_res, false); + + pr_debug("Configuring PLL1 registers.\n"); + + return 0; +} + +static int shadow_vco_set_rate_20nm(struct clk *c, unsigned long rate) +{ + int rc; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (!dsi_pll_res->resource_enable) { + pr_err("PLL resources disabled. Dynamic fps invalid\n"); + return -EINVAL; + } + + rc = shadow_pll_20nm_vco_set_rate(vco, rate); + + return rc; +} + +/* Op structures */ + +static const struct clk_ops pll1_clk_ops_dsi_vco = { + .set_rate = pll1_vco_set_rate_20nm, +}; + +static const struct clk_ops clk_ops_dsi_vco = { + .set_rate = vco_set_rate_20nm, + .round_rate = pll_20nm_vco_round_rate, + .handoff = pll_20nm_vco_handoff, + .prepare = pll_20nm_vco_prepare, + .unprepare = pll_20nm_vco_unprepare, +}; + +static struct clk_div_ops fixed_hr_oclk2_div_ops = { + .set_div = fixed_hr_oclk2_set_div, + .get_div = fixed_hr_oclk2_get_div, +}; + +static struct clk_div_ops ndiv_ops = { + .set_div = ndiv_set_div, + .get_div = ndiv_get_div, +}; + +static struct clk_div_ops hr_oclk3_div_ops = { + .set_div = hr_oclk3_set_div, + .get_div = hr_oclk3_get_div, +}; + +static struct clk_mux_ops bypass_lp_div_mux_ops = { + .set_mux_sel = set_bypass_lp_div_mux_sel, + .get_mux_sel = get_bypass_lp_div_mux_sel, +}; + +static const struct clk_ops shadow_clk_ops_dsi_vco = { + .set_rate = shadow_vco_set_rate_20nm, + .round_rate = pll_20nm_vco_round_rate, + .handoff = pll_20nm_vco_handoff, +}; + +static struct clk_div_ops shadow_fixed_hr_oclk2_div_ops = { + .set_div = shadow_fixed_hr_oclk2_set_div, + .get_div = fixed_hr_oclk2_get_div, +}; + +static struct clk_div_ops shadow_ndiv_ops = { + .set_div = shadow_ndiv_set_div, + .get_div = ndiv_get_div, +}; + +static struct clk_div_ops shadow_hr_oclk3_div_ops = { + .set_div = shadow_hr_oclk3_set_div, + .get_div = hr_oclk3_get_div, +}; + +static struct clk_mux_ops shadow_bypass_lp_div_mux_ops = { + .set_mux_sel = set_shadow_bypass_lp_div_mux_sel, + .get_mux_sel = get_bypass_lp_div_mux_sel, +}; + +static struct clk_mux_ops mdss_byte_mux_ops = { + .set_mux_sel = set_mdss_byte_mux_sel, + .get_mux_sel = get_mdss_byte_mux_sel, +}; + +static struct clk_mux_ops mdss_pixel_mux_ops = { + .set_mux_sel = set_mdss_pixel_mux_sel, + .get_mux_sel = get_mdss_pixel_mux_sel, +}; + +static struct dsi_pll_vco_clk mdss_dsi1_vco_clk_src = { + .c = { + .dbg_name = "mdss_dsi1_vco_clk_src", + .ops = &pll1_clk_ops_dsi_vco, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(mdss_dsi1_vco_clk_src.c), + }, +}; + +static struct dsi_pll_vco_clk dsi_vco_clk_8994 = { + .ref_clk_rate = 19200000, + .min_rate = 300000000, + .max_rate = 1500000000, + .pll_en_seq_cnt = 1, + .pll_enable_seqs[0] = pll_20nm_vco_enable_seq, + .c = { + .dbg_name = "dsi_vco_clk_8994", + .ops = &clk_ops_dsi_vco, + CLK_INIT(dsi_vco_clk_8994.c), + }, +}; + +static struct dsi_pll_vco_clk shadow_dsi_vco_clk_8994 = { + .ref_clk_rate = 19200000, + .min_rate = 300000000, + .max_rate = 1500000000, + .c = { + .dbg_name = "shadow_dsi_vco_clk_8994", + .ops = &shadow_clk_ops_dsi_vco, + CLK_INIT(shadow_dsi_vco_clk_8994.c), + }, +}; + +static struct div_clk ndiv_clk_8994 = { + .data = { + .max_div = 15, + .min_div = 1, + }, + .ops = &ndiv_ops, + .c = { + .parent = &dsi_vco_clk_8994.c, + .dbg_name = "ndiv_clk_8994", + .ops = &ndiv_clk_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(ndiv_clk_8994.c), + }, +}; + +static struct div_clk shadow_ndiv_clk_8994 = { + .data = { + .max_div = 15, + .min_div = 1, + }, + .ops = &shadow_ndiv_ops, + .c = { + .parent = &shadow_dsi_vco_clk_8994.c, + .dbg_name = "shadow_ndiv_clk_8994", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(shadow_ndiv_clk_8994.c), + }, +}; + +static struct div_clk indirect_path_div2_clk_8994 = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &ndiv_clk_8994.c, + .dbg_name = "indirect_path_div2_clk_8994", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(indirect_path_div2_clk_8994.c), + }, +}; + +static struct div_clk shadow_indirect_path_div2_clk_8994 = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &shadow_ndiv_clk_8994.c, + .dbg_name = "shadow_indirect_path_div2_clk_8994", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(shadow_indirect_path_div2_clk_8994.c), + }, +}; + +static struct div_clk hr_oclk3_div_clk_8994 = { + .data = { + .max_div = 255, + .min_div = 1, + }, + .ops = &hr_oclk3_div_ops, + .c = { + .parent = &dsi_vco_clk_8994.c, + .dbg_name = "hr_oclk3_div_clk_8994", + .ops = &pixel_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(hr_oclk3_div_clk_8994.c), + }, +}; + +static struct div_clk shadow_hr_oclk3_div_clk_8994 = { + .data = { + .max_div = 255, + .min_div = 1, + }, + .ops = &shadow_hr_oclk3_div_ops, + .c = { + .parent = &shadow_dsi_vco_clk_8994.c, + .dbg_name = "shadow_hr_oclk3_div_clk_8994", + .ops = &shadow_pixel_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(shadow_hr_oclk3_div_clk_8994.c), + }, +}; + +static struct div_clk pixel_clk_src = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &hr_oclk3_div_clk_8994.c, + .dbg_name = "pixel_clk_src", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(pixel_clk_src.c), + }, +}; + +static struct div_clk shadow_pixel_clk_src = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &shadow_hr_oclk3_div_clk_8994.c, + .dbg_name = "shadow_pixel_clk_src", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(shadow_pixel_clk_src.c), + }, +}; + +static struct mux_clk bypass_lp_div_mux_8994 = { + .num_parents = 2, + .parents = (struct clk_src[]){ + {&dsi_vco_clk_8994.c, 0}, + {&indirect_path_div2_clk_8994.c, 1}, + }, + .ops = &bypass_lp_div_mux_ops, + .c = { + .parent = &dsi_vco_clk_8994.c, + .dbg_name = "bypass_lp_div_mux_8994", + .ops = &bypass_lp_div_mux_clk_ops, + CLK_INIT(bypass_lp_div_mux_8994.c), + }, +}; + +static struct mux_clk shadow_bypass_lp_div_mux_8994 = { + .num_parents = 2, + .parents = (struct clk_src[]){ + {&shadow_dsi_vco_clk_8994.c, 0}, + {&shadow_indirect_path_div2_clk_8994.c, 1}, + }, + .ops = &shadow_bypass_lp_div_mux_ops, + .c = { + .parent = &shadow_dsi_vco_clk_8994.c, + .dbg_name = "shadow_bypass_lp_div_mux_8994", + .ops = &clk_ops_gen_mux, + CLK_INIT(shadow_bypass_lp_div_mux_8994.c), + }, +}; + +static struct div_clk fixed_hr_oclk2_div_clk_8994 = { + .ops = &fixed_hr_oclk2_div_ops, + .data = { + .min_div = 4, + .max_div = 4, + }, + .c = { + .parent = &bypass_lp_div_mux_8994.c, + .dbg_name = "fixed_hr_oclk2_div_clk_8994", + .ops = &byte_clk_src_ops, + CLK_INIT(fixed_hr_oclk2_div_clk_8994.c), + }, +}; + +static struct div_clk shadow_fixed_hr_oclk2_div_clk_8994 = { + .ops = &shadow_fixed_hr_oclk2_div_ops, + .data = { + .min_div = 4, + .max_div = 4, + }, + .c = { + .parent = &shadow_bypass_lp_div_mux_8994.c, + .dbg_name = "shadow_fixed_hr_oclk2_div_clk_8994", + .ops = &shadow_byte_clk_src_ops, + CLK_INIT(shadow_fixed_hr_oclk2_div_clk_8994.c), + }, +}; + +static struct div_clk byte_clk_src = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &fixed_hr_oclk2_div_clk_8994.c, + .dbg_name = "byte_clk_src", + .ops = &clk_ops_div, + CLK_INIT(byte_clk_src.c), + }, +}; + +static struct div_clk shadow_byte_clk_src = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &shadow_fixed_hr_oclk2_div_clk_8994.c, + .dbg_name = "shadow_byte_clk_src", + .ops = &clk_ops_div, + CLK_INIT(shadow_byte_clk_src.c), + }, +}; + +static struct mux_clk mdss_pixel_clk_mux = { + .num_parents = 2, + .parents = (struct clk_src[]) { + {&pixel_clk_src.c, 0}, + {&shadow_pixel_clk_src.c, 1}, + }, + .ops = &mdss_pixel_mux_ops, + .c = { + .parent = &pixel_clk_src.c, + .dbg_name = "mdss_pixel_clk_mux", + .ops = &clk_ops_gen_mux, + CLK_INIT(mdss_pixel_clk_mux.c), + } +}; + +static struct mux_clk mdss_byte_clk_mux = { + .num_parents = 2, + .parents = (struct clk_src[]) { + {&byte_clk_src.c, 0}, + {&shadow_byte_clk_src.c, 1}, + }, + .ops = &mdss_byte_mux_ops, + .c = { + .parent = &byte_clk_src.c, + .dbg_name = "mdss_byte_clk_mux", + .ops = &clk_ops_gen_mux_dsi, + CLK_INIT(mdss_byte_clk_mux.c), + } +}; + +static struct clk_lookup mdss_dsi_pll_1_cc_8994[] = { + CLK_LIST(mdss_dsi1_vco_clk_src), +}; + +static struct clk_lookup mdss_dsi_pllcc_8994[] = { + CLK_LIST(mdss_pixel_clk_mux), + CLK_LIST(mdss_byte_clk_mux), + CLK_LIST(pixel_clk_src), + CLK_LIST(byte_clk_src), + CLK_LIST(fixed_hr_oclk2_div_clk_8994), + CLK_LIST(bypass_lp_div_mux_8994), + CLK_LIST(hr_oclk3_div_clk_8994), + CLK_LIST(indirect_path_div2_clk_8994), + CLK_LIST(ndiv_clk_8994), + CLK_LIST(dsi_vco_clk_8994), + CLK_LIST(shadow_pixel_clk_src), + CLK_LIST(shadow_byte_clk_src), + CLK_LIST(shadow_fixed_hr_oclk2_div_clk_8994), + CLK_LIST(shadow_bypass_lp_div_mux_8994), + CLK_LIST(shadow_hr_oclk3_div_clk_8994), + CLK_LIST(shadow_indirect_path_div2_clk_8994), + CLK_LIST(shadow_ndiv_clk_8994), + CLK_LIST(shadow_dsi_vco_clk_8994), +}; + +static void dsi_pll_off_work(struct work_struct *work) +{ + struct mdss_pll_resources *pll_res; + + if (!work) { + pr_err("pll_resource is invalid\n"); + return; + } + + pr_debug("Starting PLL off Worker%s\n", __func__); + + pll_res = container_of(work, struct + mdss_pll_resources, pll_off); + + mdss_pll_resource_enable(pll_res, true); + __dsi_pll_disable(pll_res->pll_base); + if (pll_res->pll_1_base) + __dsi_pll_disable(pll_res->pll_1_base); + mdss_pll_resource_enable(pll_res, false); +} + +static int dsi_pll_regulator_notifier_call(struct notifier_block *self, + unsigned long event, void *data) +{ + + struct mdss_pll_resources *pll_res; + + if (!self) { + pr_err("pll_resource is invalid\n"); + goto error; + } + + pll_res = container_of(self, struct + mdss_pll_resources, gdsc_cb); + + if (event & REGULATOR_EVENT_ENABLE) { + pr_debug("Regulator ON event. Scheduling pll off worker\n"); + schedule_work(&pll_res->pll_off); + } + + if (event & REGULATOR_EVENT_DISABLE) + pr_debug("Regulator OFF event.\n"); + +error: + return NOTIFY_OK; +} + +int dsi_pll_clock_register_20nm(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc; + struct dss_vreg *pll_reg; + + if (!pdev || !pdev->dev.of_node) { + pr_err("Invalid input parameters\n"); + return -EINVAL; + } + + if (!pll_res || !pll_res->pll_base) { + pr_err("Invalid PLL resources\n"); + return -EPROBE_DEFER; + } + + /* + * Set client data to mux, div and vco clocks. + * This needs to be done only for PLL0 since, that is the one in + * use. + **/ + if (!pll_res->index) { + byte_clk_src.priv = pll_res; + pixel_clk_src.priv = pll_res; + bypass_lp_div_mux_8994.priv = pll_res; + indirect_path_div2_clk_8994.priv = pll_res; + ndiv_clk_8994.priv = pll_res; + fixed_hr_oclk2_div_clk_8994.priv = pll_res; + hr_oclk3_div_clk_8994.priv = pll_res; + dsi_vco_clk_8994.priv = pll_res; + + shadow_byte_clk_src.priv = pll_res; + shadow_pixel_clk_src.priv = pll_res; + shadow_bypass_lp_div_mux_8994.priv = pll_res; + shadow_indirect_path_div2_clk_8994.priv = pll_res; + shadow_ndiv_clk_8994.priv = pll_res; + shadow_fixed_hr_oclk2_div_clk_8994.priv = pll_res; + shadow_hr_oclk3_div_clk_8994.priv = pll_res; + shadow_dsi_vco_clk_8994.priv = pll_res; + + pll_res->vco_delay = VCO_DELAY_USEC; + + /* Set clock source operations */ + pixel_clk_src_ops = clk_ops_slave_div; + pixel_clk_src_ops.prepare = dsi_pll_div_prepare; + + ndiv_clk_ops = clk_ops_div; + ndiv_clk_ops.prepare = dsi_pll_div_prepare; + + byte_clk_src_ops = clk_ops_div; + byte_clk_src_ops.prepare = dsi_pll_div_prepare; + + bypass_lp_div_mux_clk_ops = clk_ops_gen_mux; + bypass_lp_div_mux_clk_ops.prepare = dsi_pll_mux_prepare; + + clk_ops_gen_mux_dsi = clk_ops_gen_mux; + clk_ops_gen_mux_dsi.round_rate = parent_round_rate; + clk_ops_gen_mux_dsi.set_rate = parent_set_rate; + + shadow_pixel_clk_src_ops = clk_ops_slave_div; + shadow_pixel_clk_src_ops.prepare = dsi_pll_div_prepare; + + shadow_byte_clk_src_ops = clk_ops_div; + shadow_byte_clk_src_ops.prepare = dsi_pll_div_prepare; + } else { + mdss_dsi1_vco_clk_src.priv = pll_res; + } + + if ((pll_res->target_id == MDSS_PLL_TARGET_8994) || + (pll_res->target_id == MDSS_PLL_TARGET_8992)) { + if (pll_res->index) { + rc = of_msm_clock_register(pdev->dev.of_node, + mdss_dsi_pll_1_cc_8994, + ARRAY_SIZE(mdss_dsi_pll_1_cc_8994)); + if (rc) { + pr_err("Clock register failed\n"); + rc = -EPROBE_DEFER; + } + } else { + rc = of_msm_clock_register(pdev->dev.of_node, + mdss_dsi_pllcc_8994, + ARRAY_SIZE(mdss_dsi_pllcc_8994)); + if (rc) { + pr_err("Clock register failed\n"); + rc = -EPROBE_DEFER; + } + pll_res->gdsc_cb.notifier_call = + dsi_pll_regulator_notifier_call; + INIT_WORK(&pll_res->pll_off, dsi_pll_off_work); + + pll_reg = mdss_pll_get_mp_by_reg_name(pll_res, "gdsc"); + if (pll_reg) { + pr_debug("Registering for gdsc regulator events\n"); + if (regulator_register_notifier(pll_reg->vreg, + &(pll_res->gdsc_cb))) + pr_err("Regulator notification registration failed!\n"); + } + } + + } else { + pr_err("Invalid target ID\n"); + rc = -EINVAL; + } + + if (!rc) + pr_info("Registered DSI PLL clocks successfully\n"); + + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-28hpm.c b/drivers/clk/qcom/mdss/mdss-dsi-pll-28hpm.c new file mode 100644 index 000000000000..285ba0c58f3e --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-28hpm.c @@ -0,0 +1,335 @@ +/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dsi-pll.h" + +#define VCO_DELAY_USEC 1 + +static struct clk_div_ops fixed_2div_ops; +static const struct clk_ops byte_mux_clk_ops; +static const struct clk_ops pixel_clk_src_ops; +static const struct clk_ops byte_clk_src_ops; +static const struct clk_ops analog_postdiv_clk_ops; +static struct lpfr_cfg lpfr_lut_struct[] = { + {479500000, 8}, + {480000000, 11}, + {575500000, 8}, + {576000000, 12}, + {610500000, 8}, + {659500000, 9}, + {671500000, 10}, + {672000000, 14}, + {708500000, 10}, + {750000000, 11}, +}; + +static void dsi_pll_software_reset(struct mdss_pll_resources *dsi_pll_res) +{ + /* + * Add HW recommended delays after toggling the software + * reset bit off and back on. + */ + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_TEST_CFG, 0x01); + udelay(1); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_TEST_CFG, 0x00); + udelay(1); +} + +static int vco_set_rate_hpm(struct clk *c, unsigned long rate) +{ + int rc; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + rc = vco_set_rate(vco, rate); + + mdss_pll_resource_enable(dsi_pll_res, false); + return rc; +} + +static int dsi_pll_enable_seq_8974(struct mdss_pll_resources *dsi_pll_res) +{ + int i, rc = 0; + int pll_locked; + + dsi_pll_software_reset(dsi_pll_res); + + /* + * PLL power up sequence. + * Add necessary delays recommeded by hardware. + */ + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x01); + udelay(1); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x05); + udelay(200); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x07); + udelay(500); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x0f); + udelay(500); + + for (i = 0; i < 2; i++) { + udelay(100); + /* DSI Uniphy lock detect setting */ + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_LKDET_CFG2, 0x0c); + udelay(100); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_LKDET_CFG2, 0x0d); + + pll_locked = dsi_pll_lock_status(dsi_pll_res); + if (pll_locked) + break; + + dsi_pll_software_reset(dsi_pll_res); + /* + * PLL power up sequence. + * Add necessary delays recommeded by hardware. + */ + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x1); + udelay(1); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x5); + udelay(200); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x7); + udelay(250); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x5); + udelay(200); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x7); + udelay(500); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0xf); + udelay(500); + + } + + if (!pll_locked) { + pr_err("DSI PLL lock failed\n"); + rc = -EINVAL; + } else { + pr_debug("DSI PLL Lock success\n"); + } + + return rc; +} + +/* Op structures */ + +static const struct clk_ops clk_ops_dsi_vco = { + .set_rate = vco_set_rate_hpm, + .round_rate = vco_round_rate, + .handoff = vco_handoff, + .prepare = vco_prepare, + .unprepare = vco_unprepare, +}; + + +static struct clk_div_ops fixed_4div_ops = { + .set_div = fixed_4div_set_div, + .get_div = fixed_4div_get_div, +}; + +static struct clk_div_ops analog_postdiv_ops = { + .set_div = analog_set_div, + .get_div = analog_get_div, +}; + +static struct clk_div_ops digital_postdiv_ops = { + .set_div = digital_set_div, + .get_div = digital_get_div, +}; + +static struct clk_mux_ops byte_mux_ops = { + .set_mux_sel = set_byte_mux_sel, + .get_mux_sel = get_byte_mux_sel, +}; + +static struct dsi_pll_vco_clk dsi_vco_clk_8974 = { + .ref_clk_rate = 19200000, + .min_rate = 350000000, + .max_rate = 750000000, + .pll_en_seq_cnt = 3, + .pll_enable_seqs[0] = dsi_pll_enable_seq_8974, + .pll_enable_seqs[1] = dsi_pll_enable_seq_8974, + .pll_enable_seqs[2] = dsi_pll_enable_seq_8974, + .lpfr_lut_size = 10, + .lpfr_lut = lpfr_lut_struct, + .c = { + .dbg_name = "dsi_vco_clk_8974", + .ops = &clk_ops_dsi_vco, + CLK_INIT(dsi_vco_clk_8974.c), + }, +}; + +static struct div_clk analog_postdiv_clk_8974 = { + .data = { + .max_div = 255, + .min_div = 1, + }, + .ops = &analog_postdiv_ops, + .c = { + .parent = &dsi_vco_clk_8974.c, + .dbg_name = "analog_postdiv_clk", + .ops = &analog_postdiv_clk_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(analog_postdiv_clk_8974.c), + }, +}; + +static struct div_clk indirect_path_div2_clk_8974 = { + .ops = &fixed_2div_ops, + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &analog_postdiv_clk_8974.c, + .dbg_name = "indirect_path_div2_clk", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(indirect_path_div2_clk_8974.c), + }, +}; + +static struct div_clk pixel_clk_src_8974 = { + .data = { + .max_div = 255, + .min_div = 1, + }, + .ops = &digital_postdiv_ops, + .c = { + .parent = &dsi_vco_clk_8974.c, + .dbg_name = "pixel_clk_src_8974", + .ops = &pixel_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(pixel_clk_src_8974.c), + }, +}; + +static struct mux_clk byte_mux_8974 = { + .num_parents = 2, + .parents = (struct clk_src[]){ + {&dsi_vco_clk_8974.c, 0}, + {&indirect_path_div2_clk_8974.c, 1}, + }, + .ops = &byte_mux_ops, + .c = { + .parent = &dsi_vco_clk_8974.c, + .dbg_name = "byte_mux_8974", + .ops = &byte_mux_clk_ops, + CLK_INIT(byte_mux_8974.c), + }, +}; + +static struct div_clk byte_clk_src_8974 = { + .ops = &fixed_4div_ops, + .data = { + .min_div = 4, + .max_div = 4, + }, + .c = { + .parent = &byte_mux_8974.c, + .dbg_name = "byte_clk_src_8974", + .ops = &byte_clk_src_ops, + CLK_INIT(byte_clk_src_8974.c), + }, +}; + +static struct clk_lookup mdss_dsi_pllcc_8974[] = { + CLK_LOOKUP_OF("pixel_src", pixel_clk_src_8974, + "fd8c0000.qcom,mmsscc-mdss"), + CLK_LOOKUP_OF("byte_src", byte_clk_src_8974, + "fd8c0000.qcom,mmsscc-mdss"), +}; + +int dsi_pll_clock_register_hpm(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc; + + if (!pdev || !pdev->dev.of_node) { + pr_err("Invalid input parameters\n"); + return -EINVAL; + } + + if (!pll_res || !pll_res->pll_base) { + pr_err("Invalid PLL resources\n"); + return -EPROBE_DEFER; + } + + /* Set client data to mux, div and vco clocks */ + byte_clk_src_8974.priv = pll_res; + pixel_clk_src_8974.priv = pll_res; + byte_mux_8974.priv = pll_res; + indirect_path_div2_clk_8974.priv = pll_res; + analog_postdiv_clk_8974.priv = pll_res; + dsi_vco_clk_8974.priv = pll_res; + pll_res->vco_delay = VCO_DELAY_USEC; + + /* Set clock source operations */ + pixel_clk_src_ops = clk_ops_slave_div; + pixel_clk_src_ops.prepare = dsi_pll_div_prepare; + + analog_postdiv_clk_ops = clk_ops_div; + analog_postdiv_clk_ops.prepare = dsi_pll_div_prepare; + + byte_clk_src_ops = clk_ops_div; + byte_clk_src_ops.prepare = dsi_pll_div_prepare; + + byte_mux_clk_ops = clk_ops_gen_mux; + byte_mux_clk_ops.prepare = dsi_pll_mux_prepare; + + if (pll_res->target_id == MDSS_PLL_TARGET_8974) { + rc = of_msm_clock_register(pdev->dev.of_node, + mdss_dsi_pllcc_8974, ARRAY_SIZE(mdss_dsi_pllcc_8974)); + if (rc) { + pr_err("Clock register failed\n"); + rc = -EPROBE_DEFER; + } + } else { + pr_err("Invalid target ID\n"); + rc = -EINVAL; + } + + if (!rc) + pr_info("Registered DSI PLL clocks successfully\n"); + + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-28lpm.c b/drivers/clk/qcom/mdss/mdss-dsi-pll-28lpm.c new file mode 100644 index 000000000000..f6c7e287b8ca --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-28lpm.c @@ -0,0 +1,306 @@ +/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dsi-pll.h" + +#define VCO_DELAY_USEC 1000 + +static struct clk_div_ops fixed_2div_ops; +static const struct clk_ops byte_mux_clk_ops; +static const struct clk_ops pixel_clk_src_ops; +static const struct clk_ops byte_clk_src_ops; +static const struct clk_ops analog_postdiv_clk_ops; +static struct lpfr_cfg lpfr_lut_struct[] = { + {479500000, 8}, + {480000000, 11}, + {575500000, 8}, + {576000000, 12}, + {610500000, 8}, + {659500000, 9}, + {671500000, 10}, + {672000000, 14}, + {708500000, 10}, + {750000000, 11}, +}; + +static int vco_set_rate_lpm(struct clk *c, unsigned long rate) +{ + int rc; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + /* + * DSI PLL software reset. Add HW recommended delays after toggling + * the software reset bit off and back on. + */ + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_TEST_CFG, 0x01); + udelay(1000); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_TEST_CFG, 0x00); + udelay(1000); + + rc = vco_set_rate(vco, rate); + + mdss_pll_resource_enable(dsi_pll_res, false); + return rc; +} + +static int dsi_pll_enable_seq_8916(struct mdss_pll_resources *dsi_pll_res) +{ + int pll_locked = 0; + + /* + * DSI PLL software reset. Add HW recommended delays after toggling + * the software reset bit off and back on. + */ + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_TEST_CFG, 0x01); + ndelay(500); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_TEST_CFG, 0x00); + + /* + * PLL power up sequence. + * Add necessary delays recommended by hardware. + */ + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG1, 0x34); + ndelay(500); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x01); + ndelay(500); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x05); + ndelay(500); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x0f); + ndelay(500); + + /* DSI PLL toggle lock detect setting */ + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_LKDET_CFG2, 0x04); + ndelay(500); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_LKDET_CFG2, 0x05); + udelay(512); + + pll_locked = dsi_pll_lock_status(dsi_pll_res); + + if (pll_locked) + pr_debug("PLL Locked\n"); + else + pr_err("PLL failed to lock\n"); + + return pll_locked ? 0 : -EINVAL; +} + +/* Op structures */ + +static const struct clk_ops clk_ops_dsi_vco = { + .set_rate = vco_set_rate_lpm, + .round_rate = vco_round_rate, + .handoff = vco_handoff, + .prepare = vco_prepare, + .unprepare = vco_unprepare, +}; + + +static struct clk_div_ops fixed_4div_ops = { + .set_div = fixed_4div_set_div, + .get_div = fixed_4div_get_div, +}; + +static struct clk_div_ops analog_postdiv_ops = { + .set_div = analog_set_div, + .get_div = analog_get_div, +}; + +static struct clk_div_ops digital_postdiv_ops = { + .set_div = digital_set_div, + .get_div = digital_get_div, +}; + +static struct clk_mux_ops byte_mux_ops = { + .set_mux_sel = set_byte_mux_sel, + .get_mux_sel = get_byte_mux_sel, +}; + +static struct dsi_pll_vco_clk dsi_vco_clk_8916 = { + .ref_clk_rate = 19200000, + .min_rate = 350000000, + .max_rate = 750000000, + .pll_en_seq_cnt = 1, + .pll_enable_seqs[0] = dsi_pll_enable_seq_8916, + .lpfr_lut_size = 10, + .lpfr_lut = lpfr_lut_struct, + .c = { + .dbg_name = "dsi_vco_clk_8916", + .ops = &clk_ops_dsi_vco, + CLK_INIT(dsi_vco_clk_8916.c), + }, +}; + +static struct div_clk analog_postdiv_clk_8916 = { + .data = { + .max_div = 255, + .min_div = 1, + }, + .ops = &analog_postdiv_ops, + .c = { + .parent = &dsi_vco_clk_8916.c, + .dbg_name = "analog_postdiv_clk", + .ops = &analog_postdiv_clk_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(analog_postdiv_clk_8916.c), + }, +}; + +static struct div_clk indirect_path_div2_clk_8916 = { + .ops = &fixed_2div_ops, + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &analog_postdiv_clk_8916.c, + .dbg_name = "indirect_path_div2_clk", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(indirect_path_div2_clk_8916.c), + }, +}; + +static struct div_clk pixel_clk_src = { + .data = { + .max_div = 255, + .min_div = 1, + }, + .ops = &digital_postdiv_ops, + .c = { + .parent = &dsi_vco_clk_8916.c, + .dbg_name = "pixel_clk_src_8916", + .ops = &pixel_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(pixel_clk_src.c), + }, +}; + +static struct mux_clk byte_mux_8916 = { + .num_parents = 2, + .parents = (struct clk_src[]){ + {&dsi_vco_clk_8916.c, 0}, + {&indirect_path_div2_clk_8916.c, 1}, + }, + .ops = &byte_mux_ops, + .c = { + .parent = &dsi_vco_clk_8916.c, + .dbg_name = "byte_mux_8916", + .ops = &byte_mux_clk_ops, + CLK_INIT(byte_mux_8916.c), + }, +}; + +static struct div_clk byte_clk_src = { + .ops = &fixed_4div_ops, + .data = { + .min_div = 4, + .max_div = 4, + }, + .c = { + .parent = &byte_mux_8916.c, + .dbg_name = "byte_clk_src_8916", + .ops = &byte_clk_src_ops, + CLK_INIT(byte_clk_src.c), + }, +}; + +static struct clk_lookup mdss_dsi_pllcc_8916[] = { + CLK_LIST(pixel_clk_src), + CLK_LIST(byte_clk_src), +}; + +int dsi_pll_clock_register_lpm(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc; + + if (!pdev || !pdev->dev.of_node) { + pr_err("Invalid input parameters\n"); + return -EINVAL; + } + + if (!pll_res || !pll_res->pll_base) { + pr_err("Invalid PLL resources\n"); + return -EPROBE_DEFER; + } + + /* Set client data to mux, div and vco clocks */ + byte_clk_src.priv = pll_res; + pixel_clk_src.priv = pll_res; + byte_mux_8916.priv = pll_res; + indirect_path_div2_clk_8916.priv = pll_res; + analog_postdiv_clk_8916.priv = pll_res; + dsi_vco_clk_8916.priv = pll_res; + pll_res->vco_delay = VCO_DELAY_USEC; + + /* Set clock source operations */ + pixel_clk_src_ops = clk_ops_slave_div; + pixel_clk_src_ops.prepare = dsi_pll_div_prepare; + + analog_postdiv_clk_ops = clk_ops_div; + analog_postdiv_clk_ops.prepare = dsi_pll_div_prepare; + + byte_clk_src_ops = clk_ops_div; + byte_clk_src_ops.prepare = dsi_pll_div_prepare; + + byte_mux_clk_ops = clk_ops_gen_mux; + byte_mux_clk_ops.prepare = dsi_pll_mux_prepare; + + if (pll_res->target_id == MDSS_PLL_TARGET_8916 || + pll_res->target_id == MDSS_PLL_TARGET_8939 || + pll_res->target_id == MDSS_PLL_TARGET_8909) { + rc = of_msm_clock_register(pdev->dev.of_node, + mdss_dsi_pllcc_8916, ARRAY_SIZE(mdss_dsi_pllcc_8916)); + if (rc) { + pr_err("Clock register failed\n"); + rc = -EPROBE_DEFER; + } + } else { + pr_err("Invalid target ID\n"); + rc = -EINVAL; + } + + if (!rc) + pr_info("Registered DSI PLL clocks successfully\n"); + + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-8996-util.c b/drivers/clk/qcom/mdss/mdss-dsi-pll-8996-util.c new file mode 100644 index 000000000000..afe57f394f60 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-8996-util.c @@ -0,0 +1,1137 @@ +/* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dsi-pll.h" +#include "mdss-dsi-pll-8996.h" + +#define DSI_PLL_POLL_MAX_READS 15 +#define DSI_PLL_POLL_TIMEOUT_US 1000 +#define MSM8996_DSI_PLL_REVISION_2 2 + +#define CEIL(x, y) (((x) + ((y)-1)) / (y)) + +int set_mdss_byte_mux_sel_8996(struct mux_clk *clk, int sel) +{ + return 0; +} + +int get_mdss_byte_mux_sel_8996(struct mux_clk *clk) +{ + return 0; +} + +int set_mdss_pixel_mux_sel_8996(struct mux_clk *clk, int sel) +{ + return 0; +} + +int get_mdss_pixel_mux_sel_8996(struct mux_clk *clk) +{ + return 0; +} + +static int mdss_pll_read_stored_trim_codes( + struct mdss_pll_resources *dsi_pll_res, s64 vco_clk_rate) +{ + int i; + int rc = 0; + bool found = false; + + if (!dsi_pll_res->dfps) { + rc = -EINVAL; + goto end_read; + } + + for (i = 0; i < dsi_pll_res->dfps->panel_dfps.frame_rate_cnt; i++) { + struct dfps_codes_info *codes_info = + &dsi_pll_res->dfps->codes_dfps[i]; + + pr_debug("valid=%d frame_rate=%d, vco_rate=%d, code %d %d\n", + codes_info->is_valid, codes_info->frame_rate, + codes_info->clk_rate, codes_info->pll_codes.pll_codes_1, + codes_info->pll_codes.pll_codes_2); + + if (vco_clk_rate != codes_info->clk_rate && + codes_info->is_valid) + continue; + + dsi_pll_res->cache_pll_trim_codes[0] = + codes_info->pll_codes.pll_codes_1; + dsi_pll_res->cache_pll_trim_codes[1] = + codes_info->pll_codes.pll_codes_2; + found = true; + break; + } + + if (!found) { + rc = -EINVAL; + goto end_read; + } + + pr_debug("core_kvco_code=0x%x core_vco_tune=0x%x\n", + dsi_pll_res->cache_pll_trim_codes[0], + dsi_pll_res->cache_pll_trim_codes[1]); + +end_read: + return rc; +} + +int post_n1_div_set_div(struct div_clk *clk, int div) +{ + struct mdss_pll_resources *pll = clk->priv; + struct dsi_pll_db *pdb; + struct dsi_pll_output *pout; + int rc; + u32 n1div = 0; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + pdb = (struct dsi_pll_db *)pll->priv; + pout = &pdb->out; + + /* + * vco rate = bit_clk * postdiv * n1div + * vco range from 1300 to 2600 Mhz + * postdiv = 1 + * n1div = 1 to 15 + * n1div = roundup(1300Mhz / bit_clk) + * support bit_clk above 86.67Mhz + */ + + /* this is for vco/bit clock */ + pout->pll_postdiv = 1; /* fixed, divided by 1 */ + pout->pll_n1div = div; + + n1div = MDSS_PLL_REG_R(pll->pll_base, DSIPHY_CMN_CLK_CFG0); + n1div &= ~0xf; + n1div |= (div & 0xf); + MDSS_PLL_REG_W(pll->pll_base, DSIPHY_CMN_CLK_CFG0, n1div); + /* ensure n1 divider is programed */ + wmb(); + pr_debug("ndx=%d div=%d postdiv=%x n1div=%x\n", + pll->index, div, pout->pll_postdiv, pout->pll_n1div); + + mdss_pll_resource_enable(pll, false); + + return 0; +} + +int post_n1_div_get_div(struct div_clk *clk) +{ + u32 div; + int rc; + struct mdss_pll_resources *pll = clk->priv; + + if (is_gdsc_disabled(pll)) + return 0; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + /* + * postdiv = 1/2/4/8 + * n1div = 1 - 15 + * fot the time being, assume postdiv = 1 + */ + + div = MDSS_PLL_REG_R(pll->pll_base, DSIPHY_CMN_CLK_CFG0); + div &= 0xF; + pr_debug("n1 div = %d\n", div); + + mdss_pll_resource_enable(pll, false); + + return div; +} + +int n2_div_set_div(struct div_clk *clk, int div) +{ + int rc; + u32 n2div; + struct mdss_pll_resources *pll = clk->priv; + struct dsi_pll_db *pdb; + struct dsi_pll_output *pout; + struct mdss_pll_resources *slave; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + pdb = (struct dsi_pll_db *)pll->priv; + pout = &pdb->out; + + /* this is for pixel clock */ + n2div = MDSS_PLL_REG_R(pll->pll_base, DSIPHY_CMN_CLK_CFG0); + n2div &= ~0xf0; /* bits 4 to 7 */ + n2div |= (div << 4); + MDSS_PLL_REG_W(pll->pll_base, DSIPHY_CMN_CLK_CFG0, n2div); + + /* commit slave if split display is enabled */ + slave = pll->slave; + if (slave) + MDSS_PLL_REG_W(slave->pll_base, DSIPHY_CMN_CLK_CFG0, n2div); + + pout->pll_n2div = div; + + /* set dsiclk_sel=1 so that n2div *= 2 */ + MDSS_PLL_REG_W(pll->pll_base, DSIPHY_CMN_CLK_CFG1, 1); + pr_debug("ndx=%d div=%d n2div=%x\n", pll->index, div, n2div); + + mdss_pll_resource_enable(pll, false); + + return rc; +} + +int shadow_n2_div_set_div(struct div_clk *clk, int div) +{ + struct mdss_pll_resources *pll = clk->priv; + struct dsi_pll_db *pdb; + struct dsi_pll_output *pout; + u32 data; + + pdb = pll->priv; + pout = &pdb->out; + + pout->pll_n2div = div; + + data = (pout->pll_n1div | (pout->pll_n2div << 4)); + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL19, + DSIPHY_CMN_CLK_CFG0, DSIPHY_CMN_CLK_CFG1, + data, 1); + return 0; +} + +int n2_div_get_div(struct div_clk *clk) +{ + int rc; + u32 n2div; + struct mdss_pll_resources *pll = clk->priv; + + if (is_gdsc_disabled(pll)) + return 0; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll=%d resources\n", + pll->index); + return rc; + } + + n2div = MDSS_PLL_REG_R(pll->pll_base, DSIPHY_CMN_CLK_CFG0); + n2div >>= 4; + n2div &= 0x0f; + + mdss_pll_resource_enable(pll, false); + + pr_debug("ndx=%d div=%d\n", pll->index, n2div); + + return n2div; +} + +static bool pll_is_pll_locked_8996(struct mdss_pll_resources *pll) +{ + u32 status; + bool pll_locked; + + /* poll for PLL ready status */ + if (readl_poll_timeout_atomic((pll->pll_base + + DSIPHY_PLL_RESET_SM_READY_STATUS), + status, + ((status & BIT(5)) > 0), + DSI_PLL_POLL_MAX_READS, + DSI_PLL_POLL_TIMEOUT_US)) { + pr_err("DSI PLL ndx=%d status=%x failed to Lock\n", + pll->index, status); + pll_locked = false; + } else if (readl_poll_timeout_atomic((pll->pll_base + + DSIPHY_PLL_RESET_SM_READY_STATUS), + status, + ((status & BIT(0)) > 0), + DSI_PLL_POLL_MAX_READS, + DSI_PLL_POLL_TIMEOUT_US)) { + pr_err("DSI PLL ndx=%d status=%x PLl not ready\n", + pll->index, status); + pll_locked = false; + } else { + pll_locked = true; + } + + return pll_locked; +} + +static void dsi_pll_start_8996(void __iomem *pll_base) +{ + pr_debug("start PLL at base=%p\n", pll_base); + + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_VREF_CFG1, 0x10); + MDSS_PLL_REG_W(pll_base, DSIPHY_CMN_PLL_CNTRL, 1); +} + +static void dsi_pll_stop_8996(void __iomem *pll_base) +{ + pr_debug("stop PLL at base=%p\n", pll_base); + + MDSS_PLL_REG_W(pll_base, DSIPHY_CMN_PLL_CNTRL, 0); +} + +int dsi_pll_enable_seq_8996(struct mdss_pll_resources *pll) +{ + int rc = 0; + + if (!pll) { + pr_err("Invalid PLL resources\n"); + return -EINVAL; + } + + dsi_pll_start_8996(pll->pll_base); + + /* + * both DSIPHY_PLL_CLKBUFLR_EN and DSIPHY_CMN_GLBL_TEST_CTRL + * enabled at mdss_dsi_8996_phy_config() + */ + + if (!pll_is_pll_locked_8996(pll)) { + pr_err("DSI PLL ndx=%d lock failed\n", pll->index); + rc = -EINVAL; + goto init_lock_err; + } + + pr_debug("DSI PLL ndx=%d Lock success\n", pll->index); + +init_lock_err: + return rc; +} + +static int dsi_pll_enable(struct clk *c) +{ + int i, rc = 0; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *pll = vco->priv; + + /* Try all enable sequences until one succeeds */ + for (i = 0; i < vco->pll_en_seq_cnt; i++) { + rc = vco->pll_enable_seqs[i](pll); + pr_debug("DSI PLL %s after sequence #%d\n", + rc ? "unlocked" : "locked", i + 1); + if (!rc) + break; + } + + if (rc) + pr_err("ndx=%d DSI PLL failed to lock\n", pll->index); + else + pll->pll_on = true; + + return rc; +} + +static void dsi_pll_disable(struct clk *c) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *pll = vco->priv; + struct mdss_pll_resources *slave; + + if (!pll->pll_on && + mdss_pll_resource_enable(pll, true)) { + pr_err("Failed to enable mdss dsi pll=%d\n", pll->index); + return; + } + + pll->handoff_resources = false; + slave = pll->slave; + + dsi_pll_stop_8996(pll->pll_base); + + mdss_pll_resource_enable(pll, false); + + pll->pll_on = false; + + pr_debug("DSI PLL ndx=%d Disabled\n", pll->index); +} + +static void mdss_dsi_pll_8996_input_init(struct mdss_pll_resources *pll, + struct dsi_pll_db *pdb) +{ + pdb->in.fref = 19200000; /* 19.2 Mhz*/ + pdb->in.fdata = 0; /* bit clock rate */ + pdb->in.dsiclk_sel = 1; /* 1, reg: 0x0014 */ + pdb->in.ssc_en = pll->ssc_en; /* 1, reg: 0x0494, bit 0 */ + pdb->in.ldo_en = 0; /* 0, reg: 0x004c, bit 0 */ + + /* fixed input */ + pdb->in.refclk_dbler_en = 0; /* 0, reg: 0x04c0, bit 1 */ + pdb->in.vco_measure_time = 5; /* 5, unknown */ + pdb->in.kvco_measure_time = 5; /* 5, unknown */ + pdb->in.bandgap_timer = 4; /* 4, reg: 0x0430, bit 3 - 5 */ + pdb->in.pll_wakeup_timer = 5; /* 5, reg: 0x043c, bit 0 - 2 */ + pdb->in.plllock_cnt = 1; /* 1, reg: 0x0488, bit 1 - 2 */ + pdb->in.plllock_rng = 0; /* 0, reg: 0x0488, bit 3 - 4 */ + pdb->in.ssc_center = pll->ssc_center;/* 0, reg: 0x0494, bit 1 */ + pdb->in.ssc_adj_period = 37; /* 37, reg: 0x498, bit 0 - 9 */ + pdb->in.ssc_spread = pll->ssc_ppm / 1000; + pdb->in.ssc_freq = pll->ssc_freq; + + pdb->in.pll_ie_trim = 4; /* 4, reg: 0x0400 */ + pdb->in.pll_ip_trim = 4; /* 4, reg: 0x0404 */ + pdb->in.pll_cpcset_cur = 1; /* 1, reg: 0x04f0, bit 0 - 2 */ + pdb->in.pll_cpmset_cur = 1; /* 1, reg: 0x04f0, bit 3 - 5 */ + pdb->in.pll_icpmset = 4; /* 4, reg: 0x04fc, bit 3 - 5 */ + pdb->in.pll_icpcset = 4; /* 4, reg: 0x04fc, bit 0 - 2 */ + pdb->in.pll_icpmset_p = 0; /* 0, reg: 0x04f4, bit 0 - 2 */ + pdb->in.pll_icpmset_m = 0; /* 0, reg: 0x04f4, bit 3 - 5 */ + pdb->in.pll_icpcset_p = 0; /* 0, reg: 0x04f8, bit 0 - 2 */ + pdb->in.pll_icpcset_m = 0; /* 0, reg: 0x04f8, bit 3 - 5 */ + pdb->in.pll_lpf_res1 = 3; /* 3, reg: 0x0504, bit 0 - 3 */ + pdb->in.pll_lpf_cap1 = 11; /* 11, reg: 0x0500, bit 0 - 3 */ + pdb->in.pll_lpf_cap2 = 1; /* 1, reg: 0x0500, bit 4 - 7 */ + pdb->in.pll_iptat_trim = 7; + pdb->in.pll_c3ctrl = 2; /* 2 */ + pdb->in.pll_r3ctrl = 1; /* 1 */ +} + +static void pll_8996_ssc_calc(struct mdss_pll_resources *pll, + struct dsi_pll_db *pdb) +{ + u32 period, ssc_period; + u32 ref, rem; + s64 step_size; + + pr_debug("%s: vco=%lld ref=%lld\n", __func__, + pll->vco_current_rate, pll->vco_ref_clk_rate); + + ssc_period = pdb->in.ssc_freq / 500; + period = (unsigned long)pll->vco_ref_clk_rate / 1000; + ssc_period = CEIL(period, ssc_period); + ssc_period -= 1; + pdb->out.ssc_period = ssc_period; + + pr_debug("%s: ssc, freq=%d spread=%d period=%d\n", __func__, + pdb->in.ssc_freq, pdb->in.ssc_spread, pdb->out.ssc_period); + + step_size = (u32)pll->vco_current_rate; + ref = pll->vco_ref_clk_rate; + ref /= 1000; + step_size = div_s64(step_size, ref); + step_size <<= 20; + step_size = div_s64(step_size, 1000); + step_size *= pdb->in.ssc_spread; + step_size = div_s64(step_size, 1000); + step_size *= (pdb->in.ssc_adj_period + 1); + + rem = 0; + step_size = div_s64_rem(step_size, ssc_period + 1, &rem); + if (rem) + step_size++; + + pr_debug("%s: step_size=%lld\n", __func__, step_size); + + step_size &= 0x0ffff; /* take lower 16 bits */ + + pdb->out.ssc_step_size = step_size; +} + +static void pll_8996_dec_frac_calc(struct mdss_pll_resources *pll, + struct dsi_pll_db *pdb) +{ + struct dsi_pll_input *pin = &pdb->in; + struct dsi_pll_output *pout = &pdb->out; + s64 multiplier = BIT(20); + s64 dec_start_multiple, dec_start, pll_comp_val; + s32 duration, div_frac_start; + s64 vco_clk_rate = pll->vco_current_rate; + s64 fref = pll->vco_ref_clk_rate; + + pr_debug("vco_clk_rate=%lld ref_clk_rate=%lld\n", + vco_clk_rate, fref); + + dec_start_multiple = div_s64(vco_clk_rate * multiplier, fref); + div_s64_rem(dec_start_multiple, multiplier, &div_frac_start); + + dec_start = div_s64(dec_start_multiple, multiplier); + + pout->dec_start = (u32)dec_start; + pout->div_frac_start = div_frac_start; + + if (pin->plllock_cnt == 0) + duration = 1024; + else if (pin->plllock_cnt == 1) + duration = 256; + else if (pin->plllock_cnt == 2) + duration = 128; + else + duration = 32; + + pll_comp_val = duration * dec_start_multiple; + pll_comp_val = div_s64(pll_comp_val, multiplier); + do_div(pll_comp_val, 10); + + pout->plllock_cmp = (u32)pll_comp_val; + + pout->pll_txclk_en = 1; + if (pll->revision == MSM8996_DSI_PLL_REVISION_2) + pout->cmn_ldo_cntrl = 0x3c; + else + pout->cmn_ldo_cntrl = 0x1c; +} + +static u32 pll_8996_kvco_slop(u32 vrate) +{ + u32 slop = 0; + + if (vrate > 1300000000UL && vrate <= 1800000000UL) + slop = 600; + else if (vrate > 1800000000UL && vrate < 2300000000UL) + slop = 400; + else if (vrate > 2300000000UL && vrate < 2600000000UL) + slop = 280; + + return slop; +} + +static void pll_8996_calc_vco_count(struct dsi_pll_db *pdb, + s64 vco_clk_rate, s64 fref) +{ + struct dsi_pll_input *pin = &pdb->in; + struct dsi_pll_output *pout = &pdb->out; + s64 data; + u32 cnt; + + data = fref * pin->vco_measure_time; + do_div(data, 1000000); + data &= 0x03ff; /* 10 bits */ + data -= 2; + pout->pll_vco_div_ref = data; + + data = (unsigned long)vco_clk_rate / 1000000; /* unit is Mhz */ + data *= pin->vco_measure_time; + do_div(data, 10); + pout->pll_vco_count = data; /* reg: 0x0474, 0x0478 */ + + data = fref * pin->kvco_measure_time; + do_div(data, 1000000); + data &= 0x03ff; /* 10 bits */ + data -= 1; + pout->pll_kvco_div_ref = data; + + cnt = pll_8996_kvco_slop(vco_clk_rate); + cnt *= 2; + do_div(cnt, 100); + cnt *= pin->kvco_measure_time; + pout->pll_kvco_count = cnt; + + pout->pll_misc1 = 16; + pout->pll_resetsm_cntrl = 48; + pout->pll_resetsm_cntrl2 = pin->bandgap_timer << 3; + pout->pll_resetsm_cntrl5 = pin->pll_wakeup_timer; + pout->pll_kvco_code = 0; +} + +static void pll_db_commit_ssc(struct mdss_pll_resources *pll, + struct dsi_pll_db *pdb) +{ + void __iomem *pll_base = pll->pll_base; + struct dsi_pll_input *pin = &pdb->in; + struct dsi_pll_output *pout = &pdb->out; + char data; + + data = pin->ssc_adj_period; + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_SSC_ADJ_PER1, data); + data = (pin->ssc_adj_period >> 8); + data &= 0x03; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_SSC_ADJ_PER2, data); + + data = pout->ssc_period; + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_SSC_PER1, data); + data = (pout->ssc_period >> 8); + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_SSC_PER2, data); + + data = pout->ssc_step_size; + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_SSC_STEP_SIZE1, data); + data = (pout->ssc_step_size >> 8); + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_SSC_STEP_SIZE2, data); + + data = (pin->ssc_center & 0x01); + data <<= 1; + data |= 0x01; /* enable */ + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_SSC_EN_CENTER, data); + + wmb(); /* make sure register committed */ +} + +static void pll_db_commit_common(struct mdss_pll_resources *pll, + struct dsi_pll_db *pdb) +{ + void __iomem *pll_base = pll->pll_base; + struct dsi_pll_input *pin = &pdb->in; + struct dsi_pll_output *pout = &pdb->out; + char data; + + /* confgiure the non frequency dependent pll registers */ + data = 0; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_SYSCLK_EN_RESET, data); + + /* DSIPHY_PLL_CLKBUFLR_EN updated at dsi phy */ + + data = pout->pll_txclk_en; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_TXCLK_EN, data); + + data = pout->pll_resetsm_cntrl; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_RESETSM_CNTRL, data); + data = pout->pll_resetsm_cntrl2; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_RESETSM_CNTRL2, data); + data = pout->pll_resetsm_cntrl5; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_RESETSM_CNTRL5, data); + + data = pout->pll_vco_div_ref; + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_VCO_DIV_REF1, data); + data = (pout->pll_vco_div_ref >> 8); + data &= 0x03; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_VCO_DIV_REF2, data); + + data = pout->pll_kvco_div_ref; + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_KVCO_DIV_REF1, data); + data = (pout->pll_kvco_div_ref >> 8); + data &= 0x03; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_KVCO_DIV_REF2, data); + + data = pout->pll_misc1; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLL_MISC1, data); + + data = pin->pll_ie_trim; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_IE_TRIM, data); + + data = pin->pll_ip_trim; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_IP_TRIM, data); + + data = ((pin->pll_cpmset_cur << 3) | pin->pll_cpcset_cur); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_CP_SET_CUR, data); + + data = ((pin->pll_icpcset_p << 3) | pin->pll_icpcset_m); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLL_ICPCSET, data); + + data = ((pin->pll_icpmset_p << 3) | pin->pll_icpcset_m); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLL_ICPMSET, data); + + data = ((pin->pll_icpmset << 3) | pin->pll_icpcset); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLL_ICP_SET, data); + + data = ((pdb->in.pll_lpf_cap2 << 4) | pdb->in.pll_lpf_cap1); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLL_LPF1, data); + + data = pin->pll_iptat_trim; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_IPTAT_TRIM, data); + + data = (pdb->in.pll_c3ctrl | (pdb->in.pll_r3ctrl << 4)); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLL_CRCTRL, data); +} + +static void pll_db_commit_8996(struct mdss_pll_resources *pll, + struct dsi_pll_db *pdb) +{ + void __iomem *pll_base = pll->pll_base; + struct dsi_pll_input *pin = &pdb->in; + struct dsi_pll_output *pout = &pdb->out; + char data; + + data = pout->cmn_ldo_cntrl; + MDSS_PLL_REG_W(pll_base, DSIPHY_CMN_LDO_CNTRL, data); + + pll_db_commit_common(pll, pdb); + + /* de assert pll start and apply pll sw reset */ + /* stop pll */ + MDSS_PLL_REG_W(pll_base, DSIPHY_CMN_PLL_CNTRL, 0); + + /* pll sw reset */ + MDSS_PLL_REG_W(pll_base, DSIPHY_CMN_CTRL_1, 0x20); + wmb(); /* make sure register committed */ + udelay(10); + + MDSS_PLL_REG_W(pll_base, DSIPHY_CMN_CTRL_1, 0); + wmb(); /* make sure register committed */ + + data = pdb->in.dsiclk_sel; /* set dsiclk_sel = 1 */ + MDSS_PLL_REG_W(pll_base, DSIPHY_CMN_CLK_CFG1, data); + + data = 0xff; /* data, clk, pll normal operation */ + MDSS_PLL_REG_W(pll_base, DSIPHY_CMN_CTRL_0, data); + + /* confgiure the frequency dependent pll registers */ + data = pout->dec_start; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_DEC_START, data); + + data = pout->div_frac_start; + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_DIV_FRAC_START1, data); + data = (pout->div_frac_start >> 8); + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_DIV_FRAC_START2, data); + data = (pout->div_frac_start >> 16); + data &= 0x0f; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_DIV_FRAC_START3, data); + + data = pout->plllock_cmp; + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLLLOCK_CMP1, data); + data = (pout->plllock_cmp >> 8); + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLLLOCK_CMP2, data); + data = (pout->plllock_cmp >> 16); + data &= 0x03; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLLLOCK_CMP3, data); + + data = ((pin->plllock_cnt << 1) | (pin->plllock_rng << 3)); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLLLOCK_CMP_EN, data); + + data = pout->pll_vco_count; + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_VCO_COUNT1, data); + data = (pout->pll_vco_count >> 8); + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_VCO_COUNT2, data); + + data = pout->pll_kvco_count; + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_KVCO_COUNT1, data); + data = (pout->pll_kvco_count >> 8); + data &= 0x03; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_KVCO_COUNT2, data); + + /* + * tx_band = pll_postdiv + * 0: divided by 1 <== for now + * 1: divided by 2 + * 2: divided by 4 + * 3: divided by 8 + */ + data = (((pout->pll_postdiv - 1) << 4) | pdb->in.pll_lpf_res1); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLL_LPF2_POSTDIV, data); + + data = (pout->pll_n1div | (pout->pll_n2div << 4)); + MDSS_PLL_REG_W(pll_base, DSIPHY_CMN_CLK_CFG0, data); + + if (pll->ssc_en) + pll_db_commit_ssc(pll, pdb); + + wmb(); /* make sure register committed */ +} + +/* + * pll_source_finding: + * Both GLBL_TEST_CTRL and CLKBUFLR_EN are configured + * at mdss_dsi_8996_phy_config() + */ +static int pll_source_finding(struct mdss_pll_resources *pll) +{ + u32 clk_buf_en; + u32 glbl_test_ctrl; + + glbl_test_ctrl = MDSS_PLL_REG_R(pll->pll_base, + DSIPHY_CMN_GLBL_TEST_CTRL); + clk_buf_en = MDSS_PLL_REG_R(pll->pll_base, + DSIPHY_PLL_CLKBUFLR_EN); + + glbl_test_ctrl &= BIT(2); + glbl_test_ctrl >>= 2; + + pr_debug("%s: pll=%d clk_buf_en=%x glbl_test_ctrl=%x\n", + __func__, pll->index, clk_buf_en, glbl_test_ctrl); + + clk_buf_en &= (PLL_OUTPUT_RIGHT | PLL_OUTPUT_LEFT); + + if ((glbl_test_ctrl == PLL_SOURCE_FROM_LEFT) && + (clk_buf_en == PLL_OUTPUT_BOTH)) + return PLL_MASTER; + + if ((glbl_test_ctrl == PLL_SOURCE_FROM_RIGHT) && + (clk_buf_en == PLL_OUTPUT_NONE)) + return PLL_SLAVE; + + if ((glbl_test_ctrl == PLL_SOURCE_FROM_LEFT) && + (clk_buf_en == PLL_OUTPUT_RIGHT)) + return PLL_STANDALONE; + + pr_debug("%s: Error pll setup, clk_buf_en=%x glbl_test_ctrl=%x\n", + __func__, clk_buf_en, glbl_test_ctrl); + + return PLL_UNKNOWN; +} + +static void pll_source_setup(struct mdss_pll_resources *pll) +{ + int status; + struct dsi_pll_db *pdb = (struct dsi_pll_db *)pll->priv; + struct mdss_pll_resources *other; + + if (pdb->source_setup_done) + return; + + pdb->source_setup_done++; + + status = pll_source_finding(pll); + + if (status == PLL_STANDALONE || status == PLL_UNKNOWN) + return; + + other = pdb->next->pll; + if (!other) + return; + + pr_debug("%s: status=%d pll=%d other=%d\n", __func__, + status, pll->index, other->index); + + if (status == PLL_MASTER) + pll->slave = other; + else + other->slave = pll; +} + +int pll_vco_set_rate_8996(struct clk *c, unsigned long rate) +{ + int rc; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *pll = vco->priv; + struct mdss_pll_resources *slave; + struct dsi_pll_db *pdb; + + pdb = (struct dsi_pll_db *)pll->priv; + if (!pdb) { + pr_err("No prov found\n"); + return -EINVAL; + } + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi plla=%d\n", pll->index); + return rc; + } + + pll_source_setup(pll); + + pr_debug("%s: ndx=%d base=%p rate=%lu slave=%p\n", __func__, + pll->index, pll->pll_base, rate, pll->slave); + + pll->vco_current_rate = rate; + pll->vco_ref_clk_rate = vco->ref_clk_rate; + + mdss_dsi_pll_8996_input_init(pll, pdb); + + pll_8996_dec_frac_calc(pll, pdb); + + if (pll->ssc_en) + pll_8996_ssc_calc(pll, pdb); + + pll_8996_calc_vco_count(pdb, pll->vco_current_rate, + pll->vco_ref_clk_rate); + + /* commit slave if split display is enabled */ + slave = pll->slave; + if (slave) + pll_db_commit_8996(slave, pdb); + + /* commit master itself */ + pll_db_commit_8996(pll, pdb); + + mdss_pll_resource_enable(pll, false); + + return rc; +} + +static void shadow_pll_dynamic_refresh_8996(struct mdss_pll_resources *pll, + struct dsi_pll_db *pdb) +{ + struct dsi_pll_output *pout = &pdb->out; + + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL20, + DSIPHY_CMN_CTRL_0, DSIPHY_PLL_SYSCLK_EN_RESET, + 0xFF, 0x0); + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL21, + DSIPHY_PLL_DEC_START, DSIPHY_PLL_DIV_FRAC_START1, + pout->dec_start, (pout->div_frac_start & 0x0FF)); + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL22, + DSIPHY_PLL_DIV_FRAC_START2, DSIPHY_PLL_DIV_FRAC_START3, + ((pout->div_frac_start >> 8) & 0x0FF), + ((pout->div_frac_start >> 16) & 0x0F)); + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL23, + DSIPHY_PLL_PLLLOCK_CMP1, DSIPHY_PLL_PLLLOCK_CMP2, + (pout->plllock_cmp & 0x0FF), + ((pout->plllock_cmp >> 8) & 0x0FF)); + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL24, + DSIPHY_PLL_PLLLOCK_CMP3, DSIPHY_PLL_PLL_VCO_TUNE, + ((pout->plllock_cmp >> 16) & 0x03), + (pll->cache_pll_trim_codes[1] | BIT(7))); /* VCO tune*/ + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL25, + DSIPHY_PLL_KVCO_CODE, DSIPHY_PLL_RESETSM_CNTRL, + (pll->cache_pll_trim_codes[0] | BIT(5)), 0x38); + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL26, + DSIPHY_PLL_PLL_LPF2_POSTDIV, DSIPHY_CMN_PLL_CNTRL, + (((pout->pll_postdiv - 1) << 4) | pdb->in.pll_lpf_res1), 0x01); + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL27, + DSIPHY_CMN_PLL_CNTRL, DSIPHY_CMN_PLL_CNTRL, + 0x01, 0x01); + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL28, + DSIPHY_CMN_PLL_CNTRL, DSIPHY_CMN_PLL_CNTRL, + 0x01, 0x01); + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL29, + DSIPHY_CMN_PLL_CNTRL, DSIPHY_CMN_PLL_CNTRL, + 0x01, 0x01); + MDSS_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_UPPER_ADDR, 0x0000001E); + MDSS_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_UPPER_ADDR2, 0x001FFE00); + + /* + * Ensure all the dynamic refresh registers are written before + * dynamic refresh to change the fps is triggered + */ + wmb(); +} + +int shadow_pll_vco_set_rate_8996(struct clk *c, unsigned long rate) +{ + int rc; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *pll = vco->priv; + struct dsi_pll_db *pdb; + s64 vco_clk_rate = (s64)rate; + + if (!pll) { + pr_err("PLL data not found\n"); + return -EINVAL; + } + + pdb = pll->priv; + if (!pdb) { + pr_err("No priv data found\n"); + return -EINVAL; + } + + rc = mdss_pll_read_stored_trim_codes(pll, vco_clk_rate); + if (rc) { + pr_err("cannot find pll codes rate=%lld\n", vco_clk_rate); + return -EINVAL; + } + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi plla=%d\n", pll->index); + return rc; + } + + pr_debug("%s: ndx=%d base=%p rate=%lu\n", __func__, + pll->index, pll->pll_base, rate); + + pll->vco_current_rate = rate; + pll->vco_ref_clk_rate = vco->ref_clk_rate; + + mdss_dsi_pll_8996_input_init(pll, pdb); + + pll_8996_dec_frac_calc(pll, pdb); + + pll_8996_calc_vco_count(pdb, pll->vco_current_rate, + pll->vco_ref_clk_rate); + + shadow_pll_dynamic_refresh_8996(pll, pdb); + + rc = mdss_pll_resource_enable(pll, false); + if (rc) { + pr_err("Failed to enable mdss dsi plla=%d\n", pll->index); + return rc; + } + + return rc; +} + +unsigned long pll_vco_get_rate_8996(struct clk *c) +{ + u64 vco_rate, multiplier = BIT(20); + s32 div_frac_start; + u32 dec_start; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + u64 ref_clk = vco->ref_clk_rate; + int rc; + struct mdss_pll_resources *pll = vco->priv; + + if (is_gdsc_disabled(pll)) + return 0; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll=%d\n", pll->index); + return rc; + } + + dec_start = MDSS_PLL_REG_R(pll->pll_base, + DSIPHY_PLL_DEC_START); + dec_start &= 0x0ff; + pr_debug("dec_start = 0x%x\n", dec_start); + + div_frac_start = (MDSS_PLL_REG_R(pll->pll_base, + DSIPHY_PLL_DIV_FRAC_START3) & 0x0f) << 16; + div_frac_start |= (MDSS_PLL_REG_R(pll->pll_base, + DSIPHY_PLL_DIV_FRAC_START2) & 0x0ff) << 8; + div_frac_start |= MDSS_PLL_REG_R(pll->pll_base, + DSIPHY_PLL_DIV_FRAC_START1) & 0x0ff; + pr_debug("div_frac_start = 0x%x\n", div_frac_start); + + vco_rate = ref_clk * dec_start; + vco_rate += ((ref_clk * div_frac_start) / multiplier); + + pr_debug("returning vco rate = %lu\n", (unsigned long)vco_rate); + + mdss_pll_resource_enable(pll, false); + + return (unsigned long)vco_rate; +} + +long pll_vco_round_rate_8996(struct clk *c, unsigned long rate) +{ + unsigned long rrate = rate; + u32 div; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + + div = vco->min_rate / rate; + if (div > 15) { + /* rate < 86.67 Mhz */ + pr_err("rate=%lu NOT supportted\n", rate); + return -EINVAL; + } + + if (rate < vco->min_rate) + rrate = vco->min_rate; + if (rate > vco->max_rate) + rrate = vco->max_rate; + + return rrate; +} + +enum handoff pll_vco_handoff_8996(struct clk *c) +{ + int rc; + enum handoff ret = HANDOFF_DISABLED_CLK; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *pll = vco->priv; + + if (is_gdsc_disabled(pll)) + return HANDOFF_DISABLED_CLK; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll=%d\n", pll->index); + return ret; + } + + if (pll_is_pll_locked_8996(pll)) { + pll->handoff_resources = true; + pll->pll_on = true; + c->rate = pll_vco_get_rate_8996(c); + ret = HANDOFF_ENABLED_CLK; + } else { + mdss_pll_resource_enable(pll, false); + } + + return ret; +} + +enum handoff shadow_pll_vco_handoff_8996(struct clk *c) +{ + return HANDOFF_DISABLED_CLK; +} + +int pll_vco_prepare_8996(struct clk *c) +{ + int rc = 0; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *pll = vco->priv; + + if (!pll) { + pr_err("Dsi pll resources are not available\n"); + return -EINVAL; + } + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("ndx=%d Failed to enable mdss dsi pll resources\n", + pll->index); + return rc; + } + + if ((pll->vco_cached_rate != 0) + && (pll->vco_cached_rate == c->rate)) { + rc = c->ops->set_rate(c, pll->vco_cached_rate); + if (rc) { + pr_err("index=%d vco_set_rate failed. rc=%d\n", + rc, pll->index); + mdss_pll_resource_enable(pll, false); + goto error; + } + } + + rc = dsi_pll_enable(c); + + if (rc) { + mdss_pll_resource_enable(pll, false); + pr_err("ndx=%d failed to enable dsi pll\n", pll->index); + } + +error: + return rc; +} + +void pll_vco_unprepare_8996(struct clk *c) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *pll = vco->priv; + + if (!pll) { + pr_err("Dsi pll resources are not available\n"); + return; + } + + pll->vco_cached_rate = c->rate; + dsi_pll_disable(c); +} diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-8996.c b/drivers/clk/qcom/mdss/mdss-dsi-pll-8996.c new file mode 100644 index 000000000000..e9e484c3cc9f --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-8996.c @@ -0,0 +1,566 @@ +/* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dsi-pll.h" +#include "mdss-dsi-pll-8996.h" + +#define VCO_DELAY_USEC 1 + +static struct dsi_pll_db pll_db[DSI_PLL_NUM]; + +static const struct clk_ops n2_clk_src_ops; +static const struct clk_ops shadow_n2_clk_src_ops; +static const struct clk_ops byte_clk_src_ops; +static const struct clk_ops post_n1_div_clk_src_ops; +static const struct clk_ops shadow_post_n1_div_clk_src_ops; + +static const struct clk_ops clk_ops_gen_mux_dsi; + +/* Op structures */ +static const struct clk_ops clk_ops_dsi_vco = { + .set_rate = pll_vco_set_rate_8996, + .round_rate = pll_vco_round_rate_8996, + .handoff = pll_vco_handoff_8996, + .prepare = pll_vco_prepare_8996, + .unprepare = pll_vco_unprepare_8996, +}; + +static struct clk_div_ops post_n1_div_ops = { + .set_div = post_n1_div_set_div, + .get_div = post_n1_div_get_div, +}; + +static struct clk_div_ops n2_div_ops = { /* hr_oclk3 */ + .set_div = n2_div_set_div, + .get_div = n2_div_get_div, +}; + +static struct clk_mux_ops mdss_byte_mux_ops = { + .set_mux_sel = set_mdss_byte_mux_sel_8996, + .get_mux_sel = get_mdss_byte_mux_sel_8996, +}; + +static struct clk_mux_ops mdss_pixel_mux_ops = { + .set_mux_sel = set_mdss_pixel_mux_sel_8996, + .get_mux_sel = get_mdss_pixel_mux_sel_8996, +}; + +/* Shadow ops for dynamic refresh */ +static const struct clk_ops clk_ops_shadow_dsi_vco = { + .set_rate = shadow_pll_vco_set_rate_8996, + .round_rate = pll_vco_round_rate_8996, + .handoff = shadow_pll_vco_handoff_8996, +}; + +static struct clk_div_ops shadow_post_n1_div_ops = { + .set_div = post_n1_div_set_div, +}; + +static struct clk_div_ops shadow_n2_div_ops = { + .set_div = shadow_n2_div_set_div, +}; + +static struct dsi_pll_vco_clk dsi0pll_vco_clk = { + .ref_clk_rate = 19200000UL, + .min_rate = 1300000000UL, + .max_rate = 2600000000UL, + .pll_en_seq_cnt = 1, + .pll_enable_seqs[0] = dsi_pll_enable_seq_8996, + .c = { + .dbg_name = "dsi0pll_vco_clk_8996", + .ops = &clk_ops_dsi_vco, + CLK_INIT(dsi0pll_vco_clk.c), + }, +}; + +static struct dsi_pll_vco_clk dsi0pll_shadow_vco_clk = { + .ref_clk_rate = 19200000u, + .min_rate = 1300000000u, + .max_rate = 2600000000u, + .c = { + .dbg_name = "dsi0pll_shadow_vco_clk", + .ops = &clk_ops_shadow_dsi_vco, + CLK_INIT(dsi0pll_shadow_vco_clk.c), + }, +}; + +static struct dsi_pll_vco_clk dsi1pll_vco_clk = { + .ref_clk_rate = 19200000UL, + .min_rate = 1300000000UL, + .max_rate = 2600000000UL, + .pll_en_seq_cnt = 1, + .pll_enable_seqs[0] = dsi_pll_enable_seq_8996, + .c = { + .dbg_name = "dsi1pll_vco_clk_8996", + .ops = &clk_ops_dsi_vco, + CLK_INIT(dsi1pll_vco_clk.c), + }, +}; + +static struct dsi_pll_vco_clk dsi1pll_shadow_vco_clk = { + .ref_clk_rate = 19200000u, + .min_rate = 1300000000u, + .max_rate = 2600000000u, + .pll_en_seq_cnt = 1, + .pll_enable_seqs[0] = dsi_pll_enable_seq_8996, + .c = { + .dbg_name = "dsi1pll_shadow_vco_clk", + .ops = &clk_ops_shadow_dsi_vco, + CLK_INIT(dsi1pll_shadow_vco_clk.c), + }, +}; + +static struct div_clk dsi0pll_post_n1_div_clk = { + .data = { + .max_div = 15, + .min_div = 1, + }, + .ops = &post_n1_div_ops, + .c = { + .parent = &dsi0pll_vco_clk.c, + .dbg_name = "dsi0pll_post_n1_div_clk", + .ops = &post_n1_div_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi0pll_post_n1_div_clk.c), + }, +}; + +static struct div_clk dsi0pll_shadow_post_n1_div_clk = { + .data = { + .max_div = 15, + .min_div = 1, + }, + .ops = &shadow_post_n1_div_ops, + .c = { + .parent = &dsi0pll_shadow_vco_clk.c, + .dbg_name = "dsi0pll_shadow_post_n1_div_clk", + .ops = &shadow_post_n1_div_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi0pll_shadow_post_n1_div_clk.c), + }, +}; + +static struct div_clk dsi1pll_post_n1_div_clk = { + .data = { + .max_div = 15, + .min_div = 1, + }, + .ops = &post_n1_div_ops, + .c = { + .parent = &dsi1pll_vco_clk.c, + .dbg_name = "dsi1pll_post_n1_div_clk", + .ops = &post_n1_div_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi1pll_post_n1_div_clk.c), + }, +}; + +static struct div_clk dsi1pll_shadow_post_n1_div_clk = { + .data = { + .max_div = 15, + .min_div = 1, + }, + .ops = &shadow_post_n1_div_ops, + .c = { + .parent = &dsi1pll_shadow_vco_clk.c, + .dbg_name = "dsi1pll_shadow_post_n1_div_clk", + .ops = &shadow_post_n1_div_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi1pll_shadow_post_n1_div_clk.c), + }, +}; + +static struct div_clk dsi0pll_n2_div_clk = { + .data = { + .max_div = 15, + .min_div = 1, + }, + .ops = &n2_div_ops, + .c = { + .parent = &dsi0pll_post_n1_div_clk.c, + .dbg_name = "dsi0pll_n2_div_clk", + .ops = &n2_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi0pll_n2_div_clk.c), + }, +}; + +static struct div_clk dsi0pll_shadow_n2_div_clk = { + .data = { + .max_div = 15, + .min_div = 1, + }, + .ops = &shadow_n2_div_ops, + .c = { + .parent = &dsi0pll_shadow_post_n1_div_clk.c, + .dbg_name = "dsi0pll_shadow_n2_div_clk", + .ops = &shadow_n2_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi0pll_shadow_n2_div_clk.c), + }, +}; + +static struct div_clk dsi1pll_n2_div_clk = { + .data = { + .max_div = 15, + .min_div = 1, + }, + .ops = &n2_div_ops, + .c = { + .parent = &dsi1pll_post_n1_div_clk.c, + .dbg_name = "dsi1pll_n2_div_clk", + .ops = &n2_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi1pll_n2_div_clk.c), + }, +}; + +static struct div_clk dsi1pll_shadow_n2_div_clk = { + .data = { + .max_div = 15, + .min_div = 1, + }, + .ops = &shadow_n2_div_ops, + .c = { + .parent = &dsi1pll_shadow_post_n1_div_clk.c, + .dbg_name = "dsi1pll_shadow_n2_div_clk", + .ops = &shadow_n2_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi1pll_shadow_n2_div_clk.c), + }, +}; + +static struct div_clk dsi0pll_pixel_clk_src = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &dsi0pll_n2_div_clk.c, + .dbg_name = "dsi0pll_pixel_clk_src", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi0pll_pixel_clk_src.c), + }, +}; + +static struct div_clk dsi0pll_shadow_pixel_clk_src = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &dsi0pll_shadow_n2_div_clk.c, + .dbg_name = "dsi0pll_shadow_pixel_clk_src", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi0pll_shadow_pixel_clk_src.c), + }, +}; + +static struct div_clk dsi1pll_pixel_clk_src = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &dsi1pll_n2_div_clk.c, + .dbg_name = "dsi1pll_pixel_clk_src", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi1pll_pixel_clk_src.c), + }, +}; + +static struct div_clk dsi1pll_shadow_pixel_clk_src = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &dsi1pll_shadow_n2_div_clk.c, + .dbg_name = "dsi1pll_shadow_pixel_clk_src", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi1pll_shadow_pixel_clk_src.c), + }, +}; + +static struct mux_clk dsi0pll_pixel_clk_mux = { + .num_parents = 2, + .parents = (struct clk_src[]) { + {&dsi0pll_pixel_clk_src.c, 0}, + {&dsi0pll_shadow_pixel_clk_src.c, 1}, + }, + .ops = &mdss_pixel_mux_ops, + .c = { + .parent = &dsi0pll_pixel_clk_src.c, + .dbg_name = "dsi0pll_pixel_clk_mux", + .ops = &clk_ops_gen_mux_dsi, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi0pll_pixel_clk_mux.c), + } +}; + +static struct mux_clk dsi1pll_pixel_clk_mux = { + .num_parents = 2, + .parents = (struct clk_src[]) { + {&dsi1pll_pixel_clk_src.c, 0}, + {&dsi1pll_shadow_pixel_clk_src.c, 1}, + }, + .ops = &mdss_pixel_mux_ops, + .c = { + .parent = &dsi1pll_pixel_clk_src.c, + .dbg_name = "dsi1pll_pixel_clk_mux", + .ops = &clk_ops_gen_mux_dsi, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi1pll_pixel_clk_mux.c), + } +}; + +static struct div_clk dsi0pll_byte_clk_src = { + .data = { + .div = 8, + .min_div = 8, + .max_div = 8, + }, + .c = { + .parent = &dsi0pll_post_n1_div_clk.c, + .dbg_name = "dsi0pll_byte_clk_src", + .ops = &clk_ops_div, + CLK_INIT(dsi0pll_byte_clk_src.c), + }, +}; + +static struct div_clk dsi0pll_shadow_byte_clk_src = { + .data = { + .div = 8, + .min_div = 8, + .max_div = 8, + }, + .c = { + .parent = &dsi0pll_shadow_post_n1_div_clk.c, + .dbg_name = "dsi0pll_shadow_byte_clk_src", + .ops = &clk_ops_div, + CLK_INIT(dsi0pll_shadow_byte_clk_src.c), + }, +}; + +static struct div_clk dsi1pll_byte_clk_src = { + .data = { + .div = 8, + .min_div = 8, + .max_div = 8, + }, + .c = { + .parent = &dsi1pll_post_n1_div_clk.c, + .dbg_name = "dsi1pll_byte_clk_src", + .ops = &clk_ops_div, + CLK_INIT(dsi1pll_byte_clk_src.c), + }, +}; + +static struct div_clk dsi1pll_shadow_byte_clk_src = { + .data = { + .div = 8, + .min_div = 8, + .max_div = 8, + }, + .c = { + .parent = &dsi1pll_shadow_post_n1_div_clk.c, + .dbg_name = "dsi1pll_shadow_byte_clk_src", + .ops = &clk_ops_div, + CLK_INIT(dsi1pll_shadow_byte_clk_src.c), + }, +}; + +static struct mux_clk dsi0pll_byte_clk_mux = { + .num_parents = 2, + .parents = (struct clk_src[]) { + {&dsi0pll_byte_clk_src.c, 0}, + {&dsi0pll_shadow_byte_clk_src.c, 1}, + }, + .ops = &mdss_byte_mux_ops, + .c = { + .parent = &dsi0pll_byte_clk_src.c, + .dbg_name = "dsi0pll_byte_clk_mux", + .ops = &clk_ops_gen_mux_dsi, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi0pll_byte_clk_mux.c), + } +}; +static struct mux_clk dsi1pll_byte_clk_mux = { + .num_parents = 2, + .parents = (struct clk_src[]) { + {&dsi1pll_byte_clk_src.c, 0}, + {&dsi1pll_shadow_byte_clk_src.c, 1}, + }, + .ops = &mdss_byte_mux_ops, + .c = { + .parent = &dsi1pll_byte_clk_src.c, + .dbg_name = "dsi1pll_byte_clk_mux", + .ops = &clk_ops_gen_mux_dsi, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi1pll_byte_clk_mux.c), + } +}; + +static struct clk_lookup mdss_dsi_pllcc_8996[] = { + CLK_LIST(dsi0pll_byte_clk_mux), + CLK_LIST(dsi0pll_byte_clk_src), + CLK_LIST(dsi0pll_pixel_clk_mux), + CLK_LIST(dsi0pll_pixel_clk_src), + CLK_LIST(dsi0pll_n2_div_clk), + CLK_LIST(dsi0pll_post_n1_div_clk), + CLK_LIST(dsi0pll_vco_clk), + CLK_LIST(dsi0pll_shadow_byte_clk_src), + CLK_LIST(dsi0pll_shadow_pixel_clk_src), + CLK_LIST(dsi0pll_shadow_n2_div_clk), + CLK_LIST(dsi0pll_shadow_post_n1_div_clk), + CLK_LIST(dsi0pll_shadow_vco_clk), +}; + +static struct clk_lookup mdss_dsi_pllcc_8996_1[] = { + CLK_LIST(dsi1pll_byte_clk_mux), + CLK_LIST(dsi1pll_byte_clk_src), + CLK_LIST(dsi1pll_pixel_clk_mux), + CLK_LIST(dsi1pll_pixel_clk_src), + CLK_LIST(dsi1pll_n2_div_clk), + CLK_LIST(dsi1pll_post_n1_div_clk), + CLK_LIST(dsi1pll_vco_clk), + CLK_LIST(dsi1pll_shadow_byte_clk_src), + CLK_LIST(dsi1pll_shadow_pixel_clk_src), + CLK_LIST(dsi1pll_shadow_n2_div_clk), + CLK_LIST(dsi1pll_shadow_post_n1_div_clk), + CLK_LIST(dsi1pll_shadow_vco_clk), +}; + +int dsi_pll_clock_register_8996(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = 0, ndx; + int const ssc_freq_default = 31500; /* default h/w recommended value */ + int const ssc_ppm_default = 5000; /* default h/w recommended value */ + struct dsi_pll_db *pdb; + + if (!pdev || !pdev->dev.of_node) { + pr_err("Invalid input parameters\n"); + return -EINVAL; + } + + if (!pll_res || !pll_res->pll_base) { + pr_err("Invalid PLL resources\n"); + return -EPROBE_DEFER; + } + + if (pll_res->index >= DSI_PLL_NUM) { + pr_err("pll ndx=%d is NOT supported\n", pll_res->index); + return -EINVAL; + } + + ndx = pll_res->index; + pdb = &pll_db[ndx]; + pll_res->priv = pdb; + pdb->pll = pll_res; + ndx++; + ndx %= DSI_PLL_NUM; + pdb->next = &pll_db[ndx]; + + /* Set clock source operations */ + + /* hr_oclk3, pixel */ + n2_clk_src_ops = clk_ops_slave_div; + n2_clk_src_ops.prepare = mdss_pll_div_prepare; + + shadow_n2_clk_src_ops = clk_ops_slave_div; + + /* hr_ockl2, byte, vco pll */ + post_n1_div_clk_src_ops = clk_ops_div; + post_n1_div_clk_src_ops.prepare = mdss_pll_div_prepare; + + shadow_post_n1_div_clk_src_ops = clk_ops_div; + + byte_clk_src_ops = clk_ops_div; + byte_clk_src_ops.prepare = mdss_pll_div_prepare; + + clk_ops_gen_mux_dsi = clk_ops_gen_mux; + clk_ops_gen_mux_dsi.round_rate = parent_round_rate; + clk_ops_gen_mux_dsi.set_rate = parent_set_rate; + + if (pll_res->ssc_en) { + if (!pll_res->ssc_freq) + pll_res->ssc_freq = ssc_freq_default; + if (!pll_res->ssc_ppm) + pll_res->ssc_ppm = ssc_ppm_default; + } + + /* Set client data to mux, div and vco clocks. */ + if (pll_res->index == DSI_PLL_1) { + dsi1pll_byte_clk_src.priv = pll_res; + dsi1pll_pixel_clk_src.priv = pll_res; + dsi1pll_post_n1_div_clk.priv = pll_res; + dsi1pll_n2_div_clk.priv = pll_res; + dsi1pll_vco_clk.priv = pll_res; + + dsi1pll_shadow_byte_clk_src.priv = pll_res; + dsi1pll_shadow_pixel_clk_src.priv = pll_res; + dsi1pll_shadow_post_n1_div_clk.priv = pll_res; + dsi1pll_shadow_n2_div_clk.priv = pll_res; + dsi1pll_shadow_vco_clk.priv = pll_res; + + pll_res->vco_delay = VCO_DELAY_USEC; + rc = of_msm_clock_register(pdev->dev.of_node, + mdss_dsi_pllcc_8996_1, + ARRAY_SIZE(mdss_dsi_pllcc_8996_1)); + } else { + dsi0pll_byte_clk_src.priv = pll_res; + dsi0pll_pixel_clk_src.priv = pll_res; + dsi0pll_post_n1_div_clk.priv = pll_res; + dsi0pll_n2_div_clk.priv = pll_res; + dsi0pll_vco_clk.priv = pll_res; + + dsi0pll_shadow_byte_clk_src.priv = pll_res; + dsi0pll_shadow_pixel_clk_src.priv = pll_res; + dsi0pll_shadow_post_n1_div_clk.priv = pll_res; + dsi0pll_shadow_n2_div_clk.priv = pll_res; + dsi0pll_shadow_vco_clk.priv = pll_res; + + pll_res->vco_delay = VCO_DELAY_USEC; + rc = of_msm_clock_register(pdev->dev.of_node, + mdss_dsi_pllcc_8996, + ARRAY_SIZE(mdss_dsi_pllcc_8996)); + } + + if (!rc) { + pr_info("Registered DSI PLL ndx=%d clocks successfully\n", + pll_res->index); + } + + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-8996.h b/drivers/clk/qcom/mdss/mdss-dsi-pll-8996.h new file mode 100644 index 000000000000..783a18449495 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-8996.h @@ -0,0 +1,221 @@ +/* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MDSS_DSI_PLL_8996_H +#define MDSS_DSI_PLL_8996_H + +#define DSIPHY_CMN_CLK_CFG0 0x0010 +#define DSIPHY_CMN_CLK_CFG1 0x0014 +#define DSIPHY_CMN_GLBL_TEST_CTRL 0x0018 + +#define DSIPHY_CMN_PLL_CNTRL 0x0048 +#define DSIPHY_CMN_CTRL_0 0x001c +#define DSIPHY_CMN_CTRL_1 0x0020 + +#define DSIPHY_CMN_LDO_CNTRL 0x004c + +#define DSIPHY_PLL_IE_TRIM 0x0400 +#define DSIPHY_PLL_IP_TRIM 0x0404 + +#define DSIPHY_PLL_IPTAT_TRIM 0x0410 + +#define DSIPHY_PLL_CLKBUFLR_EN 0x041c + +#define DSIPHY_PLL_SYSCLK_EN_RESET 0x0428 +#define DSIPHY_PLL_RESETSM_CNTRL 0x042c +#define DSIPHY_PLL_RESETSM_CNTRL2 0x0430 +#define DSIPHY_PLL_RESETSM_CNTRL3 0x0434 +#define DSIPHY_PLL_RESETSM_CNTRL4 0x0438 +#define DSIPHY_PLL_RESETSM_CNTRL5 0x043c +#define DSIPHY_PLL_KVCO_DIV_REF1 0x0440 +#define DSIPHY_PLL_KVCO_DIV_REF2 0x0444 +#define DSIPHY_PLL_KVCO_COUNT1 0x0448 +#define DSIPHY_PLL_KVCO_COUNT2 0x044c +#define DSIPHY_PLL_VREF_CFG1 0x045c + +#define DSIPHY_PLL_KVCO_CODE 0x0458 + +#define DSIPHY_PLL_VCO_DIV_REF1 0x046c +#define DSIPHY_PLL_VCO_DIV_REF2 0x0470 +#define DSIPHY_PLL_VCO_COUNT1 0x0474 +#define DSIPHY_PLL_VCO_COUNT2 0x0478 +#define DSIPHY_PLL_PLLLOCK_CMP1 0x047c +#define DSIPHY_PLL_PLLLOCK_CMP2 0x0480 +#define DSIPHY_PLL_PLLLOCK_CMP3 0x0484 +#define DSIPHY_PLL_PLLLOCK_CMP_EN 0x0488 +#define DSIPHY_PLL_PLL_VCO_TUNE 0x048C +#define DSIPHY_PLL_DEC_START 0x0490 +#define DSIPHY_PLL_SSC_EN_CENTER 0x0494 +#define DSIPHY_PLL_SSC_ADJ_PER1 0x0498 +#define DSIPHY_PLL_SSC_ADJ_PER2 0x049c +#define DSIPHY_PLL_SSC_PER1 0x04a0 +#define DSIPHY_PLL_SSC_PER2 0x04a4 +#define DSIPHY_PLL_SSC_STEP_SIZE1 0x04a8 +#define DSIPHY_PLL_SSC_STEP_SIZE2 0x04ac +#define DSIPHY_PLL_DIV_FRAC_START1 0x04b4 +#define DSIPHY_PLL_DIV_FRAC_START2 0x04b8 +#define DSIPHY_PLL_DIV_FRAC_START3 0x04bc +#define DSIPHY_PLL_TXCLK_EN 0x04c0 +#define DSIPHY_PLL_PLL_CRCTRL 0x04c4 + +#define DSIPHY_PLL_RESET_SM_READY_STATUS 0x04cc + +#define DSIPHY_PLL_PLL_MISC1 0x04e8 + +#define DSIPHY_PLL_CP_SET_CUR 0x04f0 +#define DSIPHY_PLL_PLL_ICPMSET 0x04f4 +#define DSIPHY_PLL_PLL_ICPCSET 0x04f8 +#define DSIPHY_PLL_PLL_ICP_SET 0x04fc +#define DSIPHY_PLL_PLL_LPF1 0x0500 +#define DSIPHY_PLL_PLL_LPF2_POSTDIV 0x0504 +#define DSIPHY_PLL_PLL_BANDGAP 0x0508 + +#define DSI_DYNAMIC_REFRESH_PLL_CTRL15 0x050 +#define DSI_DYNAMIC_REFRESH_PLL_CTRL19 0x060 +#define DSI_DYNAMIC_REFRESH_PLL_CTRL20 0x064 +#define DSI_DYNAMIC_REFRESH_PLL_CTRL21 0x068 +#define DSI_DYNAMIC_REFRESH_PLL_CTRL22 0x06C +#define DSI_DYNAMIC_REFRESH_PLL_CTRL23 0x070 +#define DSI_DYNAMIC_REFRESH_PLL_CTRL24 0x074 +#define DSI_DYNAMIC_REFRESH_PLL_CTRL25 0x078 +#define DSI_DYNAMIC_REFRESH_PLL_CTRL26 0x07C +#define DSI_DYNAMIC_REFRESH_PLL_CTRL27 0x080 +#define DSI_DYNAMIC_REFRESH_PLL_CTRL28 0x084 +#define DSI_DYNAMIC_REFRESH_PLL_CTRL29 0x088 +#define DSI_DYNAMIC_REFRESH_PLL_UPPER_ADDR 0x094 +#define DSI_DYNAMIC_REFRESH_PLL_UPPER_ADDR2 0x098 + +struct dsi_pll_input { + u32 fref; /* 19.2 Mhz, reference clk */ + u32 fdata; /* bit clock rate */ + u32 dsiclk_sel; /* 1, reg: 0x0014 */ + u32 n2div; /* 1, reg: 0x0010, bit 4-7 */ + u32 ssc_en; /* 1, reg: 0x0494, bit 0 */ + u32 ldo_en; /* 0, reg: 0x004c, bit 0 */ + + /* fixed */ + u32 refclk_dbler_en; /* 0, reg: 0x04c0, bit 1 */ + u32 vco_measure_time; /* 5, unknown */ + u32 kvco_measure_time; /* 5, unknown */ + u32 bandgap_timer; /* 4, reg: 0x0430, bit 3 - 5 */ + u32 pll_wakeup_timer; /* 5, reg: 0x043c, bit 0 - 2 */ + u32 plllock_cnt; /* 1, reg: 0x0488, bit 1 - 2 */ + u32 plllock_rng; /* 1, reg: 0x0488, bit 3 - 4 */ + u32 ssc_center; /* 0, reg: 0x0494, bit 1 */ + u32 ssc_adj_period; /* 37, reg: 0x498, bit 0 - 9 */ + u32 ssc_spread; /* 0.005 */ + u32 ssc_freq; /* unknown */ + u32 pll_ie_trim; /* 4, reg: 0x0400 */ + u32 pll_ip_trim; /* 4, reg: 0x0404 */ + u32 pll_iptat_trim; /* reg: 0x0410 */ + u32 pll_cpcset_cur; /* 1, reg: 0x04f0, bit 0 - 2 */ + u32 pll_cpmset_cur; /* 1, reg: 0x04f0, bit 3 - 5 */ + + u32 pll_icpmset; /* 4, reg: 0x04fc, bit 3 - 5 */ + u32 pll_icpcset; /* 4, reg: 0x04fc, bit 0 - 2 */ + + u32 pll_icpmset_p; /* 0, reg: 0x04f4, bit 0 - 2 */ + u32 pll_icpmset_m; /* 0, reg: 0x04f4, bit 3 - 5 */ + + u32 pll_icpcset_p; /* 0, reg: 0x04f8, bit 0 - 2 */ + u32 pll_icpcset_m; /* 0, reg: 0x04f8, bit 3 - 5 */ + + u32 pll_lpf_res1; /* 3, reg: 0x0504, bit 0 - 3 */ + u32 pll_lpf_cap1; /* 11, reg: 0x0500, bit 0 - 3 */ + u32 pll_lpf_cap2; /* 1, reg: 0x0500, bit 4 - 7 */ + u32 pll_c3ctrl; /* 2, reg: 0x04c4 */ + u32 pll_r3ctrl; /* 1, reg: 0x04c4 */ +}; + +struct dsi_pll_output { + u32 pll_txclk_en; /* reg: 0x04c0 */ + u32 dec_start; /* reg: 0x0490 */ + u32 div_frac_start; /* reg: 0x04b4, 0x4b8, 0x04bc */ + u32 ssc_period; /* reg: 0x04a0, 0x04a4 */ + u32 ssc_step_size; /* reg: 0x04a8, 0x04ac */ + u32 plllock_cmp; /* reg: 0x047c, 0x0480, 0x0484 */ + u32 pll_vco_div_ref; /* reg: 0x046c, 0x0470 */ + u32 pll_vco_count; /* reg: 0x0474, 0x0478 */ + u32 pll_kvco_div_ref; /* reg: 0x0440, 0x0444 */ + u32 pll_kvco_count; /* reg: 0x0448, 0x044c */ + u32 pll_misc1; /* reg: 0x04e8 */ + u32 pll_lpf2_postdiv; /* reg: 0x0504 */ + u32 pll_resetsm_cntrl; /* reg: 0x042c */ + u32 pll_resetsm_cntrl2; /* reg: 0x0430 */ + u32 pll_resetsm_cntrl5; /* reg: 0x043c */ + u32 pll_kvco_code; /* reg: 0x0458 */ + + u32 cmn_clk_cfg0; /* reg: 0x0010 */ + u32 cmn_clk_cfg1; /* reg: 0x0014 */ + u32 cmn_ldo_cntrl; /* reg: 0x004c */ + + u32 pll_postdiv; /* vco */ + u32 pll_n1div; /* vco */ + u32 pll_n2div; /* hr_oclk3, pixel */ + u32 fcvo; +}; + +enum { + DSI_PLL_0, + DSI_PLL_1, + DSI_PLL_NUM +}; + +struct dsi_pll_db { + struct dsi_pll_db *next; + struct mdss_pll_resources *pll; + struct dsi_pll_input in; + struct dsi_pll_output out; + int source_setup_done; +}; + +enum { + PLL_OUTPUT_NONE, + PLL_OUTPUT_RIGHT, + PLL_OUTPUT_LEFT, + PLL_OUTPUT_BOTH +}; + +enum { + PLL_SOURCE_FROM_LEFT, + PLL_SOURCE_FROM_RIGHT +}; + +enum { + PLL_UNKNOWN, + PLL_STANDALONE, + PLL_SLAVE, + PLL_MASTER +}; + +int pll_vco_set_rate_8996(struct clk *c, unsigned long rate); +long pll_vco_round_rate_8996(struct clk *c, unsigned long rate); +enum handoff pll_vco_handoff_8996(struct clk *c); +enum handoff shadow_pll_vco_handoff_8996(struct clk *c); +int shadow_post_n1_div_set_div(struct div_clk *clk, int div); +int shadow_post_n1_div_get_div(struct div_clk *clk); +int shadow_n2_div_set_div(struct div_clk *clk, int div); +int shadow_n2_div_get_div(struct div_clk *clk); +int shadow_pll_vco_set_rate_8996(struct clk *c, unsigned long rate); +int pll_vco_prepare_8996(struct clk *c); +void pll_vco_unprepare_8996(struct clk *c); +int set_mdss_byte_mux_sel_8996(struct mux_clk *clk, int sel); +int get_mdss_byte_mux_sel_8996(struct mux_clk *clk); +int set_mdss_pixel_mux_sel_8996(struct mux_clk *clk, int sel); +int get_mdss_pixel_mux_sel_8996(struct mux_clk *clk); +int post_n1_div_set_div(struct div_clk *clk, int div); +int post_n1_div_get_div(struct div_clk *clk); +int n2_div_set_div(struct div_clk *clk, int div); +int n2_div_get_div(struct div_clk *clk); +int dsi_pll_enable_seq_8996(struct mdss_pll_resources *pll); + +#endif /* MDSS_DSI_PLL_8996_H */ diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-util.c b/drivers/clk/qcom/mdss/mdss-dsi-pll-util.c new file mode 100644 index 000000000000..4e66b7837eb8 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-util.c @@ -0,0 +1,587 @@ +/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dsi-pll.h" + +#define DSI_PHY_PLL_UNIPHY_PLL_REFCLK_CFG (0x0) +#define DSI_PHY_PLL_UNIPHY_PLL_POSTDIV1_CFG (0x0004) +#define DSI_PHY_PLL_UNIPHY_PLL_CHGPUMP_CFG (0x0008) +#define DSI_PHY_PLL_UNIPHY_PLL_VCOLPF_CFG (0x000C) +#define DSI_PHY_PLL_UNIPHY_PLL_VREG_CFG (0x0010) +#define DSI_PHY_PLL_UNIPHY_PLL_PWRGEN_CFG (0x0014) +#define DSI_PHY_PLL_UNIPHY_PLL_POSTDIV2_CFG (0x0024) +#define DSI_PHY_PLL_UNIPHY_PLL_POSTDIV3_CFG (0x0028) +#define DSI_PHY_PLL_UNIPHY_PLL_LPFR_CFG (0x002C) +#define DSI_PHY_PLL_UNIPHY_PLL_LPFC1_CFG (0x0030) +#define DSI_PHY_PLL_UNIPHY_PLL_LPFC2_CFG (0x0034) +#define DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG0 (0x0038) +#define DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG1 (0x003C) +#define DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG2 (0x0040) +#define DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG3 (0x0044) +#define DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG4 (0x0048) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG0 (0x006C) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG2 (0x0074) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG3 (0x0078) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG4 (0x007C) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG5 (0x0080) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG6 (0x0084) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG7 (0x0088) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG8 (0x008C) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG9 (0x0090) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG10 (0x0094) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG11 (0x0098) +#define DSI_PHY_PLL_UNIPHY_PLL_EFUSE_CFG (0x009C) +#define DSI_PHY_PLL_UNIPHY_PLL_STATUS (0x00C0) + +#define DSI_PLL_POLL_DELAY_US 50 +#define DSI_PLL_POLL_TIMEOUT_US 500 + +int set_byte_mux_sel(struct mux_clk *clk, int sel) +{ + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + pr_debug("byte mux set to %s mode\n", sel ? "indirect" : "direct"); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_VREG_CFG, (sel << 1)); + + return 0; +} + +int get_byte_mux_sel(struct mux_clk *clk) +{ + int mux_mode, rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + mux_mode = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_VREG_CFG) & BIT(1); + + pr_debug("byte mux mode = %s", mux_mode ? "indirect" : "direct"); + mdss_pll_resource_enable(dsi_pll_res, false); + + return !!mux_mode; +} + +int dsi_pll_div_prepare(struct clk *c) +{ + struct div_clk *div = to_div_clk(c); + /* Restore the divider's value */ + return div->ops->set_div(div, div->data.div); +} + +int dsi_pll_mux_prepare(struct clk *c) +{ + struct mux_clk *mux = to_mux_clk(c); + int i, rc, sel = 0; + struct mdss_pll_resources *dsi_pll_res = mux->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + for (i = 0; i < mux->num_parents; i++) + if (mux->parents[i].src == c->parent) { + sel = mux->parents[i].sel; + break; + } + + if (i == mux->num_parents) { + pr_err("Failed to select the parent clock\n"); + rc = -EINVAL; + goto error; + } + + /* Restore the mux source select value */ + rc = mux->ops->set_mux_sel(mux, sel); + +error: + mdss_pll_resource_enable(dsi_pll_res, false); + return rc; +} + +int fixed_4div_set_div(struct div_clk *clk, int div) +{ + int rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_POSTDIV2_CFG, (div - 1)); + + mdss_pll_resource_enable(dsi_pll_res, false); + return rc; +} + +int fixed_4div_get_div(struct div_clk *clk) +{ + int div = 0, rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + div = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_POSTDIV2_CFG); + + mdss_pll_resource_enable(dsi_pll_res, false); + return div + 1; +} + +int digital_set_div(struct div_clk *clk, int div) +{ + int rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_POSTDIV3_CFG, (div - 1)); + + mdss_pll_resource_enable(dsi_pll_res, false); + return rc; +} + +int digital_get_div(struct div_clk *clk) +{ + int div = 0, rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + div = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_POSTDIV3_CFG); + + mdss_pll_resource_enable(dsi_pll_res, false); + return div + 1; +} + +int analog_set_div(struct div_clk *clk, int div) +{ + int rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_POSTDIV1_CFG, div - 1); + + mdss_pll_resource_enable(dsi_pll_res, false); + return rc; +} + +int analog_get_div(struct div_clk *clk) +{ + int div = 0, rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(clk->priv, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + div = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_POSTDIV1_CFG) + 1; + + mdss_pll_resource_enable(dsi_pll_res, false); + + return div; +} + +int dsi_pll_lock_status(struct mdss_pll_resources *dsi_pll_res) +{ + u32 status; + int pll_locked; + + /* poll for PLL ready status */ + if (readl_poll_timeout_atomic((dsi_pll_res->pll_base + + DSI_PHY_PLL_UNIPHY_PLL_STATUS), + status, + ((status & BIT(0)) == 1), + DSI_PLL_POLL_DELAY_US, + DSI_PLL_POLL_TIMEOUT_US)) { + pr_debug("DSI PLL status=%x failed to Lock\n", status); + pll_locked = 0; + } else { + pll_locked = 1; + } + + return pll_locked; +} + +int vco_set_rate(struct dsi_pll_vco_clk *vco, unsigned long rate) +{ + s64 vco_clk_rate = rate; + s32 rem; + s64 refclk_cfg, frac_n_mode, ref_doubler_en_b; + s64 ref_clk_to_pll, div_fbx1000, frac_n_value; + s64 sdm_cfg0, sdm_cfg1, sdm_cfg2, sdm_cfg3; + s64 gen_vco_clk, cal_cfg10, cal_cfg11; + u32 res; + int i; + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + /* Configure the Loop filter resistance */ + for (i = 0; i < vco->lpfr_lut_size; i++) + if (vco_clk_rate <= vco->lpfr_lut[i].vco_rate) + break; + if (i == vco->lpfr_lut_size) { + pr_err("unable to get loop filter resistance. vco=%ld\n", rate); + return -EINVAL; + } + res = vco->lpfr_lut[i].r; + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_LPFR_CFG, res); + + /* Loop filter capacitance values : c1 and c2 */ + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_LPFC1_CFG, 0x70); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_LPFC2_CFG, 0x15); + + div_s64_rem(vco_clk_rate, vco->ref_clk_rate, &rem); + if (rem) { + refclk_cfg = 0x1; + frac_n_mode = 1; + ref_doubler_en_b = 0; + } else { + refclk_cfg = 0x0; + frac_n_mode = 0; + ref_doubler_en_b = 1; + } + + pr_debug("refclk_cfg = %lld\n", refclk_cfg); + + ref_clk_to_pll = ((vco->ref_clk_rate * 2 * (refclk_cfg)) + + (ref_doubler_en_b * vco->ref_clk_rate)); + div_fbx1000 = div_s64((vco_clk_rate * 1000), ref_clk_to_pll); + + div_s64_rem(div_fbx1000, 1000, &rem); + frac_n_value = div_s64((rem * (1 << 16)), 1000); + gen_vco_clk = div_s64(div_fbx1000 * ref_clk_to_pll, 1000); + + pr_debug("ref_clk_to_pll = %lld\n", ref_clk_to_pll); + pr_debug("div_fb = %lld\n", div_fbx1000); + pr_debug("frac_n_value = %lld\n", frac_n_value); + + pr_debug("Generated VCO Clock: %lld\n", gen_vco_clk); + rem = 0; + if (frac_n_mode) { + sdm_cfg0 = (0x0 << 5); + sdm_cfg0 |= (0x0 & 0x3f); + sdm_cfg1 = (div_s64(div_fbx1000, 1000) & 0x3f) - 1; + sdm_cfg3 = div_s64_rem(frac_n_value, 256, &rem); + sdm_cfg2 = rem; + } else { + sdm_cfg0 = (0x1 << 5); + sdm_cfg0 |= (div_s64(div_fbx1000, 1000) & 0x3f) - 1; + sdm_cfg1 = (0x0 & 0x3f); + sdm_cfg2 = 0; + sdm_cfg3 = 0; + } + + pr_debug("sdm_cfg0=%lld\n", sdm_cfg0); + pr_debug("sdm_cfg1=%lld\n", sdm_cfg1); + pr_debug("sdm_cfg2=%lld\n", sdm_cfg2); + pr_debug("sdm_cfg3=%lld\n", sdm_cfg3); + + cal_cfg11 = div_s64_rem(gen_vco_clk, 256 * 1000000, &rem); + cal_cfg10 = rem / 1000000; + pr_debug("cal_cfg10=%lld, cal_cfg11=%lld\n", cal_cfg10, cal_cfg11); + + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CHGPUMP_CFG, 0x02); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG3, 0x2b); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG4, 0x66); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_LKDET_CFG2, 0x0d); + + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG1, (u32)(sdm_cfg1 & 0xff)); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG2, (u32)(sdm_cfg2 & 0xff)); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG3, (u32)(sdm_cfg3 & 0xff)); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG4, 0x00); + + /* Add hardware recommended delay for correct PLL configuration */ + if (dsi_pll_res->vco_delay) + udelay(dsi_pll_res->vco_delay); + + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_REFCLK_CFG, (u32)refclk_cfg); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_PWRGEN_CFG, 0x00); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_VCOLPF_CFG, 0x71); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG0, (u32)sdm_cfg0); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG0, 0x12); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG6, 0x30); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG7, 0x00); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG9, 0x00); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG10, (u32)(cal_cfg10 & 0xff)); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG11, (u32)(cal_cfg11 & 0xff)); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_EFUSE_CFG, 0x20); + + return 0; +} + +unsigned long vco_get_rate(struct clk *c) +{ + u32 sdm0, doubler, sdm_byp_div; + u64 vco_rate; + u32 sdm_dc_off, sdm_freq_seed, sdm2, sdm3; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + u64 ref_clk = vco->ref_clk_rate; + int rc; + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + /* Check to see if the ref clk doubler is enabled */ + doubler = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_REFCLK_CFG) & BIT(0); + ref_clk += (doubler * vco->ref_clk_rate); + + /* see if it is integer mode or sdm mode */ + sdm0 = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG0); + if (sdm0 & BIT(6)) { + /* integer mode */ + sdm_byp_div = (MDSS_PLL_REG_R(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG0) & 0x3f) + 1; + vco_rate = ref_clk * sdm_byp_div; + } else { + /* sdm mode */ + sdm_dc_off = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG1) & 0xFF; + pr_debug("sdm_dc_off = %d\n", sdm_dc_off); + sdm2 = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG2) & 0xFF; + sdm3 = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG3) & 0xFF; + sdm_freq_seed = (sdm3 << 8) | sdm2; + pr_debug("sdm_freq_seed = %d\n", sdm_freq_seed); + + vco_rate = (ref_clk * (sdm_dc_off + 1)) + + mult_frac(ref_clk, sdm_freq_seed, BIT(16)); + pr_debug("vco rate = %lld", vco_rate); + } + + pr_debug("returning vco rate = %lu\n", (unsigned long)vco_rate); + + mdss_pll_resource_enable(dsi_pll_res, false); + + return (unsigned long)vco_rate; +} + +static int dsi_pll_enable(struct clk *c) +{ + int i, rc; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + /* Try all enable sequences until one succeeds */ + for (i = 0; i < vco->pll_en_seq_cnt; i++) { + rc = vco->pll_enable_seqs[i](dsi_pll_res); + pr_debug("DSI PLL %s after sequence #%d\n", + rc ? "unlocked" : "locked", i + 1); + if (!rc) + break; + } + + if (rc) { + mdss_pll_resource_enable(dsi_pll_res, false); + pr_err("DSI PLL failed to lock\n"); + } + dsi_pll_res->pll_on = true; + + return rc; +} + +static void dsi_pll_disable(struct clk *c) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (!dsi_pll_res->pll_on && + mdss_pll_resource_enable(dsi_pll_res, true)) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return; + } + + dsi_pll_res->handoff_resources = false; + + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x00); + + mdss_pll_resource_enable(dsi_pll_res, false); + dsi_pll_res->pll_on = false; + + pr_debug("DSI PLL Disabled\n"); +} + +long vco_round_rate(struct clk *c, unsigned long rate) +{ + unsigned long rrate = rate; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + + if (rate < vco->min_rate) + rrate = vco->min_rate; + if (rate > vco->max_rate) + rrate = vco->max_rate; + + return rrate; +} + +enum handoff vco_handoff(struct clk *c) +{ + int rc; + enum handoff ret = HANDOFF_DISABLED_CLK; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return HANDOFF_DISABLED_CLK; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return ret; + } + + if (dsi_pll_lock_status(dsi_pll_res)) { + dsi_pll_res->handoff_resources = true; + dsi_pll_res->pll_on = true; + c->rate = vco_get_rate(c); + ret = HANDOFF_ENABLED_CLK; + } else { + mdss_pll_resource_enable(dsi_pll_res, false); + } + + return ret; +} + +int vco_prepare(struct clk *c) +{ + int rc = 0; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (!dsi_pll_res) { + pr_err("Dsi pll resources are not available\n"); + return -EINVAL; + } + + if ((dsi_pll_res->vco_cached_rate != 0) + && (dsi_pll_res->vco_cached_rate == c->rate)) { + rc = c->ops->set_rate(c, dsi_pll_res->vco_cached_rate); + if (rc) { + pr_err("vco_set_rate failed. rc=%d\n", rc); + goto error; + } + } + + rc = dsi_pll_enable(c); + +error: + return rc; +} + +void vco_unprepare(struct clk *c) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (!dsi_pll_res) { + pr_err("Dsi pll resources are not available\n"); + return; + } + + dsi_pll_res->vco_cached_rate = c->rate; + dsi_pll_disable(c); +} + diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll.h b/drivers/clk/qcom/mdss/mdss-dsi-pll.h new file mode 100644 index 000000000000..3324a2e94eb2 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __MDSS_DSI_PLL_H +#define __MDSS_DSI_PLL_H + +#include +#include "mdss-pll.h" +#define MAX_DSI_PLL_EN_SEQS 10 + +#define DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG (0x0020) +#define DSI_PHY_PLL_UNIPHY_PLL_LKDET_CFG2 (0x0064) +#define DSI_PHY_PLL_UNIPHY_PLL_TEST_CFG (0x0068) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG1 (0x0070) + +/* Register offsets for 20nm PHY PLL */ +#define MMSS_DSI_PHY_PLL_PLL_CNTRL (0x0014) +#define MMSS_DSI_PHY_PLL_PLL_BKG_KVCO_CAL_EN (0x002C) +#define MMSS_DSI_PHY_PLL_PLLLOCK_CMP_EN (0x009C) + +struct lpfr_cfg { + unsigned long vco_rate; + u32 r; +}; + +struct dsi_pll_vco_clk { + struct clk_hw hw; + unsigned long ref_clk_rate; + unsigned long min_rate; + unsigned long max_rate; + u32 pll_en_seq_cnt; + struct lpfr_cfg *lpfr_lut; + u32 lpfr_lut_size; + void *priv; + + int (*pll_enable_seqs[MAX_DSI_PLL_EN_SEQS]) + (struct mdss_pll_resources *dsi_pll_Res); +}; + +int dsi_pll_clock_register_10nm(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); + +static inline struct dsi_pll_vco_clk *to_vco_clk_hw(struct clk_hw *hw) +{ + return container_of(hw, struct dsi_pll_vco_clk, hw); +} + +#endif diff --git a/drivers/clk/qcom/mdss/mdss-edp-pll-28hpm.c b/drivers/clk/qcom/mdss/mdss-edp-pll-28hpm.c new file mode 100644 index 000000000000..7bd4031d4652 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-edp-pll-28hpm.c @@ -0,0 +1,593 @@ +/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "mdss-pll.h" +#include "mdss-edp-pll.h" + +#define EDP_PHY_PLL_UNIPHY_PLL_REFCLK_CFG (0x0) +#define EDP_PHY_PLL_UNIPHY_PLL_POSTDIV1_CFG (0x0004) +#define EDP_PHY_PLL_UNIPHY_PLL_VCOLPF_CFG (0x000C) +#define EDP_PHY_PLL_UNIPHY_PLL_GLB_CFG (0x0020) +#define EDP_PHY_PLL_UNIPHY_PLL_POSTDIV2_CFG (0x0024) +#define EDP_PHY_PLL_UNIPHY_PLL_POSTDIV3_CFG (0x0028) +#define EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG0 (0x0038) +#define EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG1 (0x003C) +#define EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG2 (0x0040) +#define EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG3 (0x0044) +#define EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG4 (0x0048) +#define EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG0 (0x004C) +#define EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG1 (0x0050) +#define EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG2 (0x0054) +#define EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG3 (0x0058) +#define EDP_PHY_PLL_UNIPHY_PLL_LKDET_CFG2 (0x0064) +#define EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG0 (0x006C) +#define EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG2 (0x0074) +#define EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG6 (0x0084) +#define EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG7 (0x0088) +#define EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG8 (0x008C) +#define EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG9 (0x0090) +#define EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG10 (0x0094) +#define EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG11 (0x0098) +#define EDP_PHY_PLL_UNIPHY_PLL_LKDET_CFG0 (0x005C) +#define EDP_PHY_PLL_UNIPHY_PLL_LKDET_CFG1 (0x0060) + +#define EDP_PLL_POLL_DELAY_US 50 +#define EDP_PLL_POLL_TIMEOUT_US 500 + +static const struct clk_ops edp_mainlink_clk_src_ops; +static struct clk_div_ops fixed_5div_ops; /* null ops */ +static const struct clk_ops edp_pixel_clk_ops; + +static inline struct edp_pll_vco_clk *to_edp_vco_clk(struct clk *clk) +{ + return container_of(clk, struct edp_pll_vco_clk, c); +} + +int edp_div_prepare(struct clk *c) +{ + struct div_clk *div = to_div_clk(c); + /* Restore the divider's value */ + return div->ops->set_div(div, div->data.div); +} + +static int edp_vco_set_rate(struct clk *c, unsigned long vco_rate) +{ + struct edp_pll_vco_clk *vco = to_edp_vco_clk(c); + struct mdss_pll_resources *edp_pll_res = vco->priv; + int rc; + + pr_debug("vco_rate=%d\n", (int)vco_rate); + + rc = mdss_pll_resource_enable(edp_pll_res, true); + if (rc) { + pr_err("failed to enable edp pll res rc=%d\n", rc); + rc = -EINVAL; + } + + if (vco_rate == 810000000) { + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_VCOLPF_CFG, 0x18); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_LKDET_CFG2, 0x0d); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_REFCLK_CFG, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG0, 0x36); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG1, 0x69); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG2, 0xff); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG3, 0x2f); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG4, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG0, 0x80); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG1, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG2, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG3, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG0, 0x12); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG6, 0x5a); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG7, 0x0); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG9, 0x0); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG10, 0x2a); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG11, 0x3); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_LKDET_CFG1, 0x1a); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_POSTDIV1_CFG, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_POSTDIV3_CFG, 0x00); + } else if (vco_rate == 1350000000) { + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_LKDET_CFG2, 0x0d); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_REFCLK_CFG, 0x01); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG0, 0x36); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG1, 0x62); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG2, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG3, 0x28); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG4, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG0, 0x80); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG1, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG2, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG3, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG0, 0x12); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG6, 0x5a); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG7, 0x0); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG9, 0x0); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG10, 0x46); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG11, 0x5); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_LKDET_CFG1, 0x1a); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_POSTDIV1_CFG, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_POSTDIV3_CFG, 0x00); + } else { + pr_err("rate=%d is NOT supported\n", (int)vco_rate); + vco_rate = 0; + rc = -EINVAL; + } + + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x01); + udelay(100); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x05); + udelay(100); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x07); + udelay(100); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x0f); + udelay(100); + mdss_pll_resource_enable(edp_pll_res, false); + + vco->rate = vco_rate; + + return rc; +} + +static int edp_pll_ready_poll(struct mdss_pll_resources *edp_pll_res) +{ + int cnt; + u32 status; + + cnt = 100; + while (cnt--) { + udelay(100); + status = MDSS_PLL_REG_R(edp_pll_res->pll_base, 0xc0); + status &= 0x01; + if (status) + break; + } + pr_debug("cnt=%d status=%d\n", cnt, (int)status); + + if (status) + return 1; + + return 0; +} + +static int edp_vco_enable(struct clk *c) +{ + int i, ready; + int rc; + struct edp_pll_vco_clk *vco = to_edp_vco_clk(c); + struct mdss_pll_resources *edp_pll_res = vco->priv; + + rc = mdss_pll_resource_enable(edp_pll_res, true); + if (rc) { + pr_err("edp pll resources not available\n"); + return rc; + } + + for (i = 0; i < 3; i++) { + ready = edp_pll_ready_poll(edp_pll_res); + if (ready) + break; + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x01); + udelay(100); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x05); + udelay(100); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x07); + udelay(100); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x0f); + udelay(100); + } + + if (ready) { + pr_debug("EDP PLL lock success\n"); + edp_pll_res->pll_on = true; + rc = 0; + } else { + pr_err("EDP PLL failed to lock\n"); + mdss_pll_resource_enable(edp_pll_res, false); + rc = -EINVAL; + } + + return rc; +} + +static void edp_vco_disable(struct clk *c) +{ + struct edp_pll_vco_clk *vco = to_edp_vco_clk(c); + struct mdss_pll_resources *edp_pll_res = vco->priv; + + if (!edp_pll_res) { + pr_err("Invalid input parameter\n"); + return; + } + + if (!edp_pll_res->pll_on && + mdss_pll_resource_enable(edp_pll_res, true)) { + pr_err("edp pll resources not available\n"); + return; + } + + MDSS_PLL_REG_W(edp_pll_res->pll_base, 0x20, 0x00); + + edp_pll_res->handoff_resources = false; + edp_pll_res->pll_on = false; + + mdss_pll_resource_enable(edp_pll_res, false); + + pr_debug("EDP PLL Disabled\n"); +} + +static unsigned long edp_vco_get_rate(struct clk *c) +{ + struct edp_pll_vco_clk *vco = to_edp_vco_clk(c); + struct mdss_pll_resources *edp_pll_res = vco->priv; + u32 pll_status, div2; + int rc; + + if (is_gdsc_disabled(edp_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(edp_pll_res, true); + if (rc) { + pr_err("edp pll resources not available\n"); + return rc; + } + + if (vco->rate == 0) { + pll_status = MDSS_PLL_REG_R(edp_pll_res->pll_base, 0xc0); + if (pll_status & 0x01) { + div2 = MDSS_PLL_REG_R(edp_pll_res->pll_base, 0x24); + if (div2 & 0x01) + vco->rate = 1350000000; + else + vco->rate = 810000000; + } + } + mdss_pll_resource_enable(edp_pll_res, false); + + pr_debug("rate=%d\n", (int)vco->rate); + + return vco->rate; +} + +static long edp_vco_round_rate(struct clk *c, unsigned long rate) +{ + struct edp_pll_vco_clk *vco = to_edp_vco_clk(c); + unsigned long rrate = -ENOENT; + unsigned long *lp; + + lp = vco->rate_list; + while (*lp) { + rrate = *lp; + if (rate <= rrate) + break; + lp++; + } + + pr_debug("rrate=%d\n", (int)rrate); + + return rrate; +} + +static int edp_vco_prepare(struct clk *c) +{ + struct edp_pll_vco_clk *vco = to_edp_vco_clk(c); + + pr_debug("rate=%d\n", (int)vco->rate); + + return edp_vco_set_rate(c, vco->rate); +} + +static void edp_vco_unprepare(struct clk *c) +{ + struct edp_pll_vco_clk *vco = to_edp_vco_clk(c); + + pr_debug("rate=%d\n", (int)vco->rate); + + edp_vco_disable(c); +} + +static int edp_pll_lock_status(struct mdss_pll_resources *edp_pll_res) +{ + u32 status; + int pll_locked = 0; + int rc; + + rc = mdss_pll_resource_enable(edp_pll_res, true); + if (rc) { + pr_err("edp pll resources not available\n"); + return rc; + } + + /* poll for PLL ready status */ + if (readl_poll_timeout_atomic((edp_pll_res->pll_base + 0xc0), + status, ((status & BIT(0)) == 1), + EDP_PLL_POLL_DELAY_US, + EDP_PLL_POLL_TIMEOUT_US)) { + pr_debug("EDP PLL status=%x failed to Lock\n", status); + pll_locked = 0; + } else { + pll_locked = 1; + } + mdss_pll_resource_enable(edp_pll_res, false); + + return pll_locked; +} + +static enum handoff edp_vco_handoff(struct clk *c) +{ + enum handoff ret = HANDOFF_DISABLED_CLK; + struct edp_pll_vco_clk *vco = to_edp_vco_clk(c); + struct mdss_pll_resources *edp_pll_res = vco->priv; + + if (is_gdsc_disabled(edp_pll_res)) + return HANDOFF_DISABLED_CLK; + + if (mdss_pll_resource_enable(edp_pll_res, true)) { + pr_err("edp pll resources not available\n"); + return ret; + } + + edp_pll_res->handoff_resources = true; + + if (edp_pll_lock_status(edp_pll_res)) { + c->rate = edp_vco_get_rate(c); + edp_pll_res->pll_on = true; + ret = HANDOFF_ENABLED_CLK; + } else { + edp_pll_res->handoff_resources = false; + mdss_pll_resource_enable(edp_pll_res, false); + } + + pr_debug("done, ret=%d\n", ret); + return ret; +} + +static unsigned long edp_vco_rate_list[] = { + 810000000, 1350000000, 0}; + +struct const clk_ops edp_vco_clk_ops = { + .enable = edp_vco_enable, + .set_rate = edp_vco_set_rate, + .get_rate = edp_vco_get_rate, + .round_rate = edp_vco_round_rate, + .prepare = edp_vco_prepare, + .unprepare = edp_vco_unprepare, + .handoff = edp_vco_handoff, +}; + +struct edp_pll_vco_clk edp_vco_clk = { + .ref_clk_rate = 19200000, + .rate = 0, + .rate_list = edp_vco_rate_list, + .c = { + .dbg_name = "edp_vco_clk", + .ops = &edp_vco_clk_ops, + CLK_INIT(edp_vco_clk.c), + }, +}; + +static unsigned long edp_mainlink_get_rate(struct clk *c) +{ + struct div_clk *mclk = to_div_clk(c); + struct clk *pclk; + unsigned long rate = 0; + + pclk = clk_get_parent(c); + + if (pclk && pclk->ops->get_rate) { + rate = pclk->ops->get_rate(pclk); + rate /= mclk->data.div; + } + + pr_debug("rate=%d div=%d\n", (int)rate, mclk->data.div); + + return rate; +} + + +struct div_clk edp_mainlink_clk_src = { + .ops = &fixed_5div_ops, + .data = { + .div = 5, + .min_div = 5, + .max_div = 5, + }, + .c = { + .parent = &edp_vco_clk.c, + .dbg_name = "edp_mainlink_clk_src", + .ops = &edp_mainlink_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(edp_mainlink_clk_src.c), + } +}; + +/* + * this rate is from pll to clock controller + * output from pll to CC has two possibilities + * 1: if mainlink rate is 270M, then 675M + * 2: if mainlink rate is 162M, then 810M + */ +static int edp_pixel_set_div(struct div_clk *clk, int div) +{ + int rc; + struct mdss_pll_resources *edp_pll_res = clk->priv; + + rc = mdss_pll_resource_enable(edp_pll_res, true); + if (rc) { + pr_err("edp pll resources not available\n"); + return rc; + } + + pr_debug("div=%d\n", div); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_POSTDIV2_CFG, (div - 1)); + mdss_pll_resource_enable(edp_pll_res, false); + + return 0; +} + +static int edp_pixel_get_div(struct div_clk *clk) +{ + int div = 0; + int rc; + struct mdss_pll_resources *edp_pll_res = clk->priv; + + if (is_gdsc_disabled(edp_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(edp_pll_res, true); + if (rc) { + pr_err("edp pll resources not available\n"); + return rc; + } + + div = MDSS_PLL_REG_R(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_POSTDIV2_CFG); + mdss_pll_resource_enable(edp_pll_res, false); + div &= 0x01; + pr_debug("div=%d\n", div); + return div + 1; +} + +static struct clk_div_ops edp_pixel_ops = { + .set_div = edp_pixel_set_div, + .get_div = edp_pixel_get_div, +}; + +struct div_clk edp_pixel_clk_src = { + .data = { + .max_div = 2, + .min_div = 1, + }, + .ops = &edp_pixel_ops, + .c = { + .parent = &edp_vco_clk.c, + .dbg_name = "edp_pixel_clk_src", + .ops = &edp_pixel_clk_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(edp_pixel_clk_src.c), + }, +}; + +static struct clk_lookup mdss_edp_pllcc_8974[] = { + CLK_LOOKUP("edp_pixel_src", edp_pixel_clk_src.c, + "fd8c0000.qcom,mmsscc-mdss"), + CLK_LOOKUP("edp_mainlink_src", edp_mainlink_clk_src.c, + "fd8c0000.qcom,mmsscc-mdss"), +}; + +int edp_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = -ENOTSUPP; + + if (!pll_res || !pll_res->pll_base) { + pr_err("Invalid input parameters\n"); + return -EPROBE_DEFER; + } + + /* Set client data to div and vco clocks */ + edp_pixel_clk_src.priv = pll_res; + edp_mainlink_clk_src.priv = pll_res; + edp_vco_clk.priv = pll_res; + + /* Set clock operation for mainlink and pixel clock */ + edp_mainlink_clk_src_ops = clk_ops_div; + edp_mainlink_clk_src_ops.get_parent = clk_get_parent; + edp_mainlink_clk_src_ops.get_rate = edp_mainlink_get_rate; + + edp_pixel_clk_ops = clk_ops_slave_div; + edp_pixel_clk_ops.prepare = edp_div_prepare; + + rc = of_msm_clock_register(pdev->dev.of_node, mdss_edp_pllcc_8974, + ARRAY_SIZE(mdss_edp_pllcc_8974)); + if (rc) { + pr_err("Clock register failed rc=%d\n", rc); + rc = -EPROBE_DEFER; + } + + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-hdmi-pll-20nm.c b/drivers/clk/qcom/mdss/mdss-hdmi-pll-20nm.c new file mode 100644 index 000000000000..23f809475f02 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-hdmi-pll-20nm.c @@ -0,0 +1,983 @@ +/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-hdmi-pll.h" + +/* hdmi phy registers */ + +#define HDMI_PHY_CMD_SIZE 68 +#define HDMI_PHY_CLK_SIZE 97 + +/* Set to 1 for auto KVCO cal; set to 0 for fixed value */ +#define HDMI_PHY_AUTO_KVCO_CAL 1 + +/* PLL REGISTERS */ +#define QSERDES_COM_SYS_CLK_CTRL (0x000) +#define QSERDES_COM_PLL_VCOTAIL_EN (0x004) +#define QSERDES_COM_CMN_MODE (0x008) +#define QSERDES_COM_IE_TRIM (0x00C) +#define QSERDES_COM_IP_TRIM (0x010) +#define QSERDES_COM_PLL_CNTRL (0x014) +#define QSERDES_COM_PLL_PHSEL_CONTROL (0x018) +#define QSERDES_COM_IPTAT_TRIM_VCCA_TX_SEL (0x01C) +#define QSERDES_COM_PLL_PHSEL_DC (0x020) +#define QSERDES_COM_PLL_IP_SETI (0x024) +#define QSERDES_COM_CORE_CLK_IN_SYNC_SEL (0x028) +#define QSERDES_COM_PLL_BKG_KVCO_CAL_EN (0x02C) +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN (0x030) +#define QSERDES_COM_PLL_CP_SETI (0x034) +#define QSERDES_COM_PLL_IP_SETP (0x038) +#define QSERDES_COM_PLL_CP_SETP (0x03C) +#define QSERDES_COM_ATB_SEL1 (0x040) +#define QSERDES_COM_ATB_SEL2 (0x044) +#define QSERDES_COM_SYSCLK_EN_SEL_TXBAND (0x048) +#define QSERDES_COM_RESETSM_CNTRL (0x04C) +#define QSERDES_COM_RESETSM_CNTRL2 (0x050) +#define QSERDES_COM_RESETSM_CNTRL3 (0x054) +#define QSERDES_COM_RESETSM_PLL_CAL_COUNT1 (0x058) +#define QSERDES_COM_RESETSM_PLL_CAL_COUNT2 (0x05C) +#define QSERDES_COM_DIV_REF1 (0x060) +#define QSERDES_COM_DIV_REF2 (0x064) +#define QSERDES_COM_KVCO_COUNT1 (0x068) +#define QSERDES_COM_KVCO_COUNT2 (0x06C) +#define QSERDES_COM_KVCO_CAL_CNTRL (0x070) +#define QSERDES_COM_KVCO_CODE (0x074) +#define QSERDES_COM_VREF_CFG1 (0x078) +#define QSERDES_COM_VREF_CFG2 (0x07C) +#define QSERDES_COM_VREF_CFG3 (0x080) +#define QSERDES_COM_VREF_CFG4 (0x084) +#define QSERDES_COM_VREF_CFG5 (0x088) +#define QSERDES_COM_VREF_CFG6 (0x08C) +#define QSERDES_COM_PLLLOCK_CMP1 (0x090) +#define QSERDES_COM_PLLLOCK_CMP2 (0x094) +#define QSERDES_COM_PLLLOCK_CMP3 (0x098) +#define QSERDES_COM_PLLLOCK_CMP_EN (0x09C) +#define QSERDES_COM_BGTC (0x0A0) +#define QSERDES_COM_PLL_TEST_UPDN (0x0A4) +#define QSERDES_COM_PLL_VCO_TUNE (0x0A8) +#define QSERDES_COM_DEC_START1 (0x0AC) +#define QSERDES_COM_PLL_AMP_OS (0x0B0) +#define QSERDES_COM_SSC_EN_CENTER (0x0B4) +#define QSERDES_COM_SSC_ADJ_PER1 (0x0B8) +#define QSERDES_COM_SSC_ADJ_PER2 (0x0BC) +#define QSERDES_COM_SSC_PER1 (0x0C0) +#define QSERDES_COM_SSC_PER2 (0x0C4) +#define QSERDES_COM_SSC_STEP_SIZE1 (0x0C8) +#define QSERDES_COM_SSC_STEP_SIZE2 (0x0CC) +#define QSERDES_COM_RES_CODE_UP (0x0D0) +#define QSERDES_COM_RES_CODE_DN (0x0D4) +#define QSERDES_COM_RES_CODE_UP_OFFSET (0x0D8) +#define QSERDES_COM_RES_CODE_DN_OFFSET (0x0DC) +#define QSERDES_COM_RES_CODE_START_SEG1 (0x0E0) +#define QSERDES_COM_RES_CODE_START_SEG2 (0x0E4) +#define QSERDES_COM_RES_CODE_CAL_CSR (0x0E8) +#define QSERDES_COM_RES_CODE (0x0EC) +#define QSERDES_COM_RES_TRIM_CONTROL (0x0F0) +#define QSERDES_COM_RES_TRIM_CONTROL2 (0x0F4) +#define QSERDES_COM_RES_TRIM_EN_VCOCALDONE (0x0F8) +#define QSERDES_COM_FAUX_EN (0x0FC) +#define QSERDES_COM_DIV_FRAC_START1 (0x100) +#define QSERDES_COM_DIV_FRAC_START2 (0x104) +#define QSERDES_COM_DIV_FRAC_START3 (0x108) +#define QSERDES_COM_DEC_START2 (0x10C) +#define QSERDES_COM_PLL_RXTXEPCLK_EN (0x110) +#define QSERDES_COM_PLL_CRCTRL (0x114) +#define QSERDES_COM_PLL_CLKEPDIV (0x118) +#define QSERDES_COM_PLL_FREQUPDATE (0x11C) +#define QSERDES_COM_PLL_BKGCAL_TRIM_UP (0x120) +#define QSERDES_COM_PLL_BKGCAL_TRIM_DN (0x124) +#define QSERDES_COM_PLL_BKGCAL_TRIM_MUX (0x128) +#define QSERDES_COM_PLL_BKGCAL_VREF_CFG (0x12C) +#define QSERDES_COM_PLL_BKGCAL_DIV_REF1 (0x130) +#define QSERDES_COM_PLL_BKGCAL_DIV_REF2 (0x134) +#define QSERDES_COM_MUXADDR (0x138) +#define QSERDES_COM_LOW_POWER_RO_CONTROL (0x13C) +#define QSERDES_COM_POST_DIVIDER_CONTROL (0x140) +#define QSERDES_COM_HR_OCLK2_DIVIDER (0x144) +#define QSERDES_COM_HR_OCLK3_DIVIDER (0x148) +#define QSERDES_COM_PLL_VCO_HIGH (0x14C) +#define QSERDES_COM_RESET_SM (0x150) +#define QSERDES_COM_MUXVAL (0x154) +#define QSERDES_COM_CORE_RES_CODE_DN (0x158) +#define QSERDES_COM_CORE_RES_CODE_UP (0x15C) +#define QSERDES_COM_CORE_VCO_TUNE (0x160) +#define QSERDES_COM_CORE_VCO_TAIL (0x164) +#define QSERDES_COM_CORE_KVCO_CODE (0x168) + +/* Tx Channel 0 REGISTERS */ +#define QSERDES_TX_L0_BIST_MODE_LANENO (0x00) +#define QSERDES_TX_L0_CLKBUF_ENABLE (0x04) +#define QSERDES_TX_L0_TX_EMP_POST1_LVL (0x08) +#define QSERDES_TX_L0_TX_DRV_LVL (0x0C) +#define QSERDES_TX_L0_RESET_TSYNC_EN (0x10) +#define QSERDES_TX_L0_LPB_EN (0x14) +#define QSERDES_TX_L0_RES_CODE_UP (0x18) +#define QSERDES_TX_L0_RES_CODE_DN (0x1C) +#define QSERDES_TX_L0_PERL_LENGTH1 (0x20) +#define QSERDES_TX_L0_PERL_LENGTH2 (0x24) +#define QSERDES_TX_L0_SERDES_BYP_EN_OUT (0x28) +#define QSERDES_TX_L0_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN (0x2C) +#define QSERDES_TX_L0_PARRATE_REC_DETECT_IDLE_EN (0x30) +#define QSERDES_TX_L0_BIST_PATTERN1 (0x34) +#define QSERDES_TX_L0_BIST_PATTERN2 (0x38) +#define QSERDES_TX_L0_BIST_PATTERN3 (0x3C) +#define QSERDES_TX_L0_BIST_PATTERN4 (0x40) +#define QSERDES_TX_L0_BIST_PATTERN5 (0x44) +#define QSERDES_TX_L0_BIST_PATTERN6 (0x48) +#define QSERDES_TX_L0_BIST_PATTERN7 (0x4C) +#define QSERDES_TX_L0_BIST_PATTERN8 (0x50) +#define QSERDES_TX_L0_LANE_MODE (0x54) +#define QSERDES_TX_L0_IDAC_CAL_LANE_MODE (0x58) +#define QSERDES_TX_L0_IDAC_CAL_LANE_MODE_CONFIGURATION (0x5C) +#define QSERDES_TX_L0_ATB_SEL1 (0x60) +#define QSERDES_TX_L0_ATB_SEL2 (0x64) +#define QSERDES_TX_L0_RCV_DETECT_LVL (0x68) +#define QSERDES_TX_L0_PRBS_SEED1 (0x6C) +#define QSERDES_TX_L0_PRBS_SEED2 (0x70) +#define QSERDES_TX_L0_PRBS_SEED3 (0x74) +#define QSERDES_TX_L0_PRBS_SEED4 (0x78) +#define QSERDES_TX_L0_RESET_GEN (0x7C) +#define QSERDES_TX_L0_TRAN_DRVR_EMP_EN (0x80) +#define QSERDES_TX_L0_TX_INTERFACE_MODE (0x84) +#define QSERDES_TX_L0_PWM_CTRL (0x88) +#define QSERDES_TX_L0_PWM_DATA (0x8C) +#define QSERDES_TX_L0_PWM_ENC_DIV_CTRL (0x90) +#define QSERDES_TX_L0_VMODE_CTRL1 (0x94) +#define QSERDES_TX_L0_VMODE_CTRL2 (0x98) +#define QSERDES_TX_L0_VMODE_CTRL3 (0x9C) +#define QSERDES_TX_L0_VMODE_CTRL4 (0xA0) +#define QSERDES_TX_L0_VMODE_CTRL5 (0xA4) +#define QSERDES_TX_L0_VMODE_CTRL6 (0xA8) +#define QSERDES_TX_L0_VMODE_CTRL7 (0xAC) +#define QSERDES_TX_L0_TX_ALOG_INTF_OBSV_CNTL (0xB0) +#define QSERDES_TX_L0_BIST_STATUS (0xB4) +#define QSERDES_TX_L0_BIST_ERROR_COUNT1 (0xB8) +#define QSERDES_TX_L0_BIST_ERROR_COUNT2 (0xBC) +#define QSERDES_TX_L0_TX_ALOG_INTF_OBSV (0xC0) +#define QSERDES_TX_L0_PWM_DEC_STATUS (0xC4) + +/* Tx Channel 1 REGISTERS */ +#define QSERDES_TX_L1_BIST_MODE_LANENO (0x00) +#define QSERDES_TX_L1_CLKBUF_ENABLE (0x04) +#define QSERDES_TX_L1_TX_EMP_POST1_LVL (0x08) +#define QSERDES_TX_L1_TX_DRV_LVL (0x0C) +#define QSERDES_TX_L1_RESET_TSYNC_EN (0x10) +#define QSERDES_TX_L1_LPB_EN (0x14) +#define QSERDES_TX_L1_RES_CODE_UP (0x18) +#define QSERDES_TX_L1_RES_CODE_DN (0x1C) +#define QSERDES_TX_L1_PERL_LENGTH1 (0x20) +#define QSERDES_TX_L1_PERL_LENGTH2 (0x24) +#define QSERDES_TX_L1_SERDES_BYP_EN_OUT (0x28) +#define QSERDES_TX_L1_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN (0x2C) +#define QSERDES_TX_L1_PARRATE_REC_DETECT_IDLE_EN (0x30) +#define QSERDES_TX_L1_BIST_PATTERN1 (0x34) +#define QSERDES_TX_L1_BIST_PATTERN2 (0x38) +#define QSERDES_TX_L1_BIST_PATTERN3 (0x3C) +#define QSERDES_TX_L1_BIST_PATTERN4 (0x40) +#define QSERDES_TX_L1_BIST_PATTERN5 (0x44) +#define QSERDES_TX_L1_BIST_PATTERN6 (0x48) +#define QSERDES_TX_L1_BIST_PATTERN7 (0x4C) +#define QSERDES_TX_L1_BIST_PATTERN8 (0x50) +#define QSERDES_TX_L1_LANE_MODE (0x54) +#define QSERDES_TX_L1_IDAC_CAL_LANE_MODE (0x58) +#define QSERDES_TX_L1_IDAC_CAL_LANE_MODE_CONFIGURATION (0x5C) +#define QSERDES_TX_L1_ATB_SEL1 (0x60) +#define QSERDES_TX_L1_ATB_SEL2 (0x64) +#define QSERDES_TX_L1_RCV_DETECT_LVL (0x68) +#define QSERDES_TX_L1_PRBS_SEED1 (0x6C) +#define QSERDES_TX_L1_PRBS_SEED2 (0x70) +#define QSERDES_TX_L1_PRBS_SEED3 (0x74) +#define QSERDES_TX_L1_PRBS_SEED4 (0x78) +#define QSERDES_TX_L1_RESET_GEN (0x7C) +#define QSERDES_TX_L1_TRAN_DRVR_EMP_EN (0x80) +#define QSERDES_TX_L1_TX_INTERFACE_MODE (0x84) +#define QSERDES_TX_L1_PWM_CTRL (0x88) +#define QSERDES_TX_L1_PWM_DATA (0x8C) +#define QSERDES_TX_L1_PWM_ENC_DIV_CTRL (0x90) +#define QSERDES_TX_L1_VMODE_CTRL1 (0x94) +#define QSERDES_TX_L1_VMODE_CTRL2 (0x98) +#define QSERDES_TX_L1_VMODE_CTRL3 (0x9C) +#define QSERDES_TX_L1_VMODE_CTRL4 (0xA0) +#define QSERDES_TX_L1_VMODE_CTRL5 (0xA4) +#define QSERDES_TX_L1_VMODE_CTRL6 (0xA8) +#define QSERDES_TX_L1_VMODE_CTRL7 (0xAC) +#define QSERDES_TX_L1_TX_ALOG_INTF_OBSV_CNTL (0xB0) +#define QSERDES_TX_L1_BIST_STATUS (0xB4) +#define QSERDES_TX_L1_BIST_ERROR_COUNT1 (0xB8) +#define QSERDES_TX_L1_BIST_ERROR_COUNT2 (0xBC) +#define QSERDES_TX_L1_TX_ALOG_INTF_OBSV (0xC0) +#define QSERDES_TX_L1_PWM_DEC_STATUS (0xC4) + +/* Tx Channel 2 REGISERS */ +#define QSERDES_TX_L2_BIST_MODE_LANENO (0x00) +#define QSERDES_TX_L2_CLKBUF_ENABLE (0x04) +#define QSERDES_TX_L2_TX_EMP_POST1_LVL (0x08) +#define QSERDES_TX_L2_TX_DRV_LVL (0x0C) +#define QSERDES_TX_L2_RESET_TSYNC_EN (0x10) +#define QSERDES_TX_L2_LPB_EN (0x14) +#define QSERDES_TX_L2_RES_CODE_UP (0x18) +#define QSERDES_TX_L2_RES_CODE_DN (0x1C) +#define QSERDES_TX_L2_PERL_LENGTH1 (0x20) +#define QSERDES_TX_L2_PERL_LENGTH2 (0x24) +#define QSERDES_TX_L2_SERDES_BYP_EN_OUT (0x28) +#define QSERDES_TX_L2_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN (0x2C) +#define QSERDES_TX_L2_PARRATE_REC_DETECT_IDLE_EN (0x30) +#define QSERDES_TX_L2_BIST_PATTERN1 (0x34) +#define QSERDES_TX_L2_BIST_PATTERN2 (0x38) +#define QSERDES_TX_L2_BIST_PATTERN3 (0x3C) +#define QSERDES_TX_L2_BIST_PATTERN4 (0x40) +#define QSERDES_TX_L2_BIST_PATTERN5 (0x44) +#define QSERDES_TX_L2_BIST_PATTERN6 (0x48) +#define QSERDES_TX_L2_BIST_PATTERN7 (0x4C) +#define QSERDES_TX_L2_BIST_PATTERN8 (0x50) +#define QSERDES_TX_L2_LANE_MODE (0x54) +#define QSERDES_TX_L2_IDAC_CAL_LANE_MODE (0x58) +#define QSERDES_TX_L2_IDAC_CAL_LANE_MODE_CONFIGURATION (0x5C) +#define QSERDES_TX_L2_ATB_SEL1 (0x60) +#define QSERDES_TX_L2_ATB_SEL2 (0x64) +#define QSERDES_TX_L2_RCV_DETECT_LVL (0x68) +#define QSERDES_TX_L2_PRBS_SEED1 (0x6C) +#define QSERDES_TX_L2_PRBS_SEED2 (0x70) +#define QSERDES_TX_L2_PRBS_SEED3 (0x74) +#define QSERDES_TX_L2_PRBS_SEED4 (0x78) +#define QSERDES_TX_L2_RESET_GEN (0x7C) +#define QSERDES_TX_L2_TRAN_DRVR_EMP_EN (0x80) +#define QSERDES_TX_L2_TX_INTERFACE_MODE (0x84) +#define QSERDES_TX_L2_PWM_CTRL (0x88) +#define QSERDES_TX_L2_PWM_DATA (0x8C) +#define QSERDES_TX_L2_PWM_ENC_DIV_CTRL (0x90) +#define QSERDES_TX_L2_VMODE_CTRL1 (0x94) +#define QSERDES_TX_L2_VMODE_CTRL2 (0x98) +#define QSERDES_TX_L2_VMODE_CTRL3 (0x9C) +#define QSERDES_TX_L2_VMODE_CTRL4 (0xA0) +#define QSERDES_TX_L2_VMODE_CTRL5 (0xA4) +#define QSERDES_TX_L2_VMODE_CTRL6 (0xA8) +#define QSERDES_TX_L2_VMODE_CTRL7 (0xAC) +#define QSERDES_TX_L2_TX_ALOG_INTF_OBSV_CNTL (0xB0) +#define QSERDES_TX_L2_BIST_STATUS (0xB4) +#define QSERDES_TX_L2_BIST_ERROR_COUNT1 (0xB8) +#define QSERDES_TX_L2_BIST_ERROR_COUNT2 (0xBC) +#define QSERDES_TX_L2_TX_ALOG_INTF_OBSV (0xC0) +#define QSERDES_TX_L2_PWM_DEC_STATUS (0xC4) + +/* Tx Channel 3 REGISERS */ +#define QSERDES_TX_L3_BIST_MODE_LANENO (0x00) +#define QSERDES_TX_L3_CLKBUF_ENABLE (0x04) +#define QSERDES_TX_L3_TX_EMP_POST1_LVL (0x08) +#define QSERDES_TX_L3_TX_DRV_LVL (0x0C) +#define QSERDES_TX_L3_RESET_TSYNC_EN (0x10) +#define QSERDES_TX_L3_LPB_EN (0x14) +#define QSERDES_TX_L3_RES_CODE_UP (0x18) +#define QSERDES_TX_L3_RES_CODE_DN (0x1C) +#define QSERDES_TX_L3_PERL_LENGTH1 (0x20) +#define QSERDES_TX_L3_PERL_LENGTH2 (0x24) +#define QSERDES_TX_L3_SERDES_BYP_EN_OUT (0x28) +#define QSERDES_TX_L3_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN (0x2C) +#define QSERDES_TX_L3_PARRATE_REC_DETECT_IDLE_EN (0x30) +#define QSERDES_TX_L3_BIST_PATTERN1 (0x34) +#define QSERDES_TX_L3_BIST_PATTERN2 (0x38) +#define QSERDES_TX_L3_BIST_PATTERN3 (0x3C) +#define QSERDES_TX_L3_BIST_PATTERN4 (0x40) +#define QSERDES_TX_L3_BIST_PATTERN5 (0x44) +#define QSERDES_TX_L3_BIST_PATTERN6 (0x48) +#define QSERDES_TX_L3_BIST_PATTERN7 (0x4C) +#define QSERDES_TX_L3_BIST_PATTERN8 (0x50) +#define QSERDES_TX_L3_LANE_MODE (0x54) +#define QSERDES_TX_L3_IDAC_CAL_LANE_MODE (0x58) +#define QSERDES_TX_L3_IDAC_CAL_LANE_MODE_CONFIGURATION (0x5C) +#define QSERDES_TX_L3_ATB_SEL1 (0x60) +#define QSERDES_TX_L3_ATB_SEL2 (0x64) +#define QSERDES_TX_L3_RCV_DETECT_LVL (0x68) +#define QSERDES_TX_L3_PRBS_SEED1 (0x6C) +#define QSERDES_TX_L3_PRBS_SEED2 (0x70) +#define QSERDES_TX_L3_PRBS_SEED3 (0x74) +#define QSERDES_TX_L3_PRBS_SEED4 (0x78) +#define QSERDES_TX_L3_RESET_GEN (0x7C) +#define QSERDES_TX_L3_TRAN_DRVR_EMP_EN (0x80) +#define QSERDES_TX_L3_TX_INTERFACE_MODE (0x84) +#define QSERDES_TX_L3_PWM_CTRL (0x88) +#define QSERDES_TX_L3_PWM_DATA (0x8C) +#define QSERDES_TX_L3_PWM_ENC_DIV_CTRL (0x90) +#define QSERDES_TX_L3_VMODE_CTRL1 (0x94) +#define QSERDES_TX_L3_VMODE_CTRL2 (0x98) +#define QSERDES_TX_L3_VMODE_CTRL3 (0x9C) +#define QSERDES_TX_L3_VMODE_CTRL4 (0xA0) +#define QSERDES_TX_L3_VMODE_CTRL5 (0xA4) +#define QSERDES_TX_L3_VMODE_CTRL6 (0xA8) +#define QSERDES_TX_L3_VMODE_CTRL7 (0xAC) +#define QSERDES_TX_L3_TX_ALOG_INTF_OBSV_CNTL (0xB0) +#define QSERDES_TX_L3_BIST_STATUS (0xB4) +#define QSERDES_TX_L3_BIST_ERROR_COUNT1 (0xB8) +#define QSERDES_TX_L3_BIST_ERROR_COUNT2 (0xBC) +#define QSERDES_TX_L3_TX_ALOG_INTF_OBSV (0xC0) +#define QSERDES_TX_L3_PWM_DEC_STATUS (0xC4) + +/* HDMI PHY REGISTERS */ +#define HDMI_PHY_CFG (0x00) +#define HDMI_PHY_PD_CTL (0x04) +#define HDMI_PHY_MODE (0x08) +#define HDMI_PHY_MISR_CLEAR (0x0C) +#define HDMI_PHY_TX0_TX1_BIST_CFG0 (0x10) +#define HDMI_PHY_TX0_TX1_BIST_CFG1 (0x14) +#define HDMI_PHY_TX0_TX1_PRBS_SEED_BYTE0 (0x18) +#define HDMI_PHY_TX0_TX1_PRBS_SEED_BYTE1 (0x1C) +#define HDMI_PHY_TX0_TX1_PRBS_SEED_BYTE2 (0x20) +#define HDMI_PHY_TX0_TX1_PRBS_SEED_BYTE3 (0x24) +#define HDMI_PHY_TX0_TX1_PRBS_POLY_BYTE0 (0x28) +#define HDMI_PHY_TX0_TX1_PRBS_POLY_BYTE1 (0x2C) +#define HDMI_PHY_TX0_TX1_PRBS_POLY_BYTE2 (0x30) +#define HDMI_PHY_TX0_TX1_PRBS_POLY_BYTE3 (0x34) +#define HDMI_PHY_TX2_TX3_BIST_CFG0 (0x38) +#define HDMI_PHY_TX2_TX3_BIST_CFG1 (0x3C) +#define HDMI_PHY_TX2_TX3_PRBS_SEED_BYTE0 (0x40) +#define HDMI_PHY_TX2_TX3_PRBS_SEED_BYTE1 (0x44) +#define HDMI_PHY_TX2_TX3_PRBS_SEED_BYTE2 (0x48) +#define HDMI_PHY_TX2_TX3_PRBS_SEED_BYTE3 (0x4C) +#define HDMI_PHY_TX2_TX3_PRBS_POLY_BYTE0 (0x50) +#define HDMI_PHY_TX2_TX3_PRBS_POLY_BYTE1 (0x54) +#define HDMI_PHY_TX2_TX3_PRBS_POLY_BYTE2 (0x58) +#define HDMI_PHY_TX2_TX3_PRBS_POLY_BYTE3 (0x5C) +#define HDMI_PHY_DEBUG_BUS_SEL (0x60) +#define HDMI_PHY_TXCAL_CFG0 (0x64) +#define HDMI_PHY_TXCAL_CFG1 (0x68) +#define HDMI_PHY_TX0_TX1_BIST_STATUS0 (0x6C) +#define HDMI_PHY_TX0_TX1_BIST_STATUS1 (0x70) +#define HDMI_PHY_TX0_TX1_BIST_STATUS2 (0x74) +#define HDMI_PHY_TX2_TX3_BIST_STATUS0 (0x78) +#define HDMI_PHY_TX2_TX3_BIST_STATUS1 (0x7C) +#define HDMI_PHY_TX2_TX3_BIST_STATUS2 (0x80) +#define HDMI_PHY_PRE_MISR_STATUS0 (0x84) +#define HDMI_PHY_PRE_MISR_STATUS1 (0x88) +#define HDMI_PHY_PRE_MISR_STATUS2 (0x8C) +#define HDMI_PHY_PRE_MISR_STATUS3 (0x90) +#define HDMI_PHY_POST_MISR_STATUS0 (0x94) +#define HDMI_PHY_POST_MISR_STATUS1 (0x98) +#define HDMI_PHY_POST_MISR_STATUS2 (0x9C) +#define HDMI_PHY_POST_MISR_STATUS3 (0xA0) +#define HDMI_PHY_STATUS (0xA4) +#define HDMI_PHY_MISC3_STATUS (0xA8) +#define HDMI_PHY_DEBUG_BUS0 (0xAC) +#define HDMI_PHY_DEBUG_BUS1 (0xB0) +#define HDMI_PHY_DEBUG_BUS2 (0xB4) +#define HDMI_PHY_DEBUG_BUS3 (0xB8) +#define HDMI_PHY_REVISION_ID0 (0xBC) +#define HDMI_PHY_REVISION_ID1 (0xC0) +#define HDMI_PHY_REVISION_ID2 (0xC4) +#define HDMI_PHY_REVISION_ID3 (0xC8) + +#define HDMI_PLL_POLL_DELAY_US 50 +#define HDMI_PLL_POLL_TIMEOUT_US 125000 +#define HDMI_PLL_REF_CLK_RATE 192ULL +#define HDMI_PLL_DIVISOR 10000000000ULL +#define HDMI_PLL_DIVISOR_32 100000U +#define HDMI_PLL_MIN_VCO_CLK 160000000ULL +#define HDMI_PLL_TMDS_MAX 800000000U + + +static int hdmi_20nm_pll_lock_status(struct mdss_pll_resources *io) +{ + u32 status; + int pll_locked = 0; + int phy_ready = 0; + int rc; + + rc = mdss_pll_resource_enable(io, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + /* Poll for C_READY and PHY READY */ + pr_debug("%s: Waiting for PHY Ready\n", __func__); + + /* poll for PLL ready status */ + if (!readl_poll_timeout_atomic( + (io->pll_base + QSERDES_COM_RESET_SM), + status, ((status & BIT(6)) == 1), + HDMI_PLL_POLL_DELAY_US, + HDMI_PLL_POLL_TIMEOUT_US)) { + pr_debug("%s: C READY\n", __func__); + pll_locked = 1; + } else { + pr_debug("%s: C READY TIMEOUT\n", __func__); + pll_locked = 0; + } + + /* poll for PHY ready status */ + if (pll_locked && !readl_poll_timeout_atomic( + (io->phy_base + HDMI_PHY_STATUS), + status, ((status & BIT(0)) == 1), + HDMI_PLL_POLL_DELAY_US, + HDMI_PLL_POLL_TIMEOUT_US)) { + pr_debug("%s: PHY READY\n", __func__); + phy_ready = 1; + } else { + pr_debug("%s: PHY READY TIMEOUT\n", __func__); + phy_ready = 0; + } + mdss_pll_resource_enable(io, false); + + return phy_ready; +} + +static inline struct hdmi_pll_vco_clk *to_hdmi_20nm_vco_clk(struct clk *clk) +{ + return container_of(clk, struct hdmi_pll_vco_clk, c); +} + +static inline u32 hdmi_20nm_phy_pll_vco_reg_val(struct hdmi_pll_cfg *pll_cfg, + u32 tmds_clk) +{ + u32 index = 0; + + while (pll_cfg[index].vco_rate < HDMI_PLL_TMDS_MAX && + pll_cfg[index].vco_rate < tmds_clk) + index++; + return pll_cfg[index].reg; +} + +static void hdmi_20nm_phy_pll_calc_settings(struct mdss_pll_resources *io, + struct hdmi_pll_vco_clk *vco, u32 vco_clk, u32 tmds_clk) +{ + u32 val = 0; + u64 dec_start_val, frac_start_val, pll_lock_cmp; + + /* Calculate decimal and fractional values */ + dec_start_val = 1000000UL * vco_clk; + do_div(dec_start_val, HDMI_PLL_REF_CLK_RATE); + do_div(dec_start_val, 2U); + frac_start_val = dec_start_val; + do_div(frac_start_val, HDMI_PLL_DIVISOR_32); + do_div(frac_start_val, HDMI_PLL_DIVISOR_32); + frac_start_val *= HDMI_PLL_DIVISOR; + frac_start_val = dec_start_val - frac_start_val; + frac_start_val *= (u64)(2 << 19); + do_div(frac_start_val, HDMI_PLL_DIVISOR_32); + do_div(frac_start_val, HDMI_PLL_DIVISOR_32); + pll_lock_cmp = dec_start_val; + do_div(pll_lock_cmp, 10U); + pll_lock_cmp *= 0x800; + do_div(pll_lock_cmp, HDMI_PLL_DIVISOR_32); + do_div(pll_lock_cmp, HDMI_PLL_DIVISOR_32); + pll_lock_cmp -= 1U; + do_div(dec_start_val, HDMI_PLL_DIVISOR_32); + do_div(dec_start_val, HDMI_PLL_DIVISOR_32); + + /* PLL loop bandwidth */ + val = hdmi_20nm_phy_pll_vco_reg_val(vco->ip_seti, tmds_clk); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_IP_SETI, val); + val = hdmi_20nm_phy_pll_vco_reg_val(vco->cp_seti, tmds_clk); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_CP_SETI, val); + val = hdmi_20nm_phy_pll_vco_reg_val(vco->cp_setp, tmds_clk); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_CP_SETP, val); + val = hdmi_20nm_phy_pll_vco_reg_val(vco->ip_setp, tmds_clk); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_IP_SETP, val); + val = hdmi_20nm_phy_pll_vco_reg_val(vco->crctrl, tmds_clk); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_CRCTRL, val); + + /* PLL calibration */ + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DIV_FRAC_START1, + 0x80 | (frac_start_val & 0x7F)); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DIV_FRAC_START2, + 0x80 | ((frac_start_val >> 7) & 0x7F)); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DIV_FRAC_START3, + 0x40 | ((frac_start_val >> 14) & 0x3F)); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DEC_START1, + 0x80 | (dec_start_val & 0x7F)); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DEC_START2, + 0x02 | (0x01 & (dec_start_val >> 7))); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLLLOCK_CMP1, + pll_lock_cmp & 0xFF); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLLLOCK_CMP2, + (pll_lock_cmp >> 8) & 0xFF); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLLLOCK_CMP3, + (pll_lock_cmp >> 16) & 0xFF); +} + +static u32 hdmi_20nm_phy_pll_set_clk_rate(struct clk *c, u32 tmds_clk) +{ + u32 tx_band = 0; + + struct hdmi_pll_vco_clk *vco = to_hdmi_20nm_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + u64 vco_clk = tmds_clk; + + while (vco_clk > 0 && vco_clk < HDMI_PLL_MIN_VCO_CLK) { + tx_band++; + vco_clk *= 2; + } + + /* Initially shut down PHY */ + pr_debug("%s: Disabling PHY\n", __func__); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_PD_CTL, 0x0); + udelay(1000); + /* memory barrier */ + mb(); + + /* power-up and recommended common block settings */ + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_PD_CTL, 0x1F); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x01); + udelay(1000); + /* memory barrier */ + mb(); + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x07); + udelay(1000); + /* memory barrier */ + mb(); + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x05); + udelay(1000); + /* memory barrier */ + mb(); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SYS_CLK_CTRL, 0x42); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_VCOTAIL_EN, 0x03); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CMN_MODE, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_IE_TRIM, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_IP_TRIM, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_CNTRL, 0x07); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_PHSEL_CONTROL, 0x04); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_IPTAT_TRIM_VCCA_TX_SEL, 0xA0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_PHSEL_DC, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CORE_CLK_IN_SYNC_SEL, 0x00); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_BKG_KVCO_CAL_EN, 0x00); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x0F); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_ATB_SEL1, 0x01); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_ATB_SEL2, 0x01); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SYSCLK_EN_SEL_TXBAND, + 0x4A + (0x10 * tx_band)); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VREF_CFG1, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VREF_CFG2, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_BGTC, 0xFF); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_TEST_UPDN, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_VCO_TUNE, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_AMP_OS, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SSC_EN_CENTER, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RES_CODE_UP, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RES_CODE_DN, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_KVCO_CODE, + tmds_clk > 300000000 ? 0x3F : 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_KVCO_COUNT1, + tmds_clk > 300000000 ? 0x00 : 0x8A); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DIV_REF1, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DIV_REF2, + tmds_clk > 300000000 ? 0x00 : 0x01); + + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_KVCO_CAL_CNTRL, + tmds_clk > 300000000 ? 0x00 : 0x1F); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VREF_CFG3, + tmds_clk > 300000000 ? 0x00 : 0x40); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VREF_CFG4, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VREF_CFG5, 0x10); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RESETSM_CNTRL, + tmds_clk > 300000000 ? 0x80 : 0x00); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RES_CODE_CAL_CSR, 0x77); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RES_TRIM_EN_VCOCALDONE, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_RXTXEPCLK_EN, 0x0C); + + hdmi_20nm_phy_pll_calc_settings(io, vco, vco_clk, tmds_clk); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLLLOCK_CMP_EN, 0x11); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_CNTRL, 0x07); + + /* Resistor calibration linear search */ + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RES_CODE_START_SEG1, 0x60); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RES_CODE_START_SEG2, 0x60); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RES_TRIM_CONTROL, 0x01); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RESETSM_CNTRL2, 0x07); + + udelay(1000); + /* memory barrier */ + mb(); + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_MODE, tx_band); + + /* TX lanes (transceivers) power-up sequence */ + MDSS_PLL_REG_W(io->pll_base + 0x400, QSERDES_TX_L0_CLKBUF_ENABLE, 0x03); + MDSS_PLL_REG_W(io->pll_base + 0x600, QSERDES_TX_L1_CLKBUF_ENABLE, 0x03); + MDSS_PLL_REG_W(io->pll_base + 0x800, QSERDES_TX_L2_CLKBUF_ENABLE, 0x03); + MDSS_PLL_REG_W(io->pll_base + 0xA00, QSERDES_TX_L3_CLKBUF_ENABLE, 0x03); + + MDSS_PLL_REG_W(io->pll_base + 0x400, + QSERDES_TX_L0_TRAN_DRVR_EMP_EN, 0x03); + MDSS_PLL_REG_W(io->pll_base + 0x600, + QSERDES_TX_L1_TRAN_DRVR_EMP_EN, 0x03); + MDSS_PLL_REG_W(io->pll_base + 0x800, + QSERDES_TX_L2_TRAN_DRVR_EMP_EN, 0x03); + MDSS_PLL_REG_W(io->pll_base + 0xA00, + QSERDES_TX_L3_TRAN_DRVR_EMP_EN, 0x03); + + MDSS_PLL_REG_W(io->pll_base + 0x400, + QSERDES_TX_L0_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x6F); + MDSS_PLL_REG_W(io->pll_base + 0x600, + QSERDES_TX_L1_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x6F); + MDSS_PLL_REG_W(io->pll_base + 0x800, + QSERDES_TX_L2_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x6F); + MDSS_PLL_REG_W(io->pll_base + 0xA00, + QSERDES_TX_L3_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x6F); + + MDSS_PLL_REG_W(io->pll_base + 0x400, + QSERDES_TX_L0_TX_EMP_POST1_LVL, 0x0000002F); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_TXCAL_CFG0, 0x000000AF); + + MDSS_PLL_REG_W(io->pll_base + 0x400, QSERDES_TX_L0_VMODE_CTRL1, 0x08); + MDSS_PLL_REG_W(io->pll_base + 0x800, QSERDES_TX_L2_VMODE_CTRL1, 0x09); + MDSS_PLL_REG_W(io->pll_base + 0x400, QSERDES_TX_L0_VMODE_CTRL5, 0xA0); + MDSS_PLL_REG_W(io->pll_base + 0x400, QSERDES_TX_L0_VMODE_CTRL6, 0x01); + MDSS_PLL_REG_W(io->pll_base + 0x800, QSERDES_TX_L2_VMODE_CTRL5, 0xA0); + MDSS_PLL_REG_W(io->pll_base + 0x800, QSERDES_TX_L2_VMODE_CTRL6, 0x01); + + MDSS_PLL_REG_W(io->pll_base + 0x400, + QSERDES_TX_L0_PARRATE_REC_DETECT_IDLE_EN, 0x40); + MDSS_PLL_REG_W(io->pll_base + 0x400, + QSERDES_TX_L0_TX_INTERFACE_MODE, 0x00); + MDSS_PLL_REG_W(io->pll_base + 0x600, + QSERDES_TX_L1_PARRATE_REC_DETECT_IDLE_EN, 0x40); + MDSS_PLL_REG_W(io->pll_base + 0x600, + QSERDES_TX_L1_TX_INTERFACE_MODE, 0x00); + MDSS_PLL_REG_W(io->pll_base + 0x800, + QSERDES_TX_L2_PARRATE_REC_DETECT_IDLE_EN, 0x40); + MDSS_PLL_REG_W(io->pll_base + 0x800, + QSERDES_TX_L2_TX_INTERFACE_MODE, 0x00); + MDSS_PLL_REG_W(io->pll_base + 0xA00, + QSERDES_TX_L3_PARRATE_REC_DETECT_IDLE_EN, 0x40); + MDSS_PLL_REG_W(io->pll_base + 0xA00, + QSERDES_TX_L3_TX_INTERFACE_MODE, 0x00); + + return 0; +} + +static int hdmi_20nm_vco_enable(struct clk *c) +{ + u32 ready_poll; + u32 time_out_loop; + /* Hardware recommended timeout iterator */ + u32 time_out_max = 50000; + + struct hdmi_pll_vco_clk *vco = to_hdmi_20nm_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x00000000); + udelay(100); + /* memory barrier */ + mb(); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x00000003); + udelay(100); + /* memory barrier */ + mb(); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x00000009); + udelay(100); + /* memory barrier */ + mb(); + + /* Poll for C_READY and PHY READY */ + pr_debug("%s: Waiting for PHY Ready\n", __func__); + time_out_loop = 0; + do { + ready_poll = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_RESET_SM); + time_out_loop++; + udelay(10); + } while (((ready_poll & (1 << 6)) == 0) && + (time_out_loop < time_out_max)); + if (time_out_loop >= time_out_max) + pr_err("%s: ERROR: TIMED OUT BEFORE C READY\n", __func__); + else + pr_debug("%s: C READY\n", __func__); + + /* Poll for PHY READY */ + pr_debug("%s: Waiting for PHY Ready\n", __func__); + time_out_loop = 0; + do { + ready_poll = MDSS_PLL_REG_R(io->phy_base, HDMI_PHY_STATUS); + time_out_loop++; + udelay(1); + } while (((ready_poll & 0x1) == 0) && (time_out_loop < time_out_max)); + + if (time_out_loop >= time_out_max) + pr_err("%s: TIMED OUT BEFORE PHY READY\n", __func__); + else + pr_debug("%s: HDMI PHY READY\n", __func__); + + io->pll_on = true; + + return 0; +} + + +static int hdmi_20nm_vco_set_rate(struct clk *c, unsigned long rate) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_20nm_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + void __iomem *pll_base; + void __iomem *phy_base; + unsigned int set_power_dwn = 0; + int rc; + + rc = mdss_pll_resource_enable(io, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + if (io->pll_on) + set_power_dwn = 1; + + pll_base = io->pll_base; + phy_base = io->phy_base; + + pr_debug("rate=%ld\n", rate); + + hdmi_20nm_phy_pll_set_clk_rate(c, rate); + + mdss_pll_resource_enable(io, false); + + if (set_power_dwn) + hdmi_20nm_vco_enable(c); + + vco->rate = rate; + vco->rate_set = true; + + return 0; +} + +static unsigned long hdmi_20nm_vco_get_rate(struct clk *c) +{ + unsigned long freq = 0; + int rc; + struct hdmi_pll_vco_clk *vco = to_hdmi_20nm_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + if (is_gdsc_disabled(io)) + return 0; + + rc = mdss_pll_resource_enable(io, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + mdss_pll_resource_enable(io, false); + + return freq; +} + +static long hdmi_20nm_vco_round_rate(struct clk *c, unsigned long rate) +{ + unsigned long rrate = rate; + + pr_debug("rrate=%ld\n", rrate); + + return rrate; +} + +static int hdmi_20nm_vco_prepare(struct clk *c) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_20nm_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + int ret = 0; + + pr_debug("rate=%ld\n", vco->rate); + + if (!vco->rate_set && vco->rate) + ret = hdmi_20nm_vco_set_rate(c, vco->rate); + + if (!ret) { + ret = mdss_pll_resource_enable(io, true); + if (ret) + pr_err("pll resource can't be enabled\n"); + } + + return ret; +} + +static void hdmi_20nm_vco_unprepare(struct clk *c) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_20nm_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + vco->rate_set = false; + + if (!io) { + pr_err("Invalid input parameter\n"); + return; + } + + if (!io->pll_on && + mdss_pll_resource_enable(io, true)) { + pr_err("pll resource can't be enabled\n"); + return; + } + + io->handoff_resources = false; + mdss_pll_resource_enable(io, false); + io->pll_on = false; +} + +static enum handoff hdmi_20nm_vco_handoff(struct clk *c) +{ + enum handoff ret = HANDOFF_DISABLED_CLK; + struct hdmi_pll_vco_clk *vco = to_hdmi_20nm_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + if (is_gdsc_disabled(io)) + return HANDOFF_DISABLED_CLK; + + if (mdss_pll_resource_enable(io, true)) { + pr_err("pll resource can't be enabled\n"); + return ret; + } + + io->handoff_resources = true; + + if (hdmi_20nm_pll_lock_status(io)) { + io->pll_on = true; + c->rate = hdmi_20nm_vco_get_rate(c); + ret = HANDOFF_ENABLED_CLK; + } else { + io->handoff_resources = false; + mdss_pll_resource_enable(io, false); + } + + pr_debug("done, ret=%d\n", ret); + return ret; +} + +static const struct clk_ops hdmi_20nm_vco_clk_ops = { + .enable = hdmi_20nm_vco_enable, + .set_rate = hdmi_20nm_vco_set_rate, + .get_rate = hdmi_20nm_vco_get_rate, + .round_rate = hdmi_20nm_vco_round_rate, + .prepare = hdmi_20nm_vco_prepare, + .unprepare = hdmi_20nm_vco_unprepare, + .handoff = hdmi_20nm_vco_handoff, +}; + +static struct hdmi_pll_vco_clk hdmi_20nm_vco_clk = { + .ip_seti = (struct hdmi_pll_cfg[]){ + {550890000, 0x03}, + {589240000, 0x07}, + {689290000, 0x03}, + {727600000, 0x07}, + {HDMI_PLL_TMDS_MAX, 0x03}, + }, + .cp_seti = (struct hdmi_pll_cfg[]){ + {34440000, 0x3F}, + {36830000, 0x2F}, + {68870000, 0x3F}, + {73660000, 0x2F}, + {137730000, 0x3F}, + {147310000, 0x2F}, + {275450000, 0x3F}, + {294620000, 0x2F}, + {344650000, 0x3F}, + {363800000, 0x2F}, + {477960000, 0x3F}, + {512530000, 0x2F}, + {550890000, 0x1F}, + {589240000, 0x2F}, + {630900000, 0x3F}, + {650590000, 0x2F}, + {689290000, 0x1F}, + {727600000, 0x2F}, + {HDMI_PLL_TMDS_MAX, 0x3F}, + }, + .ip_setp = (struct hdmi_pll_cfg[]){ + {497340000, 0x03}, + {512530000, 0x07}, + {535680000, 0x03}, + {550890000, 0x07}, + {574060000, 0x03}, + {727600000, 0x07}, + {HDMI_PLL_TMDS_MAX, 0x03}, + }, + .cp_setp = (struct hdmi_pll_cfg[]){ + {36830000, 0x1F}, + {40010000, 0x17}, + {73660000, 0x1F}, + {80000000, 0x17}, + {147310000, 0x1F}, + {160010000, 0x17}, + {294620000, 0x1F}, + {363800000, 0x17}, + {497340000, 0x0F}, + {512530000, 0x1F}, + {535680000, 0x0F}, + {550890000, 0x1F}, + {574060000, 0x0F}, + {589240000, 0x1F}, + {727600000, 0x17}, + {HDMI_PLL_TMDS_MAX, 0x07}, + }, + .crctrl = (struct hdmi_pll_cfg[]){ + {40010000, 0xBB}, + {40030000, 0x77}, + {80000000, 0xBB}, + {80060000, 0x77}, + {160010000, 0xBB}, + {160120000, 0x77}, + {772930000, 0xBB}, + {HDMI_PLL_TMDS_MAX, 0xFF}, + }, + .c = { + .dbg_name = "hdmi_20nm_vco_clk", + .ops = &hdmi_20nm_vco_clk_ops, + CLK_INIT(hdmi_20nm_vco_clk.c), + }, +}; + +static struct clk_lookup hdmipllcc_8994[] = { + CLK_LIST(hdmi_20nm_vco_clk), +}; + +int hdmi_20nm_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = -ENOTSUPP; + + if (!pll_res || !pll_res->phy_base || !pll_res->pll_base) { + pr_err("Invalid input parameters\n"); + return -EPROBE_DEFER; + } + + /* Set client data for vco, mux and div clocks */ + hdmi_20nm_vco_clk.priv = pll_res; + + rc = of_msm_clock_register(pdev->dev.of_node, hdmipllcc_8994, + ARRAY_SIZE(hdmipllcc_8994)); + if (rc) { + pr_err("Clock register failed rc=%d\n", rc); + rc = -EPROBE_DEFER; + } else { + pr_debug("%s: SUCCESS\n", __func__); + } + + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-hdmi-pll-28hpm.c b/drivers/clk/qcom/mdss/mdss-hdmi-pll-28hpm.c new file mode 100644 index 000000000000..9a3525ae502a --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-hdmi-pll-28hpm.c @@ -0,0 +1,1104 @@ +/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-hdmi-pll.h" + +/* hdmi phy registers */ +#define HDMI_PHY_ANA_CFG0 (0x0000) +#define HDMI_PHY_ANA_CFG1 (0x0004) +#define HDMI_PHY_ANA_CFG2 (0x0008) +#define HDMI_PHY_ANA_CFG3 (0x000C) +#define HDMI_PHY_PD_CTRL0 (0x0010) +#define HDMI_PHY_PD_CTRL1 (0x0014) +#define HDMI_PHY_GLB_CFG (0x0018) +#define HDMI_PHY_DCC_CFG0 (0x001C) +#define HDMI_PHY_DCC_CFG1 (0x0020) +#define HDMI_PHY_TXCAL_CFG0 (0x0024) +#define HDMI_PHY_TXCAL_CFG1 (0x0028) +#define HDMI_PHY_TXCAL_CFG2 (0x002C) +#define HDMI_PHY_TXCAL_CFG3 (0x0030) +#define HDMI_PHY_BIST_CFG0 (0x0034) +#define HDMI_PHY_BIST_CFG1 (0x0038) +#define HDMI_PHY_BIST_PATN0 (0x003C) +#define HDMI_PHY_BIST_PATN1 (0x0040) +#define HDMI_PHY_BIST_PATN2 (0x0044) +#define HDMI_PHY_BIST_PATN3 (0x0048) +#define HDMI_PHY_STATUS (0x005C) + +/* hdmi phy unified pll registers */ +#define HDMI_UNI_PLL_REFCLK_CFG (0x0000) +#define HDMI_UNI_PLL_POSTDIV1_CFG (0x0004) +#define HDMI_UNI_PLL_CHFPUMP_CFG (0x0008) +#define HDMI_UNI_PLL_VCOLPF_CFG (0x000C) +#define HDMI_UNI_PLL_VREG_CFG (0x0010) +#define HDMI_UNI_PLL_PWRGEN_CFG (0x0014) +#define HDMI_UNI_PLL_GLB_CFG (0x0020) +#define HDMI_UNI_PLL_POSTDIV2_CFG (0x0024) +#define HDMI_UNI_PLL_POSTDIV3_CFG (0x0028) +#define HDMI_UNI_PLL_LPFR_CFG (0x002C) +#define HDMI_UNI_PLL_LPFC1_CFG (0x0030) +#define HDMI_UNI_PLL_LPFC2_CFG (0x0034) +#define HDMI_UNI_PLL_SDM_CFG0 (0x0038) +#define HDMI_UNI_PLL_SDM_CFG1 (0x003C) +#define HDMI_UNI_PLL_SDM_CFG2 (0x0040) +#define HDMI_UNI_PLL_SDM_CFG3 (0x0044) +#define HDMI_UNI_PLL_SDM_CFG4 (0x0048) +#define HDMI_UNI_PLL_SSC_CFG0 (0x004C) +#define HDMI_UNI_PLL_SSC_CFG1 (0x0050) +#define HDMI_UNI_PLL_SSC_CFG2 (0x0054) +#define HDMI_UNI_PLL_SSC_CFG3 (0x0058) +#define HDMI_UNI_PLL_LKDET_CFG0 (0x005C) +#define HDMI_UNI_PLL_LKDET_CFG1 (0x0060) +#define HDMI_UNI_PLL_LKDET_CFG2 (0x0064) +#define HDMI_UNI_PLL_CAL_CFG0 (0x006C) +#define HDMI_UNI_PLL_CAL_CFG1 (0x0070) +#define HDMI_UNI_PLL_CAL_CFG2 (0x0074) +#define HDMI_UNI_PLL_CAL_CFG3 (0x0078) +#define HDMI_UNI_PLL_CAL_CFG4 (0x007C) +#define HDMI_UNI_PLL_CAL_CFG5 (0x0080) +#define HDMI_UNI_PLL_CAL_CFG6 (0x0084) +#define HDMI_UNI_PLL_CAL_CFG7 (0x0088) +#define HDMI_UNI_PLL_CAL_CFG8 (0x008C) +#define HDMI_UNI_PLL_CAL_CFG9 (0x0090) +#define HDMI_UNI_PLL_CAL_CFG10 (0x0094) +#define HDMI_UNI_PLL_CAL_CFG11 (0x0098) +#define HDMI_UNI_PLL_STATUS (0x00C0) + +#define HDMI_PLL_POLL_DELAY_US 50 +#define HDMI_PLL_POLL_TIMEOUT_US 500 + +static inline struct hdmi_pll_vco_clk *to_hdmi_vco_clk(struct clk *clk) +{ + return container_of(clk, struct hdmi_pll_vco_clk, c); +} + +static void hdmi_vco_disable(struct clk *c) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *hdmi_pll_res = vco->priv; + + if (!hdmi_pll_res) { + pr_err("Invalid input parameter\n"); + return; + } + + if (!hdmi_pll_res->pll_on && + mdss_pll_resource_enable(hdmi_pll_res, true)) { + pr_err("pll resource can't be enabled\n"); + return; + } + + MDSS_PLL_REG_W(hdmi_pll_res->pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0); + udelay(5); + MDSS_PLL_REG_W(hdmi_pll_res->phy_base, HDMI_PHY_GLB_CFG, 0x0); + + hdmi_pll_res->handoff_resources = false; + mdss_pll_resource_enable(hdmi_pll_res, false); + hdmi_pll_res->pll_on = false; +} /* hdmi_vco_disable */ + +static int hdmi_vco_enable(struct clk *c) +{ + u32 status; + u32 delay_us, timeout_us; + int rc; + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *hdmi_pll_res = vco->priv; + + rc = mdss_pll_resource_enable(hdmi_pll_res, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + /* Global Enable */ + MDSS_PLL_REG_W(hdmi_pll_res->phy_base, HDMI_PHY_GLB_CFG, 0x81); + /* Power up power gen */ + MDSS_PLL_REG_W(hdmi_pll_res->phy_base, HDMI_PHY_PD_CTRL0, 0x00); + udelay(350); + + /* PLL Power-Up */ + MDSS_PLL_REG_W(hdmi_pll_res->pll_base, HDMI_UNI_PLL_GLB_CFG, 0x01); + udelay(5); + /* Power up PLL LDO */ + MDSS_PLL_REG_W(hdmi_pll_res->pll_base, HDMI_UNI_PLL_GLB_CFG, 0x03); + udelay(350); + + /* PLL Power-Up */ + MDSS_PLL_REG_W(hdmi_pll_res->pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0F); + udelay(350); + + /* poll for PLL ready status */ + delay_us = 100; + timeout_us = 2000; + if (readl_poll_timeout_atomic( + (hdmi_pll_res->pll_base + HDMI_UNI_PLL_STATUS), + status, ((status & BIT(0)) == 1), delay_us, timeout_us)) { + pr_err("hdmi phy pll status=%x failed to Lock\n", status); + hdmi_vco_disable(c); + mdss_pll_resource_enable(hdmi_pll_res, false); + return -EINVAL; + } + pr_debug("hdmi phy pll is locked\n"); + + udelay(350); + /* poll for PHY ready status */ + delay_us = 100; + timeout_us = 2000; + if (readl_poll_timeout_atomic( + (hdmi_pll_res->phy_base + HDMI_PHY_STATUS), + status, ((status & BIT(0)) == 1), delay_us, timeout_us)) { + pr_err("hdmi phy status=%x failed to Lock\n", status); + hdmi_vco_disable(c); + mdss_pll_resource_enable(hdmi_pll_res, false); + return -EINVAL; + } + hdmi_pll_res->pll_on = true; + pr_debug("hdmi phy is locked\n"); + + return 0; +} /* hdmi_vco_enable */ + + +static void hdmi_phy_pll_calculator(u32 vco_freq, + struct mdss_pll_resources *hdmi_pll_res) +{ + u32 ref_clk = 19200000; + u32 sdm_mode = 1; + u32 ref_clk_multiplier = sdm_mode == 1 ? 2 : 1; + u32 int_ref_clk_freq = ref_clk * ref_clk_multiplier; + u32 fbclk_pre_div = 1; + u32 ssc_mode = 0; + u32 kvco = 270; + u32 vdd = 95; + u32 ten_power_six = 1000000; + u32 ssc_ds_ppm = ssc_mode ? 5000 : 0; + u32 sdm_res = 16; + u32 ssc_tri_step = 32; + u32 ssc_freq = 2; + u64 ssc_ds = vco_freq * ssc_ds_ppm; + u32 div_in_freq = vco_freq / fbclk_pre_div; + u64 dc_offset = (div_in_freq / int_ref_clk_freq - 1) * + ten_power_six * 10; + u32 ssc_kdiv = (int_ref_clk_freq / ssc_freq) - + ten_power_six; + u64 sdm_freq_seed; + u32 ssc_tri_inc; + u64 fb_div_n; + void __iomem *pll_base = hdmi_pll_res->pll_base; + u32 val; + + pr_debug("vco_freq = %u\n", vco_freq); + + do_div(ssc_ds, (u64)ten_power_six); + + fb_div_n = (u64)div_in_freq * (u64)ten_power_six * 10; + do_div(fb_div_n, int_ref_clk_freq); + + sdm_freq_seed = ((fb_div_n - dc_offset - ten_power_six * 10) * + (1 << sdm_res) * 10) + 5; + do_div(sdm_freq_seed, ((u64)ten_power_six * 100)); + + ssc_tri_inc = (u32)ssc_ds; + ssc_tri_inc = (ssc_tri_inc / int_ref_clk_freq) * (1 << 16) / + ssc_tri_step; + + val = (ref_clk_multiplier == 2 ? 1 : 0) + + ((fbclk_pre_div == 2 ? 1 : 0) * 16); + pr_debug("HDMI_UNI_PLL_REFCLK_CFG = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_REFCLK_CFG, val); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CHFPUMP_CFG, 0x02); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VCOLPF_CFG, 0x19); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VREG_CFG, 0x04); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_PWRGEN_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV2_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV3_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFR_CFG, 0x0E); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC1_CFG, 0x20); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC2_CFG, 0x0D); + + do_div(dc_offset, (u64)ten_power_six * 10); + val = sdm_mode == 0 ? 64 + dc_offset : 0; + pr_debug("HDMI_UNI_PLL_SDM_CFG0 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG0, val); + + val = 64 + dc_offset; + pr_debug("HDMI_UNI_PLL_SDM_CFG1 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG1, val); + + val = sdm_freq_seed & 0xFF; + pr_debug("HDMI_UNI_PLL_SDM_CFG2 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG2, val); + + val = (sdm_freq_seed >> 8) & 0xFF; + pr_debug("HDMI_UNI_PLL_SDM_CFG3 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG3, val); + + val = (sdm_freq_seed >> 16) & 0xFF; + pr_debug("HDMI_UNI_PLL_SDM_CFG4 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG4, val); + + val = (ssc_mode == 0 ? 128 : 0) + (ssc_kdiv / ten_power_six); + pr_debug("HDMI_UNI_PLL_SSC_CFG0 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SSC_CFG0, val); + + val = ssc_tri_inc & 0xFF; + pr_debug("HDMI_UNI_PLL_SSC_CFG1 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SSC_CFG1, val); + + val = (ssc_tri_inc >> 8) & 0xFF; + pr_debug("HDMI_UNI_PLL_SSC_CFG2 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SSC_CFG2, val); + + pr_debug("HDMI_UNI_PLL_SSC_CFG3 = 0x%x\n", ssc_tri_step); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SSC_CFG3, ssc_tri_step); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG1, 0x1A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG2, 0x05); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG0, 0x0A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG1, 0x04); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG3, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG4, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG5, 0x00); + + val = (kvco * vdd * 10000) / 6; + val += 500000; + val /= ten_power_six; + pr_debug("HDMI_UNI_PLL_CAL_CFG6 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG6, val & 0xFF); + + val = (kvco * vdd * 10000) / 6; + val -= ten_power_six; + val /= ten_power_six; + val = (val >> 8) & 0xFF; + pr_debug("HDMI_UNI_PLL_CAL_CFG7 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG7, val); + + val = (ref_clk * 5) / ten_power_six; + pr_debug("HDMI_UNI_PLL_CAL_CFG8 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG8, val); + + val = ((ref_clk * 5) / ten_power_six) >> 8; + pr_debug("HDMI_UNI_PLL_CAL_CFG9 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG9, val); + + vco_freq /= ten_power_six; + val = vco_freq & 0xFF; + pr_debug("HDMI_UNI_PLL_CAL_CFG10 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG10, val); + + val = vco_freq >> 8; + pr_debug("HDMI_UNI_PLL_CAL_CFG11 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG11, val); +} /* hdmi_phy_pll_calculator */ + +static int hdmi_vco_set_rate(struct clk *c, unsigned long rate) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *hdmi_pll_res = vco->priv; + void __iomem *pll_base; + void __iomem *phy_base; + unsigned int set_power_dwn = 0; + int rc; + + rc = mdss_pll_resource_enable(hdmi_pll_res, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + if (hdmi_pll_res->pll_on) { + hdmi_vco_disable(c); + set_power_dwn = 1; + } + + pll_base = hdmi_pll_res->pll_base; + phy_base = hdmi_pll_res->phy_base; + + pr_debug("rate=%ld\n", rate); + + switch (rate) { + case 0: + break; + + case 756000000: + /* 640x480p60 */ + MDSS_PLL_REG_W(phy_base, HDMI_PHY_GLB_CFG, 0x81); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_REFCLK_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VCOLPF_CFG, 0x19); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFR_CFG, 0x0E); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC1_CFG, 0x20); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC2_CFG, 0x0D); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG0, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG1, 0x52); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG2, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG3, 0xB0); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG4, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG1, 0x1A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG2, 0x05); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV2_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV3_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG9, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG10, 0xF4); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG11, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL0, 0x1F); + udelay(50); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0F); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x10); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG0, 0xDB); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG1, 0x43); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VREG_CFG, 0x04); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG0, 0xD0); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG1, 0x1A); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG0, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG3, 0x05); + udelay(200); + break; + + case 810000000: + /* 576p50/576i50 case */ + MDSS_PLL_REG_W(phy_base, HDMI_PHY_GLB_CFG, 0x81); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_REFCLK_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VCOLPF_CFG, 0x19); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFR_CFG, 0x0E); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC1_CFG, 0x20); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC2_CFG, 0x0D); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG0, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG1, 0x54); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG2, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG3, 0x18); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG4, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG1, 0x1A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG2, 0x05); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV2_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV3_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG9, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG10, 0x2A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG11, 0x03); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL0, 0x1F); + udelay(50); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0F); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x10); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG0, 0xDB); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG1, 0x43); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VREG_CFG, 0x04); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG0, 0xD0); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG1, 0x1A); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG0, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG3, 0x05); + udelay(200); + break; + + case 810900000: + /* 480p60/480i60 case */ + MDSS_PLL_REG_W(phy_base, HDMI_PHY_GLB_CFG, 0x81); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_REFCLK_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VCOLPF_CFG, 0x19); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFR_CFG, 0x0E); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC1_CFG, 0x20); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC2_CFG, 0x0D); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG0, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG1, 0x54); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG2, 0x66); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG3, 0x1D); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG4, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG1, 0x1A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG2, 0x05); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV2_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV3_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG9, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG10, 0x2A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG11, 0x03); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL0, 0x1F); + udelay(50); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0F); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x10); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG0, 0xDB); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG1, 0x43); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VREG_CFG, 0x04); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG0, 0xD0); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG1, 0x1A); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG0, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG3, 0x05); + udelay(200); + break; + + case 650000000: + MDSS_PLL_REG_W(phy_base, HDMI_PHY_GLB_CFG, 0x81); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_REFCLK_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VCOLPF_CFG, 0x19); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFR_CFG, 0x0E); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC1_CFG, 0x20); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC2_CFG, 0x0D); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG0, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG1, 0x4F); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG2, 0x55); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG3, 0xED); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG4, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG1, 0x1A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG2, 0x05); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV2_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV3_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG9, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG10, 0x8A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG11, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL0, 0x1F); + udelay(50); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0F); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x10); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG0, 0xDB); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG1, 0x43); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VREG_CFG, 0x04); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG0, 0xD0); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG1, 0x1A); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG0, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG3, 0x05); + udelay(200); + break; + + case 742500000: + /* + * 720p60/720p50/1080i60/1080i50 + * 1080p24/1080p30/1080p25 case + */ + MDSS_PLL_REG_W(phy_base, HDMI_PHY_GLB_CFG, 0x81); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_REFCLK_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VCOLPF_CFG, 0x19); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFR_CFG, 0x0E); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC1_CFG, 0x20); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC2_CFG, 0x0D); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG0, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG1, 0x52); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG2, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG3, 0x56); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG4, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG1, 0x1A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG2, 0x05); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV2_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV3_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG9, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG10, 0xE6); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG11, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL0, 0x1F); + udelay(50); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0F); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x10); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG0, 0xDB); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG1, 0x43); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VREG_CFG, 0x04); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG0, 0xD0); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG1, 0x1A); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG0, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG3, 0x05); + udelay(200); + break; + + case 1080000000: + MDSS_PLL_REG_W(phy_base, HDMI_PHY_GLB_CFG, 0x81); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_REFCLK_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VCOLPF_CFG, 0x19); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFR_CFG, 0x0E); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC1_CFG, 0x20); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC2_CFG, 0x0D); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG0, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG1, 0x5B); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG2, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG3, 0x20); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG4, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG1, 0x1A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG2, 0x05); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV2_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV3_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG9, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG10, 0x38); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG11, 0x04); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL0, 0x1F); + udelay(50); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0F); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x10); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG0, 0xDB); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG1, 0x43); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VREG_CFG, 0x04); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG0, 0xD0); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG1, 0x1A); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG0, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG3, 0x05); + udelay(200); + break; + + case 1342500000: + MDSS_PLL_REG_W(phy_base, HDMI_PHY_GLB_CFG, 0x81); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_REFCLK_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VCOLPF_CFG, 0x19); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFR_CFG, 0x0E); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC1_CFG, 0x20); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC2_CFG, 0x0D); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG0, 0x36); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG1, 0x61); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG2, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG3, 0xF6); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG4, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG1, 0x1A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG2, 0x05); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV2_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV3_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG9, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG10, 0x3E); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG11, 0x05); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL0, 0x1F); + udelay(50); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0F); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x10); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG0, 0xDB); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG1, 0x43); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x05); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VREG_CFG, 0x04); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG0, 0xD0); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG1, 0x1A); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG0, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG2, 0x11); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG3, 0x05); + udelay(200); + break; + + case 1485000000: + MDSS_PLL_REG_W(phy_base, HDMI_PHY_GLB_CFG, 0x81); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_REFCLK_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VCOLPF_CFG, 0x19); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFR_CFG, 0x0E); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC1_CFG, 0x20); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC2_CFG, 0x0D); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG0, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG1, 0x65); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG2, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG3, 0xAC); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG4, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG1, 0x1A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG2, 0x05); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV2_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV3_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG9, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG10, 0xCD); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG11, 0x05); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL0, 0x1F); + udelay(50); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0F); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x10); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG0, 0xDB); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG1, 0x43); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x06); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x03); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VREG_CFG, 0x04); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG0, 0xD0); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG1, 0x1A); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG0, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG3, 0x05); + udelay(200); + break; + + default: + pr_debug("Use pll settings calculator for rate=%ld\n", rate); + + MDSS_PLL_REG_W(phy_base, HDMI_PHY_GLB_CFG, 0x81); + hdmi_phy_pll_calculator(rate, hdmi_pll_res); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL0, 0x1F); + udelay(50); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0F); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x10); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG0, 0xDB); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG1, 0x43); + + if (rate < 825000000) { + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x01); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x00); + } else if (rate >= 825000000 && rate < 1342500000) { + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x05); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x03); + } else { + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x06); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x03); + } + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VREG_CFG, 0x04); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG0, 0xD0); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG1, 0x1A); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG0, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG1, 0x00); + + if (rate < 825000000) + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG2, 0x01); + else + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG2, 0x00); + + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG3, 0x05); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_BIST_PATN0, 0x62); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_BIST_PATN1, 0x03); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_BIST_PATN2, 0x69); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_BIST_PATN3, 0x02); + + udelay(200); + + MDSS_PLL_REG_W(phy_base, HDMI_PHY_BIST_CFG1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_BIST_CFG0, 0x00); + } + + /* Make sure writes complete before disabling iface clock */ + mb(); + + mdss_pll_resource_enable(hdmi_pll_res, false); + + if (set_power_dwn) + hdmi_vco_enable(c); + + vco->rate = rate; + vco->rate_set = true; + + return 0; +} /* hdmi_pll_set_rate */ + +/* HDMI PLL DIV CLK */ + +static unsigned long hdmi_vco_get_rate(struct clk *c) +{ + unsigned long freq = 0; + int rc; + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *hdmi_pll_res = vco->priv; + + if (is_gdsc_disabled(hdmi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(hdmi_pll_res, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + freq = MDSS_PLL_REG_R(hdmi_pll_res->pll_base, + HDMI_UNI_PLL_CAL_CFG11) << 8 | + MDSS_PLL_REG_R(hdmi_pll_res->pll_base, HDMI_UNI_PLL_CAL_CFG10); + + switch (freq) { + case 742: + freq = 742500000; + break; + case 810: + if (MDSS_PLL_REG_R(hdmi_pll_res->pll_base, + HDMI_UNI_PLL_SDM_CFG3) == 0x18) + freq = 810000000; + else + freq = 810900000; + break; + case 1342: + freq = 1342500000; + break; + default: + freq *= 1000000; + } + mdss_pll_resource_enable(hdmi_pll_res, false); + + return freq; +} + +static long hdmi_vco_round_rate(struct clk *c, unsigned long rate) +{ + unsigned long rrate = rate; + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + + if (rate < vco->min_rate) + rrate = vco->min_rate; + if (rate > vco->max_rate) + rrate = vco->max_rate; + + pr_debug("rrate=%ld\n", rrate); + + return rrate; +} + +static int hdmi_vco_prepare(struct clk *c) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + int ret = 0; + + pr_debug("rate=%ld\n", vco->rate); + + if (!vco->rate_set && vco->rate) + ret = hdmi_vco_set_rate(c, vco->rate); + + return ret; +} + +static void hdmi_vco_unprepare(struct clk *c) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + + vco->rate_set = false; +} + +static int hdmi_pll_lock_status(struct mdss_pll_resources *hdmi_pll_res) +{ + u32 status; + int pll_locked = 0; + int rc; + + rc = mdss_pll_resource_enable(hdmi_pll_res, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + /* poll for PLL ready status */ + if (readl_poll_timeout_atomic( + (hdmi_pll_res->phy_base + HDMI_PHY_STATUS), + status, ((status & BIT(0)) == 1), + HDMI_PLL_POLL_DELAY_US, + HDMI_PLL_POLL_TIMEOUT_US)) { + pr_debug("HDMI PLL status=%x failed to Lock\n", status); + pll_locked = 0; + } else { + pll_locked = 1; + } + mdss_pll_resource_enable(hdmi_pll_res, false); + + return pll_locked; +} + +static enum handoff hdmi_vco_handoff(struct clk *c) +{ + enum handoff ret = HANDOFF_DISABLED_CLK; + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *hdmi_pll_res = vco->priv; + + if (is_gdsc_disabled(hdmi_pll_res)) + return HANDOFF_DISABLED_CLK; + + if (mdss_pll_resource_enable(hdmi_pll_res, true)) { + pr_err("pll resource can't be enabled\n"); + return ret; + } + + hdmi_pll_res->handoff_resources = true; + + if (hdmi_pll_lock_status(hdmi_pll_res)) { + hdmi_pll_res->pll_on = true; + c->rate = hdmi_vco_get_rate(c); + ret = HANDOFF_ENABLED_CLK; + } else { + hdmi_pll_res->handoff_resources = false; + mdss_pll_resource_enable(hdmi_pll_res, false); + } + + pr_debug("done, ret=%d\n", ret); + return ret; +} + +static const struct clk_ops hdmi_vco_clk_ops = { + .enable = hdmi_vco_enable, + .set_rate = hdmi_vco_set_rate, + .get_rate = hdmi_vco_get_rate, + .round_rate = hdmi_vco_round_rate, + .prepare = hdmi_vco_prepare, + .unprepare = hdmi_vco_unprepare, + .disable = hdmi_vco_disable, + .handoff = hdmi_vco_handoff, +}; + +static struct hdmi_pll_vco_clk hdmi_vco_clk = { + .min_rate = 600000000, + .max_rate = 1800000000, + .c = { + .dbg_name = "hdmi_vco_clk", + .ops = &hdmi_vco_clk_ops, + CLK_INIT(hdmi_vco_clk.c), + }, +}; + +struct div_clk hdmipll_div1_clk = { + .data = { + .div = 1, + .min_div = 1, + .max_div = 1, + }, + .c = { + .parent = &hdmi_vco_clk.c, + .dbg_name = "hdmipll_div1_clk", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(hdmipll_div1_clk.c), + }, +}; + +struct div_clk hdmipll_div2_clk = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &hdmi_vco_clk.c, + .dbg_name = "hdmipll_div2_clk", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(hdmipll_div2_clk.c), + }, +}; + +struct div_clk hdmipll_div4_clk = { + .data = { + .div = 4, + .min_div = 4, + .max_div = 4, + }, + .c = { + .parent = &hdmi_vco_clk.c, + .dbg_name = "hdmipll_div4_clk", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(hdmipll_div4_clk.c), + }, +}; + +struct div_clk hdmipll_div6_clk = { + .data = { + .div = 6, + .min_div = 6, + .max_div = 6, + }, + .c = { + .parent = &hdmi_vco_clk.c, + .dbg_name = "hdmipll_div6_clk", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(hdmipll_div6_clk.c), + }, +}; + +static int hdmipll_set_mux_sel(struct mux_clk *clk, int mux_sel) +{ + struct mdss_pll_resources *hdmi_pll_res = clk->priv; + int rc; + + rc = mdss_pll_resource_enable(hdmi_pll_res, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + pr_debug("mux_sel=%d\n", mux_sel); + MDSS_PLL_REG_W(hdmi_pll_res->pll_base, + HDMI_UNI_PLL_POSTDIV1_CFG, mux_sel); + mdss_pll_resource_enable(hdmi_pll_res, false); + + return 0; +} + +static int hdmipll_get_mux_sel(struct mux_clk *clk) +{ + int rc; + int mux_sel = 0; + struct mdss_pll_resources *hdmi_pll_res = clk->priv; + + if (is_gdsc_disabled(hdmi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(hdmi_pll_res, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + mux_sel = MDSS_PLL_REG_R(hdmi_pll_res->pll_base, + HDMI_UNI_PLL_POSTDIV1_CFG); + mdss_pll_resource_enable(hdmi_pll_res, false); + mux_sel &= 0x03; + pr_debug("mux_sel=%d\n", mux_sel); + + return mux_sel; +} + +static struct clk_mux_ops hdmipll_mux_ops = { + .set_mux_sel = hdmipll_set_mux_sel, + .get_mux_sel = hdmipll_get_mux_sel, +}; + +static const struct clk_ops hdmi_mux_ops; + +static int hdmi_mux_prepare(struct clk *c) +{ + int ret = 0; + + if (c && c->ops && c->ops->set_rate) + ret = c->ops->set_rate(c, c->rate); + + return ret; +} + +struct mux_clk hdmipll_mux_clk = { + MUX_SRC_LIST( + { &hdmipll_div1_clk.c, 0 }, + { &hdmipll_div2_clk.c, 1 }, + { &hdmipll_div4_clk.c, 2 }, + { &hdmipll_div6_clk.c, 3 }, + ), + .ops = &hdmipll_mux_ops, + .c = { + .parent = &hdmipll_div1_clk.c, + .dbg_name = "hdmipll_mux_clk", + .ops = &hdmi_mux_ops, + CLK_INIT(hdmipll_mux_clk.c), + }, +}; + +struct div_clk hdmipll_clk_src = { + .data = { + .div = 5, + .min_div = 5, + .max_div = 5, + }, + .c = { + .parent = &hdmipll_mux_clk.c, + .dbg_name = "hdmipll_clk_src", + .ops = &clk_ops_div, + CLK_INIT(hdmipll_clk_src.c), + }, +}; + +static struct clk_lookup hdmipllcc_8974[] = { + CLK_LOOKUP("extp_clk_src", hdmipll_clk_src.c, + "fd8c0000.qcom,mmsscc-mdss"), +}; + +int hdmi_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = -ENOTSUPP; + + if (!pll_res || !pll_res->phy_base || !pll_res->pll_base) { + pr_err("Invalid input parameters\n"); + return -EPROBE_DEFER; + } + + /* Set client data for vco, mux and div clocks */ + hdmipll_clk_src.priv = pll_res; + hdmipll_mux_clk.priv = pll_res; + hdmipll_div1_clk.priv = pll_res; + hdmipll_div2_clk.priv = pll_res; + hdmipll_div4_clk.priv = pll_res; + hdmipll_div6_clk.priv = pll_res; + hdmi_vco_clk.priv = pll_res; + + /* Set hdmi mux clock operation */ + hdmi_mux_ops = clk_ops_gen_mux; + hdmi_mux_ops.prepare = hdmi_mux_prepare; + + rc = of_msm_clock_register(pdev->dev.of_node, hdmipllcc_8974, + ARRAY_SIZE(hdmipllcc_8974)); + if (rc) { + pr_err("Clock register failed rc=%d\n", rc); + rc = -EPROBE_DEFER; + } + + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-hdmi-pll-8996.c b/drivers/clk/qcom/mdss/mdss-hdmi-pll-8996.c new file mode 100644 index 000000000000..c9fb382bad34 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-hdmi-pll-8996.c @@ -0,0 +1,2685 @@ +/* Copyright (c) 2014-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-hdmi-pll.h" + +/* CONSTANTS */ +#define HDMI_BIT_CLK_TO_PIX_CLK_RATIO 10 +#define HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD 3400000000UL +#define HDMI_DIG_FREQ_BIT_CLK_THRESHOLD 1500000000UL +#define HDMI_MID_FREQ_BIT_CLK_THRESHOLD 750000000 +#define HDMI_CLKS_PLL_DIVSEL 0 +#define HDMI_CORECLK_DIV 5 +#define HDMI_REF_CLOCK 19200000 +#define HDMI_64B_ERR_VAL 0xFFFFFFFFFFFFFFFF +#define HDMI_VERSION_8996_V1 1 +#define HDMI_VERSION_8996_V2 2 +#define HDMI_VERSION_8996_V3 3 +#define HDMI_VERSION_8996_V3_1_8 4 + +#define HDMI_VCO_MAX_FREQ 12000000000 +#define HDMI_VCO_MIN_FREQ 8000000000 +#define HDMI_2400MHZ_BIT_CLK_HZ 2400000000UL +#define HDMI_2250MHZ_BIT_CLK_HZ 2250000000UL +#define HDMI_2000MHZ_BIT_CLK_HZ 2000000000UL +#define HDMI_1700MHZ_BIT_CLK_HZ 1700000000UL +#define HDMI_1200MHZ_BIT_CLK_HZ 1200000000UL +#define HDMI_1334MHZ_BIT_CLK_HZ 1334000000UL +#define HDMI_1000MHZ_BIT_CLK_HZ 1000000000UL +#define HDMI_850MHZ_BIT_CLK_HZ 850000000 +#define HDMI_667MHZ_BIT_CLK_HZ 667000000 +#define HDMI_600MHZ_BIT_CLK_HZ 600000000 +#define HDMI_500MHZ_BIT_CLK_HZ 500000000 +#define HDMI_450MHZ_BIT_CLK_HZ 450000000 +#define HDMI_334MHZ_BIT_CLK_HZ 334000000 +#define HDMI_300MHZ_BIT_CLK_HZ 300000000 +#define HDMI_282MHZ_BIT_CLK_HZ 282000000 +#define HDMI_250MHZ_BIT_CLK_HZ 250000000 +#define HDMI_KHZ_TO_HZ 1000 + +/* PLL REGISTERS */ +#define QSERDES_COM_ATB_SEL1 (0x000) +#define QSERDES_COM_ATB_SEL2 (0x004) +#define QSERDES_COM_FREQ_UPDATE (0x008) +#define QSERDES_COM_BG_TIMER (0x00C) +#define QSERDES_COM_SSC_EN_CENTER (0x010) +#define QSERDES_COM_SSC_ADJ_PER1 (0x014) +#define QSERDES_COM_SSC_ADJ_PER2 (0x018) +#define QSERDES_COM_SSC_PER1 (0x01C) +#define QSERDES_COM_SSC_PER2 (0x020) +#define QSERDES_COM_SSC_STEP_SIZE1 (0x024) +#define QSERDES_COM_SSC_STEP_SIZE2 (0x028) +#define QSERDES_COM_POST_DIV (0x02C) +#define QSERDES_COM_POST_DIV_MUX (0x030) +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN (0x034) +#define QSERDES_COM_CLK_ENABLE1 (0x038) +#define QSERDES_COM_SYS_CLK_CTRL (0x03C) +#define QSERDES_COM_SYSCLK_BUF_ENABLE (0x040) +#define QSERDES_COM_PLL_EN (0x044) +#define QSERDES_COM_PLL_IVCO (0x048) +#define QSERDES_COM_LOCK_CMP1_MODE0 (0x04C) +#define QSERDES_COM_LOCK_CMP2_MODE0 (0x050) +#define QSERDES_COM_LOCK_CMP3_MODE0 (0x054) +#define QSERDES_COM_LOCK_CMP1_MODE1 (0x058) +#define QSERDES_COM_LOCK_CMP2_MODE1 (0x05C) +#define QSERDES_COM_LOCK_CMP3_MODE1 (0x060) +#define QSERDES_COM_LOCK_CMP1_MODE2 (0x064) +#define QSERDES_COM_CMN_RSVD0 (0x064) +#define QSERDES_COM_LOCK_CMP2_MODE2 (0x068) +#define QSERDES_COM_EP_CLOCK_DETECT_CTRL (0x068) +#define QSERDES_COM_LOCK_CMP3_MODE2 (0x06C) +#define QSERDES_COM_SYSCLK_DET_COMP_STATUS (0x06C) +#define QSERDES_COM_BG_TRIM (0x070) +#define QSERDES_COM_CLK_EP_DIV (0x074) +#define QSERDES_COM_CP_CTRL_MODE0 (0x078) +#define QSERDES_COM_CP_CTRL_MODE1 (0x07C) +#define QSERDES_COM_CP_CTRL_MODE2 (0x080) +#define QSERDES_COM_CMN_RSVD1 (0x080) +#define QSERDES_COM_PLL_RCTRL_MODE0 (0x084) +#define QSERDES_COM_PLL_RCTRL_MODE1 (0x088) +#define QSERDES_COM_PLL_RCTRL_MODE2 (0x08C) +#define QSERDES_COM_CMN_RSVD2 (0x08C) +#define QSERDES_COM_PLL_CCTRL_MODE0 (0x090) +#define QSERDES_COM_PLL_CCTRL_MODE1 (0x094) +#define QSERDES_COM_PLL_CCTRL_MODE2 (0x098) +#define QSERDES_COM_CMN_RSVD3 (0x098) +#define QSERDES_COM_PLL_CNTRL (0x09C) +#define QSERDES_COM_PHASE_SEL_CTRL (0x0A0) +#define QSERDES_COM_PHASE_SEL_DC (0x0A4) +#define QSERDES_COM_CORE_CLK_IN_SYNC_SEL (0x0A8) +#define QSERDES_COM_BIAS_EN_CTRL_BY_PSM (0x0A8) +#define QSERDES_COM_SYSCLK_EN_SEL (0x0AC) +#define QSERDES_COM_CML_SYSCLK_SEL (0x0B0) +#define QSERDES_COM_RESETSM_CNTRL (0x0B4) +#define QSERDES_COM_RESETSM_CNTRL2 (0x0B8) +#define QSERDES_COM_RESTRIM_CTRL (0x0BC) +#define QSERDES_COM_RESTRIM_CTRL2 (0x0C0) +#define QSERDES_COM_RESCODE_DIV_NUM (0x0C4) +#define QSERDES_COM_LOCK_CMP_EN (0x0C8) +#define QSERDES_COM_LOCK_CMP_CFG (0x0CC) +#define QSERDES_COM_DEC_START_MODE0 (0x0D0) +#define QSERDES_COM_DEC_START_MODE1 (0x0D4) +#define QSERDES_COM_DEC_START_MODE2 (0x0D8) +#define QSERDES_COM_VCOCAL_DEADMAN_CTRL (0x0D8) +#define QSERDES_COM_DIV_FRAC_START1_MODE0 (0x0DC) +#define QSERDES_COM_DIV_FRAC_START2_MODE0 (0x0E0) +#define QSERDES_COM_DIV_FRAC_START3_MODE0 (0x0E4) +#define QSERDES_COM_DIV_FRAC_START1_MODE1 (0x0E8) +#define QSERDES_COM_DIV_FRAC_START2_MODE1 (0x0EC) +#define QSERDES_COM_DIV_FRAC_START3_MODE1 (0x0F0) +#define QSERDES_COM_DIV_FRAC_START1_MODE2 (0x0F4) +#define QSERDES_COM_VCO_TUNE_MINVAL1 (0x0F4) +#define QSERDES_COM_DIV_FRAC_START2_MODE2 (0x0F8) +#define QSERDES_COM_VCO_TUNE_MINVAL2 (0x0F8) +#define QSERDES_COM_DIV_FRAC_START3_MODE2 (0x0FC) +#define QSERDES_COM_CMN_RSVD4 (0x0FC) +#define QSERDES_COM_INTEGLOOP_INITVAL (0x100) +#define QSERDES_COM_INTEGLOOP_EN (0x104) +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 (0x108) +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 (0x10C) +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1 (0x110) +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1 (0x114) +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE2 (0x118) +#define QSERDES_COM_VCO_TUNE_MAXVAL1 (0x118) +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE2 (0x11C) +#define QSERDES_COM_VCO_TUNE_MAXVAL2 (0x11C) +#define QSERDES_COM_RES_TRIM_CONTROL2 (0x120) +#define QSERDES_COM_VCO_TUNE_CTRL (0x124) +#define QSERDES_COM_VCO_TUNE_MAP (0x128) +#define QSERDES_COM_VCO_TUNE1_MODE0 (0x12C) +#define QSERDES_COM_VCO_TUNE2_MODE0 (0x130) +#define QSERDES_COM_VCO_TUNE1_MODE1 (0x134) +#define QSERDES_COM_VCO_TUNE2_MODE1 (0x138) +#define QSERDES_COM_VCO_TUNE1_MODE2 (0x13C) +#define QSERDES_COM_VCO_TUNE_INITVAL1 (0x13C) +#define QSERDES_COM_VCO_TUNE2_MODE2 (0x140) +#define QSERDES_COM_VCO_TUNE_INITVAL2 (0x140) +#define QSERDES_COM_VCO_TUNE_TIMER1 (0x144) +#define QSERDES_COM_VCO_TUNE_TIMER2 (0x148) +#define QSERDES_COM_SAR (0x14C) +#define QSERDES_COM_SAR_CLK (0x150) +#define QSERDES_COM_SAR_CODE_OUT_STATUS (0x154) +#define QSERDES_COM_SAR_CODE_READY_STATUS (0x158) +#define QSERDES_COM_CMN_STATUS (0x15C) +#define QSERDES_COM_RESET_SM_STATUS (0x160) +#define QSERDES_COM_RESTRIM_CODE_STATUS (0x164) +#define QSERDES_COM_PLLCAL_CODE1_STATUS (0x168) +#define QSERDES_COM_PLLCAL_CODE2_STATUS (0x16C) +#define QSERDES_COM_BG_CTRL (0x170) +#define QSERDES_COM_CLK_SELECT (0x174) +#define QSERDES_COM_HSCLK_SEL (0x178) +#define QSERDES_COM_INTEGLOOP_BINCODE_STATUS (0x17C) +#define QSERDES_COM_PLL_ANALOG (0x180) +#define QSERDES_COM_CORECLK_DIV (0x184) +#define QSERDES_COM_SW_RESET (0x188) +#define QSERDES_COM_CORE_CLK_EN (0x18C) +#define QSERDES_COM_C_READY_STATUS (0x190) +#define QSERDES_COM_CMN_CONFIG (0x194) +#define QSERDES_COM_CMN_RATE_OVERRIDE (0x198) +#define QSERDES_COM_SVS_MODE_CLK_SEL (0x19C) +#define QSERDES_COM_DEBUG_BUS0 (0x1A0) +#define QSERDES_COM_DEBUG_BUS1 (0x1A4) +#define QSERDES_COM_DEBUG_BUS2 (0x1A8) +#define QSERDES_COM_DEBUG_BUS3 (0x1AC) +#define QSERDES_COM_DEBUG_BUS_SEL (0x1B0) +#define QSERDES_COM_CMN_MISC1 (0x1B4) +#define QSERDES_COM_CMN_MISC2 (0x1B8) +#define QSERDES_COM_CORECLK_DIV_MODE1 (0x1BC) +#define QSERDES_COM_CORECLK_DIV_MODE2 (0x1C0) +#define QSERDES_COM_CMN_RSVD5 (0x1C0) + +/* Tx Channel base addresses */ +#define HDMI_TX_L0_BASE_OFFSET (0x400) +#define HDMI_TX_L1_BASE_OFFSET (0x600) +#define HDMI_TX_L2_BASE_OFFSET (0x800) +#define HDMI_TX_L3_BASE_OFFSET (0xA00) + +/* Tx Channel PHY registers */ +#define QSERDES_TX_L0_BIST_MODE_LANENO (0x000) +#define QSERDES_TX_L0_BIST_INVERT (0x004) +#define QSERDES_TX_L0_CLKBUF_ENABLE (0x008) +#define QSERDES_TX_L0_CMN_CONTROL_ONE (0x00C) +#define QSERDES_TX_L0_CMN_CONTROL_TWO (0x010) +#define QSERDES_TX_L0_CMN_CONTROL_THREE (0x014) +#define QSERDES_TX_L0_TX_EMP_POST1_LVL (0x018) +#define QSERDES_TX_L0_TX_POST2_EMPH (0x01C) +#define QSERDES_TX_L0_TX_BOOST_LVL_UP_DN (0x020) +#define QSERDES_TX_L0_HP_PD_ENABLES (0x024) +#define QSERDES_TX_L0_TX_IDLE_LVL_LARGE_AMP (0x028) +#define QSERDES_TX_L0_TX_DRV_LVL (0x02C) +#define QSERDES_TX_L0_TX_DRV_LVL_OFFSET (0x030) +#define QSERDES_TX_L0_RESET_TSYNC_EN (0x034) +#define QSERDES_TX_L0_PRE_STALL_LDO_BOOST_EN (0x038) +#define QSERDES_TX_L0_TX_BAND (0x03C) +#define QSERDES_TX_L0_SLEW_CNTL (0x040) +#define QSERDES_TX_L0_INTERFACE_SELECT (0x044) +#define QSERDES_TX_L0_LPB_EN (0x048) +#define QSERDES_TX_L0_RES_CODE_LANE_TX (0x04C) +#define QSERDES_TX_L0_RES_CODE_LANE_RX (0x050) +#define QSERDES_TX_L0_RES_CODE_LANE_OFFSET (0x054) +#define QSERDES_TX_L0_PERL_LENGTH1 (0x058) +#define QSERDES_TX_L0_PERL_LENGTH2 (0x05C) +#define QSERDES_TX_L0_SERDES_BYP_EN_OUT (0x060) +#define QSERDES_TX_L0_DEBUG_BUS_SEL (0x064) +#define QSERDES_TX_L0_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN (0x068) +#define QSERDES_TX_L0_TX_POL_INV (0x06C) +#define QSERDES_TX_L0_PARRATE_REC_DETECT_IDLE_EN (0x070) +#define QSERDES_TX_L0_BIST_PATTERN1 (0x074) +#define QSERDES_TX_L0_BIST_PATTERN2 (0x078) +#define QSERDES_TX_L0_BIST_PATTERN3 (0x07C) +#define QSERDES_TX_L0_BIST_PATTERN4 (0x080) +#define QSERDES_TX_L0_BIST_PATTERN5 (0x084) +#define QSERDES_TX_L0_BIST_PATTERN6 (0x088) +#define QSERDES_TX_L0_BIST_PATTERN7 (0x08C) +#define QSERDES_TX_L0_BIST_PATTERN8 (0x090) +#define QSERDES_TX_L0_LANE_MODE (0x094) +#define QSERDES_TX_L0_IDAC_CAL_LANE_MODE (0x098) +#define QSERDES_TX_L0_IDAC_CAL_LANE_MODE_CONFIGURATION (0x09C) +#define QSERDES_TX_L0_ATB_SEL1 (0x0A0) +#define QSERDES_TX_L0_ATB_SEL2 (0x0A4) +#define QSERDES_TX_L0_RCV_DETECT_LVL (0x0A8) +#define QSERDES_TX_L0_RCV_DETECT_LVL_2 (0x0AC) +#define QSERDES_TX_L0_PRBS_SEED1 (0x0B0) +#define QSERDES_TX_L0_PRBS_SEED2 (0x0B4) +#define QSERDES_TX_L0_PRBS_SEED3 (0x0B8) +#define QSERDES_TX_L0_PRBS_SEED4 (0x0BC) +#define QSERDES_TX_L0_RESET_GEN (0x0C0) +#define QSERDES_TX_L0_RESET_GEN_MUXES (0x0C4) +#define QSERDES_TX_L0_TRAN_DRVR_EMP_EN (0x0C8) +#define QSERDES_TX_L0_TX_INTERFACE_MODE (0x0CC) +#define QSERDES_TX_L0_PWM_CTRL (0x0D0) +#define QSERDES_TX_L0_PWM_ENCODED_OR_DATA (0x0D4) +#define QSERDES_TX_L0_PWM_GEAR_1_DIVIDER_BAND2 (0x0D8) +#define QSERDES_TX_L0_PWM_GEAR_2_DIVIDER_BAND2 (0x0DC) +#define QSERDES_TX_L0_PWM_GEAR_3_DIVIDER_BAND2 (0x0E0) +#define QSERDES_TX_L0_PWM_GEAR_4_DIVIDER_BAND2 (0x0E4) +#define QSERDES_TX_L0_PWM_GEAR_1_DIVIDER_BAND0_1 (0x0E8) +#define QSERDES_TX_L0_PWM_GEAR_2_DIVIDER_BAND0_1 (0x0EC) +#define QSERDES_TX_L0_PWM_GEAR_3_DIVIDER_BAND0_1 (0x0F0) +#define QSERDES_TX_L0_PWM_GEAR_4_DIVIDER_BAND0_1 (0x0F4) +#define QSERDES_TX_L0_VMODE_CTRL1 (0x0F8) +#define QSERDES_TX_L0_VMODE_CTRL2 (0x0FC) +#define QSERDES_TX_L0_TX_ALOG_INTF_OBSV_CNTL (0x100) +#define QSERDES_TX_L0_BIST_STATUS (0x104) +#define QSERDES_TX_L0_BIST_ERROR_COUNT1 (0x108) +#define QSERDES_TX_L0_BIST_ERROR_COUNT2 (0x10C) +#define QSERDES_TX_L0_TX_ALOG_INTF_OBSV (0x110) + +/* HDMI PHY REGISTERS */ +#define HDMI_PHY_BASE_OFFSET (0xC00) + +#define HDMI_PHY_CFG (0x00) +#define HDMI_PHY_PD_CTL (0x04) +#define HDMI_PHY_MODE (0x08) +#define HDMI_PHY_MISR_CLEAR (0x0C) +#define HDMI_PHY_TX0_TX1_BIST_CFG0 (0x10) +#define HDMI_PHY_TX0_TX1_BIST_CFG1 (0x14) +#define HDMI_PHY_TX0_TX1_PRBS_SEED_BYTE0 (0x18) +#define HDMI_PHY_TX0_TX1_PRBS_SEED_BYTE1 (0x1C) +#define HDMI_PHY_TX0_TX1_BIST_PATTERN0 (0x20) +#define HDMI_PHY_TX0_TX1_BIST_PATTERN1 (0x24) +#define HDMI_PHY_TX2_TX3_BIST_CFG0 (0x28) +#define HDMI_PHY_TX2_TX3_BIST_CFG1 (0x2C) +#define HDMI_PHY_TX2_TX3_PRBS_SEED_BYTE0 (0x30) +#define HDMI_PHY_TX2_TX3_PRBS_SEED_BYTE1 (0x34) +#define HDMI_PHY_TX2_TX3_BIST_PATTERN0 (0x38) +#define HDMI_PHY_TX2_TX3_BIST_PATTERN1 (0x3C) +#define HDMI_PHY_DEBUG_BUS_SEL (0x40) +#define HDMI_PHY_TXCAL_CFG0 (0x44) +#define HDMI_PHY_TXCAL_CFG1 (0x48) +#define HDMI_PHY_TX0_TX1_LANE_CTL (0x4C) +#define HDMI_PHY_TX2_TX3_LANE_CTL (0x50) +#define HDMI_PHY_LANE_BIST_CONFIG (0x54) +#define HDMI_PHY_CLOCK (0x58) +#define HDMI_PHY_MISC1 (0x5C) +#define HDMI_PHY_MISC2 (0x60) +#define HDMI_PHY_TX0_TX1_BIST_STATUS0 (0x64) +#define HDMI_PHY_TX0_TX1_BIST_STATUS1 (0x68) +#define HDMI_PHY_TX0_TX1_BIST_STATUS2 (0x6C) +#define HDMI_PHY_TX2_TX3_BIST_STATUS0 (0x70) +#define HDMI_PHY_TX2_TX3_BIST_STATUS1 (0x74) +#define HDMI_PHY_TX2_TX3_BIST_STATUS2 (0x78) +#define HDMI_PHY_PRE_MISR_STATUS0 (0x7C) +#define HDMI_PHY_PRE_MISR_STATUS1 (0x80) +#define HDMI_PHY_PRE_MISR_STATUS2 (0x84) +#define HDMI_PHY_PRE_MISR_STATUS3 (0x88) +#define HDMI_PHY_POST_MISR_STATUS0 (0x8C) +#define HDMI_PHY_POST_MISR_STATUS1 (0x90) +#define HDMI_PHY_POST_MISR_STATUS2 (0x94) +#define HDMI_PHY_POST_MISR_STATUS3 (0x98) +#define HDMI_PHY_STATUS (0x9C) +#define HDMI_PHY_MISC3_STATUS (0xA0) +#define HDMI_PHY_MISC4_STATUS (0xA4) +#define HDMI_PHY_DEBUG_BUS0 (0xA8) +#define HDMI_PHY_DEBUG_BUS1 (0xAC) +#define HDMI_PHY_DEBUG_BUS2 (0xB0) +#define HDMI_PHY_DEBUG_BUS3 (0xB4) +#define HDMI_PHY_PHY_REVISION_ID0 (0xB8) +#define HDMI_PHY_PHY_REVISION_ID1 (0xBC) +#define HDMI_PHY_PHY_REVISION_ID2 (0xC0) +#define HDMI_PHY_PHY_REVISION_ID3 (0xC4) + +#define HDMI_PLL_POLL_MAX_READS 100 +#define HDMI_PLL_POLL_TIMEOUT_US 1500 + +enum hdmi_pll_freqs { + HDMI_PCLK_25200_KHZ, + HDMI_PCLK_27027_KHZ, + HDMI_PCLK_27000_KHZ, + HDMI_PCLK_74250_KHZ, + HDMI_PCLK_148500_KHZ, + HDMI_PCLK_154000_KHZ, + HDMI_PCLK_268500_KHZ, + HDMI_PCLK_297000_KHZ, + HDMI_PCLK_594000_KHZ, + HDMI_PCLK_MAX +}; + +struct hdmi_8996_phy_pll_reg_cfg { + u32 tx_l0_lane_mode; + u32 tx_l2_lane_mode; + u32 tx_l0_tx_band; + u32 tx_l1_tx_band; + u32 tx_l2_tx_band; + u32 tx_l3_tx_band; + u32 com_svs_mode_clk_sel; + u32 com_hsclk_sel; + u32 com_pll_cctrl_mode0; + u32 com_pll_rctrl_mode0; + u32 com_cp_ctrl_mode0; + u32 com_dec_start_mode0; + u32 com_div_frac_start1_mode0; + u32 com_div_frac_start2_mode0; + u32 com_div_frac_start3_mode0; + u32 com_integloop_gain0_mode0; + u32 com_integloop_gain1_mode0; + u32 com_lock_cmp_en; + u32 com_lock_cmp1_mode0; + u32 com_lock_cmp2_mode0; + u32 com_lock_cmp3_mode0; + u32 com_core_clk_en; + u32 com_coreclk_div; + u32 com_restrim_ctrl; + u32 com_vco_tune_ctrl; + + u32 tx_l0_tx_drv_lvl; + u32 tx_l0_tx_emp_post1_lvl; + u32 tx_l1_tx_drv_lvl; + u32 tx_l1_tx_emp_post1_lvl; + u32 tx_l2_tx_drv_lvl; + u32 tx_l2_tx_emp_post1_lvl; + u32 tx_l3_tx_drv_lvl; + u32 tx_l3_tx_emp_post1_lvl; + u32 tx_l0_vmode_ctrl1; + u32 tx_l0_vmode_ctrl2; + u32 tx_l1_vmode_ctrl1; + u32 tx_l1_vmode_ctrl2; + u32 tx_l2_vmode_ctrl1; + u32 tx_l2_vmode_ctrl2; + u32 tx_l3_vmode_ctrl1; + u32 tx_l3_vmode_ctrl2; + u32 tx_l0_res_code_lane_tx; + u32 tx_l1_res_code_lane_tx; + u32 tx_l2_res_code_lane_tx; + u32 tx_l3_res_code_lane_tx; + + u32 phy_mode; +}; + +struct hdmi_8996_v3_post_divider { + u64 vco_freq; + u64 hsclk_divsel; + u64 vco_ratio; + u64 tx_band_sel; + u64 half_rate_mode; +}; + +static inline struct hdmi_pll_vco_clk *to_hdmi_8996_vco_clk(struct clk *clk) +{ + return container_of(clk, struct hdmi_pll_vco_clk, c); +} + +static inline u64 hdmi_8996_v1_get_post_div_lt_2g(u64 bclk) +{ + if (bclk >= HDMI_2400MHZ_BIT_CLK_HZ) + return 2; + else if (bclk >= HDMI_1700MHZ_BIT_CLK_HZ) + return 3; + else if (bclk >= HDMI_1200MHZ_BIT_CLK_HZ) + return 4; + else if (bclk >= HDMI_850MHZ_BIT_CLK_HZ) + return 3; + else if (bclk >= HDMI_600MHZ_BIT_CLK_HZ) + return 4; + else if (bclk >= HDMI_450MHZ_BIT_CLK_HZ) + return 3; + else if (bclk >= HDMI_300MHZ_BIT_CLK_HZ) + return 4; + + return HDMI_64B_ERR_VAL; +} + +static inline u64 hdmi_8996_v2_get_post_div_lt_2g(u64 bclk, u64 vco_range) +{ + u64 hdmi_8ghz = vco_range; + u64 tmp_calc; + + hdmi_8ghz <<= 2; + tmp_calc = hdmi_8ghz; + do_div(tmp_calc, 6U); + + if (bclk >= vco_range) + return 2; + else if (bclk >= tmp_calc) + return 3; + else if (bclk >= vco_range >> 1) + return 4; + + tmp_calc = hdmi_8ghz; + do_div(tmp_calc, 12U); + if (bclk >= tmp_calc) + return 3; + else if (bclk >= vco_range >> 2) + return 4; + + tmp_calc = hdmi_8ghz; + do_div(tmp_calc, 24U); + if (bclk >= tmp_calc) + return 3; + else if (bclk >= vco_range >> 3) + return 4; + + return HDMI_64B_ERR_VAL; +} + +static inline u64 hdmi_8996_v2_get_post_div_gt_2g(u64 hsclk) +{ + if (hsclk >= 0 && hsclk <= 3) + return hsclk + 1; + + return HDMI_64B_ERR_VAL; +} + +static inline u64 hdmi_8996_get_coreclk_div_lt_2g(u64 bclk) +{ + if (bclk >= HDMI_1334MHZ_BIT_CLK_HZ) + return 1; + else if (bclk >= HDMI_1000MHZ_BIT_CLK_HZ) + return 1; + else if (bclk >= HDMI_667MHZ_BIT_CLK_HZ) + return 2; + else if (bclk >= HDMI_500MHZ_BIT_CLK_HZ) + return 2; + else if (bclk >= HDMI_334MHZ_BIT_CLK_HZ) + return 3; + else if (bclk >= HDMI_250MHZ_BIT_CLK_HZ) + return 3; + + return HDMI_64B_ERR_VAL; +} + +static inline u64 hdmi_8996_get_coreclk_div_ratio(u64 clks_pll_divsel, + u64 coreclk_div) +{ + if (clks_pll_divsel == 0) + return coreclk_div*2; + else if (clks_pll_divsel == 1) + return coreclk_div*4; + + return HDMI_64B_ERR_VAL; +} + +static inline u64 hdmi_8996_v1_get_tx_band(u64 bclk) +{ + if (bclk >= 2400000000UL) + return 0; + if (bclk >= 1200000000UL) + return 1; + if (bclk >= 600000000UL) + return 2; + if (bclk >= 300000000UL) + return 3; + + return HDMI_64B_ERR_VAL; +} + +static inline u64 hdmi_8996_v2_get_tx_band(u64 bclk, u64 vco_range) +{ + if (bclk >= vco_range) + return 0; + else if (bclk >= vco_range >> 1) + return 1; + else if (bclk >= vco_range >> 2) + return 2; + else if (bclk >= vco_range >> 3) + return 3; + + return HDMI_64B_ERR_VAL; +} + +static inline u64 hdmi_8996_v1_get_hsclk(u64 fdata) +{ + if (fdata >= 9600000000UL) + return 0; + else if (fdata >= 4800000000UL) + return 1; + else if (fdata >= 3200000000UL) + return 2; + else if (fdata >= 2400000000UL) + return 3; + + return HDMI_64B_ERR_VAL; +} + +static inline u64 hdmi_8996_v2_get_hsclk(u64 fdata, u64 vco_range) +{ + u64 tmp_calc = vco_range; + + tmp_calc <<= 2; + do_div(tmp_calc, 3U); + if (fdata >= (vco_range << 2)) + return 0; + else if (fdata >= (vco_range << 1)) + return 1; + else if (fdata >= tmp_calc) + return 2; + else if (fdata >= vco_range) + return 3; + + return HDMI_64B_ERR_VAL; + +} + +static inline u64 hdmi_8996_v2_get_vco_freq(u64 bclk, u64 vco_range) +{ + u64 tx_band_div_ratio = 1U << hdmi_8996_v2_get_tx_band(bclk, vco_range); + u64 pll_post_div_ratio; + + if (bclk >= vco_range) { + u64 hsclk = hdmi_8996_v2_get_hsclk(bclk, vco_range); + + pll_post_div_ratio = hdmi_8996_v2_get_post_div_gt_2g(hsclk); + } else { + pll_post_div_ratio = hdmi_8996_v2_get_post_div_lt_2g(bclk, + vco_range); + } + + return bclk * (pll_post_div_ratio * tx_band_div_ratio); +} + +static inline u64 hdmi_8996_v2_get_fdata(u64 bclk, u64 vco_range) +{ + if (bclk >= vco_range) + return bclk; + u64 tmp_calc = hdmi_8996_v2_get_vco_freq(bclk, vco_range); + u64 pll_post_div_ratio_lt_2g = hdmi_8996_v2_get_post_div_lt_2g( + bclk, vco_range); + if (pll_post_div_ratio_lt_2g == HDMI_64B_ERR_VAL) + return HDMI_64B_ERR_VAL; + + do_div(tmp_calc, pll_post_div_ratio_lt_2g); + return tmp_calc; +} + +static inline u64 hdmi_8996_get_cpctrl(u64 frac_start, bool gen_ssc) +{ + if ((frac_start != 0) || + (gen_ssc == true)) + /* + * This should be ROUND(11/(19.2/20))). + * Since ref clock does not change, hardcoding to 11 + */ + return 0xB; + + return 0x23; +} + +static inline u64 hdmi_8996_get_rctrl(u64 frac_start, bool gen_ssc) +{ + if ((frac_start != 0) || (gen_ssc == true)) + return 0x16; + + return 0x10; +} + +static inline u64 hdmi_8996_get_cctrl(u64 frac_start, bool gen_ssc) +{ + if ((frac_start != 0) || (gen_ssc == true)) + return 0x28; + + return 0x1; +} + +static inline u64 hdmi_8996_get_integloop_gain(u64 frac_start, bool gen_ssc) +{ + if ((frac_start != 0) || (gen_ssc == true)) + return 0x80; + + return 0xC4; +} + +static inline u64 hdmi_8996_v3_get_integloop_gain(u64 frac_start, u64 bclk, + bool gen_ssc) +{ + u64 digclk_divsel = bclk >= HDMI_DIG_FREQ_BIT_CLK_THRESHOLD ? 1 : 2; + u64 base = ((frac_start != 0) || (gen_ssc == true)) ? 0x40 : 0xC4; + + base <<= digclk_divsel; + + return (base <= 2046 ? base : 0x7FE); +} + +static inline u64 hdmi_8996_get_vco_tune(u64 fdata, u64 div) +{ + u64 vco_tune; + + vco_tune = fdata * div; + do_div(vco_tune, 1000000); + vco_tune = 13000 - vco_tune - 256; + do_div(vco_tune, 5); + + return vco_tune; +} + +static inline u64 hdmi_8996_get_pll_cmp(u64 pll_cmp_cnt, u64 core_clk) +{ + u64 pll_cmp; + u64 rem; + + pll_cmp = pll_cmp_cnt * core_clk; + rem = do_div(pll_cmp, HDMI_REF_CLOCK); + if (rem > (HDMI_REF_CLOCK >> 1)) + pll_cmp++; + pll_cmp -= 1; + + return pll_cmp; +} + +static inline u64 hdmi_8996_v3_get_pll_cmp(u64 pll_cmp_cnt, u64 fdata) +{ + u64 dividend = pll_cmp_cnt * fdata; + u64 divisor = HDMI_REF_CLOCK * 10; + u64 rem; + + rem = do_div(dividend, divisor); + if (rem > (divisor >> 1)) + dividend++; + + return dividend - 1; +} + +static int hdmi_8996_v3_get_post_div(struct hdmi_8996_v3_post_divider *pd, + u64 bclk) +{ + u32 ratio[] = {2, 3, 4, 5, 6, 9, 10, 12, 14, 15, 20, 21, 25, 28, 35}; + u32 tx_band_sel[] = {0, 1, 2, 3}; + u64 vco_freq[60]; + u64 vco, vco_optimal, half_rate_mode = 0; + int vco_optimal_index, vco_freq_index; + int i, j, k, x; + + for (i = 0; i <= 1; i++) { + vco_optimal = HDMI_VCO_MAX_FREQ; + vco_optimal_index = -1; + vco_freq_index = 0; + for (j = 0; j < 15; j++) { + for (k = 0; k < 4; k++) { + u64 ratio_mult = ratio[j] << tx_band_sel[k]; + + vco = bclk >> half_rate_mode; + vco *= ratio_mult; + vco_freq[vco_freq_index++] = vco; + } + } + + for (x = 0; x < 60; x++) { + u64 vco_tmp = vco_freq[x]; + + if ((vco_tmp >= HDMI_VCO_MIN_FREQ) && + (vco_tmp <= vco_optimal)) { + vco_optimal = vco_tmp; + vco_optimal_index = x; + } + } + + if (vco_optimal_index == -1) { + if (!half_rate_mode) + half_rate_mode++; + else + return -EINVAL; + } else { + pd->vco_freq = vco_optimal; + pd->tx_band_sel = tx_band_sel[vco_optimal_index % 4]; + pd->vco_ratio = ratio[vco_optimal_index / 4]; + break; + } + } + + switch (pd->vco_ratio) { + case 2: + pd->hsclk_divsel = 0; + break; + case 3: + pd->hsclk_divsel = 4; + break; + case 4: + pd->hsclk_divsel = 8; + break; + case 5: + pd->hsclk_divsel = 12; + break; + case 6: + pd->hsclk_divsel = 1; + break; + case 9: + pd->hsclk_divsel = 5; + break; + case 10: + pd->hsclk_divsel = 2; + break; + case 12: + pd->hsclk_divsel = 9; + break; + case 14: + pd->hsclk_divsel = 3; + break; + case 15: + pd->hsclk_divsel = 13; + break; + case 20: + pd->hsclk_divsel = 10; + break; + case 21: + pd->hsclk_divsel = 7; + break; + case 25: + pd->hsclk_divsel = 14; + break; + case 28: + pd->hsclk_divsel = 11; + break; + case 35: + pd->hsclk_divsel = 15; + break; + }; + + return 0; +} + +static int hdmi_8996_v1_calculate(u32 pix_clk, + struct hdmi_8996_phy_pll_reg_cfg *cfg) +{ + int rc = -EINVAL; + u64 fdata, clk_divtx, tmds_clk; + u64 bclk; + u64 post_div_gt_2g; + u64 post_div_lt_2g; + u64 coreclk_div1_lt_2g; + u64 core_clk_div_ratio; + u64 core_clk; + u64 pll_cmp; + u64 tx_band; + u64 tx_band_div_ratio; + u64 hsclk; + u64 dec_start; + u64 frac_start; + u64 pll_divisor = 4 * HDMI_REF_CLOCK; + u64 cpctrl; + u64 rctrl; + u64 cctrl; + u64 integloop_gain; + u64 vco_tune; + u64 vco_freq; + u64 rem; + + /* FDATA, CLK_DIVTX, PIXEL_CLK, TMDS_CLK */ + bclk = ((u64)pix_clk) * HDMI_BIT_CLK_TO_PIX_CLK_RATIO; + + if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) + tmds_clk = bclk/4; + else + tmds_clk = bclk; + + post_div_lt_2g = hdmi_8996_v1_get_post_div_lt_2g(bclk); + if (post_div_lt_2g == HDMI_64B_ERR_VAL) + goto fail; + + coreclk_div1_lt_2g = hdmi_8996_get_coreclk_div_lt_2g(bclk); + + core_clk_div_ratio = hdmi_8996_get_coreclk_div_ratio( + HDMI_CLKS_PLL_DIVSEL, HDMI_CORECLK_DIV); + + tx_band = hdmi_8996_v1_get_tx_band(bclk); + if (tx_band == HDMI_64B_ERR_VAL) + goto fail; + + tx_band_div_ratio = 1 << tx_band; + + if (bclk >= HDMI_2400MHZ_BIT_CLK_HZ) { + fdata = bclk; + hsclk = hdmi_8996_v1_get_hsclk(fdata); + if (hsclk == HDMI_64B_ERR_VAL) + goto fail; + + post_div_gt_2g = (hsclk <= 3) ? (hsclk + 1) : HDMI_64B_ERR_VAL; + if (post_div_gt_2g == HDMI_64B_ERR_VAL) + goto fail; + + vco_freq = bclk * (post_div_gt_2g * tx_band_div_ratio); + clk_divtx = vco_freq; + do_div(clk_divtx, post_div_gt_2g); + } else { + vco_freq = bclk * (post_div_lt_2g * tx_band_div_ratio); + fdata = vco_freq; + do_div(fdata, post_div_lt_2g); + hsclk = hdmi_8996_v1_get_hsclk(fdata); + if (hsclk == HDMI_64B_ERR_VAL) + goto fail; + + clk_divtx = vco_freq; + do_div(clk_divtx, post_div_lt_2g); + post_div_gt_2g = (hsclk <= 3) ? (hsclk + 1) : HDMI_64B_ERR_VAL; + if (post_div_gt_2g == HDMI_64B_ERR_VAL) + goto fail; + } + + /* Decimal and fraction values */ + dec_start = fdata * post_div_gt_2g; + do_div(dec_start, pll_divisor); + frac_start = ((pll_divisor - (((dec_start + 1) * pll_divisor) - + (fdata * post_div_gt_2g))) * (1 << 20)); + rem = do_div(frac_start, pll_divisor); + /* Round off frac_start to closest integer */ + if (rem >= (pll_divisor >> 1)) + frac_start++; + + cpctrl = hdmi_8996_get_cpctrl(frac_start, false); + rctrl = hdmi_8996_get_rctrl(frac_start, false); + cctrl = hdmi_8996_get_cctrl(frac_start, false); + integloop_gain = hdmi_8996_get_integloop_gain(frac_start, false); + vco_tune = hdmi_8996_get_vco_tune(fdata, post_div_gt_2g); + + core_clk = clk_divtx; + do_div(core_clk, core_clk_div_ratio); + pll_cmp = hdmi_8996_get_pll_cmp(1024, core_clk); + + /* Debug dump */ + DEV_DBG("%s: VCO freq: %llu\n", __func__, vco_freq); + DEV_DBG("%s: fdata: %llu\n", __func__, fdata); + DEV_DBG("%s: CLK_DIVTX: %llu\n", __func__, clk_divtx); + DEV_DBG("%s: pix_clk: %d\n", __func__, pix_clk); + DEV_DBG("%s: tmds clk: %llu\n", __func__, tmds_clk); + DEV_DBG("%s: HSCLK_SEL: %llu\n", __func__, hsclk); + DEV_DBG("%s: DEC_START: %llu\n", __func__, dec_start); + DEV_DBG("%s: DIV_FRAC_START: %llu\n", __func__, frac_start); + DEV_DBG("%s: PLL_CPCTRL: %llu\n", __func__, cpctrl); + DEV_DBG("%s: PLL_RCTRL: %llu\n", __func__, rctrl); + DEV_DBG("%s: PLL_CCTRL: %llu\n", __func__, cctrl); + DEV_DBG("%s: INTEGLOOP_GAIN: %llu\n", __func__, integloop_gain); + DEV_DBG("%s: VCO_TUNE: %llu\n", __func__, vco_tune); + DEV_DBG("%s: TX_BAND: %llu\n", __func__, tx_band); + DEV_DBG("%s: PLL_CMP: %llu\n", __func__, pll_cmp); + + /* Convert these values to register specific values */ + cfg->tx_l0_lane_mode = 0x3; + cfg->tx_l2_lane_mode = 0x3; + cfg->tx_l0_tx_band = tx_band + 4; + cfg->tx_l1_tx_band = tx_band + 4; + cfg->tx_l2_tx_band = tx_band + 4; + cfg->tx_l3_tx_band = tx_band + 4; + cfg->tx_l0_res_code_lane_tx = 0x33; + cfg->tx_l1_res_code_lane_tx = 0x33; + cfg->tx_l2_res_code_lane_tx = 0x33; + cfg->tx_l3_res_code_lane_tx = 0x33; + cfg->com_restrim_ctrl = 0x0; + cfg->com_vco_tune_ctrl = 0x1C; + + cfg->com_svs_mode_clk_sel = + (bclk >= HDMI_DIG_FREQ_BIT_CLK_THRESHOLD ? 1 : 2); + cfg->com_hsclk_sel = (0x28 | hsclk); + cfg->com_pll_cctrl_mode0 = cctrl; + cfg->com_pll_rctrl_mode0 = rctrl; + cfg->com_cp_ctrl_mode0 = cpctrl; + cfg->com_dec_start_mode0 = dec_start; + cfg->com_div_frac_start1_mode0 = (frac_start & 0xFF); + cfg->com_div_frac_start2_mode0 = ((frac_start & 0xFF00) >> 8); + cfg->com_div_frac_start3_mode0 = ((frac_start & 0xF0000) >> 16); + cfg->com_integloop_gain0_mode0 = (integloop_gain & 0xFF); + cfg->com_integloop_gain1_mode0 = ((integloop_gain & 0xF00) >> 8); + cfg->com_lock_cmp1_mode0 = (pll_cmp & 0xFF); + cfg->com_lock_cmp2_mode0 = ((pll_cmp & 0xFF00) >> 8); + cfg->com_lock_cmp3_mode0 = ((pll_cmp & 0x30000) >> 16); + cfg->com_core_clk_en = (0x6C | (HDMI_CLKS_PLL_DIVSEL << 4)); + cfg->com_coreclk_div = HDMI_CORECLK_DIV; + + if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) { + cfg->tx_l0_tx_drv_lvl = 0x25; + cfg->tx_l0_tx_emp_post1_lvl = 0x23; + cfg->tx_l1_tx_drv_lvl = 0x25; + cfg->tx_l1_tx_emp_post1_lvl = 0x23; + cfg->tx_l2_tx_drv_lvl = 0x25; + cfg->tx_l2_tx_emp_post1_lvl = 0x23; + cfg->tx_l3_tx_drv_lvl = 0x22; + cfg->tx_l3_tx_emp_post1_lvl = 0x27; + cfg->tx_l0_vmode_ctrl1 = 0x00; + cfg->tx_l0_vmode_ctrl2 = 0x0D; + cfg->tx_l1_vmode_ctrl1 = 0x00; + cfg->tx_l1_vmode_ctrl2 = 0x0D; + cfg->tx_l2_vmode_ctrl1 = 0x00; + cfg->tx_l2_vmode_ctrl2 = 0x0D; + cfg->tx_l3_vmode_ctrl1 = 0x00; + cfg->tx_l3_vmode_ctrl2 = 0x00; + cfg->com_restrim_ctrl = 0x0; + } else if (bclk > HDMI_MID_FREQ_BIT_CLK_THRESHOLD) { + cfg->tx_l0_tx_drv_lvl = 0x25; + cfg->tx_l0_tx_emp_post1_lvl = 0x23; + cfg->tx_l1_tx_drv_lvl = 0x25; + cfg->tx_l1_tx_emp_post1_lvl = 0x23; + cfg->tx_l2_tx_drv_lvl = 0x25; + cfg->tx_l2_tx_emp_post1_lvl = 0x23; + cfg->tx_l3_tx_drv_lvl = 0x25; + cfg->tx_l3_tx_emp_post1_lvl = 0x23; + cfg->tx_l0_vmode_ctrl1 = 0x00; + cfg->tx_l0_vmode_ctrl2 = 0x0D; + cfg->tx_l1_vmode_ctrl1 = 0x00; + cfg->tx_l1_vmode_ctrl2 = 0x0D; + cfg->tx_l2_vmode_ctrl1 = 0x00; + cfg->tx_l2_vmode_ctrl2 = 0x0D; + cfg->tx_l3_vmode_ctrl1 = 0x00; + cfg->tx_l3_vmode_ctrl2 = 0x00; + cfg->com_restrim_ctrl = 0x0; + } else { + cfg->tx_l0_tx_drv_lvl = 0x20; + cfg->tx_l0_tx_emp_post1_lvl = 0x20; + cfg->tx_l1_tx_drv_lvl = 0x20; + cfg->tx_l1_tx_emp_post1_lvl = 0x20; + cfg->tx_l2_tx_drv_lvl = 0x20; + cfg->tx_l2_tx_emp_post1_lvl = 0x20; + cfg->tx_l3_tx_drv_lvl = 0x20; + cfg->tx_l3_tx_emp_post1_lvl = 0x20; + cfg->tx_l0_vmode_ctrl1 = 0x00; + cfg->tx_l0_vmode_ctrl2 = 0x0E; + cfg->tx_l1_vmode_ctrl1 = 0x00; + cfg->tx_l1_vmode_ctrl2 = 0x0E; + cfg->tx_l2_vmode_ctrl1 = 0x00; + cfg->tx_l2_vmode_ctrl2 = 0x0E; + cfg->tx_l3_vmode_ctrl1 = 0x00; + cfg->tx_l3_vmode_ctrl2 = 0x0E; + cfg->com_restrim_ctrl = 0xD8; + } + + cfg->phy_mode = (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) ? 0x10 : 0x0; + DEV_DBG("HDMI 8996 PLL: PLL Settings\n"); + DEV_DBG("PLL PARAM: tx_l0_lane_mode = 0x%x\n", cfg->tx_l0_lane_mode); + DEV_DBG("PLL PARAM: tx_l2_lane_mode = 0x%x\n", cfg->tx_l2_lane_mode); + DEV_DBG("PLL PARAM: tx_l0_tx_band = 0x%x\n", cfg->tx_l0_tx_band); + DEV_DBG("PLL PARAM: tx_l1_tx_band = 0x%x\n", cfg->tx_l1_tx_band); + DEV_DBG("PLL PARAM: tx_l2_tx_band = 0x%x\n", cfg->tx_l2_tx_band); + DEV_DBG("PLL PARAM: tx_l3_tx_band = 0x%x\n", cfg->tx_l3_tx_band); + DEV_DBG("PLL PARAM: com_svs_mode_clk_sel = 0x%x\n", + cfg->com_svs_mode_clk_sel); + DEV_DBG("PLL PARAM: com_hsclk_sel = 0x%x\n", cfg->com_hsclk_sel); + DEV_DBG("PLL PARAM: com_pll_cctrl_mode0 = 0x%x\n", + cfg->com_pll_cctrl_mode0); + DEV_DBG("PLL PARAM: com_pll_rctrl_mode0 = 0x%x\n", + cfg->com_pll_rctrl_mode0); + DEV_DBG("PLL PARAM: com_cp_ctrl_mode0 = 0x%x\n", + cfg->com_cp_ctrl_mode0); + DEV_DBG("PLL PARAM: com_dec_start_mode0 = 0x%x\n", + cfg->com_dec_start_mode0); + DEV_DBG("PLL PARAM: com_div_frac_start1_mode0 = 0x%x\n", + cfg->com_div_frac_start1_mode0); + DEV_DBG("PLL PARAM: com_div_frac_start2_mode0 = 0x%x\n", + cfg->com_div_frac_start2_mode0); + DEV_DBG("PLL PARAM: com_div_frac_start3_mode0 = 0x%x\n", + cfg->com_div_frac_start3_mode0); + DEV_DBG("PLL PARAM: com_integloop_gain0_mode0 = 0x%x\n", + cfg->com_integloop_gain0_mode0); + DEV_DBG("PLL PARAM: com_integloop_gain1_mode0 = 0x%x\n", + cfg->com_integloop_gain1_mode0); + DEV_DBG("PLL PARAM: com_lock_cmp1_mode0 = 0x%x\n", + cfg->com_lock_cmp1_mode0); + DEV_DBG("PLL PARAM: com_lock_cmp2_mode0 = 0x%x\n", + cfg->com_lock_cmp2_mode0); + DEV_DBG("PLL PARAM: com_lock_cmp3_mode0 = 0x%x\n", + cfg->com_lock_cmp3_mode0); + DEV_DBG("PLL PARAM: com_core_clk_en = 0x%x\n", cfg->com_core_clk_en); + DEV_DBG("PLL PARAM: com_coreclk_div = 0x%x\n", cfg->com_coreclk_div); + DEV_DBG("PLL PARAM: com_restrim_ctrl = 0x%x\n", cfg->com_restrim_ctrl); + + DEV_DBG("PLL PARAM: l0_tx_drv_lvl = 0x%x\n", cfg->tx_l0_tx_drv_lvl); + DEV_DBG("PLL PARAM: l0_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l0_tx_emp_post1_lvl); + DEV_DBG("PLL PARAM: l1_tx_drv_lvl = 0x%x\n", cfg->tx_l1_tx_drv_lvl); + DEV_DBG("PLL PARAM: l1_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l1_tx_emp_post1_lvl); + DEV_DBG("PLL PARAM: l2_tx_drv_lvl = 0x%x\n", cfg->tx_l2_tx_drv_lvl); + DEV_DBG("PLL PARAM: l2_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l2_tx_emp_post1_lvl); + DEV_DBG("PLL PARAM: l3_tx_drv_lvl = 0x%x\n", cfg->tx_l3_tx_drv_lvl); + DEV_DBG("PLL PARAM: l3_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l3_tx_emp_post1_lvl); + + DEV_DBG("PLL PARAM: l0_vmode_ctrl1 = 0x%x\n", cfg->tx_l0_vmode_ctrl1); + DEV_DBG("PLL PARAM: l0_vmode_ctrl2 = 0x%x\n", cfg->tx_l0_vmode_ctrl2); + DEV_DBG("PLL PARAM: l1_vmode_ctrl1 = 0x%x\n", cfg->tx_l1_vmode_ctrl1); + DEV_DBG("PLL PARAM: l1_vmode_ctrl2 = 0x%x\n", cfg->tx_l1_vmode_ctrl2); + DEV_DBG("PLL PARAM: l2_vmode_ctrl1 = 0x%x\n", cfg->tx_l2_vmode_ctrl1); + DEV_DBG("PLL PARAM: l2_vmode_ctrl2 = 0x%x\n", cfg->tx_l2_vmode_ctrl2); + DEV_DBG("PLL PARAM: l3_vmode_ctrl1 = 0x%x\n", cfg->tx_l3_vmode_ctrl1); + DEV_DBG("PLL PARAM: l3_vmode_ctrl2 = 0x%x\n", cfg->tx_l3_vmode_ctrl2); + DEV_DBG("PLL PARAM: tx_l0_res_code_lane_tx = 0x%x\n", + cfg->tx_l0_res_code_lane_tx); + DEV_DBG("PLL PARAM: tx_l1_res_code_lane_tx = 0x%x\n", + cfg->tx_l1_res_code_lane_tx); + DEV_DBG("PLL PARAM: tx_l2_res_code_lane_tx = 0x%x\n", + cfg->tx_l2_res_code_lane_tx); + DEV_DBG("PLL PARAM: tx_l3_res_code_lane_tx = 0x%x\n", + cfg->tx_l3_res_code_lane_tx); + + DEV_DBG("PLL PARAM: phy_mode = 0x%x\n", cfg->phy_mode); + rc = 0; +fail: + return rc; +} + +static int hdmi_8996_v2_calculate(u32 pix_clk, + struct hdmi_8996_phy_pll_reg_cfg *cfg) +{ + int rc = -EINVAL; + u64 fdata, clk_divtx, tmds_clk; + u64 bclk; + u64 post_div; + u64 core_clk_div; + u64 core_clk_div_ratio; + u64 core_clk; + u64 pll_cmp; + u64 tx_band; + u64 tx_band_div_ratio; + u64 hsclk; + u64 dec_start; + u64 frac_start; + u64 pll_divisor = 4 * HDMI_REF_CLOCK; + u64 cpctrl; + u64 rctrl; + u64 cctrl; + u64 integloop_gain; + u64 vco_tune; + u64 vco_freq; + u64 vco_range; + u64 rem; + + /* FDATA, CLK_DIVTX, PIXEL_CLK, TMDS_CLK */ + bclk = ((u64)pix_clk) * HDMI_BIT_CLK_TO_PIX_CLK_RATIO; + + if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) + tmds_clk = pix_clk >> 2; + else + tmds_clk = pix_clk; + + vco_range = bclk < HDMI_282MHZ_BIT_CLK_HZ ? HDMI_2000MHZ_BIT_CLK_HZ : + HDMI_2250MHZ_BIT_CLK_HZ; + + fdata = hdmi_8996_v2_get_fdata(bclk, vco_range); + if (fdata == HDMI_64B_ERR_VAL) + goto fail; + + hsclk = hdmi_8996_v2_get_hsclk(fdata, vco_range); + if (hsclk == HDMI_64B_ERR_VAL) + goto fail; + + if (bclk >= vco_range) + post_div = hdmi_8996_v2_get_post_div_gt_2g(hsclk); + else + post_div = hdmi_8996_v2_get_post_div_lt_2g(bclk, vco_range); + + if (post_div == HDMI_64B_ERR_VAL) + goto fail; + + core_clk_div = 5; + core_clk_div_ratio = core_clk_div * 2; + + tx_band = hdmi_8996_v2_get_tx_band(bclk, vco_range); + if (tx_band == HDMI_64B_ERR_VAL) + goto fail; + + tx_band_div_ratio = 1 << tx_band; + + vco_freq = hdmi_8996_v2_get_vco_freq(bclk, vco_range); + clk_divtx = vco_freq; + do_div(clk_divtx, post_div); + + /* Decimal and fraction values */ + dec_start = fdata * post_div; + do_div(dec_start, pll_divisor); + frac_start = ((pll_divisor - (((dec_start + 1) * pll_divisor) - + (fdata * post_div))) * (1 << 20)); + rem = do_div(frac_start, pll_divisor); + /* Round off frac_start to closest integer */ + if (rem >= (pll_divisor >> 1)) + frac_start++; + + cpctrl = hdmi_8996_get_cpctrl(frac_start, false); + rctrl = hdmi_8996_get_rctrl(frac_start, false); + cctrl = hdmi_8996_get_cctrl(frac_start, false); + integloop_gain = hdmi_8996_get_integloop_gain(frac_start, false); + vco_tune = hdmi_8996_get_vco_tune(fdata, post_div); + + core_clk = clk_divtx; + do_div(core_clk, core_clk_div_ratio); + pll_cmp = hdmi_8996_get_pll_cmp(1024, core_clk); + + /* Debug dump */ + DEV_DBG("%s: VCO freq: %llu\n", __func__, vco_freq); + DEV_DBG("%s: fdata: %llu\n", __func__, fdata); + DEV_DBG("%s: CLK_DIVTX: %llu\n", __func__, clk_divtx); + DEV_DBG("%s: pix_clk: %d\n", __func__, pix_clk); + DEV_DBG("%s: tmds clk: %llu\n", __func__, tmds_clk); + DEV_DBG("%s: HSCLK_SEL: %llu\n", __func__, hsclk); + DEV_DBG("%s: DEC_START: %llu\n", __func__, dec_start); + DEV_DBG("%s: DIV_FRAC_START: %llu\n", __func__, frac_start); + DEV_DBG("%s: PLL_CPCTRL: %llu\n", __func__, cpctrl); + DEV_DBG("%s: PLL_RCTRL: %llu\n", __func__, rctrl); + DEV_DBG("%s: PLL_CCTRL: %llu\n", __func__, cctrl); + DEV_DBG("%s: INTEGLOOP_GAIN: %llu\n", __func__, integloop_gain); + DEV_DBG("%s: VCO_TUNE: %llu\n", __func__, vco_tune); + DEV_DBG("%s: TX_BAND: %llu\n", __func__, tx_band); + DEV_DBG("%s: PLL_CMP: %llu\n", __func__, pll_cmp); + + /* Convert these values to register specific values */ + cfg->tx_l0_lane_mode = 0x3; + cfg->tx_l2_lane_mode = 0x3; + cfg->tx_l0_tx_band = tx_band + 4; + cfg->tx_l1_tx_band = tx_band + 4; + cfg->tx_l2_tx_band = tx_band + 4; + cfg->tx_l3_tx_band = tx_band + 4; + + if (bclk > HDMI_DIG_FREQ_BIT_CLK_THRESHOLD) + cfg->com_svs_mode_clk_sel = 1; + else + cfg->com_svs_mode_clk_sel = 2; + + cfg->com_hsclk_sel = (0x28 | hsclk); + cfg->com_pll_cctrl_mode0 = cctrl; + cfg->com_pll_rctrl_mode0 = rctrl; + cfg->com_cp_ctrl_mode0 = cpctrl; + cfg->com_dec_start_mode0 = dec_start; + cfg->com_div_frac_start1_mode0 = (frac_start & 0xFF); + cfg->com_div_frac_start2_mode0 = ((frac_start & 0xFF00) >> 8); + cfg->com_div_frac_start3_mode0 = ((frac_start & 0xF0000) >> 16); + cfg->com_integloop_gain0_mode0 = (integloop_gain & 0xFF); + cfg->com_integloop_gain1_mode0 = ((integloop_gain & 0xF00) >> 8); + cfg->com_lock_cmp1_mode0 = (pll_cmp & 0xFF); + cfg->com_lock_cmp2_mode0 = ((pll_cmp & 0xFF00) >> 8); + cfg->com_lock_cmp3_mode0 = ((pll_cmp & 0x30000) >> 16); + cfg->com_core_clk_en = (0x6C | (HDMI_CLKS_PLL_DIVSEL << 4)); + cfg->com_coreclk_div = HDMI_CORECLK_DIV; + cfg->com_vco_tune_ctrl = 0x0; + + if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) { + cfg->tx_l0_tx_drv_lvl = 0x25; + cfg->tx_l0_tx_emp_post1_lvl = 0x23; + cfg->tx_l1_tx_drv_lvl = 0x25; + cfg->tx_l1_tx_emp_post1_lvl = 0x23; + cfg->tx_l2_tx_drv_lvl = 0x25; + cfg->tx_l2_tx_emp_post1_lvl = 0x23; + cfg->tx_l3_tx_drv_lvl = 0x22; + cfg->tx_l3_tx_emp_post1_lvl = 0x27; + cfg->tx_l0_vmode_ctrl1 = 0x00; + cfg->tx_l0_vmode_ctrl2 = 0x0D; + cfg->tx_l1_vmode_ctrl1 = 0x00; + cfg->tx_l1_vmode_ctrl2 = 0x0D; + cfg->tx_l2_vmode_ctrl1 = 0x00; + cfg->tx_l2_vmode_ctrl2 = 0x0D; + cfg->tx_l3_vmode_ctrl1 = 0x00; + cfg->tx_l3_vmode_ctrl2 = 0x00; + cfg->tx_l0_res_code_lane_tx = 0x3F; + cfg->tx_l1_res_code_lane_tx = 0x3F; + cfg->tx_l2_res_code_lane_tx = 0x3F; + cfg->tx_l3_res_code_lane_tx = 0x3F; + cfg->com_restrim_ctrl = 0x0; + } else if (bclk > HDMI_MID_FREQ_BIT_CLK_THRESHOLD) { + cfg->tx_l0_tx_drv_lvl = 0x25; + cfg->tx_l0_tx_emp_post1_lvl = 0x23; + cfg->tx_l1_tx_drv_lvl = 0x25; + cfg->tx_l1_tx_emp_post1_lvl = 0x23; + cfg->tx_l2_tx_drv_lvl = 0x25; + cfg->tx_l2_tx_emp_post1_lvl = 0x23; + cfg->tx_l3_tx_drv_lvl = 0x25; + cfg->tx_l3_tx_emp_post1_lvl = 0x23; + cfg->tx_l0_vmode_ctrl1 = 0x00; + cfg->tx_l0_vmode_ctrl2 = 0x0D; + cfg->tx_l1_vmode_ctrl1 = 0x00; + cfg->tx_l1_vmode_ctrl2 = 0x0D; + cfg->tx_l2_vmode_ctrl1 = 0x00; + cfg->tx_l2_vmode_ctrl2 = 0x0D; + cfg->tx_l3_vmode_ctrl1 = 0x00; + cfg->tx_l3_vmode_ctrl2 = 0x00; + cfg->tx_l0_res_code_lane_tx = 0x39; + cfg->tx_l1_res_code_lane_tx = 0x39; + cfg->tx_l2_res_code_lane_tx = 0x39; + cfg->tx_l3_res_code_lane_tx = 0x39; + cfg->com_restrim_ctrl = 0x0; + } else { + cfg->tx_l0_tx_drv_lvl = 0x20; + cfg->tx_l0_tx_emp_post1_lvl = 0x20; + cfg->tx_l1_tx_drv_lvl = 0x20; + cfg->tx_l1_tx_emp_post1_lvl = 0x20; + cfg->tx_l2_tx_drv_lvl = 0x20; + cfg->tx_l2_tx_emp_post1_lvl = 0x20; + cfg->tx_l3_tx_drv_lvl = 0x20; + cfg->tx_l3_tx_emp_post1_lvl = 0x20; + cfg->tx_l0_vmode_ctrl1 = 0x00; + cfg->tx_l0_vmode_ctrl2 = 0x0E; + cfg->tx_l1_vmode_ctrl1 = 0x00; + cfg->tx_l1_vmode_ctrl2 = 0x0E; + cfg->tx_l2_vmode_ctrl1 = 0x00; + cfg->tx_l2_vmode_ctrl2 = 0x0E; + cfg->tx_l3_vmode_ctrl1 = 0x00; + cfg->tx_l3_vmode_ctrl2 = 0x0E; + cfg->tx_l0_res_code_lane_tx = 0x3F; + cfg->tx_l1_res_code_lane_tx = 0x3F; + cfg->tx_l2_res_code_lane_tx = 0x3F; + cfg->tx_l3_res_code_lane_tx = 0x3F; + cfg->com_restrim_ctrl = 0xD8; + } + + cfg->phy_mode = (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) ? 0x10 : 0x0; + DEV_DBG("HDMI 8996 PLL: PLL Settings\n"); + DEV_DBG("PLL PARAM: tx_l0_lane_mode = 0x%x\n", cfg->tx_l0_lane_mode); + DEV_DBG("PLL PARAM: tx_l2_lane_mode = 0x%x\n", cfg->tx_l2_lane_mode); + DEV_DBG("PLL PARAM: tx_l0_tx_band = 0x%x\n", cfg->tx_l0_tx_band); + DEV_DBG("PLL PARAM: tx_l1_tx_band = 0x%x\n", cfg->tx_l1_tx_band); + DEV_DBG("PLL PARAM: tx_l2_tx_band = 0x%x\n", cfg->tx_l2_tx_band); + DEV_DBG("PLL PARAM: tx_l3_tx_band = 0x%x\n", cfg->tx_l3_tx_band); + DEV_DBG("PLL PARAM: com_svs_mode_clk_sel = 0x%x\n", + cfg->com_svs_mode_clk_sel); + DEV_DBG("PLL PARAM: com_vco_tune_ctrl = 0x%x\n", + cfg->com_vco_tune_ctrl); + DEV_DBG("PLL PARAM: com_hsclk_sel = 0x%x\n", cfg->com_hsclk_sel); + DEV_DBG("PLL PARAM: com_lock_cmp_en = 0x%x\n", cfg->com_lock_cmp_en); + DEV_DBG("PLL PARAM: com_pll_cctrl_mode0 = 0x%x\n", + cfg->com_pll_cctrl_mode0); + DEV_DBG("PLL PARAM: com_pll_rctrl_mode0 = 0x%x\n", + cfg->com_pll_rctrl_mode0); + DEV_DBG("PLL PARAM: com_cp_ctrl_mode0 = 0x%x\n", + cfg->com_cp_ctrl_mode0); + DEV_DBG("PLL PARAM: com_dec_start_mode0 = 0x%x\n", + cfg->com_dec_start_mode0); + DEV_DBG("PLL PARAM: com_div_frac_start1_mode0 = 0x%x\n", + cfg->com_div_frac_start1_mode0); + DEV_DBG("PLL PARAM: com_div_frac_start2_mode0 = 0x%x\n", + cfg->com_div_frac_start2_mode0); + DEV_DBG("PLL PARAM: com_div_frac_start3_mode0 = 0x%x\n", + cfg->com_div_frac_start3_mode0); + DEV_DBG("PLL PARAM: com_integloop_gain0_mode0 = 0x%x\n", + cfg->com_integloop_gain0_mode0); + DEV_DBG("PLL PARAM: com_integloop_gain1_mode0 = 0x%x\n", + cfg->com_integloop_gain1_mode0); + DEV_DBG("PLL PARAM: com_lock_cmp1_mode0 = 0x%x\n", + cfg->com_lock_cmp1_mode0); + DEV_DBG("PLL PARAM: com_lock_cmp2_mode0 = 0x%x\n", + cfg->com_lock_cmp2_mode0); + DEV_DBG("PLL PARAM: com_lock_cmp3_mode0 = 0x%x\n", + cfg->com_lock_cmp3_mode0); + DEV_DBG("PLL PARAM: com_core_clk_en = 0x%x\n", cfg->com_core_clk_en); + DEV_DBG("PLL PARAM: com_coreclk_div = 0x%x\n", cfg->com_coreclk_div); + + DEV_DBG("PLL PARAM: l0_tx_drv_lvl = 0x%x\n", cfg->tx_l0_tx_drv_lvl); + DEV_DBG("PLL PARAM: l0_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l0_tx_emp_post1_lvl); + DEV_DBG("PLL PARAM: l1_tx_drv_lvl = 0x%x\n", cfg->tx_l1_tx_drv_lvl); + DEV_DBG("PLL PARAM: l1_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l1_tx_emp_post1_lvl); + DEV_DBG("PLL PARAM: l2_tx_drv_lvl = 0x%x\n", cfg->tx_l2_tx_drv_lvl); + DEV_DBG("PLL PARAM: l2_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l2_tx_emp_post1_lvl); + DEV_DBG("PLL PARAM: l3_tx_drv_lvl = 0x%x\n", cfg->tx_l3_tx_drv_lvl); + DEV_DBG("PLL PARAM: l3_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l3_tx_emp_post1_lvl); + + DEV_DBG("PLL PARAM: l0_vmode_ctrl1 = 0x%x\n", cfg->tx_l0_vmode_ctrl1); + DEV_DBG("PLL PARAM: l0_vmode_ctrl2 = 0x%x\n", cfg->tx_l0_vmode_ctrl2); + DEV_DBG("PLL PARAM: l1_vmode_ctrl1 = 0x%x\n", cfg->tx_l1_vmode_ctrl1); + DEV_DBG("PLL PARAM: l1_vmode_ctrl2 = 0x%x\n", cfg->tx_l1_vmode_ctrl2); + DEV_DBG("PLL PARAM: l2_vmode_ctrl1 = 0x%x\n", cfg->tx_l2_vmode_ctrl1); + DEV_DBG("PLL PARAM: l2_vmode_ctrl2 = 0x%x\n", cfg->tx_l2_vmode_ctrl2); + DEV_DBG("PLL PARAM: l3_vmode_ctrl1 = 0x%x\n", cfg->tx_l3_vmode_ctrl1); + DEV_DBG("PLL PARAM: l3_vmode_ctrl2 = 0x%x\n", cfg->tx_l3_vmode_ctrl2); + DEV_DBG("PLL PARAM: tx_l0_res_code_lane_tx = 0x%x\n", + cfg->tx_l0_res_code_lane_tx); + DEV_DBG("PLL PARAM: tx_l1_res_code_lane_tx = 0x%x\n", + cfg->tx_l1_res_code_lane_tx); + DEV_DBG("PLL PARAM: tx_l2_res_code_lane_tx = 0x%x\n", + cfg->tx_l2_res_code_lane_tx); + DEV_DBG("PLL PARAM: tx_l3_res_code_lane_tx = 0x%x\n", + cfg->tx_l3_res_code_lane_tx); + DEV_DBG("PLL PARAM: com_restrim_ctrl = 0x%x\n", cfg->com_restrim_ctrl); + + DEV_DBG("PLL PARAM: phy_mode = 0x%x\n", cfg->phy_mode); + rc = 0; +fail: + return rc; +} + +static int hdmi_8996_v3_calculate(u32 pix_clk, + struct hdmi_8996_phy_pll_reg_cfg *cfg) +{ + int rc = -EINVAL; + struct hdmi_8996_v3_post_divider pd; + u64 fdata, tmds_clk; + u64 bclk; + u64 pll_cmp; + u64 tx_band; + u64 hsclk; + u64 dec_start; + u64 frac_start; + u64 pll_divisor = 4 * HDMI_REF_CLOCK; + u64 cpctrl; + u64 rctrl; + u64 cctrl; + u64 integloop_gain; + u64 vco_freq; + u64 rem; + + /* FDATA, HSCLK, PIXEL_CLK, TMDS_CLK */ + bclk = ((u64)pix_clk) * HDMI_BIT_CLK_TO_PIX_CLK_RATIO; + + if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) + tmds_clk = pix_clk >> 2; + else + tmds_clk = pix_clk; + + if (hdmi_8996_v3_get_post_div(&pd, bclk) || pd.vco_ratio <= 0 || + pd.vco_freq <= 0) + goto fail; + + vco_freq = pd.vco_freq; + fdata = pd.vco_freq; + do_div(fdata, pd.vco_ratio); + + hsclk = pd.hsclk_divsel; + dec_start = vco_freq; + do_div(dec_start, pll_divisor); + + frac_start = vco_freq * (1 << 20); + rem = do_div(frac_start, pll_divisor); + frac_start -= dec_start * (1 << 20); + if (rem > (pll_divisor >> 1)) + frac_start++; + + cpctrl = hdmi_8996_get_cpctrl(frac_start, false); + rctrl = hdmi_8996_get_rctrl(frac_start, false); + cctrl = hdmi_8996_get_cctrl(frac_start, false); + integloop_gain = hdmi_8996_v3_get_integloop_gain(frac_start, bclk, + false); + pll_cmp = hdmi_8996_v3_get_pll_cmp(1024, fdata); + tx_band = pd.tx_band_sel; + + /* Debug dump */ + DEV_DBG("%s: VCO freq: %llu\n", __func__, vco_freq); + DEV_DBG("%s: fdata: %llu\n", __func__, fdata); + DEV_DBG("%s: pix_clk: %d\n", __func__, pix_clk); + DEV_DBG("%s: tmds clk: %llu\n", __func__, tmds_clk); + DEV_DBG("%s: HSCLK_SEL: %llu\n", __func__, hsclk); + DEV_DBG("%s: DEC_START: %llu\n", __func__, dec_start); + DEV_DBG("%s: DIV_FRAC_START: %llu\n", __func__, frac_start); + DEV_DBG("%s: PLL_CPCTRL: %llu\n", __func__, cpctrl); + DEV_DBG("%s: PLL_RCTRL: %llu\n", __func__, rctrl); + DEV_DBG("%s: PLL_CCTRL: %llu\n", __func__, cctrl); + DEV_DBG("%s: INTEGLOOP_GAIN: %llu\n", __func__, integloop_gain); + DEV_DBG("%s: TX_BAND: %llu\n", __func__, tx_band); + DEV_DBG("%s: PLL_CMP: %llu\n", __func__, pll_cmp); + + /* Convert these values to register specific values */ + cfg->tx_l0_tx_band = tx_band + 4; + cfg->tx_l1_tx_band = tx_band + 4; + cfg->tx_l2_tx_band = tx_band + 4; + cfg->tx_l3_tx_band = tx_band + 4; + + if (bclk > HDMI_DIG_FREQ_BIT_CLK_THRESHOLD) + cfg->com_svs_mode_clk_sel = 1; + else + cfg->com_svs_mode_clk_sel = 2; + + cfg->com_hsclk_sel = (0x20 | hsclk); + cfg->com_pll_cctrl_mode0 = cctrl; + cfg->com_pll_rctrl_mode0 = rctrl; + cfg->com_cp_ctrl_mode0 = cpctrl; + cfg->com_dec_start_mode0 = dec_start; + cfg->com_div_frac_start1_mode0 = (frac_start & 0xFF); + cfg->com_div_frac_start2_mode0 = ((frac_start & 0xFF00) >> 8); + cfg->com_div_frac_start3_mode0 = ((frac_start & 0xF0000) >> 16); + cfg->com_integloop_gain0_mode0 = (integloop_gain & 0xFF); + cfg->com_integloop_gain1_mode0 = ((integloop_gain & 0xF00) >> 8); + cfg->com_lock_cmp1_mode0 = (pll_cmp & 0xFF); + cfg->com_lock_cmp2_mode0 = ((pll_cmp & 0xFF00) >> 8); + cfg->com_lock_cmp3_mode0 = ((pll_cmp & 0x30000) >> 16); + cfg->com_lock_cmp_en = 0x04; + cfg->com_core_clk_en = 0x2C; + cfg->com_coreclk_div = HDMI_CORECLK_DIV; + cfg->phy_mode = (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) ? 0x10 : 0x0; + cfg->com_vco_tune_ctrl = 0x0; + + cfg->tx_l0_lane_mode = 0x43; + cfg->tx_l2_lane_mode = 0x43; + + if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) { + cfg->tx_l0_tx_drv_lvl = 0x25; + cfg->tx_l0_tx_emp_post1_lvl = 0x23; + cfg->tx_l1_tx_drv_lvl = 0x25; + cfg->tx_l1_tx_emp_post1_lvl = 0x23; + cfg->tx_l2_tx_drv_lvl = 0x25; + cfg->tx_l2_tx_emp_post1_lvl = 0x23; + cfg->tx_l3_tx_drv_lvl = 0x22; + cfg->tx_l3_tx_emp_post1_lvl = 0x27; + cfg->tx_l0_vmode_ctrl1 = 0x00; + cfg->tx_l0_vmode_ctrl2 = 0x0D; + cfg->tx_l1_vmode_ctrl1 = 0x00; + cfg->tx_l1_vmode_ctrl2 = 0x0D; + cfg->tx_l2_vmode_ctrl1 = 0x00; + cfg->tx_l2_vmode_ctrl2 = 0x0D; + cfg->tx_l3_vmode_ctrl1 = 0x00; + cfg->tx_l3_vmode_ctrl2 = 0x00; + } else if (bclk > HDMI_MID_FREQ_BIT_CLK_THRESHOLD) { + cfg->tx_l0_tx_drv_lvl = 0x25; + cfg->tx_l0_tx_emp_post1_lvl = 0x23; + cfg->tx_l1_tx_drv_lvl = 0x25; + cfg->tx_l1_tx_emp_post1_lvl = 0x23; + cfg->tx_l2_tx_drv_lvl = 0x25; + cfg->tx_l2_tx_emp_post1_lvl = 0x23; + cfg->tx_l3_tx_drv_lvl = 0x25; + cfg->tx_l3_tx_emp_post1_lvl = 0x23; + cfg->tx_l0_vmode_ctrl1 = 0x00; + cfg->tx_l0_vmode_ctrl2 = 0x0D; + cfg->tx_l1_vmode_ctrl1 = 0x00; + cfg->tx_l1_vmode_ctrl2 = 0x0D; + cfg->tx_l2_vmode_ctrl1 = 0x00; + cfg->tx_l2_vmode_ctrl2 = 0x0D; + cfg->tx_l3_vmode_ctrl1 = 0x00; + cfg->tx_l3_vmode_ctrl2 = 0x00; + } else { + cfg->tx_l0_tx_drv_lvl = 0x20; + cfg->tx_l0_tx_emp_post1_lvl = 0x20; + cfg->tx_l1_tx_drv_lvl = 0x20; + cfg->tx_l1_tx_emp_post1_lvl = 0x20; + cfg->tx_l2_tx_drv_lvl = 0x20; + cfg->tx_l2_tx_emp_post1_lvl = 0x20; + cfg->tx_l3_tx_drv_lvl = 0x20; + cfg->tx_l3_tx_emp_post1_lvl = 0x20; + cfg->tx_l0_vmode_ctrl1 = 0x00; + cfg->tx_l0_vmode_ctrl2 = 0x0E; + cfg->tx_l1_vmode_ctrl1 = 0x00; + cfg->tx_l1_vmode_ctrl2 = 0x0E; + cfg->tx_l2_vmode_ctrl1 = 0x00; + cfg->tx_l2_vmode_ctrl2 = 0x0E; + cfg->tx_l3_vmode_ctrl1 = 0x00; + cfg->tx_l3_vmode_ctrl2 = 0x0E; + } + + DEV_DBG("HDMI 8996 PLL: PLL Settings\n"); + DEV_DBG("PLL PARAM: tx_l0_tx_band = 0x%x\n", cfg->tx_l0_tx_band); + DEV_DBG("PLL PARAM: tx_l1_tx_band = 0x%x\n", cfg->tx_l1_tx_band); + DEV_DBG("PLL PARAM: tx_l2_tx_band = 0x%x\n", cfg->tx_l2_tx_band); + DEV_DBG("PLL PARAM: tx_l3_tx_band = 0x%x\n", cfg->tx_l3_tx_band); + DEV_DBG("PLL PARAM: com_svs_mode_clk_sel = 0x%x\n", + cfg->com_svs_mode_clk_sel); + DEV_DBG("PLL PARAM: com_hsclk_sel = 0x%x\n", cfg->com_hsclk_sel); + DEV_DBG("PLL PARAM: com_lock_cmp_en = 0x%x\n", cfg->com_lock_cmp_en); + DEV_DBG("PLL PARAM: com_pll_cctrl_mode0 = 0x%x\n", + cfg->com_pll_cctrl_mode0); + DEV_DBG("PLL PARAM: com_pll_rctrl_mode0 = 0x%x\n", + cfg->com_pll_rctrl_mode0); + DEV_DBG("PLL PARAM: com_cp_ctrl_mode0 = 0x%x\n", + cfg->com_cp_ctrl_mode0); + DEV_DBG("PLL PARAM: com_dec_start_mode0 = 0x%x\n", + cfg->com_dec_start_mode0); + DEV_DBG("PLL PARAM: com_div_frac_start1_mode0 = 0x%x\n", + cfg->com_div_frac_start1_mode0); + DEV_DBG("PLL PARAM: com_div_frac_start2_mode0 = 0x%x\n", + cfg->com_div_frac_start2_mode0); + DEV_DBG("PLL PARAM: com_div_frac_start3_mode0 = 0x%x\n", + cfg->com_div_frac_start3_mode0); + DEV_DBG("PLL PARAM: com_integloop_gain0_mode0 = 0x%x\n", + cfg->com_integloop_gain0_mode0); + DEV_DBG("PLL PARAM: com_integloop_gain1_mode0 = 0x%x\n", + cfg->com_integloop_gain1_mode0); + DEV_DBG("PLL PARAM: com_lock_cmp1_mode0 = 0x%x\n", + cfg->com_lock_cmp1_mode0); + DEV_DBG("PLL PARAM: com_lock_cmp2_mode0 = 0x%x\n", + cfg->com_lock_cmp2_mode0); + DEV_DBG("PLL PARAM: com_lock_cmp3_mode0 = 0x%x\n", + cfg->com_lock_cmp3_mode0); + DEV_DBG("PLL PARAM: com_core_clk_en = 0x%x\n", cfg->com_core_clk_en); + DEV_DBG("PLL PARAM: com_coreclk_div = 0x%x\n", cfg->com_coreclk_div); + DEV_DBG("PLL PARAM: phy_mode = 0x%x\n", cfg->phy_mode); + + DEV_DBG("PLL PARAM: tx_l0_lane_mode = 0x%x\n", cfg->tx_l0_lane_mode); + DEV_DBG("PLL PARAM: tx_l2_lane_mode = 0x%x\n", cfg->tx_l2_lane_mode); + DEV_DBG("PLL PARAM: l0_tx_drv_lvl = 0x%x\n", cfg->tx_l0_tx_drv_lvl); + DEV_DBG("PLL PARAM: l0_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l0_tx_emp_post1_lvl); + DEV_DBG("PLL PARAM: l1_tx_drv_lvl = 0x%x\n", cfg->tx_l1_tx_drv_lvl); + DEV_DBG("PLL PARAM: l1_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l1_tx_emp_post1_lvl); + DEV_DBG("PLL PARAM: l2_tx_drv_lvl = 0x%x\n", cfg->tx_l2_tx_drv_lvl); + DEV_DBG("PLL PARAM: l2_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l2_tx_emp_post1_lvl); + DEV_DBG("PLL PARAM: l3_tx_drv_lvl = 0x%x\n", cfg->tx_l3_tx_drv_lvl); + DEV_DBG("PLL PARAM: l3_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l3_tx_emp_post1_lvl); + + DEV_DBG("PLL PARAM: l0_vmode_ctrl1 = 0x%x\n", cfg->tx_l0_vmode_ctrl1); + DEV_DBG("PLL PARAM: l0_vmode_ctrl2 = 0x%x\n", cfg->tx_l0_vmode_ctrl2); + DEV_DBG("PLL PARAM: l1_vmode_ctrl1 = 0x%x\n", cfg->tx_l1_vmode_ctrl1); + DEV_DBG("PLL PARAM: l1_vmode_ctrl2 = 0x%x\n", cfg->tx_l1_vmode_ctrl2); + DEV_DBG("PLL PARAM: l2_vmode_ctrl1 = 0x%x\n", cfg->tx_l2_vmode_ctrl1); + DEV_DBG("PLL PARAM: l2_vmode_ctrl2 = 0x%x\n", cfg->tx_l2_vmode_ctrl2); + DEV_DBG("PLL PARAM: l3_vmode_ctrl1 = 0x%x\n", cfg->tx_l3_vmode_ctrl1); + DEV_DBG("PLL PARAM: l3_vmode_ctrl2 = 0x%x\n", cfg->tx_l3_vmode_ctrl2); + rc = 0; +fail: + return rc; +} + +static int hdmi_8996_calculate(u32 pix_clk, + struct hdmi_8996_phy_pll_reg_cfg *cfg, u32 ver) +{ + switch (ver) { + case HDMI_VERSION_8996_V3: + case HDMI_VERSION_8996_V3_1_8: + return hdmi_8996_v3_calculate(pix_clk, cfg); + case HDMI_VERSION_8996_V2: + return hdmi_8996_v2_calculate(pix_clk, cfg); + default: + return hdmi_8996_v1_calculate(pix_clk, cfg); + } +} + +static int hdmi_8996_phy_pll_set_clk_rate(struct clk *c, u32 tmds_clk, u32 ver) +{ + int rc = 0; + struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + struct hdmi_8996_phy_pll_reg_cfg cfg = {0}; + + rc = hdmi_8996_calculate(tmds_clk, &cfg, ver); + if (rc) { + DEV_ERR("%s: PLL calculation failed\n", __func__); + return rc; + } + + /* Initially shut down PHY */ + DEV_DBG("%s: Disabling PHY\n", __func__); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_PD_CTL, 0x0); + udelay(500); + + /* Power up sequence */ + switch (ver) { + case HDMI_VERSION_8996_V2: + case HDMI_VERSION_8996_V3: + case HDMI_VERSION_8996_V3_1_8: + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_BG_CTRL, 0x04); + break; + }; + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_PD_CTL, 0x1); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RESETSM_CNTRL, 0x20); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_TX0_TX1_LANE_CTL, 0x0F); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_TX2_TX3_LANE_CTL, 0x0F); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_CLKBUF_ENABLE, 0x03); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_CLKBUF_ENABLE, 0x03); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_CLKBUF_ENABLE, 0x03); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_CLKBUF_ENABLE, 0x03); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_LANE_MODE, cfg.tx_l0_lane_mode); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_LANE_MODE, cfg.tx_l2_lane_mode); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_TX_BAND, cfg.tx_l0_tx_band); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_TX_BAND, cfg.tx_l1_tx_band); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_TX_BAND, cfg.tx_l2_tx_band); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_TX_BAND, cfg.tx_l3_tx_band); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_RESET_TSYNC_EN, 0x03); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_RESET_TSYNC_EN, 0x03); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_RESET_TSYNC_EN, 0x03); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_RESET_TSYNC_EN, 0x03); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SYSCLK_BUF_ENABLE, 0x1E); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x07); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SYSCLK_EN_SEL, 0x37); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SYS_CLK_CTRL, 0x02); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CLK_ENABLE1, 0x0E); + if (ver == HDMI_VERSION_8996_V1) + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_BG_CTRL, 0x06); + + /* Bypass VCO calibration */ + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SVS_MODE_CLK_SEL, + cfg.com_svs_mode_clk_sel); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_BG_TRIM, 0x0F); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_IVCO, 0x0F); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VCO_TUNE_CTRL, + cfg.com_vco_tune_ctrl); + + switch (ver) { + case HDMI_VERSION_8996_V1: + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SVS_MODE_CLK_SEL, + cfg.com_svs_mode_clk_sel); + break; + default: + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_BG_CTRL, 0x06); + } + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CLK_SELECT, 0x30); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_HSCLK_SEL, + cfg.com_hsclk_sel); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP_EN, + cfg.com_lock_cmp_en); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_CCTRL_MODE0, + cfg.com_pll_cctrl_mode0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_RCTRL_MODE0, + cfg.com_pll_rctrl_mode0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CP_CTRL_MODE0, + cfg.com_cp_ctrl_mode0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DEC_START_MODE0, + cfg.com_dec_start_mode0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DIV_FRAC_START1_MODE0, + cfg.com_div_frac_start1_mode0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DIV_FRAC_START2_MODE0, + cfg.com_div_frac_start2_mode0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DIV_FRAC_START3_MODE0, + cfg.com_div_frac_start3_mode0); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_INTEGLOOP_GAIN0_MODE0, + cfg.com_integloop_gain0_mode0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_INTEGLOOP_GAIN1_MODE0, + cfg.com_integloop_gain1_mode0); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP1_MODE0, + cfg.com_lock_cmp1_mode0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP2_MODE0, + cfg.com_lock_cmp2_mode0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP3_MODE0, + cfg.com_lock_cmp3_mode0); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VCO_TUNE_MAP, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CORE_CLK_EN, + cfg.com_core_clk_en); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CORECLK_DIV, + cfg.com_coreclk_div); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CMN_CONFIG, 0x02); + + if (ver == HDMI_VERSION_8996_V3 || ver == HDMI_VERSION_8996_V3_1_8) + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RESCODE_DIV_NUM, 0x15); + + /* TX lanes setup (TX 0/1/2/3) */ + if (ver == HDMI_VERSION_8996_V3_1_8) { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL, + 0x00000023); + } else { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL, + cfg.tx_l0_tx_drv_lvl); + } + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_TX_EMP_POST1_LVL, + cfg.tx_l0_tx_emp_post1_lvl); + + if (ver == HDMI_VERSION_8996_V3_1_8) { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL, + 0x00000023); + } else { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL, + cfg.tx_l1_tx_drv_lvl); + } + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_TX_EMP_POST1_LVL, + cfg.tx_l1_tx_emp_post1_lvl); + + if (ver == HDMI_VERSION_8996_V3_1_8) { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL, + 0x00000023); + } else { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL, + cfg.tx_l2_tx_drv_lvl); + } + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_TX_EMP_POST1_LVL, + cfg.tx_l2_tx_emp_post1_lvl); + + if (ver == HDMI_VERSION_8996_V3_1_8) { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL, + 0x00000020); + } else { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL, + cfg.tx_l3_tx_drv_lvl); + } + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_TX_EMP_POST1_LVL, + cfg.tx_l3_tx_emp_post1_lvl); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_VMODE_CTRL1, + cfg.tx_l0_vmode_ctrl1); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_VMODE_CTRL2, + cfg.tx_l0_vmode_ctrl2); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_VMODE_CTRL1, + cfg.tx_l1_vmode_ctrl1); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_VMODE_CTRL2, + cfg.tx_l1_vmode_ctrl2); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_VMODE_CTRL1, + cfg.tx_l2_vmode_ctrl1); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_VMODE_CTRL2, + cfg.tx_l2_vmode_ctrl2); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_VMODE_CTRL1, + cfg.tx_l3_vmode_ctrl1); + if (ver == HDMI_VERSION_8996_V3_1_8) { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_VMODE_CTRL2, + 0x0000000D); + } else { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_VMODE_CTRL2, + cfg.tx_l3_vmode_ctrl2); + } + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL_OFFSET, 0x00); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL_OFFSET, 0x00); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL_OFFSET, 0x00); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL_OFFSET, 0x00); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_RES_CODE_LANE_OFFSET, 0x00); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_RES_CODE_LANE_OFFSET, 0x00); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_RES_CODE_LANE_OFFSET, 0x00); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_RES_CODE_LANE_OFFSET, 0x00); + + if (ver < HDMI_VERSION_8996_V3) { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_RES_CODE_LANE_TX, + cfg.tx_l0_res_code_lane_tx); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_RES_CODE_LANE_TX, + cfg.tx_l1_res_code_lane_tx); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_RES_CODE_LANE_TX, + cfg.tx_l2_res_code_lane_tx); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_RES_CODE_LANE_TX, + cfg.tx_l3_res_code_lane_tx); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RESTRIM_CTRL, + cfg.com_restrim_ctrl); + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_TXCAL_CFG0, 0x00); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_TXCAL_CFG1, 0x05); + } + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_MODE, cfg.phy_mode); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_PD_CTL, 0x1F); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_TRAN_DRVR_EMP_EN, 0x03); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_TRAN_DRVR_EMP_EN, 0x03); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_TRAN_DRVR_EMP_EN, 0x03); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_TRAN_DRVR_EMP_EN, 0x03); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_PARRATE_REC_DETECT_IDLE_EN, 0x40); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_PARRATE_REC_DETECT_IDLE_EN, 0x40); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_PARRATE_REC_DETECT_IDLE_EN, 0x40); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_PARRATE_REC_DETECT_IDLE_EN, 0x40); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_HP_PD_ENABLES, 0x0C); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_HP_PD_ENABLES, 0x0C); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_HP_PD_ENABLES, 0x0C); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_HP_PD_ENABLES, 0x03); + + if (ver == HDMI_VERSION_8996_V2) { + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_ATB_SEL1, 0x01); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_ATB_SEL2, 0x01); + } + /* + * Ensure that vco configuration gets flushed to hardware before + * enabling the PLL + */ + wmb(); + return 0; +} + +static int hdmi_8996_phy_ready_status(struct mdss_pll_resources *io) +{ + u32 status = 0; + int phy_ready = 0; + int rc; + u32 read_count = 0; + + rc = mdss_pll_resource_enable(io, true); + if (rc) { + DEV_ERR("%s: pll resource can't be enabled\n", __func__); + return rc; + } + + DEV_DBG("%s: Waiting for PHY Ready\n", __func__); + + /* Poll for PHY read status */ + while (read_count < HDMI_PLL_POLL_MAX_READS) { + status = MDSS_PLL_REG_R(io->phy_base, HDMI_PHY_STATUS); + if ((status & BIT(0)) == 1) { + phy_ready = 1; + DEV_DBG("%s: PHY READY\n", __func__); + break; + } + udelay(HDMI_PLL_POLL_TIMEOUT_US); + read_count++; + } + + if (read_count == HDMI_PLL_POLL_MAX_READS) { + phy_ready = 0; + DEV_DBG("%s: PHY READY TIMEOUT\n", __func__); + } + + mdss_pll_resource_enable(io, false); + + return phy_ready; +} + +static int hdmi_8996_pll_lock_status(struct mdss_pll_resources *io) +{ + u32 status; + int pll_locked = 0; + int rc; + u32 read_count = 0; + + rc = mdss_pll_resource_enable(io, true); + if (rc) { + DEV_ERR("%s: pll resource can't be enabled\n", __func__); + return rc; + } + + DEV_DBG("%s: Waiting for PLL lock\n", __func__); + + while (read_count < HDMI_PLL_POLL_MAX_READS) { + status = MDSS_PLL_REG_R(io->pll_base, + QSERDES_COM_C_READY_STATUS); + if ((status & BIT(0)) == 1) { + pll_locked = 1; + DEV_DBG("%s: C READY\n", __func__); + break; + } + udelay(HDMI_PLL_POLL_TIMEOUT_US); + read_count++; + } + + if (read_count == HDMI_PLL_POLL_MAX_READS) { + pll_locked = 0; + DEV_DBG("%s: C READY TIMEOUT\n", __func__); + } + + mdss_pll_resource_enable(io, false); + + return pll_locked; +} + +static int hdmi_8996_v1_perform_sw_calibration(struct clk *c) +{ + int rc = 0; + struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + u32 max_code = 0x190; + u32 min_code = 0x0; + u32 max_cnt = 0; + u32 min_cnt = 0; + u32 expected_counter_value = 0; + u32 step = 0; + u32 dbus_all = 0; + u32 dbus_sel = 0; + u32 vco_code = 0; + u32 val = 0; + + vco_code = 0xC8; + + DEV_DBG("%s: Starting SW calibration with vco_code = %d\n", __func__, + vco_code); + + expected_counter_value = + (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP3_MODE0) << 16) | + (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP2_MODE0) << 8) | + (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP1_MODE0)); + + DEV_DBG("%s: expected_counter_value = %d\n", __func__, + expected_counter_value); + + val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_CMN_MISC1); + val |= BIT(4); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CMN_MISC1, val); + + val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_CMN_MISC1); + val |= BIT(3); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CMN_MISC1, val); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DEBUG_BUS_SEL, 0x4); + + val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP_CFG); + val |= BIT(1); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP_CFG, val); + + udelay(60); + + while (1) { + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VCO_TUNE1_MODE0, + vco_code & 0xFF); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VCO_TUNE2_MODE0, + (vco_code >> 8) & 0x3); + + udelay(20); + + val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP_CFG); + val &= ~BIT(1); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP_CFG, val); + + udelay(60); + + val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP_CFG); + val |= BIT(1); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP_CFG, val); + + udelay(60); + + dbus_all = + (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_DEBUG_BUS3) << 24) | + (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_DEBUG_BUS2) << 16) | + (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_DEBUG_BUS1) << 8) | + (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_DEBUG_BUS0)); + + dbus_sel = (dbus_all >> 9) & 0x3FFFF; + DEV_DBG("%s: loop[%d], dbus_all = 0x%x, dbus_sel = 0x%x\n", + __func__, step, dbus_all, dbus_sel); + if (dbus_sel == 0) + DEV_ERR("%s: CHECK HDMI REF CLK\n", __func__); + + if (dbus_sel == expected_counter_value) { + max_code = vco_code; + max_cnt = dbus_sel; + min_code = vco_code; + min_cnt = dbus_sel; + } else if (dbus_sel == 0) { + max_code = vco_code; + max_cnt = dbus_sel; + vco_code = (max_code + min_code)/2; + } else if (dbus_sel > expected_counter_value) { + min_code = vco_code; + min_cnt = dbus_sel; + vco_code = (max_code + min_code)/2; + } else if (dbus_sel < expected_counter_value) { + max_code = vco_code; + max_cnt = dbus_sel; + vco_code = (max_code + min_code)/2; + } + + step++; + + if ((vco_code == 0) || (vco_code == 0x3FF) || (step > 0x3FF)) { + DEV_ERR("%s: VCO tune code search failed\n", __func__); + rc = -ENOTSUPP; + break; + } + if ((max_code - min_code) <= 1) { + if ((max_code - min_code) == 1) { + if (abs((int)(max_cnt - expected_counter_value)) + < abs((int)(min_cnt - expected_counter_value + ))) { + vco_code = max_code; + } else { + vco_code = min_code; + } + } + break; + } + DEV_DBG("%s: loop[%d], new vco_code = %d\n", __func__, step, + vco_code); + } + + DEV_DBG("%s: CALIB done. vco_code = %d\n", __func__, vco_code); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VCO_TUNE1_MODE0, + vco_code & 0xFF); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VCO_TUNE2_MODE0, + (vco_code >> 8) & 0x3); + val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP_CFG); + val &= ~BIT(1); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP_CFG, val); + + val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_CMN_MISC1); + val |= BIT(4); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CMN_MISC1, val); + + val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_CMN_MISC1); + val &= ~BIT(3); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CMN_MISC1, val); + + return rc; +} + +static int hdmi_8996_v2_perform_sw_calibration(struct clk *c) +{ + int rc = 0; + struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + u32 vco_code1, vco_code2, integral_loop, ready_poll; + u32 read_count = 0; + + while (read_count < (HDMI_PLL_POLL_MAX_READS << 1)) { + ready_poll = MDSS_PLL_REG_R(io->pll_base, + QSERDES_COM_C_READY_STATUS); + if ((ready_poll & BIT(0)) == 1) { + ready_poll = 1; + DEV_DBG("%s: C READY\n", __func__); + break; + } + udelay(HDMI_PLL_POLL_TIMEOUT_US); + read_count++; + } + + if (read_count == (HDMI_PLL_POLL_MAX_READS << 1)) { + ready_poll = 0; + DEV_DBG("%s: C READY TIMEOUT, TRYING SW CALIBRATION\n", + __func__); + } + + vco_code1 = MDSS_PLL_REG_R(io->pll_base, + QSERDES_COM_PLLCAL_CODE1_STATUS); + vco_code2 = MDSS_PLL_REG_R(io->pll_base, + QSERDES_COM_PLLCAL_CODE2_STATUS); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DEBUG_BUS_SEL, 0x5); + integral_loop = MDSS_PLL_REG_R(io->pll_base, + QSERDES_COM_DEBUG_BUS0); + + if (((ready_poll & 0x1) == 0) || (((ready_poll & 1) == 1) && + (vco_code1 == 0xFF) && ((vco_code2 & 0x3) == 0x1) && + (integral_loop > 0xC0))) { + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_ATB_SEL1, 0x04); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_ATB_SEL2, 0x00); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x17); + udelay(100); + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x11); + udelay(100); + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x19); + } + return rc; +} + +static int hdmi_8996_perform_sw_calibration(struct clk *c, u32 ver) +{ + switch (ver) { + case HDMI_VERSION_8996_V1: + return hdmi_8996_v1_perform_sw_calibration(c); + case HDMI_VERSION_8996_V2: + return hdmi_8996_v2_perform_sw_calibration(c); + } + return 0; +} + +static int hdmi_8996_vco_enable(struct clk *c, u32 ver) +{ + int rc = 0; + struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x1); + udelay(100); + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x19); + udelay(100); + + rc = hdmi_8996_perform_sw_calibration(c, ver); + if (rc) { + DEV_ERR("%s: software calibration failed\n", __func__); + return rc; + } + + rc = hdmi_8996_pll_lock_status(io); + if (!rc) { + DEV_ERR("%s: PLL not locked\n", __func__); + return rc; + } + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, + 0x6F); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, + 0x6F); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, + 0x6F); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, + 0x6F); + + /* Disable SSC */ + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SSC_PER1, 0x0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SSC_PER2, 0x0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SSC_STEP_SIZE1, 0x0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SSC_STEP_SIZE2, 0x0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SSC_EN_CENTER, 0x2); + + rc = hdmi_8996_phy_ready_status(io); + if (!rc) { + DEV_ERR("%s: PHY not READY\n", __func__); + return rc; + } + + /* Restart the retiming buffer */ + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x18); + udelay(1); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x19); + + io->pll_on = true; + return 0; +} + +static int hdmi_8996_v1_vco_enable(struct clk *c) +{ + return hdmi_8996_vco_enable(c, HDMI_VERSION_8996_V1); +} + +static int hdmi_8996_v2_vco_enable(struct clk *c) +{ + return hdmi_8996_vco_enable(c, HDMI_VERSION_8996_V2); +} + +static int hdmi_8996_v3_vco_enable(struct clk *c) +{ + return hdmi_8996_vco_enable(c, HDMI_VERSION_8996_V3); +} + +static int hdmi_8996_v3_1p8_vco_enable(struct clk *c) +{ + return hdmi_8996_vco_enable(c, HDMI_VERSION_8996_V3_1_8); +} + +static int hdmi_8996_vco_get_lock_range(struct clk *c, unsigned long pixel_clk) +{ + u32 rng = 64, cmp_cnt = 1024; + u32 coreclk_div = 5, clks_pll_divsel = 2; + u32 vco_freq, vco_ratio, ppm_range; + u64 bclk; + struct hdmi_8996_v3_post_divider pd; + + bclk = ((u64)pixel_clk) * HDMI_BIT_CLK_TO_PIX_CLK_RATIO; + + DEV_DBG("%s: rate=%ld\n", __func__, pixel_clk); + + if (hdmi_8996_v3_get_post_div(&pd, bclk) || + pd.vco_ratio <= 0 || pd.vco_freq <= 0) { + DEV_ERR("%s: couldn't get post div\n", __func__); + return -EINVAL; + } + + do_div(pd.vco_freq, HDMI_KHZ_TO_HZ * HDMI_KHZ_TO_HZ); + + vco_freq = (u32) pd.vco_freq; + vco_ratio = (u32) pd.vco_ratio; + + DEV_DBG("%s: freq %d, ratio %d\n", __func__, + vco_freq, vco_ratio); + + ppm_range = (rng * HDMI_REF_CLOCK) / cmp_cnt; + ppm_range /= vco_freq / vco_ratio; + ppm_range *= coreclk_div * clks_pll_divsel; + + DEV_DBG("%s: ppm range: %d\n", __func__, ppm_range); + + return ppm_range; +} + +static int hdmi_8996_vco_rate_atomic_update(struct clk *c, + unsigned long rate, u32 ver) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + void __iomem *pll; + struct hdmi_8996_phy_pll_reg_cfg cfg = {0}; + int rc = 0; + + rc = hdmi_8996_calculate(rate, &cfg, ver); + if (rc) { + DEV_ERR("%s: PLL calculation failed\n", __func__); + goto end; + } + + pll = io->pll_base; + + MDSS_PLL_REG_W(pll, QSERDES_COM_DEC_START_MODE0, + cfg.com_dec_start_mode0); + MDSS_PLL_REG_W(pll, QSERDES_COM_DIV_FRAC_START1_MODE0, + cfg.com_div_frac_start1_mode0); + MDSS_PLL_REG_W(pll, QSERDES_COM_DIV_FRAC_START2_MODE0, + cfg.com_div_frac_start2_mode0); + MDSS_PLL_REG_W(pll, QSERDES_COM_DIV_FRAC_START3_MODE0, + cfg.com_div_frac_start3_mode0); + + MDSS_PLL_REG_W(pll, QSERDES_COM_FREQ_UPDATE, 0x01); + MDSS_PLL_REG_W(pll, QSERDES_COM_FREQ_UPDATE, 0x00); + + DEV_DBG("%s: updated to rate %ld\n", __func__, rate); +end: + return rc; +} + +static int hdmi_8996_vco_set_rate(struct clk *c, unsigned long rate, u32 ver) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + unsigned int set_power_dwn = 0; + bool atomic_update = false; + int rc, pll_lock_range; + + rc = mdss_pll_resource_enable(io, true); + if (rc) { + DEV_ERR("pll resource can't be enabled\n"); + return rc; + } + + DEV_DBG("%s: rate %ld\n", __func__, rate); + + if (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_C_READY_STATUS) & BIT(0) && + MDSS_PLL_REG_R(io->phy_base, HDMI_PHY_STATUS) & BIT(0)) { + pll_lock_range = hdmi_8996_vco_get_lock_range(c, vco->rate); + + if (pll_lock_range > 0 && vco->rate) { + u32 range_limit; + + range_limit = vco->rate * + (pll_lock_range / HDMI_KHZ_TO_HZ); + range_limit /= HDMI_KHZ_TO_HZ; + + DEV_DBG("%s: range limit %d\n", __func__, range_limit); + + if (abs(rate - vco->rate) < range_limit) + atomic_update = true; + } + } + + if (io->pll_on && !atomic_update) + set_power_dwn = 1; + + if (atomic_update) { + hdmi_8996_vco_rate_atomic_update(c, rate, ver); + } else { + rc = hdmi_8996_phy_pll_set_clk_rate(c, rate, ver); + if (rc) + DEV_ERR("%s: Failed to set clk rate\n", __func__); + } + + mdss_pll_resource_enable(io, false); + + if (set_power_dwn) + hdmi_8996_vco_enable(c, ver); + + vco->rate = rate; + vco->rate_set = true; + + return 0; +} + +static int hdmi_8996_v1_vco_set_rate(struct clk *c, unsigned long rate) +{ + return hdmi_8996_vco_set_rate(c, rate, HDMI_VERSION_8996_V1); +} + +static int hdmi_8996_v2_vco_set_rate(struct clk *c, unsigned long rate) +{ + return hdmi_8996_vco_set_rate(c, rate, HDMI_VERSION_8996_V2); +} + +static int hdmi_8996_v3_vco_set_rate(struct clk *c, unsigned long rate) +{ + return hdmi_8996_vco_set_rate(c, rate, HDMI_VERSION_8996_V3); +} + +static int hdmi_8996_v3_1p8_vco_set_rate(struct clk *c, unsigned long rate) +{ + return hdmi_8996_vco_set_rate(c, rate, HDMI_VERSION_8996_V3_1_8); +} + +static unsigned long hdmi_get_hsclk_sel_divisor(unsigned long hsclk_sel) +{ + unsigned long divisor; + + switch (hsclk_sel) { + case 0: + divisor = 2; + break; + case 1: + divisor = 6; + break; + case 2: + divisor = 10; + break; + case 3: + divisor = 14; + break; + case 4: + divisor = 3; + break; + case 5: + divisor = 9; + break; + case 6: + case 13: + divisor = 15; + break; + case 7: + divisor = 21; + break; + case 8: + divisor = 4; + break; + case 9: + divisor = 12; + break; + case 10: + divisor = 20; + break; + case 11: + divisor = 28; + break; + case 12: + divisor = 5; + break; + case 14: + divisor = 25; + break; + case 15: + divisor = 35; + break; + default: + divisor = 1; + DEV_ERR("%s: invalid hsclk_sel value = %lu", + __func__, hsclk_sel); + break; + } + + return divisor; +} + +static unsigned long hdmi_8996_vco_get_rate(struct clk *c) +{ + unsigned long freq = 0, hsclk_sel = 0, tx_band = 0, dec_start = 0, + div_frac_start = 0, vco_clock_freq = 0; + struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + if (mdss_pll_resource_enable(io, true)) { + DEV_ERR("%s: pll resource can't be enabled\n", __func__); + return freq; + } + + dec_start = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_DEC_START_MODE0); + + div_frac_start = + MDSS_PLL_REG_R(io->pll_base, + QSERDES_COM_DIV_FRAC_START1_MODE0) | + MDSS_PLL_REG_R(io->pll_base, + QSERDES_COM_DIV_FRAC_START2_MODE0) << 8 | + MDSS_PLL_REG_R(io->pll_base, + QSERDES_COM_DIV_FRAC_START3_MODE0) << 16; + + vco_clock_freq = (dec_start + (div_frac_start / (1 << 20))) + * 4 * (HDMI_REF_CLOCK); + + hsclk_sel = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_HSCLK_SEL) & 0x15; + hsclk_sel = hdmi_get_hsclk_sel_divisor(hsclk_sel); + tx_band = MDSS_PLL_REG_R(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_TX_BAND) & 0x3; + + freq = vco_clock_freq / (10 * hsclk_sel * (1 << tx_band)); + + mdss_pll_resource_enable(io, false); + + DEV_DBG("%s: freq = %lu\n", __func__, freq); + + return freq; +} + +static long hdmi_8996_vco_round_rate(struct clk *c, unsigned long rate) +{ + unsigned long rrate = rate; + + DEV_DBG("rrate=%ld\n", rrate); + + return rrate; +} + +static int hdmi_8996_vco_prepare(struct clk *c, u32 ver) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + int ret = 0; + + DEV_DBG("rate=%ld\n", vco->rate); + + if (!vco->rate_set && vco->rate) + ret = hdmi_8996_vco_set_rate(c, vco->rate, ver); + + if (!ret) { + ret = mdss_pll_resource_enable(io, true); + if (ret) + DEV_ERR("pll resource can't be enabled\n"); + } + + return ret; +} + +static int hdmi_8996_v1_vco_prepare(struct clk *c) +{ + return hdmi_8996_vco_prepare(c, HDMI_VERSION_8996_V1); +} + +static int hdmi_8996_v2_vco_prepare(struct clk *c) +{ + return hdmi_8996_vco_prepare(c, HDMI_VERSION_8996_V2); +} + +static int hdmi_8996_v3_vco_prepare(struct clk *c) +{ + return hdmi_8996_vco_prepare(c, HDMI_VERSION_8996_V3); +} + +static int hdmi_8996_v3_1p8_vco_prepare(struct clk *c) +{ + return hdmi_8996_vco_prepare(c, HDMI_VERSION_8996_V3_1_8); +} + +static void hdmi_8996_vco_unprepare(struct clk *c) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + vco->rate_set = false; + + if (!io) { + DEV_ERR("Invalid input parameter\n"); + return; + } + + if (!io->pll_on && + mdss_pll_resource_enable(io, true)) { + DEV_ERR("pll resource can't be enabled\n"); + return; + } + + io->handoff_resources = false; + mdss_pll_resource_enable(io, false); + io->pll_on = false; +} + +static enum handoff hdmi_8996_vco_handoff(struct clk *c) +{ + enum handoff ret = HANDOFF_DISABLED_CLK; + struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + if (is_gdsc_disabled(io)) + return HANDOFF_DISABLED_CLK; + + if (mdss_pll_resource_enable(io, true)) { + DEV_ERR("pll resource can't be enabled\n"); + return ret; + } + + io->handoff_resources = true; + + if (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_C_READY_STATUS) & BIT(0)) { + if (MDSS_PLL_REG_R(io->phy_base, HDMI_PHY_STATUS) & BIT(0)) { + io->pll_on = true; + c->rate = hdmi_8996_vco_get_rate(c); + vco->rate = c->rate; + ret = HANDOFF_ENABLED_CLK; + } else { + io->handoff_resources = false; + mdss_pll_resource_enable(io, false); + DEV_DBG("%s: PHY not ready\n", __func__); + } + } else { + io->handoff_resources = false; + mdss_pll_resource_enable(io, false); + DEV_DBG("%s: PLL not locked\n", __func__); + } + + DEV_DBG("done, ret=%d\n", ret); + return ret; +} + +static const struct clk_ops hdmi_8996_v1_vco_clk_ops = { + .enable = hdmi_8996_v1_vco_enable, + .set_rate = hdmi_8996_v1_vco_set_rate, + .get_rate = hdmi_8996_vco_get_rate, + .round_rate = hdmi_8996_vco_round_rate, + .prepare = hdmi_8996_v1_vco_prepare, + .unprepare = hdmi_8996_vco_unprepare, + .handoff = hdmi_8996_vco_handoff, +}; + +static const struct clk_ops hdmi_8996_v2_vco_clk_ops = { + .enable = hdmi_8996_v2_vco_enable, + .set_rate = hdmi_8996_v2_vco_set_rate, + .get_rate = hdmi_8996_vco_get_rate, + .round_rate = hdmi_8996_vco_round_rate, + .prepare = hdmi_8996_v2_vco_prepare, + .unprepare = hdmi_8996_vco_unprepare, + .handoff = hdmi_8996_vco_handoff, +}; + +static const struct clk_ops hdmi_8996_v3_vco_clk_ops = { + .enable = hdmi_8996_v3_vco_enable, + .set_rate = hdmi_8996_v3_vco_set_rate, + .get_rate = hdmi_8996_vco_get_rate, + .round_rate = hdmi_8996_vco_round_rate, + .prepare = hdmi_8996_v3_vco_prepare, + .unprepare = hdmi_8996_vco_unprepare, + .handoff = hdmi_8996_vco_handoff, +}; + +static const struct clk_ops hdmi_8996_v3_1p8_vco_clk_ops = { + .enable = hdmi_8996_v3_1p8_vco_enable, + .set_rate = hdmi_8996_v3_1p8_vco_set_rate, + .get_rate = hdmi_8996_vco_get_rate, + .round_rate = hdmi_8996_vco_round_rate, + .prepare = hdmi_8996_v3_1p8_vco_prepare, + .unprepare = hdmi_8996_vco_unprepare, + .handoff = hdmi_8996_vco_handoff, +}; + + +static struct hdmi_pll_vco_clk hdmi_vco_clk = { + .c = { + .dbg_name = "hdmi_8996_vco_clk", + .ops = &hdmi_8996_v1_vco_clk_ops, + CLK_INIT(hdmi_vco_clk.c), + }, +}; + +static struct clk_lookup hdmipllcc_8996[] = { + CLK_LIST(hdmi_vco_clk), +}; + +int hdmi_8996_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res, u32 ver) +{ + int rc = -ENOTSUPP; + + if (!pll_res || !pll_res->phy_base || !pll_res->pll_base) { + DEV_ERR("%s: Invalid input parameters\n", __func__); + return -EPROBE_DEFER; + } + + /* Set client data for vco, mux and div clocks */ + hdmi_vco_clk.priv = pll_res; + + switch (ver) { + case HDMI_VERSION_8996_V2: + hdmi_vco_clk.c.ops = &hdmi_8996_v2_vco_clk_ops; + break; + case HDMI_VERSION_8996_V3: + hdmi_vco_clk.c.ops = &hdmi_8996_v3_vco_clk_ops; + break; + case HDMI_VERSION_8996_V3_1_8: + hdmi_vco_clk.c.ops = &hdmi_8996_v3_1p8_vco_clk_ops; + break; + default: + hdmi_vco_clk.c.ops = &hdmi_8996_v1_vco_clk_ops; + break; + }; + + rc = of_msm_clock_register(pdev->dev.of_node, hdmipllcc_8996, + ARRAY_SIZE(hdmipllcc_8996)); + if (rc) { + DEV_ERR("%s: Clock register failed rc=%d\n", __func__, rc); + rc = -EPROBE_DEFER; + } else { + DEV_DBG("%s SUCCESS\n", __func__); + } + + return rc; +} + +int hdmi_8996_v1_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + return hdmi_8996_pll_clock_register(pdev, pll_res, + HDMI_VERSION_8996_V1); +} + +int hdmi_8996_v2_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + return hdmi_8996_pll_clock_register(pdev, pll_res, + HDMI_VERSION_8996_V2); +} + +int hdmi_8996_v3_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + return hdmi_8996_pll_clock_register(pdev, pll_res, + HDMI_VERSION_8996_V3); +} + +int hdmi_8996_v3_1p8_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + return hdmi_8996_pll_clock_register(pdev, pll_res, + HDMI_VERSION_8996_V3_1_8); +} diff --git a/drivers/clk/qcom/mdss/mdss-hdmi-pll-8998.c b/drivers/clk/qcom/mdss/mdss-hdmi-pll-8998.c new file mode 100644 index 000000000000..f27b816e4124 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-hdmi-pll-8998.c @@ -0,0 +1,841 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-hdmi-pll.h" + +#define _W(x, y, z) MDSS_PLL_REG_W(x, y, z) +#define _R(x, y) MDSS_PLL_REG_R(x, y) + +/* PLL REGISTERS */ +#define BIAS_EN_CLKBUFLR_EN (0x034) +#define CLK_ENABLE1 (0x038) +#define SYS_CLK_CTRL (0x03C) +#define SYSCLK_BUF_ENABLE (0x040) +#define PLL_IVCO (0x048) +#define CP_CTRL_MODE0 (0x060) +#define PLL_RCTRL_MODE0 (0x068) +#define PLL_CCTRL_MODE0 (0x070) +#define SYSCLK_EN_SEL (0x080) +#define RESETSM_CNTRL (0x088) +#define LOCK_CMP_EN (0x090) +#define LOCK_CMP1_MODE0 (0x098) +#define LOCK_CMP2_MODE0 (0x09C) +#define LOCK_CMP3_MODE0 (0x0A0) +#define DEC_START_MODE0 (0x0B0) +#define DIV_FRAC_START1_MODE0 (0x0B8) +#define DIV_FRAC_START2_MODE0 (0x0BC) +#define DIV_FRAC_START3_MODE0 (0x0C0) +#define INTEGLOOP_GAIN0_MODE0 (0x0D8) +#define INTEGLOOP_GAIN1_MODE0 (0x0DC) +#define VCO_TUNE_CTRL (0x0EC) +#define VCO_TUNE_MAP (0x0F0) +#define CLK_SELECT (0x138) +#define HSCLK_SEL (0x13C) +#define CORECLK_DIV_MODE0 (0x148) +#define CORE_CLK_EN (0x154) +#define C_READY_STATUS (0x158) +#define SVS_MODE_CLK_SEL (0x164) + +/* Tx Channel PHY registers */ +#define PHY_TX_EMP_POST1_LVL(n) ((((n) * 0x200) + 0x400) + 0x000) +#define PHY_TX_INTERFACE_SELECT_TX_BAND(n) ((((n) * 0x200) + 0x400) + 0x008) +#define PHY_TX_CLKBUF_TERM_ENABLE(n) ((((n) * 0x200) + 0x400) + 0x00C) +#define PHY_TX_DRV_LVL_RES_CODE_OFFSET(n) ((((n) * 0x200) + 0x400) + 0x014) +#define PHY_TX_DRV_LVL(n) ((((n) * 0x200) + 0x400) + 0x018) +#define PHY_TX_LANE_CONFIG(n) ((((n) * 0x200) + 0x400) + 0x01C) +#define PHY_TX_PRE_DRIVER_1(n) ((((n) * 0x200) + 0x400) + 0x024) +#define PHY_TX_PRE_DRIVER_2(n) ((((n) * 0x200) + 0x400) + 0x028) +#define PHY_TX_LANE_MODE(n) ((((n) * 0x200) + 0x400) + 0x02C) + +/* HDMI PHY registers */ +#define PHY_CFG (0x00) +#define PHY_PD_CTL (0x04) +#define PHY_MODE (0x10) +#define PHY_CLOCK (0x5C) +#define PHY_CMN_CTRL (0x68) +#define PHY_STATUS (0xB4) + +#define HDMI_BIT_CLK_TO_PIX_CLK_RATIO 10 +#define HDMI_MHZ_TO_HZ 1000000 +#define HDMI_HZ_TO_MHZ 1000000 +#define HDMI_REF_CLOCK_MHZ 19.2 +#define HDMI_REF_CLOCK_HZ (HDMI_REF_CLOCK_MHZ * 1000000) +#define HDMI_VCO_MIN_RATE_HZ 25000000 +#define HDMI_VCO_MAX_RATE_HZ 600000000 + +struct 8998_reg_cfg { + u32 tx_band; + u32 svs_mode_clk_sel; + u32 hsclk_sel; + u32 lock_cmp_en; + u32 cctrl_mode0; + u32 rctrl_mode0; + u32 cpctrl_mode0; + u32 dec_start_mode0; + u32 div_frac_start1_mode0; + u32 div_frac_start2_mode0; + u32 div_frac_start3_mode0; + u32 integloop_gain0_mode0; + u32 integloop_gain1_mode0; + u32 lock_cmp1_mode0; + u32 lock_cmp2_mode0; + u32 lock_cmp3_mode0; + u32 ssc_per1; + u32 ssc_per2; + u32 ssc_step_size1; + u32 ssc_step_size2; + u32 core_clk_en; + u32 coreclk_div_mode0; + u32 phy_mode; + u32 vco_freq; + u32 hsclk_divsel; + u32 vco_ratio; + u32 ssc_en_center; + + u32 l0_tx_drv_lvl; + u32 l0_tx_emp_post1_lvl; + u32 l1_tx_drv_lvl; + u32 l1_tx_emp_post1_lvl; + u32 l2_tx_drv_lvl; + u32 l2_tx_emp_post1_lvl; + u32 l3_tx_drv_lvl; + u32 l3_tx_emp_post1_lvl; + + u32 l0_pre_driver_1; + u32 l0_pre_driver_2; + u32 l1_pre_driver_1; + u32 l1_pre_driver_2; + u32 l2_pre_driver_1; + u32 l2_pre_driver_2; + u32 l3_pre_driver_1; + u32 l3_pre_driver_2; + + bool debug; +}; + +static void hdmi_8998_get_div(struct 8998_reg_cfg * cfg, unsigned long pclk) +{ + u32 const ratio_list[] = {1, 2, 3, 4, 5, 6, + 9, 10, 12, 15, 25}; + u32 const band_list[] = {0, 1, 2, 3}; + u32 const sz_ratio = ARRAY_SIZE(ratio_list); + u32 const sz_band = ARRAY_SIZE(band_list); + u32 const min_freq = 8000, max_freq = 12000; + u32 const cmp_cnt = 1024; + u32 const th_min = 500, th_max = 1000; + u64 bit_clk = pclk * HDMI_BIT_CLK_TO_PIX_CLK_RATIO; + u32 half_rate_mode = 0; + u32 freq_optimal, list_elements; + int optimal_index; + u32 i, j, k; + u32 freq_list[sz_ratio * sz_band]; + u32 found_hsclk_divsel = 0, found_vco_ratio; + u32 found_tx_band_sel, found_vco_freq; + +find_optimal_index: + freq_optimal = max_freq; + optimal_index = -1; + list_elements = 0; + + for (i = 0; i < sz_ratio; i++) { + for (j = 0; j < sz_band; j++) { + u64 freq = (bit_clk / (1 << half_rate_mode)); + + freq *= (ratio_list[i] * (1 << band_list[j])); + do_div(freq, (u64) HDMI_MHZ_TO_HZ); + freq_list[list_elements++] = freq; + } + } + + for (k = 0; k < ARRAY_SIZE(freq_list); k++) { + u32 const clks_pll_div = 2, core_clk_div = 5; + u32 const rng1 = 16, rng2 = 8; + u32 core_clk, rvar1; + u32 th1, th2; + + core_clk = (((freq_list[k] / + ratio_list[k / sz_band]) / + clks_pll_div) / core_clk_div); + + rvar1 = HDMI_REF_CLOCK_HZ / cmp_cnt; + rvar1 *= rng1; + rvar1 /= core_clk; + + th1 = rvar1; + + rvar1 = HDMI_REF_CLOCK_HZ / cmp_cnt; + rvar1 *= rng2; + rvar1 /= core_clk; + + th2 = rvar1; + + if (freq_list[k] >= min_freq && + freq_list[k] <= max_freq) { + if ((th1 >= th_min && th1 <= th_max) || + (th2 >= th_min && th2 <= th_max)) { + if (freq_list[k] <= freq_optimal) { + freq_optimal = freq_list[k]; + optimal_index = k; + } + } + } + } + + if (optimal_index == -1) { + if (!half_rate_mode) { + half_rate_mode = 1; + goto find_optimal_index; + } else { + /* set to default values */ + found_vco_freq = max_freq; + found_hsclk_divsel = 0; + found_vco_ratio = 2; + found_tx_band_sel = 0; + pr_err("Config error for pclk %ld\n", pclk); + } + } else { + found_vco_ratio = ratio_list[optimal_index / sz_band]; + found_tx_band_sel = band_list[optimal_index % sz_band]; + found_vco_freq = freq_optimal; + } + + switch (found_vco_ratio) { + case 1: + found_hsclk_divsel = 15; + break; + case 2: + found_hsclk_divsel = 0; + break; + case 3: + found_hsclk_divsel = 4; + break; + case 4: + found_hsclk_divsel = 8; + break; + case 5: + found_hsclk_divsel = 12; + break; + case 6: + found_hsclk_divsel = 1; + break; + case 9: + found_hsclk_divsel = 5; + break; + case 10: + found_hsclk_divsel = 2; + break; + case 12: + found_hsclk_divsel = 9; + break; + case 15: + found_hsclk_divsel = 13; + break; + case 25: + found_hsclk_divsel = 14; + break; + }; + + pr_debug("found_vco_freq=%d\n", found_vco_freq); + pr_debug("found_hsclk_divsel=%d\n", found_hsclk_divsel); + pr_debug("found_vco_ratio=%d\n", found_vco_ratio); + pr_debug("found_tx_band_sel=%d\n", found_tx_band_sel); + pr_debug("half_rate_mode=%d\n", half_rate_mode); + pr_debug("optimal_index=%d\n", optimal_index); + + cfg->vco_freq = found_vco_freq; + cfg->hsclk_divsel = found_hsclk_divsel; + cfg->vco_ratio = found_vco_ratio; + cfg->tx_band = found_tx_band_sel; +} + +static int hdmi_8998_config_phy(unsigned long rate, + struct 8998_reg_cfg * cfg) +{ + u64 const high_freq_bit_clk_threshold = 3400000000UL; + u64 const dig_freq_bit_clk_threshold = 1500000000UL; + u64 const mid_freq_bit_clk_threshold = 750000000; + int rc = 0; + u64 fdata, tmds_clk; + u64 pll_div = 4 * HDMI_REF_CLOCK_HZ; + u64 bclk; + u64 vco_freq_mhz; + u64 hsclk_sel, dec_start, div_frac_start; + u64 rem; + u64 cpctrl, rctrl, cctrl; + u64 integloop_gain; + u32 digclk_divsel; + u32 tmds_bclk_ratio; + u64 cmp_rng, cmp_cnt = 1024, pll_cmp; + bool gen_ssc = false; + + bclk = rate * HDMI_BIT_CLK_TO_PIX_CLK_RATIO; + + if (bclk > high_freq_bit_clk_threshold) { + tmds_clk = rate / 4; + tmds_bclk_ratio = 1; + } else { + tmds_clk = rate; + tmds_bclk_ratio = 0; + } + + hdmi_8998_get_div(cfg, rate); + + vco_freq_mhz = cfg->vco_freq * (u64) HDMI_HZ_TO_MHZ; + fdata = cfg->vco_freq; + do_div(fdata, cfg->vco_ratio); + + hsclk_sel = cfg->hsclk_divsel; + dec_start = vco_freq_mhz; + do_div(dec_start, pll_div); + + div_frac_start = vco_freq_mhz * (1 << 20); + rem = do_div(div_frac_start, pll_div); + div_frac_start -= (dec_start * (1 << 20)); + if (rem > (pll_div >> 1)) + div_frac_start++; + + if ((div_frac_start != 0) || (gen_ssc == true)) { + cpctrl = 0x8; + rctrl = 0x16; + cctrl = 0x34; + } else { + cpctrl = 0x30; + rctrl = 0x18; + cctrl = 0x2; + } + + digclk_divsel = (bclk > dig_freq_bit_clk_threshold) ? 0x1 : 0x2; + + integloop_gain = ((div_frac_start != 0) || + (gen_ssc == true)) ? 0x3F : 0xC4; + integloop_gain <<= digclk_divsel; + integloop_gain = (integloop_gain <= 2046 ? integloop_gain : 0x7FE); + + cmp_rng = gen_ssc ? 0x40 : 0x10; + + pll_cmp = cmp_cnt * fdata; + rem = do_div(pll_cmp, (u64)(HDMI_REF_CLOCK_MHZ * 10)); + if (rem > ((u64)(HDMI_REF_CLOCK_MHZ * 10) >> 1)) + pll_cmp++; + + pll_cmp = pll_cmp - 1; + + pr_debug("VCO_FREQ = %u\n", cfg->vco_freq); + pr_debug("FDATA = %llu\n", fdata); + pr_debug("DEC_START = %llu\n", dec_start); + pr_debug("DIV_FRAC_START = %llu\n", div_frac_start); + pr_debug("CPCTRL = %llu\n", cpctrl); + pr_debug("RCTRL = %llu\n", rctrl); + pr_debug("CCTRL = %llu\n", cctrl); + pr_debug("DIGCLK_DIVSEL = %u\n", digclk_divsel); + pr_debug("INTEGLOOP_GAIN = %llu\n", integloop_gain); + pr_debug("CMP_RNG = %llu\n", cmp_rng); + pr_debug("PLL_CMP = %llu\n", pll_cmp); + + cfg->svs_mode_clk_sel = (digclk_divsel & 0xFF); + cfg->hsclk_sel = (0x20 | hsclk_sel); + cfg->lock_cmp_en = (gen_ssc ? 0x4 : 0x0); + cfg->cctrl_mode0 = (cctrl & 0xFF); + cfg->rctrl_mode0 = (rctrl & 0xFF); + cfg->cpctrl_mode0 = (cpctrl & 0xFF); + cfg->dec_start_mode0 = (dec_start & 0xFF); + cfg->div_frac_start1_mode0 = (div_frac_start & 0xFF); + cfg->div_frac_start2_mode0 = ((div_frac_start & 0xFF00) >> 8); + cfg->div_frac_start3_mode0 = ((div_frac_start & 0xF0000) >> 16); + cfg->integloop_gain0_mode0 = (integloop_gain & 0xFF); + cfg->integloop_gain1_mode0 = (integloop_gain & 0xF00) >> 8; + cfg->lock_cmp1_mode0 = (pll_cmp & 0xFF); + cfg->lock_cmp2_mode0 = ((pll_cmp & 0xFF00) >> 8); + cfg->lock_cmp3_mode0 = ((pll_cmp & 0x30000) >> 16); + cfg->ssc_per1 = 0; + cfg->ssc_per2 = 0; + cfg->ssc_step_size1 = 0; + cfg->ssc_step_size2 = 0; + cfg->core_clk_en = 0x2C; + cfg->coreclk_div_mode0 = 0x5; + cfg->phy_mode = (tmds_bclk_ratio ? 0x5 : 0x4); + cfg->ssc_en_center = 0x0; + + if (bclk > high_freq_bit_clk_threshold) { + cfg->l0_tx_drv_lvl = 0xA; + cfg->l0_tx_emp_post1_lvl = 0x3; + cfg->l1_tx_drv_lvl = 0xA; + cfg->l1_tx_emp_post1_lvl = 0x3; + cfg->l2_tx_drv_lvl = 0xA; + cfg->l2_tx_emp_post1_lvl = 0x3; + cfg->l3_tx_drv_lvl = 0x8; + cfg->l3_tx_emp_post1_lvl = 0x3; + cfg->l0_pre_driver_1 = 0x0; + cfg->l0_pre_driver_2 = 0x1C; + cfg->l1_pre_driver_1 = 0x0; + cfg->l1_pre_driver_2 = 0x1C; + cfg->l2_pre_driver_1 = 0x0; + cfg->l2_pre_driver_2 = 0x1C; + cfg->l3_pre_driver_1 = 0x0; + cfg->l3_pre_driver_2 = 0x0; + } else if (bclk > dig_freq_bit_clk_threshold) { + cfg->l0_tx_drv_lvl = 0x9; + cfg->l0_tx_emp_post1_lvl = 0x3; + cfg->l1_tx_drv_lvl = 0x9; + cfg->l1_tx_emp_post1_lvl = 0x3; + cfg->l2_tx_drv_lvl = 0x9; + cfg->l2_tx_emp_post1_lvl = 0x3; + cfg->l3_tx_drv_lvl = 0x8; + cfg->l3_tx_emp_post1_lvl = 0x3; + cfg->l0_pre_driver_1 = 0x0; + cfg->l0_pre_driver_2 = 0x16; + cfg->l1_pre_driver_1 = 0x0; + cfg->l1_pre_driver_2 = 0x16; + cfg->l2_pre_driver_1 = 0x0; + cfg->l2_pre_driver_2 = 0x16; + cfg->l3_pre_driver_1 = 0x0; + cfg->l3_pre_driver_2 = 0x0; + } else if (bclk > mid_freq_bit_clk_threshold) { + cfg->l0_tx_drv_lvl = 0x9; + cfg->l0_tx_emp_post1_lvl = 0x3; + cfg->l1_tx_drv_lvl = 0x9; + cfg->l1_tx_emp_post1_lvl = 0x3; + cfg->l2_tx_drv_lvl = 0x9; + cfg->l2_tx_emp_post1_lvl = 0x3; + cfg->l3_tx_drv_lvl = 0x8; + cfg->l3_tx_emp_post1_lvl = 0x3; + cfg->l0_pre_driver_1 = 0x0; + cfg->l0_pre_driver_2 = 0x0E; + cfg->l1_pre_driver_1 = 0x0; + cfg->l1_pre_driver_2 = 0x0E; + cfg->l2_pre_driver_1 = 0x0; + cfg->l2_pre_driver_2 = 0x0E; + cfg->l3_pre_driver_1 = 0x0; + cfg->l3_pre_driver_2 = 0x0; + } else { + cfg->l0_tx_drv_lvl = 0x0; + cfg->l0_tx_emp_post1_lvl = 0x0; + cfg->l1_tx_drv_lvl = 0x0; + cfg->l1_tx_emp_post1_lvl = 0x0; + cfg->l2_tx_drv_lvl = 0x0; + cfg->l2_tx_emp_post1_lvl = 0x0; + cfg->l3_tx_drv_lvl = 0x0; + cfg->l3_tx_emp_post1_lvl = 0x0; + cfg->l0_pre_driver_1 = 0x0; + cfg->l0_pre_driver_2 = 0x01; + cfg->l1_pre_driver_1 = 0x0; + cfg->l1_pre_driver_2 = 0x01; + cfg->l2_pre_driver_1 = 0x0; + cfg->l2_pre_driver_2 = 0x01; + cfg->l3_pre_driver_1 = 0x0; + cfg->l3_pre_driver_2 = 0x0; + } + + return rc; +} + +static int hdmi_8998_pll_set_clk_rate(struct clk *c, unsigned long rate) +{ + int rc = 0; + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + struct 8998_reg_cfg cfg = {0}; + void __iomem *phy = io->phy_base, *pll = io->pll_base; + + rc = hdmi_8998_config_phy(rate, &cfg); + if (rc) { + pr_err("rate calculation failed\n, rc=%d", rc); + return rc; + } + + _W(phy, PHY_PD_CTL, 0x0); + udelay(500); + + _W(phy, PHY_PD_CTL, 0x1); + _W(pll, RESETSM_CNTRL, 0x20); + _W(phy, PHY_CMN_CTRL, 0x6); + _W(pll, PHY_TX_INTERFACE_SELECT_TX_BAND(0), cfg.tx_band); + _W(pll, PHY_TX_INTERFACE_SELECT_TX_BAND(1), cfg.tx_band); + _W(pll, PHY_TX_INTERFACE_SELECT_TX_BAND(2), cfg.tx_band); + _W(pll, PHY_TX_INTERFACE_SELECT_TX_BAND(3), cfg.tx_band); + _W(pll, PHY_TX_CLKBUF_TERM_ENABLE(0), 0x1); + _W(pll, PHY_TX_LANE_MODE(0), 0x20); + _W(pll, PHY_TX_LANE_MODE(1), 0x20); + _W(pll, PHY_TX_LANE_MODE(2), 0x20); + _W(pll, PHY_TX_LANE_MODE(3), 0x20); + _W(pll, PHY_TX_CLKBUF_TERM_ENABLE(1), 0x1); + _W(pll, PHY_TX_CLKBUF_TERM_ENABLE(2), 0x1); + _W(pll, PHY_TX_CLKBUF_TERM_ENABLE(3), 0x1); + _W(pll, SYSCLK_BUF_ENABLE, 0x2); + _W(pll, BIAS_EN_CLKBUFLR_EN, 0xB); + _W(pll, SYSCLK_EN_SEL, 0x37); + _W(pll, SYS_CLK_CTRL, 0x2); + _W(pll, CLK_ENABLE1, 0xE); + _W(pll, PLL_IVCO, 0xF); + _W(pll, VCO_TUNE_CTRL, 0x0); + _W(pll, SVS_MODE_CLK_SEL, cfg.svs_mode_clk_sel); + _W(pll, CLK_SELECT, 0x30); + _W(pll, HSCLK_SEL, cfg.hsclk_sel); + _W(pll, LOCK_CMP_EN, cfg.lock_cmp_en); + _W(pll, PLL_CCTRL_MODE0, cfg.cctrl_mode0); + _W(pll, PLL_RCTRL_MODE0, cfg.rctrl_mode0); + _W(pll, CP_CTRL_MODE0, cfg.cpctrl_mode0); + _W(pll, DEC_START_MODE0, cfg.dec_start_mode0); + _W(pll, DIV_FRAC_START1_MODE0, cfg.div_frac_start1_mode0); + _W(pll, DIV_FRAC_START2_MODE0, cfg.div_frac_start2_mode0); + _W(pll, DIV_FRAC_START3_MODE0, cfg.div_frac_start3_mode0); + _W(pll, INTEGLOOP_GAIN0_MODE0, cfg.integloop_gain0_mode0); + _W(pll, INTEGLOOP_GAIN1_MODE0, cfg.integloop_gain1_mode0); + _W(pll, LOCK_CMP1_MODE0, cfg.lock_cmp1_mode0); + _W(pll, LOCK_CMP2_MODE0, cfg.lock_cmp2_mode0); + _W(pll, LOCK_CMP3_MODE0, cfg.lock_cmp3_mode0); + _W(pll, VCO_TUNE_MAP, 0x0); + _W(pll, CORE_CLK_EN, cfg.core_clk_en); + _W(pll, CORECLK_DIV_MODE0, cfg.coreclk_div_mode0); + + _W(pll, PHY_TX_DRV_LVL(0), cfg.l0_tx_drv_lvl); + _W(pll, PHY_TX_DRV_LVL(1), cfg.l1_tx_drv_lvl); + _W(pll, PHY_TX_DRV_LVL(2), cfg.l2_tx_drv_lvl); + _W(pll, PHY_TX_DRV_LVL(3), cfg.l3_tx_drv_lvl); + + _W(pll, PHY_TX_EMP_POST1_LVL(0), cfg.l0_tx_emp_post1_lvl); + _W(pll, PHY_TX_EMP_POST1_LVL(1), cfg.l1_tx_emp_post1_lvl); + _W(pll, PHY_TX_EMP_POST1_LVL(2), cfg.l2_tx_emp_post1_lvl); + _W(pll, PHY_TX_EMP_POST1_LVL(3), cfg.l3_tx_emp_post1_lvl); + + _W(pll, PHY_TX_PRE_DRIVER_1(0), cfg.l0_pre_driver_1); + _W(pll, PHY_TX_PRE_DRIVER_1(1), cfg.l1_pre_driver_1); + _W(pll, PHY_TX_PRE_DRIVER_1(2), cfg.l2_pre_driver_1); + _W(pll, PHY_TX_PRE_DRIVER_1(3), cfg.l3_pre_driver_1); + + _W(pll, PHY_TX_PRE_DRIVER_2(0), cfg.l0_pre_driver_2); + _W(pll, PHY_TX_PRE_DRIVER_2(1), cfg.l1_pre_driver_2); + _W(pll, PHY_TX_PRE_DRIVER_2(2), cfg.l2_pre_driver_2); + _W(pll, PHY_TX_PRE_DRIVER_2(3), cfg.l3_pre_driver_2); + + _W(pll, PHY_TX_DRV_LVL_RES_CODE_OFFSET(0), 0x0); + _W(pll, PHY_TX_DRV_LVL_RES_CODE_OFFSET(1), 0x0); + _W(pll, PHY_TX_DRV_LVL_RES_CODE_OFFSET(2), 0x0); + _W(pll, PHY_TX_DRV_LVL_RES_CODE_OFFSET(3), 0x0); + + _W(phy, PHY_MODE, cfg.phy_mode); + + _W(pll, PHY_TX_LANE_CONFIG(0), 0x10); + _W(pll, PHY_TX_LANE_CONFIG(1), 0x10); + _W(pll, PHY_TX_LANE_CONFIG(2), 0x10); + _W(pll, PHY_TX_LANE_CONFIG(3), 0x10); + + /* Ensure all registers are flushed to hardware */ + wmb(); + + return 0; +} + +static int hdmi_8998_pll_lock_status(struct mdss_pll_resources *io) +{ + u32 const delay_us = 100; + u32 const timeout_us = 5000; + u32 status; + int rc = 0; + void __iomem *pll = io->pll_base; + + rc = mdss_pll_resource_enable(io, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + rc = readl_poll_timeout_atomic(pll + C_READY_STATUS, + status, + ((status & BIT(0)) > 0), + delay_us, + timeout_us); + if (rc) + pr_err("HDMI PLL(%d) lock failed, status=0x%08x\n", + io->index, status); + else + pr_debug("HDMI PLL(%d) lock passed, status=0x%08x\n", + io->index, status); + + mdss_pll_resource_enable(io, false); + + return rc; +} + +static int hdmi_8998_phy_ready_status(struct mdss_pll_resources *io) +{ + u32 const delay_us = 100; + u32 const timeout_us = 5000; + u32 status; + int rc = 0; + void __iomem *phy = io->phy_base; + + rc = mdss_pll_resource_enable(io, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + rc = readl_poll_timeout_atomic(phy + PHY_STATUS, + status, + ((status & BIT(0)) > 0), + delay_us, + timeout_us); + if (rc) + pr_err("HDMI PHY(%d) not ready, status=0x%08x\n", + io->index, status); + else + pr_debug("HDMI PHY(%d) ready, status=0x%08x\n", + io->index, status); + + mdss_pll_resource_enable(io, false); + + return rc; +} + +static int hdmi_8998_vco_set_rate(struct clk *c, unsigned long rate) +{ + int rc = 0; + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + rc = mdss_pll_resource_enable(io, true); + if (rc) { + pr_err("pll resource enable failed, rc=%d\n", rc); + return rc; + } + + if (io->pll_on) + goto error; + + rc = hdmi_8998_pll_set_clk_rate(c, rate); + if (rc) { + pr_err("failed to set clk rate, rc=%d\n", rc); + goto error; + } + + vco->rate = rate; + vco->rate_set = true; + +error: + (void)mdss_pll_resource_enable(io, false); + + return rc; +} + +static long hdmi_8998_vco_round_rate(struct clk *c, unsigned long rate) +{ + unsigned long rrate = rate; + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + + if (rate < vco->min_rate) + rrate = vco->min_rate; + if (rate > vco->max_rate) + rrate = vco->max_rate; + + return rrate; +} + +static int hdmi_8998_pll_enable(struct clk *c) +{ + int rc = 0; + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + void __iomem *phy = io->phy_base, *pll = io->pll_base; + + _W(phy, PHY_CFG, 0x1); + udelay(100); + _W(phy, PHY_CFG, 0x59); + udelay(100); + + _W(phy, PHY_CLOCK, 0x6); + + /* Ensure all registers are flushed to hardware */ + wmb(); + + rc = hdmi_8998_pll_lock_status(io); + if (rc) { + pr_err("PLL not locked, rc=%d\n", rc); + return rc; + } + + _W(pll, PHY_TX_LANE_CONFIG(0), 0x1F); + _W(pll, PHY_TX_LANE_CONFIG(1), 0x1F); + _W(pll, PHY_TX_LANE_CONFIG(2), 0x1F); + _W(pll, PHY_TX_LANE_CONFIG(3), 0x1F); + + /* Ensure all registers are flushed to hardware */ + wmb(); + + rc = hdmi_8998_phy_ready_status(io); + if (rc) { + pr_err("PHY NOT READY, rc=%d\n", rc); + return rc; + } + + _W(phy, PHY_CFG, 0x58); + udelay(1); + _W(phy, PHY_CFG, 0x59); + + /* Ensure all registers are flushed to hardware */ + wmb(); + + io->pll_on = true; + return rc; +} + +static int hdmi_8998_vco_prepare(struct clk *c) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + int rc = 0; + + if (!io) { + pr_err("hdmi pll resources are not available\n"); + return -EINVAL; + } + + rc = mdss_pll_resource_enable(io, true); + if (rc) { + pr_err("pll resource enable failed, rc=%d\n", rc); + return rc; + } + + if (!vco->rate_set && vco->rate) { + rc = hdmi_8998_pll_set_clk_rate(c, vco->rate); + if (rc) { + pr_err("set rate failed, rc=%d\n", rc); + goto error; + } + } + + rc = hdmi_8998_pll_enable(c); + if (rc) + pr_err("pll enabled failed, rc=%d\n", rc); + +error: + if (rc) + mdss_pll_resource_enable(io, false); + + return rc; +} + +static void hdmi_8998_pll_disable(struct hdmi_pll_vco_clk *vco) +{ + struct mdss_pll_resources *io = vco->priv; + void __iomem *phy = io->phy_base; + + if (!io->pll_on) + return; + + _W(phy, PHY_PD_CTL, 0x0); + + /* Ensure all registers are flushed to hardware */ + wmb(); + + vco->rate_set = false; + io->handoff_resources = false; + io->pll_on = false; +} + +static void hdmi_8998_vco_unprepare(struct clk *c) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + if (!io) { + pr_err("HDMI pll resources not available\n"); + return; + } + + hdmi_8998_pll_disable(vco); + mdss_pll_resource_enable(io, false); +} + +static enum handoff hdmi_8998_vco_handoff(struct clk *c) +{ + enum handoff ret = HANDOFF_DISABLED_CLK; + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + if (mdss_pll_resource_enable(io, true)) { + pr_err("pll resource can't be enabled\n"); + return ret; + } + + io->handoff_resources = true; + + if (_R(io->pll_base, C_READY_STATUS) & BIT(0) && + _R(io->phy_base, PHY_STATUS) & BIT(0)) { + io->pll_on = true; + /* TODO: calculate rate based on the phy/pll register values. */ + ret = HANDOFF_ENABLED_CLK; + } else { + io->handoff_resources = false; + mdss_pll_resource_enable(io, false); + pr_debug("%s: PHY/PLL not ready\n", __func__); + } + + pr_debug("done, ret=%d\n", ret); + return ret; +} + +static const struct clk_ops hdmi_8998_vco_clk_ops = { + .set_rate = hdmi_8998_vco_set_rate, + .round_rate = hdmi_8998_vco_round_rate, + .prepare = hdmi_8998_vco_prepare, + .unprepare = hdmi_8998_vco_unprepare, + .handoff = hdmi_8998_vco_handoff, +}; + +static struct hdmi_pll_vco_clk hdmi_vco_clk = { + .min_rate = HDMI_VCO_MIN_RATE_HZ, + .max_rate = HDMI_VCO_MAX_RATE_HZ, + .c = { + .dbg_name = "hdmi_8998_vco_clk", + .ops = &hdmi_8998_vco_clk_ops, + CLK_INIT(hdmi_vco_clk.c), + }, +}; + +static struct clk_lookup hdmipllcc_8998[] = { + CLK_LIST(hdmi_vco_clk), +}; + +int hdmi_8998_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = 0; + + if (!pdev || !pll_res) { + pr_err("invalid input parameters\n"); + return -EINVAL; + } + + hdmi_vco_clk.priv = pll_res; + + rc = of_msm_clock_register(pdev->dev.of_node, hdmipllcc_8998, + ARRAY_SIZE(hdmipllcc_8998)); + if (rc) { + pr_err("clock register failed, rc=%d\n", rc); + return rc; + } + + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-hdmi-pll.h b/drivers/clk/qcom/mdss/mdss-hdmi-pll.h new file mode 100644 index 000000000000..5c73ed4714c3 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-hdmi-pll.h @@ -0,0 +1,61 @@ +/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __MDSS_HDMI_PLL_H +#define __MDSS_HDMI_PLL_H + +struct hdmi_pll_cfg { + unsigned long vco_rate; + u32 reg; +}; + +struct hdmi_pll_vco_clk { + unsigned long rate; /* current vco rate */ + unsigned long min_rate; /* min vco rate */ + unsigned long max_rate; /* max vco rate */ + bool rate_set; + struct hdmi_pll_cfg *ip_seti; + struct hdmi_pll_cfg *cp_seti; + struct hdmi_pll_cfg *ip_setp; + struct hdmi_pll_cfg *cp_setp; + struct hdmi_pll_cfg *crctrl; + void *priv; + + struct clk c; +}; + +static inline struct hdmi_pll_vco_clk *to_hdmi_vco_clk(struct clk *clk) +{ + return container_of(clk, struct hdmi_pll_vco_clk, c); +} + +int hdmi_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); + +int hdmi_20nm_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); + +int hdmi_8996_v1_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); + +int hdmi_8996_v2_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); + +int hdmi_8996_v3_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); + +int hdmi_8996_v3_1p8_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); + +int hdmi_8998_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); +#endif diff --git a/drivers/clk/qcom/mdss/mdss-pll-util.c b/drivers/clk/qcom/mdss/mdss-pll-util.c new file mode 100644 index 000000000000..4d797729bb75 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-pll-util.c @@ -0,0 +1,437 @@ +/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +#include "mdss-pll.h" + +int mdss_pll_util_resource_init(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = 0; + struct dss_module_power *mp = &pll_res->mp; + + rc = msm_dss_config_vreg(&pdev->dev, + mp->vreg_config, mp->num_vreg, 1); + if (rc) { + pr_err("Vreg config failed rc=%d\n", rc); + goto vreg_err; + } + + rc = msm_dss_get_clk(&pdev->dev, mp->clk_config, mp->num_clk); + if (rc) { + pr_err("Clock get failed rc=%d\n", rc); + goto clk_err; + } + + return rc; + +clk_err: + msm_dss_config_vreg(&pdev->dev, mp->vreg_config, mp->num_vreg, 0); +vreg_err: + return rc; +} + +/** + * mdss_pll_get_mp_by_reg_name() -- Find power module by regulator name + *@pll_res: Pointer to the PLL resource + *@name: Regulator name as specified in the pll dtsi + * + * This is a helper function to retrieve the regulator information + * for each pll resource. + */ +struct dss_vreg *mdss_pll_get_mp_by_reg_name(struct mdss_pll_resources *pll_res + , char *name) +{ + + struct dss_vreg *regulator = NULL; + int i; + + if ((pll_res == NULL) || (pll_res->mp.vreg_config == NULL)) { + pr_err("%s Invalid PLL resource\n", __func__); + goto error; + } + + regulator = pll_res->mp.vreg_config; + + for (i = 0; i < pll_res->mp.num_vreg; i++) { + if (!strcmp(name, regulator->vreg_name)) { + pr_debug("Found regulator match for %s\n", name); + break; + } + regulator++; + } + +error: + return regulator; +} + +void mdss_pll_util_resource_deinit(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + struct dss_module_power *mp = &pll_res->mp; + + msm_dss_put_clk(mp->clk_config, mp->num_clk); + + msm_dss_config_vreg(&pdev->dev, mp->vreg_config, mp->num_vreg, 0); +} + +void mdss_pll_util_resource_release(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + struct dss_module_power *mp = &pll_res->mp; + + devm_kfree(&pdev->dev, mp->clk_config); + devm_kfree(&pdev->dev, mp->vreg_config); + mp->num_vreg = 0; + mp->num_clk = 0; +} + +int mdss_pll_util_resource_enable(struct mdss_pll_resources *pll_res, + bool enable) +{ + int rc = 0; + struct dss_module_power *mp = &pll_res->mp; + + if (enable) { + rc = msm_dss_enable_vreg(mp->vreg_config, mp->num_vreg, enable); + if (rc) { + pr_err("Failed to enable vregs rc=%d\n", rc); + goto vreg_err; + } + + rc = msm_dss_clk_set_rate(mp->clk_config, mp->num_clk); + if (rc) { + pr_err("Failed to set clock rate rc=%d\n", rc); + goto clk_err; + } + + rc = msm_dss_enable_clk(mp->clk_config, mp->num_clk, enable); + if (rc) { + pr_err("clock enable failed rc:%d\n", rc); + goto clk_err; + } + } else { + msm_dss_enable_clk(mp->clk_config, mp->num_clk, enable); + + msm_dss_enable_vreg(mp->vreg_config, mp->num_vreg, enable); + } + + return rc; + +clk_err: + msm_dss_enable_vreg(mp->vreg_config, mp->num_vreg, 0); +vreg_err: + return rc; +} + +static int mdss_pll_util_parse_dt_supply(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int i = 0, rc = 0; + u32 tmp = 0; + struct device_node *of_node = NULL, *supply_root_node = NULL; + struct device_node *supply_node = NULL; + struct dss_module_power *mp = &pll_res->mp; + + of_node = pdev->dev.of_node; + + mp->num_vreg = 0; + supply_root_node = of_get_child_by_name(of_node, + "qcom,platform-supply-entries"); + if (!supply_root_node) { + pr_err("no supply entry present\n"); + return rc; + } + + for_each_child_of_node(supply_root_node, supply_node) { + mp->num_vreg++; + } + + if (mp->num_vreg == 0) { + pr_debug("no vreg\n"); + return rc; + } + pr_debug("vreg found. count=%d\n", mp->num_vreg); + + mp->vreg_config = devm_kzalloc(&pdev->dev, sizeof(struct dss_vreg) * + mp->num_vreg, GFP_KERNEL); + if (!mp->vreg_config) { + rc = -ENOMEM; + return rc; + } + + for_each_child_of_node(supply_root_node, supply_node) { + + const char *st = NULL; + + rc = of_property_read_string(supply_node, + "qcom,supply-name", &st); + if (rc) { + pr_err(":error reading name. rc=%d\n", rc); + goto error; + } + + strlcpy(mp->vreg_config[i].vreg_name, st, + sizeof(mp->vreg_config[i].vreg_name)); + + rc = of_property_read_u32(supply_node, + "qcom,supply-min-voltage", &tmp); + if (rc) { + pr_err(": error reading min volt. rc=%d\n", rc); + goto error; + } + mp->vreg_config[i].min_voltage = tmp; + + rc = of_property_read_u32(supply_node, + "qcom,supply-max-voltage", &tmp); + if (rc) { + pr_err(": error reading max volt. rc=%d\n", rc); + goto error; + } + mp->vreg_config[i].max_voltage = tmp; + + rc = of_property_read_u32(supply_node, + "qcom,supply-enable-load", &tmp); + if (rc) { + pr_err(": error reading enable load. rc=%d\n", rc); + goto error; + } + mp->vreg_config[i].enable_load = tmp; + + rc = of_property_read_u32(supply_node, + "qcom,supply-disable-load", &tmp); + if (rc) { + pr_err(": error reading disable load. rc=%d\n", rc); + goto error; + } + mp->vreg_config[i].disable_load = tmp; + + rc = of_property_read_u32(supply_node, + "qcom,supply-pre-on-sleep", &tmp); + if (rc) + pr_debug("error reading supply pre sleep value. rc=%d\n", + rc); + + mp->vreg_config[i].pre_on_sleep = (!rc ? tmp : 0); + + rc = of_property_read_u32(supply_node, + "qcom,supply-pre-off-sleep", &tmp); + if (rc) + pr_debug("error reading supply pre sleep value. rc=%d\n", + rc); + + mp->vreg_config[i].pre_off_sleep = (!rc ? tmp : 0); + + rc = of_property_read_u32(supply_node, + "qcom,supply-post-on-sleep", &tmp); + if (rc) + pr_debug("error reading supply post sleep value. rc=%d\n", + rc); + + mp->vreg_config[i].post_on_sleep = (!rc ? tmp : 0); + + rc = of_property_read_u32(supply_node, + "qcom,supply-post-off-sleep", &tmp); + if (rc) + pr_debug("error reading supply post sleep value. rc=%d\n", + rc); + + mp->vreg_config[i].post_off_sleep = (!rc ? tmp : 0); + + pr_debug("%s min=%d, max=%d, enable=%d, disable=%d, preonsleep=%d, postonsleep=%d, preoffsleep=%d, postoffsleep=%d\n", + mp->vreg_config[i].vreg_name, + mp->vreg_config[i].min_voltage, + mp->vreg_config[i].max_voltage, + mp->vreg_config[i].enable_load, + mp->vreg_config[i].disable_load, + mp->vreg_config[i].pre_on_sleep, + mp->vreg_config[i].post_on_sleep, + mp->vreg_config[i].pre_off_sleep, + mp->vreg_config[i].post_off_sleep); + ++i; + + rc = 0; + } + + return rc; + +error: + if (mp->vreg_config) { + devm_kfree(&pdev->dev, mp->vreg_config); + mp->vreg_config = NULL; + mp->num_vreg = 0; + } + + return rc; +} + +static int mdss_pll_util_parse_dt_clock(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + u32 i = 0, rc = 0; + struct dss_module_power *mp = &pll_res->mp; + const char *clock_name; + u32 clock_rate; + + mp->num_clk = of_property_count_strings(pdev->dev.of_node, + "clock-names"); + if (mp->num_clk <= 0) { + pr_err("clocks are not defined\n"); + goto clk_err; + } + + mp->clk_config = devm_kzalloc(&pdev->dev, + sizeof(struct dss_clk) * mp->num_clk, GFP_KERNEL); + if (!mp->clk_config) { + rc = -ENOMEM; + mp->num_clk = 0; + goto clk_err; + } + + for (i = 0; i < mp->num_clk; i++) { + of_property_read_string_index(pdev->dev.of_node, "clock-names", + i, &clock_name); + strlcpy(mp->clk_config[i].clk_name, clock_name, + sizeof(mp->clk_config[i].clk_name)); + + of_property_read_u32_index(pdev->dev.of_node, "clock-rate", + i, &clock_rate); + mp->clk_config[i].rate = clock_rate; + + if (!clock_rate) + mp->clk_config[i].type = DSS_CLK_AHB; + else + mp->clk_config[i].type = DSS_CLK_PCLK; + } + +clk_err: + return rc; +} + +static void mdss_pll_free_bootmem(u32 mem_addr, u32 size) +{ + unsigned long pfn_start, pfn_end, pfn_idx; + + pfn_start = mem_addr >> PAGE_SHIFT; + pfn_end = (mem_addr + size) >> PAGE_SHIFT; + for (pfn_idx = pfn_start; pfn_idx < pfn_end; pfn_idx++) + free_reserved_page(pfn_to_page(pfn_idx)); +} + +static int mdss_pll_util_parse_dt_dfps(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = 0; + struct device_node *pnode; + const u32 *addr; + struct vm_struct *area; + u64 size; + u32 offsets[2]; + unsigned long virt_add; + + pnode = of_parse_phandle(pdev->dev.of_node, "memory-region", 0); + if (IS_ERR_OR_NULL(pnode)) { + rc = PTR_ERR(pnode); + goto pnode_err; + } + + addr = of_get_address(pnode, 0, &size, NULL); + if (!addr) { + pr_err("failed to parse the dfps memory address\n"); + rc = -EINVAL; + goto pnode_err; + } + /* maintain compatibility for 32/64 bit */ + offsets[0] = (u32) of_read_ulong(addr, 2); + offsets[1] = (u32) size; + + area = get_vm_area(offsets[1], VM_IOREMAP); + if (!area) { + rc = -ENOMEM; + goto dfps_mem_err; + } + + virt_add = (unsigned long)area->addr; + rc = ioremap_page_range(virt_add, (virt_add + offsets[1]), + offsets[0], PAGE_KERNEL); + if (rc) { + rc = -ENOMEM; + goto ioremap_err; + } + + pll_res->dfps = kzalloc(sizeof(struct dfps_info), GFP_KERNEL); + if (IS_ERR_OR_NULL(pll_res->dfps)) { + rc = PTR_ERR(pll_res->dfps); + pr_err("couldn't allocate dfps kernel memory\n"); + goto addr_err; + } + + /* memcopy complete dfps structure from kernel virtual memory */ + memcpy_fromio(pll_res->dfps, area->addr, sizeof(struct dfps_info)); + +addr_err: + if (virt_add) + unmap_kernel_range(virt_add, (unsigned long) size); +ioremap_err: + if (area) + vfree(area->addr); +dfps_mem_err: + /* free the dfps memory here */ + memblock_free(offsets[0], offsets[1]); + mdss_pll_free_bootmem(offsets[0], offsets[1]); +pnode_err: + if (pnode) + of_node_put(pnode); + + dma_release_declared_memory(&pdev->dev); + return rc; +} + +int mdss_pll_util_resource_parse(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = 0; + struct dss_module_power *mp = &pll_res->mp; + + rc = mdss_pll_util_parse_dt_supply(pdev, pll_res); + if (rc) { + pr_err("vreg parsing failed rc=%d\n", rc); + goto end; + } + + rc = mdss_pll_util_parse_dt_clock(pdev, pll_res); + if (rc) { + pr_err("clock name parsing failed rc=%d", rc); + goto clk_err; + } + + if (mdss_pll_util_parse_dt_dfps(pdev, pll_res)) + pr_err("dfps not enabled!\n"); + + return rc; + +clk_err: + devm_kfree(&pdev->dev, mp->vreg_config); + mp->num_vreg = 0; +end: + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-pll.c b/drivers/clk/qcom/mdss/mdss-pll.c new file mode 100644 index 000000000000..e292ef863ff7 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-pll.c @@ -0,0 +1,421 @@ +/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include "mdss-pll.h" +#include "mdss-dsi-pll.h" +#include "mdss-dp-pll.h" + +int mdss_pll_resource_enable(struct mdss_pll_resources *pll_res, bool enable) +{ + int rc = 0; + int changed = 0; + + if (!pll_res) { + pr_err("Invalid input parameters\n"); + return -EINVAL; + } + + /* + * Don't turn off resources during handoff or add more than + * 1 refcount. + */ + if (pll_res->handoff_resources && + (!enable || (enable & pll_res->resource_enable))) { + pr_debug("Do not turn on/off pll resources during handoff case\n"); + return rc; + } + + if (enable) { + if (pll_res->resource_ref_cnt == 0) + changed++; + pll_res->resource_ref_cnt++; + } else { + if (pll_res->resource_ref_cnt) { + pll_res->resource_ref_cnt--; + if (pll_res->resource_ref_cnt == 0) + changed++; + } else { + pr_err("PLL Resources already OFF\n"); + } + } + + if (changed) { + rc = mdss_pll_util_resource_enable(pll_res, enable); + if (rc) + pr_err("Resource update failed rc=%d\n", rc); + else + pll_res->resource_enable = enable; + } + + return rc; +} + +static int mdss_pll_resource_init(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + if (!pdev || !pll_res) { + pr_err("Invalid input parameters\n"); + return -EINVAL; + } + + return mdss_pll_util_resource_init(pdev, pll_res); +} + +static void mdss_pll_resource_deinit(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + if (!pdev || !pll_res) { + pr_err("Invalid input parameters\n"); + return; + } + + mdss_pll_util_resource_deinit(pdev, pll_res); +} + +static void mdss_pll_resource_release(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + if (!pdev || !pll_res) { + pr_err("Invalid input parameters\n"); + return; + } + + mdss_pll_util_resource_release(pdev, pll_res); +} + +static int mdss_pll_resource_parse(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = 0; + const char *compatible_stream; + + if (!pdev || !pll_res) { + pr_err("Invalid input parameters\n"); + return -EINVAL; + } + + rc = mdss_pll_util_resource_parse(pdev, pll_res); + if (rc) { + pr_err("Failed to parse the resources rc=%d\n", rc); + goto end; + } + + compatible_stream = of_get_property(pdev->dev.of_node, + "compatible", NULL); + if (!compatible_stream) { + pr_err("Failed to parse the compatible stream\n"); + goto err; + } + + if (!strcmp(compatible_stream, "qcom,mdss_dsi_pll_10nm")) + pll_res->pll_interface_type = MDSS_DSI_PLL_10NM; + if (!strcmp(compatible_stream, "qcom,mdss_dp_pll_10nm")) + pll_res->pll_interface_type = MDSS_DP_PLL_10NM; + else + goto err; + + return rc; + +err: + mdss_pll_resource_release(pdev, pll_res); +end: + return rc; +} + +static int mdss_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc; + + if (!pdev || !pll_res) { + pr_err("Invalid input parameters\n"); + return -EINVAL; + } + + switch (pll_res->pll_interface_type) { + case MDSS_DSI_PLL_10NM: + rc = dsi_pll_clock_register_10nm(pdev, pll_res); + break; + case MDSS_DP_PLL_10NM: + rc = dp_pll_clock_register_10nm(pdev, pll_res); + break; + case MDSS_UNKNOWN_PLL: + default: + rc = -EINVAL; + break; + } + + if (rc) { + pr_err("Pll ndx=%d clock register failed rc=%d\n", + pll_res->index, rc); + } + + return rc; +} + +static int mdss_pll_probe(struct platform_device *pdev) +{ + int rc = 0; + const char *label; + struct resource *pll_base_reg; + struct resource *phy_base_reg; + struct resource *tx0_base_reg, *tx1_base_reg; + struct resource *dynamic_pll_base_reg; + struct resource *gdsc_base_reg; + struct mdss_pll_resources *pll_res; + + if (!pdev->dev.of_node) { + pr_err("MDSS pll driver only supports device tree probe\n"); + rc = -ENOTSUPP; + goto error; + } + + label = of_get_property(pdev->dev.of_node, "label", NULL); + if (!label) + pr_info("%d: MDSS pll label not specified\n", __LINE__); + else + pr_info("MDSS pll label = %s\n", label); + + pll_res = devm_kzalloc(&pdev->dev, sizeof(struct mdss_pll_resources), + GFP_KERNEL); + if (!pll_res) { + rc = -ENOMEM; + goto error; + } + platform_set_drvdata(pdev, pll_res); + + rc = of_property_read_u32(pdev->dev.of_node, "cell-index", + &pll_res->index); + if (rc) { + pr_err("Unable to get the cell-index rc=%d\n", rc); + pll_res->index = 0; + } + + pll_res->ssc_en = of_property_read_bool(pdev->dev.of_node, + "qcom,dsi-pll-ssc-en"); + + if (pll_res->ssc_en) { + pr_info("%s: label=%s PLL SSC enabled\n", __func__, label); + + rc = of_property_read_u32(pdev->dev.of_node, + "qcom,ssc-frequency-hz", &pll_res->ssc_freq); + + rc = of_property_read_u32(pdev->dev.of_node, + "qcom,ssc-ppm", &pll_res->ssc_ppm); + + pll_res->ssc_center = false; + + label = of_get_property(pdev->dev.of_node, + "qcom,dsi-pll-ssc-mode", NULL); + + if (label && !strcmp(label, "center-spread")) + pll_res->ssc_center = true; + } + + pll_base_reg = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "pll_base"); + if (!pll_base_reg) { + pr_err("Unable to get the pll base resources\n"); + rc = -ENOMEM; + goto io_error; + } + + pll_res->pll_base = ioremap(pll_base_reg->start, + resource_size(pll_base_reg)); + if (!pll_res->pll_base) { + pr_err("Unable to remap pll base resources\n"); + rc = -ENOMEM; + goto io_error; + } + + pr_debug("%s: ndx=%d base=%p\n", __func__, + pll_res->index, pll_res->pll_base); + + rc = mdss_pll_resource_parse(pdev, pll_res); + if (rc) { + pr_err("Pll resource parsing from dt failed rc=%d\n", rc); + goto res_parse_error; + } + + phy_base_reg = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "phy_base"); + if (phy_base_reg) { + pll_res->phy_base = ioremap(phy_base_reg->start, + resource_size(phy_base_reg)); + if (!pll_res->phy_base) { + pr_err("Unable to remap pll phy base resources\n"); + rc = -ENOMEM; + goto phy_io_error; + } + } + + dynamic_pll_base_reg = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "dynamic_pll_base"); + if (dynamic_pll_base_reg) { + pll_res->dyn_pll_base = ioremap(dynamic_pll_base_reg->start, + resource_size(dynamic_pll_base_reg)); + if (!pll_res->dyn_pll_base) { + pr_err("Unable to remap dynamic pll base resources\n"); + rc = -ENOMEM; + goto dyn_pll_io_error; + } + } + + tx0_base_reg = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "ln_tx0_base"); + if (tx0_base_reg) { + pll_res->ln_tx0_base = ioremap(tx0_base_reg->start, + resource_size(tx0_base_reg)); + if (!pll_res->ln_tx0_base) { + pr_err("Unable to remap Lane TX0 base resources\n"); + rc = -ENOMEM; + goto tx0_io_error; + } + } + + tx1_base_reg = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "ln_tx1_base"); + if (tx1_base_reg) { + pll_res->ln_tx1_base = ioremap(tx1_base_reg->start, + resource_size(tx1_base_reg)); + if (!pll_res->ln_tx1_base) { + pr_err("Unable to remap Lane TX1 base resources\n"); + rc = -ENOMEM; + goto tx1_io_error; + } + } + + gdsc_base_reg = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "gdsc_base"); + if (!gdsc_base_reg) { + pr_err("Unable to get the gdsc base resource\n"); + rc = -ENOMEM; + goto gdsc_io_error; + } + pll_res->gdsc_base = ioremap(gdsc_base_reg->start, + resource_size(gdsc_base_reg)); + if (!pll_res->gdsc_base) { + pr_err("Unable to remap gdsc base resources\n"); + rc = -ENOMEM; + goto gdsc_io_error; + } + + rc = mdss_pll_resource_init(pdev, pll_res); + if (rc) { + pr_err("Pll ndx=%d resource init failed rc=%d\n", + pll_res->index, rc); + goto res_init_error; + } + + rc = mdss_pll_clock_register(pdev, pll_res); + if (rc) { + pr_err("Pll ndx=%d clock register failed rc=%d\n", + pll_res->index, rc); + goto clock_register_error; + } + + return rc; + +clock_register_error: + mdss_pll_resource_deinit(pdev, pll_res); +res_init_error: + if (pll_res->gdsc_base) + iounmap(pll_res->gdsc_base); +gdsc_io_error: + if (pll_res->ln_tx1_base) + iounmap(pll_res->ln_tx1_base); +tx1_io_error: + if (pll_res->ln_tx0_base) + iounmap(pll_res->ln_tx0_base); +tx0_io_error: + if (pll_res->dyn_pll_base) + iounmap(pll_res->dyn_pll_base); +dyn_pll_io_error: + if (pll_res->phy_base) + iounmap(pll_res->phy_base); +phy_io_error: + mdss_pll_resource_release(pdev, pll_res); +res_parse_error: + iounmap(pll_res->pll_base); +io_error: + devm_kfree(&pdev->dev, pll_res); +error: + return rc; +} + +static int mdss_pll_remove(struct platform_device *pdev) +{ + struct mdss_pll_resources *pll_res; + + pll_res = platform_get_drvdata(pdev); + if (!pll_res) { + pr_err("Invalid PLL resource data"); + return 0; + } + + mdss_pll_resource_deinit(pdev, pll_res); + if (pll_res->phy_base) + iounmap(pll_res->phy_base); + if (pll_res->gdsc_base) + iounmap(pll_res->gdsc_base); + mdss_pll_resource_release(pdev, pll_res); + iounmap(pll_res->pll_base); + devm_kfree(&pdev->dev, pll_res); + return 0; +} + +static const struct of_device_id mdss_pll_dt_match[] = { + {.compatible = "qcom,mdss_dsi_pll_10nm"}, + {.compatible = "qcom,mdss_dp_pll_10nm"}, + {} +}; + +MODULE_DEVICE_TABLE(of, mdss_clock_dt_match); + +static struct platform_driver mdss_pll_driver = { + .probe = mdss_pll_probe, + .remove = mdss_pll_remove, + .driver = { + .name = "mdss_pll", + .of_match_table = mdss_pll_dt_match, + }, +}; + +static int __init mdss_pll_driver_init(void) +{ + int rc; + + rc = platform_driver_register(&mdss_pll_driver); + if (rc) + pr_err("mdss_register_pll_driver() failed!\n"); + + return rc; +} +fs_initcall(mdss_pll_driver_init); + +static void __exit mdss_pll_driver_deinit(void) +{ + platform_driver_unregister(&mdss_pll_driver); +} +module_exit(mdss_pll_driver_deinit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("mdss pll driver"); diff --git a/drivers/clk/qcom/mdss/mdss-pll.h b/drivers/clk/qcom/mdss/mdss-pll.h new file mode 100644 index 000000000000..2f92270841ac --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-pll.h @@ -0,0 +1,240 @@ +/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __MDSS_PLL_H +#define __MDSS_PLL_H +#include +#include +#include +#include +#include +#include +#include "../clk-regmap.h" +#include "../clk-regmap-divider.h" +#include "../clk-regmap-mux.h" + + +#define MDSS_PLL_REG_W(base, offset, data) \ + writel_relaxed((data), (base) + (offset)) +#define MDSS_PLL_REG_R(base, offset) readl_relaxed((base) + (offset)) + +#define PLL_CALC_DATA(addr0, addr1, data0, data1) \ + (((data1) << 24) | ((((addr1) / 4) & 0xFF) << 16) | \ + ((data0) << 8) | (((addr0) / 4) & 0xFF)) + +#define MDSS_DYN_PLL_REG_W(base, offset, addr0, addr1, data0, data1) \ + writel_relaxed(PLL_CALC_DATA(addr0, addr1, data0, data1), \ + (base) + (offset)) + +enum { + MDSS_DSI_PLL_10NM, + MDSS_DP_PLL_10NM, + MDSS_UNKNOWN_PLL, +}; + +enum { + MDSS_PLL_TARGET_8996, +}; + +#define DFPS_MAX_NUM_OF_FRAME_RATES 20 + +struct dfps_panel_info { + uint32_t enabled; + uint32_t frame_rate_cnt; + uint32_t frame_rate[DFPS_MAX_NUM_OF_FRAME_RATES]; /* hz */ +}; + +struct dfps_pll_codes { + uint32_t pll_codes_1; + uint32_t pll_codes_2; +}; + +struct dfps_codes_info { + uint32_t is_valid; + uint32_t frame_rate; /* hz */ + uint32_t clk_rate; /* hz */ + struct dfps_pll_codes pll_codes; +}; + +struct dfps_info { + struct dfps_panel_info panel_dfps; + struct dfps_codes_info codes_dfps[DFPS_MAX_NUM_OF_FRAME_RATES]; + void *dfps_fb_base; +}; + +struct mdss_pll_resources { + + /* Pll specific resources like GPIO, power supply, clocks, etc*/ + struct dss_module_power mp; + + /* + * dsi/edp/hmdi plls' base register, phy, gdsc and dynamic refresh + * register mapping + */ + void __iomem *pll_base; + void __iomem *phy_base; + void __iomem *ln_tx0_base; + void __iomem *ln_tx1_base; + void __iomem *gdsc_base; + void __iomem *dyn_pll_base; + + bool is_init_locked; + s64 vco_current_rate; + s64 vco_locking_rate; + s64 vco_ref_clk_rate; + + /* + * Certain pll's needs to update the same vco rate after resume in + * suspend/resume scenario. Cached the vco rate for such plls. + */ + unsigned long vco_cached_rate; + u32 cached_cfg0; + u32 cached_cfg1; + u32 cached_outdiv; + + /* dsi/edp/hmdi pll interface type */ + u32 pll_interface_type; + + /* + * Target ID. Used in pll_register API for valid target check before + * registering the PLL clocks. + */ + u32 target_id; + + /* HW recommended delay during configuration of vco clock rate */ + u32 vco_delay; + + /* Ref-count of the PLL resources */ + u32 resource_ref_cnt; + + /* + * Keep track to resource status to avoid updating same status for the + * pll from different paths + */ + bool resource_enable; + + /* + * Certain plls' do not allow vco rate update if it is on. Keep track of + * status for them to turn on/off after set rate success. + */ + bool pll_on; + + /* + * handoff_status is true of pll is already enabled by bootloader with + * continuous splash enable case. Clock API will call the handoff API + * to enable the status. It is disabled if continuous splash + * feature is disabled. + */ + bool handoff_resources; + + /* + * caching the pll trim codes in the case of dynamic refresh + */ + int cache_pll_trim_codes[2]; + + /* + * for maintaining the status of saving trim codes + */ + bool reg_upd; + + /* + * Notifier callback for MDSS gdsc regulator events + */ + struct notifier_block gdsc_cb; + + /* + * Worker function to call PLL off event + */ + struct work_struct pll_off; + + /* + * PLL index if multiple index are available. Eg. in case of + * DSI we have 2 plls. + */ + uint32_t index; + + bool ssc_en; /* share pll with master */ + bool ssc_center; /* default is down spread */ + u32 ssc_freq; + u32 ssc_ppm; + + struct mdss_pll_resources *slave; + + /* + * target pll revision information + */ + int revision; + + void *priv; + + /* + * dynamic refresh pll codes stored in this structure + */ + struct dfps_info *dfps; + +}; + +struct mdss_pll_vco_calc { + s32 div_frac_start1; + s32 div_frac_start2; + s32 div_frac_start3; + s64 dec_start1; + s64 dec_start2; + s64 pll_plllock_cmp1; + s64 pll_plllock_cmp2; + s64 pll_plllock_cmp3; +}; + +static inline bool is_gdsc_disabled(struct mdss_pll_resources *pll_res) +{ + if (!pll_res->gdsc_base) { + WARN(1, "gdsc_base register is not defined\n"); + return true; + } + return readl_relaxed(pll_res->gdsc_base) & BIT(31) ? false : true; +} + +static inline int mdss_pll_div_prepare(struct clk_hw *hw) +{ + struct clk_hw *parent_hw = clk_hw_get_parent(hw); + /* Restore the divider's value */ + return hw->init->ops->set_rate(hw, clk_hw_get_rate(hw), + clk_hw_get_rate(parent_hw)); +} + +static inline int mdss_set_mux_sel(void *context, unsigned int reg, + unsigned int val) +{ + return 0; +} + +static inline int mdss_get_mux_sel(void *context, unsigned int reg, + unsigned int *val) +{ + *val = 0; + return 0; +} + +int mdss_pll_resource_enable(struct mdss_pll_resources *pll_res, bool enable); +int mdss_pll_util_resource_init(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); +void mdss_pll_util_resource_deinit(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); +void mdss_pll_util_resource_release(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); +int mdss_pll_util_resource_enable(struct mdss_pll_resources *pll_res, + bool enable); +int mdss_pll_util_resource_parse(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); +struct dss_vreg *mdss_pll_get_mp_by_reg_name(struct mdss_pll_resources *pll_res + , char *name); +#endif diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 83cb2a88c204..9e420497479c 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -8,6 +8,7 @@ menuconfig DRM tristate "Direct Rendering Manager (XFree86 4.1.0 and higher DRI support)" depends on (AGP || AGP=n) && !EMULATED_CMPXCHG && HAS_DMA select HDMI + select FB select FB_CMDLINE select I2C select I2C_ALGOBIT @@ -91,7 +92,7 @@ config DRM_FBDEV_EMULATION depends on DRM select DRM_KMS_HELPER select DRM_KMS_FB_HELPER - default y + default n help Choose this option if you have a need for the legacy fbdev support. Note that this support also provides the linux console diff --git a/drivers/gpu/drm/drm_dp_helper.c b/drivers/gpu/drm/drm_dp_helper.c index 08af8d6b844b..abfd4530f98d 100644 --- a/drivers/gpu/drm/drm_dp_helper.c +++ b/drivers/gpu/drm/drm_dp_helper.c @@ -144,6 +144,8 @@ u8 drm_dp_link_rate_to_bw_code(int link_rate) return DP_LINK_BW_2_7; case 540000: return DP_LINK_BW_5_4; + case 810000: + return DP_LINK_BW_8_1; } } EXPORT_SYMBOL(drm_dp_link_rate_to_bw_code); @@ -158,6 +160,8 @@ int drm_dp_bw_code_to_link_rate(u8 link_bw) return 270000; case DP_LINK_BW_5_4: return 540000; + case DP_LINK_BW_8_1: + return 810000; } } EXPORT_SYMBOL(drm_dp_bw_code_to_link_rate); @@ -354,20 +358,13 @@ EXPORT_SYMBOL(drm_dp_link_probe); */ int drm_dp_link_power_up(struct drm_dp_aux *aux, struct drm_dp_link *link) { - u8 value; + u8 value = DP_SET_POWER_D0; int err; /* DP_SET_POWER register is only available on DPCD v1.1 and later */ if (link->revision < 0x11) return 0; - err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value); - if (err < 0) - return err; - - value &= ~DP_SET_POWER_MASK; - value |= DP_SET_POWER_D0; - err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value); if (err < 0) return err; diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 6bb6337be920..f0890dee0658 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -96,6 +96,14 @@ struct detailed_mode_closure { #define LEVEL_GTF2 2 #define LEVEL_CVT 3 +/*Enum storing luminance types for HDR blocks in EDID*/ +enum luminance_value { + NO_LUMINANCE_DATA = 3, + MAXIMUM_LUMINANCE = 4, + FRAME_AVERAGE_LUMINANCE = 5, + MINIMUM_LUMINANCE = 6 +}; + static const struct edid_quirk { char vendor[4]; int product_id; @@ -2781,7 +2789,9 @@ add_detailed_modes(struct drm_connector *connector, struct edid *edid, #define VIDEO_BLOCK 0x02 #define VENDOR_BLOCK 0x03 #define SPEAKER_BLOCK 0x04 +#define HDR_STATIC_METADATA_EXTENDED_DATA_BLOCK 0x06 #define USE_EXTENDED_TAG 0x07 +#define VIDEO_CAPABILITY_BLOCK 0x07 #define EXT_VIDEO_CAPABILITY_BLOCK 0x00 #define EXT_VIDEO_DATA_BLOCK_420 0x0E #define EXT_VIDEO_CAP_BLOCK_Y420CMDB 0x0F @@ -3767,6 +3777,72 @@ drm_parse_hdmi_vsdb_audio(struct drm_connector *connector, const u8 *db) connector->audio_latency[1]); } +static u8 * +drm_edid_find_extended_tag_block(struct edid *edid, int blk_id) +{ + u8 *db = NULL; + u8 *cea = NULL; + + if (!edid) + return NULL; + + cea = drm_find_cea_extension(edid); + + if (cea && cea_revision(cea) >= 3) { + int i, start, end; + + if (cea_db_offsets(cea, &start, &end)) + return NULL; + + for_each_cea_db(cea, i, start, end) { + db = &cea[i]; + if ((cea_db_tag(db) == USE_EXTENDED_TAG) && + (db[1] == blk_id)) + return db; + } + } + return NULL; +} + +/* + * add_YCbCr420VDB_modes - add the modes found in Ycbcr420 VDB block + * @connector: connector corresponding to the HDMI sink + * @edid: handle to the EDID structure + * Parses the YCbCr420 VDB block and adds the modes to @connector. + */ +static int +add_YCbCr420VDB_modes(struct drm_connector *connector, struct edid *edid) +{ + + const u8 *db = NULL; + u32 i = 0; + u32 modes = 0; + u32 video_format = 0; + u8 len = 0; + + /*Find the YCbCr420 VDB*/ + db = drm_edid_find_extended_tag_block(edid, EXT_VIDEO_DATA_BLOCK_420); + /* Offset to byte 3 */ + if (db) { + len = db[0] & 0x1F; + db += 2; + for (i = 0; i < len - 1; i++) { + struct drm_display_mode *mode; + + video_format = *(db + i) & 0x7F; + mode = drm_display_mode_from_vic_index(connector, + db, len-1, i); + if (mode) { + DRM_DEBUG_KMS("Adding mode for vic = %d\n", + video_format); + drm_mode_probed_add(connector, mode); + modes++; + } + } + } + return modes; +} + static void monitor_name(struct detailed_timing *t, void *data) { @@ -3804,7 +3880,7 @@ void drm_edid_get_monitor_name(struct edid *edid, char *name, int bufsize) { int name_length; char buf[13]; - + if (bufsize <= 0) return; @@ -4635,6 +4711,7 @@ int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid) num_modes += add_cea_modes(connector, edid); num_modes += add_alternate_cea_modes(connector, edid); num_modes += add_displayid_detailed_modes(connector, edid); + num_modes += add_YCbCr420VDB_modes(connector, edid); if (edid->features & DRM_EDID_FEATURE_DEFAULT_GTF) num_modes += add_inferred_modes(connector, edid); diff --git a/drivers/gpu/drm/drm_framebuffer.c b/drivers/gpu/drm/drm_framebuffer.c index af279844d7ce..64d45fe156e7 100644 --- a/drivers/gpu/drm/drm_framebuffer.c +++ b/drivers/gpu/drm/drm_framebuffer.c @@ -265,7 +265,8 @@ drm_internal_framebuffer_create(struct drm_device *dev, struct drm_framebuffer *fb; int ret; - if (r->flags & ~(DRM_MODE_FB_INTERLACED | DRM_MODE_FB_MODIFIERS)) { + if (r->flags & ~(DRM_MODE_FB_INTERLACED | DRM_MODE_FB_MODIFIERS | + DRM_MODE_FB_SECURE)) { DRM_DEBUG_KMS("bad framebuffer flags 0x%08x\n", r->flags); return ERR_PTR(-EINVAL); } diff --git a/drivers/gpu/drm/drm_mipi_dsi.c b/drivers/gpu/drm/drm_mipi_dsi.c index 4b47226b90d4..efc3de178974 100644 --- a/drivers/gpu/drm/drm_mipi_dsi.c +++ b/drivers/gpu/drm/drm_mipi_dsi.c @@ -393,6 +393,7 @@ bool mipi_dsi_packet_format_is_short(u8 type) case MIPI_DSI_DCS_SHORT_WRITE_PARAM: case MIPI_DSI_DCS_READ: case MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE: + case MIPI_DSI_COMPRESSION_MODE: return true; } @@ -424,6 +425,7 @@ bool mipi_dsi_packet_format_is_long(u8 type) case MIPI_DSI_PACKED_PIXEL_STREAM_18: case MIPI_DSI_PIXEL_STREAM_3BYTE_18: case MIPI_DSI_PACKED_PIXEL_STREAM_24: + case MIPI_DSI_PPS: return true; } @@ -454,7 +456,7 @@ int mipi_dsi_create_packet(struct mipi_dsi_packet *packet, return -EINVAL; memset(packet, 0, sizeof(*packet)); - packet->header[0] = ((msg->channel & 0x3) << 6) | (msg->type & 0x3f); + packet->header[2] = ((msg->channel & 0x3) << 6) | (msg->type & 0x3f); /* TODO: compute ECC if hardware support is not available */ @@ -466,16 +468,16 @@ int mipi_dsi_create_packet(struct mipi_dsi_packet *packet, * and 2. */ if (mipi_dsi_packet_format_is_long(msg->type)) { - packet->header[1] = (msg->tx_len >> 0) & 0xff; - packet->header[2] = (msg->tx_len >> 8) & 0xff; + packet->header[0] = (msg->tx_len >> 0) & 0xff; + packet->header[1] = (msg->tx_len >> 8) & 0xff; packet->payload_length = msg->tx_len; packet->payload = msg->tx_buf; } else { const u8 *tx = msg->tx_buf; - packet->header[1] = (msg->tx_len > 0) ? tx[0] : 0; - packet->header[2] = (msg->tx_len > 1) ? tx[1] : 0; + packet->header[0] = (msg->tx_len > 0) ? tx[0] : 0; + packet->header[1] = (msg->tx_len > 1) ? tx[1] : 0; } packet->size = sizeof(packet->header) + packet->payload_length; diff --git a/drivers/gpu/drm/msm/Kconfig b/drivers/gpu/drm/msm/Kconfig index 99d39b2aefa6..c4ad821fef37 100644 --- a/drivers/gpu/drm/msm/Kconfig +++ b/drivers/gpu/drm/msm/Kconfig @@ -14,7 +14,8 @@ config DRM_MSM select QCOM_SCM select SND_SOC_HDMI_CODEC if SND_SOC select SYNC_FILE - select PM_OPP + select HDCP_QSEECOM + select MSM_EXT_DISPLAY default y help DRM/KMS driver for MSM/snapdragon. @@ -31,20 +32,44 @@ config DRM_MSM_REGISTER_LOGGING config DRM_MSM_HDMI_HDCP bool "Enable HDMI HDCP support in MSM DRM driver" depends on DRM_MSM && QCOM_SCM - default y + default n + help + Compile in support for logging register reads/writes in a format + that can be parsed by envytools demsm tool. If enabled, register + logging can be switched on via msm.reglog=y module param. + +config DRM_MSM_HDMI + bool "Enable HDMI support in MSM DRM driver" + depends on DRM_MSM + default n help - Choose this option to enable HDCP state machine + Compile in support for HDMI driver in msm drm + driver. HDMI external display support is enabled + through this config option. It can be primary or + secondary display on device. config DRM_MSM_DSI bool "Enable DSI support in MSM DRM driver" depends on DRM_MSM select DRM_PANEL select DRM_MIPI_DSI - default y + default n help Choose this option if you have a need for MIPI DSI connector support. +config DRM_MSM_DSI_STAGING + bool "Enable new DSI driver support in MSM DRM driver" + depends on DRM_MSM + select DRM_PANEL + select DRM_MIPI_DSI + default y + help + Choose this option if you need MIPI DSI connector support on MSM + which conforms to DRM. MIPI stands for Mobile Industry Processor + Interface and DSI stands for Display Serial Interface which powers + the primary display of your mobile device. + config DRM_MSM_DSI_PLL bool "Enable DSI PLL driver in MSM DRM" depends on DRM_MSM_DSI && COMMON_CLK @@ -75,9 +100,70 @@ config DRM_MSM_DSI_28NM_8960_PHY Choose this option if the 28nm DSI PHY 8960 variant is used on the platform. +config DRM_MSM_MDP5 + tristate "MSM MDP5 DRM driver" + depends on DRM_MSM + default n + help + Choose this option if MSM MDP5 revision support is + needed in DRM/KMS. This is not required if sde/mdp4 + only target enabled. MDP5 supports DSI and HDMI + displays. + +config DRM_MSM_MDP4 + tristate "MSM MDP4 DRM driver" + depends on DRM_MSM + default n + help + Choose this option if MSM MDP4 revision support is needed in DRM/KMS. + MSM MDP4 DRM driver should be disabled for other MDP revisions to + avoid possible conflicts. Only select this option if the target + MSM platform is MDP4 based. + +config DRM_MSM_HDCP + tristate "HDCP for MSM DRM" + depends on DRM_MSM + default n + help + Chose this option if High-bandwidth Digital Content Protection (HDCP) + support is needed in the MSM DRM/KMS driver. Enabling this config + will allow HDCP encrypted (copy protected) media to be displayed. + This should only be enabled if required for platforms supporting HDCP + over the desired interface. + +config DRM_SDE_WB + bool "Enable Writeback support in SDE DRM" + depends on DRM_MSM + default y + help + Choose this option for writeback connector support. + This option enables a virtual writeback connector where + the output image is written back to memory in the format + selected by the connector's mode and property settings. + config DRM_MSM_DSI_14NM_PHY bool "Enable DSI 14nm PHY driver in MSM DRM (used by MSM8996/APQ8096)" depends on DRM_MSM_DSI default y help Choose this option if DSI PHY on 8996 is used on the platform. + +config DRM_SDE_EVTLOG_DEBUG + bool "Enable event logging in MSM DRM" + depends on DRM_MSM + help + The SDE DRM debugging provides support to enable display debugging + features to: dump SDE registers during driver errors, panic + driver during fatal errors and enable some display-driver logging + into an internal buffer (this avoids logging overhead). + +config DRM_SDE_RSC + bool "Enable sde resource state coordinator(rsc) driver" + depends on DRM_MSM + help + The SDE DRM RSC provides display Resource State Coordinator support + to vote the ab/ib bandwidth for primary display. Each rsc client + can vote their active state. Any active request from any client + avoids the display core power collapse. A client can also register + for display core power collapse events on rsc. + diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile index 33008fa1be9b..331a223525d3 100644 --- a/drivers/gpu/drm/msm/Makefile +++ b/drivers/gpu/drm/msm/Makefile @@ -1,14 +1,49 @@ -ccflags-y := -Idrivers/gpu/drm/msm +ccflags-y := -Idrivers/gpu/drm/msm -Idrivers/gpu/drm/msm/dsi-staging -Idrivers/gpu/drm/msm/dp +ccflags-y += -Idrivers/gpu/drm/msm/display-manager ccflags-$(CONFIG_DRM_MSM_DSI) += -Idrivers/gpu/drm/msm/dsi +ccflags-$(CONFIG_DRM_MSM_DSI_PLL) += -Idrivers/gpu/drm/msm/dsi +ccflags-y += -Idrivers/gpu/drm/msm/sde +ccflags-y += -Idrivers/media/platform/msm/sde/rotator +ccflags-y += -Idrivers/gpu/drm/msm/hdmi -msm-y := \ - adreno/adreno_device.o \ - adreno/adreno_gpu.o \ - adreno/a3xx_gpu.o \ - adreno/a4xx_gpu.o \ - adreno/a5xx_gpu.o \ - adreno/a5xx_power.o \ - hdmi/hdmi.o \ +msm_drm-y := \ + dp/dp_usbpd.o \ + dp/dp_parser.o \ + dp/dp_power.o \ + dp/dp_catalog.o \ + dp/dp_aux.o \ + dp/dp_panel.o \ + dp/dp_link.o \ + dp/dp_ctrl.o \ + dp/dp_audio.o \ + dp/dp_debug.o \ + dp/dp_display.o \ + dp/dp_drm.o \ + dp/dp_hdcp2p2.o \ + sde/sde_crtc.o \ + sde/sde_encoder.o \ + sde/sde_encoder_phys_vid.o \ + sde/sde_encoder_phys_cmd.o \ + sde/sde_irq.o \ + sde/sde_core_irq.o \ + sde/sde_core_perf.o \ + sde/sde_rm.o \ + sde/sde_kms_utils.o \ + sde/sde_kms.o \ + sde/sde_plane.o \ + sde/sde_connector.o \ + sde/sde_color_processing.o \ + sde/sde_vbif.o \ + sde_dbg.o \ + sde_dbg_evtlog.o \ + sde_io_util.o \ + sde/sde_hw_reg_dma_v1_color_proc.o \ + sde/sde_hw_color_proc_v4.o \ + sde/sde_hw_ad4.o \ + sde_edid_parser.o \ + sde_hdcp_1x.o + +msm_drm-$(CONFIG_DRM_MSM_HDMI) += hdmi/hdmi.o \ hdmi/hdmi_audio.o \ hdmi/hdmi_bridge.o \ hdmi/hdmi_connector.o \ @@ -17,21 +52,16 @@ msm-y := \ hdmi/hdmi_phy_8960.o \ hdmi/hdmi_phy_8x60.o \ hdmi/hdmi_phy_8x74.o \ - edp/edp.o \ + +msm_drm-$(CONFIG_DRM_MSM_EDP) += edp/edp.o \ edp/edp_aux.o \ edp/edp_bridge.o \ edp/edp_connector.o \ edp/edp_ctrl.o \ edp/edp_phy.o \ - mdp/mdp_format.o \ + +msm_drm-$(CONFIG_DRM_MSM_MDP5) += mdp/mdp_format.o \ mdp/mdp_kms.o \ - mdp/mdp4/mdp4_crtc.o \ - mdp/mdp4/mdp4_dtv_encoder.o \ - mdp/mdp4/mdp4_lcdc_encoder.o \ - mdp/mdp4/mdp4_lvds_connector.o \ - mdp/mdp4/mdp4_irq.o \ - mdp/mdp4/mdp4_kms.o \ - mdp/mdp4/mdp4_plane.o \ mdp/mdp5/mdp5_cfg.o \ mdp/mdp5/mdp5_ctl.o \ mdp/mdp5/mdp5_crtc.o \ @@ -43,47 +73,123 @@ msm-y := \ mdp/mdp5/mdp5_mixer.o \ mdp/mdp5/mdp5_plane.o \ mdp/mdp5/mdp5_smp.o \ - msm_atomic.o \ - msm_debugfs.o \ - msm_drv.o \ - msm_fb.o \ - msm_fence.o \ - msm_gem.o \ - msm_gem_prime.o \ - msm_gem_shrinker.o \ - msm_gem_submit.o \ - msm_gem_vma.o \ - msm_gpu.o \ - msm_iommu.o \ - msm_perf.o \ - msm_rd.o \ - msm_ringbuffer.o -msm-$(CONFIG_DRM_FBDEV_EMULATION) += msm_fbdev.o -msm-$(CONFIG_COMMON_CLK) += mdp/mdp4/mdp4_lvds_pll.o -msm-$(CONFIG_COMMON_CLK) += hdmi/hdmi_pll_8960.o -msm-$(CONFIG_COMMON_CLK) += hdmi/hdmi_phy_8996.o +msm_drm-$(CONFIG_DRM_SDE_RSC) += sde_rsc.o \ + sde_rsc_hw.o \ + +# use drm gpu driver only if qcom_kgsl driver not available +ifneq ($(CONFIG_QCOM_KGSL),y) +msm_drm-y += adreno/adreno_device.o \ + adreno/adreno_gpu.o \ + adreno/a3xx_gpu.o \ + adreno/a4xx_gpu.o \ + adreno/a5xx_gpu.o \ + adreno/a5xx_power.o +endif + +msm_drm-$(CONFIG_DRM_MSM_MDP4) += mdp/mdp4/mdp4_crtc.o \ + mdp/mdp4/mdp4_dtv_encoder.o \ + mdp/mdp4/mdp4_lcdc_encoder.o \ + mdp/mdp4/mdp4_lvds_connector.o \ + mdp/mdp4/mdp4_irq.o \ + mdp/mdp4/mdp4_kms.o \ + mdp/mdp4/mdp4_dsi_encoder.o \ + mdp/mdp4/mdp4_plane.o + +msm_drm-$(CONFIG_DRM_FBDEV_EMULATION) += msm_fbdev.o +msm_drm-$(CONFIG_SYNC_FILE) += sde/sde_fence.o +msm_drm-$(CONFIG_DRM_MSM_MDP4) += mdp/mdp4/mdp4_lvds_pll.o +msm_drm-$(CONFIG_DRM_MSM_HDMI) += hdmi/hdmi_pll_8960.o +msm_drm-$(CONFIG_DRM_MSM_HDMI) += hdmi/hdmi_phy_8996.o -msm-$(CONFIG_DRM_MSM_HDMI_HDCP) += hdmi/hdmi_hdcp.o +msm_drm-$(CONFIG_DRM_MSM_HDMI_HDCP) += hdmi/hdmi_hdcp.o -msm-$(CONFIG_DRM_MSM_DSI) += dsi/dsi.o \ +msm_drm-$(CONFIG_DRM_MSM_DSI) += dsi/dsi.o \ mdp/mdp4/mdp4_dsi_encoder.o \ dsi/dsi_cfg.o \ dsi/dsi_host.o \ dsi/dsi_manager.o \ dsi/phy/dsi_phy.o \ + dsi/dsi_manager.o \ mdp/mdp5/mdp5_cmd_encoder.o -msm-$(CONFIG_DRM_MSM_DSI_28NM_PHY) += dsi/phy/dsi_phy_28nm.o -msm-$(CONFIG_DRM_MSM_DSI_20NM_PHY) += dsi/phy/dsi_phy_20nm.o -msm-$(CONFIG_DRM_MSM_DSI_28NM_8960_PHY) += dsi/phy/dsi_phy_28nm_8960.o -msm-$(CONFIG_DRM_MSM_DSI_14NM_PHY) += dsi/phy/dsi_phy_14nm.o +msm_drm-$(CONFIG_DRM_MSM_DSI_28NM_PHY) += dsi/phy/dsi_phy_28nm.o +msm_drm-$(CONFIG_DRM_MSM_DSI_20NM_PHY) += dsi/phy/dsi_phy_20nm.o +msm_drm-$(CONFIG_DRM_MSM_DSI_28NM_8960_PHY) += dsi/phy/dsi_phy_28nm_8960.o +msm_drm-$(CONFIG_DRM_MSM_DSI_14NM_PHY) += dsi/phy/dsi_phy_14nm.o ifeq ($(CONFIG_DRM_MSM_DSI_PLL),y) -msm-y += dsi/pll/dsi_pll.o -msm-$(CONFIG_DRM_MSM_DSI_28NM_PHY) += dsi/pll/dsi_pll_28nm.o -msm-$(CONFIG_DRM_MSM_DSI_28NM_8960_PHY) += dsi/pll/dsi_pll_28nm_8960.o -msm-$(CONFIG_DRM_MSM_DSI_14NM_PHY) += dsi/pll/dsi_pll_14nm.o +msm_drm-y += dsi/pll/dsi_pll.o +msm_drm-$(CONFIG_DRM_MSM_DSI_28NM_PHY) += dsi/pll/dsi_pll_28nm.o +msm_drm-$(CONFIG_DRM_MSM_DSI_28NM_8960_PHY) += dsi/pll/dsi_pll_28nm_8960.o +msm_drm-$(CONFIG_DRM_MSM_DSI_14NM_PHY) += dsi/pll/dsi_pll_14nm.o endif +msm_drm-$(CONFIG_DRM_MSM_DSI_STAGING) += dsi-staging/dsi_phy.o \ + dsi-staging/dsi_pwr.o \ + dsi-staging/dsi_phy.o \ + dsi-staging/dsi_phy_hw_v2_0.o \ + dsi-staging/dsi_phy_hw_v3_0.o \ + dsi-staging/dsi_phy_timing_calc.o \ + dsi-staging/dsi_phy_timing_v2_0.o \ + dsi-staging/dsi_phy_timing_v3_0.o \ + dsi-staging/dsi_ctrl_hw_cmn.o \ + dsi-staging/dsi_ctrl_hw_1_4.o \ + dsi-staging/dsi_ctrl_hw_2_0.o \ + dsi-staging/dsi_ctrl_hw_2_2.o \ + dsi-staging/dsi_ctrl.o \ + dsi-staging/dsi_catalog.o \ + dsi-staging/dsi_drm.o \ + dsi-staging/dsi_display.o \ + dsi-staging/dsi_panel.o \ + dsi-staging/dsi_clk_manager.o \ + dsi-staging/dsi_display_test.o + +msm_drm-$(CONFIG_DRM_MSM_DSI_PLL) += dsi/pll/dsi_pll.o \ + dsi/pll/dsi_pll_28nm.o + +msm_drm-$(CONFIG_DRM_MSM) += \ + sde/sde_hw_catalog.o \ + sde/sde_hw_cdm.o \ + sde/sde_hw_dspp.o \ + sde/sde_hw_intf.o \ + sde/sde_hw_lm.o \ + sde/sde_hw_ctl.o \ + sde/sde_hw_util.o \ + sde/sde_hw_sspp.o \ + sde/sde_hw_wb.o \ + sde/sde_hw_rot.o \ + sde/sde_hw_pingpong.o \ + sde/sde_hw_top.o \ + sde/sde_hw_interrupts.o \ + sde/sde_hw_vbif.o \ + sde/sde_hw_blk.o \ + sde/sde_formats.o \ + sde_power_handle.o \ + sde/sde_hw_color_processing_v1_7.o \ + sde/sde_reg_dma.o \ + sde/sde_hw_reg_dma_v1.o \ + sde/sde_hw_dsc.o \ + sde/sde_hw_ds.o + +msm_drm-$(CONFIG_DRM_SDE_WB) += sde/sde_wb.o \ + sde/sde_encoder_phys_wb.o + +msm_drm-$(CONFIG_DRM_MSM) += \ + msm_atomic.o \ + msm_drv.o \ + msm_fb.o \ + msm_gem.o \ + msm_gem_prime.o \ + msm_gem_submit.o \ + msm_gem_shrinker.o \ + msm_gpu.o \ + msm_iommu.o \ + msm_smmu.o \ + msm_perf.o \ + msm_rd.o \ + msm_ringbuffer.o \ + msm_prop.o \ + msm_fence.o \ + msm_debugfs.o -obj-$(CONFIG_DRM_MSM) += msm.o +obj-$(CONFIG_DRM_MSM) += msm_drm.o diff --git a/drivers/gpu/drm/msm/dp/dp_audio.c b/drivers/gpu/drm/msm/dp/dp_audio.c new file mode 100644 index 000000000000..c4a60dc87e9d --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_audio.c @@ -0,0 +1,796 @@ +/* + * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include +#include + +#include + +#include "dp_catalog.h" +#include "dp_audio.h" +#include "dp_panel.h" + +#define HEADER_BYTE_2_BIT 0 +#define PARITY_BYTE_2_BIT 8 +#define HEADER_BYTE_1_BIT 16 +#define PARITY_BYTE_1_BIT 24 +#define HEADER_BYTE_3_BIT 16 +#define PARITY_BYTE_3_BIT 24 + +struct dp_audio_private { + struct platform_device *ext_pdev; + struct platform_device *pdev; + struct dp_catalog_audio *catalog; + struct msm_ext_disp_init_data ext_audio_data; + struct dp_panel *panel; + + bool ack_enabled; + bool session_on; + bool engine_on; + + u32 channels; + + struct completion hpd_comp; + + struct dp_audio dp_audio; +}; + +static u8 dp_audio_get_g0_value(u8 data) +{ + u8 c[4]; + u8 g[4]; + u8 ret_data = 0; + u8 i; + + for (i = 0; i < 4; i++) + c[i] = (data >> i) & 0x01; + + g[0] = c[3]; + g[1] = c[0] ^ c[3]; + g[2] = c[1]; + g[3] = c[2]; + + for (i = 0; i < 4; i++) + ret_data = ((g[i] & 0x01) << i) | ret_data; + + return ret_data; +} + +static u8 dp_audio_get_g1_value(u8 data) +{ + u8 c[4]; + u8 g[4]; + u8 ret_data = 0; + u8 i; + + for (i = 0; i < 4; i++) + c[i] = (data >> i) & 0x01; + + g[0] = c[0] ^ c[3]; + g[1] = c[0] ^ c[1] ^ c[3]; + g[2] = c[1] ^ c[2]; + g[3] = c[2] ^ c[3]; + + for (i = 0; i < 4; i++) + ret_data = ((g[i] & 0x01) << i) | ret_data; + + return ret_data; +} + +static u8 dp_audio_calculate_parity(u32 data) +{ + u8 x0 = 0; + u8 x1 = 0; + u8 ci = 0; + u8 iData = 0; + u8 i = 0; + u8 parity_byte; + u8 num_byte = (data & 0xFF00) > 0 ? 8 : 2; + + for (i = 0; i < num_byte; i++) { + iData = (data >> i*4) & 0xF; + + ci = iData ^ x1; + x1 = x0 ^ dp_audio_get_g1_value(ci); + x0 = dp_audio_get_g0_value(ci); + } + + parity_byte = x1 | (x0 << 4); + + return parity_byte; +} + +static u32 dp_audio_get_header(struct dp_catalog_audio *catalog, + enum dp_catalog_audio_sdp_type sdp, + enum dp_catalog_audio_header_type header) +{ + catalog->sdp_type = sdp; + catalog->sdp_header = header; + catalog->get_header(catalog); + + return catalog->data; +} + +static void dp_audio_set_header(struct dp_catalog_audio *catalog, + u32 data, + enum dp_catalog_audio_sdp_type sdp, + enum dp_catalog_audio_header_type header) +{ + catalog->sdp_type = sdp; + catalog->sdp_header = header; + catalog->data = data; + catalog->set_header(catalog); +} + +static void dp_audio_stream_sdp(struct dp_audio_private *audio) +{ + struct dp_catalog_audio *catalog = audio->catalog; + u32 value, new_value; + u8 parity_byte; + + /* Config header and parity byte 1 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_STREAM, DP_AUDIO_SDP_HEADER_1); + + new_value = 0x02; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_1_BIT) + | (parity_byte << PARITY_BYTE_1_BIT)); + pr_debug("Header Byte 1: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_STREAM, DP_AUDIO_SDP_HEADER_1); + + /* Config header and parity byte 2 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_STREAM, DP_AUDIO_SDP_HEADER_2); + new_value = value; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_2_BIT) + | (parity_byte << PARITY_BYTE_2_BIT)); + pr_debug("Header Byte 2: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_STREAM, DP_AUDIO_SDP_HEADER_2); + + /* Config header and parity byte 3 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_STREAM, DP_AUDIO_SDP_HEADER_3); + + new_value = audio->channels - 1; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_3_BIT) + | (parity_byte << PARITY_BYTE_3_BIT)); + pr_debug("Header Byte 3: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_STREAM, DP_AUDIO_SDP_HEADER_3); +} + +static void dp_audio_timestamp_sdp(struct dp_audio_private *audio) +{ + struct dp_catalog_audio *catalog = audio->catalog; + u32 value, new_value; + u8 parity_byte; + + /* Config header and parity byte 1 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_TIMESTAMP, DP_AUDIO_SDP_HEADER_1); + + new_value = 0x1; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_1_BIT) + | (parity_byte << PARITY_BYTE_1_BIT)); + pr_debug("Header Byte 1: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_TIMESTAMP, DP_AUDIO_SDP_HEADER_1); + + /* Config header and parity byte 2 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_TIMESTAMP, DP_AUDIO_SDP_HEADER_2); + + new_value = 0x17; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_2_BIT) + | (parity_byte << PARITY_BYTE_2_BIT)); + pr_debug("Header Byte 2: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_TIMESTAMP, DP_AUDIO_SDP_HEADER_2); + + /* Config header and parity byte 3 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_TIMESTAMP, DP_AUDIO_SDP_HEADER_3); + + new_value = (0x0 | (0x11 << 2)); + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_3_BIT) + | (parity_byte << PARITY_BYTE_3_BIT)); + pr_debug("Header Byte 3: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_TIMESTAMP, DP_AUDIO_SDP_HEADER_3); +} + +static void dp_audio_infoframe_sdp(struct dp_audio_private *audio) +{ + struct dp_catalog_audio *catalog = audio->catalog; + u32 value, new_value; + u8 parity_byte; + + /* Config header and parity byte 1 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_INFOFRAME, DP_AUDIO_SDP_HEADER_1); + + new_value = 0x84; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_1_BIT) + | (parity_byte << PARITY_BYTE_1_BIT)); + pr_debug("Header Byte 1: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_INFOFRAME, DP_AUDIO_SDP_HEADER_1); + + /* Config header and parity byte 2 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_INFOFRAME, DP_AUDIO_SDP_HEADER_2); + + new_value = 0x1b; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_2_BIT) + | (parity_byte << PARITY_BYTE_2_BIT)); + pr_debug("Header Byte 2: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_INFOFRAME, DP_AUDIO_SDP_HEADER_2); + + /* Config header and parity byte 3 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_INFOFRAME, DP_AUDIO_SDP_HEADER_3); + + new_value = (0x0 | (0x11 << 2)); + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_3_BIT) + | (parity_byte << PARITY_BYTE_3_BIT)); + pr_debug("Header Byte 3: value = 0x%x, parity_byte = 0x%x\n", + new_value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_INFOFRAME, DP_AUDIO_SDP_HEADER_3); +} + +static void dp_audio_copy_management_sdp(struct dp_audio_private *audio) +{ + struct dp_catalog_audio *catalog = audio->catalog; + u32 value, new_value; + u8 parity_byte; + + /* Config header and parity byte 1 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_COPYMANAGEMENT, DP_AUDIO_SDP_HEADER_1); + + new_value = 0x05; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_1_BIT) + | (parity_byte << PARITY_BYTE_1_BIT)); + pr_debug("Header Byte 1: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_COPYMANAGEMENT, DP_AUDIO_SDP_HEADER_1); + + /* Config header and parity byte 2 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_COPYMANAGEMENT, DP_AUDIO_SDP_HEADER_2); + + new_value = 0x0F; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_2_BIT) + | (parity_byte << PARITY_BYTE_2_BIT)); + pr_debug("Header Byte 2: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_COPYMANAGEMENT, DP_AUDIO_SDP_HEADER_2); + + /* Config header and parity byte 3 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_COPYMANAGEMENT, DP_AUDIO_SDP_HEADER_3); + + new_value = 0x0; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_3_BIT) + | (parity_byte << PARITY_BYTE_3_BIT)); + pr_debug("Header Byte 3: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_COPYMANAGEMENT, DP_AUDIO_SDP_HEADER_3); +} + +static void dp_audio_isrc_sdp(struct dp_audio_private *audio) +{ + struct dp_catalog_audio *catalog = audio->catalog; + u32 value, new_value; + u8 parity_byte; + + /* Config header and parity byte 1 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_ISRC, DP_AUDIO_SDP_HEADER_1); + + new_value = 0x06; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_1_BIT) + | (parity_byte << PARITY_BYTE_1_BIT)); + pr_debug("Header Byte 1: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_ISRC, DP_AUDIO_SDP_HEADER_1); + + /* Config header and parity byte 2 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_ISRC, DP_AUDIO_SDP_HEADER_2); + + new_value = 0x0F; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_2_BIT) + | (parity_byte << PARITY_BYTE_2_BIT)); + pr_debug("Header Byte 2: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_ISRC, DP_AUDIO_SDP_HEADER_2); +} + +static void dp_audio_setup_sdp(struct dp_audio_private *audio) +{ + audio->catalog->config_sdp(audio->catalog); + + dp_audio_stream_sdp(audio); + dp_audio_timestamp_sdp(audio); + dp_audio_infoframe_sdp(audio); + dp_audio_copy_management_sdp(audio); + dp_audio_isrc_sdp(audio); +} + +static void dp_audio_setup_acr(struct dp_audio_private *audio) +{ + u32 select = 0; + struct dp_catalog_audio *catalog = audio->catalog; + + switch (audio->dp_audio.bw_code) { + case DP_LINK_BW_1_62: + select = 0; + break; + case DP_LINK_BW_2_7: + select = 1; + break; + case DP_LINK_BW_5_4: + select = 2; + break; + case DP_LINK_BW_8_1: + select = 3; + break; + default: + pr_debug("Unknown link rate\n"); + select = 0; + break; + } + + catalog->data = select; + catalog->config_acr(catalog); +} + +static void dp_audio_safe_to_exit_level(struct dp_audio_private *audio) +{ + struct dp_catalog_audio *catalog = audio->catalog; + u32 safe_to_exit_level = 0; + + switch (audio->dp_audio.lane_count) { + case 1: + safe_to_exit_level = 14; + break; + case 2: + safe_to_exit_level = 8; + break; + case 4: + safe_to_exit_level = 5; + break; + default: + pr_debug("setting the default safe_to_exit_level = %u\n", + safe_to_exit_level); + safe_to_exit_level = 14; + break; + } + + catalog->data = safe_to_exit_level; + catalog->safe_to_exit_level(catalog); +} + +static void dp_audio_enable(struct dp_audio_private *audio, bool enable) +{ + struct dp_catalog_audio *catalog = audio->catalog; + + catalog->data = enable; + catalog->enable(catalog); + + audio->engine_on = enable; +} + +static struct dp_audio_private *get_audio_get_data(struct platform_device *pdev) +{ + struct msm_ext_disp_data *ext_data; + struct dp_audio *dp_audio; + + if (!pdev) { + pr_err("invalid input\n"); + return ERR_PTR(-ENODEV); + } + + ext_data = platform_get_drvdata(pdev); + if (!ext_data) { + pr_err("invalid ext disp data\n"); + return ERR_PTR(-EINVAL); + } + + dp_audio = ext_data->intf_data; + if (!ext_data) { + pr_err("invalid intf data\n"); + return ERR_PTR(-EINVAL); + } + + return container_of(dp_audio, struct dp_audio_private, dp_audio); +} + +static int dp_audio_info_setup(struct platform_device *pdev, + struct msm_ext_disp_audio_setup_params *params) +{ + int rc = 0; + struct dp_audio_private *audio; + + audio = get_audio_get_data(pdev); + if (IS_ERR(audio)) { + rc = PTR_ERR(audio); + goto end; + } + + audio->channels = params->num_of_channels; + + dp_audio_setup_sdp(audio); + dp_audio_setup_acr(audio); + dp_audio_safe_to_exit_level(audio); + dp_audio_enable(audio, true); +end: + return rc; +} + +static int dp_audio_get_edid_blk(struct platform_device *pdev, + struct msm_ext_disp_audio_edid_blk *blk) +{ + int rc = 0; + struct dp_audio_private *audio; + struct sde_edid_ctrl *edid; + + audio = get_audio_get_data(pdev); + if (IS_ERR(audio)) { + rc = PTR_ERR(audio); + goto end; + } + + if (!audio->panel || !audio->panel->edid_ctrl) { + pr_err("invalid panel data\n"); + rc = -EINVAL; + goto end; + } + + edid = audio->panel->edid_ctrl; + + blk->audio_data_blk = edid->audio_data_block; + blk->audio_data_blk_size = edid->adb_size; + + blk->spk_alloc_data_blk = edid->spkr_alloc_data_block; + blk->spk_alloc_data_blk_size = edid->sadb_size; +end: + return rc; +} + +static int dp_audio_get_cable_status(struct platform_device *pdev, u32 vote) +{ + int rc = 0; + struct dp_audio_private *audio; + + audio = get_audio_get_data(pdev); + if (IS_ERR(audio)) { + rc = PTR_ERR(audio); + goto end; + } + + if (!audio->panel) { + pr_err("invalid panel data\n"); + rc = -EINVAL; + goto end; + } + + return audio->session_on; +end: + return rc; +} + +static int dp_audio_get_intf_id(struct platform_device *pdev) +{ + int rc = 0; + struct dp_audio_private *audio; + + audio = get_audio_get_data(pdev); + if (IS_ERR(audio)) { + rc = PTR_ERR(audio); + goto end; + } + + return EXT_DISPLAY_TYPE_DP; +end: + return rc; +} + +static void dp_audio_teardown_done(struct platform_device *pdev) +{ + struct dp_audio_private *audio; + + audio = get_audio_get_data(pdev); + if (IS_ERR(audio)) + return; + + if (!audio->panel) { + pr_err("invalid panel data\n"); + return; + } + + dp_audio_enable(audio, false); + + complete_all(&audio->hpd_comp); + + pr_debug("audio engine disabled\n"); +} + +static int dp_audio_ack_done(struct platform_device *pdev, u32 ack) +{ + int rc = 0, ack_hpd; + struct dp_audio_private *audio; + + audio = get_audio_get_data(pdev); + if (IS_ERR(audio)) { + rc = PTR_ERR(audio); + goto end; + } + + if (ack & AUDIO_ACK_SET_ENABLE) { + audio->ack_enabled = ack & AUDIO_ACK_ENABLE ? + true : false; + + pr_debug("audio ack feature %s\n", + audio->ack_enabled ? "enabled" : "disabled"); + goto end; + } + + if (!audio->ack_enabled) + goto end; + + ack_hpd = ack & AUDIO_ACK_CONNECT; + + pr_debug("acknowledging audio (%d)\n", ack_hpd); + + if (!audio->engine_on) + complete_all(&audio->hpd_comp); +end: + return rc; +} + +static int dp_audio_init_ext_disp(struct dp_audio_private *audio) +{ + int rc = 0; + struct device_node *pd = NULL; + const char *phandle = "qcom,ext-disp"; + struct msm_ext_disp_init_data *ext; + struct msm_ext_disp_audio_codec_ops *ops; + + ext = &audio->ext_audio_data; + ops = &ext->codec_ops; + + ext->type = EXT_DISPLAY_TYPE_DP; + ext->pdev = audio->pdev; + ext->intf_data = &audio->dp_audio; + + ops->audio_info_setup = dp_audio_info_setup; + ops->get_audio_edid_blk = dp_audio_get_edid_blk; + ops->cable_status = dp_audio_get_cable_status; + ops->get_intf_id = dp_audio_get_intf_id; + ops->teardown_done = dp_audio_teardown_done; + ops->acknowledge = dp_audio_ack_done; + + if (!audio->pdev->dev.of_node) { + pr_err("cannot find audio dev.of_node\n"); + rc = -ENODEV; + goto end; + } + + pd = of_parse_phandle(audio->pdev->dev.of_node, phandle, 0); + if (!pd) { + pr_err("cannot parse %s handle\n", phandle); + rc = -ENODEV; + goto end; + } + + audio->ext_pdev = of_find_device_by_node(pd); + if (!audio->ext_pdev) { + pr_err("cannot find %s pdev\n", phandle); + rc = -ENODEV; + goto end; + } + + rc = msm_ext_disp_register_intf(audio->ext_pdev, ext); + if (rc) + pr_err("failed to register disp\n"); +end: + if (pd) + of_node_put(pd); + + return rc; +} + +static int dp_audio_on(struct dp_audio *dp_audio) +{ + int rc = 0; + struct dp_audio_private *audio; + struct msm_ext_disp_init_data *ext; + + if (!dp_audio) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + audio = container_of(dp_audio, struct dp_audio_private, dp_audio); + + ext = &audio->ext_audio_data; + + audio->session_on = true; + + rc = ext->intf_ops.audio_config(audio->ext_pdev, + EXT_DISPLAY_TYPE_DP, + EXT_DISPLAY_CABLE_CONNECT); + if (rc) { + pr_err("failed to config audio, err=%d\n", rc); + goto end; + } + + rc = ext->intf_ops.audio_notify(audio->ext_pdev, + EXT_DISPLAY_TYPE_DP, + EXT_DISPLAY_CABLE_CONNECT); + if (rc) { + pr_err("failed to notify audio, err=%d\n", rc); + goto end; + } + + reinit_completion(&audio->hpd_comp); + rc = wait_for_completion_timeout(&audio->hpd_comp, HZ * 5); + if (!rc) { + pr_err("timeout\n"); + rc = -ETIMEDOUT; + goto end; + } + + pr_debug("success\n"); +end: + return rc; +} + +static int dp_audio_off(struct dp_audio *dp_audio) +{ + int rc = 0; + struct dp_audio_private *audio; + struct msm_ext_disp_init_data *ext; + + if (!dp_audio) { + pr_err("invalid input\n"); + return -EINVAL; + } + + audio = container_of(dp_audio, struct dp_audio_private, dp_audio); + ext = &audio->ext_audio_data; + + rc = ext->intf_ops.audio_notify(audio->ext_pdev, + EXT_DISPLAY_TYPE_DP, + EXT_DISPLAY_CABLE_DISCONNECT); + if (rc) { + pr_err("failed to notify audio, err=%d\n", rc); + goto end; + } + + reinit_completion(&audio->hpd_comp); + rc = wait_for_completion_timeout(&audio->hpd_comp, HZ * 5); + if (!rc) { + pr_err("timeout\n"); + rc = -ETIMEDOUT; + goto end; + } + + pr_debug("success\n"); +end: + rc = ext->intf_ops.audio_config(audio->ext_pdev, + EXT_DISPLAY_TYPE_DP, + EXT_DISPLAY_CABLE_DISCONNECT); + if (rc) + pr_err("failed to config audio, err=%d\n", rc); + + audio->session_on = false; + audio->engine_on = false; + + return rc; +} + +struct dp_audio *dp_audio_get(struct platform_device *pdev, + struct dp_panel *panel, + struct dp_catalog_audio *catalog) +{ + int rc = 0; + struct dp_audio_private *audio; + struct dp_audio *dp_audio; + + if (!pdev || !panel || !catalog) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + audio = devm_kzalloc(&pdev->dev, sizeof(*audio), GFP_KERNEL); + if (!audio) { + rc = -ENOMEM; + goto error; + } + + init_completion(&audio->hpd_comp); + + audio->pdev = pdev; + audio->panel = panel; + audio->catalog = catalog; + + dp_audio = &audio->dp_audio; + + dp_audio->on = dp_audio_on; + dp_audio->off = dp_audio_off; + + rc = dp_audio_init_ext_disp(audio); + if (rc) { + devm_kfree(&pdev->dev, audio); + goto error; + } + + catalog->init(catalog); + + return dp_audio; +error: + return ERR_PTR(rc); +} + +void dp_audio_put(struct dp_audio *dp_audio) +{ + struct dp_audio_private *audio; + + if (!dp_audio) + return; + + audio = container_of(dp_audio, struct dp_audio_private, dp_audio); + + devm_kfree(&audio->pdev->dev, audio); +} diff --git a/drivers/gpu/drm/msm/dp/dp_audio.h b/drivers/gpu/drm/msm/dp/dp_audio.h new file mode 100644 index 000000000000..d6e6b74b7a63 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_audio.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _DP_AUDIO_H_ +#define _DP_AUDIO_H_ + +#include + +#include "dp_panel.h" +#include "dp_catalog.h" + +/** + * struct dp_audio + * @lane_count: number of lanes configured in current session + * @bw_code: link rate's bandwidth code for current session + */ +struct dp_audio { + u32 lane_count; + u32 bw_code; + + /** + * on() + * + * Enables the audio by notifying the user module. + * + * @dp_audio: an instance of struct dp_audio. + * + * Returns the error code in case of failure, 0 in success case. + */ + int (*on)(struct dp_audio *dp_audio); + + /** + * off() + * + * Disables the audio by notifying the user module. + * + * @dp_audio: an instance of struct dp_audio. + * + * Returns the error code in case of failure, 0 in success case. + */ + int (*off)(struct dp_audio *dp_audio); +}; + +/** + * dp_audio_get() + * + * Creates and instance of dp audio. + * + * @pdev: caller's platform device instance. + * @panel: an instance of dp_panel module. + * @catalog: an instance of dp_catalog_audio module. + * + * Returns the error code in case of failure, otherwize + * an instance of newly created dp_module. + */ +struct dp_audio *dp_audio_get(struct platform_device *pdev, + struct dp_panel *panel, + struct dp_catalog_audio *catalog); + +/** + * dp_audio_put() + * + * Cleans the dp_audio instance. + * + * @dp_audio: an instance of dp_audio. + */ +void dp_audio_put(struct dp_audio *dp_audio); +#endif /* _DP_AUDIO_H_ */ + + diff --git a/drivers/gpu/drm/msm/dp/dp_aux.c b/drivers/gpu/drm/msm/dp/dp_aux.c new file mode 100644 index 000000000000..7426fc447805 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_aux.c @@ -0,0 +1,570 @@ +/* + * Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include + +#include "dp_aux.h" + +#define DP_AUX_ENUM_STR(x) #x + +enum { + DP_AUX_DATA_INDEX_WRITE = BIT(31), +}; + +struct dp_aux_private { + struct device *dev; + struct dp_aux dp_aux; + struct dp_catalog_aux *catalog; + struct dp_aux_cfg *cfg; + + struct mutex mutex; + struct completion comp; + + u32 aux_error_num; + u32 retry_cnt; + bool cmd_busy; + bool native; + bool read; + bool no_send_addr; + bool no_send_stop; + u32 offset; + u32 segment; + + struct drm_dp_aux drm_aux; +}; + +static char *dp_aux_get_error(u32 aux_error) +{ + switch (aux_error) { + case DP_AUX_ERR_NONE: + return DP_AUX_ENUM_STR(DP_AUX_ERR_NONE); + case DP_AUX_ERR_ADDR: + return DP_AUX_ENUM_STR(DP_AUX_ERR_ADDR); + case DP_AUX_ERR_TOUT: + return DP_AUX_ENUM_STR(DP_AUX_ERR_TOUT); + case DP_AUX_ERR_NACK: + return DP_AUX_ENUM_STR(DP_AUX_ERR_NACK); + case DP_AUX_ERR_DEFER: + return DP_AUX_ENUM_STR(DP_AUX_ERR_DEFER); + case DP_AUX_ERR_NACK_DEFER: + return DP_AUX_ENUM_STR(DP_AUX_ERR_NACK_DEFER); + default: + return "unknown"; + } +} + +static u32 dp_aux_write(struct dp_aux_private *aux, + struct drm_dp_aux_msg *msg) +{ + u32 data[4], reg, len; + u8 *msgdata = msg->buffer; + int const aux_cmd_fifo_len = 128; + int i = 0; + + if (aux->read) + len = 4; + else + len = msg->size + 4; + + /* + * cmd fifo only has depth of 144 bytes + * limit buf length to 128 bytes here + */ + if (len > aux_cmd_fifo_len) { + pr_err("buf len error\n"); + return 0; + } + + /* Pack cmd and write to HW */ + data[0] = (msg->address >> 16) & 0xf; /* addr[19:16] */ + if (aux->read) + data[0] |= BIT(4); /* R/W */ + + data[1] = (msg->address >> 8) & 0xff; /* addr[15:8] */ + data[2] = msg->address & 0xff; /* addr[7:0] */ + data[3] = (msg->size - 1) & 0xff; /* len[7:0] */ + + for (i = 0; i < len; i++) { + reg = (i < 4) ? data[i] : msgdata[i - 4]; + reg = ((reg) << 8) & 0x0000ff00; /* index = 0, write */ + if (i == 0) + reg |= DP_AUX_DATA_INDEX_WRITE; + aux->catalog->data = reg; + aux->catalog->write_data(aux->catalog); + } + + aux->catalog->clear_trans(aux->catalog, false); + + reg = 0; /* Transaction number == 1 */ + if (!aux->native) { /* i2c */ + reg |= BIT(8); + + if (aux->no_send_addr) + reg |= BIT(10); + + if (aux->no_send_stop) + reg |= BIT(11); + } + + reg |= BIT(9); + aux->catalog->data = reg; + aux->catalog->write_trans(aux->catalog); + + return len; +} + +static int dp_aux_cmd_fifo_tx(struct dp_aux_private *aux, + struct drm_dp_aux_msg *msg) +{ + u32 ret = 0, len = 0, timeout; + int const aux_timeout_ms = HZ/4; + + reinit_completion(&aux->comp); + + len = dp_aux_write(aux, msg); + if (len == 0) { + pr_err("DP AUX write failed\n"); + return -EINVAL; + } + + timeout = wait_for_completion_timeout(&aux->comp, aux_timeout_ms); + if (!timeout) { + pr_err("aux %s timeout\n", (aux->read ? "read" : "write")); + return -ETIMEDOUT; + } + + if (aux->aux_error_num == DP_AUX_ERR_NONE) { + ret = len; + } else { + pr_err_ratelimited("aux err: %s\n", + dp_aux_get_error(aux->aux_error_num)); + + ret = -EINVAL; + } + + return ret; +} + +static void dp_aux_cmd_fifo_rx(struct dp_aux_private *aux, + struct drm_dp_aux_msg *msg) +{ + u32 data; + u8 *dp; + u32 i, actual_i; + u32 len = msg->size; + + aux->catalog->clear_trans(aux->catalog, true); + + data = 0; + data |= DP_AUX_DATA_INDEX_WRITE; /* INDEX_WRITE */ + data |= BIT(0); /* read */ + + aux->catalog->data = data; + aux->catalog->write_data(aux->catalog); + + dp = msg->buffer; + + /* discard first byte */ + data = aux->catalog->read_data(aux->catalog); + + for (i = 0; i < len; i++) { + data = aux->catalog->read_data(aux->catalog); + *dp++ = (u8)((data >> 8) & 0xff); + + actual_i = (data >> 16) & 0xFF; + if (i != actual_i) + pr_warn("Index mismatch: expected %d, found %d\n", + i, actual_i); + } +} + +static void dp_aux_native_handler(struct dp_aux_private *aux) +{ + u32 isr = aux->catalog->isr; + + if (isr & DP_INTR_AUX_I2C_DONE) + aux->aux_error_num = DP_AUX_ERR_NONE; + else if (isr & DP_INTR_WRONG_ADDR) + aux->aux_error_num = DP_AUX_ERR_ADDR; + else if (isr & DP_INTR_TIMEOUT) + aux->aux_error_num = DP_AUX_ERR_TOUT; + if (isr & DP_INTR_NACK_DEFER) + aux->aux_error_num = DP_AUX_ERR_NACK; + + complete(&aux->comp); +} + +static void dp_aux_i2c_handler(struct dp_aux_private *aux) +{ + u32 isr = aux->catalog->isr; + + if (isr & DP_INTR_AUX_I2C_DONE) { + if (isr & (DP_INTR_I2C_NACK | DP_INTR_I2C_DEFER)) + aux->aux_error_num = DP_AUX_ERR_NACK; + else + aux->aux_error_num = DP_AUX_ERR_NONE; + } else { + if (isr & DP_INTR_WRONG_ADDR) + aux->aux_error_num = DP_AUX_ERR_ADDR; + else if (isr & DP_INTR_TIMEOUT) + aux->aux_error_num = DP_AUX_ERR_TOUT; + if (isr & DP_INTR_NACK_DEFER) + aux->aux_error_num = DP_AUX_ERR_NACK_DEFER; + if (isr & DP_INTR_I2C_NACK) + aux->aux_error_num = DP_AUX_ERR_NACK; + if (isr & DP_INTR_I2C_DEFER) + aux->aux_error_num = DP_AUX_ERR_DEFER; + } + + complete(&aux->comp); +} + +static void dp_aux_isr(struct dp_aux *dp_aux) +{ + struct dp_aux_private *aux; + + if (!dp_aux) { + pr_err("invalid input\n"); + return; + } + + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); + + aux->catalog->get_irq(aux->catalog, aux->cmd_busy); + + if (!aux->cmd_busy) + return; + + if (aux->native) + dp_aux_native_handler(aux); + else + dp_aux_i2c_handler(aux); +} + +static void dp_aux_reconfig(struct dp_aux *dp_aux) +{ + struct dp_aux_private *aux; + + if (!dp_aux) { + pr_err("invalid input\n"); + return; + } + + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); + + aux->catalog->update_aux_cfg(aux->catalog, + aux->cfg, PHY_AUX_CFG1); + aux->catalog->reset(aux->catalog); +} + +static void dp_aux_update_offset_and_segment(struct dp_aux_private *aux, + struct drm_dp_aux_msg *input_msg) +{ + u32 const edid_address = 0x50; + u32 const segment_address = 0x30; + bool i2c_read = input_msg->request & + (DP_AUX_I2C_READ & DP_AUX_NATIVE_READ); + u8 *data = NULL; + + if (aux->native || i2c_read || ((input_msg->address != edid_address) && + (input_msg->address != segment_address))) + return; + + + data = input_msg->buffer; + if (input_msg->address == segment_address) + aux->segment = *data; + else + aux->offset = *data; +} + +/** + * dp_aux_transfer_helper() - helper function for EDID read transactions + * + * @aux: DP AUX private structure + * @input_msg: input message from DRM upstream APIs + * + * return: void + * + * This helper function is used to fix EDID reads for non-compliant + * sinks that do not handle the i2c middle-of-transaction flag correctly. + */ +static void dp_aux_transfer_helper(struct dp_aux_private *aux, + struct drm_dp_aux_msg *input_msg) +{ + struct drm_dp_aux_msg helper_msg; + u32 const message_size = 0x10; + u32 const segment_address = 0x30; + bool i2c_mot = input_msg->request & DP_AUX_I2C_MOT; + bool i2c_read = input_msg->request & + (DP_AUX_I2C_READ & DP_AUX_NATIVE_READ); + + if (!i2c_mot || !i2c_read || (input_msg->size == 0)) + return; + + aux->read = false; + aux->cmd_busy = true; + aux->no_send_addr = true; + aux->no_send_stop = true; + + /* + * Send the segment address for every i2c read in which the + * middle-of-tranaction flag is set. This is required to support EDID + * reads of more than 2 blocks as the segment address is reset to 0 + * since we are overriding the middle-of-transaction flag for read + * transactions. + */ + memset(&helper_msg, 0, sizeof(helper_msg)); + helper_msg.address = segment_address; + helper_msg.buffer = &aux->segment; + helper_msg.size = 1; + dp_aux_cmd_fifo_tx(aux, &helper_msg); + + /* + * Send the offset address for every i2c read in which the + * middle-of-transaction flag is set. This will ensure that the sink + * will update its read pointer and return the correct portion of the + * EDID buffer in the subsequent i2c read trasntion triggered in the + * native AUX transfer function. + */ + memset(&helper_msg, 0, sizeof(helper_msg)); + helper_msg.address = input_msg->address; + helper_msg.buffer = &aux->offset; + helper_msg.size = 1; + dp_aux_cmd_fifo_tx(aux, &helper_msg); + aux->offset += message_size; + + if (aux->offset == 0x80 || aux->offset == 0x100) + aux->segment = 0x0; /* reset segment at end of block */ +} + +/* + * This function does the real job to process an AUX transaction. + * It will call aux_reset() function to reset the AUX channel, + * if the waiting is timeout. + */ +static ssize_t dp_aux_transfer(struct drm_dp_aux *drm_aux, + struct drm_dp_aux_msg *msg) +{ + ssize_t ret; + int const aux_cmd_native_max = 16; + int const aux_cmd_i2c_max = 128; + int const retry_count = 5; + struct dp_aux_private *aux = container_of(drm_aux, + struct dp_aux_private, drm_aux); + + mutex_lock(&aux->mutex); + + aux->native = msg->request & (DP_AUX_NATIVE_WRITE & DP_AUX_NATIVE_READ); + + /* Ignore address only message */ + if ((msg->size == 0) || (msg->buffer == NULL)) { + msg->reply = aux->native ? + DP_AUX_NATIVE_REPLY_ACK : DP_AUX_I2C_REPLY_ACK; + ret = msg->size; + goto unlock_exit; + } + + /* msg sanity check */ + if ((aux->native && (msg->size > aux_cmd_native_max)) || + (msg->size > aux_cmd_i2c_max)) { + pr_err("%s: invalid msg: size(%zu), request(%x)\n", + __func__, msg->size, msg->request); + ret = -EINVAL; + goto unlock_exit; + } + + dp_aux_update_offset_and_segment(aux, msg); + dp_aux_transfer_helper(aux, msg); + + aux->read = msg->request & (DP_AUX_I2C_READ & DP_AUX_NATIVE_READ); + aux->cmd_busy = true; + + if (aux->read) { + aux->no_send_addr = true; + aux->no_send_stop = false; + } else { + aux->no_send_addr = true; + aux->no_send_stop = true; + } + + ret = dp_aux_cmd_fifo_tx(aux, msg); + if ((ret < 0) && aux->native) { + aux->retry_cnt++; + if (!(aux->retry_cnt % retry_count)) + aux->catalog->update_aux_cfg(aux->catalog, + aux->cfg, PHY_AUX_CFG1); + aux->catalog->reset(aux->catalog); + goto unlock_exit; + } else if (ret < 0) { + goto unlock_exit; + } + + if (aux->aux_error_num == DP_AUX_ERR_NONE) { + if (aux->read) + dp_aux_cmd_fifo_rx(aux, msg); + + msg->reply = aux->native ? + DP_AUX_NATIVE_REPLY_ACK : DP_AUX_I2C_REPLY_ACK; + } else { + /* Reply defer to retry */ + msg->reply = aux->native ? + DP_AUX_NATIVE_REPLY_DEFER : DP_AUX_I2C_REPLY_DEFER; + } + + /* Return requested size for success or retry */ + ret = msg->size; + aux->retry_cnt = 0; + +unlock_exit: + aux->cmd_busy = false; + mutex_unlock(&aux->mutex); + return ret; +} + +static void dp_aux_reset_phy_config_indices(struct dp_aux_cfg *aux_cfg) +{ + int i = 0; + + for (i = 0; i < PHY_AUX_CFG_MAX; i++) + aux_cfg[i].current_index = 0; +} + +static void dp_aux_init(struct dp_aux *dp_aux, struct dp_aux_cfg *aux_cfg) +{ + struct dp_aux_private *aux; + + if (!dp_aux || !aux_cfg) { + pr_err("invalid input\n"); + return; + } + + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); + + aux->catalog->reset(aux->catalog); + aux->catalog->enable(aux->catalog, true); + aux->retry_cnt = 0; + dp_aux_reset_phy_config_indices(aux_cfg); + aux->catalog->setup(aux->catalog, aux_cfg); +} + +static void dp_aux_deinit(struct dp_aux *dp_aux) +{ + struct dp_aux_private *aux; + + if (!dp_aux) { + pr_err("invalid input\n"); + return; + } + + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); + + aux->catalog->enable(aux->catalog, false); +} + +static int dp_aux_register(struct dp_aux *dp_aux) +{ + struct dp_aux_private *aux; + int ret = 0; + + if (!dp_aux) { + pr_err("invalid input\n"); + ret = -EINVAL; + goto exit; + } + + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); + + aux->drm_aux.name = "sde_dp_aux"; + aux->drm_aux.dev = aux->dev; + aux->drm_aux.transfer = dp_aux_transfer; + ret = drm_dp_aux_register(&aux->drm_aux); + if (ret) { + pr_err("%s: failed to register drm aux: %d\n", __func__, ret); + goto exit; + } + dp_aux->drm_aux = &aux->drm_aux; +exit: + return ret; +} + +static void dp_aux_deregister(struct dp_aux *dp_aux) +{ + struct dp_aux_private *aux; + + if (!dp_aux) { + pr_err("invalid input\n"); + return; + } + + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); + drm_dp_aux_unregister(&aux->drm_aux); +} + +struct dp_aux *dp_aux_get(struct device *dev, struct dp_catalog_aux *catalog, + struct dp_aux_cfg *aux_cfg) +{ + int rc = 0; + struct dp_aux_private *aux; + struct dp_aux *dp_aux; + + if (!catalog || !aux_cfg) { + pr_err("invalid input\n"); + rc = -ENODEV; + goto error; + } + + aux = devm_kzalloc(dev, sizeof(*aux), GFP_KERNEL); + if (!aux) { + rc = -ENOMEM; + goto error; + } + + init_completion(&aux->comp); + aux->cmd_busy = false; + mutex_init(&aux->mutex); + + aux->dev = dev; + aux->catalog = catalog; + aux->cfg = aux_cfg; + dp_aux = &aux->dp_aux; + aux->retry_cnt = 0; + + dp_aux->isr = dp_aux_isr; + dp_aux->init = dp_aux_init; + dp_aux->deinit = dp_aux_deinit; + dp_aux->drm_aux_register = dp_aux_register; + dp_aux->drm_aux_deregister = dp_aux_deregister; + dp_aux->reconfig = dp_aux_reconfig; + + return dp_aux; +error: + return ERR_PTR(rc); +} + +void dp_aux_put(struct dp_aux *dp_aux) +{ + struct dp_aux_private *aux; + + if (!dp_aux) + return; + + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); + + mutex_destroy(&aux->mutex); + + devm_kfree(aux->dev, aux); +} diff --git a/drivers/gpu/drm/msm/dp/dp_aux.h b/drivers/gpu/drm/msm/dp/dp_aux.h new file mode 100644 index 000000000000..5d96fd9b71d6 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_aux.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _DP_AUX_H_ +#define _DP_AUX_H_ + +#include "dp_catalog.h" +#include "drm_dp_helper.h" + +enum dp_aux_error { + DP_AUX_ERR_NONE = 0, + DP_AUX_ERR_ADDR = -1, + DP_AUX_ERR_TOUT = -2, + DP_AUX_ERR_NACK = -3, + DP_AUX_ERR_DEFER = -4, + DP_AUX_ERR_NACK_DEFER = -5, +}; + +struct dp_aux { + struct drm_dp_aux *drm_aux; + int (*drm_aux_register)(struct dp_aux *aux); + void (*drm_aux_deregister)(struct dp_aux *aux); + void (*isr)(struct dp_aux *aux); + void (*init)(struct dp_aux *aux, struct dp_aux_cfg *aux_cfg); + void (*deinit)(struct dp_aux *aux); + void (*reconfig)(struct dp_aux *aux); +}; + +struct dp_aux *dp_aux_get(struct device *dev, struct dp_catalog_aux *catalog, + struct dp_aux_cfg *aux_cfg); +void dp_aux_put(struct dp_aux *aux); + +#endif /*__DP_AUX_H_*/ diff --git a/drivers/gpu/drm/msm/dp/dp_catalog.c b/drivers/gpu/drm/msm/dp/dp_catalog.c new file mode 100644 index 000000000000..8b17aede3ed0 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_catalog.c @@ -0,0 +1,1320 @@ +/* + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include +#include + +#include "dp_catalog.h" +#include "dp_reg.h" + +#define DP_GET_MSB(x) (x >> 8) +#define DP_GET_LSB(x) (x & 0xff) + +#define dp_read(offset) readl_relaxed((offset)) +#define dp_write(offset, data) writel_relaxed((data), (offset)) + +#define dp_catalog_get_priv(x) { \ + struct dp_catalog *dp_catalog; \ + dp_catalog = container_of(x, struct dp_catalog, x); \ + catalog = container_of(dp_catalog, struct dp_catalog_private, \ + dp_catalog); \ +} + +#define DP_INTERRUPT_STATUS1 \ + (DP_INTR_AUX_I2C_DONE| \ + DP_INTR_WRONG_ADDR | DP_INTR_TIMEOUT | \ + DP_INTR_NACK_DEFER | DP_INTR_WRONG_DATA_CNT | \ + DP_INTR_I2C_NACK | DP_INTR_I2C_DEFER | \ + DP_INTR_PLL_UNLOCKED | DP_INTR_AUX_ERROR) + +#define DP_INTR_MASK1 (DP_INTERRUPT_STATUS1 << 2) + +#define DP_INTERRUPT_STATUS2 \ + (DP_INTR_READY_FOR_VIDEO | DP_INTR_IDLE_PATTERN_SENT | \ + DP_INTR_FRAME_END | DP_INTR_CRC_UPDATED) + +#define DP_INTR_MASK2 (DP_INTERRUPT_STATUS2 << 2) + +static u8 const vm_pre_emphasis[4][4] = { + {0x00, 0x0B, 0x12, 0xFF}, /* pe0, 0 db */ + {0x00, 0x0A, 0x12, 0xFF}, /* pe1, 3.5 db */ + {0x00, 0x0C, 0xFF, 0xFF}, /* pe2, 6.0 db */ + {0xFF, 0xFF, 0xFF, 0xFF} /* pe3, 9.5 db */ +}; + +/* voltage swing, 0.2v and 1.0v are not support */ +static u8 const vm_voltage_swing[4][4] = { + {0x07, 0x0F, 0x14, 0xFF}, /* sw0, 0.4v */ + {0x11, 0x1D, 0x1F, 0xFF}, /* sw1, 0.6 v */ + {0x18, 0x1F, 0xFF, 0xFF}, /* sw1, 0.8 v */ + {0xFF, 0xFF, 0xFF, 0xFF} /* sw1, 1.2 v, optional */ +}; + +/* audio related catalog functions */ +struct dp_catalog_private { + struct device *dev; + struct dp_io *io; + + u32 (*audio_map)[DP_AUDIO_SDP_HEADER_MAX]; + struct dp_catalog dp_catalog; +}; + +/* aux related catalog functions */ +static u32 dp_catalog_aux_read_data(struct dp_catalog_aux *aux) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!aux) { + pr_err("invalid input\n"); + goto end; + } + + dp_catalog_get_priv(aux); + base = catalog->io->ctrl_io.base; + + return dp_read(base + DP_AUX_DATA); +end: + return 0; +} + +static int dp_catalog_aux_write_data(struct dp_catalog_aux *aux) +{ + int rc = 0; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!aux) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + dp_catalog_get_priv(aux); + base = catalog->io->ctrl_io.base; + + dp_write(base + DP_AUX_DATA, aux->data); +end: + return rc; +} + +static int dp_catalog_aux_write_trans(struct dp_catalog_aux *aux) +{ + int rc = 0; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!aux) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + dp_catalog_get_priv(aux); + base = catalog->io->ctrl_io.base; + + dp_write(base + DP_AUX_TRANS_CTRL, aux->data); +end: + return rc; +} + +static int dp_catalog_aux_clear_trans(struct dp_catalog_aux *aux, bool read) +{ + int rc = 0; + u32 data = 0; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!aux) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + dp_catalog_get_priv(aux); + base = catalog->io->ctrl_io.base; + + if (read) { + data = dp_read(base + DP_AUX_TRANS_CTRL); + data &= ~BIT(9); + dp_write(base + DP_AUX_TRANS_CTRL, data); + } else { + dp_write(base + DP_AUX_TRANS_CTRL, 0); + } +end: + return rc; +} + +static void dp_catalog_aux_reset(struct dp_catalog_aux *aux) +{ + u32 aux_ctrl; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!aux) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(aux); + base = catalog->io->ctrl_io.base; + + aux_ctrl = dp_read(base + DP_AUX_CTRL); + + aux_ctrl |= BIT(1); + dp_write(base + DP_AUX_CTRL, aux_ctrl); + usleep_range(1000, 1010); /* h/w recommended delay */ + + aux_ctrl &= ~BIT(1); + dp_write(base + DP_AUX_CTRL, aux_ctrl); +} + +static void dp_catalog_aux_enable(struct dp_catalog_aux *aux, bool enable) +{ + u32 aux_ctrl; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!aux) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(aux); + base = catalog->io->ctrl_io.base; + + aux_ctrl = dp_read(base + DP_AUX_CTRL); + + if (enable) { + dp_write(base + DP_TIMEOUT_COUNT, 0xffff); + dp_write(base + DP_AUX_LIMITS, 0xffff); + aux_ctrl |= BIT(0); + } else { + aux_ctrl &= ~BIT(0); + } + + dp_write(base + DP_AUX_CTRL, aux_ctrl); +} + +static void dp_catalog_aux_update_cfg(struct dp_catalog_aux *aux, + struct dp_aux_cfg *cfg, enum dp_phy_aux_config_type type) +{ + struct dp_catalog_private *catalog; + u32 new_index = 0, current_index = 0; + + if (!aux || !cfg || (type >= PHY_AUX_CFG_MAX)) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(aux); + + current_index = cfg[type].current_index; + new_index = (current_index + 1) % cfg[type].cfg_cnt; + pr_debug("Updating %s from 0x%08x to 0x%08x\n", + dp_phy_aux_config_type_to_string(type), + cfg[type].lut[current_index], cfg[type].lut[new_index]); + + dp_write(catalog->io->phy_io.base + cfg[type].offset, + cfg[type].lut[new_index]); + cfg[type].current_index = new_index; +} + +static void dp_catalog_aux_setup(struct dp_catalog_aux *aux, + struct dp_aux_cfg *cfg) +{ + struct dp_catalog_private *catalog; + int i = 0; + + if (!aux || !cfg) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(aux); + + dp_write(catalog->io->phy_io.base + DP_PHY_PD_CTL, 0x65); + wmb(); /* make sure PD programming happened */ + + /* Turn on BIAS current for PHY/PLL */ + dp_write(catalog->io->dp_pll_io.base + + QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x1b); + + /* DP AUX CFG register programming */ + for (i = 0; i < PHY_AUX_CFG_MAX; i++) { + pr_debug("%s: offset=0x%08x, value=0x%08x\n", + dp_phy_aux_config_type_to_string(i), + cfg[i].offset, cfg[i].lut[cfg[i].current_index]); + dp_write(catalog->io->phy_io.base + cfg[i].offset, + cfg[i].lut[cfg[i].current_index]); + } + + dp_write(catalog->io->phy_io.base + DP_PHY_AUX_INTERRUPT_MASK, 0x1F); +} + +static void dp_catalog_aux_get_irq(struct dp_catalog_aux *aux, bool cmd_busy) +{ + u32 ack; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!aux) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(aux); + base = catalog->io->ctrl_io.base; + + aux->isr = dp_read(base + DP_INTR_STATUS); + aux->isr &= ~DP_INTR_MASK1; + ack = aux->isr & DP_INTERRUPT_STATUS1; + ack <<= 1; + ack |= DP_INTR_MASK1; + dp_write(base + DP_INTR_STATUS, ack); +} + +/* controller related catalog functions */ +static u32 dp_catalog_ctrl_read_hdcp_status(struct dp_catalog_ctrl *ctrl) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + return dp_read(base + DP_HDCP_STATUS); +} + +static void dp_catalog_ctrl_setup_infoframe_sdp(struct dp_catalog_ctrl *ctrl) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + u32 header, data; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + header = dp_read(base + MMSS_DP_VSCEXT_0); + header |= ctrl->hdr_data.vsc_hdr_byte1; + dp_write(base + MMSS_DP_VSCEXT_0, header); + + header = dp_read(base + MMSS_DP_VSCEXT_1); + header |= ctrl->hdr_data.vsc_hdr_byte1; + dp_write(base + MMSS_DP_VSCEXT_1, header); + + header = dp_read(base + MMSS_DP_VSCEXT_1); + header |= ctrl->hdr_data.vsc_hdr_byte1; + dp_write(base + MMSS_DP_VSCEXT_1, header); + + header = ctrl->hdr_data.version; + header |= ctrl->hdr_data.length << 8; + header |= ctrl->hdr_data.eotf << 16; + header |= (ctrl->hdr_data.descriptor_id << 24); + dp_write(base + MMSS_DP_VSCEXT_2, header); + + data = (DP_GET_LSB(ctrl->hdr_data.display_primaries_x[0]) | + (DP_GET_MSB(ctrl->hdr_data.display_primaries_x[0]) << 8) | + (DP_GET_LSB(ctrl->hdr_data.display_primaries_y[0]) << 16) | + (DP_GET_MSB(ctrl->hdr_data.display_primaries_y[0]) << 24)); + dp_write(base + MMSS_DP_VSCEXT_3, data); + + data = (DP_GET_LSB(ctrl->hdr_data.display_primaries_x[1]) | + (DP_GET_MSB(ctrl->hdr_data.display_primaries_x[1]) << 8) | + (DP_GET_LSB(ctrl->hdr_data.display_primaries_y[1]) << 16) | + (DP_GET_MSB(ctrl->hdr_data.display_primaries_y[1]) << 24)); + dp_write(base + MMSS_DP_VSCEXT_4, data); + + data = (DP_GET_LSB(ctrl->hdr_data.display_primaries_x[2]) | + (DP_GET_MSB(ctrl->hdr_data.display_primaries_x[2]) << 8) | + (DP_GET_LSB(ctrl->hdr_data.display_primaries_y[2]) << 16) | + (DP_GET_MSB(ctrl->hdr_data.display_primaries_y[2]) << 24)); + dp_write(base + MMSS_DP_VSCEXT_5, data); + + data = (DP_GET_LSB(ctrl->hdr_data.white_point_x) | + (DP_GET_MSB(ctrl->hdr_data.white_point_x) << 8) | + (DP_GET_LSB(ctrl->hdr_data.white_point_y) << 16) | + (DP_GET_MSB(ctrl->hdr_data.white_point_y) << 24)); + dp_write(base + MMSS_DP_VSCEXT_6, data); + + data = (DP_GET_LSB(ctrl->hdr_data.max_luminance) | + (DP_GET_MSB(ctrl->hdr_data.max_luminance) << 8) | + (DP_GET_LSB(ctrl->hdr_data.min_luminance) << 16) | + (DP_GET_MSB(ctrl->hdr_data.min_luminance) << 24)); + dp_write(base + MMSS_DP_VSCEXT_7, data); + + data = (DP_GET_LSB(ctrl->hdr_data.max_content_light_level) | + (DP_GET_MSB(ctrl->hdr_data.max_content_light_level) << 8) | + (DP_GET_LSB(ctrl->hdr_data.max_average_light_level) << 16) | + (DP_GET_MSB(ctrl->hdr_data.max_average_light_level) << 24)); + dp_write(base + MMSS_DP_VSCEXT_8, data); + + dp_write(base + MMSS_DP_VSCEXT_9, 0x00); +} + +static void dp_catalog_ctrl_setup_vsc_sdp(struct dp_catalog_ctrl *ctrl) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + u32 value; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + value = dp_read(base + MMSS_DP_GENERIC0_0); + value |= ctrl->hdr_data.vsc_hdr_byte1; + dp_write(base + MMSS_DP_GENERIC0_0, value); + + value = dp_read(base + MMSS_DP_GENERIC0_1); + value |= ctrl->hdr_data.vsc_hdr_byte2; + dp_write(base + MMSS_DP_GENERIC0_1, value); + + value = dp_read(base + MMSS_DP_GENERIC0_1); + value |= ctrl->hdr_data.vsc_hdr_byte3; + dp_write(base + MMSS_DP_GENERIC0_1, value); + + dp_write(base + MMSS_DP_GENERIC0_2, 0x00); + dp_write(base + MMSS_DP_GENERIC0_3, 0x00); + dp_write(base + MMSS_DP_GENERIC0_4, 0x00); + dp_write(base + MMSS_DP_GENERIC0_5, 0x00); + + dp_write(base + MMSS_DP_GENERIC0_6, ctrl->hdr_data.pkt_payload); + dp_write(base + MMSS_DP_GENERIC0_7, 0x00); + dp_write(base + MMSS_DP_GENERIC0_8, 0x00); + dp_write(base + MMSS_DP_GENERIC0_9, 0x00); +} + +static void dp_catalog_ctrl_config_hdr(struct dp_catalog_ctrl *ctrl) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + u32 cfg, cfg2; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + cfg = dp_read(base + MMSS_DP_SDP_CFG); + /* VSCEXT_SDP_EN */ + cfg |= BIT(16); + + /* GEN0_SDP_EN */ + cfg |= BIT(17); + + dp_write(base + MMSS_DP_SDP_CFG, cfg); + + cfg2 = dp_read(base + MMSS_DP_SDP_CFG2); + /* Generic0 SDP Payload is 19 bytes which is > 16, so Bit16 is 1 */ + cfg2 |= BIT(16); + dp_write(base + MMSS_DP_SDP_CFG2, cfg2); + + dp_catalog_ctrl_setup_vsc_sdp(ctrl); + dp_catalog_ctrl_setup_infoframe_sdp(ctrl); + + cfg = dp_read(base + DP_MISC1_MISC0); + /* Indicates presence of VSC */ + cfg |= BIT(6) << 8; + + dp_write(base + DP_MISC1_MISC0, cfg); + + cfg = dp_read(base + DP_CONFIGURATION_CTRL); + /* Send VSC */ + cfg |= BIT(7); + + switch (ctrl->hdr_data.bpc) { + default: + case 10: + cfg |= BIT(9); + break; + case 8: + cfg |= BIT(8); + break; + } + + dp_write(base + DP_CONFIGURATION_CTRL, cfg); + + cfg = dp_read(base + DP_COMPRESSION_MODE_CTRL); + + /* Trigger SDP values in registers */ + cfg |= BIT(8); + dp_write(base + DP_COMPRESSION_MODE_CTRL, cfg); +} + +static void dp_catalog_ctrl_update_transfer_unit(struct dp_catalog_ctrl *ctrl) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + dp_write(base + DP_VALID_BOUNDARY, ctrl->valid_boundary); + dp_write(base + DP_TU, ctrl->dp_tu); + dp_write(base + DP_VALID_BOUNDARY_2, ctrl->valid_boundary2); +} + +static void dp_catalog_ctrl_state_ctrl(struct dp_catalog_ctrl *ctrl, u32 state) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + dp_write(base + DP_STATE_CTRL, state); +} + +static void dp_catalog_ctrl_config_ctrl(struct dp_catalog_ctrl *ctrl, u32 cfg) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + pr_debug("DP_CONFIGURATION_CTRL=0x%x\n", cfg); + + dp_write(base + DP_CONFIGURATION_CTRL, cfg); + dp_write(base + DP_MAINLINK_LEVELS, 0xa08); + dp_write(base + MMSS_DP_ASYNC_FIFO_CONFIG, 0x1); +} + +static void dp_catalog_ctrl_lane_mapping(struct dp_catalog_ctrl *ctrl) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + dp_write(base + DP_LOGICAL2PHYSCIAL_LANE_MAPPING, 0xe4); +} + +static void dp_catalog_ctrl_mainlink_ctrl(struct dp_catalog_ctrl *ctrl, + bool enable) +{ + u32 mainlink_ctrl; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + if (enable) { + dp_write(base + DP_MAINLINK_CTRL, 0x02000000); + wmb(); /* make sure mainlink is turned off before reset */ + dp_write(base + DP_MAINLINK_CTRL, 0x02000002); + wmb(); /* make sure mainlink entered reset */ + dp_write(base + DP_MAINLINK_CTRL, 0x02000000); + wmb(); /* make sure mainlink reset done */ + dp_write(base + DP_MAINLINK_CTRL, 0x02000001); + wmb(); /* make sure mainlink turned on */ + } else { + mainlink_ctrl = dp_read(base + DP_MAINLINK_CTRL); + mainlink_ctrl &= ~BIT(0); + dp_write(base + DP_MAINLINK_CTRL, mainlink_ctrl); + } +} + +static void dp_catalog_ctrl_config_misc(struct dp_catalog_ctrl *ctrl, + u32 cc, u32 tb) +{ + u32 misc_val; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + misc_val = dp_read(base + DP_MISC1_MISC0); + misc_val |= cc; + misc_val |= (tb << 5); + misc_val |= BIT(0); /* Configure clock to synchronous mode */ + + pr_debug("misc settings = 0x%x\n", misc_val); + dp_write(base + DP_MISC1_MISC0, misc_val); +} + +static void dp_catalog_ctrl_config_msa(struct dp_catalog_ctrl *ctrl, + u32 rate, u32 stream_rate_khz, + bool fixed_nvid) +{ + u32 pixel_m, pixel_n; + u32 mvid, nvid; + u64 mvid_calc; + u32 const nvid_fixed = 0x8000; + u32 const link_rate_hbr2 = 540000; + u32 const link_rate_hbr3 = 810000; + struct dp_catalog_private *catalog; + void __iomem *base_cc, *base_ctrl; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + if (fixed_nvid) { + pr_debug("use fixed NVID=0x%x\n", nvid_fixed); + nvid = nvid_fixed; + + pr_debug("link rate=%dkbps, stream_rate_khz=%uKhz", + rate, stream_rate_khz); + + /* + * For intermediate results, use 64 bit arithmetic to avoid + * loss of precision. + */ + mvid_calc = (u64) stream_rate_khz * nvid; + mvid_calc = div_u64(mvid_calc, rate); + + /* + * truncate back to 32 bits as this final divided value will + * always be within the range of a 32 bit unsigned int. + */ + mvid = (u32) mvid_calc; + } else { + base_cc = catalog->io->dp_cc_io.base; + + pixel_m = dp_read(base_cc + MMSS_DP_PIXEL_M); + pixel_n = dp_read(base_cc + MMSS_DP_PIXEL_N); + pr_debug("pixel_m=0x%x, pixel_n=0x%x\n", pixel_m, pixel_n); + + mvid = (pixel_m & 0xFFFF) * 5; + nvid = (0xFFFF & (~pixel_n)) + (pixel_m & 0xFFFF); + + pr_debug("rate = %d\n", rate); + + if (link_rate_hbr2 == rate) + nvid *= 2; + + if (link_rate_hbr3 == rate) + nvid *= 3; + } + + base_ctrl = catalog->io->ctrl_io.base; + pr_debug("mvid=0x%x, nvid=0x%x\n", mvid, nvid); + dp_write(base_ctrl + DP_SOFTWARE_MVID, mvid); + dp_write(base_ctrl + DP_SOFTWARE_NVID, nvid); +} + +static void dp_catalog_ctrl_set_pattern(struct dp_catalog_ctrl *ctrl, + u32 pattern) +{ + int bit, cnt = 10; + u32 data; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + bit = 1; + bit <<= (pattern - 1); + pr_debug("hw: bit=%d train=%d\n", bit, pattern); + dp_write(base + DP_STATE_CTRL, bit); + + bit = 8; + bit <<= (pattern - 1); + + while (cnt--) { + data = dp_read(base + DP_MAINLINK_READY); + if (data & bit) + break; + } + + if (cnt == 0) + pr_err("set link_train=%d failed\n", pattern); +} + +static void dp_catalog_ctrl_usb_reset(struct dp_catalog_ctrl *ctrl, bool flip) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + + base = catalog->io->usb3_dp_com.base; + + dp_write(base + USB3_DP_COM_RESET_OVRD_CTRL, 0x0a); + dp_write(base + USB3_DP_COM_PHY_MODE_CTRL, 0x02); + dp_write(base + USB3_DP_COM_SW_RESET, 0x01); + /* make sure usb3 com phy software reset is done */ + wmb(); + + if (!flip) /* CC1 */ + dp_write(base + USB3_DP_COM_TYPEC_CTRL, 0x02); + else /* CC2 */ + dp_write(base + USB3_DP_COM_TYPEC_CTRL, 0x03); + + dp_write(base + USB3_DP_COM_SWI_CTRL, 0x00); + dp_write(base + USB3_DP_COM_SW_RESET, 0x00); + /* make sure the software reset is done */ + wmb(); + + dp_write(base + USB3_DP_COM_POWER_DOWN_CTRL, 0x01); + dp_write(base + USB3_DP_COM_RESET_OVRD_CTRL, 0x00); + /* make sure phy is brought out of reset */ + wmb(); + +} + +static void dp_catalog_ctrl_reset(struct dp_catalog_ctrl *ctrl) +{ + u32 sw_reset; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + sw_reset = dp_read(base + DP_SW_RESET); + + sw_reset |= BIT(0); + dp_write(base + DP_SW_RESET, sw_reset); + usleep_range(1000, 1010); /* h/w recommended delay */ + + sw_reset &= ~BIT(0); + dp_write(base + DP_SW_RESET, sw_reset); +} + +static bool dp_catalog_ctrl_mainlink_ready(struct dp_catalog_ctrl *ctrl) +{ + u32 data; + int cnt = 10; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + goto end; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + while (--cnt) { + /* DP_MAINLINK_READY */ + data = dp_read(base + DP_MAINLINK_READY); + if (data & BIT(0)) + return true; + + usleep_range(1000, 1010); /* 1ms wait before next reg read */ + } + pr_err("mainlink not ready\n"); +end: + return false; +} + +static void dp_catalog_ctrl_enable_irq(struct dp_catalog_ctrl *ctrl, + bool enable) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + if (enable) { + dp_write(base + DP_INTR_STATUS, DP_INTR_MASK1); + dp_write(base + DP_INTR_STATUS2, DP_INTR_MASK2); + } else { + dp_write(base + DP_INTR_STATUS, 0x00); + dp_write(base + DP_INTR_STATUS2, 0x00); + } +} + +static void dp_catalog_ctrl_hpd_config(struct dp_catalog_ctrl *ctrl, bool en) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + if (en) { + u32 reftimer = dp_read(base + DP_DP_HPD_REFTIMER); + + dp_write(base + DP_DP_HPD_INT_ACK, 0xF); + dp_write(base + DP_DP_HPD_INT_MASK, 0xF); + + /* Enabling REFTIMER */ + reftimer |= BIT(16); + dp_write(base + DP_DP_HPD_REFTIMER, 0xF); + /* Enable HPD */ + dp_write(base + DP_DP_HPD_CTRL, 0x1); + } else { + /*Disable HPD */ + dp_write(base + DP_DP_HPD_CTRL, 0x0); + } +} + +static void dp_catalog_ctrl_get_interrupt(struct dp_catalog_ctrl *ctrl) +{ + u32 ack = 0; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + ctrl->isr = dp_read(base + DP_INTR_STATUS2); + ctrl->isr &= ~DP_INTR_MASK2; + ack = ctrl->isr & DP_INTERRUPT_STATUS2; + ack <<= 1; + ack |= DP_INTR_MASK2; + dp_write(base + DP_INTR_STATUS2, ack); +} + +static void dp_catalog_ctrl_phy_reset(struct dp_catalog_ctrl *ctrl) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + dp_write(base + DP_PHY_CTRL, 0x5); /* bit 0 & 2 */ + usleep_range(1000, 1010); /* h/w recommended delay */ + dp_write(base + DP_PHY_CTRL, 0x0); + wmb(); /* make sure PHY reset done */ +} + +static void dp_catalog_ctrl_phy_lane_cfg(struct dp_catalog_ctrl *ctrl, + bool flipped, u8 ln_cnt) +{ + u32 info = 0x0; + struct dp_catalog_private *catalog; + u8 orientation = BIT(!!flipped); + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + + info |= (ln_cnt & 0x0F); + info |= ((orientation & 0x0F) << 4); + pr_debug("Shared Info = 0x%x\n", info); + + dp_write(catalog->io->phy_io.base + DP_PHY_SPARE0, info); +} + +static void dp_catalog_ctrl_update_vx_px(struct dp_catalog_ctrl *ctrl, + u8 v_level, u8 p_level) +{ + struct dp_catalog_private *catalog; + void __iomem *base0, *base1; + u8 value0, value1; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base0 = catalog->io->ln_tx0_io.base; + base1 = catalog->io->ln_tx1_io.base; + + pr_debug("hw: v=%d p=%d\n", v_level, p_level); + + value0 = vm_voltage_swing[v_level][p_level]; + value1 = vm_pre_emphasis[v_level][p_level]; + + /* program default setting first */ + dp_write(base0 + TXn_TX_DRV_LVL, 0x2A); + dp_write(base1 + TXn_TX_DRV_LVL, 0x2A); + dp_write(base0 + TXn_TX_EMP_POST1_LVL, 0x20); + dp_write(base1 + TXn_TX_EMP_POST1_LVL, 0x20); + + /* Enable MUX to use Cursor values from these registers */ + value0 |= BIT(5); + value1 |= BIT(5); + + /* Configure host and panel only if both values are allowed */ + if (value0 != 0xFF && value1 != 0xFF) { + dp_write(base0 + TXn_TX_DRV_LVL, value0); + dp_write(base1 + TXn_TX_DRV_LVL, value0); + dp_write(base0 + TXn_TX_EMP_POST1_LVL, value1); + dp_write(base1 + TXn_TX_EMP_POST1_LVL, value1); + + pr_debug("hw: vx_value=0x%x px_value=0x%x\n", + value0, value1); + } else { + pr_err("invalid vx (0x%x=0x%x), px (0x%x=0x%x\n", + v_level, value0, p_level, value1); + } +} + +static void dp_catalog_ctrl_send_phy_pattern(struct dp_catalog_ctrl *ctrl, + u32 pattern) +{ + struct dp_catalog_private *catalog; + u32 value = 0x0; + void __iomem *base = NULL; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + + base = catalog->io->ctrl_io.base; + + dp_write(base + DP_STATE_CTRL, 0x0); + + switch (pattern) { + case DP_TEST_PHY_PATTERN_D10_2_NO_SCRAMBLING: + dp_write(base + DP_STATE_CTRL, 0x1); + break; + case DP_TEST_PHY_PATTERN_SYMBOL_ERR_MEASUREMENT_CNT: + value &= ~(1 << 16); + dp_write(base + DP_HBR2_COMPLIANCE_SCRAMBLER_RESET, value); + value |= 0xFC; + dp_write(base + DP_HBR2_COMPLIANCE_SCRAMBLER_RESET, value); + dp_write(base + DP_MAINLINK_LEVELS, 0x2); + dp_write(base + DP_STATE_CTRL, 0x10); + break; + case DP_TEST_PHY_PATTERN_PRBS7: + dp_write(base + DP_STATE_CTRL, 0x20); + break; + case DP_TEST_PHY_PATTERN_80_BIT_CUSTOM_PATTERN: + dp_write(base + DP_STATE_CTRL, 0x40); + /* 00111110000011111000001111100000 */ + dp_write(base + DP_TEST_80BIT_CUSTOM_PATTERN_REG0, 0x3E0F83E0); + /* 00001111100000111110000011111000 */ + dp_write(base + DP_TEST_80BIT_CUSTOM_PATTERN_REG1, 0x0F83E0F8); + /* 1111100000111110 */ + dp_write(base + DP_TEST_80BIT_CUSTOM_PATTERN_REG2, 0x0000F83E); + break; + case DP_TEST_PHY_PATTERN_HBR2_CTS_EYE_PATTERN: + value = BIT(16); + dp_write(base + DP_HBR2_COMPLIANCE_SCRAMBLER_RESET, value); + value |= 0xFC; + dp_write(base + DP_HBR2_COMPLIANCE_SCRAMBLER_RESET, value); + dp_write(base + DP_MAINLINK_LEVELS, 0x2); + dp_write(base + DP_STATE_CTRL, 0x10); + break; + default: + pr_debug("No valid test pattern requested: 0x%x\n", pattern); + return; + } + + /* Make sure the test pattern is programmed in the hardware */ + wmb(); +} + +static u32 dp_catalog_ctrl_read_phy_pattern(struct dp_catalog_ctrl *ctrl) +{ + struct dp_catalog_private *catalog; + void __iomem *base = NULL; + + if (!ctrl) { + pr_err("invalid input\n"); + return 0; + } + + dp_catalog_get_priv(ctrl); + + base = catalog->io->ctrl_io.base; + + return dp_read(base + DP_MAINLINK_READY); +} + +/* panel related catalog functions */ +static int dp_catalog_panel_timing_cfg(struct dp_catalog_panel *panel) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!panel) { + pr_err("invalid input\n"); + goto end; + } + + dp_catalog_get_priv(panel); + base = catalog->io->ctrl_io.base; + + dp_write(base + DP_TOTAL_HOR_VER, panel->total); + dp_write(base + DP_START_HOR_VER_FROM_SYNC, panel->sync_start); + dp_write(base + DP_HSYNC_VSYNC_WIDTH_POLARITY, panel->width_blanking); + dp_write(base + DP_ACTIVE_HOR_VER, panel->dp_active); +end: + return 0; +} + +static void dp_catalog_audio_init(struct dp_catalog_audio *audio) +{ + struct dp_catalog_private *catalog; + static u32 sdp_map[][DP_AUDIO_SDP_HEADER_MAX] = { + { + MMSS_DP_AUDIO_STREAM_0, + MMSS_DP_AUDIO_STREAM_1, + MMSS_DP_AUDIO_STREAM_1, + }, + { + MMSS_DP_AUDIO_TIMESTAMP_0, + MMSS_DP_AUDIO_TIMESTAMP_1, + MMSS_DP_AUDIO_TIMESTAMP_1, + }, + { + MMSS_DP_AUDIO_INFOFRAME_0, + MMSS_DP_AUDIO_INFOFRAME_1, + MMSS_DP_AUDIO_INFOFRAME_1, + }, + { + MMSS_DP_AUDIO_COPYMANAGEMENT_0, + MMSS_DP_AUDIO_COPYMANAGEMENT_1, + MMSS_DP_AUDIO_COPYMANAGEMENT_1, + }, + { + MMSS_DP_AUDIO_ISRC_0, + MMSS_DP_AUDIO_ISRC_1, + MMSS_DP_AUDIO_ISRC_1, + }, + }; + + if (!audio) + return; + + dp_catalog_get_priv(audio); + + catalog->audio_map = sdp_map; +} + +static void dp_catalog_audio_config_sdp(struct dp_catalog_audio *audio) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + u32 sdp_cfg = 0; + u32 sdp_cfg2 = 0; + + if (!audio) + return; + + dp_catalog_get_priv(audio); + base = catalog->io->ctrl_io.base; + + /* AUDIO_TIMESTAMP_SDP_EN */ + sdp_cfg |= BIT(1); + /* AUDIO_STREAM_SDP_EN */ + sdp_cfg |= BIT(2); + /* AUDIO_COPY_MANAGEMENT_SDP_EN */ + sdp_cfg |= BIT(5); + /* AUDIO_ISRC_SDP_EN */ + sdp_cfg |= BIT(6); + /* AUDIO_INFOFRAME_SDP_EN */ + sdp_cfg |= BIT(20); + + pr_debug("sdp_cfg = 0x%x\n", sdp_cfg); + dp_write(base + MMSS_DP_SDP_CFG, sdp_cfg); + + sdp_cfg2 = dp_read(base + MMSS_DP_SDP_CFG2); + /* IFRM_REGSRC -> Do not use reg values */ + sdp_cfg2 &= ~BIT(0); + /* AUDIO_STREAM_HB3_REGSRC-> Do not use reg values */ + sdp_cfg2 &= ~BIT(1); + + pr_debug("sdp_cfg2 = 0x%x\n", sdp_cfg2); + dp_write(base + MMSS_DP_SDP_CFG2, sdp_cfg2); +} + +static void dp_catalog_audio_get_header(struct dp_catalog_audio *audio) +{ + struct dp_catalog_private *catalog; + u32 (*sdp_map)[DP_AUDIO_SDP_HEADER_MAX]; + void __iomem *base; + enum dp_catalog_audio_sdp_type sdp; + enum dp_catalog_audio_header_type header; + + if (!audio) + return; + + dp_catalog_get_priv(audio); + + base = catalog->io->ctrl_io.base; + sdp_map = catalog->audio_map; + sdp = audio->sdp_type; + header = audio->sdp_header; + + audio->data = dp_read(base + sdp_map[sdp][header]); +} + +static void dp_catalog_audio_set_header(struct dp_catalog_audio *audio) +{ + struct dp_catalog_private *catalog; + u32 (*sdp_map)[DP_AUDIO_SDP_HEADER_MAX]; + void __iomem *base; + enum dp_catalog_audio_sdp_type sdp; + enum dp_catalog_audio_header_type header; + u32 data; + + if (!audio) + return; + + dp_catalog_get_priv(audio); + + base = catalog->io->ctrl_io.base; + sdp_map = catalog->audio_map; + sdp = audio->sdp_type; + header = audio->sdp_header; + data = audio->data; + + dp_write(base + sdp_map[sdp][header], data); +} + +static void dp_catalog_audio_config_acr(struct dp_catalog_audio *audio) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + u32 acr_ctrl, select; + + dp_catalog_get_priv(audio); + + select = audio->data; + base = catalog->io->ctrl_io.base; + + acr_ctrl = select << 4 | BIT(31) | BIT(8) | BIT(14); + + pr_debug("select = 0x%x, acr_ctrl = 0x%x\n", select, acr_ctrl); + + dp_write(base + MMSS_DP_AUDIO_ACR_CTRL, acr_ctrl); +} + +static void dp_catalog_audio_safe_to_exit_level(struct dp_catalog_audio *audio) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + u32 mainlink_levels, safe_to_exit_level; + + dp_catalog_get_priv(audio); + + base = catalog->io->ctrl_io.base; + safe_to_exit_level = audio->data; + + mainlink_levels = dp_read(base + DP_MAINLINK_LEVELS); + mainlink_levels &= 0xFE0; + mainlink_levels |= safe_to_exit_level; + + pr_debug("mainlink_level = 0x%x, safe_to_exit_level = 0x%x\n", + mainlink_levels, safe_to_exit_level); + + dp_write(base + DP_MAINLINK_LEVELS, mainlink_levels); +} + +static void dp_catalog_audio_enable(struct dp_catalog_audio *audio) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + bool enable; + u32 audio_ctrl; + + dp_catalog_get_priv(audio); + + base = catalog->io->ctrl_io.base; + enable = !!audio->data; + + audio_ctrl = dp_read(base + MMSS_DP_AUDIO_CFG); + + if (enable) + audio_ctrl |= BIT(0); + else + audio_ctrl &= ~BIT(0); + + pr_debug("dp_audio_cfg = 0x%x\n", audio_ctrl); + dp_write(base + MMSS_DP_AUDIO_CFG, audio_ctrl); + + /* make sure audio engine is disabled */ + wmb(); +} + +struct dp_catalog *dp_catalog_get(struct device *dev, struct dp_io *io) +{ + int rc = 0; + struct dp_catalog *dp_catalog; + struct dp_catalog_private *catalog; + struct dp_catalog_aux aux = { + .read_data = dp_catalog_aux_read_data, + .write_data = dp_catalog_aux_write_data, + .write_trans = dp_catalog_aux_write_trans, + .clear_trans = dp_catalog_aux_clear_trans, + .reset = dp_catalog_aux_reset, + .update_aux_cfg = dp_catalog_aux_update_cfg, + .enable = dp_catalog_aux_enable, + .setup = dp_catalog_aux_setup, + .get_irq = dp_catalog_aux_get_irq, + }; + struct dp_catalog_ctrl ctrl = { + .state_ctrl = dp_catalog_ctrl_state_ctrl, + .config_ctrl = dp_catalog_ctrl_config_ctrl, + .lane_mapping = dp_catalog_ctrl_lane_mapping, + .mainlink_ctrl = dp_catalog_ctrl_mainlink_ctrl, + .config_misc = dp_catalog_ctrl_config_misc, + .config_msa = dp_catalog_ctrl_config_msa, + .set_pattern = dp_catalog_ctrl_set_pattern, + .reset = dp_catalog_ctrl_reset, + .usb_reset = dp_catalog_ctrl_usb_reset, + .mainlink_ready = dp_catalog_ctrl_mainlink_ready, + .enable_irq = dp_catalog_ctrl_enable_irq, + .hpd_config = dp_catalog_ctrl_hpd_config, + .phy_reset = dp_catalog_ctrl_phy_reset, + .phy_lane_cfg = dp_catalog_ctrl_phy_lane_cfg, + .update_vx_px = dp_catalog_ctrl_update_vx_px, + .get_interrupt = dp_catalog_ctrl_get_interrupt, + .config_hdr = dp_catalog_ctrl_config_hdr, + .update_transfer_unit = dp_catalog_ctrl_update_transfer_unit, + .read_hdcp_status = dp_catalog_ctrl_read_hdcp_status, + .send_phy_pattern = dp_catalog_ctrl_send_phy_pattern, + .read_phy_pattern = dp_catalog_ctrl_read_phy_pattern, + }; + struct dp_catalog_audio audio = { + .init = dp_catalog_audio_init, + .config_acr = dp_catalog_audio_config_acr, + .enable = dp_catalog_audio_enable, + .config_sdp = dp_catalog_audio_config_sdp, + .set_header = dp_catalog_audio_set_header, + .get_header = dp_catalog_audio_get_header, + .safe_to_exit_level = dp_catalog_audio_safe_to_exit_level, + }; + struct dp_catalog_panel panel = { + .timing_cfg = dp_catalog_panel_timing_cfg, + }; + + if (!io) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + catalog = devm_kzalloc(dev, sizeof(*catalog), GFP_KERNEL); + if (!catalog) { + rc = -ENOMEM; + goto error; + } + + catalog->dev = dev; + catalog->io = io; + + dp_catalog = &catalog->dp_catalog; + + dp_catalog->aux = aux; + dp_catalog->ctrl = ctrl; + dp_catalog->audio = audio; + dp_catalog->panel = panel; + + return dp_catalog; +error: + return ERR_PTR(rc); +} + +void dp_catalog_put(struct dp_catalog *dp_catalog) +{ + struct dp_catalog_private *catalog; + + if (!dp_catalog) + return; + + catalog = container_of(dp_catalog, struct dp_catalog_private, + dp_catalog); + + devm_kfree(catalog->dev, catalog); +} diff --git a/drivers/gpu/drm/msm/dp/dp_catalog.h b/drivers/gpu/drm/msm/dp/dp_catalog.h new file mode 100644 index 000000000000..c1b7a7ef889f --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_catalog.h @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _DP_CATALOG_H_ +#define _DP_CATALOG_H_ + +#include "dp_parser.h" + +/* interrupts */ +#define DP_INTR_HPD BIT(0) +#define DP_INTR_AUX_I2C_DONE BIT(3) +#define DP_INTR_WRONG_ADDR BIT(6) +#define DP_INTR_TIMEOUT BIT(9) +#define DP_INTR_NACK_DEFER BIT(12) +#define DP_INTR_WRONG_DATA_CNT BIT(15) +#define DP_INTR_I2C_NACK BIT(18) +#define DP_INTR_I2C_DEFER BIT(21) +#define DP_INTR_PLL_UNLOCKED BIT(24) +#define DP_INTR_AUX_ERROR BIT(27) + +#define DP_INTR_READY_FOR_VIDEO BIT(0) +#define DP_INTR_IDLE_PATTERN_SENT BIT(3) +#define DP_INTR_FRAME_END BIT(6) +#define DP_INTR_CRC_UPDATED BIT(9) + +#define HDR_PRIMARIES_COUNT 3 + +struct dp_catalog_hdr_data { + u32 vsc_hdr_byte0; + u32 vsc_hdr_byte1; + u32 vsc_hdr_byte2; + u32 vsc_hdr_byte3; + u32 pkt_payload; + + u32 bpc; + + u32 version; + u32 length; + u32 eotf; + u32 descriptor_id; + + u32 display_primaries_x[HDR_PRIMARIES_COUNT]; + u32 display_primaries_y[HDR_PRIMARIES_COUNT]; + u32 white_point_x; + u32 white_point_y; + u32 max_luminance; + u32 min_luminance; + u32 max_content_light_level; + u32 max_average_light_level; +}; + +struct dp_catalog_aux { + u32 data; + u32 isr; + + u32 (*read_data)(struct dp_catalog_aux *aux); + int (*write_data)(struct dp_catalog_aux *aux); + int (*write_trans)(struct dp_catalog_aux *aux); + int (*clear_trans)(struct dp_catalog_aux *aux, bool read); + void (*reset)(struct dp_catalog_aux *aux); + void (*enable)(struct dp_catalog_aux *aux, bool enable); + void (*update_aux_cfg)(struct dp_catalog_aux *aux, + struct dp_aux_cfg *cfg, enum dp_phy_aux_config_type type); + void (*setup)(struct dp_catalog_aux *aux, + struct dp_aux_cfg *aux_cfg); + void (*get_irq)(struct dp_catalog_aux *aux, bool cmd_busy); +}; + +struct dp_catalog_ctrl { + u32 dp_tu; + u32 valid_boundary; + u32 valid_boundary2; + u32 isr; + struct dp_catalog_hdr_data hdr_data; + + void (*state_ctrl)(struct dp_catalog_ctrl *ctrl, u32 state); + void (*config_ctrl)(struct dp_catalog_ctrl *ctrl, u32 config); + void (*lane_mapping)(struct dp_catalog_ctrl *ctrl); + void (*mainlink_ctrl)(struct dp_catalog_ctrl *ctrl, bool enable); + void (*config_misc)(struct dp_catalog_ctrl *ctrl, u32 cc, u32 tb); + void (*config_msa)(struct dp_catalog_ctrl *ctrl, u32 rate, + u32 stream_rate_khz, bool fixed_nvid); + void (*set_pattern)(struct dp_catalog_ctrl *ctrl, u32 pattern); + void (*reset)(struct dp_catalog_ctrl *ctrl); + void (*usb_reset)(struct dp_catalog_ctrl *ctrl, bool flip); + bool (*mainlink_ready)(struct dp_catalog_ctrl *ctrl); + void (*enable_irq)(struct dp_catalog_ctrl *ctrl, bool enable); + void (*hpd_config)(struct dp_catalog_ctrl *ctrl, bool enable); + void (*phy_reset)(struct dp_catalog_ctrl *ctrl); + void (*phy_lane_cfg)(struct dp_catalog_ctrl *ctrl, bool flipped, + u8 lane_cnt); + void (*update_vx_px)(struct dp_catalog_ctrl *ctrl, u8 v_level, + u8 p_level); + void (*get_interrupt)(struct dp_catalog_ctrl *ctrl); + void (*config_hdr)(struct dp_catalog_ctrl *ctrl); + void (*update_transfer_unit)(struct dp_catalog_ctrl *ctrl); + u32 (*read_hdcp_status)(struct dp_catalog_ctrl *ctrl); + void (*send_phy_pattern)(struct dp_catalog_ctrl *ctrl, + u32 pattern); + u32 (*read_phy_pattern)(struct dp_catalog_ctrl *ctrl); +}; + +enum dp_catalog_audio_sdp_type { + DP_AUDIO_SDP_STREAM, + DP_AUDIO_SDP_TIMESTAMP, + DP_AUDIO_SDP_INFOFRAME, + DP_AUDIO_SDP_COPYMANAGEMENT, + DP_AUDIO_SDP_ISRC, + DP_AUDIO_SDP_MAX, +}; + +enum dp_catalog_audio_header_type { + DP_AUDIO_SDP_HEADER_1, + DP_AUDIO_SDP_HEADER_2, + DP_AUDIO_SDP_HEADER_3, + DP_AUDIO_SDP_HEADER_MAX, +}; + +struct dp_catalog_audio { + enum dp_catalog_audio_sdp_type sdp_type; + enum dp_catalog_audio_header_type sdp_header; + u32 data; + + void (*init)(struct dp_catalog_audio *audio); + void (*enable)(struct dp_catalog_audio *audio); + void (*config_acr)(struct dp_catalog_audio *audio); + void (*config_sdp)(struct dp_catalog_audio *audio); + void (*set_header)(struct dp_catalog_audio *audio); + void (*get_header)(struct dp_catalog_audio *audio); + void (*safe_to_exit_level)(struct dp_catalog_audio *audio); +}; + +struct dp_catalog_panel { + u32 total; + u32 sync_start; + u32 width_blanking; + u32 dp_active; + + int (*timing_cfg)(struct dp_catalog_panel *panel); +}; + +struct dp_catalog { + struct dp_catalog_aux aux; + struct dp_catalog_ctrl ctrl; + struct dp_catalog_audio audio; + struct dp_catalog_panel panel; +}; + +struct dp_catalog *dp_catalog_get(struct device *dev, struct dp_io *io); +void dp_catalog_put(struct dp_catalog *catalog); + +#endif /* _DP_CATALOG_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c new file mode 100644 index 000000000000..13ca6b24c1b0 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -0,0 +1,1474 @@ +/* + * Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include +#include +#include + +#include "dp_ctrl.h" + +#define DP_KHZ_TO_HZ 1000 + +#define DP_CTRL_INTR_READY_FOR_VIDEO BIT(0) +#define DP_CTRL_INTR_IDLE_PATTERN_SENT BIT(3) + +/* dp state ctrl */ +#define ST_TRAIN_PATTERN_1 BIT(0) +#define ST_TRAIN_PATTERN_2 BIT(1) +#define ST_TRAIN_PATTERN_3 BIT(2) +#define ST_TRAIN_PATTERN_4 BIT(3) +#define ST_SYMBOL_ERR_RATE_MEASUREMENT BIT(4) +#define ST_PRBS7 BIT(5) +#define ST_CUSTOM_80_BIT_PATTERN BIT(6) +#define ST_SEND_VIDEO BIT(7) +#define ST_PUSH_IDLE BIT(8) + +#define MR_LINK_TRAINING1 0x8 +#define MR_LINK_SYMBOL_ERM 0x80 +#define MR_LINK_PRBS7 0x100 +#define MR_LINK_CUSTOM80 0x200 + +struct dp_vc_tu_mapping_table { + u32 vic; + u8 lanes; + u8 lrate; /* DP_LINK_RATE -> 162(6), 270(10), 540(20), 810 (30) */ + u8 bpp; + u8 valid_boundary_link; + u16 delay_start_link; + bool boundary_moderation_en; + u8 valid_lower_boundary_link; + u8 upper_boundary_count; + u8 lower_boundary_count; + u8 tu_size_minus1; +}; + +struct dp_ctrl_private { + struct dp_ctrl dp_ctrl; + + struct device *dev; + struct dp_aux *aux; + struct dp_panel *panel; + struct dp_link *link; + struct dp_power *power; + struct dp_parser *parser; + struct dp_catalog_ctrl *catalog; + + struct completion idle_comp; + struct completion video_comp; + + bool orientation; + atomic_t aborted; + + u32 pixel_rate; + u32 vic; +}; + +enum notification_status { + NOTIFY_UNKNOWN, + NOTIFY_CONNECT, + NOTIFY_DISCONNECT, + NOTIFY_CONNECT_IRQ_HPD, + NOTIFY_DISCONNECT_IRQ_HPD, +}; + +static void dp_ctrl_idle_patterns_sent(struct dp_ctrl_private *ctrl) +{ + pr_debug("idle_patterns_sent\n"); + complete(&ctrl->idle_comp); +} + +static void dp_ctrl_video_ready(struct dp_ctrl_private *ctrl) +{ + pr_debug("dp_video_ready\n"); + complete(&ctrl->video_comp); +} + +static void dp_ctrl_abort(struct dp_ctrl *dp_ctrl) +{ + struct dp_ctrl_private *ctrl; + + if (!dp_ctrl) { + pr_err("Invalid input data\n"); + return; + } + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + + atomic_set(&ctrl->aborted, 1); +} + +static void dp_ctrl_state_ctrl(struct dp_ctrl_private *ctrl, u32 state) +{ + ctrl->catalog->state_ctrl(ctrl->catalog, state); +} + +static void dp_ctrl_push_idle(struct dp_ctrl *dp_ctrl) +{ + int const idle_pattern_completion_timeout_ms = 3 * HZ / 100; + struct dp_ctrl_private *ctrl; + + if (!dp_ctrl) { + pr_err("Invalid input data\n"); + return; + } + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + + reinit_completion(&ctrl->idle_comp); + dp_ctrl_state_ctrl(ctrl, ST_PUSH_IDLE); + + if (!wait_for_completion_timeout(&ctrl->idle_comp, + idle_pattern_completion_timeout_ms)) + pr_warn("PUSH_IDLE pattern timedout\n"); + + pr_debug("mainlink off done\n"); +} + +static void dp_ctrl_config_ctrl(struct dp_ctrl_private *ctrl) +{ + u32 config = 0, tbd; + u8 *dpcd = ctrl->panel->dpcd; + + config |= (2 << 13); /* Default-> LSCLK DIV: 1/4 LCLK */ + config |= (0 << 11); /* RGB */ + + /* Scrambler reset enable */ + if (dpcd[DP_EDP_CONFIGURATION_CAP] & DP_ALTERNATE_SCRAMBLER_RESET_CAP) + config |= (1 << 10); + + tbd = ctrl->link->get_test_bits_depth(ctrl->link, + ctrl->panel->pinfo.bpp); + + if (tbd == DP_TEST_BIT_DEPTH_UNKNOWN) + tbd = DP_TEST_BIT_DEPTH_8; + + config |= tbd << 8; + + /* Num of Lanes */ + config |= ((ctrl->link->link_params.lane_count - 1) << 4); + + if (drm_dp_enhanced_frame_cap(dpcd)) + config |= 0x40; + + config |= 0x04; /* progressive video */ + + config |= 0x03; /* sycn clock & static Mvid */ + + ctrl->catalog->config_ctrl(ctrl->catalog, config); +} + +/** + * dp_ctrl_configure_source_params() - configures DP transmitter source params + * @ctrl: Display Port Driver data + * + * Configures the DP transmitter source params including details such as lane + * configuration, output format and sink/panel timing information. + */ +static void dp_ctrl_configure_source_params(struct dp_ctrl_private *ctrl) +{ + u32 cc, tb; + + ctrl->catalog->lane_mapping(ctrl->catalog); + ctrl->catalog->mainlink_ctrl(ctrl->catalog, true); + + dp_ctrl_config_ctrl(ctrl); + + tb = ctrl->link->get_test_bits_depth(ctrl->link, + ctrl->panel->pinfo.bpp); + cc = ctrl->link->get_colorimetry_config(ctrl->link); + ctrl->catalog->config_misc(ctrl->catalog, cc, tb); + ctrl->panel->timing_cfg(ctrl->panel); +} + +static void dp_ctrl_get_extra_req_bytes(u64 result_valid, + int valid_bdary_link, + u64 value1, u64 value2, + bool *negative, u64 *result, + u64 compare) +{ + *negative = false; + if (result_valid >= compare) { + if (valid_bdary_link + >= compare) + *result = value1 + value2; + else { + if (value1 < value2) + *negative = true; + *result = (value1 >= value2) ? + (value1 - value2) : (value2 - value1); + } + } else { + if (valid_bdary_link + >= compare) { + if (value1 >= value2) + *negative = true; + *result = (value1 >= value2) ? + (value1 - value2) : (value2 - value1); + } else { + *result = value1 + value2; + *negative = true; + } + } +} + +static u64 roundup_u64(u64 x, u64 y) +{ + x += (y - 1); + return (div64_ul(x, y) * y); +} + +static u64 rounddown_u64(u64 x, u64 y) +{ + u64 rem; + + div64_u64_rem(x, y, &rem); + return (x - rem); +} + +static void dp_ctrl_calc_tu_parameters(struct dp_ctrl_private *ctrl, + struct dp_vc_tu_mapping_table *tu_table) +{ + u32 const multiplier = 1000000; + u64 pclk, lclk; + u8 bpp, ln_cnt; + int run_idx = 0; + u32 lwidth, h_blank; + u32 fifo_empty = 0; + u32 ratio_scale = 1001; + u64 temp, ratio, original_ratio; + u64 temp2, reminder; + u64 temp3, temp4, result = 0; + + u64 err = multiplier; + u64 n_err = 0, n_n_err = 0; + bool n_err_neg, nn_err_neg; + u8 hblank_margin = 16; + + u8 tu_size, tu_size_desired = 0, tu_size_minus1; + int valid_boundary_link; + u64 resulting_valid; + u64 total_valid; + u64 effective_valid; + u64 effective_valid_recorded; + int n_tus; + int n_tus_per_lane; + int paired_tus; + int remainder_tus; + int remainder_tus_upper, remainder_tus_lower; + int extra_bytes; + int filler_size; + int delay_start_link; + int boundary_moderation_en = 0; + int upper_bdry_cnt = 0; + int lower_bdry_cnt = 0; + int i_upper_bdry_cnt = 0; + int i_lower_bdry_cnt = 0; + int valid_lower_boundary_link = 0; + int even_distribution_bf = 0; + int even_distribution_legacy = 0; + int even_distribution = 0; + int min_hblank = 0; + int extra_pclk_cycles; + u8 extra_pclk_cycle_delay = 4; + int extra_pclk_cycles_in_link_clk; + u64 ratio_by_tu; + u64 average_valid2; + u64 extra_buffer_margin; + int new_valid_boundary_link; + + u64 resulting_valid_tmp; + u64 ratio_by_tu_tmp; + int n_tus_tmp; + int extra_pclk_cycles_tmp; + int extra_pclk_cycles_in_lclk_tmp; + int extra_req_bytes_new_tmp; + int filler_size_tmp; + int lower_filler_size_tmp; + int delay_start_link_tmp; + int min_hblank_tmp = 0; + bool extra_req_bytes_is_neg = false; + struct dp_panel_info *pinfo = &ctrl->panel->pinfo; + + u8 dp_brute_force = 1; + u64 brute_force_threshold = 10; + u64 diff_abs; + + ln_cnt = ctrl->link->link_params.lane_count; + + bpp = pinfo->bpp; + lwidth = pinfo->h_active; + h_blank = pinfo->h_back_porch + pinfo->h_front_porch + + pinfo->h_sync_width; + pclk = pinfo->pixel_clk_khz * 1000; + + boundary_moderation_en = 0; + upper_bdry_cnt = 0; + lower_bdry_cnt = 0; + i_upper_bdry_cnt = 0; + i_lower_bdry_cnt = 0; + valid_lower_boundary_link = 0; + even_distribution_bf = 0; + even_distribution_legacy = 0; + even_distribution = 0; + min_hblank = 0; + + lclk = drm_dp_bw_code_to_link_rate( + ctrl->link->link_params.bw_code) * DP_KHZ_TO_HZ; + + pr_debug("pclk=%lld, active_width=%d, h_blank=%d\n", + pclk, lwidth, h_blank); + pr_debug("lclk = %lld, ln_cnt = %d\n", lclk, ln_cnt); + ratio = div64_u64_rem(pclk * bpp * multiplier, + 8 * ln_cnt * lclk, &reminder); + ratio = div64_u64((pclk * bpp * multiplier), (8 * ln_cnt * lclk)); + original_ratio = ratio; + + extra_buffer_margin = roundup_u64(div64_u64(extra_pclk_cycle_delay + * lclk * multiplier, pclk), multiplier); + extra_buffer_margin = div64_u64(extra_buffer_margin, multiplier); + + /* To deal with cases where lines are not distributable */ + if (((lwidth % ln_cnt) != 0) && ratio < multiplier) { + ratio = ratio * ratio_scale; + ratio = ratio < (1000 * multiplier) + ? ratio : (1000 * multiplier); + } + pr_debug("ratio = %lld\n", ratio); + + for (tu_size = 32; tu_size <= 64; tu_size++) { + temp = ratio * tu_size; + temp2 = ((temp / multiplier) + 1) * multiplier; + n_err = roundup_u64(temp, multiplier) - temp; + + if (n_err < err) { + err = n_err; + tu_size_desired = tu_size; + } + } + pr_debug("Info: tu_size_desired = %d\n", tu_size_desired); + + tu_size_minus1 = tu_size_desired - 1; + + valid_boundary_link = roundup_u64(ratio * tu_size_desired, multiplier); + valid_boundary_link /= multiplier; + n_tus = rounddown((lwidth * bpp * multiplier) + / (8 * valid_boundary_link), multiplier) / multiplier; + even_distribution_legacy = n_tus % ln_cnt == 0 ? 1 : 0; + pr_debug("Info: n_symbol_per_tu=%d, number_of_tus=%d\n", + valid_boundary_link, n_tus); + + extra_bytes = roundup_u64((n_tus + 1) + * ((valid_boundary_link * multiplier) + - (original_ratio * tu_size_desired)), multiplier); + extra_bytes /= multiplier; + extra_pclk_cycles = roundup(extra_bytes * 8 * multiplier / bpp, + multiplier); + extra_pclk_cycles /= multiplier; + extra_pclk_cycles_in_link_clk = roundup_u64(div64_u64(extra_pclk_cycles + * lclk * multiplier, pclk), multiplier); + extra_pclk_cycles_in_link_clk /= multiplier; + filler_size = roundup_u64((tu_size_desired - valid_boundary_link) + * multiplier, multiplier); + filler_size /= multiplier; + ratio_by_tu = div64_u64(ratio * tu_size_desired, multiplier); + + pr_debug("extra_pclk_cycles_in_link_clk=%d, extra_bytes=%d\n", + extra_pclk_cycles_in_link_clk, extra_bytes); + pr_debug("extra_pclk_cycles_in_link_clk=%d\n", + extra_pclk_cycles_in_link_clk); + pr_debug("filler_size=%d, extra_buffer_margin=%lld\n", + filler_size, extra_buffer_margin); + + delay_start_link = ((extra_bytes > extra_pclk_cycles_in_link_clk) + ? extra_bytes + : extra_pclk_cycles_in_link_clk) + + filler_size + extra_buffer_margin; + resulting_valid = valid_boundary_link; + pr_debug("Info: delay_start_link=%d, filler_size=%d\n", + delay_start_link, filler_size); + pr_debug("valid_boundary_link=%d ratio_by_tu=%lld\n", + valid_boundary_link, ratio_by_tu); + + diff_abs = (resulting_valid >= ratio_by_tu) + ? (resulting_valid - ratio_by_tu) + : (ratio_by_tu - resulting_valid); + + if (err != 0 && ((diff_abs > brute_force_threshold) + || (even_distribution_legacy == 0) + || (dp_brute_force == 1))) { + err = multiplier; + for (tu_size = 32; tu_size <= 64; tu_size++) { + for (i_upper_bdry_cnt = 1; i_upper_bdry_cnt <= 15; + i_upper_bdry_cnt++) { + for (i_lower_bdry_cnt = 1; + i_lower_bdry_cnt <= 15; + i_lower_bdry_cnt++) { + new_valid_boundary_link = + roundup_u64(ratio + * tu_size, multiplier); + average_valid2 = (i_upper_bdry_cnt + * new_valid_boundary_link + + i_lower_bdry_cnt + * (new_valid_boundary_link + - multiplier)) + / (i_upper_bdry_cnt + + i_lower_bdry_cnt); + n_tus = rounddown_u64(div64_u64(lwidth + * multiplier * multiplier + * (bpp / 8), average_valid2), + multiplier); + n_tus /= multiplier; + n_tus_per_lane + = rounddown(n_tus + * multiplier + / ln_cnt, multiplier); + n_tus_per_lane /= multiplier; + paired_tus = + rounddown((n_tus_per_lane) + * multiplier + / (i_upper_bdry_cnt + + i_lower_bdry_cnt), + multiplier); + paired_tus /= multiplier; + remainder_tus = n_tus_per_lane + - paired_tus + * (i_upper_bdry_cnt + + i_lower_bdry_cnt); + if ((remainder_tus + - i_upper_bdry_cnt) > 0) { + remainder_tus_upper + = i_upper_bdry_cnt; + remainder_tus_lower = + remainder_tus + - i_upper_bdry_cnt; + } else { + remainder_tus_upper + = remainder_tus; + remainder_tus_lower = 0; + } + total_valid = paired_tus + * (i_upper_bdry_cnt + * new_valid_boundary_link + + i_lower_bdry_cnt + * (new_valid_boundary_link + - multiplier)) + + (remainder_tus_upper + * new_valid_boundary_link) + + (remainder_tus_lower + * (new_valid_boundary_link + - multiplier)); + n_err_neg = nn_err_neg = false; + effective_valid + = div_u64(total_valid, + n_tus_per_lane); + n_n_err = (effective_valid + >= (ratio * tu_size)) + ? (effective_valid + - (ratio * tu_size)) + : ((ratio * tu_size) + - effective_valid); + if (effective_valid < (ratio * tu_size)) + nn_err_neg = true; + n_err = (average_valid2 + >= (ratio * tu_size)) + ? (average_valid2 + - (ratio * tu_size)) + : ((ratio * tu_size) + - average_valid2); + if (average_valid2 < (ratio * tu_size)) + n_err_neg = true; + even_distribution = + n_tus % ln_cnt == 0 ? 1 : 0; + diff_abs = + resulting_valid >= ratio_by_tu + ? (resulting_valid + - ratio_by_tu) + : (ratio_by_tu + - resulting_valid); + + resulting_valid_tmp = div64_u64( + (i_upper_bdry_cnt + * new_valid_boundary_link + + i_lower_bdry_cnt + * (new_valid_boundary_link + - multiplier)), + (i_upper_bdry_cnt + + i_lower_bdry_cnt)); + ratio_by_tu_tmp = + original_ratio * tu_size; + ratio_by_tu_tmp /= multiplier; + n_tus_tmp = rounddown_u64( + div64_u64(lwidth + * multiplier * multiplier + * bpp / 8, + resulting_valid_tmp), + multiplier); + n_tus_tmp /= multiplier; + + temp3 = (resulting_valid_tmp + >= (original_ratio * tu_size)) + ? (resulting_valid_tmp + - original_ratio * tu_size) + : (original_ratio * tu_size) + - resulting_valid_tmp; + temp3 = (n_tus_tmp + 1) * temp3; + temp4 = (new_valid_boundary_link + >= (original_ratio * tu_size)) + ? (new_valid_boundary_link + - original_ratio + * tu_size) + : (original_ratio * tu_size) + - new_valid_boundary_link; + temp4 = (i_upper_bdry_cnt + * ln_cnt * temp4); + + temp3 = roundup_u64(temp3, multiplier); + temp4 = roundup_u64(temp4, multiplier); + dp_ctrl_get_extra_req_bytes + (resulting_valid_tmp, + new_valid_boundary_link, + temp3, temp4, + &extra_req_bytes_is_neg, + &result, + (original_ratio * tu_size)); + extra_req_bytes_new_tmp + = div64_ul(result, multiplier); + if ((extra_req_bytes_is_neg) + && (extra_req_bytes_new_tmp + > 1)) + extra_req_bytes_new_tmp + = extra_req_bytes_new_tmp - 1; + if (extra_req_bytes_new_tmp == 0) + extra_req_bytes_new_tmp = 1; + extra_pclk_cycles_tmp = + (u64)(extra_req_bytes_new_tmp + * 8 * multiplier) / bpp; + extra_pclk_cycles_tmp /= multiplier; + + if (extra_pclk_cycles_tmp <= 0) + extra_pclk_cycles_tmp = 1; + extra_pclk_cycles_in_lclk_tmp = + roundup_u64(div64_u64( + extra_pclk_cycles_tmp + * lclk * multiplier, + pclk), multiplier); + extra_pclk_cycles_in_lclk_tmp + /= multiplier; + filler_size_tmp = roundup_u64( + (tu_size * multiplier * + new_valid_boundary_link), + multiplier); + filler_size_tmp /= multiplier; + lower_filler_size_tmp = + filler_size_tmp + 1; + if (extra_req_bytes_is_neg) + temp3 = (extra_req_bytes_new_tmp + > extra_pclk_cycles_in_lclk_tmp + ? extra_pclk_cycles_in_lclk_tmp + : extra_req_bytes_new_tmp); + else + temp3 = (extra_req_bytes_new_tmp + > extra_pclk_cycles_in_lclk_tmp + ? extra_req_bytes_new_tmp : + extra_pclk_cycles_in_lclk_tmp); + + temp4 = lower_filler_size_tmp + + extra_buffer_margin; + if (extra_req_bytes_is_neg) + delay_start_link_tmp + = (temp3 >= temp4) + ? (temp3 - temp4) + : (temp4 - temp3); + else + delay_start_link_tmp + = temp3 + temp4; + + min_hblank_tmp = (int)div64_u64( + roundup_u64( + div64_u64(delay_start_link_tmp + * pclk * multiplier, lclk), + multiplier), multiplier) + + hblank_margin; + + if (((even_distribution == 1) + || ((even_distribution_bf == 0) + && (even_distribution_legacy + == 0))) + && !n_err_neg && !nn_err_neg + && n_n_err < err + && (n_n_err < diff_abs + || (dp_brute_force == 1)) + && (new_valid_boundary_link + - 1) > 0 + && (h_blank >= + (u32)min_hblank_tmp)) { + upper_bdry_cnt = + i_upper_bdry_cnt; + lower_bdry_cnt = + i_lower_bdry_cnt; + err = n_n_err; + boundary_moderation_en = 1; + tu_size_desired = tu_size; + valid_boundary_link = + new_valid_boundary_link; + effective_valid_recorded + = effective_valid; + delay_start_link + = delay_start_link_tmp; + filler_size = filler_size_tmp; + min_hblank = min_hblank_tmp; + n_tus = n_tus_tmp; + even_distribution_bf = 1; + + pr_debug("upper_bdry_cnt=%d, lower_boundary_cnt=%d, err=%lld, tu_size_desired=%d, valid_boundary_link=%d, effective_valid=%lld\n", + upper_bdry_cnt, + lower_bdry_cnt, err, + tu_size_desired, + valid_boundary_link, + effective_valid); + } + } + } + } + + if (boundary_moderation_en == 1) { + resulting_valid = (u64)(upper_bdry_cnt + *valid_boundary_link + lower_bdry_cnt + * (valid_boundary_link - 1)) + / (upper_bdry_cnt + lower_bdry_cnt); + ratio_by_tu = original_ratio * tu_size_desired; + valid_lower_boundary_link = + (valid_boundary_link / multiplier) - 1; + + tu_size_minus1 = tu_size_desired - 1; + even_distribution_bf = 1; + valid_boundary_link /= multiplier; + pr_debug("Info: Boundary_moderation enabled\n"); + } + } + + min_hblank = ((int) roundup_u64(div64_u64(delay_start_link * pclk + * multiplier, lclk), multiplier)) + / multiplier + hblank_margin; + if (h_blank < (u32)min_hblank) { + pr_debug(" WARNING: run_idx=%d Programmed h_blank %d is smaller than the min_hblank %d supported.\n", + run_idx, h_blank, min_hblank); + } + + if (fifo_empty) { + tu_size_minus1 = 31; + valid_boundary_link = 32; + delay_start_link = 0; + boundary_moderation_en = 0; + } + + pr_debug("tu_size_minus1=%d valid_boundary_link=%d delay_start_link=%d boundary_moderation_en=%d\n upper_boundary_cnt=%d lower_boundary_cnt=%d valid_lower_boundary_link=%d min_hblank=%d\n", + tu_size_minus1, valid_boundary_link, delay_start_link, + boundary_moderation_en, upper_bdry_cnt, lower_bdry_cnt, + valid_lower_boundary_link, min_hblank); + + tu_table->valid_boundary_link = valid_boundary_link; + tu_table->delay_start_link = delay_start_link; + tu_table->boundary_moderation_en = boundary_moderation_en; + tu_table->valid_lower_boundary_link = valid_lower_boundary_link; + tu_table->upper_boundary_count = upper_bdry_cnt; + tu_table->lower_boundary_count = lower_bdry_cnt; + tu_table->tu_size_minus1 = tu_size_minus1; +} + +static void dp_ctrl_setup_tr_unit(struct dp_ctrl_private *ctrl) +{ + u32 dp_tu = 0x0; + u32 valid_boundary = 0x0; + u32 valid_boundary2 = 0x0; + struct dp_vc_tu_mapping_table tu_calc_table; + + dp_ctrl_calc_tu_parameters(ctrl, &tu_calc_table); + + dp_tu |= tu_calc_table.tu_size_minus1; + valid_boundary |= tu_calc_table.valid_boundary_link; + valid_boundary |= (tu_calc_table.delay_start_link << 16); + + valid_boundary2 |= (tu_calc_table.valid_lower_boundary_link << 1); + valid_boundary2 |= (tu_calc_table.upper_boundary_count << 16); + valid_boundary2 |= (tu_calc_table.lower_boundary_count << 20); + + if (tu_calc_table.boundary_moderation_en) + valid_boundary2 |= BIT(0); + + pr_debug("dp_tu=0x%x, valid_boundary=0x%x, valid_boundary2=0x%x\n", + dp_tu, valid_boundary, valid_boundary2); + + ctrl->catalog->dp_tu = dp_tu; + ctrl->catalog->valid_boundary = valid_boundary; + ctrl->catalog->valid_boundary2 = valid_boundary2; + + ctrl->catalog->update_transfer_unit(ctrl->catalog); +} + +static int dp_ctrl_wait4video_ready(struct dp_ctrl_private *ctrl) +{ + int ret = 0; + + ret = wait_for_completion_timeout(&ctrl->video_comp, HZ / 2); + if (ret <= 0) { + pr_err("Link Train timedout\n"); + ret = -EINVAL; + } + + return ret; +} + +static int dp_ctrl_update_sink_vx_px(struct dp_ctrl_private *ctrl, + u32 voltage_level, u32 pre_emphasis_level) +{ + int i; + u8 buf[4]; + u32 max_level_reached = 0; + + if (voltage_level == DP_LINK_VOLTAGE_MAX) { + pr_debug("max. voltage swing level reached %d\n", + voltage_level); + max_level_reached |= BIT(2); + } + + if (pre_emphasis_level == DP_LINK_PRE_EMPHASIS_MAX) { + pr_debug("max. pre-emphasis level reached %d\n", + pre_emphasis_level); + max_level_reached |= BIT(5); + } + + pre_emphasis_level <<= 3; + + for (i = 0; i < 4; i++) + buf[i] = voltage_level | pre_emphasis_level | max_level_reached; + + pr_debug("sink: p|v=0x%x\n", voltage_level | pre_emphasis_level); + return drm_dp_dpcd_write(ctrl->aux->drm_aux, 0x103, buf, 4); +} + +static void dp_ctrl_update_vx_px(struct dp_ctrl_private *ctrl) +{ + struct dp_link *link = ctrl->link; + + ctrl->catalog->update_vx_px(ctrl->catalog, + link->phy_params.v_level, link->phy_params.p_level); + + dp_ctrl_update_sink_vx_px(ctrl, link->phy_params.v_level, + link->phy_params.p_level); +} + +static void dp_ctrl_train_pattern_set(struct dp_ctrl_private *ctrl, + u8 pattern) +{ + u8 buf[4]; + + pr_debug("sink: pattern=%x\n", pattern); + + buf[0] = pattern; + drm_dp_dpcd_write(ctrl->aux->drm_aux, DP_TRAINING_PATTERN_SET, buf, 1); +} + +static int dp_ctrl_read_link_status(struct dp_ctrl_private *ctrl, + u8 *link_status) +{ + int ret = 0, len; + u32 const offset = DP_LANE_ALIGN_STATUS_UPDATED - DP_LANE0_1_STATUS; + u32 link_status_read_max_retries = 100; + + while (--link_status_read_max_retries) { + len = drm_dp_dpcd_read_link_status(ctrl->aux->drm_aux, + link_status); + if (len != DP_LINK_STATUS_SIZE) { + pr_err("DP link status read failed, err: %d\n", len); + ret = len; + break; + } + + if (!(link_status[offset] & DP_LINK_STATUS_UPDATED)) + break; + } + + return ret; +} + +static int dp_ctrl_link_train_1(struct dp_ctrl_private *ctrl) +{ + int tries, old_v_level, ret = 0; + u8 link_status[DP_LINK_STATUS_SIZE]; + int const maximum_retries = 5; + + dp_ctrl_state_ctrl(ctrl, 0); + /* Make sure to clear the current pattern before starting a new one */ + wmb(); + + ctrl->catalog->set_pattern(ctrl->catalog, 0x01); + dp_ctrl_train_pattern_set(ctrl, DP_TRAINING_PATTERN_1 | + DP_LINK_SCRAMBLING_DISABLE); /* train_1 */ + dp_ctrl_update_vx_px(ctrl); + + tries = 0; + old_v_level = ctrl->link->phy_params.v_level; + while (1) { + drm_dp_link_train_clock_recovery_delay(ctrl->panel->dpcd); + + ret = dp_ctrl_read_link_status(ctrl, link_status); + if (ret) + break; + + if (drm_dp_clock_recovery_ok(link_status, + ctrl->link->link_params.lane_count)) { + break; + } + + if (ctrl->link->phy_params.v_level == DP_LINK_VOLTAGE_MAX) { + pr_err_ratelimited("max v_level reached\n"); + ret = -EAGAIN; + break; + } + + if (old_v_level == ctrl->link->phy_params.v_level) { + tries++; + if (tries >= maximum_retries) { + pr_err("max tries reached\n"); + ret = -ETIMEDOUT; + break; + } + } else { + tries = 0; + old_v_level = ctrl->link->phy_params.v_level; + } + + pr_debug("clock recovery not done, adjusting vx px\n"); + + ctrl->link->adjust_levels(ctrl->link, link_status); + dp_ctrl_update_vx_px(ctrl); + } + + return ret; +} + +static int dp_ctrl_link_rate_down_shift(struct dp_ctrl_private *ctrl) +{ + int ret = 0; + + if (!ctrl) + return -EINVAL; + + switch (ctrl->link->link_params.bw_code) { + case DP_LINK_BW_8_1: + ctrl->link->link_params.bw_code = DP_LINK_BW_5_4; + break; + case DP_LINK_BW_5_4: + ctrl->link->link_params.bw_code = DP_LINK_BW_2_7; + break; + case DP_LINK_BW_2_7: + case DP_LINK_BW_1_62: + default: + ctrl->link->link_params.bw_code = DP_LINK_BW_1_62; + break; + }; + + pr_debug("new bw code=0x%x\n", ctrl->link->link_params.bw_code); + + return ret; +} + +static void dp_ctrl_clear_training_pattern(struct dp_ctrl_private *ctrl) +{ + dp_ctrl_train_pattern_set(ctrl, 0); + drm_dp_link_train_channel_eq_delay(ctrl->panel->dpcd); +} + +static int dp_ctrl_link_training_2(struct dp_ctrl_private *ctrl) +{ + int tries = 0, ret = 0; + char pattern; + int const maximum_retries = 5; + u8 link_status[DP_LINK_STATUS_SIZE]; + + dp_ctrl_state_ctrl(ctrl, 0); + /* Make sure to clear the current pattern before starting a new one */ + wmb(); + + if (drm_dp_tps3_supported(ctrl->panel->dpcd)) + pattern = DP_TRAINING_PATTERN_3; + else + pattern = DP_TRAINING_PATTERN_2; + + dp_ctrl_update_vx_px(ctrl); + ctrl->catalog->set_pattern(ctrl->catalog, pattern); + dp_ctrl_train_pattern_set(ctrl, pattern | DP_RECOVERED_CLOCK_OUT_EN); + + do { + drm_dp_link_train_channel_eq_delay(ctrl->panel->dpcd); + + ret = dp_ctrl_read_link_status(ctrl, link_status); + if (ret) + break; + + if (drm_dp_channel_eq_ok(link_status, + ctrl->link->link_params.lane_count)) + break; + + if (tries > maximum_retries) { + ret = -ETIMEDOUT; + break; + } + tries++; + + ctrl->link->adjust_levels(ctrl->link, link_status); + dp_ctrl_update_vx_px(ctrl); + } while (1); + + return ret; +} + +static int dp_ctrl_link_train(struct dp_ctrl_private *ctrl) +{ + int ret = 0; + u8 encoding = 0x1; + struct drm_dp_link link_info = {0}; + + ctrl->link->phy_params.p_level = 0; + ctrl->link->phy_params.v_level = 0; + + dp_ctrl_config_ctrl(ctrl); + + link_info.num_lanes = ctrl->link->link_params.lane_count; + link_info.rate = drm_dp_bw_code_to_link_rate( + ctrl->link->link_params.bw_code); + link_info.capabilities = ctrl->panel->link_info.capabilities; + + drm_dp_link_configure(ctrl->aux->drm_aux, &link_info); + drm_dp_dpcd_write(ctrl->aux->drm_aux, DP_MAIN_LINK_CHANNEL_CODING_SET, + &encoding, 1); + + ret = dp_ctrl_link_train_1(ctrl); + if (ret) { + pr_err("link training #1 failed\n"); + goto end; + } + + /* print success info as this is a result of user initiated action */ + pr_info("link training #1 successful\n"); + + ret = dp_ctrl_link_training_2(ctrl); + if (ret) { + pr_err("link training #2 failed\n"); + goto end; + } + + /* print success info as this is a result of user initiated action */ + pr_debug("link training #2 successful\n"); + +end: + dp_ctrl_state_ctrl(ctrl, 0); + /* Make sure to clear the current pattern before starting a new one */ + wmb(); + + dp_ctrl_clear_training_pattern(ctrl); + return ret; +} + +static int dp_ctrl_setup_main_link(struct dp_ctrl_private *ctrl, bool train) +{ + bool mainlink_ready = false; + int ret = 0; + + ctrl->catalog->mainlink_ctrl(ctrl->catalog, true); + + ret = ctrl->link->psm_config(ctrl->link, + &ctrl->panel->link_info, false); + if (ret) + goto end; + + if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) + goto end; + + if (!train) + goto send_video; + + /* + * As part of previous calls, DP controller state might have + * transitioned to PUSH_IDLE. In order to start transmitting a link + * training pattern, we have to first to a DP software reset. + */ + ctrl->catalog->reset(ctrl->catalog); + + ret = dp_ctrl_link_train(ctrl); + if (ret) + goto end; + +send_video: + /* + * Set up transfer unit values and set controller state to send + * video. + */ + dp_ctrl_setup_tr_unit(ctrl); + ctrl->catalog->state_ctrl(ctrl->catalog, ST_SEND_VIDEO); + + dp_ctrl_wait4video_ready(ctrl); + mainlink_ready = ctrl->catalog->mainlink_ready(ctrl->catalog); + pr_debug("mainlink %s\n", mainlink_ready ? "READY" : "NOT READY"); +end: + return ret; +} + +static void dp_ctrl_set_clock_rate(struct dp_ctrl_private *ctrl, + char *name, u32 rate) +{ + u32 num = ctrl->parser->mp[DP_CTRL_PM].num_clk; + struct dss_clk *cfg = ctrl->parser->mp[DP_CTRL_PM].clk_config; + + while (num && strcmp(cfg->clk_name, name)) { + num--; + cfg++; + } + + pr_debug("setting rate=%d on clk=%s\n", rate, name); + + if (num) + cfg->rate = rate; + else + pr_err("%s clock could not be set with rate %d\n", name, rate); +} + +static int dp_ctrl_enable_mainlink_clocks(struct dp_ctrl_private *ctrl) +{ + int ret = 0; + + ctrl->power->set_pixel_clk_parent(ctrl->power); + + dp_ctrl_set_clock_rate(ctrl, "ctrl_link_clk", + drm_dp_bw_code_to_link_rate(ctrl->link->link_params.bw_code)); + + dp_ctrl_set_clock_rate(ctrl, "ctrl_pixel_clk", ctrl->pixel_rate); + + ret = ctrl->power->clk_enable(ctrl->power, DP_CTRL_PM, true); + if (ret) { + pr_err("Unabled to start link clocks\n"); + ret = -EINVAL; + } + + return ret; +} + +static int dp_ctrl_disable_mainlink_clocks(struct dp_ctrl_private *ctrl) +{ + return ctrl->power->clk_enable(ctrl->power, DP_CTRL_PM, false); +} + +static int dp_ctrl_host_init(struct dp_ctrl *dp_ctrl, bool flip) +{ + struct dp_ctrl_private *ctrl; + struct dp_catalog_ctrl *catalog; + + if (!dp_ctrl) { + pr_err("Invalid input data\n"); + return -EINVAL; + } + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + + ctrl->orientation = flip; + catalog = ctrl->catalog; + + catalog->usb_reset(ctrl->catalog, flip); + catalog->phy_reset(ctrl->catalog); + catalog->enable_irq(ctrl->catalog, true); + + return 0; +} + +/** + * dp_ctrl_host_deinit() - Uninitialize DP controller + * @ctrl: Display Port Driver data + * + * Perform required steps to uninitialize DP controller + * and its resources. + */ +static void dp_ctrl_host_deinit(struct dp_ctrl *dp_ctrl) +{ + struct dp_ctrl_private *ctrl; + + if (!dp_ctrl) { + pr_err("Invalid input data\n"); + return; + } + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + + ctrl->catalog->enable_irq(ctrl->catalog, false); + + pr_debug("Host deinitialized successfully\n"); +} + +static bool dp_ctrl_use_fixed_nvid(struct dp_ctrl_private *ctrl) +{ + u8 *dpcd = ctrl->panel->dpcd; + + /* + * For better interop experience, used a fixed NVID=0x8000 + * whenever connected to a VGA dongle downstream. + */ + if (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT) { + u8 type = dpcd[DP_DOWNSTREAMPORT_PRESENT] & + DP_DWN_STRM_PORT_TYPE_MASK; + if (type == DP_DWN_STRM_PORT_TYPE_ANALOG) + return true; + } + + return false; +} + +static int dp_ctrl_link_maintenance(struct dp_ctrl_private *ctrl) +{ + int ret = 0; + + ctrl->dp_ctrl.push_idle(&ctrl->dp_ctrl); + ctrl->dp_ctrl.reset(&ctrl->dp_ctrl); + + ctrl->pixel_rate = ctrl->panel->pinfo.pixel_clk_khz; + + do { + if (ret == -EAGAIN) { + /* try with lower link rate */ + dp_ctrl_link_rate_down_shift(ctrl); + + ctrl->catalog->mainlink_ctrl(ctrl->catalog, false); + } + + ctrl->catalog->phy_lane_cfg(ctrl->catalog, + ctrl->orientation, ctrl->link->link_params.lane_count); + + /* + * Disable and re-enable the mainlink clock since the + * link clock might have been adjusted as part of the + * link maintenance. + */ + dp_ctrl_disable_mainlink_clocks(ctrl); + + ret = dp_ctrl_enable_mainlink_clocks(ctrl); + if (ret) + continue; + + dp_ctrl_configure_source_params(ctrl); + + ctrl->catalog->config_msa(ctrl->catalog, + drm_dp_bw_code_to_link_rate( + ctrl->link->link_params.bw_code), + ctrl->pixel_rate, dp_ctrl_use_fixed_nvid(ctrl)); + + reinit_completion(&ctrl->idle_comp); + + ret = dp_ctrl_setup_main_link(ctrl, true); + } while (ret == -EAGAIN); + + return ret; +} + +static void dp_ctrl_process_phy_test_request(struct dp_ctrl_private *ctrl) +{ + int ret = 0; + + if (!ctrl->link->phy_params.phy_test_pattern_sel) { + pr_debug("no test pattern selected by sink\n"); + return; + } + + pr_debug("start\n"); + + ctrl->dp_ctrl.push_idle(&ctrl->dp_ctrl); + /* + * The global reset will need DP link ralated clocks to be + * running. Add the global reset just before disabling the + * link clocks and core clocks. + */ + ctrl->dp_ctrl.reset(&ctrl->dp_ctrl); + ctrl->dp_ctrl.off(&ctrl->dp_ctrl); + + ret = ctrl->dp_ctrl.on(&ctrl->dp_ctrl); + if (ret) + pr_err("failed to enable DP controller\n"); + + pr_debug("end\n"); +} + +static void dp_ctrl_send_phy_test_pattern(struct dp_ctrl_private *ctrl) +{ + bool success = false; + u32 pattern_sent = 0x0; + u32 pattern_requested = ctrl->link->phy_params.phy_test_pattern_sel; + + pr_debug("request: %s\n", + dp_link_get_phy_test_pattern(pattern_requested)); + + ctrl->catalog->update_vx_px(ctrl->catalog, + ctrl->link->phy_params.v_level, + ctrl->link->phy_params.p_level); + ctrl->catalog->send_phy_pattern(ctrl->catalog, pattern_requested); + ctrl->link->send_test_response(ctrl->link); + + pattern_sent = ctrl->catalog->read_phy_pattern(ctrl->catalog); + + switch (pattern_sent) { + case MR_LINK_TRAINING1: + if (pattern_requested == + DP_TEST_PHY_PATTERN_D10_2_NO_SCRAMBLING) + success = true; + break; + case MR_LINK_SYMBOL_ERM: + if ((pattern_requested == + DP_TEST_PHY_PATTERN_SYMBOL_ERR_MEASUREMENT_CNT) + || (pattern_requested == + DP_TEST_PHY_PATTERN_HBR2_CTS_EYE_PATTERN)) + success = true; + break; + case MR_LINK_PRBS7: + if (pattern_requested == DP_TEST_PHY_PATTERN_PRBS7) + success = true; + break; + case MR_LINK_CUSTOM80: + if (pattern_requested == + DP_TEST_PHY_PATTERN_80_BIT_CUSTOM_PATTERN) + success = true; + break; + default: + success = false; + return; + } + + pr_debug("%s: %s\n", success ? "success" : "failed", + dp_link_get_phy_test_pattern(pattern_requested)); +} + +static void dp_ctrl_handle_sink_request(struct dp_ctrl *dp_ctrl) +{ + struct dp_ctrl_private *ctrl; + u32 sink_request = 0x0; + + if (!dp_ctrl) { + pr_err("invalid input\n"); + return; + } + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + sink_request = ctrl->link->sink_request; + + if (sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) { + pr_info("PHY_TEST_PATTERN request\n"); + dp_ctrl_process_phy_test_request(ctrl); + } + + if (sink_request & DP_LINK_STATUS_UPDATED) + dp_ctrl_link_maintenance(ctrl); + + if (sink_request & DP_TEST_LINK_TRAINING) { + ctrl->link->send_test_response(ctrl->link); + dp_ctrl_link_maintenance(ctrl); + } +} + +static void dp_ctrl_reset(struct dp_ctrl *dp_ctrl) +{ + struct dp_ctrl_private *ctrl; + + if (!dp_ctrl) { + pr_err("invalid params\n"); + return; + } + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + ctrl->catalog->reset(ctrl->catalog); +} + +static int dp_ctrl_on(struct dp_ctrl *dp_ctrl) +{ + int rc = 0; + struct dp_ctrl_private *ctrl; + u32 rate = 0; + u32 link_train_max_retries = 100; + u32 const phy_cts_pixel_clk_khz = 148500; + + if (!dp_ctrl) { + rc = -EINVAL; + goto end; + } + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + + atomic_set(&ctrl->aborted, 0); + rate = ctrl->panel->link_info.rate; + + ctrl->power->clk_enable(ctrl->power, DP_CORE_PM, true); + ctrl->catalog->hpd_config(ctrl->catalog, true); + + if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) { + pr_debug("using phy test link parameters\n"); + if (!ctrl->panel->pinfo.pixel_clk_khz) + ctrl->pixel_rate = phy_cts_pixel_clk_khz; + } else { + ctrl->link->link_params.bw_code = + drm_dp_link_rate_to_bw_code(rate); + ctrl->link->link_params.lane_count = + ctrl->panel->link_info.num_lanes; + ctrl->pixel_rate = ctrl->panel->pinfo.pixel_clk_khz; + } + + pr_debug("bw_code=%d, lane_count=%d, pixel_rate=%d\n", + ctrl->link->link_params.bw_code, + ctrl->link->link_params.lane_count, ctrl->pixel_rate); + + ctrl->catalog->phy_lane_cfg(ctrl->catalog, + ctrl->orientation, ctrl->link->link_params.lane_count); + + rc = dp_ctrl_enable_mainlink_clocks(ctrl); + if (rc) + goto end; + + reinit_completion(&ctrl->idle_comp); + + dp_ctrl_configure_source_params(ctrl); + + while (--link_train_max_retries && !atomic_read(&ctrl->aborted)) { + ctrl->catalog->config_msa(ctrl->catalog, + drm_dp_bw_code_to_link_rate( + ctrl->link->link_params.bw_code), + ctrl->pixel_rate, dp_ctrl_use_fixed_nvid(ctrl)); + + rc = dp_ctrl_setup_main_link(ctrl, true); + if (!rc) + break; + + /* try with lower link rate */ + dp_ctrl_link_rate_down_shift(ctrl); + + ctrl->catalog->mainlink_ctrl(ctrl->catalog, false); + + dp_ctrl_disable_mainlink_clocks(ctrl); + /* hw recommended delay before re-enabling clocks */ + msleep(20); + + dp_ctrl_enable_mainlink_clocks(ctrl); + } + + if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) + dp_ctrl_send_phy_test_pattern(ctrl); + + pr_debug("End-\n"); + +end: + return rc; +} + +static void dp_ctrl_off(struct dp_ctrl *dp_ctrl) +{ + struct dp_ctrl_private *ctrl; + + if (!dp_ctrl) + return; + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + + ctrl->catalog->mainlink_ctrl(ctrl->catalog, false); + ctrl->catalog->reset(ctrl->catalog); + + /* Make sure DP is disabled before clk disable */ + wmb(); + + dp_ctrl_disable_mainlink_clocks(ctrl); + + pr_debug("DP off done\n"); +} + +static void dp_ctrl_isr(struct dp_ctrl *dp_ctrl) +{ + struct dp_ctrl_private *ctrl; + + if (!dp_ctrl) + return; + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + + ctrl->catalog->get_interrupt(ctrl->catalog); + + if (ctrl->catalog->isr & DP_CTRL_INTR_READY_FOR_VIDEO) + dp_ctrl_video_ready(ctrl); + + if (ctrl->catalog->isr & DP_CTRL_INTR_IDLE_PATTERN_SENT) + dp_ctrl_idle_patterns_sent(ctrl); +} + +struct dp_ctrl *dp_ctrl_get(struct dp_ctrl_in *in) +{ + int rc = 0; + struct dp_ctrl_private *ctrl; + struct dp_ctrl *dp_ctrl; + + if (!in->dev || !in->panel || !in->aux || + !in->link || !in->catalog) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + ctrl = devm_kzalloc(in->dev, sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) { + rc = -ENOMEM; + goto error; + } + + init_completion(&ctrl->idle_comp); + init_completion(&ctrl->video_comp); + + /* in parameters */ + ctrl->parser = in->parser; + ctrl->panel = in->panel; + ctrl->power = in->power; + ctrl->aux = in->aux; + ctrl->link = in->link; + ctrl->catalog = in->catalog; + + dp_ctrl = &ctrl->dp_ctrl; + + /* out parameters */ + dp_ctrl->init = dp_ctrl_host_init; + dp_ctrl->deinit = dp_ctrl_host_deinit; + dp_ctrl->on = dp_ctrl_on; + dp_ctrl->off = dp_ctrl_off; + dp_ctrl->push_idle = dp_ctrl_push_idle; + dp_ctrl->abort = dp_ctrl_abort; + dp_ctrl->isr = dp_ctrl_isr; + dp_ctrl->reset = dp_ctrl_reset; + dp_ctrl->handle_sink_request = dp_ctrl_handle_sink_request; + + return dp_ctrl; +error: + return ERR_PTR(rc); +} + +void dp_ctrl_put(struct dp_ctrl *dp_ctrl) +{ + struct dp_ctrl_private *ctrl; + + if (!dp_ctrl) + return; + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + + devm_kfree(ctrl->dev, ctrl); +} diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h new file mode 100644 index 000000000000..d6d10ed0fa55 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _DP_CTRL_H_ +#define _DP_CTRL_H_ + +#include "dp_aux.h" +#include "dp_panel.h" +#include "dp_link.h" +#include "dp_parser.h" +#include "dp_power.h" +#include "dp_catalog.h" + +struct dp_ctrl { + int (*init)(struct dp_ctrl *dp_ctrl, bool flip); + void (*deinit)(struct dp_ctrl *dp_ctrl); + int (*on)(struct dp_ctrl *dp_ctrl); + void (*off)(struct dp_ctrl *dp_ctrl); + void (*reset)(struct dp_ctrl *dp_ctrl); + void (*push_idle)(struct dp_ctrl *dp_ctrl); + void (*abort)(struct dp_ctrl *dp_ctrl); + void (*isr)(struct dp_ctrl *dp_ctrl); + void (*handle_sink_request)(struct dp_ctrl *dp_ctrl); +}; + +struct dp_ctrl_in { + struct device *dev; + struct dp_panel *panel; + struct dp_aux *aux; + struct dp_link *link; + struct dp_parser *parser; + struct dp_power *power; + struct dp_catalog_ctrl *catalog; +}; + +struct dp_ctrl *dp_ctrl_get(struct dp_ctrl_in *in); +void dp_ctrl_put(struct dp_ctrl *dp_ctrl); + +#endif /* _DP_CTRL_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_debug.c b/drivers/gpu/drm/msm/dp/dp_debug.c new file mode 100644 index 000000000000..d0512e6fea45 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_debug.c @@ -0,0 +1,503 @@ +/* + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include + +#include "dp_parser.h" +#include "dp_power.h" +#include "dp_catalog.h" +#include "dp_aux.h" +#include "dp_ctrl.h" +#include "dp_debug.h" +#include "drm_connector.h" +#include "dp_display.h" + +#define DEBUG_NAME "drm_dp" + +struct dp_debug_private { + struct dentry *root; + + struct dp_usbpd *usbpd; + struct dp_link *link; + struct dp_panel *panel; + struct drm_connector **connector; + struct device *dev; + + struct dp_debug dp_debug; +}; + +static ssize_t dp_debug_write_hpd(struct file *file, + const char __user *user_buff, size_t count, loff_t *ppos) +{ + struct dp_debug_private *debug = file->private_data; + char buf[SZ_8]; + size_t len = 0; + int hpd; + + if (!debug) + return -ENODEV; + + if (*ppos) + return 0; + + /* Leave room for termination char */ + len = min_t(size_t, count, SZ_8 - 1); + if (copy_from_user(buf, user_buff, len)) + goto end; + + buf[len] = '\0'; + + if (kstrtoint(buf, 10, &hpd) != 0) + goto end; + + debug->usbpd->connect(debug->usbpd, hpd); +end: + return -len; +} + +static ssize_t dp_debug_write_edid_modes(struct file *file, + const char __user *user_buff, size_t count, loff_t *ppos) +{ + struct dp_debug_private *debug = file->private_data; + char buf[SZ_32]; + size_t len = 0; + int hdisplay = 0, vdisplay = 0, vrefresh = 0; + + if (!debug) + return -ENODEV; + + if (*ppos) + goto end; + + /* Leave room for termination char */ + len = min_t(size_t, count, SZ_32 - 1); + if (copy_from_user(buf, user_buff, len)) + goto clear; + + buf[len] = '\0'; + + if (sscanf(buf, "%d %d %d", &hdisplay, &vdisplay, &vrefresh) != 3) + goto clear; + + if (!hdisplay || !vdisplay || !vrefresh) + goto clear; + + debug->dp_debug.debug_en = true; + debug->dp_debug.hdisplay = hdisplay; + debug->dp_debug.vdisplay = vdisplay; + debug->dp_debug.vrefresh = vrefresh; + goto end; +clear: + pr_debug("clearing debug modes\n"); + debug->dp_debug.debug_en = false; +end: + return len; +} + +static ssize_t dp_debug_read_connected(struct file *file, + char __user *user_buff, size_t count, loff_t *ppos) +{ + struct dp_debug_private *debug = file->private_data; + char buf[SZ_8]; + u32 len = 0; + + if (!debug) + return -ENODEV; + + if (*ppos) + return 0; + + len += snprintf(buf, SZ_8, "%d\n", debug->usbpd->hpd_high); + + if (copy_to_user(user_buff, buf, len)) + return -EFAULT; + + *ppos += len; + return len; +} + +static ssize_t dp_debug_read_edid_modes(struct file *file, + char __user *user_buff, size_t count, loff_t *ppos) +{ + struct dp_debug_private *debug = file->private_data; + char *buf; + u32 len = 0; + int rc = 0; + struct drm_connector *connector; + struct drm_display_mode *mode; + + if (!debug) { + pr_err("invalid data\n"); + rc = -ENODEV; + goto error; + } + + connector = *debug->connector; + + if (!connector) { + pr_err("connector is NULL\n"); + rc = -EINVAL; + goto error; + } + + if (*ppos) + goto error; + + buf = kzalloc(SZ_4K, GFP_KERNEL); + if (!buf) { + rc = -ENOMEM; + goto error; + } + + list_for_each_entry(mode, &connector->modes, head) { + len += snprintf(buf + len, SZ_4K - len, + "%s %d %d %d %d %d %d %d %d %d 0x%x\n", + mode->name, mode->vrefresh, mode->hdisplay, + mode->hsync_start, mode->hsync_end, mode->htotal, + mode->vdisplay, mode->vsync_start, mode->vsync_end, + mode->vtotal, mode->flags); + } + + if (copy_to_user(user_buff, buf, len)) { + kfree(buf); + rc = -EFAULT; + goto error; + } + + *ppos += len; + kfree(buf); + + return len; +error: + return rc; +} + +static int dp_debug_check_buffer_overflow(int rc, int *max_size, int *len) +{ + if (rc >= *max_size) { + pr_err("buffer overflow\n"); + return -EINVAL; + } + *len += rc; + *max_size = SZ_4K - *len; + + return 0; +} + +static ssize_t dp_debug_read_info(struct file *file, char __user *user_buff, + size_t count, loff_t *ppos) +{ + struct dp_debug_private *debug = file->private_data; + char *buf; + u32 len = 0, rc = 0; + u64 lclk = 0; + u32 max_size = SZ_4K; + + if (!debug) + return -ENODEV; + + if (*ppos) + return 0; + + buf = kzalloc(SZ_4K, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + rc = snprintf(buf + len, max_size, "\tname = %s\n", DEBUG_NAME); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\tdp_panel\n\t\tmax_pclk_khz = %d\n", + debug->panel->max_pclk_khz); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\tdrm_dp_link\n\t\trate = %u\n", + debug->panel->link_info.rate); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tnum_lanes = %u\n", + debug->panel->link_info.num_lanes); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tcapabilities = %lu\n", + debug->panel->link_info.capabilities); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\tdp_panel_info:\n\t\tactive = %dx%d\n", + debug->panel->pinfo.h_active, + debug->panel->pinfo.v_active); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tback_porch = %dx%d\n", + debug->panel->pinfo.h_back_porch, + debug->panel->pinfo.v_back_porch); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tfront_porch = %dx%d\n", + debug->panel->pinfo.h_front_porch, + debug->panel->pinfo.v_front_porch); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tsync_width = %dx%d\n", + debug->panel->pinfo.h_sync_width, + debug->panel->pinfo.v_sync_width); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tactive_low = %dx%d\n", + debug->panel->pinfo.h_active_low, + debug->panel->pinfo.v_active_low); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\th_skew = %d\n", + debug->panel->pinfo.h_skew); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\trefresh rate = %d\n", + debug->panel->pinfo.refresh_rate); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tpixel clock khz = %d\n", + debug->panel->pinfo.pixel_clk_khz); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tbpp = %d\n", + debug->panel->pinfo.bpp); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + /* Link Information */ + rc = snprintf(buf + len, max_size, + "\tdp_link:\n\t\ttest_requested = %d\n", + debug->link->sink_request); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tlane_count = %d\n", debug->link->link_params.lane_count); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tbw_code = %d\n", debug->link->link_params.bw_code); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + lclk = drm_dp_bw_code_to_link_rate( + debug->link->link_params.bw_code) * 1000; + rc = snprintf(buf + len, max_size, + "\t\tlclk = %lld\n", lclk); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tv_level = %d\n", debug->link->phy_params.v_level); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tp_level = %d\n", debug->link->phy_params.p_level); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + if (copy_to_user(user_buff, buf, len)) + goto error; + + *ppos += len; + + kfree(buf); + return len; +error: + kfree(buf); + return -EINVAL; +} + +static const struct file_operations dp_debug_fops = { + .open = simple_open, + .read = dp_debug_read_info, +}; + +static const struct file_operations edid_modes_fops = { + .open = simple_open, + .read = dp_debug_read_edid_modes, + .write = dp_debug_write_edid_modes, +}; + +static const struct file_operations hpd_fops = { + .open = simple_open, + .write = dp_debug_write_hpd, +}; + +static const struct file_operations connected_fops = { + .open = simple_open, + .read = dp_debug_read_connected, +}; + +static int dp_debug_init(struct dp_debug *dp_debug) +{ + int rc = 0; + struct dp_debug_private *debug = container_of(dp_debug, + struct dp_debug_private, dp_debug); + struct dentry *dir, *file, *edid_modes; + struct dentry *hpd, *connected; + struct dentry *root = debug->root; + + dir = debugfs_create_dir(DEBUG_NAME, NULL); + if (IS_ERR_OR_NULL(dir)) { + rc = PTR_ERR(dir); + pr_err("[%s] debugfs create dir failed, rc = %d\n", + DEBUG_NAME, rc); + goto error; + } + + file = debugfs_create_file("dp_debug", 0444, dir, + debug, &dp_debug_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs create file failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + edid_modes = debugfs_create_file("edid_modes", 0644, dir, + debug, &edid_modes_fops); + if (IS_ERR_OR_NULL(edid_modes)) { + rc = PTR_ERR(edid_modes); + pr_err("[%s] debugfs create edid_modes failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + hpd = debugfs_create_file("hpd", 0644, dir, + debug, &hpd_fops); + if (IS_ERR_OR_NULL(hpd)) { + rc = PTR_ERR(hpd); + pr_err("[%s] debugfs hpd failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + connected = debugfs_create_file("connected", 0444, dir, + debug, &connected_fops); + if (IS_ERR_OR_NULL(connected)) { + rc = PTR_ERR(connected); + pr_err("[%s] debugfs connected failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + root = dir; + return rc; +error_remove_dir: + debugfs_remove(dir); +error: + return rc; +} + +struct dp_debug *dp_debug_get(struct device *dev, struct dp_panel *panel, + struct dp_usbpd *usbpd, struct dp_link *link, + struct drm_connector **connector) +{ + int rc = 0; + struct dp_debug_private *debug; + struct dp_debug *dp_debug; + + if (!dev || !panel || !usbpd || !link) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + debug = devm_kzalloc(dev, sizeof(*debug), GFP_KERNEL); + if (!debug) { + rc = -ENOMEM; + goto error; + } + + debug->dp_debug.debug_en = false; + debug->usbpd = usbpd; + debug->link = link; + debug->panel = panel; + debug->dev = dev; + debug->connector = connector; + + dp_debug = &debug->dp_debug; + dp_debug->vdisplay = 0; + dp_debug->hdisplay = 0; + dp_debug->vrefresh = 0; + + rc = dp_debug_init(dp_debug); + if (rc) { + devm_kfree(dev, debug); + goto error; + } + + return dp_debug; +error: + return ERR_PTR(rc); +} + +static int dp_debug_deinit(struct dp_debug *dp_debug) +{ + struct dp_debug_private *debug; + + if (!dp_debug) + return -EINVAL; + + debug = container_of(dp_debug, struct dp_debug_private, dp_debug); + + debugfs_remove(debug->root); + + return 0; +} + +void dp_debug_put(struct dp_debug *dp_debug) +{ + struct dp_debug_private *debug; + + if (!dp_debug) + return; + + debug = container_of(dp_debug, struct dp_debug_private, dp_debug); + + dp_debug_deinit(dp_debug); + + devm_kfree(debug->dev, debug); +} diff --git a/drivers/gpu/drm/msm/dp/dp_debug.h b/drivers/gpu/drm/msm/dp/dp_debug.h new file mode 100644 index 000000000000..7fd533082cc2 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_debug.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _DP_DEBUG_H_ +#define _DP_DEBUG_H_ + +#include "dp_panel.h" +#include "dp_link.h" +#include "dp_usbpd.h" + +/** + * struct dp_debug + * @debug_en: specifies whether debug mode enabled + * @vdisplay: used to filter out vdisplay value + * @hdisplay: used to filter out hdisplay value + * @vrefresh: used to filter out vrefresh value + */ +struct dp_debug { + bool debug_en; + int vdisplay; + int hdisplay; + int vrefresh; +}; + +/** + * dp_debug_get() - configure and get the DisplayPlot debug module data + * + * @dev: device instance of the caller + * @panel: instance of panel module + * @usbpd: instance of usbpd module + * @link: instance of link module + * @connector: double pointer to display connector + * return: pointer to allocated debug module data + * + * This function sets up the debug module and provides a way + * for debugfs input to be communicated with existing modules + */ +struct dp_debug *dp_debug_get(struct device *dev, struct dp_panel *panel, + struct dp_usbpd *usbpd, struct dp_link *link, + struct drm_connector **connector); +/** + * dp_debug_put() + * + * Cleans up dp_debug instance + * + * @dp_debug: instance of dp_debug + */ +void dp_debug_put(struct dp_debug *dp_debug); +#endif /* _DP_DEBUG_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c new file mode 100644 index 000000000000..a0b6cef92fc3 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -0,0 +1,1263 @@ +/* + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +#include "msm_drv.h" +#include "dp_usbpd.h" +#include "dp_parser.h" +#include "dp_power.h" +#include "dp_catalog.h" +#include "dp_aux.h" +#include "dp_link.h" +#include "dp_panel.h" +#include "dp_ctrl.h" +#include "dp_audio.h" +#include "dp_display.h" +#include "sde_hdcp.h" +#include "dp_debug.h" + +static struct dp_display *g_dp_display; +#define HPD_STRING_SIZE 30 + +struct dp_hdcp { + void *data; + struct sde_hdcp_ops *ops; + + void *hdcp1; + void *hdcp2; + + int enc_lvl; + + bool auth_state; + bool hdcp1_present; + bool hdcp2_present; + bool feature_enabled; +}; + +struct dp_display_private { + char *name; + int irq; + + /* state variables */ + bool core_initialized; + bool power_on; + bool hpd_irq_on; + bool audio_supported; + + struct platform_device *pdev; + struct dentry *root; + struct completion notification_comp; + + struct dp_usbpd *usbpd; + struct dp_parser *parser; + struct dp_power *power; + struct dp_catalog *catalog; + struct dp_aux *aux; + struct dp_link *link; + struct dp_panel *panel; + struct dp_ctrl *ctrl; + struct dp_audio *audio; + struct dp_debug *debug; + + struct dp_hdcp hdcp; + + struct dp_usbpd_cb usbpd_cb; + struct dp_display_mode mode; + struct dp_display dp_display; + + struct workqueue_struct *hdcp_workqueue; + struct delayed_work hdcp_cb_work; + struct mutex hdcp_mutex; + int hdcp_status; +}; + +static const struct of_device_id dp_dt_match[] = { + {.compatible = "qcom,dp-display"}, + {} +}; + +static inline bool dp_display_is_hdcp_enabled(struct dp_display_private *dp) +{ + return dp->hdcp.feature_enabled && + (dp->hdcp.hdcp1_present || dp->hdcp.hdcp2_present) && + dp->hdcp.ops; +} + +static irqreturn_t dp_display_irq(int irq, void *dev_id) +{ + struct dp_display_private *dp = dev_id; + + if (!dp) { + pr_err("invalid data\n"); + return IRQ_NONE; + } + + /* DP controller isr */ + dp->ctrl->isr(dp->ctrl); + + /* DP aux isr */ + dp->aux->isr(dp->aux); + + /* HDCP isr */ + if (dp_display_is_hdcp_enabled(dp) && dp->hdcp.ops->isr) { + if (dp->hdcp.ops->isr(dp->hdcp.data)) + pr_err("dp_hdcp_isr failed\n"); + } + + return IRQ_HANDLED; +} + +static void dp_display_hdcp_cb_work(struct work_struct *work) +{ + struct dp_display_private *dp; + struct delayed_work *dw = to_delayed_work(work); + struct sde_hdcp_ops *ops; + int rc = 0; + u32 hdcp_auth_state; + + dp = container_of(dw, struct dp_display_private, hdcp_cb_work); + + rc = dp->catalog->ctrl.read_hdcp_status(&dp->catalog->ctrl); + if (rc >= 0) { + hdcp_auth_state = (rc >> 20) & 0x3; + pr_debug("hdcp auth state %d\n", hdcp_auth_state); + } + + ops = dp->hdcp.ops; + + switch (dp->hdcp_status) { + case HDCP_STATE_AUTHENTICATING: + pr_debug("start authenticaton\n"); + + if (dp->hdcp.ops && dp->hdcp.ops->authenticate) + rc = dp->hdcp.ops->authenticate(dp->hdcp.data); + + break; + case HDCP_STATE_AUTHENTICATED: + pr_debug("hdcp authenticated\n"); + dp->hdcp.auth_state = true; + break; + case HDCP_STATE_AUTH_FAIL: + dp->hdcp.auth_state = false; + + if (dp->power_on) { + pr_debug("Reauthenticating\n"); + if (ops && ops->reauthenticate) { + rc = ops->reauthenticate(dp->hdcp.data); + if (rc) + pr_err("reauth failed rc=%d\n", rc); + } + } else { + pr_debug("not reauthenticating, cable disconnected\n"); + } + + break; + default: + break; + } +} + +static void dp_display_notify_hdcp_status_cb(void *ptr, + enum sde_hdcp_states status) +{ + struct dp_display_private *dp = ptr; + + if (!dp) { + pr_err("invalid input\n"); + return; + } + + dp->hdcp_status = status; + + if (dp->dp_display.is_connected) + queue_delayed_work(dp->hdcp_workqueue, &dp->hdcp_cb_work, HZ/4); +} + +static int dp_display_create_hdcp_workqueue(struct dp_display_private *dp) +{ + dp->hdcp_workqueue = create_workqueue("sdm_dp_hdcp"); + if (IS_ERR_OR_NULL(dp->hdcp_workqueue)) { + pr_err("Error creating hdcp_workqueue\n"); + return -EPERM; + } + + INIT_DELAYED_WORK(&dp->hdcp_cb_work, dp_display_hdcp_cb_work); + + return 0; +} + +static void dp_display_destroy_hdcp_workqueue(struct dp_display_private *dp) +{ + if (dp->hdcp_workqueue) + destroy_workqueue(dp->hdcp_workqueue); +} + +static void dp_display_update_hdcp_info(struct dp_display_private *dp) +{ + void *fd = NULL; + struct sde_hdcp_ops *ops = NULL; + + if (!dp) { + pr_err("invalid input\n"); + return; + } + + if (!dp->hdcp.feature_enabled) { + pr_debug("feature not enabled\n"); + return; + } + + fd = dp->hdcp.hdcp2; + if (fd) + ops = sde_dp_hdcp2p2_start(fd); + + if (ops && ops->feature_supported) + dp->hdcp.hdcp2_present = ops->feature_supported(fd); + else + dp->hdcp.hdcp2_present = false; + + pr_debug("hdcp2p2: %s\n", + dp->hdcp.hdcp2_present ? "supported" : "not supported"); + + if (!dp->hdcp.hdcp2_present) { + dp->hdcp.hdcp1_present = hdcp1_check_if_supported_load_app(); + + if (dp->hdcp.hdcp1_present) { + fd = dp->hdcp.hdcp1; + ops = sde_hdcp_1x_start(fd); + } + } + + pr_debug("hdcp1x: %s\n", + dp->hdcp.hdcp1_present ? "supported" : "not supported"); + + if (dp->hdcp.hdcp2_present || dp->hdcp.hdcp1_present) { + dp->hdcp.data = fd; + dp->hdcp.ops = ops; + } else { + dp->hdcp.data = NULL; + dp->hdcp.ops = NULL; + } +} + +static void dp_display_deinitialize_hdcp(struct dp_display_private *dp) +{ + if (!dp) { + pr_err("invalid input\n"); + return; + } + + sde_dp_hdcp2p2_deinit(dp->hdcp.data); + dp_display_destroy_hdcp_workqueue(dp); + if (&dp->hdcp_mutex) + mutex_destroy(&dp->hdcp_mutex); +} + +static int dp_display_initialize_hdcp(struct dp_display_private *dp) +{ + struct sde_hdcp_init_data hdcp_init_data; + struct resource *res; + int rc = 0; + + if (!dp) { + pr_err("invalid input\n"); + return -EINVAL; + } + + mutex_init(&dp->hdcp_mutex); + + rc = dp_display_create_hdcp_workqueue(dp); + if (rc) { + pr_err("Failed to create HDCP workqueue\n"); + goto error; + } + + res = platform_get_resource_byname(dp->pdev, + IORESOURCE_MEM, "dp_ctrl"); + if (!res) { + pr_err("Error getting dp ctrl resource\n"); + rc = -EINVAL; + goto error; + } + + hdcp_init_data.phy_addr = res->start; + hdcp_init_data.client_id = HDCP_CLIENT_DP; + hdcp_init_data.drm_aux = dp->aux->drm_aux; + hdcp_init_data.cb_data = (void *)dp; + hdcp_init_data.workq = dp->hdcp_workqueue; + hdcp_init_data.mutex = &dp->hdcp_mutex; + hdcp_init_data.sec_access = true; + hdcp_init_data.notify_status = dp_display_notify_hdcp_status_cb; + hdcp_init_data.core_io = &dp->parser->io.ctrl_io; + hdcp_init_data.qfprom_io = &dp->parser->io.qfprom_io; + hdcp_init_data.hdcp_io = &dp->parser->io.hdcp_io; + hdcp_init_data.revision = &dp->panel->link_info.revision; + + dp->hdcp.hdcp1 = sde_hdcp_1x_init(&hdcp_init_data); + if (IS_ERR_OR_NULL(dp->hdcp.hdcp1)) { + pr_err("Error initializing HDCP 1.x\n"); + rc = -EINVAL; + goto error; + } + + pr_debug("HDCP 1.3 initialized\n"); + + dp->hdcp.hdcp2 = sde_dp_hdcp2p2_init(&hdcp_init_data); + if (!IS_ERR_OR_NULL(dp->hdcp.hdcp2)) + pr_debug("HDCP 2.2 initialized\n"); + + dp->hdcp.feature_enabled = true; + + return 0; +error: + dp_display_deinitialize_hdcp(dp); + return rc; +} + +static int dp_display_bind(struct device *dev, struct device *master, + void *data) +{ + int rc = 0; + struct dp_display_private *dp; + struct drm_device *drm; + struct msm_drm_private *priv; + struct platform_device *pdev = to_platform_device(dev); + + if (!dev || !pdev || !master) { + pr_err("invalid param(s), dev %pK, pdev %pK, master %pK\n", + dev, pdev, master); + rc = -EINVAL; + goto end; + } + + drm = dev_get_drvdata(master); + dp = platform_get_drvdata(pdev); + if (!drm || !dp) { + pr_err("invalid param(s), drm %pK, dp %pK\n", + drm, dp); + rc = -EINVAL; + goto end; + } + + dp->dp_display.drm_dev = drm; + priv = drm->dev_private; + + rc = dp->parser->parse(dp->parser); + if (rc) { + pr_err("device tree parsing failed\n"); + goto end; + } + + rc = dp->aux->drm_aux_register(dp->aux); + if (rc) { + pr_err("DRM DP AUX register failed\n"); + goto end; + } + + rc = dp->panel->sde_edid_register(dp->panel); + if (rc) { + pr_err("DRM DP EDID register failed\n"); + goto end; + } + + rc = dp->power->power_client_init(dp->power, &priv->phandle); + if (rc) { + pr_err("Power client create failed\n"); + goto end; + } + + rc = dp_display_initialize_hdcp(dp); + if (rc) { + pr_err("HDCP initialization failed\n"); + goto end; + } +end: + return rc; +} + +static void dp_display_unbind(struct device *dev, struct device *master, + void *data) +{ + struct dp_display_private *dp; + struct platform_device *pdev = to_platform_device(dev); + + if (!dev || !pdev) { + pr_err("invalid param(s)\n"); + return; + } + + dp = platform_get_drvdata(pdev); + if (!dp) { + pr_err("Invalid params\n"); + return; + } + + (void)dp->power->power_client_deinit(dp->power); + (void)dp->panel->sde_edid_deregister(dp->panel); + (void)dp->aux->drm_aux_deregister(dp->aux); + dp_display_deinitialize_hdcp(dp); +} + +static const struct component_ops dp_display_comp_ops = { + .bind = dp_display_bind, + .unbind = dp_display_unbind, +}; + +static bool dp_display_is_ds_bridge(struct dp_panel *panel) +{ + return (panel->dpcd[DP_DOWNSTREAMPORT_PRESENT] & + DP_DWN_STRM_PORT_PRESENT); +} + +static bool dp_display_is_sink_count_zero(struct dp_display_private *dp) +{ + return dp_display_is_ds_bridge(dp->panel) && + (dp->link->sink_count.count == 0); +} + +static void dp_display_send_hpd_event(struct dp_display *dp_display) +{ + struct drm_device *dev = NULL; + struct dp_display_private *dp; + struct drm_connector *connector; + char name[HPD_STRING_SIZE], status[HPD_STRING_SIZE], + bpp[HPD_STRING_SIZE], pattern[HPD_STRING_SIZE]; + char *envp[5]; + + if (!dp_display) { + pr_err("invalid input\n"); + return; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + if (!dp) { + pr_err("invalid params\n"); + return; + } + connector = dp->dp_display.connector; + dev = dp_display->connector->dev; + + connector->status = connector->funcs->detect(connector, false); + pr_debug("[%s] status updated to %s\n", + connector->name, + drm_get_connector_status_name(connector->status)); + snprintf(name, HPD_STRING_SIZE, "name=%s", connector->name); + snprintf(status, HPD_STRING_SIZE, "status=%s", + drm_get_connector_status_name(connector->status)); + snprintf(bpp, HPD_STRING_SIZE, "bpp=%d", + dp_link_bit_depth_to_bpp( + dp->link->test_video.test_bit_depth)); + snprintf(pattern, HPD_STRING_SIZE, "pattern=%d", + dp->link->test_video.test_video_pattern); + + pr_debug("generating hotplug event [%s]:[%s] [%s] [%s]\n", + name, status, bpp, pattern); + envp[0] = name; + envp[1] = status; + envp[2] = bpp; + envp[3] = pattern; + envp[4] = NULL; + kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, + envp); +} + +static int dp_display_send_hpd_notification(struct dp_display_private *dp, + bool hpd) +{ + if ((hpd && dp->dp_display.is_connected) || + (!hpd && !dp->dp_display.is_connected)) { + pr_info("HPD already %s\n", (hpd ? "on" : "off")); + return 0; + } + + /* reset video pattern flag on disconnect */ + if (!hpd) + dp->panel->video_test = false; + + dp->dp_display.is_connected = hpd; + reinit_completion(&dp->notification_comp); + dp_display_send_hpd_event(&dp->dp_display); + + if (!wait_for_completion_timeout(&dp->notification_comp, HZ * 2)) { + pr_warn("%s timeout\n", hpd ? "connect" : "disconnect"); + return -EINVAL; + } + + return 0; +} + +static int dp_display_process_hpd_high(struct dp_display_private *dp) +{ + int rc = 0; + u32 max_pclk_from_edid = 0; + struct edid *edid; + + dp->aux->init(dp->aux, dp->parser->aux_cfg); + + if (dp->link->psm_enabled) + goto notify; + + rc = dp->panel->read_sink_caps(dp->panel, dp->dp_display.connector); + if (rc) + goto notify; + + dp->link->process_request(dp->link); + + if (dp_display_is_sink_count_zero(dp)) { + pr_debug("no downstream devices connected\n"); + rc = -EINVAL; + goto end; + } + + edid = dp->panel->edid_ctrl->edid; + + dp->audio_supported = drm_detect_monitor_audio(edid); + + dp->panel->handle_sink_request(dp->panel); + + max_pclk_from_edid = dp->panel->get_max_pclk(dp->panel); + + dp->dp_display.max_pclk_khz = min(max_pclk_from_edid, + dp->parser->max_pclk_khz); + +notify: + dp_display_send_hpd_notification(dp, true); + +end: + return rc; +} + +static void dp_display_host_init(struct dp_display_private *dp) +{ + bool flip = false; + + if (dp->core_initialized) { + pr_debug("DP core already initialized\n"); + return; + } + + if (dp->usbpd->orientation == ORIENTATION_CC2) + flip = true; + + dp->power->init(dp->power, flip); + dp->ctrl->init(dp->ctrl, flip); + enable_irq(dp->irq); + dp->core_initialized = true; +} + +static void dp_display_host_deinit(struct dp_display_private *dp) +{ + if (!dp->core_initialized) { + pr_debug("DP core already off\n"); + return; + } + + dp->ctrl->deinit(dp->ctrl); + dp->power->deinit(dp->power); + disable_irq(dp->irq); + dp->core_initialized = false; +} + +static void dp_display_process_hpd_low(struct dp_display_private *dp) +{ + /* cancel any pending request */ + dp->ctrl->abort(dp->ctrl); + + if (dp_display_is_hdcp_enabled(dp) && dp->hdcp.ops->off) { + cancel_delayed_work_sync(&dp->hdcp_cb_work); + dp->hdcp.ops->off(dp->hdcp.data); + } + + if (dp->audio_supported) + dp->audio->off(dp->audio); + + dp_display_send_hpd_notification(dp, false); + + dp->aux->deinit(dp->aux); +} + +static int dp_display_usbpd_configure_cb(struct device *dev) +{ + int rc = 0; + struct dp_display_private *dp; + + if (!dev) { + pr_err("invalid dev\n"); + rc = -EINVAL; + goto end; + } + + dp = dev_get_drvdata(dev); + if (!dp) { + pr_err("no driver data found\n"); + rc = -ENODEV; + goto end; + } + + dp_display_host_init(dp); + + if (dp->usbpd->hpd_high) + dp_display_process_hpd_high(dp); +end: + return rc; +} + +static void dp_display_clean(struct dp_display_private *dp) +{ + if (dp_display_is_hdcp_enabled(dp)) { + dp->hdcp_status = HDCP_STATE_INACTIVE; + + cancel_delayed_work_sync(&dp->hdcp_cb_work); + if (dp->hdcp.ops->off) + dp->hdcp.ops->off(dp->hdcp.data); + } + + dp->ctrl->push_idle(dp->ctrl); + dp->ctrl->off(dp->ctrl); +} + +static int dp_display_usbpd_disconnect_cb(struct device *dev) +{ + int rc = 0; + struct dp_display_private *dp; + + if (!dev) { + pr_err("invalid dev\n"); + rc = -EINVAL; + goto end; + } + + dp = dev_get_drvdata(dev); + if (!dp) { + pr_err("no driver data found\n"); + rc = -ENODEV; + goto end; + } + + /* cancel any pending request */ + dp->ctrl->abort(dp->ctrl); + + if (dp->audio_supported) + dp->audio->off(dp->audio); + + rc = dp_display_send_hpd_notification(dp, false); + + /* if cable is disconnected, reset psm_enabled flag */ + if (!dp->usbpd->alt_mode_cfg_done) + dp->link->psm_enabled = false; + + if ((rc < 0) && dp->power_on) + dp_display_clean(dp); + + dp_display_host_deinit(dp); +end: + return rc; +} + +static void dp_display_handle_video_request(struct dp_display_private *dp) +{ + if (dp->link->sink_request & DP_TEST_LINK_VIDEO_PATTERN) { + /* force disconnect followed by connect */ + dp->usbpd->connect(dp->usbpd, false); + dp->panel->video_test = true; + dp->usbpd->connect(dp->usbpd, true); + dp->link->send_test_response(dp->link); + } +} + +static int dp_display_handle_hpd_irq(struct dp_display_private *dp) +{ + if (dp->link->sink_request & DS_PORT_STATUS_CHANGED) { + dp_display_send_hpd_notification(dp, false); + + if (dp_display_is_sink_count_zero(dp)) { + pr_debug("sink count is zero, nothing to do\n"); + return 0; + } + + return dp_display_process_hpd_high(dp); + } + + dp->ctrl->handle_sink_request(dp->ctrl); + + dp_display_handle_video_request(dp); + + return 0; +} + +static int dp_display_usbpd_attention_cb(struct device *dev) +{ + int rc = 0; + struct dp_display_private *dp; + + if (!dev) { + pr_err("invalid dev\n"); + return -EINVAL; + } + + dp = dev_get_drvdata(dev); + if (!dp) { + pr_err("no driver data found\n"); + return -ENODEV; + } + + if (dp->usbpd->hpd_irq) { + dp->hpd_irq_on = true; + + if (dp_display_is_hdcp_enabled(dp) && dp->hdcp.ops->cp_irq) { + if (!dp->hdcp.ops->cp_irq(dp->hdcp.data)) + goto end; + } + + rc = dp->link->process_request(dp->link); + /* check for any test request issued by sink */ + if (!rc) + dp_display_handle_hpd_irq(dp); + + dp->hpd_irq_on = false; + goto end; + } + + if (!dp->usbpd->hpd_high) { + dp_display_process_hpd_low(dp); + goto end; + } + + if (dp->usbpd->alt_mode_cfg_done) + dp_display_process_hpd_high(dp); +end: + return rc; +} + +static void dp_display_deinit_sub_modules(struct dp_display_private *dp) +{ + dp_audio_put(dp->audio); + dp_ctrl_put(dp->ctrl); + dp_link_put(dp->link); + dp_panel_put(dp->panel); + dp_aux_put(dp->aux); + dp_power_put(dp->power); + dp_catalog_put(dp->catalog); + dp_parser_put(dp->parser); + dp_usbpd_put(dp->usbpd); + dp_debug_put(dp->debug); +} + +static int dp_init_sub_modules(struct dp_display_private *dp) +{ + int rc = 0; + struct device *dev = &dp->pdev->dev; + struct dp_usbpd_cb *cb = &dp->usbpd_cb; + struct dp_ctrl_in ctrl_in = { + .dev = dev, + }; + struct dp_panel_in panel_in = { + .dev = dev, + }; + + cb->configure = dp_display_usbpd_configure_cb; + cb->disconnect = dp_display_usbpd_disconnect_cb; + cb->attention = dp_display_usbpd_attention_cb; + + dp->usbpd = dp_usbpd_get(dev, cb); + if (IS_ERR(dp->usbpd)) { + rc = PTR_ERR(dp->usbpd); + pr_err("failed to initialize usbpd, rc = %d\n", rc); + dp->usbpd = NULL; + goto error; + } + + dp->parser = dp_parser_get(dp->pdev); + if (IS_ERR(dp->parser)) { + rc = PTR_ERR(dp->parser); + pr_err("failed to initialize parser, rc = %d\n", rc); + dp->parser = NULL; + goto error_parser; + } + + dp->catalog = dp_catalog_get(dev, &dp->parser->io); + if (IS_ERR(dp->catalog)) { + rc = PTR_ERR(dp->catalog); + pr_err("failed to initialize catalog, rc = %d\n", rc); + dp->catalog = NULL; + goto error_catalog; + } + + dp->power = dp_power_get(dp->parser); + if (IS_ERR(dp->power)) { + rc = PTR_ERR(dp->power); + pr_err("failed to initialize power, rc = %d\n", rc); + dp->power = NULL; + goto error_power; + } + + dp->aux = dp_aux_get(dev, &dp->catalog->aux, dp->parser->aux_cfg); + if (IS_ERR(dp->aux)) { + rc = PTR_ERR(dp->aux); + pr_err("failed to initialize aux, rc = %d\n", rc); + dp->aux = NULL; + goto error_aux; + } + + dp->link = dp_link_get(dev, dp->aux); + if (IS_ERR(dp->link)) { + rc = PTR_ERR(dp->link); + pr_err("failed to initialize link, rc = %d\n", rc); + dp->link = NULL; + goto error_link; + } + + panel_in.aux = dp->aux; + panel_in.catalog = &dp->catalog->panel; + panel_in.link = dp->link; + + dp->panel = dp_panel_get(&panel_in); + if (IS_ERR(dp->panel)) { + rc = PTR_ERR(dp->panel); + pr_err("failed to initialize panel, rc = %d\n", rc); + dp->panel = NULL; + goto error_panel; + } + + ctrl_in.link = dp->link; + ctrl_in.panel = dp->panel; + ctrl_in.aux = dp->aux; + ctrl_in.power = dp->power; + ctrl_in.catalog = &dp->catalog->ctrl; + ctrl_in.parser = dp->parser; + + dp->ctrl = dp_ctrl_get(&ctrl_in); + if (IS_ERR(dp->ctrl)) { + rc = PTR_ERR(dp->ctrl); + pr_err("failed to initialize ctrl, rc = %d\n", rc); + dp->ctrl = NULL; + goto error_ctrl; + } + + dp->audio = dp_audio_get(dp->pdev, dp->panel, &dp->catalog->audio); + if (IS_ERR(dp->audio)) { + rc = PTR_ERR(dp->audio); + pr_err("failed to initialize audio, rc = %d\n", rc); + dp->audio = NULL; + goto error_audio; + } + + dp->debug = dp_debug_get(dev, dp->panel, dp->usbpd, + dp->link, &dp->dp_display.connector); + if (IS_ERR(dp->debug)) { + rc = PTR_ERR(dp->debug); + pr_err("failed to initialize debug, rc = %d\n", rc); + dp->debug = NULL; + goto error_debug; + } + + return rc; +error_debug: + dp_audio_put(dp->audio); +error_audio: + dp_ctrl_put(dp->ctrl); +error_ctrl: + dp_panel_put(dp->panel); +error_panel: + dp_link_put(dp->link); +error_link: + dp_aux_put(dp->aux); +error_aux: + dp_power_put(dp->power); +error_power: + dp_catalog_put(dp->catalog); +error_catalog: + dp_parser_put(dp->parser); +error_parser: + dp_usbpd_put(dp->usbpd); +error: + return rc; +} + +static int dp_display_set_mode(struct dp_display *dp_display, + struct dp_display_mode *mode) +{ + int rc = 0; + struct dp_display_private *dp; + + if (!dp_display) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + dp = container_of(dp_display, struct dp_display_private, dp_display); + + dp->panel->pinfo = mode->timing; + dp->panel->init_info(dp->panel); +error: + return rc; +} + +static int dp_display_prepare(struct dp_display *dp) +{ + return 0; +} + +static int dp_display_enable(struct dp_display *dp_display) +{ + int rc = 0; + struct dp_display_private *dp; + + if (!dp_display) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + if (dp->power_on) { + pr_debug("Link already setup, return\n"); + return 0; + } + + rc = dp->ctrl->on(dp->ctrl); + if (!rc) + dp->power_on = true; +error: + return rc; +} + +static int dp_display_post_enable(struct dp_display *dp_display) +{ + int rc = 0; + struct dp_display_private *dp; + + if (!dp_display) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + if (dp->audio_supported) { + dp->audio->bw_code = dp->link->link_params.bw_code; + dp->audio->lane_count = dp->link->link_params.lane_count; + dp->audio->on(dp->audio); + } + + complete_all(&dp->notification_comp); + + dp_display_update_hdcp_info(dp); + + if (dp_display_is_hdcp_enabled(dp)) { + cancel_delayed_work_sync(&dp->hdcp_cb_work); + + dp->hdcp_status = HDCP_STATE_AUTHENTICATING; + queue_delayed_work(dp->hdcp_workqueue, + &dp->hdcp_cb_work, HZ / 2); + } +end: + return rc; +} + +static int dp_display_pre_disable(struct dp_display *dp_display) +{ + int rc = 0; + struct dp_display_private *dp; + + if (!dp_display) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + if (dp_display_is_hdcp_enabled(dp)) { + dp->hdcp_status = HDCP_STATE_INACTIVE; + + cancel_delayed_work_sync(&dp->hdcp_cb_work); + if (dp->hdcp.ops->off) + dp->hdcp.ops->off(dp->hdcp.data); + } + + if (dp->usbpd->alt_mode_cfg_done && (dp->usbpd->hpd_high || + dp->usbpd->forced_disconnect)) + dp->link->psm_config(dp->link, &dp->panel->link_info, true); + + dp->ctrl->push_idle(dp->ctrl); +error: + return rc; +} + +static int dp_display_disable(struct dp_display *dp_display) +{ + int rc = 0; + struct dp_display_private *dp; + + if (!dp_display) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + if (!dp->power_on || !dp->core_initialized) + goto error; + + dp->ctrl->off(dp->ctrl); + + dp->power_on = false; + + complete_all(&dp->notification_comp); +error: + return rc; +} + +static int dp_request_irq(struct dp_display *dp_display) +{ + int rc = 0; + struct dp_display_private *dp; + + if (!dp_display) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + dp->irq = irq_of_parse_and_map(dp->pdev->dev.of_node, 0); + if (dp->irq < 0) { + rc = dp->irq; + pr_err("failed to get irq: %d\n", rc); + return rc; + } + + rc = devm_request_irq(&dp->pdev->dev, dp->irq, dp_display_irq, + IRQF_TRIGGER_HIGH, "dp_display_isr", dp); + if (rc < 0) { + pr_err("failed to request IRQ%u: %d\n", + dp->irq, rc); + return rc; + } + disable_irq(dp->irq); + + return 0; +} + +static struct dp_debug *dp_get_debug(struct dp_display *dp_display) +{ + struct dp_display_private *dp; + + if (!dp_display) { + pr_err("invalid input\n"); + return ERR_PTR(-EINVAL); + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + return dp->debug; +} + +static int dp_display_unprepare(struct dp_display *dp) +{ + return 0; +} + +static int dp_display_validate_mode(struct dp_display *dp, + struct dp_display_mode *mode) +{ + return 0; +} + +static int dp_display_get_modes(struct dp_display *dp, + struct dp_display_mode *dp_mode) +{ + struct dp_display_private *dp_display; + int ret = 0; + + if (!dp) { + pr_err("invalid params\n"); + return 0; + } + + dp_display = container_of(dp, struct dp_display_private, dp_display); + + ret = dp_display->panel->get_modes(dp_display->panel, + dp->connector, dp_mode); + if (dp_mode->timing.pixel_clk_khz) + dp->max_pclk_khz = dp_mode->timing.pixel_clk_khz; + return ret; +} + +static bool dp_display_check_video_test(struct dp_display *dp) +{ + struct dp_display_private *dp_display; + + if (!dp) { + pr_err("invalid params\n"); + return false; + } + + dp_display = container_of(dp, struct dp_display_private, dp_display); + + if (dp_display->panel->video_test) + return true; + + return false; +} + +static int dp_display_get_test_bpp(struct dp_display *dp) +{ + struct dp_display_private *dp_display; + + if (!dp) { + pr_err("invalid params\n"); + return 0; + } + + dp_display = container_of(dp, struct dp_display_private, dp_display); + + return dp_link_bit_depth_to_bpp( + dp_display->link->test_video.test_bit_depth); +} + +static int dp_display_probe(struct platform_device *pdev) +{ + int rc = 0; + struct dp_display_private *dp; + + if (!pdev || !pdev->dev.of_node) { + pr_err("pdev not found\n"); + return -ENODEV; + } + + dp = devm_kzalloc(&pdev->dev, sizeof(*dp), GFP_KERNEL); + if (!dp) + return -ENOMEM; + + init_completion(&dp->notification_comp); + + dp->pdev = pdev; + dp->name = "drm_dp"; + + rc = dp_init_sub_modules(dp); + if (rc) { + devm_kfree(&pdev->dev, dp); + return -EPROBE_DEFER; + } + + platform_set_drvdata(pdev, dp); + + g_dp_display = &dp->dp_display; + + g_dp_display->enable = dp_display_enable; + g_dp_display->post_enable = dp_display_post_enable; + g_dp_display->pre_disable = dp_display_pre_disable; + g_dp_display->disable = dp_display_disable; + g_dp_display->set_mode = dp_display_set_mode; + g_dp_display->validate_mode = dp_display_validate_mode; + g_dp_display->get_modes = dp_display_get_modes; + g_dp_display->prepare = dp_display_prepare; + g_dp_display->unprepare = dp_display_unprepare; + g_dp_display->request_irq = dp_request_irq; + g_dp_display->get_debug = dp_get_debug; + g_dp_display->send_hpd_event = dp_display_send_hpd_event; + g_dp_display->is_video_test = dp_display_check_video_test; + g_dp_display->get_test_bpp = dp_display_get_test_bpp; + + rc = component_add(&pdev->dev, &dp_display_comp_ops); + if (rc) { + pr_err("component add failed, rc=%d\n", rc); + dp_display_deinit_sub_modules(dp); + devm_kfree(&pdev->dev, dp); + } + + return rc; +} + +int dp_display_get_displays(void **displays, int count) +{ + if (!displays) { + pr_err("invalid data\n"); + return -EINVAL; + } + + if (count != 1) { + pr_err("invalid number of displays\n"); + return -EINVAL; + } + + displays[0] = g_dp_display; + return count; +} + +int dp_display_get_num_of_displays(void) +{ + return 1; +} + +static int dp_display_remove(struct platform_device *pdev) +{ + struct dp_display_private *dp; + + if (!pdev) + return -EINVAL; + + dp = platform_get_drvdata(pdev); + + dp_display_deinit_sub_modules(dp); + + platform_set_drvdata(pdev, NULL); + devm_kfree(&pdev->dev, dp); + + return 0; +} + +static struct platform_driver dp_display_driver = { + .probe = dp_display_probe, + .remove = dp_display_remove, + .driver = { + .name = "msm-dp-display", + .of_match_table = dp_dt_match, + }, +}; + +static int __init dp_display_init(void) +{ + int ret; + + ret = platform_driver_register(&dp_display_driver); + if (ret) { + pr_err("driver register failed"); + return ret; + } + + return ret; +} +module_init(dp_display_init); + +static void __exit dp_display_cleanup(void) +{ + platform_driver_unregister(&dp_display_driver); +} +module_exit(dp_display_cleanup); + diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h new file mode 100644 index 000000000000..5539d61ef0d7 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_display.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _DP_DISPLAY_H_ +#define _DP_DISPLAY_H_ + +#include + +#include "dp_panel.h" + +struct dp_display { + struct drm_device *drm_dev; + struct dp_bridge *bridge; + struct drm_connector *connector; + bool is_connected; + u32 max_pclk_khz; + + int (*enable)(struct dp_display *dp_display); + int (*post_enable)(struct dp_display *dp_display); + + int (*pre_disable)(struct dp_display *dp_display); + int (*disable)(struct dp_display *dp_display); + + int (*set_mode)(struct dp_display *dp_display, + struct dp_display_mode *mode); + int (*validate_mode)(struct dp_display *dp_display, + struct dp_display_mode *mode); + int (*get_modes)(struct dp_display *dp_display, + struct dp_display_mode *dp_mode); + int (*prepare)(struct dp_display *dp_display); + int (*unprepare)(struct dp_display *dp_display); + int (*request_irq)(struct dp_display *dp_display); + struct dp_debug *(*get_debug)(struct dp_display *dp_display); + void (*send_hpd_event)(struct dp_display *dp_display); + bool (*is_video_test)(struct dp_display *dp_display); + int (*get_test_bpp)(struct dp_display *dp_display); +}; + +int dp_display_get_num_of_displays(void); +int dp_display_get_displays(void **displays, int count); +#endif /* _DP_DISPLAY_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_drm.c b/drivers/gpu/drm/msm/dp/dp_drm.c new file mode 100644 index 000000000000..06f85580ca7f --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_drm.c @@ -0,0 +1,538 @@ +/* + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "[drm-dp]: %s: " fmt, __func__ + +#include +#include +#include + +#include "msm_drv.h" +#include "msm_kms.h" +#include "sde_connector.h" +#include "dp_drm.h" +#include "dp_debug.h" + +#define to_dp_bridge(x) container_of((x), struct dp_bridge, base) + +static void convert_to_dp_mode(const struct drm_display_mode *drm_mode, + struct dp_display_mode *dp_mode, struct dp_display *dp) +{ + const u32 num_components = 3; + + memset(dp_mode, 0, sizeof(*dp_mode)); + + dp_mode->timing.h_active = drm_mode->hdisplay; + dp_mode->timing.h_back_porch = drm_mode->htotal - drm_mode->hsync_end; + dp_mode->timing.h_sync_width = drm_mode->htotal - + (drm_mode->hsync_start + dp_mode->timing.h_back_porch); + dp_mode->timing.h_front_porch = drm_mode->hsync_start - + drm_mode->hdisplay; + dp_mode->timing.h_skew = drm_mode->hskew; + + dp_mode->timing.v_active = drm_mode->vdisplay; + dp_mode->timing.v_back_porch = drm_mode->vtotal - drm_mode->vsync_end; + dp_mode->timing.v_sync_width = drm_mode->vtotal - + (drm_mode->vsync_start + dp_mode->timing.v_back_porch); + + dp_mode->timing.v_front_porch = drm_mode->vsync_start - + drm_mode->vdisplay; + + if (dp->is_video_test(dp)) + dp_mode->timing.bpp = dp->get_test_bpp(dp); + else + dp_mode->timing.bpp = dp->connector->display_info.bpc * + num_components; + + if (!dp_mode->timing.bpp) + dp_mode->timing.bpp = 24; + + dp_mode->timing.refresh_rate = drm_mode->vrefresh; + + dp_mode->timing.pixel_clk_khz = drm_mode->clock; + + dp_mode->timing.v_active_low = + !!(drm_mode->flags & DRM_MODE_FLAG_NVSYNC); + + dp_mode->timing.h_active_low = + !!(drm_mode->flags & DRM_MODE_FLAG_NHSYNC); +} + +static void convert_to_drm_mode(const struct dp_display_mode *dp_mode, + struct drm_display_mode *drm_mode) +{ + u32 flags = 0; + + memset(drm_mode, 0, sizeof(*drm_mode)); + + drm_mode->hdisplay = dp_mode->timing.h_active; + drm_mode->hsync_start = drm_mode->hdisplay + + dp_mode->timing.h_front_porch; + drm_mode->hsync_end = drm_mode->hsync_start + + dp_mode->timing.h_sync_width; + drm_mode->htotal = drm_mode->hsync_end + dp_mode->timing.h_back_porch; + drm_mode->hskew = dp_mode->timing.h_skew; + + drm_mode->vdisplay = dp_mode->timing.v_active; + drm_mode->vsync_start = drm_mode->vdisplay + + dp_mode->timing.v_front_porch; + drm_mode->vsync_end = drm_mode->vsync_start + + dp_mode->timing.v_sync_width; + drm_mode->vtotal = drm_mode->vsync_end + dp_mode->timing.v_back_porch; + + drm_mode->vrefresh = dp_mode->timing.refresh_rate; + drm_mode->clock = dp_mode->timing.pixel_clk_khz; + + if (dp_mode->timing.h_active_low) + flags |= DRM_MODE_FLAG_NHSYNC; + else + flags |= DRM_MODE_FLAG_PHSYNC; + + if (dp_mode->timing.v_active_low) + flags |= DRM_MODE_FLAG_NVSYNC; + else + flags |= DRM_MODE_FLAG_PVSYNC; + + drm_mode->flags = flags; + + drm_mode->type = 0x48; + drm_mode_set_name(drm_mode); +} + +static int dp_bridge_attach(struct drm_bridge *dp_bridge) +{ + struct dp_bridge *bridge = to_dp_bridge(dp_bridge); + + if (!dp_bridge) { + pr_err("Invalid params\n"); + return -EINVAL; + } + + pr_debug("[%d] attached\n", bridge->id); + + return 0; +} + +static void dp_bridge_pre_enable(struct drm_bridge *drm_bridge) +{ + int rc = 0; + struct dp_bridge *bridge; + struct dp_display *dp; + + if (!drm_bridge) { + pr_err("Invalid params\n"); + return; + } + + bridge = to_dp_bridge(drm_bridge); + dp = bridge->display; + + /* By this point mode should have been validated through mode_fixup */ + rc = dp->set_mode(dp, &bridge->dp_mode); + if (rc) { + pr_err("[%d] failed to perform a mode set, rc=%d\n", + bridge->id, rc); + return; + } + + rc = dp->prepare(dp); + if (rc) { + pr_err("[%d] DP display prepare failed, rc=%d\n", + bridge->id, rc); + return; + } + + rc = dp->enable(dp); + if (rc) { + pr_err("[%d] DP display enable failed, rc=%d\n", + bridge->id, rc); + dp->unprepare(dp); + } +} + +static void dp_bridge_enable(struct drm_bridge *drm_bridge) +{ + int rc = 0; + struct dp_bridge *bridge; + struct dp_display *dp; + + if (!drm_bridge) { + pr_err("Invalid params\n"); + return; + } + + bridge = to_dp_bridge(drm_bridge); + dp = bridge->display; + + rc = dp->post_enable(dp); + if (rc) + pr_err("[%d] DP display post enable failed, rc=%d\n", + bridge->id, rc); +} + +static void dp_bridge_disable(struct drm_bridge *drm_bridge) +{ + int rc = 0; + struct dp_bridge *bridge; + struct dp_display *dp; + + if (!drm_bridge) { + pr_err("Invalid params\n"); + return; + } + + bridge = to_dp_bridge(drm_bridge); + dp = bridge->display; + + rc = dp->pre_disable(dp); + if (rc) { + pr_err("[%d] DP display pre disable failed, rc=%d\n", + bridge->id, rc); + } +} + +static void dp_bridge_post_disable(struct drm_bridge *drm_bridge) +{ + int rc = 0; + struct dp_bridge *bridge; + struct dp_display *dp; + + if (!drm_bridge) { + pr_err("Invalid params\n"); + return; + } + + bridge = to_dp_bridge(drm_bridge); + dp = bridge->display; + + rc = dp->disable(dp); + if (rc) { + pr_err("[%d] DP display disable failed, rc=%d\n", + bridge->id, rc); + return; + } + + rc = dp->unprepare(dp); + if (rc) { + pr_err("[%d] DP display unprepare failed, rc=%d\n", + bridge->id, rc); + return; + } +} + +static void dp_bridge_mode_set(struct drm_bridge *drm_bridge, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct dp_bridge *bridge; + struct dp_display *dp; + + if (!drm_bridge || !mode || !adjusted_mode) { + pr_err("Invalid params\n"); + return; + } + + bridge = to_dp_bridge(drm_bridge); + dp = bridge->display; + + memset(&bridge->dp_mode, 0x0, sizeof(struct dp_display_mode)); + convert_to_dp_mode(adjusted_mode, &bridge->dp_mode, dp); +} + +static bool dp_bridge_mode_fixup(struct drm_bridge *drm_bridge, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + int rc = 0; + bool ret = true; + struct dp_display_mode dp_mode; + struct dp_bridge *bridge; + struct dp_display *dp; + + if (!drm_bridge || !mode || !adjusted_mode) { + pr_err("Invalid params\n"); + ret = false; + goto end; + } + + bridge = to_dp_bridge(drm_bridge); + dp = bridge->display; + + convert_to_dp_mode(mode, &dp_mode, dp); + + rc = dp->validate_mode(dp, &dp_mode); + if (rc) { + pr_err("[%d] mode is not valid, rc=%d\n", bridge->id, rc); + ret = false; + } else { + convert_to_drm_mode(&dp_mode, adjusted_mode); + } +end: + return ret; +} + +static const struct drm_bridge_funcs dp_bridge_ops = { + .attach = dp_bridge_attach, + .mode_fixup = dp_bridge_mode_fixup, + .pre_enable = dp_bridge_pre_enable, + .enable = dp_bridge_enable, + .disable = dp_bridge_disable, + .post_disable = dp_bridge_post_disable, + .mode_set = dp_bridge_mode_set, +}; + +int dp_connector_post_init(struct drm_connector *connector, + void *info, + void *display) +{ + struct dp_display *dp_display = display; + + if (!info || !dp_display) + return -EINVAL; + + dp_display->connector = connector; + return 0; +} + +int dp_connector_get_mode_info(const struct drm_display_mode *drm_mode, + struct msm_mode_info *mode_info, u32 max_mixer_width) +{ + const u32 dual_lm = 2; + const u32 single_lm = 1; + const u32 single_intf = 1; + const u32 no_enc = 0; + struct msm_display_topology *topology; + + if (!drm_mode || !mode_info || !max_mixer_width) { + pr_err("invalid params\n"); + return -EINVAL; + } + + topology = &mode_info->topology; + topology->num_lm = (max_mixer_width <= drm_mode->hdisplay) ? + dual_lm : single_lm; + topology->num_enc = no_enc; + topology->num_intf = single_intf; + + mode_info->frame_rate = drm_mode->vrefresh; + mode_info->vtotal = drm_mode->vtotal; + mode_info->comp_info.comp_type = MSM_DISPLAY_COMPRESSION_NONE; + + return 0; +} + +int dp_connector_get_info(struct msm_display_info *info, void *data) +{ + struct dp_display *display = data; + + if (!info || !display) { + pr_err("invalid params\n"); + return -EINVAL; + } + + info->intf_type = DRM_MODE_CONNECTOR_DisplayPort; + + info->num_of_h_tiles = 1; + info->h_tile_instance[0] = 0; + info->is_connected = display->is_connected; + info->capabilities = MSM_DISPLAY_CAP_VID_MODE | MSM_DISPLAY_CAP_EDID | + MSM_DISPLAY_CAP_HOT_PLUG; + + return 0; +} + +enum drm_connector_status dp_connector_detect(struct drm_connector *conn, + bool force, + void *display) +{ + enum drm_connector_status status = connector_status_unknown; + struct msm_display_info info; + int rc; + + if (!conn || !display) + return status; + + /* get display dp_info */ + memset(&info, 0x0, sizeof(info)); + rc = dp_connector_get_info(&info, display); + if (rc) { + pr_err("failed to get display info, rc=%d\n", rc); + return connector_status_disconnected; + } + + if (info.capabilities & MSM_DISPLAY_CAP_HOT_PLUG) + status = (info.is_connected ? connector_status_connected : + connector_status_disconnected); + else + status = connector_status_connected; + + conn->display_info.width_mm = info.width_mm; + conn->display_info.height_mm = info.height_mm; + + return status; +} + +void dp_connector_send_hpd_event(void *display) +{ + struct dp_display *dp; + + if (!display) { + pr_err("invalid input\n"); + return; + } + + dp = display; + + if (dp->send_hpd_event) + dp->send_hpd_event(dp); +} + +int dp_connector_get_modes(struct drm_connector *connector, + void *display) +{ + int rc = 0; + struct dp_display *dp; + struct dp_display_mode *dp_mode = NULL; + struct drm_display_mode *m, drm_mode; + + if (!connector || !display) + return 0; + + dp = display; + + dp_mode = kzalloc(sizeof(*dp_mode), GFP_KERNEL); + if (!dp_mode) + return 0; + + /* pluggable case assumes EDID is read when HPD */ + if (dp->is_connected) { + rc = dp->get_modes(dp, dp_mode); + if (!rc) + pr_err("failed to get DP sink modes, rc=%d\n", rc); + + if (dp_mode->timing.pixel_clk_khz) { /* valid DP mode */ + memset(&drm_mode, 0x0, sizeof(drm_mode)); + convert_to_drm_mode(dp_mode, &drm_mode); + m = drm_mode_duplicate(connector->dev, &drm_mode); + if (!m) { + pr_err("failed to add mode %ux%u\n", + drm_mode.hdisplay, + drm_mode.vdisplay); + kfree(dp_mode); + return 0; + } + m->width_mm = connector->display_info.width_mm; + m->height_mm = connector->display_info.height_mm; + drm_mode_probed_add(connector, m); + } + } else { + pr_err("No sink connected\n"); + } + kfree(dp_mode); + + return rc; +} + +int dp_drm_bridge_init(void *data, struct drm_encoder *encoder) +{ + int rc = 0; + struct dp_bridge *bridge; + struct drm_device *dev; + struct dp_display *display = data; + struct msm_drm_private *priv = NULL; + + bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); + if (!bridge) { + rc = -ENOMEM; + goto error; + } + + dev = display->drm_dev; + bridge->display = display; + bridge->base.funcs = &dp_bridge_ops; + bridge->base.encoder = encoder; + + priv = dev->dev_private; + + rc = drm_bridge_attach(dev, &bridge->base); + if (rc) { + pr_err("failed to attach bridge, rc=%d\n", rc); + goto error_free_bridge; + } + + rc = display->request_irq(display); + if (rc) { + pr_err("request_irq failed, rc=%d\n", rc); + goto error_free_bridge; + } + + encoder->bridge = &bridge->base; + priv->bridges[priv->num_bridges++] = &bridge->base; + display->bridge = bridge; + + return 0; +error_free_bridge: + kfree(bridge); +error: + return rc; +} + +void dp_drm_bridge_deinit(void *data) +{ + struct dp_display *display = data; + struct dp_bridge *bridge = display->bridge; + + if (bridge && bridge->base.encoder) + bridge->base.encoder->bridge = NULL; + + kfree(bridge); +} + +enum drm_mode_status dp_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode, + void *display) +{ + struct dp_display *dp_disp; + struct dp_debug *debug; + + if (!mode || !display) { + pr_err("invalid params\n"); + return MODE_ERROR; + } + + dp_disp = display; + debug = dp_disp->get_debug(dp_disp); + + if (debug->debug_en) { + if (mode->hdisplay == debug->hdisplay && + mode->vdisplay == debug->vdisplay && + mode->vrefresh == debug->vrefresh && + mode->clock <= dp_disp->max_pclk_khz) + return MODE_OK; + else + return MODE_ERROR; + } else { + if (mode->vrefresh == 0) { + int vrefresh = (mode->clock * 1000) / + (mode->vtotal * mode->htotal); + if (vrefresh > 60) + return MODE_BAD; + } + + if (mode->clock > dp_disp->max_pclk_khz) + return MODE_BAD; + else + return MODE_OK; + } +} diff --git a/drivers/gpu/drm/msm/dp/dp_drm.h b/drivers/gpu/drm/msm/dp/dp_drm.h new file mode 100644 index 000000000000..53570f5a2a7a --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_drm.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _DP_DRM_H_ +#define _DP_DRM_H_ + +#include +#include +#include +#include + +#include "msm_drv.h" +#include "dp_display.h" + +struct dp_bridge { + struct drm_bridge base; + u32 id; + + struct dp_display *display; + struct dp_display_mode dp_mode; +}; + +/** + * dp_connector_post_init - callback to perform additional initialization steps + * @connector: Pointer to drm connector structure + * @info: Pointer to sde connector info structure + * @display: Pointer to private display handle + * Returns: Zero on success + */ +int dp_connector_post_init(struct drm_connector *connector, + void *info, + void *display); + +/** + * dp_connector_detect - callback to determine if connector is connected + * @connector: Pointer to drm connector structure + * @force: Force detect setting from drm framework + * @display: Pointer to private display handle + * Returns: Connector 'is connected' status + */ +enum drm_connector_status dp_connector_detect(struct drm_connector *conn, + bool force, + void *display); + +/** + * dp_connector_get_modes - callback to add drm modes via drm_mode_probed_add() + * @connector: Pointer to drm connector structure + * @display: Pointer to private display handle + * Returns: Number of modes added + */ +int dp_connector_get_modes(struct drm_connector *connector, + void *display); + +/** + * dp_connector_mode_valid - callback to determine if specified mode is valid + * @connector: Pointer to drm connector structure + * @mode: Pointer to drm mode structure + * @display: Pointer to private display handle + * Returns: Validity status for specified mode + */ +enum drm_mode_status dp_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode, + void *display); + +/** + * dp_connector_get_mode_info - retrieve information of the mode selected + * @drm_mode: Display mode set for the display + * @mode_info: Out parameter. Information of the mode + * @max_mixer_width: max width supported by HW layer mixer + * Returns: zero on success + */ +int dp_connector_get_mode_info(const struct drm_display_mode *drm_mode, + struct msm_mode_info *mode_info, + u32 max_mixer_width); + +int dp_connector_get_info(struct msm_display_info *info, void *display); + +void dp_connector_send_hpd_event(void *display); + +int dp_drm_bridge_init(void *display, + struct drm_encoder *encoder); + +void dp_drm_bridge_deinit(void *display); +#endif /* _DP_DRM_H_ */ + diff --git a/drivers/gpu/drm/msm/dp/dp_hdcp2p2.c b/drivers/gpu/drm/msm/dp/dp_hdcp2p2.c new file mode 100644 index 000000000000..016e1b873662 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_hdcp2p2.c @@ -0,0 +1,927 @@ +/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "[dp-hdcp2p2] %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sde_hdcp.h" + +#define DP_INTR_STATUS2 (0x00000024) +#define DP_INTR_STATUS3 (0x00000028) +#define dp_read(offset) readl_relaxed((offset)) +#define dp_write(offset, data) writel_relaxed((data), (offset)) +#define DP_HDCP_RXCAPS_LENGTH 3 + +enum dp_hdcp2p2_sink_status { + SINK_DISCONNECTED, + SINK_CONNECTED +}; + +enum dp_auth_status { + DP_HDCP_AUTH_STATUS_FAILURE, + DP_HDCP_AUTH_STATUS_SUCCESS +}; + +struct dp_hdcp2p2_ctrl { + atomic_t auth_state; + enum dp_hdcp2p2_sink_status sink_status; /* Is sink connected */ + struct dp_hdcp2p2_interrupts *intr; + struct sde_hdcp_init_data init_data; + struct mutex mutex; /* mutex to protect access to ctrl */ + struct mutex msg_lock; /* mutex to protect access to msg buffer */ + struct mutex wakeup_mutex; /* mutex to protect access to wakeup call*/ + struct sde_hdcp_ops *ops; + void *lib_ctx; /* Handle to HDCP 2.2 Trustzone library */ + struct hdcp_txmtr_ops *lib; /* Ops for driver to call into TZ */ + enum hdcp_wakeup_cmd wakeup_cmd; + enum dp_auth_status auth_status; + + struct task_struct *thread; + struct kthread_worker worker; + struct kthread_work status; + struct kthread_work auth; + struct kthread_work send_msg; + struct kthread_work recv_msg; + struct kthread_work link; + char *msg_buf; + uint32_t send_msg_len; /* length of all parameters in msg */ + uint32_t timeout; + uint32_t num_messages; + struct hdcp_msg_part msg_part[HDCP_MAX_MESSAGE_PARTS]; + u8 sink_rx_status; + u8 rx_status; + char abort_mask; + + bool cp_irq_done; + bool polling; +}; + +struct dp_hdcp2p2_int_set { + u32 interrupt; + char *name; + void (*func)(struct dp_hdcp2p2_ctrl *ctrl); +}; + +struct dp_hdcp2p2_interrupts { + u32 reg; + struct dp_hdcp2p2_int_set *int_set; +}; + +static inline bool dp_hdcp2p2_is_valid_state(struct dp_hdcp2p2_ctrl *ctrl) +{ + if (ctrl->wakeup_cmd == HDCP_WKUP_CMD_AUTHENTICATE) + return true; + + if (atomic_read(&ctrl->auth_state) != HDCP_STATE_INACTIVE) + return true; + + return false; +} + +static int dp_hdcp2p2_copy_buf(struct dp_hdcp2p2_ctrl *ctrl, + struct hdcp_wakeup_data *data) +{ + int i = 0; + + if (!data || !data->message_data) + return 0; + + mutex_lock(&ctrl->msg_lock); + + ctrl->timeout = data->timeout; + ctrl->num_messages = data->message_data->num_messages; + ctrl->send_msg_len = 0; /* Total len of all messages */ + + for (i = 0; i < ctrl->num_messages ; i++) + ctrl->send_msg_len += data->message_data->messages[i].length; + + memcpy(ctrl->msg_part, data->message_data->messages, + sizeof(data->message_data->messages)); + + ctrl->rx_status = data->message_data->rx_status; + ctrl->abort_mask = data->abort_mask; + + if (!data->send_msg_len) { + mutex_unlock(&ctrl->msg_lock); + return 0; + } + + kzfree(ctrl->msg_buf); + + ctrl->msg_buf = kzalloc(ctrl->send_msg_len, GFP_KERNEL); + + if (!ctrl->msg_buf) { + mutex_unlock(&ctrl->msg_lock); + return -ENOMEM; + } + + /* ignore first byte as it contains message id */ + memcpy(ctrl->msg_buf, data->send_msg_buf + 1, ctrl->send_msg_len); + + mutex_unlock(&ctrl->msg_lock); + + return 0; +} + +static int dp_hdcp2p2_wakeup(struct hdcp_wakeup_data *data) +{ + struct dp_hdcp2p2_ctrl *ctrl; + u32 const default_timeout_us = 500; + + if (!data) { + pr_err("invalid input\n"); + return -EINVAL; + } + + ctrl = data->context; + if (!ctrl) { + pr_err("invalid ctrl\n"); + return -EINVAL; + } + + mutex_lock(&ctrl->wakeup_mutex); + + ctrl->wakeup_cmd = data->cmd; + + if (data->timeout) + ctrl->timeout = (data->timeout) * 2; + else + ctrl->timeout = default_timeout_us; + + if (!dp_hdcp2p2_is_valid_state(ctrl)) { + pr_err("invalid state\n"); + goto exit; + } + + if (dp_hdcp2p2_copy_buf(ctrl, data)) + goto exit; + + if (ctrl->wakeup_cmd == HDCP_WKUP_CMD_STATUS_SUCCESS) + ctrl->auth_status = DP_HDCP_AUTH_STATUS_SUCCESS; + else if (ctrl->wakeup_cmd == HDCP_WKUP_CMD_STATUS_FAILED) + ctrl->auth_status = DP_HDCP_AUTH_STATUS_FAILURE; + + switch (ctrl->wakeup_cmd) { + case HDCP_WKUP_CMD_SEND_MESSAGE: + kthread_queue_work(&ctrl->worker, &ctrl->send_msg); + break; + case HDCP_WKUP_CMD_RECV_MESSAGE: + kthread_queue_work(&ctrl->worker, &ctrl->recv_msg); + break; + case HDCP_WKUP_CMD_STATUS_SUCCESS: + case HDCP_WKUP_CMD_STATUS_FAILED: + kthread_queue_work(&ctrl->worker, &ctrl->status); + break; + case HDCP_WKUP_CMD_LINK_POLL: + if (ctrl->cp_irq_done) + kthread_queue_work(&ctrl->worker, &ctrl->recv_msg); + else + ctrl->polling = true; + break; + case HDCP_WKUP_CMD_AUTHENTICATE: + kthread_queue_work(&ctrl->worker, &ctrl->auth); + break; + default: + pr_err("invalid wakeup command %d\n", ctrl->wakeup_cmd); + } +exit: + mutex_unlock(&ctrl->wakeup_mutex); + + return 0; +} + +static inline void dp_hdcp2p2_wakeup_lib(struct dp_hdcp2p2_ctrl *ctrl, + struct hdcp_lib_wakeup_data *data) +{ + int rc = 0; + + if (ctrl && ctrl->lib && ctrl->lib->wakeup && + data && (data->cmd != HDCP_LIB_WKUP_CMD_INVALID)) { + rc = ctrl->lib->wakeup(data); + if (rc) + pr_err("error sending %s to lib\n", + hdcp_lib_cmd_to_str(data->cmd)); + } +} + +static void dp_hdcp2p2_reset(struct dp_hdcp2p2_ctrl *ctrl) +{ + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + ctrl->sink_status = SINK_DISCONNECTED; + atomic_set(&ctrl->auth_state, HDCP_STATE_INACTIVE); +} + +static void dp_hdcp2p2_set_interrupts(struct dp_hdcp2p2_ctrl *ctrl, bool enable) +{ + void __iomem *base = ctrl->init_data.core_io->base; + struct dp_hdcp2p2_interrupts *intr = ctrl->intr; + + while (intr && intr->reg) { + struct dp_hdcp2p2_int_set *int_set = intr->int_set; + u32 interrupts = 0; + + while (int_set && int_set->interrupt) { + interrupts |= int_set->interrupt; + int_set++; + } + + if (enable) + dp_write(base + intr->reg, + dp_read(base + intr->reg) | interrupts); + else + dp_write(base + intr->reg, + dp_read(base + intr->reg) & ~interrupts); + intr++; + } +} + +static void dp_hdcp2p2_off(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = (struct dp_hdcp2p2_ctrl *)input; + struct hdcp_wakeup_data cdata = {HDCP_WKUP_CMD_AUTHENTICATE}; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("hdcp is off\n"); + return; + } + + dp_hdcp2p2_set_interrupts(ctrl, false); + + dp_hdcp2p2_reset(ctrl); + + kthread_flush_worker(&ctrl->worker); + + cdata.context = input; + dp_hdcp2p2_wakeup(&cdata); +} + +static int dp_hdcp2p2_authenticate(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = input; + struct hdcp_wakeup_data cdata = {HDCP_WKUP_CMD_AUTHENTICATE}; + int rc = 0; + + kthread_flush_worker(&ctrl->worker); + + dp_hdcp2p2_set_interrupts(ctrl, true); + + ctrl->sink_status = SINK_CONNECTED; + atomic_set(&ctrl->auth_state, HDCP_STATE_AUTHENTICATING); + + cdata.context = input; + dp_hdcp2p2_wakeup(&cdata); + + return rc; +} + +static int dp_hdcp2p2_reauthenticate(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = (struct dp_hdcp2p2_ctrl *)input; + + if (!ctrl) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp_hdcp2p2_reset((struct dp_hdcp2p2_ctrl *)input); + + return dp_hdcp2p2_authenticate(input); +} + +static void dp_hdcp2p2_min_level_change(void *client_ctx, + int min_enc_level) +{ + struct dp_hdcp2p2_ctrl *ctrl = (struct dp_hdcp2p2_ctrl *)client_ctx; + struct hdcp_lib_wakeup_data cdata = { + HDCP_LIB_WKUP_CMD_QUERY_STREAM_TYPE}; + bool enc_notify = true; + int enc_lvl; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + switch (min_enc_level) { + case 0: + enc_lvl = HDCP_STATE_AUTH_ENC_NONE; + break; + case 1: + enc_lvl = HDCP_STATE_AUTH_ENC_1X; + break; + case 2: + enc_lvl = HDCP_STATE_AUTH_ENC_2P2; + break; + default: + enc_notify = false; + } + + pr_debug("enc level changed %d\n", min_enc_level); + + cdata.context = ctrl->lib_ctx; + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); + + if (enc_notify && ctrl->init_data.notify_status) + ctrl->init_data.notify_status(ctrl->init_data.cb_data, enc_lvl); +} + +static void dp_hdcp2p2_auth_failed(struct dp_hdcp2p2_ctrl *ctrl) +{ + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_hdcp2p2_set_interrupts(ctrl, false); + + atomic_set(&ctrl->auth_state, HDCP_STATE_AUTH_FAIL); + + /* notify DP about HDCP failure */ + ctrl->init_data.notify_status(ctrl->init_data.cb_data, + HDCP_STATE_AUTH_FAIL); +} + +static int dp_hdcp2p2_aux_read_message(struct dp_hdcp2p2_ctrl *ctrl, + u8 *buf, int size, int offset, u32 timeout) +{ + int const max_size = 16; + int rc = 0, read_size = 0, bytes_read = 0; + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("hdcp is off\n"); + return -EINVAL; + } + + do { + read_size = min(size, max_size); + + bytes_read = drm_dp_dpcd_read(ctrl->init_data.drm_aux, + offset, buf, read_size); + if (bytes_read != read_size) { + pr_err("fail: offset(0x%x), size(0x%x), rc(0x%x)\n", + offset, read_size, bytes_read); + break; + } + + buf += read_size; + offset += read_size; + size -= read_size; + } while (size > 0); + + return rc; +} + +static int dp_hdcp2p2_aux_write_message(struct dp_hdcp2p2_ctrl *ctrl, + u8 *buf, int size, uint offset, uint timeout) +{ + int const max_size = 16; + int rc = 0, write_size = 0, bytes_written = 0; + + do { + write_size = min(size, max_size); + + bytes_written = drm_dp_dpcd_write(ctrl->init_data.drm_aux, + offset, buf, write_size); + if (bytes_written != write_size) { + pr_err("fail: offset(0x%x), size(0x%x), rc(0x%x)\n", + offset, write_size, bytes_written); + break; + } + + buf += write_size; + offset += write_size; + size -= write_size; + } while (size > 0); + + return rc; +} + +static bool dp_hdcp2p2_feature_supported(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = input; + struct hdcp_txmtr_ops *lib = NULL; + bool supported = false; + + if (!ctrl) { + pr_err("invalid input\n"); + goto end; + } + + lib = ctrl->lib; + if (!lib) { + pr_err("invalid lib ops data\n"); + goto end; + } + + if (lib->feature_supported) + supported = lib->feature_supported( + ctrl->lib_ctx); +end: + return supported; +} + +static void dp_hdcp2p2_send_msg_work(struct kthread_work *work) +{ + int rc = 0; + struct dp_hdcp2p2_ctrl *ctrl = container_of(work, + struct dp_hdcp2p2_ctrl, send_msg); + struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID}; + + if (!ctrl) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto exit; + } + + cdata.context = ctrl->lib_ctx; + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("hdcp is off\n"); + goto exit; + } + + mutex_lock(&ctrl->msg_lock); + + rc = dp_hdcp2p2_aux_write_message(ctrl, ctrl->msg_buf, + ctrl->send_msg_len, ctrl->msg_part->offset, + ctrl->timeout); + if (rc) { + pr_err("Error sending msg to sink %d\n", rc); + mutex_unlock(&ctrl->msg_lock); + goto exit; + } + + cdata.cmd = HDCP_LIB_WKUP_CMD_MSG_SEND_SUCCESS; + cdata.timeout = ctrl->timeout; + mutex_unlock(&ctrl->msg_lock); + +exit: + if (rc == -ETIMEDOUT) + cdata.cmd = HDCP_LIB_WKUP_CMD_MSG_RECV_TIMEOUT; + else if (rc) + cdata.cmd = HDCP_LIB_WKUP_CMD_MSG_RECV_FAILED; + + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); +} + +static int dp_hdcp2p2_get_msg_from_sink(struct dp_hdcp2p2_ctrl *ctrl) +{ + int rc = 0; + char *recvd_msg_buf = NULL; + struct hdcp_lib_wakeup_data cdata = { HDCP_LIB_WKUP_CMD_INVALID }; + + cdata.context = ctrl->lib_ctx; + + recvd_msg_buf = kzalloc(ctrl->send_msg_len, GFP_KERNEL); + if (!recvd_msg_buf) { + rc = -ENOMEM; + goto exit; + } + + rc = dp_hdcp2p2_aux_read_message(ctrl, recvd_msg_buf, + ctrl->send_msg_len, ctrl->msg_part->offset, + ctrl->timeout); + if (rc) { + pr_err("error reading message %d\n", rc); + goto exit; + } + + cdata.recvd_msg_buf = recvd_msg_buf; + cdata.recvd_msg_len = ctrl->send_msg_len; + cdata.timeout = ctrl->timeout; +exit: + if (rc == -ETIMEDOUT) + cdata.cmd = HDCP_LIB_WKUP_CMD_MSG_RECV_TIMEOUT; + else if (rc) + cdata.cmd = HDCP_LIB_WKUP_CMD_MSG_RECV_FAILED; + else + cdata.cmd = HDCP_LIB_WKUP_CMD_MSG_RECV_SUCCESS; + + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); + kfree(recvd_msg_buf); + + return rc; +} + +static void dp_hdcp2p2_recv_msg_work(struct kthread_work *work) +{ + struct hdcp_lib_wakeup_data cdata = { HDCP_LIB_WKUP_CMD_INVALID }; + struct dp_hdcp2p2_ctrl *ctrl = container_of(work, + struct dp_hdcp2p2_ctrl, recv_msg); + + cdata.context = ctrl->lib_ctx; + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("hdcp is off\n"); + return; + } + + if (ctrl->rx_status) { + if (!ctrl->cp_irq_done) { + pr_debug("waiting for CP_IRQ\n"); + ctrl->polling = true; + return; + } + + if (ctrl->rx_status & ctrl->sink_rx_status) { + ctrl->cp_irq_done = false; + ctrl->sink_rx_status = 0; + ctrl->rx_status = 0; + } + } + + dp_hdcp2p2_get_msg_from_sink(ctrl); +} + +static void dp_hdcp2p2_auth_status_work(struct kthread_work *work) +{ + struct dp_hdcp2p2_ctrl *ctrl = container_of(work, + struct dp_hdcp2p2_ctrl, status); + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("hdcp is off\n"); + return; + } + + if (ctrl->auth_status == DP_HDCP_AUTH_STATUS_SUCCESS) { + ctrl->init_data.notify_status(ctrl->init_data.cb_data, + HDCP_STATE_AUTHENTICATED); + + atomic_set(&ctrl->auth_state, HDCP_STATE_AUTHENTICATED); + } else { + dp_hdcp2p2_auth_failed(ctrl); + } +} + +static void dp_hdcp2p2_link_work(struct kthread_work *work) +{ + int rc = 0; + struct dp_hdcp2p2_ctrl *ctrl = container_of(work, + struct dp_hdcp2p2_ctrl, link); + struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID}; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_AUTH_FAIL || + atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("invalid hdcp state\n"); + return; + } + + cdata.context = ctrl->lib_ctx; + + if (ctrl->sink_rx_status & ctrl->abort_mask) { + if (ctrl->sink_rx_status & BIT(3)) + pr_err("reauth_req set by sink\n"); + + if (ctrl->sink_rx_status & BIT(4)) + pr_err("link failure reported by sink\n"); + + ctrl->sink_rx_status = 0; + ctrl->rx_status = 0; + + rc = -ENOLINK; + + cdata.cmd = HDCP_LIB_WKUP_CMD_LINK_FAILED; + atomic_set(&ctrl->auth_state, HDCP_STATE_AUTH_FAIL); + goto exit; + } + + if (ctrl->polling && (ctrl->sink_rx_status & ctrl->rx_status)) { + ctrl->sink_rx_status = 0; + ctrl->rx_status = 0; + + dp_hdcp2p2_get_msg_from_sink(ctrl); + + ctrl->polling = false; + } else { + ctrl->cp_irq_done = true; + } +exit: + if (rc) + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); +} + +static void dp_hdcp2p2_auth_work(struct kthread_work *work) +{ + struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID}; + struct dp_hdcp2p2_ctrl *ctrl = container_of(work, + struct dp_hdcp2p2_ctrl, auth); + + cdata.context = ctrl->lib_ctx; + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_AUTHENTICATING) + cdata.cmd = HDCP_LIB_WKUP_CMD_START; + else + cdata.cmd = HDCP_LIB_WKUP_CMD_STOP; + + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); +} + +static int dp_hdcp2p2_read_rx_status(struct dp_hdcp2p2_ctrl *ctrl, + u8 *rx_status) +{ + u32 const cp_irq_dpcd_offset = 0x201; + u32 const rxstatus_dpcd_offset = 0x69493; + ssize_t const bytes_to_read = 1; + ssize_t bytes_read = 0; + u8 buf = 0; + int rc = 0; + bool cp_irq = 0; + + *rx_status = 0; + + bytes_read = drm_dp_dpcd_read(ctrl->init_data.drm_aux, + cp_irq_dpcd_offset, &buf, bytes_to_read); + if (bytes_read != bytes_to_read) { + pr_err("cp irq read failed\n"); + rc = bytes_read; + goto error; + } + + cp_irq = buf & BIT(2); + pr_debug("cp_irq=0x%x\n", cp_irq); + buf = 0; + + if (cp_irq) { + bytes_read = drm_dp_dpcd_read(ctrl->init_data.drm_aux, + rxstatus_dpcd_offset, &buf, bytes_to_read); + if (bytes_read != bytes_to_read) { + pr_err("rxstatus read failed\n"); + rc = bytes_read; + goto error; + } + *rx_status = buf; + pr_debug("rx_status=0x%x\n", *rx_status); + } + +error: + return rc; +} + +static int dp_hdcp2p2_cp_irq(void *input) +{ + int rc = 0; + struct dp_hdcp2p2_ctrl *ctrl = input; + + if (!ctrl) { + pr_err("invalid input\n"); + return -EINVAL; + } + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_AUTH_FAIL || + atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("invalid hdcp state\n"); + rc = -EINVAL; + goto error; + } + + ctrl->sink_rx_status = 0; + rc = dp_hdcp2p2_read_rx_status(ctrl, &ctrl->sink_rx_status); + if (rc) { + pr_err("failed to read rx status\n"); + goto error; + } + + pr_debug("sink_rx_status=0x%x\n", ctrl->sink_rx_status); + + if (!ctrl->sink_rx_status) { + pr_debug("not a hdcp 2.2 irq\n"); + rc = -EINVAL; + goto error; + } + + kthread_queue_work(&ctrl->worker, &ctrl->link); + + return 0; +error: + return rc; +} + +static int dp_hdcp2p2_isr(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = (struct dp_hdcp2p2_ctrl *)input; + int rc = 0; + struct dss_io_data *io; + struct dp_hdcp2p2_interrupts *intr; + u32 hdcp_int_val = 0; + + if (!ctrl || !ctrl->init_data.core_io) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + io = ctrl->init_data.core_io; + intr = ctrl->intr; + + while (intr && intr->reg) { + struct dp_hdcp2p2_int_set *int_set = intr->int_set; + + hdcp_int_val = dp_read(io->base + intr->reg); + + while (int_set && int_set->interrupt) { + if (hdcp_int_val & (int_set->interrupt >> 2)) { + pr_debug("%s\n", int_set->name); + + if (int_set->func) + int_set->func(ctrl); + + dp_write(io->base + intr->reg, hdcp_int_val | + (int_set->interrupt >> 1)); + } + int_set++; + } + intr++; + } +end: + return rc; +} + +void sde_dp_hdcp2p2_deinit(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = (struct dp_hdcp2p2_ctrl *)input; + struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID}; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + cdata.cmd = HDCP_LIB_WKUP_CMD_STOP; + cdata.context = ctrl->lib_ctx; + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); + + kthread_stop(ctrl->thread); + + mutex_destroy(&ctrl->mutex); + mutex_destroy(&ctrl->msg_lock); + mutex_destroy(&ctrl->wakeup_mutex); + kzfree(ctrl->msg_buf); + kfree(ctrl); +} + +void *sde_dp_hdcp2p2_init(struct sde_hdcp_init_data *init_data) +{ + int rc; + struct dp_hdcp2p2_ctrl *ctrl; + static struct hdcp_txmtr_ops txmtr_ops; + struct hdcp_register_data register_data; + static struct sde_hdcp_ops ops = { + .isr = dp_hdcp2p2_isr, + .reauthenticate = dp_hdcp2p2_reauthenticate, + .authenticate = dp_hdcp2p2_authenticate, + .feature_supported = dp_hdcp2p2_feature_supported, + .off = dp_hdcp2p2_off, + .cp_irq = dp_hdcp2p2_cp_irq, + }; + + static struct hdcp_client_ops client_ops = { + .wakeup = dp_hdcp2p2_wakeup, + .notify_lvl_change = dp_hdcp2p2_min_level_change, + }; + static struct dp_hdcp2p2_int_set int_set1[] = { + {BIT(17), "authentication successful", NULL}, + {BIT(20), "authentication failed", NULL}, + {BIT(24), "encryption enabled", NULL}, + {BIT(27), "encryption disabled", NULL}, + {0}, + }; + static struct dp_hdcp2p2_int_set int_set2[] = { + {BIT(2), "key fifo underflow", NULL}, + {0}, + }; + static struct dp_hdcp2p2_interrupts intr[] = { + {DP_INTR_STATUS2, int_set1}, + {DP_INTR_STATUS3, int_set2}, + {0} + }; + + if (!init_data || !init_data->cb_data || + !init_data->notify_status || !init_data->drm_aux) { + pr_err("invalid input\n"); + return ERR_PTR(-EINVAL); + } + + ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return ERR_PTR(-ENOMEM); + + ctrl->init_data = *init_data; + ctrl->lib = &txmtr_ops; + ctrl->msg_buf = NULL; + + ctrl->sink_status = SINK_DISCONNECTED; + ctrl->intr = intr; + + atomic_set(&ctrl->auth_state, HDCP_STATE_INACTIVE); + + ctrl->ops = &ops; + mutex_init(&ctrl->mutex); + mutex_init(&ctrl->msg_lock); + mutex_init(&ctrl->wakeup_mutex); + + register_data.hdcp_ctx = &ctrl->lib_ctx; + register_data.client_ops = &client_ops; + register_data.txmtr_ops = &txmtr_ops; + register_data.device_type = HDCP_TXMTR_DP; + register_data.client_ctx = ctrl; + + rc = hdcp_library_register(®ister_data); + if (rc) { + pr_err("Unable to register with HDCP 2.2 library\n"); + goto error; + } + + kthread_init_worker(&ctrl->worker); + + kthread_init_work(&ctrl->auth, dp_hdcp2p2_auth_work); + kthread_init_work(&ctrl->send_msg, dp_hdcp2p2_send_msg_work); + kthread_init_work(&ctrl->recv_msg, dp_hdcp2p2_recv_msg_work); + kthread_init_work(&ctrl->status, dp_hdcp2p2_auth_status_work); + kthread_init_work(&ctrl->link, dp_hdcp2p2_link_work); + + ctrl->thread = kthread_run(kthread_worker_fn, + &ctrl->worker, "dp_hdcp2p2"); + + if (IS_ERR(ctrl->thread)) { + pr_err("unable to start DP hdcp2p2 thread\n"); + rc = PTR_ERR(ctrl->thread); + ctrl->thread = NULL; + goto error; + } + + return ctrl; +error: + kfree(ctrl); + return ERR_PTR(rc); +} + +static bool dp_hdcp2p2_supported(struct dp_hdcp2p2_ctrl *ctrl) +{ + u32 const rxcaps_dpcd_offset = 0x6921d; + ssize_t bytes_read = 0; + u8 buf[DP_HDCP_RXCAPS_LENGTH]; + + bytes_read = drm_dp_dpcd_read(ctrl->init_data.drm_aux, + rxcaps_dpcd_offset, &buf, DP_HDCP_RXCAPS_LENGTH); + if (bytes_read != DP_HDCP_RXCAPS_LENGTH) { + pr_err("RxCaps read failed\n"); + goto error; + } + + pr_debug("HDCP_CAPABLE=%lu\n", (buf[2] & BIT(1)) >> 1); + pr_debug("VERSION=%d\n", buf[0]); + + if ((buf[2] & BIT(1)) && (buf[0] == 0x2)) + return true; + +error: + return false; +} + +struct sde_hdcp_ops *sde_dp_hdcp2p2_start(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = input; + + pr_debug("Checking sink capability\n"); + if (dp_hdcp2p2_supported(ctrl)) + return ctrl->ops; + else + return NULL; +} + diff --git a/drivers/gpu/drm/msm/dp/dp_link.c b/drivers/gpu/drm/msm/dp/dp_link.c new file mode 100644 index 000000000000..0cf488d51c52 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_link.c @@ -0,0 +1,1548 @@ +/* + * Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include "dp_link.h" +#include "dp_panel.h" + +enum dynamic_range { + DP_DYNAMIC_RANGE_RGB_VESA = 0x00, + DP_DYNAMIC_RANGE_RGB_CEA = 0x01, + DP_DYNAMIC_RANGE_UNKNOWN = 0xFFFFFFFF, +}; + +enum audio_sample_rate { + AUDIO_SAMPLE_RATE_32_KHZ = 0x00, + AUDIO_SAMPLE_RATE_44_1_KHZ = 0x01, + AUDIO_SAMPLE_RATE_48_KHZ = 0x02, + AUDIO_SAMPLE_RATE_88_2_KHZ = 0x03, + AUDIO_SAMPLE_RATE_96_KHZ = 0x04, + AUDIO_SAMPLE_RATE_176_4_KHZ = 0x05, + AUDIO_SAMPLE_RATE_192_KHZ = 0x06, +}; + +enum audio_pattern_type { + AUDIO_TEST_PATTERN_OPERATOR_DEFINED = 0x00, + AUDIO_TEST_PATTERN_SAWTOOTH = 0x01, +}; + +struct dp_link_request { + u32 test_requested; + u32 test_link_rate; + u32 test_lane_count; +}; + +struct dp_link_private { + u32 prev_sink_count; + struct device *dev; + struct dp_aux *aux; + struct dp_link dp_link; + + struct dp_link_request request; + u8 link_status[DP_LINK_STATUS_SIZE]; +}; + +static char *dp_link_get_audio_test_pattern(u32 pattern) +{ + switch (pattern) { + case AUDIO_TEST_PATTERN_OPERATOR_DEFINED: + return DP_LINK_ENUM_STR(AUDIO_TEST_PATTERN_OPERATOR_DEFINED); + case AUDIO_TEST_PATTERN_SAWTOOTH: + return DP_LINK_ENUM_STR(AUDIO_TEST_PATTERN_SAWTOOTH); + default: + return "unknown"; + } +} + +static char *dp_link_get_audio_sample_rate(u32 rate) +{ + switch (rate) { + case AUDIO_SAMPLE_RATE_32_KHZ: + return DP_LINK_ENUM_STR(AUDIO_SAMPLE_RATE_32_KHZ); + case AUDIO_SAMPLE_RATE_44_1_KHZ: + return DP_LINK_ENUM_STR(AUDIO_SAMPLE_RATE_44_1_KHZ); + case AUDIO_SAMPLE_RATE_48_KHZ: + return DP_LINK_ENUM_STR(AUDIO_SAMPLE_RATE_48_KHZ); + case AUDIO_SAMPLE_RATE_88_2_KHZ: + return DP_LINK_ENUM_STR(AUDIO_SAMPLE_RATE_88_2_KHZ); + case AUDIO_SAMPLE_RATE_96_KHZ: + return DP_LINK_ENUM_STR(AUDIO_SAMPLE_RATE_96_KHZ); + case AUDIO_SAMPLE_RATE_176_4_KHZ: + return DP_LINK_ENUM_STR(AUDIO_SAMPLE_RATE_176_4_KHZ); + case AUDIO_SAMPLE_RATE_192_KHZ: + return DP_LINK_ENUM_STR(AUDIO_SAMPLE_RATE_192_KHZ); + default: + return "unknown"; + } +} + +static int dp_link_get_period(struct dp_link_private *link, int const addr) +{ + int ret = 0; + u8 bp; + u8 data; + u32 const param_len = 0x1; + u32 const max_audio_period = 0xA; + + /* TEST_AUDIO_PERIOD_CH_XX */ + if (drm_dp_dpcd_read(link->aux->drm_aux, addr, &bp, + param_len) < param_len) { + pr_err("failed to read test_audio_period (0x%x)\n", addr); + ret = -EINVAL; + goto exit; + } + + data = bp; + + /* Period - Bits 3:0 */ + data = data & 0xF; + if ((int)data > max_audio_period) { + pr_err("invalid test_audio_period_ch_1 = 0x%x\n", data); + ret = -EINVAL; + goto exit; + } + + ret = data; +exit: + return ret; +} + +static int dp_link_parse_audio_channel_period(struct dp_link_private *link) +{ + int ret = 0; + struct dp_link_test_audio *req = &link->dp_link.test_audio; + + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH1); + if (ret == -EINVAL) + goto exit; + + req->test_audio_period_ch_1 = ret; + pr_debug("test_audio_period_ch_1 = 0x%x\n", ret); + + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH2); + if (ret == -EINVAL) + goto exit; + + req->test_audio_period_ch_2 = ret; + pr_debug("test_audio_period_ch_2 = 0x%x\n", ret); + + /* TEST_AUDIO_PERIOD_CH_3 (Byte 0x275) */ + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH3); + if (ret == -EINVAL) + goto exit; + + req->test_audio_period_ch_3 = ret; + pr_debug("test_audio_period_ch_3 = 0x%x\n", ret); + + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH4); + if (ret == -EINVAL) + goto exit; + + req->test_audio_period_ch_4 = ret; + pr_debug("test_audio_period_ch_4 = 0x%x\n", ret); + + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH5); + if (ret == -EINVAL) + goto exit; + + req->test_audio_period_ch_5 = ret; + pr_debug("test_audio_period_ch_5 = 0x%x\n", ret); + + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH6); + if (ret == -EINVAL) + goto exit; + + req->test_audio_period_ch_6 = ret; + pr_debug("test_audio_period_ch_6 = 0x%x\n", ret); + + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH7); + if (ret == -EINVAL) + goto exit; + + req->test_audio_period_ch_7 = ret; + pr_debug("test_audio_period_ch_7 = 0x%x\n", ret); + + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH8); + if (ret == -EINVAL) + goto exit; + + req->test_audio_period_ch_8 = ret; + pr_debug("test_audio_period_ch_8 = 0x%x\n", ret); +exit: + return ret; +} + +static int dp_link_parse_audio_pattern_type(struct dp_link_private *link) +{ + int ret = 0; + u8 bp; + u8 data; + int rlen; + int const param_len = 0x1; + int const max_audio_pattern_type = 0x1; + + rlen = drm_dp_dpcd_read(link->aux->drm_aux, + DP_TEST_AUDIO_PATTERN_TYPE, &bp, param_len); + if (rlen < param_len) { + pr_err("failed to read link audio mode data\n"); + ret = -EINVAL; + goto exit; + } + data = bp; + + /* Audio Pattern Type - Bits 7:0 */ + if ((int)data > max_audio_pattern_type) { + pr_err("invalid audio pattern type = 0x%x\n", data); + ret = -EINVAL; + goto exit; + } + + link->dp_link.test_audio.test_audio_pattern_type = data; + pr_debug("audio pattern type = %s\n", + dp_link_get_audio_test_pattern(data)); +exit: + return ret; +} + +static int dp_link_parse_audio_mode(struct dp_link_private *link) +{ + int ret = 0; + u8 bp; + u8 data; + int rlen; + int const param_len = 0x1; + int const max_audio_sampling_rate = 0x6; + int const max_audio_channel_count = 0x8; + int sampling_rate = 0x0; + int channel_count = 0x0; + + rlen = drm_dp_dpcd_read(link->aux->drm_aux, DP_TEST_AUDIO_MODE, + &bp, param_len); + if (rlen < param_len) { + pr_err("failed to read link audio mode data\n"); + ret = -EINVAL; + goto exit; + } + data = bp; + + /* Sampling Rate - Bits 3:0 */ + sampling_rate = data & 0xF; + if (sampling_rate > max_audio_sampling_rate) { + pr_err("sampling rate (0x%x) greater than max (0x%x)\n", + sampling_rate, max_audio_sampling_rate); + ret = -EINVAL; + goto exit; + } + + /* Channel Count - Bits 7:4 */ + channel_count = ((data & 0xF0) >> 4) + 1; + if (channel_count > max_audio_channel_count) { + pr_err("channel_count (0x%x) greater than max (0x%x)\n", + channel_count, max_audio_channel_count); + ret = -EINVAL; + goto exit; + } + + link->dp_link.test_audio.test_audio_sampling_rate = sampling_rate; + link->dp_link.test_audio.test_audio_channel_count = channel_count; + pr_debug("sampling_rate = %s, channel_count = 0x%x\n", + dp_link_get_audio_sample_rate(sampling_rate), channel_count); +exit: + return ret; +} + +/** + * dp_parse_audio_pattern_params() - parses audio pattern parameters from DPCD + * @link: Display Port Driver data + * + * Returns 0 if it successfully parses the audio link pattern parameters. + */ +static int dp_link_parse_audio_pattern_params(struct dp_link_private *link) +{ + int ret = 0; + + ret = dp_link_parse_audio_mode(link); + if (ret) + goto exit; + + ret = dp_link_parse_audio_pattern_type(link); + if (ret) + goto exit; + + ret = dp_link_parse_audio_channel_period(link); + +exit: + return ret; +} + +/** + * dp_link_is_video_pattern_valid() - validates the video pattern + * @pattern: video pattern requested by the sink + * + * Returns true if the requested video pattern is supported. + */ +static bool dp_link_is_video_pattern_valid(u32 pattern) +{ + switch (pattern) { + case DP_NO_TEST_PATTERN: + case DP_COLOR_RAMP: + case DP_BLACK_AND_WHITE_VERTICAL_LINES: + case DP_COLOR_SQUARE: + return true; + default: + return false; + } +} + +static char *dp_link_video_pattern_to_string(u32 test_video_pattern) +{ + switch (test_video_pattern) { + case DP_NO_TEST_PATTERN: + return DP_LINK_ENUM_STR(DP_NO_TEST_PATTERN); + case DP_COLOR_RAMP: + return DP_LINK_ENUM_STR(DP_COLOR_RAMP); + case DP_BLACK_AND_WHITE_VERTICAL_LINES: + return DP_LINK_ENUM_STR(DP_BLACK_AND_WHITE_VERTICAL_LINES); + case DP_COLOR_SQUARE: + return DP_LINK_ENUM_STR(DP_COLOR_SQUARE); + default: + return "unknown"; + } +} + +/** + * dp_link_is_dynamic_range_valid() - validates the dynamic range + * @bit_depth: the dynamic range value to be checked + * + * Returns true if the dynamic range value is supported. + */ +static bool dp_link_is_dynamic_range_valid(u32 dr) +{ + switch (dr) { + case DP_DYNAMIC_RANGE_RGB_VESA: + case DP_DYNAMIC_RANGE_RGB_CEA: + return true; + default: + return false; + } +} + +static char *dp_link_dynamic_range_to_string(u32 dr) +{ + switch (dr) { + case DP_DYNAMIC_RANGE_RGB_VESA: + return DP_LINK_ENUM_STR(DP_DYNAMIC_RANGE_RGB_VESA); + case DP_DYNAMIC_RANGE_RGB_CEA: + return DP_LINK_ENUM_STR(DP_DYNAMIC_RANGE_RGB_CEA); + case DP_DYNAMIC_RANGE_UNKNOWN: + default: + return "unknown"; + } +} + +/** + * dp_link_is_bit_depth_valid() - validates the bit depth requested + * @bit_depth: bit depth requested by the sink + * + * Returns true if the requested bit depth is supported. + */ +static bool dp_link_is_bit_depth_valid(u32 tbd) +{ + /* DP_TEST_VIDEO_PATTERN_NONE is treated as invalid */ + switch (tbd) { + case DP_TEST_BIT_DEPTH_6: + case DP_TEST_BIT_DEPTH_8: + case DP_TEST_BIT_DEPTH_10: + return true; + default: + return false; + } +} + +static char *dp_link_bit_depth_to_string(u32 tbd) +{ + switch (tbd) { + case DP_TEST_BIT_DEPTH_6: + return DP_LINK_ENUM_STR(DP_TEST_BIT_DEPTH_6); + case DP_TEST_BIT_DEPTH_8: + return DP_LINK_ENUM_STR(DP_TEST_BIT_DEPTH_8); + case DP_TEST_BIT_DEPTH_10: + return DP_LINK_ENUM_STR(DP_TEST_BIT_DEPTH_10); + case DP_TEST_BIT_DEPTH_UNKNOWN: + default: + return "unknown"; + } +} + +static int dp_link_parse_timing_params1(struct dp_link_private *link, + int const addr, int const len, u32 *val) +{ + u8 bp[2]; + int rlen; + + if (len < 2) + return -EINVAL; + + /* Read the requested video link pattern (Byte 0x221). */ + rlen = drm_dp_dpcd_read(link->aux->drm_aux, addr, bp, len); + if (rlen < len) { + pr_err("failed to read 0x%x\n", addr); + return -EINVAL; + } + + *val = bp[1] | (bp[0] << 8); + + return 0; +} + +static int dp_link_parse_timing_params2(struct dp_link_private *link, + int const addr, int const len, u32 *val1, u32 *val2) +{ + u8 bp[2]; + int rlen; + + if (len < 2) + return -EINVAL; + + /* Read the requested video link pattern (Byte 0x221). */ + rlen = drm_dp_dpcd_read(link->aux->drm_aux, addr, bp, len); + if (rlen < len) { + pr_err("failed to read 0x%x\n", addr); + return -EINVAL; + } + + *val1 = (bp[0] & BIT(7)) >> 7; + *val2 = bp[1] | ((bp[0] & 0x7F) << 8); + + return 0; +} + +static int dp_link_parse_timing_params3(struct dp_link_private *link, + int const addr, u32 *val) +{ + u8 bp; + u32 len = 1; + int rlen; + + rlen = drm_dp_dpcd_read(link->aux->drm_aux, addr, &bp, len); + if (rlen < 1) { + pr_err("failed to read 0x%x\n", addr); + return -EINVAL; + } + *val = bp; + + return 0; +} + +/** + * dp_parse_video_pattern_params() - parses video pattern parameters from DPCD + * @link: Display Port Driver data + * + * Returns 0 if it successfully parses the video link pattern and the link + * bit depth requested by the sink and, and if the values parsed are valid. + */ +static int dp_link_parse_video_pattern_params(struct dp_link_private *link) +{ + int ret = 0; + int rlen; + u8 bp; + u8 data; + u32 dyn_range; + int const param_len = 0x1; + + rlen = drm_dp_dpcd_read(link->aux->drm_aux, DP_TEST_PATTERN, + &bp, param_len); + if (rlen < param_len) { + pr_err("failed to read link video pattern\n"); + ret = -EINVAL; + goto exit; + } + data = bp; + + if (!dp_link_is_video_pattern_valid(data)) { + pr_err("invalid link video pattern = 0x%x\n", data); + ret = -EINVAL; + goto exit; + } + + link->dp_link.test_video.test_video_pattern = data; + pr_debug("link video pattern = 0x%x (%s)\n", + link->dp_link.test_video.test_video_pattern, + dp_link_video_pattern_to_string( + link->dp_link.test_video.test_video_pattern)); + + /* Read the requested color bit depth and dynamic range (Byte 0x232) */ + rlen = drm_dp_dpcd_read(link->aux->drm_aux, DP_TEST_MISC0, + &bp, param_len); + if (rlen < param_len) { + pr_err("failed to read link bit depth\n"); + ret = -EINVAL; + goto exit; + } + data = bp; + + /* Dynamic Range */ + dyn_range = (data & DP_TEST_DYNAMIC_RANGE_CEA) >> 3; + if (!dp_link_is_dynamic_range_valid(dyn_range)) { + pr_err("invalid link dynamic range = 0x%x", dyn_range); + ret = -EINVAL; + goto exit; + } + link->dp_link.test_video.test_dyn_range = dyn_range; + pr_debug("link dynamic range = 0x%x (%s)\n", + link->dp_link.test_video.test_dyn_range, + dp_link_dynamic_range_to_string( + link->dp_link.test_video.test_dyn_range)); + + /* Color bit depth */ + data &= DP_TEST_BIT_DEPTH_MASK; + if (!dp_link_is_bit_depth_valid(data)) { + pr_err("invalid link bit depth = 0x%x\n", data); + ret = -EINVAL; + goto exit; + } + + link->dp_link.test_video.test_bit_depth = data; + pr_debug("link bit depth = 0x%x (%s)\n", + link->dp_link.test_video.test_bit_depth, + dp_link_bit_depth_to_string( + link->dp_link.test_video.test_bit_depth)); + + /* resolution timing params */ + ret = dp_link_parse_timing_params1(link, DP_TEST_H_TOTAL_HI, 2, + &link->dp_link.test_video.test_h_total); + if (ret) { + pr_err("failed to parse test_h_total (DP_TEST_H_TOTAL_HI)\n"); + goto exit; + } + pr_debug("TEST_H_TOTAL = %d\n", link->dp_link.test_video.test_h_total); + + ret = dp_link_parse_timing_params1(link, DP_TEST_V_TOTAL_HI, 2, + &link->dp_link.test_video.test_v_total); + if (ret) { + pr_err("failed to parse test_v_total (DP_TEST_V_TOTAL_HI)\n"); + goto exit; + } + pr_debug("TEST_V_TOTAL = %d\n", link->dp_link.test_video.test_v_total); + + ret = dp_link_parse_timing_params1(link, DP_TEST_H_START_HI, 2, + &link->dp_link.test_video.test_h_start); + if (ret) { + pr_err("failed to parse test_h_start (DP_TEST_H_START_HI)\n"); + goto exit; + } + pr_debug("TEST_H_START = %d\n", link->dp_link.test_video.test_h_start); + + ret = dp_link_parse_timing_params1(link, DP_TEST_V_START_HI, 2, + &link->dp_link.test_video.test_v_start); + if (ret) { + pr_err("failed to parse test_v_start (DP_TEST_V_START_HI)\n"); + goto exit; + } + pr_debug("TEST_V_START = %d\n", link->dp_link.test_video.test_v_start); + + ret = dp_link_parse_timing_params2(link, DP_TEST_HSYNC_HI, 2, + &link->dp_link.test_video.test_hsync_pol, + &link->dp_link.test_video.test_hsync_width); + if (ret) { + pr_err("failed to parse (DP_TEST_HSYNC_HI)\n"); + goto exit; + } + pr_debug("TEST_HSYNC_POL = %d\n", + link->dp_link.test_video.test_hsync_pol); + pr_debug("TEST_HSYNC_WIDTH = %d\n", + link->dp_link.test_video.test_hsync_width); + + ret = dp_link_parse_timing_params2(link, DP_TEST_VSYNC_HI, 2, + &link->dp_link.test_video.test_vsync_pol, + &link->dp_link.test_video.test_vsync_width); + if (ret) { + pr_err("failed to parse (DP_TEST_VSYNC_HI)\n"); + goto exit; + } + pr_debug("TEST_VSYNC_POL = %d\n", + link->dp_link.test_video.test_vsync_pol); + pr_debug("TEST_VSYNC_WIDTH = %d\n", + link->dp_link.test_video.test_vsync_width); + + ret = dp_link_parse_timing_params1(link, DP_TEST_H_WIDTH_HI, 2, + &link->dp_link.test_video.test_h_width); + if (ret) { + pr_err("failed to parse test_h_width (DP_TEST_H_WIDTH_HI)\n"); + goto exit; + } + pr_debug("TEST_H_WIDTH = %d\n", link->dp_link.test_video.test_h_width); + + ret = dp_link_parse_timing_params1(link, DP_TEST_V_HEIGHT_HI, 2, + &link->dp_link.test_video.test_v_height); + if (ret) { + pr_err("failed to parse test_v_height (DP_TEST_V_HEIGHT_HI)\n"); + goto exit; + } + pr_debug("TEST_V_HEIGHT = %d\n", + link->dp_link.test_video.test_v_height); + + ret = dp_link_parse_timing_params3(link, DP_TEST_MISC1, + &link->dp_link.test_video.test_rr_d); + link->dp_link.test_video.test_rr_d &= DP_TEST_REFRESH_DENOMINATOR; + if (ret) { + pr_err("failed to parse test_rr_d (DP_TEST_MISC1)\n"); + goto exit; + } + pr_debug("TEST_REFRESH_DENOMINATOR = %d\n", + link->dp_link.test_video.test_rr_d); + + ret = dp_link_parse_timing_params3(link, DP_TEST_REFRESH_RATE_NUMERATOR, + &link->dp_link.test_video.test_rr_n); + if (ret) { + pr_err("failed to parse test_rr_n (DP_TEST_REFRESH_RATE_NUMERATOR)\n"); + goto exit; + } + pr_debug("TEST_REFRESH_NUMERATOR = %d\n", + link->dp_link.test_video.test_rr_n); +exit: + return ret; +} + +/** + * dp_link_parse_link_training_params() - parses link training parameters from + * DPCD + * @link: Display Port Driver data + * + * Returns 0 if it successfully parses the link rate (Byte 0x219) and lane + * count (Byte 0x220), and if these values parse are valid. + */ +static int dp_link_parse_link_training_params(struct dp_link_private *link) +{ + u8 bp; + u8 data; + int ret = 0; + int rlen; + int const param_len = 0x1; + + rlen = drm_dp_dpcd_read(link->aux->drm_aux, DP_TEST_LINK_RATE, + &bp, param_len); + if (rlen < param_len) { + pr_err("failed to read link rate\n"); + ret = -EINVAL; + goto exit; + } + data = bp; + + if (!is_link_rate_valid(data)) { + pr_err("invalid link rate = 0x%x\n", data); + ret = -EINVAL; + goto exit; + } + + link->request.test_link_rate = data; + pr_debug("link rate = 0x%x\n", link->request.test_link_rate); + + rlen = drm_dp_dpcd_read(link->aux->drm_aux, DP_TEST_LANE_COUNT, + &bp, param_len); + if (rlen < param_len) { + pr_err("failed to read lane count\n"); + ret = -EINVAL; + goto exit; + } + data = bp; + data &= 0x1F; + + if (!is_lane_count_valid(data)) { + pr_err("invalid lane count = 0x%x\n", data); + ret = -EINVAL; + goto exit; + } + + link->request.test_lane_count = data; + pr_debug("lane count = 0x%x\n", link->request.test_lane_count); +exit: + return ret; +} + +static bool dp_link_is_phy_test_pattern_supported(u32 phy_test_pattern_sel) +{ + switch (phy_test_pattern_sel) { + case DP_TEST_PHY_PATTERN_NONE: + case DP_TEST_PHY_PATTERN_D10_2_NO_SCRAMBLING: + case DP_TEST_PHY_PATTERN_SYMBOL_ERR_MEASUREMENT_CNT: + case DP_TEST_PHY_PATTERN_PRBS7: + case DP_TEST_PHY_PATTERN_80_BIT_CUSTOM_PATTERN: + case DP_TEST_PHY_PATTERN_HBR2_CTS_EYE_PATTERN: + return true; + default: + return false; + } +} + +/** + * dp_parse_phy_test_params() - parses the phy link parameters + * @link: Display Port Driver data + * + * Parses the DPCD (Byte 0x248) for the DP PHY link pattern that is being + * requested. + */ +static int dp_link_parse_phy_test_params(struct dp_link_private *link) +{ + u8 bp; + u8 data; + int rlen; + int const param_len = 0x1; + int ret = 0; + + rlen = drm_dp_dpcd_read(link->aux->drm_aux, DP_TEST_PHY_PATTERN, + &bp, param_len); + if (rlen < param_len) { + pr_err("failed to read phy link pattern\n"); + ret = -EINVAL; + goto end; + } + + data = bp; + + link->dp_link.phy_params.phy_test_pattern_sel = data; + + pr_debug("phy_test_pattern_sel = %s\n", + dp_link_get_phy_test_pattern(data)); + + if (!dp_link_is_phy_test_pattern_supported(data)) + ret = -EINVAL; +end: + return ret; +} + +static char *dp_link_get_test_name(u32 test_requested) +{ + switch (test_requested) { + case DP_TEST_LINK_TRAINING: + return DP_LINK_ENUM_STR(DP_TEST_LINK_TRAINING); + case DP_TEST_LINK_VIDEO_PATTERN: + return DP_LINK_ENUM_STR(DP_TEST_LINK_VIDEO_PATTERN); + case DP_TEST_LINK_EDID_READ: + return DP_LINK_ENUM_STR(DP_TEST_LINK_EDID_READ); + case DP_TEST_LINK_PHY_TEST_PATTERN: + return DP_LINK_ENUM_STR(DP_TEST_LINK_PHY_TEST_PATTERN); + case DP_TEST_LINK_AUDIO_PATTERN: + return DP_LINK_ENUM_STR(DP_TEST_LINK_AUDIO_PATTERN); + default: + return "unknown"; + } +} + +/** + * dp_link_is_video_audio_test_requested() - checks for audio/video link request + * @link: link requested by the sink + * + * Returns true if the requested link is a permitted audio/video link. + */ +static bool dp_link_is_video_audio_test_requested(u32 link) +{ + return (link == DP_TEST_LINK_VIDEO_PATTERN) || + (link == (DP_TEST_LINK_AUDIO_PATTERN | + DP_TEST_LINK_VIDEO_PATTERN)) || + (link == DP_TEST_LINK_AUDIO_PATTERN) || + (link == (DP_TEST_LINK_AUDIO_PATTERN | + DP_TEST_LINK_AUDIO_DISABLED_VIDEO)); +} + +/** + * dp_link_supported() - checks if link requested by sink is supported + * @test_requested: link requested by the sink + * + * Returns true if the requested link is supported. + */ +static bool dp_link_is_test_supported(u32 test_requested) +{ + return (test_requested == DP_TEST_LINK_TRAINING) || + (test_requested == DP_TEST_LINK_EDID_READ) || + (test_requested == DP_TEST_LINK_PHY_TEST_PATTERN) || + dp_link_is_video_audio_test_requested(test_requested); +} + +static bool dp_link_is_test_edid_read(struct dp_link_private *link) +{ + return (link->request.test_requested == DP_TEST_LINK_EDID_READ); +} + +/** + * dp_sink_parse_test_request() - parses link request parameters from sink + * @link: Display Port Driver data + * + * Parses the DPCD to check if an automated link is requested (Byte 0x201), + * and what type of link automation is being requested (Byte 0x218). + */ +static int dp_link_parse_request(struct dp_link_private *link) +{ + int ret = 0; + u8 bp; + u8 data; + int rlen; + u32 const param_len = 0x1; + + /** + * Read the device service IRQ vector (Byte 0x201) to determine + * whether an automated link has been requested by the sink. + */ + rlen = drm_dp_dpcd_read(link->aux->drm_aux, + DP_DEVICE_SERVICE_IRQ_VECTOR, &bp, param_len); + if (rlen < param_len) { + pr_err("aux read failed\n"); + ret = -EINVAL; + goto end; + } + + data = bp; + + pr_debug("device service irq vector = 0x%x\n", data); + + if (!(data & DP_AUTOMATED_TEST_REQUEST)) { + pr_debug("no test requested\n"); + return 0; + } + + /** + * Read the link request byte (Byte 0x218) to determine what type + * of automated link has been requested by the sink. + */ + rlen = drm_dp_dpcd_read(link->aux->drm_aux, DP_TEST_REQUEST, + &bp, param_len); + if (rlen < param_len) { + pr_err("aux read failed\n"); + ret = -EINVAL; + goto end; + } + + data = bp; + + if (!dp_link_is_test_supported(data)) { + pr_debug("link 0x%x not supported\n", data); + goto end; + } + + pr_debug("%s (0x%x) requested\n", dp_link_get_test_name(data), data); + link->request.test_requested = data; + + if (link->request.test_requested == DP_TEST_LINK_PHY_TEST_PATTERN) { + ret = dp_link_parse_phy_test_params(link); + if (ret) + goto end; + ret = dp_link_parse_link_training_params(link); + } + + if (link->request.test_requested == DP_TEST_LINK_TRAINING) + ret = dp_link_parse_link_training_params(link); + + if (dp_link_is_video_audio_test_requested( + link->request.test_requested)) { + ret = dp_link_parse_video_pattern_params(link); + if (ret) + goto end; + + ret = dp_link_parse_audio_pattern_params(link); + } +end: + /** + * Send a DP_TEST_ACK if all link parameters are valid, otherwise send + * a DP_TEST_NAK. + */ + if (ret) { + link->dp_link.test_response = DP_TEST_NAK; + } else { + if (!dp_link_is_test_edid_read(link)) + link->dp_link.test_response = DP_TEST_ACK; + else + link->dp_link.test_response = + DP_TEST_EDID_CHECKSUM_WRITE; + } + + return ret; +} + +/** + * dp_link_parse_sink_count() - parses the sink count + * + * Parses the DPCD to check if there is an update to the sink count + * (Byte 0x200), and whether all the sink devices connected have Content + * Protection enabled. + */ +static int dp_link_parse_sink_count(struct dp_link *dp_link) +{ + int rlen; + int const param_len = 0x1; + struct dp_link_private *link = container_of(dp_link, + struct dp_link_private, dp_link); + + rlen = drm_dp_dpcd_read(link->aux->drm_aux, DP_SINK_COUNT, + &link->dp_link.sink_count.count, param_len); + if (rlen < param_len) { + pr_err("failed to read sink count\n"); + return -EINVAL; + } + + link->dp_link.sink_count.cp_ready = + link->dp_link.sink_count.count & DP_SINK_CP_READY; + /* BIT 7, BIT 5:0 */ + link->dp_link.sink_count.count = + DP_GET_SINK_COUNT(link->dp_link.sink_count.count); + + pr_debug("sink_count = 0x%x, cp_ready = 0x%x\n", + link->dp_link.sink_count.count, + link->dp_link.sink_count.cp_ready); + return 0; +} + +static void dp_link_parse_sink_status_field(struct dp_link_private *link) +{ + int len = 0; + + link->prev_sink_count = link->dp_link.sink_count.count; + dp_link_parse_sink_count(&link->dp_link); + + len = drm_dp_dpcd_read_link_status(link->aux->drm_aux, + link->link_status); + if (len < DP_LINK_STATUS_SIZE) + pr_err("DP link status read failed\n"); + dp_link_parse_request(link); +} + +static bool dp_link_is_link_training_requested(struct dp_link_private *link) +{ + return (link->request.test_requested == DP_TEST_LINK_TRAINING); +} + +/** + * dp_link_process_link_training_request() - processes new training requests + * @link: Display Port link data + * + * This function will handle new link training requests that are initiated by + * the sink. In particular, it will update the requested lane count and link + * link rate, and then trigger the link retraining procedure. + * + * The function will return 0 if a link training request has been processed, + * otherwise it will return -EINVAL. + */ +static int dp_link_process_link_training_request(struct dp_link_private *link) +{ + if (!dp_link_is_link_training_requested(link)) + return -EINVAL; + + pr_debug("%s link rate = 0x%x, lane count = 0x%x\n", + dp_link_get_test_name(DP_TEST_LINK_TRAINING), + link->request.test_link_rate, + link->request.test_lane_count); + + link->dp_link.link_params.lane_count = link->request.test_lane_count; + link->dp_link.link_params.bw_code = link->request.test_link_rate; + + return 0; +} + +static void dp_link_send_test_response(struct dp_link *dp_link) +{ + struct dp_link_private *link = NULL; + u32 const response_len = 0x1; + + if (!dp_link) { + pr_err("invalid input\n"); + return; + } + + link = container_of(dp_link, struct dp_link_private, dp_link); + + drm_dp_dpcd_write(link->aux->drm_aux, DP_TEST_RESPONSE, + &dp_link->test_response, response_len); +} + +static int dp_link_psm_config(struct dp_link *dp_link, + struct drm_dp_link *link_info, bool enable) +{ + struct dp_link_private *link = NULL; + int ret = 0; + + if (!dp_link) { + pr_err("invalid params\n"); + return -EINVAL; + } + + link = container_of(dp_link, struct dp_link_private, dp_link); + + if (enable) + ret = drm_dp_link_power_down(link->aux->drm_aux, link_info); + else + ret = drm_dp_link_power_up(link->aux->drm_aux, link_info); + + if (ret) + pr_err("Failed to %s low power mode\n", + (enable ? "enter" : "exit")); + else + dp_link->psm_enabled = enable; + + return ret; +} + +static void dp_link_send_edid_checksum(struct dp_link *dp_link, u8 checksum) +{ + struct dp_link_private *link = NULL; + u32 const response_len = 0x1; + + if (!dp_link) { + pr_err("invalid input\n"); + return; + } + + link = container_of(dp_link, struct dp_link_private, dp_link); + + drm_dp_dpcd_write(link->aux->drm_aux, DP_TEST_EDID_CHECKSUM, + &checksum, response_len); +} + +static int dp_link_parse_vx_px(struct dp_link_private *link) +{ + u8 bp; + u8 data; + int const param_len = 0x1; + int ret = 0; + u32 v0, p0, v1, p1, v2, p2, v3, p3; + int rlen; + + pr_debug("\n"); + + rlen = drm_dp_dpcd_read(link->aux->drm_aux, DP_ADJUST_REQUEST_LANE0_1, + &bp, param_len); + if (rlen < param_len) { + pr_err("failed reading lanes 0/1\n"); + ret = -EINVAL; + goto end; + } + + data = bp; + + pr_debug("lanes 0/1 (Byte 0x206): 0x%x\n", data); + + v0 = data & 0x3; + data = data >> 2; + p0 = data & 0x3; + data = data >> 2; + + v1 = data & 0x3; + data = data >> 2; + p1 = data & 0x3; + data = data >> 2; + + rlen = drm_dp_dpcd_read(link->aux->drm_aux, DP_ADJUST_REQUEST_LANE2_3, + &bp, param_len); + if (rlen < param_len) { + pr_err("failed reading lanes 2/3\n"); + ret = -EINVAL; + goto end; + } + + data = bp; + + pr_debug("lanes 2/3 (Byte 0x207): 0x%x\n", data); + + v2 = data & 0x3; + data = data >> 2; + p2 = data & 0x3; + data = data >> 2; + + v3 = data & 0x3; + data = data >> 2; + p3 = data & 0x3; + data = data >> 2; + + pr_debug("vx: 0=%d, 1=%d, 2=%d, 3=%d\n", v0, v1, v2, v3); + pr_debug("px: 0=%d, 1=%d, 2=%d, 3=%d\n", p0, p1, p2, p3); + + /** + * Update the voltage and pre-emphasis levels as per DPCD request + * vector. + */ + pr_debug("Current: v_level = 0x%x, p_level = 0x%x\n", + link->dp_link.phy_params.v_level, + link->dp_link.phy_params.p_level); + pr_debug("Requested: v_level = 0x%x, p_level = 0x%x\n", v0, p0); + link->dp_link.phy_params.v_level = v0; + link->dp_link.phy_params.p_level = p0; + + pr_debug("Success\n"); +end: + return ret; +} + +/** + * dp_link_process_phy_test_pattern_request() - process new phy link requests + * @link: Display Port Driver data + * + * This function will handle new phy link pattern requests that are initiated + * by the sink. The function will return 0 if a phy link pattern has been + * processed, otherwise it will return -EINVAL. + */ +static int dp_link_process_phy_test_pattern_request( + struct dp_link_private *link) +{ + u32 test_link_rate = 0, test_lane_count = 0; + + if (!(link->request.test_requested & DP_TEST_LINK_PHY_TEST_PATTERN)) { + pr_debug("no phy test\n"); + return -EINVAL; + } + + test_link_rate = link->request.test_link_rate; + test_lane_count = link->request.test_lane_count; + + if (!is_link_rate_valid(test_link_rate) || + !is_lane_count_valid(test_lane_count)) { + pr_err("Invalid params: link rate = 0x%x, lane count = 0x%x\n", + test_link_rate, test_lane_count); + return -EINVAL; + } + + pr_debug("start\n"); + + pr_info("Current: bw_code = 0x%x, lane count = 0x%x\n", + link->dp_link.link_params.bw_code, + link->dp_link.link_params.lane_count); + + pr_info("Requested: bw_code = 0x%x, lane count = 0x%x\n", + test_link_rate, test_lane_count); + + link->dp_link.link_params.lane_count = link->request.test_lane_count; + link->dp_link.link_params.bw_code = link->request.test_link_rate; + + dp_link_parse_vx_px(link); + + pr_debug("end\n"); + + return 0; +} + +static u8 get_link_status(const u8 link_status[DP_LINK_STATUS_SIZE], int r) +{ + return link_status[r - DP_LANE0_1_STATUS]; +} + +/** + * dp_link_process_link_status_update() - processes link status updates + * @link: Display Port link module data + * + * This function will check for changes in the link status, e.g. clock + * recovery done on all lanes, and trigger link training if there is a + * failure/error on the link. + * + * The function will return 0 if the a link status update has been processed, + * otherwise it will return -EINVAL. + */ +static int dp_link_process_link_status_update(struct dp_link_private *link) +{ + if (!(get_link_status(link->link_status, DP_LANE_ALIGN_STATUS_UPDATED) & + DP_LINK_STATUS_UPDATED) || /* link status updated */ + (drm_dp_clock_recovery_ok(link->link_status, + link->dp_link.link_params.lane_count) && + drm_dp_channel_eq_ok(link->link_status, + link->dp_link.link_params.lane_count))) + return -EINVAL; + + pr_debug("channel_eq_done = %d, clock_recovery_done = %d\n", + drm_dp_clock_recovery_ok(link->link_status, + link->dp_link.link_params.lane_count), + drm_dp_clock_recovery_ok(link->link_status, + link->dp_link.link_params.lane_count)); + + return 0; +} + +static bool dp_link_is_ds_port_status_changed(struct dp_link_private *link) +{ + if (get_link_status(link->link_status, DP_LANE_ALIGN_STATUS_UPDATED) & + DP_DOWNSTREAM_PORT_STATUS_CHANGED) /* port status changed */ + return true; + + if (link->prev_sink_count != link->dp_link.sink_count.count) + return true; + + return false; +} + +/** + * dp_link_process_downstream_port_status_change() - process port status changes + * @link: Display Port Driver data + * + * This function will handle downstream port updates that are initiated by + * the sink. If the downstream port status has changed, the EDID is read via + * AUX. + * + * The function will return 0 if a downstream port update has been + * processed, otherwise it will return -EINVAL. + */ +static int dp_link_process_ds_port_status_change(struct dp_link_private *link) +{ + if (!dp_link_is_ds_port_status_changed(link)) + return -EINVAL; + + /* reset prev_sink_count */ + link->prev_sink_count = link->dp_link.sink_count.count; + + return 0; +} + +static bool dp_link_is_video_pattern_requested(struct dp_link_private *link) +{ + return (link->request.test_requested & DP_TEST_LINK_VIDEO_PATTERN) + && !(link->request.test_requested & + DP_TEST_LINK_AUDIO_DISABLED_VIDEO); +} + +static bool dp_link_is_audio_pattern_requested(struct dp_link_private *link) +{ + return (link->request.test_requested & DP_TEST_LINK_AUDIO_PATTERN); +} + +/** + * dp_link_process_video_pattern_request() - process new video pattern request + * @link: Display Port link module's data + * + * This function will handle a new video pattern request that are initiated by + * the sink. This is acheieved by first sending a disconnect notification to + * the sink followed by a subsequent connect notification to the user modules, + * where it is expected that the user modules would draw the required link + * pattern. + */ +static int dp_link_process_video_pattern_request(struct dp_link_private *link) +{ + if (!dp_link_is_video_pattern_requested(link)) + goto end; + + pr_debug("%s: bit depth=%d(%d bpp) pattern=%s\n", + dp_link_get_test_name(DP_TEST_LINK_VIDEO_PATTERN), + link->dp_link.test_video.test_bit_depth, + dp_link_bit_depth_to_bpp( + link->dp_link.test_video.test_bit_depth), + dp_link_video_pattern_to_string( + link->dp_link.test_video.test_video_pattern)); + + return 0; +end: + return -EINVAL; +} + +/** + * dp_link_process_audio_pattern_request() - process new audio pattern request + * @link: Display Port link module data + * + * This function will handle a new audio pattern request that is initiated by + * the sink. This is acheieved by sending the necessary secondary data packets + * to the sink. It is expected that any simulatenous requests for video + * patterns will be handled before the audio pattern is sent to the sink. + */ +static int dp_link_process_audio_pattern_request(struct dp_link_private *link) +{ + if (!dp_link_is_audio_pattern_requested(link)) + return -EINVAL; + + pr_debug("sampling_rate=%s, channel_count=%d, pattern_type=%s\n", + dp_link_get_audio_sample_rate( + link->dp_link.test_audio.test_audio_sampling_rate), + link->dp_link.test_audio.test_audio_channel_count, + dp_link_get_audio_test_pattern( + link->dp_link.test_audio.test_audio_pattern_type)); + + pr_debug("audio_period: ch1=0x%x, ch2=0x%x, ch3=0x%x, ch4=0x%x\n", + link->dp_link.test_audio.test_audio_period_ch_1, + link->dp_link.test_audio.test_audio_period_ch_2, + link->dp_link.test_audio.test_audio_period_ch_3, + link->dp_link.test_audio.test_audio_period_ch_4); + + pr_debug("audio_period: ch5=0x%x, ch6=0x%x, ch7=0x%x, ch8=0x%x\n", + link->dp_link.test_audio.test_audio_period_ch_5, + link->dp_link.test_audio.test_audio_period_ch_6, + link->dp_link.test_audio.test_audio_period_ch_7, + link->dp_link.test_audio.test_audio_period_ch_8); + + return 0; +} + +static void dp_link_reset_data(struct dp_link_private *link) +{ + link->request = (const struct dp_link_request){ 0 }; + link->dp_link.test_video = (const struct dp_link_test_video){ 0 }; + link->dp_link.test_video.test_bit_depth = DP_TEST_BIT_DEPTH_UNKNOWN; + link->dp_link.test_audio = (const struct dp_link_test_audio){ 0 }; + link->dp_link.phy_params.phy_test_pattern_sel = 0; + link->dp_link.sink_request = 0; + link->dp_link.test_response = 0; +} + +/** + * dp_link_process_request() - handle HPD IRQ transition to HIGH + * @link: pointer to link module data + * + * This function will handle the HPD IRQ state transitions from LOW to HIGH + * (including cases when there are back to back HPD IRQ HIGH) indicating + * the start of a new link training request or sink status update. + */ +static int dp_link_process_request(struct dp_link *dp_link) +{ + int ret = 0; + struct dp_link_private *link; + + if (!dp_link) { + pr_err("invalid input\n"); + return -EINVAL; + } + + link = container_of(dp_link, struct dp_link_private, dp_link); + + pr_debug("start\n"); + + dp_link_reset_data(link); + + dp_link_parse_sink_status_field(link); + + if (dp_link_is_test_edid_read(link)) { + dp_link->sink_request |= DP_TEST_LINK_EDID_READ; + goto exit; + } + + ret = dp_link_process_ds_port_status_change(link); + if (!ret) { + dp_link->sink_request |= DS_PORT_STATUS_CHANGED; + goto exit; + } + + ret = dp_link_process_link_training_request(link); + if (!ret) { + dp_link->sink_request |= DP_TEST_LINK_TRAINING; + goto exit; + } + + ret = dp_link_process_phy_test_pattern_request(link); + if (!ret) { + dp_link->sink_request |= DP_TEST_LINK_PHY_TEST_PATTERN; + goto exit; + } + + ret = dp_link_process_link_status_update(link); + if (!ret) { + dp_link->sink_request |= DP_LINK_STATUS_UPDATED; + goto exit; + } + + ret = dp_link_process_video_pattern_request(link); + if (!ret) { + dp_link->sink_request |= DP_TEST_LINK_VIDEO_PATTERN; + goto exit; + } + + ret = dp_link_process_audio_pattern_request(link); + if (!ret) { + dp_link->sink_request |= DP_TEST_LINK_AUDIO_PATTERN; + goto exit; + } + + pr_debug("done\n"); +exit: + return ret; +} + +static int dp_link_get_colorimetry_config(struct dp_link *dp_link) +{ + u32 cc; + enum dynamic_range dr; + struct dp_link_private *link; + + if (!dp_link) { + pr_err("invalid input\n"); + return -EINVAL; + } + + link = container_of(dp_link, struct dp_link_private, dp_link); + + /* unless a video pattern CTS test is ongoing, use CEA_VESA */ + if (dp_link_is_video_pattern_requested(link)) + dr = link->dp_link.test_video.test_dyn_range; + else + dr = DP_DYNAMIC_RANGE_RGB_VESA; + + /* Only RGB_VESA nd RGB_CEA supported for now */ + switch (dr) { + case DP_DYNAMIC_RANGE_RGB_CEA: + cc = BIT(3); + break; + case DP_DYNAMIC_RANGE_RGB_VESA: + default: + cc = 0; + } + + return cc; +} + +static int dp_link_adjust_levels(struct dp_link *dp_link, u8 *link_status) +{ + int i; + int max = 0; + u8 data; + struct dp_link_private *link; + + if (!dp_link) { + pr_err("invalid input\n"); + return -EINVAL; + } + + link = container_of(dp_link, struct dp_link_private, dp_link); + + /* use the max level across lanes */ + for (i = 0; i < dp_link->link_params.lane_count; i++) { + data = drm_dp_get_adjust_request_voltage(link_status, i); + pr_debug("lane=%d req_voltage_swing=%d\n", i, data); + if (max < data) + max = data; + } + + dp_link->phy_params.v_level = max >> DP_TRAIN_VOLTAGE_SWING_SHIFT; + + /* use the max level across lanes */ + max = 0; + for (i = 0; i < dp_link->link_params.lane_count; i++) { + data = drm_dp_get_adjust_request_pre_emphasis(link_status, i); + pr_debug("lane=%d req_pre_emphasis=%d\n", i, data); + if (max < data) + max = data; + } + + dp_link->phy_params.p_level = max >> DP_TRAIN_PRE_EMPHASIS_SHIFT; + + /** + * Adjust the voltage swing and pre-emphasis level combination to within + * the allowable range. + */ + if (dp_link->phy_params.v_level > DP_LINK_VOLTAGE_MAX) { + pr_debug("Requested vSwingLevel=%d, change to %d\n", + dp_link->phy_params.v_level, DP_LINK_VOLTAGE_MAX); + dp_link->phy_params.v_level = DP_LINK_VOLTAGE_MAX; + } + + if (dp_link->phy_params.p_level > DP_LINK_PRE_EMPHASIS_MAX) { + pr_debug("Requested preEmphasisLevel=%d, change to %d\n", + dp_link->phy_params.p_level, DP_LINK_PRE_EMPHASIS_MAX); + dp_link->phy_params.p_level = DP_LINK_PRE_EMPHASIS_MAX; + } + + if ((dp_link->phy_params.p_level > DP_LINK_PRE_EMPHASIS_LEVEL_1) + && (dp_link->phy_params.v_level == DP_LINK_VOLTAGE_LEVEL_2)) { + pr_debug("Requested preEmphasisLevel=%d, change to %d\n", + dp_link->phy_params.p_level, + DP_LINK_PRE_EMPHASIS_LEVEL_1); + dp_link->phy_params.p_level = DP_LINK_PRE_EMPHASIS_LEVEL_1; + } + + pr_debug("adjusted: v_level=%d, p_level=%d\n", + dp_link->phy_params.v_level, dp_link->phy_params.p_level); + + return 0; +} + +static int dp_link_send_psm_request(struct dp_link *dp_link, bool req) +{ + struct dp_link_private *link; + + if (!dp_link) { + pr_err("invalid input\n"); + return -EINVAL; + } + + link = container_of(dp_link, struct dp_link_private, dp_link); + + return 0; +} + +static u32 dp_link_get_test_bits_depth(struct dp_link *dp_link, u32 bpp) +{ + u32 tbd; + + /* + * Few simplistic rules and assumptions made here: + * 1. Test bit depth is bit depth per color component + * 2. Assume 3 color components + */ + switch (bpp) { + case 18: + tbd = DP_TEST_BIT_DEPTH_6; + break; + case 24: + tbd = DP_TEST_BIT_DEPTH_8; + break; + case 30: + tbd = DP_TEST_BIT_DEPTH_10; + break; + default: + tbd = DP_TEST_BIT_DEPTH_UNKNOWN; + break; + } + + if (tbd != DP_TEST_BIT_DEPTH_UNKNOWN) + tbd = (tbd >> DP_TEST_BIT_DEPTH_SHIFT); + + return tbd; +} + +struct dp_link *dp_link_get(struct device *dev, struct dp_aux *aux) +{ + int rc = 0; + struct dp_link_private *link; + struct dp_link *dp_link; + + if (!dev || !aux) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + link = devm_kzalloc(dev, sizeof(*link), GFP_KERNEL); + if (!link) { + rc = -EINVAL; + goto error; + } + + link->dev = dev; + link->aux = aux; + + dp_link = &link->dp_link; + + dp_link->process_request = dp_link_process_request; + dp_link->get_test_bits_depth = dp_link_get_test_bits_depth; + dp_link->get_colorimetry_config = dp_link_get_colorimetry_config; + dp_link->adjust_levels = dp_link_adjust_levels; + dp_link->send_psm_request = dp_link_send_psm_request; + dp_link->send_test_response = dp_link_send_test_response; + dp_link->psm_config = dp_link_psm_config; + dp_link->send_edid_checksum = dp_link_send_edid_checksum; + + return dp_link; +error: + return ERR_PTR(rc); +} + +void dp_link_put(struct dp_link *dp_link) +{ + struct dp_link_private *link; + + if (!dp_link) + return; + + link = container_of(dp_link, struct dp_link_private, dp_link); + + devm_kfree(link->dev, link); +} diff --git a/drivers/gpu/drm/msm/dp/dp_link.h b/drivers/gpu/drm/msm/dp/dp_link.h new file mode 100644 index 000000000000..b1d92498d7cb --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_link.h @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _DP_LINK_H_ +#define _DP_LINK_H_ + +#include "dp_aux.h" + +#define DS_PORT_STATUS_CHANGED 0x200 +#define DP_TEST_BIT_DEPTH_UNKNOWN 0xFFFFFFFF +#define DP_LINK_ENUM_STR(x) #x + +enum dp_link_voltage_level { + DP_LINK_VOLTAGE_LEVEL_0 = 0, + DP_LINK_VOLTAGE_LEVEL_1 = 1, + DP_LINK_VOLTAGE_LEVEL_2 = 2, + DP_LINK_VOLTAGE_MAX = DP_LINK_VOLTAGE_LEVEL_2, +}; + +enum dp_link_preemaphasis_level { + DP_LINK_PRE_EMPHASIS_LEVEL_0 = 0, + DP_LINK_PRE_EMPHASIS_LEVEL_1 = 1, + DP_LINK_PRE_EMPHASIS_LEVEL_2 = 2, + DP_LINK_PRE_EMPHASIS_MAX = DP_LINK_PRE_EMPHASIS_LEVEL_2, +}; + +struct dp_link_sink_count { + u32 count; + bool cp_ready; +}; + +struct dp_link_test_video { + u32 test_video_pattern; + u32 test_bit_depth; + u32 test_dyn_range; + u32 test_h_total; + u32 test_v_total; + u32 test_h_start; + u32 test_v_start; + u32 test_hsync_pol; + u32 test_hsync_width; + u32 test_vsync_pol; + u32 test_vsync_width; + u32 test_h_width; + u32 test_v_height; + u32 test_rr_d; + u32 test_rr_n; +}; + +struct dp_link_test_audio { + u32 test_audio_sampling_rate; + u32 test_audio_channel_count; + u32 test_audio_pattern_type; + u32 test_audio_period_ch_1; + u32 test_audio_period_ch_2; + u32 test_audio_period_ch_3; + u32 test_audio_period_ch_4; + u32 test_audio_period_ch_5; + u32 test_audio_period_ch_6; + u32 test_audio_period_ch_7; + u32 test_audio_period_ch_8; +}; + +struct dp_link_phy_params { + u32 phy_test_pattern_sel; + u8 v_level; + u8 p_level; +}; + +struct dp_link_params { + u32 lane_count; + u32 bw_code; +}; + +struct dp_link { + u32 sink_request; + u32 test_response; + bool psm_enabled; + + struct dp_link_sink_count sink_count; + struct dp_link_test_video test_video; + struct dp_link_test_audio test_audio; + struct dp_link_phy_params phy_params; + struct dp_link_params link_params; + + u32 (*get_test_bits_depth)(struct dp_link *dp_link, u32 bpp); + int (*process_request)(struct dp_link *dp_link); + int (*get_colorimetry_config)(struct dp_link *dp_link); + int (*adjust_levels)(struct dp_link *dp_link, u8 *link_status); + int (*send_psm_request)(struct dp_link *dp_link, bool req); + void (*send_test_response)(struct dp_link *dp_link); + int (*psm_config)(struct dp_link *dp_link, + struct drm_dp_link *link_info, bool enable); + void (*send_edid_checksum)(struct dp_link *dp_link, u8 checksum); +}; + +static inline char *dp_link_get_phy_test_pattern(u32 phy_test_pattern_sel) +{ + switch (phy_test_pattern_sel) { + case DP_TEST_PHY_PATTERN_NONE: + return DP_LINK_ENUM_STR(DP_TEST_PHY_PATTERN_NONE); + case DP_TEST_PHY_PATTERN_D10_2_NO_SCRAMBLING: + return DP_LINK_ENUM_STR( + DP_TEST_PHY_PATTERN_D10_2_NO_SCRAMBLING); + case DP_TEST_PHY_PATTERN_SYMBOL_ERR_MEASUREMENT_CNT: + return DP_LINK_ENUM_STR( + DP_TEST_PHY_PATTERN_SYMBOL_ERR_MEASUREMENT_CNT); + case DP_TEST_PHY_PATTERN_PRBS7: + return DP_LINK_ENUM_STR(DP_TEST_PHY_PATTERN_PRBS7); + case DP_TEST_PHY_PATTERN_80_BIT_CUSTOM_PATTERN: + return DP_LINK_ENUM_STR( + DP_TEST_PHY_PATTERN_80_BIT_CUSTOM_PATTERN); + case DP_TEST_PHY_PATTERN_HBR2_CTS_EYE_PATTERN: + return DP_LINK_ENUM_STR( + DP_TEST_PHY_PATTERN_HBR2_CTS_EYE_PATTERN); + default: + return "unknown"; + } +} + +/** + * mdss_dp_test_bit_depth_to_bpp() - convert test bit depth to bpp + * @tbd: test bit depth + * + * Returns the bits per pixel (bpp) to be used corresponding to the + * git bit depth value. This function assumes that bit depth has + * already been validated. + */ +static inline u32 dp_link_bit_depth_to_bpp(u32 tbd) +{ + u32 bpp; + + /* + * Few simplistic rules and assumptions made here: + * 1. Bit depth is per color component + * 2. If bit depth is unknown return 0 + * 3. Assume 3 color components + */ + switch (tbd) { + case DP_TEST_BIT_DEPTH_6: + bpp = 18; + break; + case DP_TEST_BIT_DEPTH_8: + bpp = 24; + break; + case DP_TEST_BIT_DEPTH_10: + bpp = 30; + break; + case DP_TEST_BIT_DEPTH_UNKNOWN: + default: + bpp = 0; + } + + return bpp; +} + +/** + * dp_link_get() - get the functionalities of dp test module + * + * + * return: a pointer to dp_link struct + */ +struct dp_link *dp_link_get(struct device *dev, struct dp_aux *aux); + +/** + * dp_link_put() - releases the dp test module's resources + * + * @dp_link: an instance of dp_link module + * + */ +void dp_link_put(struct dp_link *dp_link); + +#endif /* _DP_LINK_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c new file mode 100644 index 000000000000..e7fc6465a988 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_panel.c @@ -0,0 +1,531 @@ +/* + * Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include "dp_panel.h" + +#define DP_PANEL_DEFAULT_BPP 24 +#define DP_MAX_DS_PORT_COUNT 1 + +enum { + DP_LINK_RATE_MULTIPLIER = 27000000, +}; + +struct dp_panel_private { + struct device *dev; + struct dp_panel dp_panel; + struct dp_aux *aux; + struct dp_link *link; + struct dp_catalog_panel *catalog; + bool aux_cfg_update_done; +}; + +static const struct dp_panel_info fail_safe = { + .h_active = 640, + .v_active = 480, + .h_back_porch = 48, + .h_front_porch = 16, + .h_sync_width = 96, + .h_active_low = 0, + .v_back_porch = 33, + .v_front_porch = 10, + .v_sync_width = 2, + .v_active_low = 0, + .h_skew = 0, + .refresh_rate = 60, + .pixel_clk_khz = 25200, + .bpp = 24, +}; + +static int dp_panel_read_dpcd(struct dp_panel *dp_panel) +{ + int rlen, rc = 0; + struct dp_panel_private *panel; + struct drm_dp_link *link_info; + u8 *dpcd, major = 0, minor = 0; + u32 dfp_count = 0; + unsigned long caps = DP_LINK_CAP_ENHANCED_FRAMING; + + if (!dp_panel) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + dpcd = dp_panel->dpcd; + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + link_info = &dp_panel->link_info; + + rlen = drm_dp_dpcd_read(panel->aux->drm_aux, DP_DPCD_REV, + dpcd, (DP_RECEIVER_CAP_SIZE + 1)); + if (rlen < (DP_RECEIVER_CAP_SIZE + 1)) { + pr_err("dpcd read failed, rlen=%d\n", rlen); + rc = -EINVAL; + goto end; + } + + link_info->revision = dp_panel->dpcd[DP_DPCD_REV]; + + major = (link_info->revision >> 4) & 0x0f; + minor = link_info->revision & 0x0f; + pr_debug("version: %d.%d\n", major, minor); + + link_info->rate = + drm_dp_bw_code_to_link_rate(dp_panel->dpcd[DP_MAX_LINK_RATE]); + pr_debug("link_rate=%d\n", link_info->rate); + + link_info->num_lanes = dp_panel->dpcd[DP_MAX_LANE_COUNT] & + DP_MAX_LANE_COUNT_MASK; + + pr_debug("lane_count=%d\n", link_info->num_lanes); + + if (drm_dp_enhanced_frame_cap(dpcd)) + link_info->capabilities |= caps; + + dfp_count = dpcd[DP_DOWN_STREAM_PORT_COUNT] & + DP_DOWN_STREAM_PORT_COUNT; + + if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT) + && (dpcd[DP_DPCD_REV] > 0x10)) { + rlen = drm_dp_dpcd_read(panel->aux->drm_aux, + DP_DOWNSTREAM_PORT_0, dp_panel->ds_ports, + DP_MAX_DOWNSTREAM_PORTS); + if (rlen < DP_MAX_DOWNSTREAM_PORTS) { + pr_err("ds port status failed, rlen=%d\n", rlen); + rc = -EINVAL; + goto end; + } + } + + if (dfp_count > DP_MAX_DS_PORT_COUNT) + pr_debug("DS port count %d greater that max (%d) supported\n", + dfp_count, DP_MAX_DS_PORT_COUNT); + +end: + return rc; +} + +static int dp_panel_set_default_link_params(struct dp_panel *dp_panel) +{ + struct drm_dp_link *link_info; + const int default_bw_code = 162000; + const int default_num_lanes = 1; + + if (!dp_panel) { + pr_err("invalid input\n"); + return -EINVAL; + } + link_info = &dp_panel->link_info; + link_info->rate = default_bw_code; + link_info->num_lanes = default_num_lanes; + pr_debug("link_rate=%d num_lanes=%d\n", + link_info->rate, link_info->num_lanes); + return 0; +} + +static int dp_panel_read_edid(struct dp_panel *dp_panel, + struct drm_connector *connector) +{ + int retry_cnt = 0; + const int max_retry = 10; + struct dp_panel_private *panel; + + if (!dp_panel) { + pr_err("invalid input\n"); + return -EINVAL; + } + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + + do { + sde_get_edid(connector, &panel->aux->drm_aux->ddc, + (void **)&dp_panel->edid_ctrl); + if (!dp_panel->edid_ctrl->edid) { + pr_err("EDID read failed\n"); + retry_cnt++; + panel->aux->reconfig(panel->aux); + panel->aux_cfg_update_done = true; + } else { + return 0; + } + } while (retry_cnt < max_retry); + + return -EINVAL; +} + +static int dp_panel_read_sink_caps(struct dp_panel *dp_panel, + struct drm_connector *connector) +{ + int rc = 0; + struct dp_panel_private *panel; + + if (!dp_panel || !connector) { + pr_err("invalid input\n"); + return -EINVAL; + } + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + + rc = dp_panel_read_dpcd(dp_panel); + if (rc || !is_link_rate_valid(drm_dp_link_rate_to_bw_code( + dp_panel->link_info.rate)) || !is_lane_count_valid( + dp_panel->link_info.num_lanes)) { + pr_err("panel dpcd read failed/incorrect, set default params\n"); + dp_panel_set_default_link_params(dp_panel); + } + + rc = dp_panel_read_edid(dp_panel, connector); + if (rc) { + pr_err("panel edid read failed, set failsafe mode\n"); + return rc; + } + + if (panel->aux_cfg_update_done) { + pr_debug("read DPCD with updated AUX config\n"); + dp_panel_read_dpcd(dp_panel); + panel->aux_cfg_update_done = false; + } + + return 0; +} + +static u32 dp_panel_get_max_pclk(struct dp_panel *dp_panel) +{ + struct drm_dp_link *link_info; + const u8 num_components = 3; + u32 bpc = 0, bpp = 0, max_data_rate_khz = 0, max_pclk_rate_khz = 0; + + if (!dp_panel) { + pr_err("invalid input\n"); + return 0; + } + + link_info = &dp_panel->link_info; + + bpc = sde_get_sink_bpc(dp_panel->edid_ctrl); + bpp = bpc * num_components; + if (!bpp) + bpp = DP_PANEL_DEFAULT_BPP; + + max_data_rate_khz = (link_info->num_lanes * link_info->rate * 8); + max_pclk_rate_khz = max_data_rate_khz / bpp; + + pr_debug("bpp=%d, max_lane_cnt=%d\n", bpp, link_info->num_lanes); + pr_debug("max_data_rate=%dKHz, max_pclk_rate=%dKHz\n", + max_data_rate_khz, max_pclk_rate_khz); + + return max_pclk_rate_khz; +} + +static void dp_panel_set_test_mode(struct dp_panel_private *panel, + struct dp_display_mode *mode) +{ + struct dp_panel_info *pinfo = NULL; + struct dp_link_test_video *test_info = NULL; + + if (!panel) { + pr_err("invalid params\n"); + return; + } + + pinfo = &mode->timing; + test_info = &panel->link->test_video; + + pinfo->h_active = test_info->test_h_width; + pinfo->h_sync_width = test_info->test_hsync_width; + pinfo->h_back_porch = test_info->test_h_start - + test_info->test_hsync_width; + pinfo->h_front_porch = test_info->test_h_total - + (test_info->test_h_start + test_info->test_h_width); + + pinfo->v_active = test_info->test_v_height; + pinfo->v_sync_width = test_info->test_vsync_width; + pinfo->v_back_porch = test_info->test_v_start - + test_info->test_vsync_width; + pinfo->v_front_porch = test_info->test_v_total - + (test_info->test_v_start + test_info->test_v_height); + + pinfo->bpp = dp_link_bit_depth_to_bpp(test_info->test_bit_depth); + pinfo->h_active_low = test_info->test_hsync_pol; + pinfo->v_active_low = test_info->test_vsync_pol; + + pinfo->refresh_rate = test_info->test_rr_n; + pinfo->pixel_clk_khz = test_info->test_h_total * + test_info->test_v_total * pinfo->refresh_rate; + + if (test_info->test_rr_d == 0) + pinfo->pixel_clk_khz /= 1000; + else + pinfo->pixel_clk_khz /= 1001; + + if (test_info->test_h_width == 640) + pinfo->pixel_clk_khz = 25170; +} + +static int dp_panel_get_modes(struct dp_panel *dp_panel, + struct drm_connector *connector, struct dp_display_mode *mode) +{ + struct dp_panel_private *panel; + + if (!dp_panel) { + pr_err("invalid input\n"); + return -EINVAL; + } + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + + if (dp_panel->video_test) { + dp_panel_set_test_mode(panel, mode); + return 1; + } else if (dp_panel->edid_ctrl->edid) { + return _sde_edid_update_modes(connector, dp_panel->edid_ctrl); + } + + /* fail-safe mode */ + memcpy(&mode->timing, &fail_safe, + sizeof(fail_safe)); + return 1; +} + +static void dp_panel_handle_sink_request(struct dp_panel *dp_panel) +{ + struct dp_panel_private *panel; + + if (!dp_panel) { + pr_err("invalid input\n"); + return; + } + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + + if (panel->link->sink_request & DP_TEST_LINK_EDID_READ) { + u8 checksum = sde_get_edid_checksum(dp_panel->edid_ctrl); + + panel->link->send_edid_checksum(panel->link, checksum); + panel->link->send_test_response(panel->link); + } +} + +static int dp_panel_timing_cfg(struct dp_panel *dp_panel) +{ + int rc = 0; + u32 data, total_ver, total_hor; + struct dp_catalog_panel *catalog; + struct dp_panel_private *panel; + struct dp_panel_info *pinfo; + + if (!dp_panel) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + catalog = panel->catalog; + pinfo = &panel->dp_panel.pinfo; + + pr_debug("width=%d hporch= %d %d %d\n", + pinfo->h_active, pinfo->h_back_porch, + pinfo->h_front_porch, pinfo->h_sync_width); + + pr_debug("height=%d vporch= %d %d %d\n", + pinfo->v_active, pinfo->v_back_porch, + pinfo->v_front_porch, pinfo->v_sync_width); + + total_hor = pinfo->h_active + pinfo->h_back_porch + + pinfo->h_front_porch + pinfo->h_sync_width; + + total_ver = pinfo->v_active + pinfo->v_back_porch + + pinfo->v_front_porch + pinfo->v_sync_width; + + data = total_ver; + data <<= 16; + data |= total_hor; + + catalog->total = data; + + data = (pinfo->v_back_porch + pinfo->v_sync_width); + data <<= 16; + data |= (pinfo->h_back_porch + pinfo->h_sync_width); + + catalog->sync_start = data; + + data = pinfo->v_sync_width; + data <<= 16; + data |= (pinfo->v_active_low << 31); + data |= pinfo->h_sync_width; + data |= (pinfo->h_active_low << 15); + + catalog->width_blanking = data; + + data = pinfo->v_active; + data <<= 16; + data |= pinfo->h_active; + + catalog->dp_active = data; + + panel->catalog->timing_cfg(catalog); +end: + return rc; +} + +static int dp_panel_edid_register(struct dp_panel *dp_panel) +{ + int rc = 0; + + if (!dp_panel) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + dp_panel->edid_ctrl = sde_edid_init(); + if (!dp_panel->edid_ctrl) { + pr_err("sde edid init for DP failed\n"); + rc = -ENOMEM; + goto end; + } +end: + return rc; +} + +static void dp_panel_edid_deregister(struct dp_panel *dp_panel) +{ + if (!dp_panel) { + pr_err("invalid input\n"); + return; + } + + sde_edid_deinit((void **)&dp_panel->edid_ctrl); +} + +static int dp_panel_init_panel_info(struct dp_panel *dp_panel) +{ + int rc = 0; + struct dp_panel_info *pinfo; + + if (!dp_panel) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + pinfo = &dp_panel->pinfo; + + /* + * print resolution info as this is a result + * of user initiated action of cable connection + */ + pr_info("SET NEW RESOLUTION:\n"); + pr_info("%dx%d@%dfps\n", pinfo->h_active, + pinfo->v_active, pinfo->refresh_rate); + pr_info("h_porches(back|front|width) = (%d|%d|%d)\n", + pinfo->h_back_porch, + pinfo->h_front_porch, + pinfo->h_sync_width); + pr_info("v_porches(back|front|width) = (%d|%d|%d)\n", + pinfo->v_back_porch, + pinfo->v_front_porch, + pinfo->v_sync_width); + pr_info("pixel clock (KHz)=(%d)\n", pinfo->pixel_clk_khz); + pr_info("bpp = %d\n", pinfo->bpp); + pr_info("active low (h|v)=(%d|%d)\n", pinfo->h_active_low, + pinfo->v_active_low); + + pinfo->bpp = max_t(u32, 18, min_t(u32, pinfo->bpp, 30)); + pr_info("updated bpp = %d\n", pinfo->bpp); +end: + return rc; +} + +static u32 dp_panel_get_min_req_link_rate(struct dp_panel *dp_panel) +{ + const u32 encoding_factx10 = 8; + u32 min_link_rate_khz = 0, lane_cnt; + struct dp_panel_info *pinfo; + + if (!dp_panel) { + pr_err("invalid input\n"); + goto end; + } + + lane_cnt = dp_panel->link_info.num_lanes; + pinfo = &dp_panel->pinfo; + + /* num_lanes * lane_count * 8 >= pclk * bpp * 10 */ + min_link_rate_khz = pinfo->pixel_clk_khz / + (lane_cnt * encoding_factx10); + min_link_rate_khz *= pinfo->bpp; + + pr_debug("min lclk req=%d khz for pclk=%d khz, lanes=%d, bpp=%d\n", + min_link_rate_khz, pinfo->pixel_clk_khz, lane_cnt, + pinfo->bpp); +end: + return min_link_rate_khz; +} + +struct dp_panel *dp_panel_get(struct dp_panel_in *in) +{ + int rc = 0; + struct dp_panel_private *panel; + struct dp_panel *dp_panel; + + if (!in->dev || !in->catalog || !in->aux || !in->link) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + panel = devm_kzalloc(in->dev, sizeof(*panel), GFP_KERNEL); + if (!panel) { + rc = -ENOMEM; + goto error; + } + + panel->dev = in->dev; + panel->aux = in->aux; + panel->catalog = in->catalog; + panel->link = in->link; + + dp_panel = &panel->dp_panel; + panel->aux_cfg_update_done = false; + + dp_panel->sde_edid_register = dp_panel_edid_register; + dp_panel->sde_edid_deregister = dp_panel_edid_deregister; + dp_panel->init_info = dp_panel_init_panel_info; + dp_panel->timing_cfg = dp_panel_timing_cfg; + dp_panel->read_sink_caps = dp_panel_read_sink_caps; + dp_panel->get_min_req_link_rate = dp_panel_get_min_req_link_rate; + dp_panel->get_max_pclk = dp_panel_get_max_pclk; + dp_panel->get_modes = dp_panel_get_modes; + dp_panel->handle_sink_request = dp_panel_handle_sink_request; + + return dp_panel; +error: + return ERR_PTR(rc); +} + +void dp_panel_put(struct dp_panel *dp_panel) +{ + struct dp_panel_private *panel; + + if (!dp_panel) + return; + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + + devm_kfree(panel->dev, panel); +} diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h new file mode 100644 index 000000000000..4486a8445fe4 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_panel.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _DP_PANEL_H_ +#define _DP_PANEL_H_ + +#include "dp_aux.h" +#include "dp_link.h" +#include "dp_usbpd.h" +#include "sde_edid_parser.h" + +enum dp_lane_count { + DP_LANE_COUNT_1 = 1, + DP_LANE_COUNT_2 = 2, + DP_LANE_COUNT_4 = 4, +}; + +#define DP_MAX_DOWNSTREAM_PORTS 0x10 + +struct dp_panel_info { + u32 h_active; + u32 v_active; + u32 h_back_porch; + u32 h_front_porch; + u32 h_sync_width; + u32 h_active_low; + u32 v_back_porch; + u32 v_front_porch; + u32 v_sync_width; + u32 v_active_low; + u32 h_skew; + u32 refresh_rate; + u32 pixel_clk_khz; + u32 bpp; +}; + +struct dp_display_mode { + struct dp_panel_info timing; + u32 capabilities; +}; + +struct dp_panel_in { + struct device *dev; + struct dp_aux *aux; + struct dp_link *link; + struct dp_catalog_panel *catalog; +}; + +struct dp_panel { + /* dpcd raw data */ + u8 dpcd[DP_RECEIVER_CAP_SIZE]; + u8 ds_ports[DP_MAX_DOWNSTREAM_PORTS]; + + struct drm_dp_link link_info; + struct sde_edid_ctrl *edid_ctrl; + struct drm_connector *connector; + struct dp_panel_info pinfo; + bool video_test; + + u32 vic; + u32 max_pclk_khz; + + int (*sde_edid_register)(struct dp_panel *dp_panel); + void (*sde_edid_deregister)(struct dp_panel *dp_panel); + int (*init_info)(struct dp_panel *dp_panel); + int (*timing_cfg)(struct dp_panel *dp_panel); + int (*read_sink_caps)(struct dp_panel *dp_panel, + struct drm_connector *connector); + u32 (*get_min_req_link_rate)(struct dp_panel *dp_panel); + u32 (*get_max_pclk)(struct dp_panel *dp_panel); + int (*get_modes)(struct dp_panel *dp_panel, + struct drm_connector *connector, struct dp_display_mode *mode); + void (*handle_sink_request)(struct dp_panel *dp_panel); +}; + +/** + * is_link_rate_valid() - validates the link rate + * @lane_rate: link rate requested by the sink + * + * Returns true if the requested link rate is supported. + */ +static inline bool is_link_rate_valid(u32 bw_code) +{ + return ((bw_code == DP_LINK_BW_1_62) || + (bw_code == DP_LINK_BW_2_7) || + (bw_code == DP_LINK_BW_5_4) || + (bw_code == DP_LINK_BW_8_1)); +} + +/** + * dp_link_is_lane_count_valid() - validates the lane count + * @lane_count: lane count requested by the sink + * + * Returns true if the requested lane count is supported. + */ +static inline bool is_lane_count_valid(u32 lane_count) +{ + return (lane_count == DP_LANE_COUNT_1) || + (lane_count == DP_LANE_COUNT_2) || + (lane_count == DP_LANE_COUNT_4); +} + +struct dp_panel *dp_panel_get(struct dp_panel_in *in); +void dp_panel_put(struct dp_panel *dp_panel); +#endif /* _DP_PANEL_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_parser.c b/drivers/gpu/drm/msm/dp/dp_parser.c new file mode 100644 index 000000000000..c8da99a65b57 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_parser.c @@ -0,0 +1,645 @@ +/* + * Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include + +#include "dp_parser.h" + +static void dp_parser_unmap_io_resources(struct dp_parser *parser) +{ + struct dp_io *io = &parser->io; + + msm_dss_iounmap(&io->ctrl_io); + msm_dss_iounmap(&io->phy_io); + msm_dss_iounmap(&io->ln_tx0_io); + msm_dss_iounmap(&io->ln_tx0_io); + msm_dss_iounmap(&io->dp_pll_io); + msm_dss_iounmap(&io->dp_cc_io); + msm_dss_iounmap(&io->usb3_dp_com); + msm_dss_iounmap(&io->qfprom_io); + msm_dss_iounmap(&io->hdcp_io); +} + +static int dp_parser_ctrl_res(struct dp_parser *parser) +{ + int rc = 0; + u32 index; + struct platform_device *pdev = parser->pdev; + struct device_node *of_node = parser->pdev->dev.of_node; + struct dp_io *io = &parser->io; + + rc = of_property_read_u32(of_node, "cell-index", &index); + if (rc) { + pr_err("cell-index not specified, rc=%d\n", rc); + goto err; + } + + rc = msm_dss_ioremap_byname(pdev, &io->ctrl_io, "dp_ctrl"); + if (rc) { + pr_err("unable to remap dp io resources\n"); + goto err; + } + + rc = msm_dss_ioremap_byname(pdev, &io->phy_io, "dp_phy"); + if (rc) { + pr_err("unable to remap dp PHY resources\n"); + goto err; + } + + rc = msm_dss_ioremap_byname(pdev, &io->ln_tx0_io, "dp_ln_tx0"); + if (rc) { + pr_err("unable to remap dp TX0 resources\n"); + goto err; + } + + rc = msm_dss_ioremap_byname(pdev, &io->ln_tx1_io, "dp_ln_tx1"); + if (rc) { + pr_err("unable to remap dp TX1 resources\n"); + goto err; + } + + rc = msm_dss_ioremap_byname(pdev, &io->dp_pll_io, "dp_pll"); + if (rc) { + pr_err("unable to remap DP PLL resources\n"); + goto err; + } + + rc = msm_dss_ioremap_byname(pdev, &io->usb3_dp_com, "usb3_dp_com"); + if (rc) { + pr_err("unable to remap USB3 DP com resources\n"); + goto err; + } + + if (msm_dss_ioremap_byname(pdev, &io->dp_cc_io, "dp_mmss_cc")) { + pr_err("unable to remap dp MMSS_CC resources\n"); + goto err; + } + + if (msm_dss_ioremap_byname(pdev, &io->qfprom_io, "qfprom_physical")) + pr_warn("unable to remap dp qfprom resources\n"); + + if (msm_dss_ioremap_byname(pdev, &io->hdcp_io, "hdcp_physical")) + pr_warn("unable to remap dp hdcp resources\n"); + + return 0; +err: + dp_parser_unmap_io_resources(parser); + return rc; +} + +static const char *dp_get_phy_aux_config_property(u32 cfg_type) +{ + switch (cfg_type) { + case PHY_AUX_CFG0: + return "qcom,aux-cfg0-settings"; + case PHY_AUX_CFG1: + return "qcom,aux-cfg1-settings"; + case PHY_AUX_CFG2: + return "qcom,aux-cfg2-settings"; + case PHY_AUX_CFG3: + return "qcom,aux-cfg3-settings"; + case PHY_AUX_CFG4: + return "qcom,aux-cfg4-settings"; + case PHY_AUX_CFG5: + return "qcom,aux-cfg5-settings"; + case PHY_AUX_CFG6: + return "qcom,aux-cfg6-settings"; + case PHY_AUX_CFG7: + return "qcom,aux-cfg7-settings"; + case PHY_AUX_CFG8: + return "qcom,aux-cfg8-settings"; + case PHY_AUX_CFG9: + return "qcom,aux-cfg9-settings"; + default: + return "unknown"; + } +} + +static void dp_parser_phy_aux_cfg_reset(struct dp_parser *parser) +{ + int i = 0; + + for (i = 0; i < PHY_AUX_CFG_MAX; i++) + parser->aux_cfg[i] = (const struct dp_aux_cfg){ 0 }; +} + +static int dp_parser_aux(struct dp_parser *parser) +{ + struct device_node *of_node = parser->pdev->dev.of_node; + int len = 0, i = 0, j = 0, config_count = 0; + const char *data; + int const minimum_config_count = 1; + + for (i = 0; i < PHY_AUX_CFG_MAX; i++) { + const char *property = dp_get_phy_aux_config_property(i); + + data = of_get_property(of_node, property, &len); + if (!data) { + pr_err("Unable to read %s\n", property); + goto error; + } + + config_count = len - 1; + if ((config_count < minimum_config_count) || + (config_count > DP_AUX_CFG_MAX_VALUE_CNT)) { + pr_err("Invalid config count (%d) configs for %s\n", + config_count, property); + goto error; + } + + parser->aux_cfg[i].offset = data[0]; + parser->aux_cfg[i].cfg_cnt = config_count; + pr_debug("%s offset=0x%x, cfg_cnt=%d\n", + property, + parser->aux_cfg[i].offset, + parser->aux_cfg[i].cfg_cnt); + for (j = 1; j < len; j++) { + parser->aux_cfg[i].lut[j - 1] = data[j]; + pr_debug("%s lut[%d]=0x%x\n", + property, + i, + parser->aux_cfg[i].lut[j - 1]); + } + } + return 0; + +error: + dp_parser_phy_aux_cfg_reset(parser); + return -EINVAL; +} + +static int dp_parser_misc(struct dp_parser *parser) +{ + int rc = 0; + struct device_node *of_node = parser->pdev->dev.of_node; + + rc = of_property_read_u32(of_node, + "qcom,max-pclk-frequency-khz", &parser->max_pclk_khz); + if (rc) + parser->max_pclk_khz = DP_MAX_PIXEL_CLK_KHZ; + + return 0; +} + +static int dp_parser_pinctrl(struct dp_parser *parser) +{ + int rc = 0; + struct dp_pinctrl *pinctrl = &parser->pinctrl; + + pinctrl->pin = devm_pinctrl_get(&parser->pdev->dev); + + if (IS_ERR_OR_NULL(pinctrl->pin)) { + rc = PTR_ERR(pinctrl->pin); + pr_err("failed to get pinctrl, rc=%d\n", rc); + goto error; + } + + pinctrl->state_active = pinctrl_lookup_state(pinctrl->pin, + "mdss_dp_active"); + if (IS_ERR_OR_NULL(pinctrl->state_active)) { + rc = PTR_ERR(pinctrl->state_active); + pr_err("failed to get pinctrl active state, rc=%d\n", rc); + goto error; + } + + pinctrl->state_suspend = pinctrl_lookup_state(pinctrl->pin, + "mdss_dp_sleep"); + if (IS_ERR_OR_NULL(pinctrl->state_suspend)) { + rc = PTR_ERR(pinctrl->state_suspend); + pr_err("failed to get pinctrl suspend state, rc=%d\n", rc); + goto error; + } +error: + return rc; +} + +static int dp_parser_gpio(struct dp_parser *parser) +{ + int i = 0; + struct device *dev = &parser->pdev->dev; + struct device_node *of_node = dev->of_node; + struct dss_module_power *mp = &parser->mp[DP_CORE_PM]; + static const char * const dp_gpios[] = { + "qcom,aux-en-gpio", + "qcom,aux-sel-gpio", + "qcom,usbplug-cc-gpio", + }; + + mp->gpio_config = devm_kzalloc(dev, + sizeof(struct dss_gpio) * ARRAY_SIZE(dp_gpios), GFP_KERNEL); + mp->num_gpio = ARRAY_SIZE(dp_gpios); + + for (i = 0; i < ARRAY_SIZE(dp_gpios); i++) { + mp->gpio_config[i].gpio = of_get_named_gpio(of_node, + dp_gpios[i], 0); + + if (!gpio_is_valid(mp->gpio_config[i].gpio)) { + pr_err("%s gpio not specified\n", dp_gpios[i]); + return -EINVAL; + } + + strlcpy(mp->gpio_config[i].gpio_name, dp_gpios[i], + sizeof(mp->gpio_config[i].gpio_name)); + + mp->gpio_config[i].value = 0; + } + + return 0; +} + +static const char *dp_parser_supply_node_name(enum dp_pm_type module) +{ + switch (module) { + case DP_CORE_PM: return "qcom,core-supply-entries"; + case DP_CTRL_PM: return "qcom,ctrl-supply-entries"; + case DP_PHY_PM: return "qcom,phy-supply-entries"; + default: return "???"; + } +} + +static int dp_parser_get_vreg(struct dp_parser *parser, + enum dp_pm_type module) +{ + int i = 0, rc = 0; + u32 tmp = 0; + const char *pm_supply_name = NULL; + struct device_node *supply_node = NULL; + struct device_node *of_node = parser->pdev->dev.of_node; + struct device_node *supply_root_node = NULL; + struct dss_module_power *mp = &parser->mp[module]; + + mp->num_vreg = 0; + pm_supply_name = dp_parser_supply_node_name(module); + supply_root_node = of_get_child_by_name(of_node, pm_supply_name); + if (!supply_root_node) { + pr_err("no supply entry present: %s\n", pm_supply_name); + goto novreg; + } + + mp->num_vreg = of_get_available_child_count(supply_root_node); + + if (mp->num_vreg == 0) { + pr_debug("no vreg\n"); + goto novreg; + } else { + pr_debug("vreg found. count=%d\n", mp->num_vreg); + } + + mp->vreg_config = devm_kzalloc(&parser->pdev->dev, + sizeof(struct dss_vreg) * mp->num_vreg, GFP_KERNEL); + if (!mp->vreg_config) { + rc = -ENOMEM; + goto error; + } + + for_each_child_of_node(supply_root_node, supply_node) { + const char *st = NULL; + /* vreg-name */ + rc = of_property_read_string(supply_node, + "qcom,supply-name", &st); + if (rc) { + pr_err("error reading name. rc=%d\n", + rc); + goto error; + } + snprintf(mp->vreg_config[i].vreg_name, + ARRAY_SIZE((mp->vreg_config[i].vreg_name)), "%s", st); + /* vreg-min-voltage */ + rc = of_property_read_u32(supply_node, + "qcom,supply-min-voltage", &tmp); + if (rc) { + pr_err("error reading min volt. rc=%d\n", + rc); + goto error; + } + mp->vreg_config[i].min_voltage = tmp; + + /* vreg-max-voltage */ + rc = of_property_read_u32(supply_node, + "qcom,supply-max-voltage", &tmp); + if (rc) { + pr_err("error reading max volt. rc=%d\n", + rc); + goto error; + } + mp->vreg_config[i].max_voltage = tmp; + + /* enable-load */ + rc = of_property_read_u32(supply_node, + "qcom,supply-enable-load", &tmp); + if (rc) { + pr_err("error reading enable load. rc=%d\n", + rc); + goto error; + } + mp->vreg_config[i].enable_load = tmp; + + /* disable-load */ + rc = of_property_read_u32(supply_node, + "qcom,supply-disable-load", &tmp); + if (rc) { + pr_err("error reading disable load. rc=%d\n", + rc); + goto error; + } + mp->vreg_config[i].disable_load = tmp; + + pr_debug("%s min=%d, max=%d, enable=%d, disable=%d\n", + mp->vreg_config[i].vreg_name, + mp->vreg_config[i].min_voltage, + mp->vreg_config[i].max_voltage, + mp->vreg_config[i].enable_load, + mp->vreg_config[i].disable_load + ); + ++i; + } + + return rc; + +error: + if (mp->vreg_config) { + devm_kfree(&parser->pdev->dev, mp->vreg_config); + mp->vreg_config = NULL; + } +novreg: + mp->num_vreg = 0; + + return rc; +} + +static void dp_parser_put_vreg_data(struct device *dev, + struct dss_module_power *mp) +{ + if (!mp) { + DEV_ERR("invalid input\n"); + return; + } + + if (mp->vreg_config) { + devm_kfree(dev, mp->vreg_config); + mp->vreg_config = NULL; + } + mp->num_vreg = 0; +} + +static int dp_parser_regulator(struct dp_parser *parser) +{ + int i, rc = 0; + struct platform_device *pdev = parser->pdev; + + /* Parse the regulator information */ + for (i = DP_CORE_PM; i < DP_MAX_PM; i++) { + rc = dp_parser_get_vreg(parser, i); + if (rc) { + pr_err("get_dt_vreg_data failed for %s. rc=%d\n", + dp_parser_pm_name(i), rc); + i--; + for (; i >= DP_CORE_PM; i--) + dp_parser_put_vreg_data(&pdev->dev, + &parser->mp[i]); + break; + } + } + + return rc; +} + +static bool dp_parser_check_prefix(const char *clk_prefix, const char *clk_name) +{ + return !!strnstr(clk_name, clk_prefix, strlen(clk_name)); +} + +static void dp_parser_put_clk_data(struct device *dev, + struct dss_module_power *mp) +{ + if (!mp) { + DEV_ERR("%s: invalid input\n", __func__); + return; + } + + if (mp->clk_config) { + devm_kfree(dev, mp->clk_config); + mp->clk_config = NULL; + } + + mp->num_clk = 0; +} + +static int dp_parser_init_clk_data(struct dp_parser *parser) +{ + int num_clk = 0, i = 0, rc = 0; + int core_clk_count = 0, ctrl_clk_count = 0; + const char *core_clk = "core"; + const char *ctrl_clk = "ctrl"; + const char *clk_name; + struct device *dev = &parser->pdev->dev; + struct dss_module_power *core_power = &parser->mp[DP_CORE_PM]; + struct dss_module_power *ctrl_power = &parser->mp[DP_CTRL_PM]; + + num_clk = of_property_count_strings(dev->of_node, "clock-names"); + if (num_clk <= 0) { + pr_err("no clocks are defined\n"); + rc = -EINVAL; + goto exit; + } + + for (i = 0; i < num_clk; i++) { + of_property_read_string_index(dev->of_node, + "clock-names", i, &clk_name); + + if (dp_parser_check_prefix(core_clk, clk_name)) + core_clk_count++; + + if (dp_parser_check_prefix(ctrl_clk, clk_name)) + ctrl_clk_count++; + } + + /* Initialize the CORE power module */ + if (core_clk_count <= 0) { + pr_err("no core clocks are defined\n"); + rc = -EINVAL; + goto exit; + } + + core_power->num_clk = core_clk_count; + core_power->clk_config = devm_kzalloc(dev, + sizeof(struct dss_clk) * core_power->num_clk, + GFP_KERNEL); + if (!core_power->clk_config) { + rc = -EINVAL; + goto exit; + } + + /* Initialize the CTRL power module */ + if (ctrl_clk_count <= 0) { + pr_err("no ctrl clocks are defined\n"); + rc = -EINVAL; + goto ctrl_clock_error; + } + + ctrl_power->num_clk = ctrl_clk_count; + ctrl_power->clk_config = devm_kzalloc(dev, + sizeof(struct dss_clk) * ctrl_power->num_clk, + GFP_KERNEL); + if (!ctrl_power->clk_config) { + ctrl_power->num_clk = 0; + rc = -EINVAL; + goto ctrl_clock_error; + } + + return rc; + +ctrl_clock_error: + dp_parser_put_clk_data(dev, core_power); +exit: + return rc; +} + +static int dp_parser_clock(struct dp_parser *parser) +{ + int rc = 0, i = 0; + int num_clk = 0; + int core_clk_index = 0, ctrl_clk_index = 0; + int core_clk_count = 0, ctrl_clk_count = 0; + const char *clk_name; + const char *core_clk = "core"; + const char *ctrl_clk = "ctrl"; + struct device *dev = &parser->pdev->dev; + struct dss_module_power *core_power = &parser->mp[DP_CORE_PM]; + struct dss_module_power *ctrl_power = &parser->mp[DP_CTRL_PM]; + + core_power = &parser->mp[DP_CORE_PM]; + ctrl_power = &parser->mp[DP_CTRL_PM]; + + rc = dp_parser_init_clk_data(parser); + if (rc) { + pr_err("failed to initialize power data\n"); + rc = -EINVAL; + goto exit; + } + + core_clk_count = core_power->num_clk; + ctrl_clk_count = ctrl_power->num_clk; + + num_clk = core_clk_count + ctrl_clk_count; + + for (i = 0; i < num_clk; i++) { + of_property_read_string_index(dev->of_node, "clock-names", + i, &clk_name); + + if (dp_parser_check_prefix(core_clk, clk_name) && + core_clk_index < core_clk_count) { + struct dss_clk *clk = + &core_power->clk_config[core_clk_index]; + strlcpy(clk->clk_name, clk_name, sizeof(clk->clk_name)); + clk->type = DSS_CLK_AHB; + core_clk_index++; + } else if (dp_parser_check_prefix(ctrl_clk, clk_name) && + ctrl_clk_index < ctrl_clk_count) { + struct dss_clk *clk = + &ctrl_power->clk_config[ctrl_clk_index]; + strlcpy(clk->clk_name, clk_name, sizeof(clk->clk_name)); + ctrl_clk_index++; + + if (!strcmp(clk_name, "ctrl_link_clk") || + !strcmp(clk_name, "ctrl_pixel_clk")) + clk->type = DSS_CLK_PCLK; + else + clk->type = DSS_CLK_AHB; + } + } + + pr_debug("clock parsing successful\n"); + +exit: + return rc; +} + +static int dp_parser_parse(struct dp_parser *parser) +{ + int rc = 0; + + if (!parser) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto err; + } + + rc = dp_parser_ctrl_res(parser); + if (rc) + goto err; + + rc = dp_parser_aux(parser); + if (rc) + goto err; + + rc = dp_parser_misc(parser); + if (rc) + goto err; + + rc = dp_parser_clock(parser); + if (rc) + goto err; + + rc = dp_parser_regulator(parser); + if (rc) + goto err; + + rc = dp_parser_gpio(parser); + if (rc) + goto err; + + rc = dp_parser_pinctrl(parser); +err: + return rc; +} + +struct dp_parser *dp_parser_get(struct platform_device *pdev) +{ + struct dp_parser *parser; + + parser = devm_kzalloc(&pdev->dev, sizeof(*parser), GFP_KERNEL); + if (!parser) + return ERR_PTR(-ENOMEM); + + parser->parse = dp_parser_parse; + parser->pdev = pdev; + + return parser; +} + +void dp_parser_put(struct dp_parser *parser) +{ + int i = 0; + struct dss_module_power *power = NULL; + + if (!parser) { + pr_err("invalid parser module\n"); + return; + } + + power = parser->mp; + + for (i = 0; i < DP_MAX_PM; i++) { + struct dss_module_power *mp = &power[i]; + + devm_kfree(&parser->pdev->dev, mp->clk_config); + devm_kfree(&parser->pdev->dev, mp->vreg_config); + devm_kfree(&parser->pdev->dev, mp->gpio_config); + } + + devm_kfree(&parser->pdev->dev, parser); +} diff --git a/drivers/gpu/drm/msm/dp/dp_parser.h b/drivers/gpu/drm/msm/dp/dp_parser.h new file mode 100644 index 000000000000..d11fe8fb3f11 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_parser.h @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _DP_PARSER_H_ +#define _DP_PARSER_H_ + +#include + +#define DP_LABEL "MDSS DP DISPLAY" +#define AUX_CFG_LEN 10 +#define DP_MAX_PIXEL_CLK_KHZ 675000 + +enum dp_pm_type { + DP_CORE_PM, + DP_CTRL_PM, + DP_PHY_PM, + DP_MAX_PM +}; + +static inline const char *dp_parser_pm_name(enum dp_pm_type module) +{ + switch (module) { + case DP_CORE_PM: return "DP_CORE_PM"; + case DP_CTRL_PM: return "DP_CTRL_PM"; + case DP_PHY_PM: return "DP_PHY_PM"; + default: return "???"; + } +} + +/** + * struct dp_display_data - display related device tree data. + * + * @ctrl_node: referece to controller device + * @phy_node: reference to phy device + * @is_active: is the controller currently active + * @name: name of the display + * @display_type: type of the display + */ +struct dp_display_data { + struct device_node *ctrl_node; + struct device_node *phy_node; + bool is_active; + const char *name; + const char *display_type; +}; + +/** + * struct dp_ctrl_resource - controller's IO related data + * + * @ctrl_io: controller's mapped memory address + * @phy_io: phy's mapped memory address + * @ln_tx0_io: USB-DP lane TX0's mapped memory address + * @ln_tx1_io: USB-DP lane TX1's mapped memory address + * @dp_cc_io: DP cc's mapped memory address + * @qfprom_io: qfprom's mapped memory address + * @dp_pll_io: DP PLL mapped memory address + * @usb3_dp_com: USB3 DP PHY combo mapped memory address + * @hdcp_io: hdcp's mapped memory address + */ +struct dp_io { + struct dss_io_data ctrl_io; + struct dss_io_data phy_io; + struct dss_io_data ln_tx0_io; + struct dss_io_data ln_tx1_io; + struct dss_io_data dp_cc_io; + struct dss_io_data qfprom_io; + struct dss_io_data dp_pll_io; + struct dss_io_data usb3_dp_com; + struct dss_io_data hdcp_io; +}; + +/** + * struct dp_pinctrl - DP's pin control + * + * @pin: pin-controller's instance + * @state_active: active state pin control + * @state_hpd_active: hpd active state pin control + * @state_suspend: suspend state pin control + */ +struct dp_pinctrl { + struct pinctrl *pin; + struct pinctrl_state *state_active; + struct pinctrl_state *state_hpd_active; + struct pinctrl_state *state_suspend; +}; + +#define DP_ENUM_STR(x) #x +#define DP_AUX_CFG_MAX_VALUE_CNT 3 +/** + * struct dp_aux_cfg - DP's AUX configuration settings + * + * @cfg_cnt: count of the configurable settings for the AUX register + * @current_index: current index of the AUX config lut + * @offset: register offset of the AUX config register + * @lut: look up table for the AUX config values for this register + */ +struct dp_aux_cfg { + u32 cfg_cnt; + u32 current_index; + u32 offset; + u32 lut[DP_AUX_CFG_MAX_VALUE_CNT]; +}; + +/* PHY AUX config registers */ +enum dp_phy_aux_config_type { + PHY_AUX_CFG0, + PHY_AUX_CFG1, + PHY_AUX_CFG2, + PHY_AUX_CFG3, + PHY_AUX_CFG4, + PHY_AUX_CFG5, + PHY_AUX_CFG6, + PHY_AUX_CFG7, + PHY_AUX_CFG8, + PHY_AUX_CFG9, + PHY_AUX_CFG_MAX, +}; + +static inline char *dp_phy_aux_config_type_to_string(u32 cfg_type) +{ + switch (cfg_type) { + case PHY_AUX_CFG0: + return DP_ENUM_STR(PHY_AUX_CFG0); + case PHY_AUX_CFG1: + return DP_ENUM_STR(PHY_AUX_CFG1); + case PHY_AUX_CFG2: + return DP_ENUM_STR(PHY_AUX_CFG2); + case PHY_AUX_CFG3: + return DP_ENUM_STR(PHY_AUX_CFG3); + case PHY_AUX_CFG4: + return DP_ENUM_STR(PHY_AUX_CFG4); + case PHY_AUX_CFG5: + return DP_ENUM_STR(PHY_AUX_CFG5); + case PHY_AUX_CFG6: + return DP_ENUM_STR(PHY_AUX_CFG6); + case PHY_AUX_CFG7: + return DP_ENUM_STR(PHY_AUX_CFG7); + case PHY_AUX_CFG8: + return DP_ENUM_STR(PHY_AUX_CFG8); + case PHY_AUX_CFG9: + return DP_ENUM_STR(PHY_AUX_CFG9); + default: + return "unknown"; + } +} + +/** + * struct dp_parser - DP parser's data exposed to clients + * + * @pdev: platform data of the client + * @mp: gpio, regulator and clock related data + * @pinctrl: pin-control related data + * @disp_data: controller's display related data + * @parse: function to be called by client to parse device tree. + */ +struct dp_parser { + struct platform_device *pdev; + struct dss_module_power mp[DP_MAX_PM]; + struct dp_pinctrl pinctrl; + struct dp_io io; + struct dp_display_data disp_data; + + u8 l_map[4]; + struct dp_aux_cfg aux_cfg[AUX_CFG_LEN]; + u32 max_pclk_khz; + + int (*parse)(struct dp_parser *parser); +}; + +/** + * dp_parser_get() - get the DP's device tree parser module + * + * @pdev: platform data of the client + * return: pointer to dp_parser structure. + * + * This function provides client capability to parse the + * device tree and populate the data structures. The data + * related to clock, regulators, pin-control and other + * can be parsed using this module. + */ +struct dp_parser *dp_parser_get(struct platform_device *pdev); + +/** + * dp_parser_put() - cleans the dp_parser module + * + * @parser: pointer to the parser's data. + */ +void dp_parser_put(struct dp_parser *parser); +#endif diff --git a/drivers/gpu/drm/msm/dp/dp_power.c b/drivers/gpu/drm/msm/dp/dp_power.c new file mode 100644 index 000000000000..eb787faa2aaf --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_power.c @@ -0,0 +1,623 @@ +/* + * Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include +#include "dp_power.h" + +#define DP_CLIENT_NAME_SIZE 20 + +struct dp_power_private { + struct dp_parser *parser; + struct platform_device *pdev; + struct clk *pixel_clk_rcg; + struct clk *pixel_parent; + + struct dp_power dp_power; + struct sde_power_client *dp_core_client; + struct sde_power_handle *phandle; + + bool core_clks_on; + bool link_clks_on; +}; + +static int dp_power_regulator_init(struct dp_power_private *power) +{ + int rc = 0, i = 0, j = 0; + struct platform_device *pdev; + struct dp_parser *parser; + + parser = power->parser; + pdev = power->pdev; + + for (i = DP_CORE_PM; !rc && (i < DP_MAX_PM); i++) { + rc = msm_dss_config_vreg(&pdev->dev, + parser->mp[i].vreg_config, + parser->mp[i].num_vreg, 1); + if (rc) { + pr_err("failed to init vregs for %s\n", + dp_parser_pm_name(i)); + for (j = i - 1; j >= DP_CORE_PM; j--) { + msm_dss_config_vreg(&pdev->dev, + parser->mp[j].vreg_config, + parser->mp[j].num_vreg, 0); + } + + goto error; + } + } +error: + return rc; +} + +static void dp_power_regulator_deinit(struct dp_power_private *power) +{ + int rc = 0, i = 0; + struct platform_device *pdev; + struct dp_parser *parser; + + parser = power->parser; + pdev = power->pdev; + + for (i = DP_CORE_PM; (i < DP_MAX_PM); i++) { + rc = msm_dss_config_vreg(&pdev->dev, + parser->mp[i].vreg_config, + parser->mp[i].num_vreg, 0); + if (rc) + pr_err("failed to deinit vregs for %s\n", + dp_parser_pm_name(i)); + } +} + +static int dp_power_regulator_ctrl(struct dp_power_private *power, bool enable) +{ + int rc = 0, i = 0, j = 0; + struct dp_parser *parser; + + parser = power->parser; + + for (i = DP_CORE_PM; i < DP_MAX_PM; i++) { + rc = msm_dss_enable_vreg( + parser->mp[i].vreg_config, + parser->mp[i].num_vreg, enable); + if (rc) { + pr_err("failed to '%s' vregs for %s\n", + enable ? "enable" : "disable", + dp_parser_pm_name(i)); + if (enable) { + for (j = i-1; j >= DP_CORE_PM; j--) { + msm_dss_enable_vreg( + parser->mp[j].vreg_config, + parser->mp[j].num_vreg, 0); + } + } + goto error; + } + } +error: + return rc; +} + +static int dp_power_pinctrl_set(struct dp_power_private *power, bool active) +{ + int rc = -EFAULT; + struct pinctrl_state *pin_state; + struct dp_parser *parser; + + parser = power->parser; + + if (IS_ERR_OR_NULL(parser->pinctrl.pin)) + return PTR_ERR(parser->pinctrl.pin); + + pin_state = active ? parser->pinctrl.state_active + : parser->pinctrl.state_suspend; + if (!IS_ERR_OR_NULL(pin_state)) { + rc = pinctrl_select_state(parser->pinctrl.pin, + pin_state); + if (rc) + pr_err("can not set %s pins\n", + active ? "dp_active" + : "dp_sleep"); + } else { + pr_err("invalid '%s' pinstate\n", + active ? "dp_active" + : "dp_sleep"); + } + + return rc; +} + +static int dp_power_clk_init(struct dp_power_private *power, bool enable) +{ + int rc = 0; + struct dss_module_power *core, *ctrl; + struct device *dev; + + core = &power->parser->mp[DP_CORE_PM]; + ctrl = &power->parser->mp[DP_CTRL_PM]; + + dev = &power->pdev->dev; + + if (!core || !ctrl) { + pr_err("invalid power_data\n"); + rc = -EINVAL; + goto exit; + } + + if (enable) { + rc = msm_dss_get_clk(dev, core->clk_config, core->num_clk); + if (rc) { + pr_err("failed to get %s clk. err=%d\n", + dp_parser_pm_name(DP_CORE_PM), rc); + goto exit; + } + + rc = msm_dss_get_clk(dev, ctrl->clk_config, ctrl->num_clk); + if (rc) { + pr_err("failed to get %s clk. err=%d\n", + dp_parser_pm_name(DP_CTRL_PM), rc); + goto ctrl_get_error; + } + + power->pixel_clk_rcg = devm_clk_get(dev, "pixel_clk_rcg"); + if (IS_ERR(power->pixel_clk_rcg)) { + pr_debug("Unable to get DP pixel clk RCG\n"); + power->pixel_clk_rcg = NULL; + } + + power->pixel_parent = devm_clk_get(dev, "pixel_parent"); + if (IS_ERR(power->pixel_parent)) { + pr_debug("Unable to get DP pixel RCG parent\n"); + power->pixel_parent = NULL; + } + } else { + if (power->pixel_parent) + devm_clk_put(dev, power->pixel_parent); + + if (power->pixel_clk_rcg) + devm_clk_put(dev, power->pixel_clk_rcg); + + msm_dss_put_clk(ctrl->clk_config, ctrl->num_clk); + msm_dss_put_clk(core->clk_config, core->num_clk); + } + + return rc; + +ctrl_get_error: + msm_dss_put_clk(core->clk_config, core->num_clk); +exit: + return rc; +} + +static int dp_power_clk_set_rate(struct dp_power_private *power, + enum dp_pm_type module, bool enable) +{ + int rc = 0; + struct dss_module_power *mp; + + if (!power) { + pr_err("invalid power data\n"); + rc = -EINVAL; + goto exit; + } + + mp = &power->parser->mp[module]; + + if (enable) { + rc = msm_dss_clk_set_rate(mp->clk_config, mp->num_clk); + if (rc) { + pr_err("failed to set clks rate.\n"); + goto exit; + } + + rc = msm_dss_enable_clk(mp->clk_config, mp->num_clk, 1); + if (rc) { + pr_err("failed to enable clks\n"); + goto exit; + } + } else { + rc = msm_dss_enable_clk(mp->clk_config, mp->num_clk, 0); + if (rc) { + pr_err("failed to disable clks\n"); + goto exit; + } + } +exit: + return rc; +} + +static int dp_power_clk_enable(struct dp_power *dp_power, + enum dp_pm_type pm_type, bool enable) +{ + int rc = 0; + struct dss_module_power *mp; + struct dp_power_private *power; + + if (!dp_power) { + pr_err("invalid power data\n"); + rc = -EINVAL; + goto error; + } + + power = container_of(dp_power, struct dp_power_private, dp_power); + + mp = &power->parser->mp[pm_type]; + + if ((pm_type != DP_CORE_PM) && (pm_type != DP_CTRL_PM)) { + pr_err("unsupported power module: %s\n", + dp_parser_pm_name(pm_type)); + return -EINVAL; + } + + if (enable) { + if ((pm_type == DP_CORE_PM) + && (power->core_clks_on)) { + pr_debug("core clks already enabled\n"); + return 0; + } + + if ((pm_type == DP_CTRL_PM) + && (power->link_clks_on)) { + pr_debug("links clks already enabled\n"); + return 0; + } + + if ((pm_type == DP_CTRL_PM) && (!power->core_clks_on)) { + pr_debug("Need to enable core clks before link clks\n"); + + rc = dp_power_clk_set_rate(power, pm_type, enable); + if (rc) { + pr_err("failed to enable clks: %s. err=%d\n", + dp_parser_pm_name(DP_CORE_PM), rc); + goto error; + } else { + power->core_clks_on = true; + } + } + } + + rc = dp_power_clk_set_rate(power, pm_type, enable); + if (rc) { + pr_err("failed to '%s' clks for: %s. err=%d\n", + enable ? "enable" : "disable", + dp_parser_pm_name(pm_type), rc); + goto error; + } + + if (pm_type == DP_CORE_PM) + power->core_clks_on = enable; + else + power->link_clks_on = enable; + + pr_debug("%s clocks for %s\n", + enable ? "enable" : "disable", + dp_parser_pm_name(pm_type)); + pr_debug("link_clks:%s core_clks:%s\n", + power->link_clks_on ? "on" : "off", + power->core_clks_on ? "on" : "off"); +error: + return rc; +} + +static int dp_power_request_gpios(struct dp_power_private *power) +{ + int rc = 0, i; + struct device *dev; + struct dss_module_power *mp; + static const char * const gpio_names[] = { + "aux_enable", "aux_sel", "usbplug_cc", + }; + + if (!power) { + pr_err("invalid power data\n"); + return -EINVAL; + } + + dev = &power->pdev->dev; + mp = &power->parser->mp[DP_CORE_PM]; + + for (i = 0; i < ARRAY_SIZE(gpio_names); i++) { + unsigned int gpio = mp->gpio_config[i].gpio; + + if (gpio_is_valid(gpio)) { + rc = devm_gpio_request(dev, gpio, gpio_names[i]); + if (rc) { + pr_err("request %s gpio failed, rc=%d\n", + gpio_names[i], rc); + goto error; + } + } + } + return 0; +error: + for (i = 0; i < ARRAY_SIZE(gpio_names); i++) { + unsigned int gpio = mp->gpio_config[i].gpio; + + if (gpio_is_valid(gpio)) + gpio_free(gpio); + } + return rc; +} + +static bool dp_power_find_gpio(const char *gpio1, const char *gpio2) +{ + return !!strnstr(gpio1, gpio2, strlen(gpio1)); +} + +static void dp_power_set_gpio(struct dp_power_private *power, bool flip) +{ + int i; + struct dss_module_power *mp = &power->parser->mp[DP_CORE_PM]; + struct dss_gpio *config = mp->gpio_config; + + for (i = 0; i < mp->num_gpio; i++) { + if (dp_power_find_gpio(config->gpio_name, "aux-sel")) + config->value = flip; + + if (gpio_is_valid(config->gpio)) { + pr_debug("gpio %s, value %d\n", config->gpio_name, + config->value); + + if (dp_power_find_gpio(config->gpio_name, "aux-en") || + dp_power_find_gpio(config->gpio_name, "aux-sel")) + gpio_direction_output(config->gpio, + config->value); + else + gpio_set_value(config->gpio, config->value); + + } + config++; + } +} + +static int dp_power_config_gpios(struct dp_power_private *power, bool flip, + bool enable) +{ + int rc = 0, i; + struct dss_module_power *mp; + struct dss_gpio *config; + + mp = &power->parser->mp[DP_CORE_PM]; + config = mp->gpio_config; + + if (enable) { + rc = dp_power_request_gpios(power); + if (rc) { + pr_err("gpio request failed\n"); + return rc; + } + + dp_power_set_gpio(power, flip); + } else { + for (i = 0; i < mp->num_gpio; i++) { + gpio_set_value(config[i].gpio, 0); + gpio_free(config[i].gpio); + } + } + + return 0; +} + +static int dp_power_client_init(struct dp_power *dp_power, + struct sde_power_handle *phandle) +{ + int rc = 0; + struct dp_power_private *power; + char dp_client_name[DP_CLIENT_NAME_SIZE]; + + if (!dp_power) { + pr_err("invalid power data\n"); + return -EINVAL; + } + + power = container_of(dp_power, struct dp_power_private, dp_power); + + rc = dp_power_regulator_init(power); + if (rc) { + pr_err("failed to init regulators\n"); + goto error_power; + } + + rc = dp_power_clk_init(power, true); + if (rc) { + pr_err("failed to init clocks\n"); + goto error_clk; + } + + power->phandle = phandle; + snprintf(dp_client_name, DP_CLIENT_NAME_SIZE, "dp_core_client"); + power->dp_core_client = sde_power_client_create(phandle, + dp_client_name); + if (IS_ERR_OR_NULL(power->dp_core_client)) { + pr_err("[%s] client creation failed for DP", dp_client_name); + rc = -EINVAL; + goto error_client; + } + return 0; + +error_client: + dp_power_clk_init(power, false); +error_clk: + dp_power_regulator_deinit(power); +error_power: + return rc; +} + +static void dp_power_client_deinit(struct dp_power *dp_power) +{ + struct dp_power_private *power; + + if (!dp_power) { + pr_err("invalid power data\n"); + return; + } + + power = container_of(dp_power, struct dp_power_private, dp_power); + + sde_power_client_destroy(power->phandle, power->dp_core_client); + dp_power_clk_init(power, false); + dp_power_regulator_deinit(power); +} + +static int dp_power_set_pixel_clk_parent(struct dp_power *dp_power) +{ + int rc = 0; + struct dp_power_private *power; + + if (!dp_power) { + pr_err("invalid power data\n"); + rc = -EINVAL; + goto exit; + } + + power = container_of(dp_power, struct dp_power_private, dp_power); + + if (power->pixel_clk_rcg && power->pixel_parent) + clk_set_parent(power->pixel_clk_rcg, power->pixel_parent); +exit: + return rc; +} + +static int dp_power_init(struct dp_power *dp_power, bool flip) +{ + int rc = 0; + struct dp_power_private *power; + + if (!dp_power) { + pr_err("invalid power data\n"); + rc = -EINVAL; + goto exit; + } + + power = container_of(dp_power, struct dp_power_private, dp_power); + + rc = dp_power_regulator_ctrl(power, true); + if (rc) { + pr_err("failed to enable regulators\n"); + goto exit; + } + + rc = dp_power_pinctrl_set(power, true); + if (rc) { + pr_err("failed to set pinctrl state\n"); + goto err_pinctrl; + } + + rc = dp_power_config_gpios(power, flip, true); + if (rc) { + pr_err("failed to enable gpios\n"); + goto err_gpio; + } + + rc = sde_power_resource_enable(power->phandle, + power->dp_core_client, true); + if (rc) { + pr_err("Power resource enable failed\n"); + goto err_sde_power; + } + + rc = dp_power_clk_enable(dp_power, DP_CORE_PM, true); + if (rc) { + pr_err("failed to enable DP core clocks\n"); + goto err_clk; + } + + return 0; + +err_clk: + sde_power_resource_enable(power->phandle, power->dp_core_client, false); +err_sde_power: + dp_power_config_gpios(power, flip, false); +err_gpio: + dp_power_pinctrl_set(power, false); +err_pinctrl: + dp_power_regulator_ctrl(power, false); +exit: + return rc; +} + +static int dp_power_deinit(struct dp_power *dp_power) +{ + int rc = 0; + struct dp_power_private *power; + + if (!dp_power) { + pr_err("invalid power data\n"); + rc = -EINVAL; + goto exit; + } + + power = container_of(dp_power, struct dp_power_private, dp_power); + + dp_power_clk_enable(dp_power, DP_CORE_PM, false); + rc = sde_power_resource_enable(power->phandle, + power->dp_core_client, false); + if (rc) { + pr_err("Power resource enable failed, rc=%d\n", rc); + goto exit; + } + dp_power_config_gpios(power, false, false); + dp_power_pinctrl_set(power, false); + dp_power_regulator_ctrl(power, false); +exit: + return rc; +} + +struct dp_power *dp_power_get(struct dp_parser *parser) +{ + int rc = 0; + struct dp_power_private *power; + struct dp_power *dp_power; + + if (!parser) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + power = devm_kzalloc(&parser->pdev->dev, sizeof(*power), GFP_KERNEL); + if (!power) { + rc = -ENOMEM; + goto error; + } + + power->parser = parser; + power->pdev = parser->pdev; + + dp_power = &power->dp_power; + + dp_power->init = dp_power_init; + dp_power->deinit = dp_power_deinit; + dp_power->clk_enable = dp_power_clk_enable; + dp_power->set_pixel_clk_parent = dp_power_set_pixel_clk_parent; + dp_power->power_client_init = dp_power_client_init; + dp_power->power_client_deinit = dp_power_client_deinit; + + return dp_power; +error: + return ERR_PTR(rc); +} + +void dp_power_put(struct dp_power *dp_power) +{ + struct dp_power_private *power = NULL; + + if (!dp_power) + return; + + power = container_of(dp_power, struct dp_power_private, dp_power); + + devm_kfree(&power->pdev->dev, power); +} diff --git a/drivers/gpu/drm/msm/dp/dp_power.h b/drivers/gpu/drm/msm/dp/dp_power.h new file mode 100644 index 000000000000..e6e990080e53 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_power.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _DP_POWER_H_ +#define _DP_POWER_H_ + +#include "dp_parser.h" +#include "sde_power_handle.h" + +/** + * sruct dp_power - DisplayPort's power related data + * + * @init: initializes the regulators/core clocks/GPIOs/pinctrl + * @deinit: turns off the regulators/core clocks/GPIOs/pinctrl + * @clk_enable: enable/disable the DP clocks + * @set_pixel_clk_parent: set the parent of DP pixel clock + */ +struct dp_power { + int (*init)(struct dp_power *power, bool flip); + int (*deinit)(struct dp_power *power); + int (*clk_enable)(struct dp_power *power, enum dp_pm_type pm_type, + bool enable); + int (*set_pixel_clk_parent)(struct dp_power *power); + int (*power_client_init)(struct dp_power *power, + struct sde_power_handle *phandle); + void (*power_client_deinit)(struct dp_power *power); +}; + +/** + * dp_power_get() - configure and get the DisplayPort power module data + * + * @parser: instance of parser module + * return: pointer to allocated power module data + * + * This API will configure the DisplayPort's power module and provides + * methods to be called by the client to configure the power related + * modueles. + */ +struct dp_power *dp_power_get(struct dp_parser *parser); + +/** + * dp_power_put() - release the power related resources + * + * @power: pointer to the power module's data + */ +void dp_power_put(struct dp_power *power); +#endif /* _DP_POWER_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h new file mode 100644 index 000000000000..5aaad2468b41 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_reg.h @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _DP_REG_H_ +#define _DP_REG_H_ + +/* DP_TX Registers */ +#define DP_HW_VERSION (0x00000000) +#define DP_SW_RESET (0x00000010) +#define DP_PHY_CTRL (0x00000014) +#define DP_CLK_CTRL (0x00000018) +#define DP_CLK_ACTIVE (0x0000001C) +#define DP_INTR_STATUS (0x00000020) +#define DP_INTR_STATUS2 (0x00000024) +#define DP_INTR_STATUS3 (0x00000028) + +#define DP_DP_HPD_CTRL (0x00000200) +#define DP_DP_HPD_INT_STATUS (0x00000204) +#define DP_DP_HPD_INT_ACK (0x00000208) +#define DP_DP_HPD_INT_MASK (0x0000020C) +#define DP_DP_HPD_REFTIMER (0x00000218) +#define DP_DP_HPD_EVENT_TIME_0 (0x0000021C) +#define DP_DP_HPD_EVENT_TIME_1 (0x00000220) +#define DP_AUX_CTRL (0x00000230) +#define DP_AUX_DATA (0x00000234) +#define DP_AUX_TRANS_CTRL (0x00000238) +#define DP_TIMEOUT_COUNT (0x0000023C) +#define DP_AUX_LIMITS (0x00000240) +#define DP_AUX_STATUS (0x00000244) + +#define DP_DPCD_CP_IRQ (0x201) +#define DP_DPCD_RXSTATUS (0x69493) + +#define DP_INTERRUPT_TRANS_NUM (0x000002A0) + +#define DP_MAINLINK_CTRL (0x00000400) +#define DP_STATE_CTRL (0x00000404) +#define DP_CONFIGURATION_CTRL (0x00000408) +#define DP_SOFTWARE_MVID (0x00000410) +#define DP_SOFTWARE_NVID (0x00000418) +#define DP_TOTAL_HOR_VER (0x0000041C) +#define DP_START_HOR_VER_FROM_SYNC (0x00000420) +#define DP_HSYNC_VSYNC_WIDTH_POLARITY (0x00000424) +#define DP_ACTIVE_HOR_VER (0x00000428) +#define DP_MISC1_MISC0 (0x0000042C) +#define DP_VALID_BOUNDARY (0x00000430) +#define DP_VALID_BOUNDARY_2 (0x00000434) +#define DP_LOGICAL2PHYSCIAL_LANE_MAPPING (0x00000438) + +#define DP_MAINLINK_READY (0x00000440) +#define DP_MAINLINK_LEVELS (0x00000444) +#define DP_TU (0x0000044C) + +#define DP_HBR2_COMPLIANCE_SCRAMBLER_RESET (0x00000454) +#define DP_TEST_80BIT_CUSTOM_PATTERN_REG0 (0x000004C0) +#define DP_TEST_80BIT_CUSTOM_PATTERN_REG1 (0x000004C4) +#define DP_TEST_80BIT_CUSTOM_PATTERN_REG2 (0x000004C8) + +#define MMSS_DP_MISC1_MISC0 (0x0000042C) +#define MMSS_DP_AUDIO_TIMING_GEN (0x00000480) +#define MMSS_DP_AUDIO_TIMING_RBR_32 (0x00000484) +#define MMSS_DP_AUDIO_TIMING_HBR_32 (0x00000488) +#define MMSS_DP_AUDIO_TIMING_RBR_44 (0x0000048C) +#define MMSS_DP_AUDIO_TIMING_HBR_44 (0x00000490) +#define MMSS_DP_AUDIO_TIMING_RBR_48 (0x00000494) +#define MMSS_DP_AUDIO_TIMING_HBR_48 (0x00000498) + +#define MMSS_DP_PSR_CRC_RG (0x00000554) +#define MMSS_DP_PSR_CRC_B (0x00000558) + +#define DP_COMPRESSION_MODE_CTRL (0x00000580) + +#define MMSS_DP_AUDIO_CFG (0x00000600) +#define MMSS_DP_AUDIO_STATUS (0x00000604) +#define MMSS_DP_AUDIO_PKT_CTRL (0x00000608) +#define MMSS_DP_AUDIO_PKT_CTRL2 (0x0000060C) +#define MMSS_DP_AUDIO_ACR_CTRL (0x00000610) +#define MMSS_DP_AUDIO_CTRL_RESET (0x00000614) + +#define MMSS_DP_SDP_CFG (0x00000628) +#define MMSS_DP_SDP_CFG2 (0x0000062C) +#define MMSS_DP_AUDIO_TIMESTAMP_0 (0x00000630) +#define MMSS_DP_AUDIO_TIMESTAMP_1 (0x00000634) + +#define MMSS_DP_AUDIO_STREAM_0 (0x00000640) +#define MMSS_DP_AUDIO_STREAM_1 (0x00000644) + +#define MMSS_DP_EXTENSION_0 (0x00000650) +#define MMSS_DP_EXTENSION_1 (0x00000654) +#define MMSS_DP_EXTENSION_2 (0x00000658) +#define MMSS_DP_EXTENSION_3 (0x0000065C) +#define MMSS_DP_EXTENSION_4 (0x00000660) +#define MMSS_DP_EXTENSION_5 (0x00000664) +#define MMSS_DP_EXTENSION_6 (0x00000668) +#define MMSS_DP_EXTENSION_7 (0x0000066C) +#define MMSS_DP_EXTENSION_8 (0x00000670) +#define MMSS_DP_EXTENSION_9 (0x00000674) +#define MMSS_DP_AUDIO_COPYMANAGEMENT_0 (0x00000678) +#define MMSS_DP_AUDIO_COPYMANAGEMENT_1 (0x0000067C) +#define MMSS_DP_AUDIO_COPYMANAGEMENT_2 (0x00000680) +#define MMSS_DP_AUDIO_COPYMANAGEMENT_3 (0x00000684) +#define MMSS_DP_AUDIO_COPYMANAGEMENT_4 (0x00000688) +#define MMSS_DP_AUDIO_COPYMANAGEMENT_5 (0x0000068C) +#define MMSS_DP_AUDIO_ISRC_0 (0x00000690) +#define MMSS_DP_AUDIO_ISRC_1 (0x00000694) +#define MMSS_DP_AUDIO_ISRC_2 (0x00000698) +#define MMSS_DP_AUDIO_ISRC_3 (0x0000069C) +#define MMSS_DP_AUDIO_ISRC_4 (0x000006A0) +#define MMSS_DP_AUDIO_ISRC_5 (0x000006A4) +#define MMSS_DP_AUDIO_INFOFRAME_0 (0x000006A8) +#define MMSS_DP_AUDIO_INFOFRAME_1 (0x000006AC) +#define MMSS_DP_AUDIO_INFOFRAME_2 (0x000006B0) + +#define MMSS_DP_GENERIC0_0 (0x00000700) +#define MMSS_DP_GENERIC0_1 (0x00000704) +#define MMSS_DP_GENERIC0_2 (0x00000708) +#define MMSS_DP_GENERIC0_3 (0x0000070C) +#define MMSS_DP_GENERIC0_4 (0x00000710) +#define MMSS_DP_GENERIC0_5 (0x00000714) +#define MMSS_DP_GENERIC0_6 (0x00000718) +#define MMSS_DP_GENERIC0_7 (0x0000071C) +#define MMSS_DP_GENERIC0_8 (0x00000720) +#define MMSS_DP_GENERIC0_9 (0x00000724) +#define MMSS_DP_GENERIC1_0 (0x00000728) +#define MMSS_DP_GENERIC1_1 (0x0000072C) +#define MMSS_DP_GENERIC1_2 (0x00000730) +#define MMSS_DP_GENERIC1_3 (0x00000734) +#define MMSS_DP_GENERIC1_4 (0x00000738) +#define MMSS_DP_GENERIC1_5 (0x0000073C) +#define MMSS_DP_GENERIC1_6 (0x00000740) +#define MMSS_DP_GENERIC1_7 (0x00000744) +#define MMSS_DP_GENERIC1_8 (0x00000748) +#define MMSS_DP_GENERIC1_9 (0x0000074C) + +#define MMSS_DP_VSCEXT_0 (0x000006D0) +#define MMSS_DP_VSCEXT_1 (0x000006D4) +#define MMSS_DP_VSCEXT_2 (0x000006D8) +#define MMSS_DP_VSCEXT_3 (0x000006DC) +#define MMSS_DP_VSCEXT_4 (0x000006E0) +#define MMSS_DP_VSCEXT_5 (0x000006E4) +#define MMSS_DP_VSCEXT_6 (0x000006E8) +#define MMSS_DP_VSCEXT_7 (0x000006EC) +#define MMSS_DP_VSCEXT_8 (0x000006F0) +#define MMSS_DP_VSCEXT_9 (0x000006F4) + +#define MMSS_DP_TIMING_ENGINE_EN (0x00000A10) +#define MMSS_DP_ASYNC_FIFO_CONFIG (0x00000A88) + +/*DP PHY Register offsets */ +#define DP_PHY_REVISION_ID0 (0x00000000) +#define DP_PHY_REVISION_ID1 (0x00000004) +#define DP_PHY_REVISION_ID2 (0x00000008) +#define DP_PHY_REVISION_ID3 (0x0000000C) + +#define DP_PHY_CFG (0x00000010) +#define DP_PHY_PD_CTL (0x00000018) +#define DP_PHY_MODE (0x0000001C) + +#define DP_PHY_AUX_CFG0 (0x00000020) +#define DP_PHY_AUX_CFG1 (0x00000024) +#define DP_PHY_AUX_CFG2 (0x00000028) +#define DP_PHY_AUX_CFG3 (0x0000002C) +#define DP_PHY_AUX_CFG4 (0x00000030) +#define DP_PHY_AUX_CFG5 (0x00000034) +#define DP_PHY_AUX_CFG6 (0x00000038) +#define DP_PHY_AUX_CFG7 (0x0000003C) +#define DP_PHY_AUX_CFG8 (0x00000040) +#define DP_PHY_AUX_CFG9 (0x00000044) +#define DP_PHY_AUX_INTERRUPT_MASK (0x00000048) +#define DP_PHY_AUX_INTERRUPT_CLEAR (0x0000004C) + +#define DP_PHY_SPARE0 (0x00AC) + +#define TXn_TX_EMP_POST1_LVL (0x000C) +#define TXn_TX_DRV_LVL (0x001C) + +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN (0x004) + +/* DP MMSS_CC registers */ +#define MMSS_DP_LINK_CMD_RCGR (0x0138) +#define MMSS_DP_LINK_CFG_RCGR (0x013C) +#define MMSS_DP_PIXEL_M (0x0174) +#define MMSS_DP_PIXEL_N (0x0178) + +/* DP HDCP 1.3 registers */ +#define DP_HDCP_CTRL (0x0A0) +#define DP_HDCP_STATUS (0x0A4) +#define DP_HDCP_SW_UPPER_AKSV (0x298) +#define DP_HDCP_SW_LOWER_AKSV (0x29C) +#define DP_HDCP_ENTROPY_CTRL0 (0x750) +#define DP_HDCP_ENTROPY_CTRL1 (0x75C) +#define DP_HDCP_SHA_STATUS (0x0C8) +#define DP_HDCP_RCVPORT_DATA2_0 (0x0B0) +#define DP_HDCP_RCVPORT_DATA3 (0x2A4) +#define DP_HDCP_RCVPORT_DATA4 (0x2A8) +#define DP_HDCP_RCVPORT_DATA5 (0x0C0) +#define DP_HDCP_RCVPORT_DATA6 (0x0C4) + +#define HDCP_SEC_DP_TZ_HV_HLOS_HDCP_SHA_CTRL (0x024) +#define HDCP_SEC_DP_TZ_HV_HLOS_HDCP_SHA_DATA (0x028) +#define HDCP_SEC_DP_TZ_HV_HLOS_HDCP_RCVPORT_DATA0 (0x004) +#define HDCP_SEC_DP_TZ_HV_HLOS_HDCP_RCVPORT_DATA1 (0x008) +#define HDCP_SEC_DP_TZ_HV_HLOS_HDCP_RCVPORT_DATA7 (0x00C) +#define HDCP_SEC_DP_TZ_HV_HLOS_HDCP_RCVPORT_DATA8 (0x010) +#define HDCP_SEC_DP_TZ_HV_HLOS_HDCP_RCVPORT_DATA9 (0x014) +#define HDCP_SEC_DP_TZ_HV_HLOS_HDCP_RCVPORT_DATA10 (0x018) +#define HDCP_SEC_DP_TZ_HV_HLOS_HDCP_RCVPORT_DATA11 (0x01C) +#define HDCP_SEC_DP_TZ_HV_HLOS_HDCP_RCVPORT_DATA12 (0x020) + +/* USB3 DP COM registers */ +#define USB3_DP_COM_RESET_OVRD_CTRL (0x1C) +#define USB3_DP_COM_PHY_MODE_CTRL (0x00) +#define USB3_DP_COM_SW_RESET (0x04) +#define USB3_DP_COM_TYPEC_CTRL (0x10) +#define USB3_DP_COM_SWI_CTRL (0x0c) +#define USB3_DP_COM_POWER_DOWN_CTRL (0x08) + + + +#endif /* _DP_REG_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_usbpd.c b/drivers/gpu/drm/msm/dp/dp_usbpd.c new file mode 100644 index 000000000000..98781abba444 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_usbpd.c @@ -0,0 +1,491 @@ +/* + * Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include +#include + +#include "dp_usbpd.h" + +/* DP specific VDM commands */ +#define DP_USBPD_VDM_STATUS 0x10 +#define DP_USBPD_VDM_CONFIGURE 0x11 + +/* USBPD-TypeC specific Macros */ +#define VDM_VERSION 0x0 +#define USB_C_DP_SID 0xFF01 + +enum dp_usbpd_pin_assignment { + DP_USBPD_PIN_A, + DP_USBPD_PIN_B, + DP_USBPD_PIN_C, + DP_USBPD_PIN_D, + DP_USBPD_PIN_E, + DP_USBPD_PIN_F, + DP_USBPD_PIN_MAX, +}; + +enum dp_usbpd_events { + DP_USBPD_EVT_DISCOVER, + DP_USBPD_EVT_ENTER, + DP_USBPD_EVT_STATUS, + DP_USBPD_EVT_CONFIGURE, + DP_USBPD_EVT_CC_PIN_POLARITY, + DP_USBPD_EVT_EXIT, + DP_USBPD_EVT_ATTENTION, +}; + +enum dp_usbpd_alt_mode { + DP_USBPD_ALT_MODE_NONE = 0, + DP_USBPD_ALT_MODE_INIT = BIT(0), + DP_USBPD_ALT_MODE_DISCOVER = BIT(1), + DP_USBPD_ALT_MODE_ENTER = BIT(2), + DP_USBPD_ALT_MODE_STATUS = BIT(3), + DP_USBPD_ALT_MODE_CONFIGURE = BIT(4), +}; + +struct dp_usbpd_capabilities { + enum dp_usbpd_port port; + bool receptacle_state; + u8 ulink_pin_config; + u8 dlink_pin_config; +}; + +struct dp_usbpd_private { + u32 vdo; + struct device *dev; + struct usbpd *pd; + struct usbpd_svid_handler svid_handler; + struct dp_usbpd_cb *dp_cb; + struct dp_usbpd_capabilities cap; + struct dp_usbpd dp_usbpd; + enum dp_usbpd_alt_mode alt_mode; + u32 dp_usbpd_config; +}; + +static const char *dp_usbpd_pin_name(u8 pin) +{ + switch (pin) { + case DP_USBPD_PIN_A: return "DP_USBPD_PIN_ASSIGNMENT_A"; + case DP_USBPD_PIN_B: return "DP_USBPD_PIN_ASSIGNMENT_B"; + case DP_USBPD_PIN_C: return "DP_USBPD_PIN_ASSIGNMENT_C"; + case DP_USBPD_PIN_D: return "DP_USBPD_PIN_ASSIGNMENT_D"; + case DP_USBPD_PIN_E: return "DP_USBPD_PIN_ASSIGNMENT_E"; + case DP_USBPD_PIN_F: return "DP_USBPD_PIN_ASSIGNMENT_F"; + default: return "UNKNOWN"; + } +} + +static const char *dp_usbpd_port_name(enum dp_usbpd_port port) +{ + switch (port) { + case DP_USBPD_PORT_NONE: return "DP_USBPD_PORT_NONE"; + case DP_USBPD_PORT_UFP_D: return "DP_USBPD_PORT_UFP_D"; + case DP_USBPD_PORT_DFP_D: return "DP_USBPD_PORT_DFP_D"; + case DP_USBPD_PORT_D_UFP_D: return "DP_USBPD_PORT_D_UFP_D"; + default: return "DP_USBPD_PORT_NONE"; + } +} + +static const char *dp_usbpd_cmd_name(u8 cmd) +{ + switch (cmd) { + case USBPD_SVDM_DISCOVER_MODES: return "USBPD_SVDM_DISCOVER_MODES"; + case USBPD_SVDM_ENTER_MODE: return "USBPD_SVDM_ENTER_MODE"; + case USBPD_SVDM_ATTENTION: return "USBPD_SVDM_ATTENTION"; + case DP_USBPD_VDM_STATUS: return "DP_USBPD_VDM_STATUS"; + case DP_USBPD_VDM_CONFIGURE: return "DP_USBPD_VDM_CONFIGURE"; + default: return "DP_USBPD_VDM_ERROR"; + } +} + +static void dp_usbpd_init_port(enum dp_usbpd_port *port, u32 in_port) +{ + switch (in_port) { + case 0: + *port = DP_USBPD_PORT_NONE; + break; + case 1: + *port = DP_USBPD_PORT_UFP_D; + break; + case 2: + *port = DP_USBPD_PORT_DFP_D; + break; + case 3: + *port = DP_USBPD_PORT_D_UFP_D; + break; + default: + *port = DP_USBPD_PORT_NONE; + } + pr_debug("port:%s\n", dp_usbpd_port_name(*port)); +} + +static void dp_usbpd_get_capabilities(struct dp_usbpd_private *pd) +{ + struct dp_usbpd_capabilities *cap = &pd->cap; + u32 buf = pd->vdo; + int port = buf & 0x3; + + cap->receptacle_state = (buf & BIT(6)) ? true : false; + cap->dlink_pin_config = (buf >> 8) & 0xff; + cap->ulink_pin_config = (buf >> 16) & 0xff; + + dp_usbpd_init_port(&cap->port, port); +} + +static void dp_usbpd_get_status(struct dp_usbpd_private *pd) +{ + struct dp_usbpd *status = &pd->dp_usbpd; + u32 buf = pd->vdo; + int port = buf & 0x3; + + status->low_pow_st = (buf & BIT(2)) ? true : false; + status->adaptor_dp_en = (buf & BIT(3)) ? true : false; + status->multi_func = (buf & BIT(4)) ? true : false; + status->usb_config_req = (buf & BIT(5)) ? true : false; + status->exit_dp_mode = (buf & BIT(6)) ? true : false; + status->hpd_high = (buf & BIT(7)) ? true : false; + status->hpd_irq = (buf & BIT(8)) ? true : false; + + pr_debug("low_pow_st = %d, adaptor_dp_en = %d, multi_func = %d\n", + status->low_pow_st, status->adaptor_dp_en, + status->multi_func); + pr_debug("usb_config_req = %d, exit_dp_mode = %d, hpd_high =%d\n", + status->usb_config_req, + status->exit_dp_mode, status->hpd_high); + pr_debug("hpd_irq = %d\n", status->hpd_irq); + + dp_usbpd_init_port(&status->port, port); +} + +static u32 dp_usbpd_gen_config_pkt(struct dp_usbpd_private *pd) +{ + u8 pin_cfg, pin; + u32 config = 0; + const u32 ufp_d_config = 0x2, dp_ver = 0x1; + + if (pd->cap.receptacle_state) + pin_cfg = pd->cap.ulink_pin_config; + else + pin_cfg = pd->cap.dlink_pin_config; + + for (pin = DP_USBPD_PIN_A; pin < DP_USBPD_PIN_MAX; pin++) { + if (pin_cfg & BIT(pin)) { + if (pd->dp_usbpd.multi_func) { + if (pin == DP_USBPD_PIN_D) + break; + } else { + break; + } + } + } + + if (pin == DP_USBPD_PIN_MAX) + pin = DP_USBPD_PIN_C; + + pr_debug("pin assignment: %s\n", dp_usbpd_pin_name(pin)); + + config |= BIT(pin) << 8; + + config |= (dp_ver << 2); + config |= ufp_d_config; + + pr_debug("config = 0x%x\n", config); + return config; +} + +static void dp_usbpd_send_event(struct dp_usbpd_private *pd, + enum dp_usbpd_events event) +{ + u32 config; + + switch (event) { + case DP_USBPD_EVT_DISCOVER: + usbpd_send_svdm(pd->pd, USB_C_DP_SID, + USBPD_SVDM_DISCOVER_MODES, + SVDM_CMD_TYPE_INITIATOR, 0x0, 0x0, 0x0); + break; + case DP_USBPD_EVT_ENTER: + usbpd_send_svdm(pd->pd, USB_C_DP_SID, + USBPD_SVDM_ENTER_MODE, + SVDM_CMD_TYPE_INITIATOR, 0x1, 0x0, 0x0); + break; + case DP_USBPD_EVT_EXIT: + usbpd_send_svdm(pd->pd, USB_C_DP_SID, + USBPD_SVDM_EXIT_MODE, + SVDM_CMD_TYPE_INITIATOR, 0x1, 0x0, 0x0); + break; + case DP_USBPD_EVT_STATUS: + config = 0x1; /* DFP_D connected */ + usbpd_send_svdm(pd->pd, USB_C_DP_SID, DP_USBPD_VDM_STATUS, + SVDM_CMD_TYPE_INITIATOR, 0x1, &config, 0x1); + break; + case DP_USBPD_EVT_CONFIGURE: + config = dp_usbpd_gen_config_pkt(pd); + usbpd_send_svdm(pd->pd, USB_C_DP_SID, DP_USBPD_VDM_CONFIGURE, + SVDM_CMD_TYPE_INITIATOR, 0x1, &config, 0x1); + break; + default: + pr_err("unknown event:%d\n", event); + } +} + +static void dp_usbpd_connect_cb(struct usbpd_svid_handler *hdlr) +{ + struct dp_usbpd_private *pd; + + pd = container_of(hdlr, struct dp_usbpd_private, svid_handler); + if (!pd) { + pr_err("get_usbpd phandle failed\n"); + return; + } + + pr_debug("\n"); + dp_usbpd_send_event(pd, DP_USBPD_EVT_DISCOVER); +} + +static void dp_usbpd_disconnect_cb(struct usbpd_svid_handler *hdlr) +{ + struct dp_usbpd_private *pd; + + pd = container_of(hdlr, struct dp_usbpd_private, svid_handler); + if (!pd) { + pr_err("get_usbpd phandle failed\n"); + return; + } + + pd->alt_mode = DP_USBPD_ALT_MODE_NONE; + pd->dp_usbpd.alt_mode_cfg_done = false; + pr_debug("\n"); + + if (pd->dp_cb && pd->dp_cb->disconnect) + pd->dp_cb->disconnect(pd->dev); +} + +static int dp_usbpd_validate_callback(u8 cmd, + enum usbpd_svdm_cmd_type cmd_type, int num_vdos) +{ + int ret = 0; + + if (cmd_type == SVDM_CMD_TYPE_RESP_NAK) { + pr_err("error: NACK\n"); + ret = -EINVAL; + goto end; + } + + if (cmd_type == SVDM_CMD_TYPE_RESP_BUSY) { + pr_err("error: BUSY\n"); + ret = -EBUSY; + goto end; + } + + if (cmd == USBPD_SVDM_ATTENTION) { + if (cmd_type != SVDM_CMD_TYPE_INITIATOR) { + pr_err("error: invalid cmd type for attention\n"); + ret = -EINVAL; + goto end; + } + + if (!num_vdos) { + pr_err("error: no vdo provided\n"); + ret = -EINVAL; + goto end; + } + } else { + if (cmd_type != SVDM_CMD_TYPE_RESP_ACK) { + pr_err("error: invalid cmd type\n"); + ret = -EINVAL; + } + } +end: + return ret; +} + +static void dp_usbpd_response_cb(struct usbpd_svid_handler *hdlr, u8 cmd, + enum usbpd_svdm_cmd_type cmd_type, + const u32 *vdos, int num_vdos) +{ + struct dp_usbpd_private *pd; + + pd = container_of(hdlr, struct dp_usbpd_private, svid_handler); + + pr_debug("callback -> cmd: %s, *vdos = 0x%x, num_vdos = %d\n", + dp_usbpd_cmd_name(cmd), *vdos, num_vdos); + + if (dp_usbpd_validate_callback(cmd, cmd_type, num_vdos)) { + pr_debug("invalid callback received\n"); + return; + } + + switch (cmd) { + case USBPD_SVDM_DISCOVER_MODES: + pd->vdo = *vdos; + dp_usbpd_get_capabilities(pd); + + pd->alt_mode |= DP_USBPD_ALT_MODE_DISCOVER; + + if (pd->cap.port & BIT(0)) + dp_usbpd_send_event(pd, DP_USBPD_EVT_ENTER); + break; + case USBPD_SVDM_ENTER_MODE: + pd->alt_mode |= DP_USBPD_ALT_MODE_ENTER; + + dp_usbpd_send_event(pd, DP_USBPD_EVT_STATUS); + break; + case USBPD_SVDM_ATTENTION: + if (pd->dp_usbpd.forced_disconnect) + break; + + pd->vdo = *vdos; + dp_usbpd_get_status(pd); + + if (pd->dp_cb && pd->dp_cb->attention) + pd->dp_cb->attention(pd->dev); + + if (!pd->dp_usbpd.alt_mode_cfg_done) + dp_usbpd_send_event(pd, DP_USBPD_EVT_CONFIGURE); + break; + case DP_USBPD_VDM_STATUS: + pd->vdo = *vdos; + dp_usbpd_get_status(pd); + + if (!(pd->alt_mode & DP_USBPD_ALT_MODE_CONFIGURE)) { + pd->alt_mode |= DP_USBPD_ALT_MODE_STATUS; + + if (pd->dp_usbpd.port & BIT(1)) + dp_usbpd_send_event(pd, DP_USBPD_EVT_CONFIGURE); + } + break; + case DP_USBPD_VDM_CONFIGURE: + pd->alt_mode |= DP_USBPD_ALT_MODE_CONFIGURE; + pd->dp_usbpd.alt_mode_cfg_done = true; + dp_usbpd_get_status(pd); + + pd->dp_usbpd.orientation = usbpd_get_plug_orientation(pd->pd); + + /* + * By default, USB reserves two lanes for Super Speed. + * Which means DP has remaining two lanes to operate on. + * If multi-function is not supported, request USB to + * release the Super Speed lanes so that DP can use + * all four lanes in case DPCD indicates support for + * four lanes. + */ + if (!pd->dp_usbpd.multi_func) + pd->svid_handler.request_usb_ss_lane(pd->pd, + &pd->svid_handler); + + if (pd->dp_cb && pd->dp_cb->configure) + pd->dp_cb->configure(pd->dev); + break; + default: + pr_err("unknown cmd: %d\n", cmd); + break; + } +} + +static int dp_usbpd_connect(struct dp_usbpd *dp_usbpd, bool hpd) +{ + int rc = 0; + struct dp_usbpd_private *pd; + + if (!dp_usbpd) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + pd = container_of(dp_usbpd, struct dp_usbpd_private, dp_usbpd); + + dp_usbpd->hpd_high = hpd; + dp_usbpd->forced_disconnect = !hpd; + + if (hpd) + pd->dp_cb->configure(pd->dev); + else + pd->dp_cb->disconnect(pd->dev); + +error: + return rc; +} + +struct dp_usbpd *dp_usbpd_get(struct device *dev, struct dp_usbpd_cb *cb) +{ + int rc = 0; + const char *pd_phandle = "qcom,dp-usbpd-detection"; + struct usbpd *pd = NULL; + struct dp_usbpd_private *usbpd; + struct dp_usbpd *dp_usbpd; + struct usbpd_svid_handler svid_handler = { + .svid = USB_C_DP_SID, + .vdm_received = NULL, + .connect = &dp_usbpd_connect_cb, + .svdm_received = &dp_usbpd_response_cb, + .disconnect = &dp_usbpd_disconnect_cb, + }; + + if (!cb) { + pr_err("invalid cb data\n"); + rc = -EINVAL; + goto error; + } + + pd = devm_usbpd_get_by_phandle(dev, pd_phandle); + if (IS_ERR(pd)) { + pr_err("usbpd phandle failed (%ld)\n", PTR_ERR(pd)); + rc = PTR_ERR(pd); + goto error; + } + + usbpd = devm_kzalloc(dev, sizeof(*usbpd), GFP_KERNEL); + if (!usbpd) { + rc = -ENOMEM; + goto error; + } + + usbpd->dev = dev; + usbpd->pd = pd; + usbpd->svid_handler = svid_handler; + usbpd->dp_cb = cb; + + rc = usbpd_register_svid(pd, &usbpd->svid_handler); + if (rc) { + pr_err("pd registration failed\n"); + rc = -ENODEV; + devm_kfree(dev, usbpd); + goto error; + } + + dp_usbpd = &usbpd->dp_usbpd; + dp_usbpd->connect = dp_usbpd_connect; + + return dp_usbpd; +error: + return ERR_PTR(rc); +} + +void dp_usbpd_put(struct dp_usbpd *dp_usbpd) +{ + struct dp_usbpd_private *usbpd; + + if (!dp_usbpd) + return; + + usbpd = container_of(dp_usbpd, struct dp_usbpd_private, dp_usbpd); + + usbpd_unregister_svid(usbpd->pd, &usbpd->svid_handler); + + devm_kfree(usbpd->dev, usbpd); +} diff --git a/drivers/gpu/drm/msm/dp/dp_usbpd.h b/drivers/gpu/drm/msm/dp/dp_usbpd.h new file mode 100644 index 000000000000..5b392f5831fa --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_usbpd.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _DP_USBPD_H_ +#define _DP_USBPD_H_ + +#include + +#include +#include + +/** + * enum dp_usbpd_port - usb/dp port type + * @DP_USBPD_PORT_NONE: port not configured + * @DP_USBPD_PORT_UFP_D: Upstream Facing Port - DisplayPort + * @DP_USBPD_PORT_DFP_D: Downstream Facing Port - DisplayPort + * @DP_USBPD_PORT_D_UFP_D: Both UFP & DFP - DisplayPort + */ + +enum dp_usbpd_port { + DP_USBPD_PORT_NONE, + DP_USBPD_PORT_UFP_D, + DP_USBPD_PORT_DFP_D, + DP_USBPD_PORT_D_UFP_D, +}; + +/** + * struct dp_usbpd - DisplayPort status + * + * @port: port configured + * orientation: plug orientation configuration + * @low_pow_st: low power state + * @adaptor_dp_en: adaptor functionality enabled + * @multi_func: multi-function preferred + * @usb_config_req: request to switch to usb + * @exit_dp_mode: request exit from displayport mode + * @hpd_high: Hot Plug Detect signal is high. + * @hpd_irq: Change in the status since last message + * @alt_mode_cfg_done: bool to specify alt mode status + * @debug_en: bool to specify debug mode + * @connect: simulate disconnect or connect for debug mode + */ +struct dp_usbpd { + enum dp_usbpd_port port; + enum plug_orientation orientation; + bool low_pow_st; + bool adaptor_dp_en; + bool multi_func; + bool usb_config_req; + bool exit_dp_mode; + bool hpd_high; + bool hpd_irq; + bool alt_mode_cfg_done; + bool debug_en; + bool forced_disconnect; + + int (*connect)(struct dp_usbpd *dp_usbpd, bool hpd); +}; + +/** + * struct dp_usbpd_cb - callback functions provided by the client + * + * @configure: called by usbpd module when PD communication has + * been completed and the usb peripheral has been configured on + * dp mode. + * @disconnect: notify the cable disconnect issued by usb. + * @attention: notify any attention message issued by usb. + */ +struct dp_usbpd_cb { + int (*configure)(struct device *dev); + int (*disconnect)(struct device *dev); + int (*attention)(struct device *dev); +}; + +/** + * dp_usbpd_get() - setup usbpd module + * + * @dev: device instance of the caller + * @cb: struct containing callback function pointers. + * + * This function allows the client to initialize the usbpd + * module. The module will communicate with usb driver and + * handles the power delivery (PD) communication with the + * sink/usb device. This module will notify the client using + * the callback functions about the connection and status. + */ +struct dp_usbpd *dp_usbpd_get(struct device *dev, struct dp_usbpd_cb *cb); + +void dp_usbpd_put(struct dp_usbpd *pd); +#endif /* _DP_USBPD_H_ */ diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_catalog.c b/drivers/gpu/drm/msm/dsi-staging/dsi_catalog.c new file mode 100644 index 000000000000..38a6e474aae0 --- /dev/null +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_catalog.c @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "msm-dsi-catalog:[%s] " fmt, __func__ +#include + +#include "dsi_catalog.h" + +/** + * dsi_catalog_cmn_init() - catalog init for dsi controller v1.4 + */ +static void dsi_catalog_cmn_init(struct dsi_ctrl_hw *ctrl, + enum dsi_ctrl_version version) +{ + /* common functions */ + ctrl->ops.host_setup = dsi_ctrl_hw_cmn_host_setup; + ctrl->ops.video_engine_en = dsi_ctrl_hw_cmn_video_engine_en; + ctrl->ops.video_engine_setup = dsi_ctrl_hw_cmn_video_engine_setup; + ctrl->ops.set_video_timing = dsi_ctrl_hw_cmn_set_video_timing; + ctrl->ops.set_timing_db = dsi_ctrl_hw_cmn_set_timing_db; + ctrl->ops.cmd_engine_setup = dsi_ctrl_hw_cmn_cmd_engine_setup; + ctrl->ops.setup_cmd_stream = dsi_ctrl_hw_cmn_setup_cmd_stream; + ctrl->ops.ctrl_en = dsi_ctrl_hw_cmn_ctrl_en; + ctrl->ops.cmd_engine_en = dsi_ctrl_hw_cmn_cmd_engine_en; + ctrl->ops.phy_sw_reset = dsi_ctrl_hw_cmn_phy_sw_reset; + ctrl->ops.soft_reset = dsi_ctrl_hw_cmn_soft_reset; + ctrl->ops.kickoff_command = dsi_ctrl_hw_cmn_kickoff_command; + ctrl->ops.kickoff_fifo_command = dsi_ctrl_hw_cmn_kickoff_fifo_command; + ctrl->ops.reset_cmd_fifo = dsi_ctrl_hw_cmn_reset_cmd_fifo; + ctrl->ops.trigger_command_dma = dsi_ctrl_hw_cmn_trigger_command_dma; + ctrl->ops.get_interrupt_status = dsi_ctrl_hw_cmn_get_interrupt_status; + ctrl->ops.get_error_status = dsi_ctrl_hw_cmn_get_error_status; + ctrl->ops.clear_error_status = dsi_ctrl_hw_cmn_clear_error_status; + ctrl->ops.clear_interrupt_status = + dsi_ctrl_hw_cmn_clear_interrupt_status; + ctrl->ops.enable_status_interrupts = + dsi_ctrl_hw_cmn_enable_status_interrupts; + ctrl->ops.enable_error_interrupts = + dsi_ctrl_hw_cmn_enable_error_interrupts; + ctrl->ops.video_test_pattern_setup = + dsi_ctrl_hw_cmn_video_test_pattern_setup; + ctrl->ops.cmd_test_pattern_setup = + dsi_ctrl_hw_cmn_cmd_test_pattern_setup; + ctrl->ops.test_pattern_enable = dsi_ctrl_hw_cmn_test_pattern_enable; + ctrl->ops.trigger_cmd_test_pattern = + dsi_ctrl_hw_cmn_trigger_cmd_test_pattern; + ctrl->ops.clear_phy0_ln_err = dsi_ctrl_hw_dln0_phy_err; + ctrl->ops.phy_reset_config = dsi_ctrl_hw_cmn_phy_reset_config; + ctrl->ops.setup_misr = dsi_ctrl_hw_cmn_setup_misr; + ctrl->ops.collect_misr = dsi_ctrl_hw_cmn_collect_misr; + ctrl->ops.debug_bus = dsi_ctrl_hw_cmn_debug_bus; + + switch (version) { + case DSI_CTRL_VERSION_1_4: + ctrl->ops.setup_lane_map = dsi_ctrl_hw_14_setup_lane_map; + ctrl->ops.ulps_ops.ulps_request = dsi_ctrl_hw_14_ulps_request; + ctrl->ops.ulps_ops.ulps_exit = dsi_ctrl_hw_14_ulps_exit; + ctrl->ops.wait_for_lane_idle = + dsi_ctrl_hw_14_wait_for_lane_idle; + ctrl->ops.ulps_ops.get_lanes_in_ulps = + dsi_ctrl_hw_14_get_lanes_in_ulps; + ctrl->ops.clamp_enable = dsi_ctrl_hw_14_clamp_enable; + ctrl->ops.clamp_disable = dsi_ctrl_hw_14_clamp_disable; + ctrl->ops.reg_dump_to_buffer = + dsi_ctrl_hw_14_reg_dump_to_buffer; + break; + case DSI_CTRL_VERSION_2_0: + ctrl->ops.setup_lane_map = dsi_ctrl_hw_20_setup_lane_map; + ctrl->ops.wait_for_lane_idle = + dsi_ctrl_hw_20_wait_for_lane_idle; + ctrl->ops.reg_dump_to_buffer = + dsi_ctrl_hw_20_reg_dump_to_buffer; + ctrl->ops.ulps_ops.ulps_request = NULL; + ctrl->ops.ulps_ops.ulps_exit = NULL; + ctrl->ops.ulps_ops.get_lanes_in_ulps = NULL; + ctrl->ops.clamp_enable = NULL; + ctrl->ops.clamp_disable = NULL; + break; + case DSI_CTRL_VERSION_2_2: + ctrl->ops.phy_reset_config = dsi_ctrl_hw_22_phy_reset_config; + ctrl->ops.setup_lane_map = dsi_ctrl_hw_20_setup_lane_map; + ctrl->ops.wait_for_lane_idle = + dsi_ctrl_hw_20_wait_for_lane_idle; + ctrl->ops.reg_dump_to_buffer = + dsi_ctrl_hw_20_reg_dump_to_buffer; + ctrl->ops.ulps_ops.ulps_request = NULL; + ctrl->ops.ulps_ops.ulps_exit = NULL; + ctrl->ops.ulps_ops.get_lanes_in_ulps = NULL; + ctrl->ops.clamp_enable = NULL; + ctrl->ops.clamp_disable = NULL; + break; + default: + break; + } +} + +/** + * dsi_catalog_ctrl_setup() - return catalog info for dsi controller + * @ctrl: Pointer to DSI controller hw object. + * @version: DSI controller version. + * @index: DSI controller instance ID. + * @phy_isolation_enabled: DSI controller works isolated from phy. + * + * This function setups the catalog information in the dsi_ctrl_hw object. + * + * return: error code for failure and 0 for success. + */ +int dsi_catalog_ctrl_setup(struct dsi_ctrl_hw *ctrl, + enum dsi_ctrl_version version, u32 index, + bool phy_isolation_enabled) +{ + int rc = 0; + + if (version == DSI_CTRL_VERSION_UNKNOWN || + version >= DSI_CTRL_VERSION_MAX) { + pr_err("Unsupported version: %d\n", version); + return -ENOTSUPP; + } + + ctrl->index = index; + set_bit(DSI_CTRL_VIDEO_TPG, ctrl->feature_map); + set_bit(DSI_CTRL_CMD_TPG, ctrl->feature_map); + set_bit(DSI_CTRL_VARIABLE_REFRESH_RATE, ctrl->feature_map); + set_bit(DSI_CTRL_DYNAMIC_REFRESH, ctrl->feature_map); + set_bit(DSI_CTRL_DESKEW_CALIB, ctrl->feature_map); + set_bit(DSI_CTRL_DPHY, ctrl->feature_map); + + switch (version) { + case DSI_CTRL_VERSION_1_4: + dsi_catalog_cmn_init(ctrl, version); + break; + case DSI_CTRL_VERSION_2_0: + case DSI_CTRL_VERSION_2_2: + ctrl->phy_isolation_enabled = phy_isolation_enabled; + dsi_catalog_cmn_init(ctrl, version); + break; + default: + return -ENOTSUPP; + } + + return rc; +} + +/** + * dsi_catalog_phy_2_0_init() - catalog init for DSI PHY 14nm + */ +static void dsi_catalog_phy_2_0_init(struct dsi_phy_hw *phy) +{ + phy->ops.regulator_enable = dsi_phy_hw_v2_0_regulator_enable; + phy->ops.regulator_disable = dsi_phy_hw_v2_0_regulator_disable; + phy->ops.enable = dsi_phy_hw_v2_0_enable; + phy->ops.disable = dsi_phy_hw_v2_0_disable; + phy->ops.calculate_timing_params = + dsi_phy_hw_calculate_timing_params; + phy->ops.phy_idle_on = dsi_phy_hw_v2_0_idle_on; + phy->ops.phy_idle_off = dsi_phy_hw_v2_0_idle_off; + phy->ops.calculate_timing_params = + dsi_phy_hw_calculate_timing_params; + phy->ops.phy_timing_val = dsi_phy_hw_timing_val_v2_0; +} + +/** + * dsi_catalog_phy_3_0_init() - catalog init for DSI PHY 10nm + */ +static void dsi_catalog_phy_3_0_init(struct dsi_phy_hw *phy) +{ + phy->ops.regulator_enable = dsi_phy_hw_v3_0_regulator_enable; + phy->ops.regulator_disable = dsi_phy_hw_v3_0_regulator_disable; + phy->ops.enable = dsi_phy_hw_v3_0_enable; + phy->ops.disable = dsi_phy_hw_v3_0_disable; + phy->ops.calculate_timing_params = + dsi_phy_hw_calculate_timing_params; + phy->ops.ulps_ops.wait_for_lane_idle = + dsi_phy_hw_v3_0_wait_for_lane_idle; + phy->ops.ulps_ops.ulps_request = + dsi_phy_hw_v3_0_ulps_request; + phy->ops.ulps_ops.ulps_exit = + dsi_phy_hw_v3_0_ulps_exit; + phy->ops.ulps_ops.get_lanes_in_ulps = + dsi_phy_hw_v3_0_get_lanes_in_ulps; + phy->ops.ulps_ops.is_lanes_in_ulps = + dsi_phy_hw_v3_0_is_lanes_in_ulps; + phy->ops.phy_timing_val = dsi_phy_hw_timing_val_v3_0; +} + +/** + * dsi_catalog_phy_setup() - return catalog info for dsi phy hardware + * @ctrl: Pointer to DSI PHY hw object. + * @version: DSI PHY version. + * @index: DSI PHY instance ID. + * + * This function setups the catalog information in the dsi_phy_hw object. + * + * return: error code for failure and 0 for success. + */ +int dsi_catalog_phy_setup(struct dsi_phy_hw *phy, + enum dsi_phy_version version, + u32 index) +{ + int rc = 0; + + if (version == DSI_PHY_VERSION_UNKNOWN || + version >= DSI_PHY_VERSION_MAX) { + pr_err("Unsupported version: %d\n", version); + return -ENOTSUPP; + } + + phy->index = index; + set_bit(DSI_PHY_DPHY, phy->feature_map); + + dsi_phy_timing_calc_init(phy, version); + + switch (version) { + case DSI_PHY_VERSION_2_0: + dsi_catalog_phy_2_0_init(phy); + break; + case DSI_PHY_VERSION_3_0: + dsi_catalog_phy_3_0_init(phy); + break; + case DSI_PHY_VERSION_0_0_HPM: + case DSI_PHY_VERSION_0_0_LPM: + case DSI_PHY_VERSION_1_0: + default: + return -ENOTSUPP; + } + + return rc; +} + + diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_catalog.h b/drivers/gpu/drm/msm/dsi-staging/dsi_catalog.h new file mode 100644 index 000000000000..84448ecf819d --- /dev/null +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_catalog.h @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _DSI_CATALOG_H_ +#define _DSI_CATALOG_H_ + +#include "dsi_ctrl_hw.h" +#include "dsi_phy_hw.h" + +/** + * dsi_catalog_ctrl_setup() - return catalog info for dsi controller + * @ctrl: Pointer to DSI controller hw object. + * @version: DSI controller version. + * @index: DSI controller instance ID. + * @phy_isolation_enabled: DSI controller works isolated from phy. + * + * This function setups the catalog information in the dsi_ctrl_hw object. + * + * return: error code for failure and 0 for success. + */ +int dsi_catalog_ctrl_setup(struct dsi_ctrl_hw *ctrl, + enum dsi_ctrl_version version, u32 index, + bool phy_isolation_enabled); + +/** + * dsi_catalog_phy_setup() - return catalog info for dsi phy hardware + * @phy: Pointer to DSI PHY hw object. + * @version: DSI PHY version. + * @index: DSI PHY instance ID. + * + * This function setups the catalog information in the dsi_phy_hw object. + * + * return: error code for failure and 0 for success. + */ +int dsi_catalog_phy_setup(struct dsi_phy_hw *phy, + enum dsi_phy_version version, + u32 index); + +/** + * dsi_phy_timing_calc_init() - initialize info for DSI PHY timing calculations + * @phy: Pointer to DSI PHY hw object. + * @version: DSI PHY version. + * + * This function setups the catalog information in the dsi_phy_hw object. + * + * return: error code for failure and 0 for success. + */ +int dsi_phy_timing_calc_init(struct dsi_phy_hw *phy, + enum dsi_phy_version version); + +/** + * dsi_phy_hw_calculate_timing_params() - DSI PHY timing parameter calculations + * @phy: Pointer to DSI PHY hw object. + * @mode: DSI mode information. + * @host: DSI host configuration. + * @timing: DSI phy lane configurations. + * + * This function setups the catalog information in the dsi_phy_hw object. + * + * return: error code for failure and 0 for success. + */ +int dsi_phy_hw_calculate_timing_params(struct dsi_phy_hw *phy, + struct dsi_mode_info *mode, + struct dsi_host_common_cfg *host, + struct dsi_phy_per_lane_cfgs *timing); + +/* Definitions for 14nm PHY hardware driver */ +void dsi_phy_hw_v2_0_regulator_enable(struct dsi_phy_hw *phy, + struct dsi_phy_per_lane_cfgs *cfg); +void dsi_phy_hw_v2_0_regulator_disable(struct dsi_phy_hw *phy); +void dsi_phy_hw_v2_0_enable(struct dsi_phy_hw *phy, struct dsi_phy_cfg *cfg); +void dsi_phy_hw_v2_0_disable(struct dsi_phy_hw *phy, struct dsi_phy_cfg *cfg); +void dsi_phy_hw_v2_0_idle_on(struct dsi_phy_hw *phy, struct dsi_phy_cfg *cfg); +void dsi_phy_hw_v2_0_idle_off(struct dsi_phy_hw *phy); +int dsi_phy_hw_timing_val_v2_0(struct dsi_phy_per_lane_cfgs *timing_cfg, + u32 *timing_val, u32 size); + +/* Definitions for 10nm PHY hardware driver */ +void dsi_phy_hw_v3_0_regulator_enable(struct dsi_phy_hw *phy, + struct dsi_phy_per_lane_cfgs *cfg); +void dsi_phy_hw_v3_0_regulator_disable(struct dsi_phy_hw *phy); +void dsi_phy_hw_v3_0_enable(struct dsi_phy_hw *phy, struct dsi_phy_cfg *cfg); +void dsi_phy_hw_v3_0_disable(struct dsi_phy_hw *phy, struct dsi_phy_cfg *cfg); +int dsi_phy_hw_v3_0_wait_for_lane_idle(struct dsi_phy_hw *phy, u32 lanes); +void dsi_phy_hw_v3_0_ulps_request(struct dsi_phy_hw *phy, + struct dsi_phy_cfg *cfg, u32 lanes); +void dsi_phy_hw_v3_0_ulps_exit(struct dsi_phy_hw *phy, + struct dsi_phy_cfg *cfg, u32 lanes); +u32 dsi_phy_hw_v3_0_get_lanes_in_ulps(struct dsi_phy_hw *phy); +bool dsi_phy_hw_v3_0_is_lanes_in_ulps(u32 lanes, u32 ulps_lanes); +int dsi_phy_hw_timing_val_v3_0(struct dsi_phy_per_lane_cfgs *timing_cfg, + u32 *timing_val, u32 size); + +/* DSI controller common ops */ +u32 dsi_ctrl_hw_cmn_get_interrupt_status(struct dsi_ctrl_hw *ctrl); +void dsi_ctrl_hw_cmn_debug_bus(struct dsi_ctrl_hw *ctrl); +void dsi_ctrl_hw_cmn_clear_interrupt_status(struct dsi_ctrl_hw *ctrl, u32 ints); +void dsi_ctrl_hw_cmn_enable_status_interrupts(struct dsi_ctrl_hw *ctrl, + u32 ints); + +u64 dsi_ctrl_hw_cmn_get_error_status(struct dsi_ctrl_hw *ctrl); +void dsi_ctrl_hw_cmn_clear_error_status(struct dsi_ctrl_hw *ctrl, u64 errors); +void dsi_ctrl_hw_cmn_enable_error_interrupts(struct dsi_ctrl_hw *ctrl, + u64 errors); + +void dsi_ctrl_hw_cmn_video_test_pattern_setup(struct dsi_ctrl_hw *ctrl, + enum dsi_test_pattern type, + u32 init_val); +void dsi_ctrl_hw_cmn_cmd_test_pattern_setup(struct dsi_ctrl_hw *ctrl, + enum dsi_test_pattern type, + u32 init_val, + u32 stream_id); +void dsi_ctrl_hw_cmn_test_pattern_enable(struct dsi_ctrl_hw *ctrl, bool enable); +void dsi_ctrl_hw_cmn_trigger_cmd_test_pattern(struct dsi_ctrl_hw *ctrl, + u32 stream_id); + +void dsi_ctrl_hw_cmn_host_setup(struct dsi_ctrl_hw *ctrl, + struct dsi_host_common_cfg *config); +void dsi_ctrl_hw_cmn_video_engine_en(struct dsi_ctrl_hw *ctrl, bool on); +void dsi_ctrl_hw_cmn_video_engine_setup(struct dsi_ctrl_hw *ctrl, + struct dsi_host_common_cfg *common_cfg, + struct dsi_video_engine_cfg *cfg); +void dsi_ctrl_hw_cmn_set_video_timing(struct dsi_ctrl_hw *ctrl, + struct dsi_mode_info *mode); +void dsi_ctrl_hw_cmn_set_timing_db(struct dsi_ctrl_hw *ctrl, + bool enable); +void dsi_ctrl_hw_cmn_cmd_engine_setup(struct dsi_ctrl_hw *ctrl, + struct dsi_host_common_cfg *common_cfg, + struct dsi_cmd_engine_cfg *cfg); + +void dsi_ctrl_hw_cmn_ctrl_en(struct dsi_ctrl_hw *ctrl, bool on); +void dsi_ctrl_hw_cmn_cmd_engine_en(struct dsi_ctrl_hw *ctrl, bool on); + +void dsi_ctrl_hw_cmn_setup_cmd_stream(struct dsi_ctrl_hw *ctrl, + struct dsi_mode_info *mode, + u32 h_stride, + u32 vc_id, + struct dsi_rect *roi); +void dsi_ctrl_hw_cmn_phy_sw_reset(struct dsi_ctrl_hw *ctrl); +void dsi_ctrl_hw_cmn_soft_reset(struct dsi_ctrl_hw *ctrl); + +void dsi_ctrl_hw_cmn_setup_misr(struct dsi_ctrl_hw *ctrl, + enum dsi_op_mode panel_mode, + bool enable, u32 frame_count); +u32 dsi_ctrl_hw_cmn_collect_misr(struct dsi_ctrl_hw *ctrl, + enum dsi_op_mode panel_mode); + +void dsi_ctrl_hw_cmn_kickoff_command(struct dsi_ctrl_hw *ctrl, + struct dsi_ctrl_cmd_dma_info *cmd, + u32 flags); + +void dsi_ctrl_hw_cmn_kickoff_fifo_command(struct dsi_ctrl_hw *ctrl, + struct dsi_ctrl_cmd_dma_fifo_info *cmd, + u32 flags); +void dsi_ctrl_hw_cmn_reset_cmd_fifo(struct dsi_ctrl_hw *ctrl); +void dsi_ctrl_hw_cmn_trigger_command_dma(struct dsi_ctrl_hw *ctrl); +void dsi_ctrl_hw_dln0_phy_err(struct dsi_ctrl_hw *ctrl); +void dsi_ctrl_hw_cmn_phy_reset_config(struct dsi_ctrl_hw *ctrl, + bool enable); +void dsi_ctrl_hw_22_phy_reset_config(struct dsi_ctrl_hw *ctrl, + bool enable); + +/* Definitions specific to 1.4 DSI controller hardware */ +int dsi_ctrl_hw_14_wait_for_lane_idle(struct dsi_ctrl_hw *ctrl, u32 lanes); +void dsi_ctrl_hw_14_setup_lane_map(struct dsi_ctrl_hw *ctrl, + struct dsi_lane_map *lane_map); +void dsi_ctrl_hw_14_ulps_request(struct dsi_ctrl_hw *ctrl, u32 lanes); +void dsi_ctrl_hw_14_ulps_exit(struct dsi_ctrl_hw *ctrl, u32 lanes); +u32 dsi_ctrl_hw_14_get_lanes_in_ulps(struct dsi_ctrl_hw *ctrl); + +void dsi_ctrl_hw_14_clamp_enable(struct dsi_ctrl_hw *ctrl, + u32 lanes, + bool enable_ulps); + +void dsi_ctrl_hw_14_clamp_disable(struct dsi_ctrl_hw *ctrl, + u32 lanes, + bool disable_ulps); +ssize_t dsi_ctrl_hw_14_reg_dump_to_buffer(struct dsi_ctrl_hw *ctrl, + char *buf, + u32 size); + +/* Definitions specific to 2.0 DSI controller hardware */ +void dsi_ctrl_hw_20_setup_lane_map(struct dsi_ctrl_hw *ctrl, + struct dsi_lane_map *lane_map); +int dsi_ctrl_hw_20_wait_for_lane_idle(struct dsi_ctrl_hw *ctrl, u32 lanes); +ssize_t dsi_ctrl_hw_20_reg_dump_to_buffer(struct dsi_ctrl_hw *ctrl, + char *buf, + u32 size); + +#endif /* _DSI_CATALOG_H_ */ diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_clk.h b/drivers/gpu/drm/msm/dsi-staging/dsi_clk.h new file mode 100644 index 000000000000..2a84a2d93847 --- /dev/null +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_clk.h @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _DSI_CLK_H_ +#define _DSI_CLK_H_ + +#include +#include +#include +#include +#include "sde_power_handle.h" + +#define MAX_STRING_LEN 32 +#define MAX_DSI_CTRL 2 + +enum dsi_clk_state { + DSI_CLK_OFF, + DSI_CLK_ON, + DSI_CLK_EARLY_GATE, +}; + +enum clk_req_client { + DSI_CLK_REQ_MDP_CLIENT = 0, + DSI_CLK_REQ_DSI_CLIENT, +}; + +enum dsi_link_clk_type { + DSI_LINK_ESC_CLK, + DSI_LINK_BYTE_CLK, + DSI_LINK_PIX_CLK, + DSI_LINK_BYTE_INTF_CLK, + DSI_LINK_CLK_MAX, +}; + +enum dsi_clk_type { + DSI_CORE_CLK = BIT(0), + DSI_LINK_CLK = BIT(1), + DSI_ALL_CLKS = (BIT(0) | BIT(1)), + DSI_CLKS_MAX = BIT(2), +}; + +struct dsi_clk_ctrl_info { + enum dsi_clk_type clk_type; + enum dsi_clk_state clk_state; + enum clk_req_client client; +}; + +struct clk_ctrl_cb { + void *priv; + int (*dsi_clk_cb)(void *priv, struct dsi_clk_ctrl_info clk_ctrl_info); +}; + +/** + * struct dsi_core_clk_info - Core clock information for DSI hardware + * @mdp_core_clk: Handle to MDP core clock. + * @iface_clk: Handle to MDP interface clock. + * @core_mmss_clk: Handle to MMSS core clock. + * @bus_clk: Handle to bus clock. + * @mnoc_clk: Handle to MMSS NOC clock. + * @dsi_core_client: Pointer to SDE power client + * @phandle: Pointer to SDE power handle + */ +struct dsi_core_clk_info { + struct clk *mdp_core_clk; + struct clk *iface_clk; + struct clk *core_mmss_clk; + struct clk *bus_clk; + struct clk *mnoc_clk; + struct sde_power_client *dsi_core_client; + struct sde_power_handle *phandle; +}; + +/** + * struct dsi_link_clk_info - Link clock information for DSI hardware. + * @byte_clk: Handle to DSI byte clock. + * @pixel_clk: Handle to DSI pixel clock. + * @esc_clk: Handle to DSI escape clock. + * @byte_intf_clk: Handle to DSI byte intf. clock. + */ +struct dsi_link_clk_info { + struct clk *byte_clk; + struct clk *pixel_clk; + struct clk *esc_clk; + struct clk *byte_intf_clk; +}; + +/** + * struct link_clk_freq - Clock frequency information for Link clocks + * @byte_clk_rate: Frequency of DSI byte clock in KHz. + * @pixel_clk_rate: Frequency of DSI pixel clock in KHz. + * @esc_clk_rate: Frequency of DSI escape clock in KHz. + */ +struct link_clk_freq { + u32 byte_clk_rate; + u32 pix_clk_rate; + u32 esc_clk_rate; +}; + +/** + * typedef *pre_clockoff_cb() - Callback before clock is turned off + * @priv: private data pointer. + * @clk_type: clock which is being turned off. + * @new_state: next state for the clock. + * + * @return: error code. + */ +typedef int (*pre_clockoff_cb)(void *priv, + enum dsi_clk_type clk_type, + enum dsi_clk_state new_state); + +/** + * typedef *post_clockoff_cb() - Callback after clock is turned off + * @priv: private data pointer. + * @clk_type: clock which was turned off. + * @curr_state: current state for the clock. + * + * @return: error code. + */ +typedef int (*post_clockoff_cb)(void *priv, + enum dsi_clk_type clk_type, + enum dsi_clk_state curr_state); + +/** + * typedef *post_clockon_cb() - Callback after clock is turned on + * @priv: private data pointer. + * @clk_type: clock which was turned on. + * @curr_state: current state for the clock. + * + * @return: error code. + */ +typedef int (*post_clockon_cb)(void *priv, + enum dsi_clk_type clk_type, + enum dsi_clk_state curr_state); + +/** + * typedef *pre_clockon_cb() - Callback before clock is turned on + * @priv: private data pointer. + * @clk_type: clock which is being turned on. + * @new_state: next state for the clock. + * + * @return: error code. + */ +typedef int (*pre_clockon_cb)(void *priv, + enum dsi_clk_type clk_type, + enum dsi_clk_state new_state); + + +/** + * struct dsi_clk_info - clock information for DSI hardware. + * @name: client name. + * @c_clks[MAX_DSI_CTRL] array of core clock configurations + * @l_clks[MAX_DSI_CTRL] array of link clock configurations + * @bus_handle[MAX_DSI_CTRL] array of bus handles + * @ctrl_index[MAX_DSI_CTRL] array of DSI controller indexes mapped + * to core and link clock configurations + * @pre_clkoff_cb callback before clock is turned off + * @post_clkoff_cb callback after clock is turned off + * @post_clkon_cb callback after clock is turned on + * @pre_clkon_cb callback before clock is turned on + * @priv_data pointer to private data + * @master_ndx master DSI controller index + * @dsi_ctrl_count number of DSI controllers + */ +struct dsi_clk_info { + char name[MAX_STRING_LEN]; + struct dsi_core_clk_info c_clks[MAX_DSI_CTRL]; + struct dsi_link_clk_info l_clks[MAX_DSI_CTRL]; + u32 bus_handle[MAX_DSI_CTRL]; + u32 ctrl_index[MAX_DSI_CTRL]; + pre_clockoff_cb pre_clkoff_cb; + post_clockoff_cb post_clkoff_cb; + post_clockon_cb post_clkon_cb; + pre_clockon_cb pre_clkon_cb; + void *priv_data; + u32 master_ndx; + u32 dsi_ctrl_count; +}; + +/** + * struct dsi_clk_link_set - Pair of clock handles to describe link clocks + * @byte_clk: Handle to DSi byte clock. + * @pixel_clk: Handle to DSI pixel clock. + */ +struct dsi_clk_link_set { + struct clk *byte_clk; + struct clk *pixel_clk; +}; + +/** + * dsi_display_clk_mgr_register() - Register DSI clock manager + * @info: Structure containing DSI clock information + */ +void *dsi_display_clk_mngr_register(struct dsi_clk_info *info); + +/** + * dsi_display_clk_mngr_deregister() - Deregister DSI clock manager + * @clk_mngr: DSI clock manager pointer + */ +int dsi_display_clk_mngr_deregister(void *clk_mngr); + +/** + * dsi_register_clk_handle() - Register clock handle with DSI clock manager + * @clk_mngr: DSI clock manager pointer + * @client: DSI clock client pointer. + */ +void *dsi_register_clk_handle(void *clk_mngr, char *client); + +/** + * dsi_deregister_clk_handle() - Deregister clock handle from DSI clock manager + * @client: DSI clock client pointer. + * + * return: error code in case of failure or 0 for success. + */ +int dsi_deregister_clk_handle(void *client); + +/** + * dsi_display_clk_ctrl() - set frequencies for link clks + * @handle: Handle of desired DSI clock client. + * @clk_type: Clock which is being controlled. + * @clk_state: Desired state of clock + * + * return: error code in case of failure or 0 for success. + */ +int dsi_display_clk_ctrl(void *handle, + enum dsi_clk_type clk_type, enum dsi_clk_state clk_state); + +/** + * dsi_clk_set_link_frequencies() - set frequencies for link clks + * @client: DSI clock client pointer. + * @freq: Structure containing link clock frequencies. + * @index: Index of the DSI controller. + * + * return: error code in case of failure or 0 for success. + */ +int dsi_clk_set_link_frequencies(void *client, struct link_clk_freq freq, + u32 index); + + +/** + * dsi_clk_set_pixel_clk_rate() - set frequency for pixel clock + * @client: DSI clock client pointer. + * @pixel_clk: Pixel clock rate in Hz. + * @index: Index of the DSI controller. + * return: error code in case of failure or 0 for success. + */ +int dsi_clk_set_pixel_clk_rate(void *client, u64 pixel_clk, u32 index); + + +/** + * dsi_clk_set_byte_clk_rate() - set frequency for byte clock + * @client: DSI clock client pointer. + * @byte_clk: Pixel clock rate in Hz. + * @index: Index of the DSI controller. + * return: error code in case of failure or 0 for success. + */ +int dsi_clk_set_byte_clk_rate(void *client, u64 byte_clk, u32 index); + +/** + * dsi_clk_update_parent() - update parent clocks for specified clock + * @parent: link clock pair which are set as parent. + * @child: link clock pair whose parent has to be set. + */ +int dsi_clk_update_parent(struct dsi_clk_link_set *parent, + struct dsi_clk_link_set *child); +#endif /* _DSI_CLK_H_ */ diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_clk_manager.c b/drivers/gpu/drm/msm/dsi-staging/dsi_clk_manager.c new file mode 100644 index 000000000000..560964e670d0 --- /dev/null +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_clk_manager.c @@ -0,0 +1,1226 @@ +/* + * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include "dsi_clk.h" + +struct dsi_core_clks { + struct dsi_core_clk_info clks; + u32 bus_handle; +}; + +struct dsi_link_clks { + struct dsi_link_clk_info clks; + struct link_clk_freq freq; +}; + +struct dsi_clk_mngr { + char name[MAX_STRING_LEN]; + struct mutex clk_mutex; + struct list_head client_list; + + u32 dsi_ctrl_count; + u32 master_ndx; + struct dsi_core_clks core_clks[MAX_DSI_CTRL]; + struct dsi_link_clks link_clks[MAX_DSI_CTRL]; + u32 ctrl_index[MAX_DSI_CTRL]; + u32 core_clk_state; + u32 link_clk_state; + + pre_clockoff_cb pre_clkoff_cb; + post_clockoff_cb post_clkoff_cb; + post_clockon_cb post_clkon_cb; + pre_clockon_cb pre_clkon_cb; + + void *priv_data; +}; + +struct dsi_clk_client_info { + char name[MAX_STRING_LEN]; + u32 core_refcount; + u32 link_refcount; + u32 core_clk_state; + u32 link_clk_state; + struct list_head list; + struct dsi_clk_mngr *mngr; +}; + +static int _get_clk_mngr_index(struct dsi_clk_mngr *mngr, + u32 dsi_ctrl_index, + u32 *clk_mngr_index) +{ + int i; + + for (i = 0; i < mngr->dsi_ctrl_count; i++) { + if (mngr->ctrl_index[i] == dsi_ctrl_index) { + *clk_mngr_index = i; + return 0; + } + } + + return -EINVAL; +} + +/** + * dsi_clk_set_link_frequencies() - set frequencies for link clks + * @clks: Link clock information + * @pixel_clk: pixel clock frequency in KHz. + * @byte_clk: Byte clock frequency in KHz. + * @esc_clk: Escape clock frequency in KHz. + * + * return: error code in case of failure or 0 for success. + */ +int dsi_clk_set_link_frequencies(void *client, struct link_clk_freq freq, + u32 index) +{ + int rc = 0, clk_mngr_index = 0; + struct dsi_clk_client_info *c = client; + struct dsi_clk_mngr *mngr; + + if (!client) { + pr_err("invalid params\n"); + return -EINVAL; + } + + mngr = c->mngr; + rc = _get_clk_mngr_index(mngr, index, &clk_mngr_index); + if (rc) { + pr_err("failed to map control index %d\n", index); + return -EINVAL; + } + + memcpy(&mngr->link_clks[clk_mngr_index].freq, &freq, + sizeof(struct link_clk_freq)); + + return rc; +} + +/** + * dsi_clk_set_pixel_clk_rate() - set frequency for pixel clock + * @clks: DSI link clock information. + * @pixel_clk: Pixel clock rate in KHz. + * + * return: error code in case of failure or 0 for success. + */ +int dsi_clk_set_pixel_clk_rate(void *client, u64 pixel_clk, u32 index) +{ + int rc = 0; + struct dsi_clk_client_info *c = client; + struct dsi_clk_mngr *mngr; + + mngr = c->mngr; + rc = clk_set_rate(mngr->link_clks[index].clks.pixel_clk, pixel_clk); + if (rc) + pr_err("failed to set clk rate for pixel clk, rc=%d\n", rc); + else + mngr->link_clks[index].freq.pix_clk_rate = pixel_clk; + + return rc; +} + +/** + * dsi_clk_set_byte_clk_rate() - set frequency for byte clock + * @client: DSI clock client pointer. + * @byte_clk: Pixel clock rate in Hz. + * @index: Index of the DSI controller. + * return: error code in case of failure or 0 for success. + */ +int dsi_clk_set_byte_clk_rate(void *client, u64 byte_clk, u32 index) +{ + int rc = 0; + struct dsi_clk_client_info *c = client; + struct dsi_clk_mngr *mngr; + + mngr = c->mngr; + rc = clk_set_rate(mngr->link_clks[index].clks.byte_clk, byte_clk); + if (rc) + pr_err("failed to set clk rate for byte clk, rc=%d\n", rc); + else + mngr->link_clks[index].freq.byte_clk_rate = byte_clk; + + return rc; + +} + +/** + * dsi_clk_update_parent() - update parent clocks for specified clock + * @parent: link clock pair which are set as parent. + * @child: link clock pair whose parent has to be set. + */ +int dsi_clk_update_parent(struct dsi_clk_link_set *parent, + struct dsi_clk_link_set *child) +{ + int rc = 0; + + rc = clk_set_parent(child->byte_clk, parent->byte_clk); + if (rc) { + pr_err("failed to set byte clk parent\n"); + goto error; + } + + rc = clk_set_parent(child->pixel_clk, parent->pixel_clk); + if (rc) { + pr_err("failed to set pixel clk parent\n"); + goto error; + } +error: + return rc; +} + +int dsi_core_clk_start(struct dsi_core_clks *c_clks) +{ + int rc = 0; + + if (c_clks->clks.mdp_core_clk) { + rc = clk_prepare_enable(c_clks->clks.mdp_core_clk); + if (rc) { + pr_err("failed to enable mdp_core_clk, rc=%d\n", rc); + goto error; + } + } + + if (c_clks->clks.mnoc_clk) { + rc = clk_prepare_enable(c_clks->clks.mnoc_clk); + if (rc) { + pr_err("failed to enable mnoc_clk, rc=%d\n", rc); + goto error_disable_core_clk; + } + } + + if (c_clks->clks.iface_clk) { + rc = clk_prepare_enable(c_clks->clks.iface_clk); + if (rc) { + pr_err("failed to enable iface_clk, rc=%d\n", rc); + goto error_disable_mnoc_clk; + } + } + + if (c_clks->clks.bus_clk) { + rc = clk_prepare_enable(c_clks->clks.bus_clk); + if (rc) { + pr_err("failed to enable bus_clk, rc=%d\n", rc); + goto error_disable_iface_clk; + } + } + + if (c_clks->clks.core_mmss_clk) { + rc = clk_prepare_enable(c_clks->clks.core_mmss_clk); + if (rc) { + pr_err("failed to enable core_mmss_clk, rc=%d\n", rc); + goto error_disable_bus_clk; + } + } + + if (c_clks->bus_handle) { + rc = msm_bus_scale_client_update_request(c_clks->bus_handle, 1); + if (rc) { + pr_err("bus scale client enable failed, rc=%d\n", rc); + goto error_disable_mmss_clk; + } + } + + return rc; + +error_disable_mmss_clk: + if (c_clks->clks.core_mmss_clk) + clk_disable_unprepare(c_clks->clks.core_mmss_clk); +error_disable_bus_clk: + if (c_clks->clks.bus_clk) + clk_disable_unprepare(c_clks->clks.bus_clk); +error_disable_iface_clk: + if (c_clks->clks.iface_clk) + clk_disable_unprepare(c_clks->clks.iface_clk); +error_disable_mnoc_clk: + if (c_clks->clks.mnoc_clk) + clk_disable_unprepare(c_clks->clks.mnoc_clk); +error_disable_core_clk: + if (c_clks->clks.mdp_core_clk) + clk_disable_unprepare(c_clks->clks.mdp_core_clk); +error: + return rc; +} + +int dsi_core_clk_stop(struct dsi_core_clks *c_clks) +{ + int rc = 0; + + if (c_clks->bus_handle) { + rc = msm_bus_scale_client_update_request(c_clks->bus_handle, 0); + if (rc) { + pr_err("bus scale client disable failed, rc=%d\n", rc); + return rc; + } + } + + if (c_clks->clks.core_mmss_clk) + clk_disable_unprepare(c_clks->clks.core_mmss_clk); + + if (c_clks->clks.bus_clk) + clk_disable_unprepare(c_clks->clks.bus_clk); + + if (c_clks->clks.iface_clk) + clk_disable_unprepare(c_clks->clks.iface_clk); + + if (c_clks->clks.mnoc_clk) + clk_disable_unprepare(c_clks->clks.mnoc_clk); + + if (c_clks->clks.mdp_core_clk) + clk_disable_unprepare(c_clks->clks.mdp_core_clk); + + return rc; +} + +static int dsi_link_clk_set_rate(struct dsi_link_clks *l_clks) +{ + int rc = 0; + + rc = clk_set_rate(l_clks->clks.esc_clk, l_clks->freq.esc_clk_rate); + if (rc) { + pr_err("clk_set_rate failed for esc_clk rc = %d\n", rc); + goto error; + } + + rc = clk_set_rate(l_clks->clks.byte_clk, l_clks->freq.byte_clk_rate); + if (rc) { + pr_err("clk_set_rate failed for byte_clk rc = %d\n", rc); + goto error; + } + + rc = clk_set_rate(l_clks->clks.pixel_clk, l_clks->freq.pix_clk_rate); + if (rc) { + pr_err("clk_set_rate failed for pixel_clk rc = %d\n", rc); + goto error; + } + + /* + * If byte_intf_clk is present, set rate for that too. + * For DPHY: byte_intf_clk_rate = byte_clk_rate / 2 + * todo: this needs to be revisited when support for CPHY is added + */ + if (l_clks->clks.byte_intf_clk) { + rc = clk_set_rate(l_clks->clks.byte_intf_clk, + (l_clks->freq.byte_clk_rate / 2)); + if (rc) { + pr_err("set_rate failed for byte_intf_clk rc = %d\n", + rc); + goto error; + } + } +error: + return rc; +} + +static int dsi_link_clk_prepare(struct dsi_link_clks *l_clks) +{ + int rc = 0; + + rc = clk_prepare(l_clks->clks.esc_clk); + if (rc) { + pr_err("Failed to prepare dsi esc clk, rc=%d\n", rc); + goto esc_clk_err; + } + + rc = clk_prepare(l_clks->clks.byte_clk); + if (rc) { + pr_err("Failed to prepare dsi byte clk, rc=%d\n", rc); + goto byte_clk_err; + } + + rc = clk_prepare(l_clks->clks.pixel_clk); + if (rc) { + pr_err("Failed to prepare dsi pixel clk, rc=%d\n", rc); + goto pixel_clk_err; + } + + if (l_clks->clks.byte_intf_clk) { + rc = clk_prepare(l_clks->clks.byte_intf_clk); + if (rc) { + pr_err("Failed to prepare dsi byte intf clk, rc=%d\n", + rc); + goto byte_intf_clk_err; + } + } + + return rc; + +byte_intf_clk_err: + clk_unprepare(l_clks->clks.pixel_clk); +pixel_clk_err: + clk_unprepare(l_clks->clks.byte_clk); +byte_clk_err: + clk_unprepare(l_clks->clks.esc_clk); +esc_clk_err: + return rc; +} + +static void dsi_link_clk_unprepare(struct dsi_link_clks *l_clks) +{ + if (l_clks->clks.byte_intf_clk) + clk_unprepare(l_clks->clks.byte_intf_clk); + clk_unprepare(l_clks->clks.pixel_clk); + clk_unprepare(l_clks->clks.byte_clk); + clk_unprepare(l_clks->clks.esc_clk); +} + +static int dsi_link_clk_enable(struct dsi_link_clks *l_clks) +{ + int rc = 0; + + rc = clk_enable(l_clks->clks.esc_clk); + if (rc) { + pr_err("Failed to enable dsi esc clk, rc=%d\n", rc); + goto esc_clk_err; + } + + rc = clk_enable(l_clks->clks.byte_clk); + if (rc) { + pr_err("Failed to enable dsi byte clk, rc=%d\n", rc); + goto byte_clk_err; + } + + rc = clk_enable(l_clks->clks.pixel_clk); + if (rc) { + pr_err("Failed to enable dsi pixel clk, rc=%d\n", rc); + goto pixel_clk_err; + } + + if (l_clks->clks.byte_intf_clk) { + rc = clk_enable(l_clks->clks.byte_intf_clk); + if (rc) { + pr_err("Failed to enable dsi byte intf clk, rc=%d\n", + rc); + goto byte_intf_clk_err; + } + } + + return rc; + +byte_intf_clk_err: + clk_disable(l_clks->clks.pixel_clk); +pixel_clk_err: + clk_disable(l_clks->clks.byte_clk); +byte_clk_err: + clk_disable(l_clks->clks.esc_clk); +esc_clk_err: + return rc; +} + +static void dsi_link_clk_disable(struct dsi_link_clks *l_clks) +{ + if (l_clks->clks.byte_intf_clk) + clk_disable(l_clks->clks.byte_intf_clk); + clk_disable(l_clks->clks.esc_clk); + clk_disable(l_clks->clks.pixel_clk); + clk_disable(l_clks->clks.byte_clk); +} + +/** + * dsi_link_clk_start() - enable dsi link clocks + */ +int dsi_link_clk_start(struct dsi_link_clks *clks) +{ + int rc = 0; + + rc = dsi_link_clk_set_rate(clks); + if (rc) { + pr_err("failed to set clk rates, rc = %d\n", rc); + goto error; + } + + rc = dsi_link_clk_prepare(clks); + if (rc) { + pr_err("failed to prepare link clks, rc = %d\n", rc); + goto error; + } + + rc = dsi_link_clk_enable(clks); + if (rc) { + pr_err("failed to enable link clks, rc = %d\n", rc); + goto error_unprepare; + } + + pr_debug("Link clocks are enabled\n"); + return rc; +error_unprepare: + dsi_link_clk_unprepare(clks); +error: + return rc; +} + +/** + * dsi_link_clk_stop() - Stop DSI link clocks. + */ +int dsi_link_clk_stop(struct dsi_link_clks *clks) +{ + dsi_link_clk_disable(clks); + dsi_link_clk_unprepare(clks); + + pr_debug("Link clocks disabled\n"); + + return 0; +} + +static int dsi_display_core_clk_enable(struct dsi_core_clks *clks, + u32 ctrl_count, u32 master_ndx) +{ + int rc = 0; + int i; + struct dsi_core_clks *clk, *m_clks; + + /* + * In case of split DSI usecases, the clock for master controller should + * be enabled before the other controller. Master controller in the + * clock context refers to the controller that sources the clock. + */ + + m_clks = &clks[master_ndx]; + rc = sde_power_resource_enable(m_clks->clks.phandle, + m_clks->clks.dsi_core_client, true); + + if (rc) { + pr_err("Power resource enable failed, rc=%d\n", rc); + goto error; + } + + rc = dsi_core_clk_start(m_clks); + if (rc) { + pr_err("failed to turn on master clocks, rc=%d\n", rc); + goto error_disable_master_resource; + } + + /* Turn on rest of the core clocks */ + for (i = 0; i < ctrl_count; i++) { + clk = &clks[i]; + if (!clk || (clk == m_clks)) + continue; + + rc = sde_power_resource_enable(clk->clks.phandle, + clk->clks.dsi_core_client, true); + if (rc) { + pr_err("Power resource enable failed, rc=%d\n", rc); + goto error_disable_master; + } + + rc = dsi_core_clk_start(clk); + if (rc) { + pr_err("failed to turn on clocks, rc=%d\n", rc); + (void)sde_power_resource_enable(clk->clks.phandle, + clk->clks.dsi_core_client, false); + goto error_disable_master; + } + } + return rc; +error_disable_master: + (void)dsi_core_clk_stop(m_clks); + +error_disable_master_resource: + (void)sde_power_resource_enable(m_clks->clks.phandle, + m_clks->clks.dsi_core_client, false); +error: + return rc; +} + +static int dsi_display_link_clk_enable(struct dsi_link_clks *clks, + u32 ctrl_count, u32 master_ndx) +{ + int rc = 0; + int i; + struct dsi_link_clks *clk, *m_clks; + + /* + * In case of split DSI usecases, the clock for master controller should + * be enabled before the other controller. Master controller in the + * clock context refers to the controller that sources the clock. + */ + + m_clks = &clks[master_ndx]; + + rc = dsi_link_clk_start(m_clks); + if (rc) { + pr_err("failed to turn on master clocks, rc=%d\n", rc); + goto error; + } + + /* Turn on rest of the core clocks */ + for (i = 0; i < ctrl_count; i++) { + clk = &clks[i]; + if (!clk || (clk == m_clks)) + continue; + + rc = dsi_link_clk_start(clk); + if (rc) { + pr_err("failed to turn on clocks, rc=%d\n", rc); + goto error_disable_master; + } + } + return rc; +error_disable_master: + (void)dsi_link_clk_stop(m_clks); +error: + return rc; +} + +static int dsi_display_core_clk_disable(struct dsi_core_clks *clks, + u32 ctrl_count, u32 master_ndx) +{ + int rc = 0; + int i; + struct dsi_core_clks *clk, *m_clks; + + /* + * In case of split DSI usecases, clock for slave DSI controllers should + * be disabled first before disabling clock for master controller. Slave + * controllers in the clock context refer to controller which source + * clock from another controller. + */ + + m_clks = &clks[master_ndx]; + + /* Turn off non-master core clocks */ + for (i = 0; i < ctrl_count; i++) { + clk = &clks[i]; + if (!clk || (clk == m_clks)) + continue; + + rc = dsi_core_clk_stop(clk); + if (rc) { + pr_debug("failed to turn off clocks, rc=%d\n", rc); + goto error; + } + + rc = sde_power_resource_enable(clk->clks.phandle, + clk->clks.dsi_core_client, false); + if (rc) { + pr_err("Power resource disable failed: %d\n", rc); + goto error; + } + } + + rc = dsi_core_clk_stop(m_clks); + if (rc) { + pr_err("failed to turn off master clocks, rc=%d\n", rc); + goto error; + } + + rc = sde_power_resource_enable(m_clks->clks.phandle, + m_clks->clks.dsi_core_client, false); + if (rc) + pr_err("Power resource disable failed: %d\n", rc); +error: + return rc; +} + +static int dsi_display_link_clk_disable(struct dsi_link_clks *clks, + u32 ctrl_count, u32 master_ndx) +{ + int rc = 0; + int i; + struct dsi_link_clks *clk, *m_clks; + + /* + * In case of split DSI usecases, clock for slave DSI controllers should + * be disabled first before disabling clock for master controller. Slave + * controllers in the clock context refer to controller which source + * clock from another controller. + */ + + m_clks = &clks[master_ndx]; + + /* Turn off non-master link clocks */ + for (i = 0; i < ctrl_count; i++) { + clk = &clks[i]; + if (!clk || (clk == m_clks)) + continue; + + rc = dsi_link_clk_stop(clk); + if (rc) + pr_err("failed to turn off clocks, rc=%d\n", rc); + } + + rc = dsi_link_clk_stop(m_clks); + if (rc) + pr_err("failed to turn off master clocks, rc=%d\n", rc); + + return rc; +} + +static int dsi_update_clk_state(struct dsi_core_clks *c_clks, u32 c_state, + struct dsi_link_clks *l_clks, u32 l_state) +{ + int rc = 0; + struct dsi_clk_mngr *mngr; + bool l_c_on = false; + + if (c_clks) { + mngr = + container_of(c_clks, struct dsi_clk_mngr, core_clks[0]); + } else if (l_clks) { + mngr = + container_of(l_clks, struct dsi_clk_mngr, link_clks[0]); + } else { + mngr = NULL; + } + + if (!mngr) + return -EINVAL; + + pr_debug("c_state = %d, l_state = %d\n", + c_clks ? c_state : -1, l_clks ? l_state : -1); + /* + * Below is the sequence to toggle DSI clocks: + * 1. For ON sequence, Core clocks before link clocks + * 2. For OFF sequence, Link clocks before core clocks. + */ + if (c_clks && (c_state == DSI_CLK_ON)) { + if (mngr->core_clk_state == DSI_CLK_OFF) { + rc = mngr->pre_clkon_cb(mngr->priv_data, + DSI_CORE_CLK, + DSI_CLK_ON); + if (rc) { + pr_err("failed to turn on MDP FS rc= %d\n", rc); + goto error; + } + } + rc = dsi_display_core_clk_enable(c_clks, mngr->dsi_ctrl_count, + mngr->master_ndx); + if (rc) { + pr_err("failed to turn on core clks rc = %d\n", rc); + goto error; + } + + if (mngr->post_clkon_cb) { + rc = mngr->post_clkon_cb(mngr->priv_data, + DSI_CORE_CLK, + DSI_CLK_ON); + if (rc) + pr_err("post clk on cb failed, rc = %d\n", rc); + } + mngr->core_clk_state = DSI_CLK_ON; + } + + if (l_clks) { + if (l_state == DSI_CLK_ON) { + if (mngr->pre_clkon_cb) { + rc = mngr->pre_clkon_cb(mngr->priv_data, + DSI_LINK_CLK, l_state); + if (rc) + pr_err("pre link clk on cb failed\n"); + } + rc = dsi_display_link_clk_enable(l_clks, + mngr->dsi_ctrl_count, mngr->master_ndx); + if (rc) { + pr_err("failed to start link clk rc= %d\n", rc); + goto error; + } + if (mngr->post_clkon_cb) { + rc = mngr->post_clkon_cb(mngr->priv_data, + DSI_LINK_CLK, + l_state); + if (rc) + pr_err("post link clk on cb failed\n"); + } + } else { + /* + * Two conditions that need to be checked for Link + * clocks: + * 1. Link clocks need core clocks to be on when + * transitioning from EARLY_GATE to OFF state. + * 2. ULPS mode might have to be enabled in case of OFF + * state. For ULPS, Link clocks should be turned ON + * first before they are turned off again. + * + * If Link is going from EARLY_GATE to OFF state AND + * Core clock is already in EARLY_GATE or OFF state, + * turn on Core clocks and link clocks. + * + * ULPS state is managed as part of the pre_clkoff_cb. + */ + if ((l_state == DSI_CLK_OFF) && + (mngr->link_clk_state == + DSI_CLK_EARLY_GATE) && + (mngr->core_clk_state != + DSI_CLK_ON)) { + rc = dsi_display_core_clk_enable( + mngr->core_clks, mngr->dsi_ctrl_count, + mngr->master_ndx); + if (rc) { + pr_err("core clks did not start\n"); + goto error; + } + + rc = dsi_display_link_clk_enable(l_clks, + mngr->dsi_ctrl_count, mngr->master_ndx); + if (rc) { + pr_err("Link clks did not start\n"); + goto error; + } + l_c_on = true; + pr_debug("ECG: core and Link_on\n"); + } + + if (mngr->pre_clkoff_cb) { + rc = mngr->pre_clkoff_cb(mngr->priv_data, + DSI_LINK_CLK, l_state); + if (rc) + pr_err("pre link clk off cb failed\n"); + } + + rc = dsi_display_link_clk_disable(l_clks, + mngr->dsi_ctrl_count, mngr->master_ndx); + if (rc) { + pr_err("failed to stop link clk, rc = %d\n", + rc); + goto error; + } + + if (mngr->post_clkoff_cb) { + rc = mngr->post_clkoff_cb(mngr->priv_data, + DSI_LINK_CLK, l_state); + if (rc) + pr_err("post link clk off cb failed\n"); + } + /* + * This check is to save unnecessary clock state + * change when going from EARLY_GATE to OFF. In the + * case where the request happens for both Core and Link + * clocks in the same call, core clocks need to be + * turned on first before OFF state can be entered. + * + * Core clocks are turned on here for Link clocks to go + * to OFF state. If core clock request is also present, + * then core clocks can be turned off Core clocks are + * transitioned to OFF state. + */ + if (l_c_on && (!(c_clks && (c_state == DSI_CLK_OFF) + && (mngr->core_clk_state == + DSI_CLK_EARLY_GATE)))) { + rc = dsi_display_core_clk_disable( + mngr->core_clks, mngr->dsi_ctrl_count, + mngr->master_ndx); + if (rc) { + pr_err("core clks did not stop\n"); + goto error; + } + + l_c_on = false; + pr_debug("ECG: core off\n"); + } else + pr_debug("ECG: core off skip\n"); + } + + mngr->link_clk_state = l_state; + } + + if (c_clks && (c_state != DSI_CLK_ON)) { + /* + * When going to OFF state from EARLY GATE state, Core clocks + * should be turned on first so that the IOs can be clamped. + * l_c_on flag is set, then the core clocks were turned before + * to the Link clocks go to OFF state. So Core clocks are + * already ON and this step can be skipped. + * + * IOs are clamped in pre_clkoff_cb callback. + */ + if ((c_state == DSI_CLK_OFF) && + (mngr->core_clk_state == + DSI_CLK_EARLY_GATE) && !l_c_on) { + rc = dsi_display_core_clk_enable(mngr->core_clks, + mngr->dsi_ctrl_count, mngr->master_ndx); + if (rc) { + pr_err("core clks did not start\n"); + goto error; + } + pr_debug("ECG: core on\n"); + } else + pr_debug("ECG: core on skip\n"); + + if (mngr->pre_clkoff_cb) { + rc = mngr->pre_clkoff_cb(mngr->priv_data, + DSI_CORE_CLK, + c_state); + if (rc) + pr_err("pre core clk off cb failed\n"); + } + + rc = dsi_display_core_clk_disable(c_clks, mngr->dsi_ctrl_count, + mngr->master_ndx); + if (rc) { + pr_err("failed to turn off core clks rc = %d\n", rc); + goto error; + } + + if (c_state == DSI_CLK_OFF) { + if (mngr->post_clkoff_cb) { + rc = mngr->post_clkoff_cb(mngr->priv_data, + DSI_CORE_CLK, + DSI_CLK_OFF); + if (rc) + pr_err("post clkoff cb fail, rc = %d\n", + rc); + } + } + mngr->core_clk_state = c_state; + } + +error: + return rc; +} + +static int dsi_recheck_clk_state(struct dsi_clk_mngr *mngr) +{ + int rc = 0; + struct list_head *pos = NULL; + struct dsi_clk_client_info *c; + u32 new_core_clk_state = DSI_CLK_OFF; + u32 new_link_clk_state = DSI_CLK_OFF; + u32 old_c_clk_state = DSI_CLK_OFF; + u32 old_l_clk_state = DSI_CLK_OFF; + struct dsi_core_clks *c_clks = NULL; + struct dsi_link_clks *l_clks = NULL; + + /* + * Conditions to maintain DSI manager clock state based on + * clock states of various clients: + * 1. If any client has clock in ON state, DSI manager clock state + * should be ON. + * 2. If any client is in ECG state with rest of them turned OFF, + * go to Early gate state. + * 3. If all clients have clocks as OFF, then go to OFF state. + */ + list_for_each(pos, &mngr->client_list) { + c = list_entry(pos, struct dsi_clk_client_info, list); + if (c->core_clk_state == DSI_CLK_ON) { + new_core_clk_state = DSI_CLK_ON; + break; + } else if (c->core_clk_state == DSI_CLK_EARLY_GATE) { + new_core_clk_state = DSI_CLK_EARLY_GATE; + } + } + + list_for_each(pos, &mngr->client_list) { + c = list_entry(pos, struct dsi_clk_client_info, list); + if (c->link_clk_state == DSI_CLK_ON) { + new_link_clk_state = DSI_CLK_ON; + break; + } else if (c->link_clk_state == DSI_CLK_EARLY_GATE) { + new_link_clk_state = DSI_CLK_EARLY_GATE; + } + } + + if (new_core_clk_state != mngr->core_clk_state) + c_clks = mngr->core_clks; + + if (new_link_clk_state != mngr->link_clk_state) + l_clks = mngr->link_clks; + + old_c_clk_state = mngr->core_clk_state; + old_l_clk_state = mngr->link_clk_state; + + pr_debug("c_clk_state (%d -> %d)\n", + old_c_clk_state, new_core_clk_state); + pr_debug("l_clk_state (%d -> %d)\n", + old_l_clk_state, new_link_clk_state); + + if (c_clks || l_clks) { + rc = dsi_update_clk_state(c_clks, new_core_clk_state, + l_clks, new_link_clk_state); + if (rc) { + pr_err("failed to update clock state, rc = %d\n", rc); + goto error; + } + } + +error: + return rc; +} + +int dsi_clk_req_state(void *client, enum dsi_clk_type clk, + enum dsi_clk_state state) +{ + int rc = 0; + struct dsi_clk_client_info *c = client; + struct dsi_clk_mngr *mngr; + bool changed = false; + + if (!client || !clk || clk > (DSI_CORE_CLK | DSI_LINK_CLK) || + state > DSI_CLK_EARLY_GATE) { + pr_err("Invalid params, client = %pK, clk = 0x%x, state = %d\n", + client, clk, state); + return -EINVAL; + } + + mngr = c->mngr; + mutex_lock(&mngr->clk_mutex); + + pr_debug("[%s]%s: CLK=%d, new_state=%d, core=%d, linkl=%d\n", + mngr->name, c->name, clk, state, c->core_clk_state, + c->link_clk_state); + + /* + * Clock refcount handling as below: + * i. Increment refcount whenever ON is called. + * ii. Decrement refcount when transitioning from ON state to + * either OFF or EARLY_GATE. + * iii. Do not decrement refcount when changing from + * EARLY_GATE to OFF. + */ + if (state == DSI_CLK_ON) { + if (clk & DSI_CORE_CLK) { + c->core_refcount++; + if (c->core_clk_state != DSI_CLK_ON) { + c->core_clk_state = DSI_CLK_ON; + changed = true; + } + } + if (clk & DSI_LINK_CLK) { + c->link_refcount++; + if (c->link_clk_state != DSI_CLK_ON) { + c->link_clk_state = DSI_CLK_ON; + changed = true; + } + } + } else if ((state == DSI_CLK_EARLY_GATE) || + (state == DSI_CLK_OFF)) { + if (clk & DSI_CORE_CLK) { + if (c->core_refcount == 0) { + if ((c->core_clk_state == + DSI_CLK_EARLY_GATE) && + (state == DSI_CLK_OFF)) { + changed = true; + c->core_clk_state = DSI_CLK_OFF; + } else { + pr_warn("Core refcount is zero for %s", + c->name); + } + } else { + c->core_refcount--; + if (c->core_refcount == 0) { + c->core_clk_state = state; + changed = true; + } + } + } + if (clk & DSI_LINK_CLK) { + if (c->link_refcount == 0) { + if ((c->link_clk_state == + DSI_CLK_EARLY_GATE) && + (state == DSI_CLK_OFF)) { + changed = true; + c->link_clk_state = DSI_CLK_OFF; + } else { + pr_warn("Link refcount is zero for %s", + c->name); + } + } else { + c->link_refcount--; + if (c->link_refcount == 0) { + c->link_clk_state = state; + changed = true; + } + } + } + } + pr_debug("[%s]%s: change=%d, Core (ref=%d, state=%d), Link (ref=%d, state=%d)\n", + mngr->name, c->name, changed, c->core_refcount, + c->core_clk_state, c->link_refcount, c->link_clk_state); + + if (changed) { + rc = dsi_recheck_clk_state(mngr); + if (rc) + pr_err("Failed to adjust clock state rc = %d\n", rc); + } + + mutex_unlock(&mngr->clk_mutex); + return rc; +} + +DEFINE_MUTEX(dsi_mngr_clk_mutex); + +int dsi_display_clk_ctrl(void *handle, + enum dsi_clk_type clk_type, enum dsi_clk_state clk_state) +{ + int rc = 0; + + if (!handle) { + pr_err("%s: Invalid arg\n", __func__); + return -EINVAL; + } + + mutex_lock(&dsi_mngr_clk_mutex); + rc = dsi_clk_req_state(handle, clk_type, clk_state); + if (rc) + pr_err("%s: failed set clk state, rc = %d\n", __func__, rc); + mutex_unlock(&dsi_mngr_clk_mutex); + + return rc; +} + +void *dsi_register_clk_handle(void *clk_mngr, char *client) +{ + void *handle = NULL; + struct dsi_clk_mngr *mngr = clk_mngr; + struct dsi_clk_client_info *c; + + if (!mngr) { + pr_err("bad params\n"); + return ERR_PTR(-EINVAL); + } + + mutex_lock(&mngr->clk_mutex); + + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) { + handle = ERR_PTR(-ENOMEM); + goto error; + } + + strlcpy(c->name, client, MAX_STRING_LEN); + c->mngr = mngr; + + list_add(&c->list, &mngr->client_list); + + pr_debug("[%s]: Added new client (%s)\n", mngr->name, c->name); + handle = c; +error: + mutex_unlock(&mngr->clk_mutex); + return handle; +} + +int dsi_deregister_clk_handle(void *client) +{ + int rc = 0; + struct dsi_clk_client_info *c = client; + struct dsi_clk_mngr *mngr; + struct list_head *pos = NULL; + struct list_head *tmp = NULL; + struct dsi_clk_client_info *node = NULL; + + if (!client) { + pr_err("Invalid params\n"); + return -EINVAL; + } + + mngr = c->mngr; + pr_debug("%s: ENTER\n", mngr->name); + mutex_lock(&mngr->clk_mutex); + c->core_clk_state = DSI_CLK_OFF; + c->link_clk_state = DSI_CLK_OFF; + + rc = dsi_recheck_clk_state(mngr); + if (rc) { + pr_err("clock state recheck failed rc = %d\n", rc); + goto error; + } + + list_for_each_safe(pos, tmp, &mngr->client_list) { + node = list_entry(pos, struct dsi_clk_client_info, + list); + if (node == c) { + list_del(&node->list); + pr_debug("Removed device (%s)\n", node->name); + kfree(node); + break; + } + } + +error: + mutex_unlock(&mngr->clk_mutex); + pr_debug("%s: EXIT, rc = %d\n", mngr->name, rc); + return rc; +} + +void *dsi_display_clk_mngr_register(struct dsi_clk_info *info) +{ + struct dsi_clk_mngr *mngr; + int i = 0; + + if (!info) { + pr_err("Invalid params\n"); + return ERR_PTR(-EINVAL); + } + + mngr = kzalloc(sizeof(*mngr), GFP_KERNEL); + if (!mngr) { + mngr = ERR_PTR(-ENOMEM); + goto error; + } + + mutex_init(&mngr->clk_mutex); + mngr->dsi_ctrl_count = info->dsi_ctrl_count; + mngr->master_ndx = info->master_ndx; + + if (mngr->dsi_ctrl_count > MAX_DSI_CTRL) { + kfree(mngr); + return ERR_PTR(-EINVAL); + } + + for (i = 0; i < mngr->dsi_ctrl_count; i++) { + memcpy(&mngr->core_clks[i].clks, &info->c_clks[i], + sizeof(struct dsi_core_clk_info)); + memcpy(&mngr->link_clks[i].clks, &info->l_clks[i], + sizeof(struct dsi_link_clk_info)); + mngr->core_clks[i].bus_handle = info->bus_handle[i]; + mngr->ctrl_index[i] = info->ctrl_index[i]; + } + + INIT_LIST_HEAD(&mngr->client_list); + mngr->pre_clkon_cb = info->pre_clkon_cb; + mngr->post_clkon_cb = info->post_clkon_cb; + mngr->pre_clkoff_cb = info->pre_clkoff_cb; + mngr->post_clkoff_cb = info->post_clkoff_cb; + mngr->priv_data = info->priv_data; + memcpy(mngr->name, info->name, MAX_STRING_LEN); + +error: + pr_debug("EXIT, rc = %ld\n", PTR_ERR(mngr)); + return mngr; +} + +int dsi_display_clk_mngr_deregister(void *clk_mngr) +{ + int rc = 0; + struct dsi_clk_mngr *mngr = clk_mngr; + struct list_head *position = NULL; + struct list_head *tmp = NULL; + struct dsi_clk_client_info *node = NULL; + + if (!mngr) { + pr_err("Invalid params\n"); + return -EINVAL; + } + + pr_debug("%s: ENTER\n", mngr->name); + mutex_lock(&mngr->clk_mutex); + + list_for_each_safe(position, tmp, &mngr->client_list) { + node = list_entry(position, struct dsi_clk_client_info, + list); + list_del(&node->list); + pr_debug("Removed device (%s)\n", node->name); + kfree(node); + } + + rc = dsi_recheck_clk_state(mngr); + if (rc) + pr_err("failed to disable all clocks\n"); + + mutex_unlock(&mngr->clk_mutex); + pr_debug("%s: EXIT, rc = %d\n", mngr->name, rc); + kfree(mngr); + return rc; +} + diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_ctrl.c b/drivers/gpu/drm/msm/dsi-staging/dsi_ctrl.c new file mode 100644 index 000000000000..a18105587ed0 --- /dev/null +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_ctrl.c @@ -0,0 +1,2859 @@ +/* + * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "dsi-ctrl:[%s] " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include