// SPDX-License-Identifier: GPL-2.0-only/* * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ #include <linux/types.h>#include <linux/clk.h>#include <linux/completion.h>#include <linux/delay.h>#include <linux/iopoll.h>#include <linux/phy/phy.h>#include <linux/phy/phy-dp.h>#include <linux/pm_opp.h>#include <linux/rational.h>#include <linux/string_choices.h> #include <drm/display/drm_dp_helper.h>#include <drm/drm_device.h>#include <drm/drm_fixed.h>#include <drm/drm_print.h> #include "dp_reg.h"#include "dp_ctrl.h"#include "dp_link.h" #define POLLING_SLEEP_US 1000#define POLLING_TIMEOUT_US 10000 #define DP_KHZ_TO_HZ 1000#define IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES (30 * HZ / 1000) /* 30 ms */#define PSR_OPERATION_COMPLETION_TIMEOUT_JIFFIES (300 * HZ / 1000) /* 300 ms */#define WAIT_FOR_VIDEO_READY_TIMEOUT_JIFFIES (HZ / 2) #define DP_INTERRUPT_STATUS_ACK_SHIFT 1#define DP_INTERRUPT_STATUS_MASK_SHIFT 2 #define DP_INTERRUPT_STATUS1 \ (DP_INTR_AUX_XFER_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_INTERRUPT_STATUS1_ACK \ (DP_INTERRUPT_STATUS1 << DP_INTERRUPT_STATUS_ACK_SHIFT)#define DP_INTERRUPT_STATUS1_MASK \ (DP_INTERRUPT_STATUS1 << DP_INTERRUPT_STATUS_MASK_SHIFT) #define DP_INTERRUPT_STATUS2 \ (DP_INTR_READY_FOR_VIDEO | DP_INTR_IDLE_PATTERN_SENT | \ DP_INTR_FRAME_END | DP_INTR_CRC_UPDATED) #define DP_INTERRUPT_STATUS2_ACK \ (DP_INTERRUPT_STATUS2 << DP_INTERRUPT_STATUS_ACK_SHIFT)#define DP_INTERRUPT_STATUS2_MASK \ (DP_INTERRUPT_STATUS2 << DP_INTERRUPT_STATUS_MASK_SHIFT) #define DP_INTERRUPT_STATUS4 \ (PSR_UPDATE_INT | PSR_CAPTURE_INT | PSR_EXIT_INT | \ PSR_UPDATE_ERROR_INT | PSR_WAKE_ERROR_INT) #define DP_INTERRUPT_MASK4 \ (PSR_UPDATE_MASK | PSR_CAPTURE_MASK | PSR_EXIT_MASK | \ PSR_UPDATE_ERROR_MASK | PSR_WAKE_ERROR_MASK) #define DP_CTRL_INTR_READY_FOR_VIDEO BIT(0)#define DP_CTRL_INTR_IDLE_PATTERN_SENT BIT(3) #define MR_LINK_TRAINING1 0x8#define MR_LINK_SYMBOL_ERM 0x80#define MR_LINK_PRBS7 0x100#define MR_LINK_CUSTOM80 0x200#define MR_LINK_TRAINING4 0x40 enum { DP_TRAINING_NONE, DP_TRAINING_1, DP_TRAINING_2,}; struct msm_dp_tu_calc_input { u64 lclk; /* 162, 270, 540 and 810 */ u64 pclk_khz; /* in KHz */ u64 hactive; /* active h-width */ u64 hporch; /* bp + fp + pulse */ int nlanes; /* no.of.lanes */ int bpp; /* bits */ int pixel_enc; /* 444, 420, 422 */ int dsc_en; /* dsc on/off */ int async_en; /* async mode */ int fec_en; /* fec */ int compress_ratio; /* 2:1 = 200, 3:1 = 300, 3.75:1 = 375 */ int num_of_dsc_slices; /* number of slices per line */}; struct msm_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 msm_dp_ctrl_private { struct msm_dp_ctrl msm_dp_ctrl; struct drm_device *drm_dev; struct device *dev; struct drm_dp_aux *aux; struct msm_dp_link *link; void __iomem *ahb_base; void __iomem *link_base; struct phy *phy; unsigned int num_core_clks; struct clk_bulk_data *core_clks; unsigned int num_link_clks; struct clk_bulk_data *link_clks; struct clk *pixel_clk; union phy_configure_opts phy_opts; struct completion idle_comp; struct completion psr_op_comp; struct completion video_comp; u32 hw_revision; bool core_clks_on; bool link_clks_on; bool stream_clks_on;}; static inline u32 msm_dp_read_ahb(const struct msm_dp_ctrl_private *ctrl, u32 offset){ return readl_relaxed(ctrl->ahb_base + offset);} static inline void msm_dp_write_ahb(struct msm_dp_ctrl_private *ctrl, u32 offset, u32 data){ /* * To make sure phy reg writes happens before any other operation, * this function uses writel() instread of writel_relaxed() */ writel(data, ctrl->ahb_base + offset);} static inline u32 msm_dp_read_link(struct msm_dp_ctrl_private *ctrl, u32 offset){ return readl_relaxed(ctrl->link_base + offset);} static inline void msm_dp_write_link(struct msm_dp_ctrl_private *ctrl, u32 offset, u32 data){ /* * To make sure link reg writes happens before any other operation, * this function uses writel() instread of writel_relaxed() */ writel(data, ctrl->link_base + offset);} static int msm_dp_aux_link_configure(struct drm_dp_aux *aux, struct msm_dp_link_info *link){ u8 lane_count, bw_code; int err; lane_count = link->num_lanes; if (link->capabilities & DP_LINK_CAP_ENHANCED_FRAMING) lane_count |= DP_LANE_COUNT_ENHANCED_FRAME_EN; err = drm_dp_dpcd_writeb(aux, DP_LANE_COUNT_SET, lane_count); if (err < 0) return err; if (link->use_rate_set) { DRM_DEBUG_DP("using LINK_RATE_SET: 0x%02x", link->rate_set); err = drm_dp_dpcd_writeb(aux, DP_LINK_RATE_SET, link->rate_set); } else { bw_code = drm_dp_link_rate_to_bw_code(link->rate); DRM_DEBUG_DP("using LINK_BW_SET: 0x%02x", bw_code); err = drm_dp_dpcd_writeb(aux, DP_LINK_BW_SET, bw_code); } return err;} /* * NOTE: resetting DP controller will also clear any pending HPD related interrupts */void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel){ struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); u32 sw_reset; sw_reset = msm_dp_read_ahb(ctrl, REG_DP_SW_RESET); sw_reset |= DP_SW_RESET; msm_dp_write_ahb(ctrl, REG_DP_SW_RESET, sw_reset); usleep_range(1000, 1100); /* h/w recommended delay */ sw_reset &= ~DP_SW_RESET; msm_dp_write_ahb(ctrl, REG_DP_SW_RESET, sw_reset); if (!ctrl->hw_revision) ctrl->hw_revision = msm_dp_read_ahb(ctrl, REG_DP_HW_VERSION); panel->hw_revision = ctrl->hw_revision;} static u32 msm_dp_ctrl_get_aux_interrupt(struct msm_dp_ctrl_private *ctrl){ u32 intr, intr_ack; intr = msm_dp_read_ahb(ctrl, REG_DP_INTR_STATUS); intr &= ~DP_INTERRUPT_STATUS1_MASK; intr_ack = (intr & DP_INTERRUPT_STATUS1) << DP_INTERRUPT_STATUS_ACK_SHIFT; msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS, intr_ack | DP_INTERRUPT_STATUS1_MASK); return intr; } static u32 msm_dp_ctrl_get_interrupt(struct msm_dp_ctrl_private *ctrl){ u32 intr, intr_ack; intr = msm_dp_read_ahb(ctrl, REG_DP_INTR_STATUS2); intr &= ~DP_INTERRUPT_STATUS2_MASK; intr_ack = (intr & DP_INTERRUPT_STATUS2) << DP_INTERRUPT_STATUS_ACK_SHIFT; msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS2, intr_ack | DP_INTERRUPT_STATUS2_MASK); return intr;} void msm_dp_ctrl_enable_irq(struct msm_dp_ctrl *msm_dp_ctrl){ struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS, DP_INTERRUPT_STATUS1_MASK); msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS2, DP_INTERRUPT_STATUS2_MASK);} void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl){ struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS, 0x00); msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS2, 0x00);} static u32 msm_dp_ctrl_get_psr_interrupt(struct msm_dp_ctrl_private *ctrl){ u32 intr, intr_ack; intr = msm_dp_read_ahb(ctrl, REG_DP_INTR_STATUS4); intr_ack = (intr & DP_INTERRUPT_STATUS4) << DP_INTERRUPT_STATUS_ACK_SHIFT; msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS4, intr_ack); return intr;} static void msm_dp_ctrl_config_psr_interrupt(struct msm_dp_ctrl_private *ctrl){ msm_dp_write_ahb(ctrl, REG_DP_INTR_MASK4, DP_INTERRUPT_MASK4);} static void msm_dp_ctrl_psr_mainlink_enable(struct msm_dp_ctrl_private *ctrl){ u32 val; val = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL); val |= DP_MAINLINK_CTRL_ENABLE; msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, val);} static void msm_dp_ctrl_psr_mainlink_disable(struct msm_dp_ctrl_private *ctrl){ u32 val; val = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL); val &= ~DP_MAINLINK_CTRL_ENABLE; msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, val);} static void msm_dp_ctrl_mainlink_enable(struct msm_dp_ctrl_private *ctrl){ u32 mainlink_ctrl; drm_dbg_dp(ctrl->drm_dev, "enable\n"); mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL); mainlink_ctrl &= ~(DP_MAINLINK_CTRL_RESET | DP_MAINLINK_CTRL_ENABLE); msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl); mainlink_ctrl |= DP_MAINLINK_CTRL_RESET; msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl); mainlink_ctrl &= ~DP_MAINLINK_CTRL_RESET; msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl); mainlink_ctrl |= (DP_MAINLINK_CTRL_ENABLE | DP_MAINLINK_FB_BOUNDARY_SEL); msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);} static void msm_dp_ctrl_mainlink_disable(struct msm_dp_ctrl_private *ctrl){ u32 mainlink_ctrl; drm_dbg_dp(ctrl->drm_dev, "disable\n"); mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL); mainlink_ctrl &= ~DP_MAINLINK_CTRL_ENABLE; msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);} static void msm_dp_setup_peripheral_flush(struct msm_dp_ctrl_private *ctrl){ u32 mainlink_ctrl; mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL); if (ctrl->hw_revision >= DP_HW_VERSION_1_2) mainlink_ctrl |= DP_MAINLINK_FLUSH_MODE_SDE_PERIPH_UPDATE; else mainlink_ctrl |= DP_MAINLINK_FLUSH_MODE_UPDATE_SDP; msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);} static bool msm_dp_ctrl_mainlink_ready(struct msm_dp_ctrl_private *ctrl){ u32 data; int ret; /* Poll for mainlink ready status */ ret = readl_poll_timeout(ctrl->link_base + REG_DP_MAINLINK_READY, data, data & DP_MAINLINK_READY_FOR_VIDEO, POLLING_SLEEP_US, POLLING_TIMEOUT_US); if (ret < 0) { DRM_ERROR("mainlink not ready\n"); return false; } return true;} void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl){ struct msm_dp_ctrl_private *ctrl; ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); reinit_completion(&ctrl->idle_comp); msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_PUSH_IDLE); if (!wait_for_completion_timeout(&ctrl->idle_comp, IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES)) pr_warn("PUSH_IDLE pattern timedout\n"); drm_dbg_dp(ctrl->drm_dev, "mainlink off\n");} static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl, struct msm_dp_panel *msm_dp_panel){ u32 config = 0, tbd; /* * RMW: called from atomic_enable(), serialized by the DRM atomic framework. * Clear stream-specific fields before OR-ing to avoid bit accumulation. */ config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL); config &= ~(DP_CONFIGURATION_CTRL_RGB_YUV_MASK | DP_CONFIGURATION_CTRL_BPC_MASK | DP_CONFIGURATION_CTRL_SEND_VSC); if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420) config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */ tbd = msm_dp_link_get_test_bits_depth(ctrl->link, msm_dp_panel->msm_dp_mode.bpp); config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT; if (msm_dp_panel->psr_cap.version) config |= DP_CONFIGURATION_CTRL_SEND_VSC; drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config); msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);} static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl, struct msm_dp_panel *panel){ u32 config = 0; const u8 *dpcd = panel->dpcd; /* Default-> LSCLK DIV: 1/4 LCLK */ config |= (2 << DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT); /* Scrambler reset enable */ if (drm_dp_alternate_scrambler_reset_cap(dpcd)) config |= DP_CONFIGURATION_CTRL_ASSR; /* Num of Lanes */ config |= ((ctrl->link->link_params.num_lanes - 1) << DP_CONFIGURATION_CTRL_NUM_OF_LANES_SHIFT); if (drm_dp_enhanced_frame_cap(dpcd)) config |= DP_CONFIGURATION_CTRL_ENHANCED_FRAMING; config |= DP_CONFIGURATION_CTRL_P_INTERLACED; /* progressive video */ /* sync clock & static Mvid */ config |= DP_CONFIGURATION_CTRL_STATIC_DYNAMIC_CN; config |= DP_CONFIGURATION_CTRL_SYNC_ASYNC_CLK; drm_dbg_dp(ctrl->drm_dev, "link DP_CONFIGURATION_CTRL=0x%x\n", config); msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);} static void msm_dp_ctrl_lane_mapping(struct msm_dp_ctrl_private *ctrl){ u32 *lane_map = ctrl->link->lane_map; u32 ln_mapping; ln_mapping = lane_map[0] << LANE0_MAPPING_SHIFT; ln_mapping |= lane_map[1] << LANE1_MAPPING_SHIFT; ln_mapping |= lane_map[2] << LANE2_MAPPING_SHIFT; ln_mapping |= lane_map[3] << LANE3_MAPPING_SHIFT; msm_dp_write_link(ctrl, REG_DP_LOGICAL2PHYSICAL_LANE_MAPPING, ln_mapping);} static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl, struct msm_dp_panel *msm_dp_panel){ u32 colorimetry_cfg, test_bits_depth, misc_val; test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link, msm_dp_panel->msm_dp_mode.bpp); colorimetry_cfg = msm_dp_link_get_colorimetry_config(ctrl->link); misc_val = msm_dp_read_link(ctrl, REG_DP_MISC1_MISC0); /* clear bpp bits */ misc_val &= ~(0x07 << DP_MISC0_TEST_BITS_DEPTH_SHIFT); misc_val |= colorimetry_cfg << DP_MISC0_COLORIMETRY_CFG_SHIFT; misc_val |= test_bits_depth << DP_MISC0_TEST_BITS_DEPTH_SHIFT; /* Configure clock to synchronous mode */ misc_val |= DP_MISC0_SYNCHRONOUS_CLK; drm_dbg_dp(ctrl->drm_dev, "misc settings = 0x%x\n", misc_val); msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0, misc_val);} static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl, struct msm_dp_panel *panel){ msm_dp_ctrl_config_ctrl_streams(ctrl, panel); msm_dp_ctrl_config_misc1_misc0(ctrl, panel); msm_dp_panel_timing_cfg(panel, ctrl->msm_dp_ctrl.wide_bus_en);} /* * The structure and few functions present below are IP/Hardware * specific implementation. Most of the implementation will not * have coding commentsLines 1–500 of 2813. Load more
// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ #include <linux/types.h> #include <linux/clk.h> #include <linux/completion.h> #include <linux/delay.h> #include <linux/iopoll.h> #include <linux/phy/phy.h> #include <linux/phy/phy-dp.h> #include <linux/pm_opp.h> #include <linux/rational.h> #include <linux/string_choices.h> #include <drm/display/drm_dp_helper.h> #include <drm/drm_device.h> #include <drm/drm_fixed.h> #include <drm/drm_print.h> #include "dp_reg.h" #include "dp_ctrl.h" #include "dp_link.h" #define POLLING_SLEEP_US 1000 #define POLLING_TIMEOUT_US 10000 #define DP_KHZ_TO_HZ 1000 #define IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES (30 * HZ / 1000) /* 30 ms */ #define PSR_OPERATION_COMPLETION_TIMEOUT_JIFFIES (300 * HZ / 1000) /* 300 ms */ #define WAIT_FOR_VIDEO_READY_TIMEOUT_JIFFIES (HZ / 2) #define DP_INTERRUPT_STATUS_ACK_SHIFT 1 #define DP_INTERRUPT_STATUS_MASK_SHIFT 2 #define DP_INTERRUPT_STATUS1 \ (DP_INTR_AUX_XFER_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_INTERRUPT_STATUS1_ACK \ (DP_INTERRUPT_STATUS1 << DP_INTERRUPT_STATUS_ACK_SHIFT) #define DP_INTERRUPT_STATUS1_MASK \ (DP_INTERRUPT_STATUS1 << DP_INTERRUPT_STATUS_MASK_SHIFT) #define DP_INTERRUPT_STATUS2 \ (DP_INTR_READY_FOR_VIDEO | DP_INTR_IDLE_PATTERN_SENT | \ DP_INTR_FRAME_END | DP_INTR_CRC_UPDATED) #define DP_INTERRUPT_STATUS2_ACK \ (DP_INTERRUPT_STATUS2 << DP_INTERRUPT_STATUS_ACK_SHIFT) #define DP_INTERRUPT_STATUS2_MASK \ (DP_INTERRUPT_STATUS2 << DP_INTERRUPT_STATUS_MASK_SHIFT) #define DP_INTERRUPT_STATUS4 \ (PSR_UPDATE_INT | PSR_CAPTURE_INT | PSR_EXIT_INT | \ PSR_UPDATE_ERROR_INT | PSR_WAKE_ERROR_INT) #define DP_INTERRUPT_MASK4 \ (PSR_UPDATE_MASK | PSR_CAPTURE_MASK | PSR_EXIT_MASK | \ PSR_UPDATE_ERROR_MASK | PSR_WAKE_ERROR_MASK) #define DP_CTRL_INTR_READY_FOR_VIDEO BIT(0) #define DP_CTRL_INTR_IDLE_PATTERN_SENT BIT(3) #define MR_LINK_TRAINING1 0x8 #define MR_LINK_SYMBOL_ERM 0x80 #define MR_LINK_PRBS7 0x100 #define MR_LINK_CUSTOM80 0x200 #define MR_LINK_TRAINING4 0x40 enum { DP_TRAINING_NONE, DP_TRAINING_1, DP_TRAINING_2, }; struct msm_dp_tu_calc_input { u64 lclk; /* 162, 270, 540 and 810 */ u64 pclk_khz; /* in KHz */ u64 hactive; /* active h-width */ u64 hporch; /* bp + fp + pulse */ int nlanes; /* no.of.lanes */ int bpp; /* bits */ int pixel_enc; /* 444, 420, 422 */ int dsc_en; /* dsc on/off */ int async_en; /* async mode */ int fec_en; /* fec */ int compress_ratio; /* 2:1 = 200, 3:1 = 300, 3.75:1 = 375 */ int num_of_dsc_slices; /* number of slices per line */ }; struct msm_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 msm_dp_ctrl_private { struct msm_dp_ctrl msm_dp_ctrl; struct drm_device *drm_dev; struct device *dev; struct drm_dp_aux *aux; struct msm_dp_link *link; void __iomem *ahb_base; void __iomem *link_base; struct phy *phy; unsigned int num_core_clks; struct clk_bulk_data *core_clks; unsigned int num_link_clks; struct clk_bulk_data *link_clks; struct clk *pixel_clk; union phy_configure_opts phy_opts; struct completion idle_comp; struct completion psr_op_comp; struct completion video_comp; u32 hw_revision; bool core_clks_on; bool link_clks_on; bool stream_clks_on; }; static inline u32 msm_dp_read_ahb(const struct msm_dp_ctrl_private *ctrl, u32 offset) { return readl_relaxed(ctrl->ahb_base + offset); } static inline void msm_dp_write_ahb(struct msm_dp_ctrl_private *ctrl, u32 offset, u32 data) { /* * To make sure phy reg writes happens before any other operation, * this function uses writel() instread of writel_relaxed() */ writel(data, ctrl->ahb_base + offset); } static inline u32 msm_dp_read_link(struct msm_dp_ctrl_private *ctrl, u32 offset) { return readl_relaxed(ctrl->link_base + offset); } static inline void msm_dp_write_link(struct msm_dp_ctrl_private *ctrl, u32 offset, u32 data) { /* * To make sure link reg writes happens before any other operation, * this function uses writel() instread of writel_relaxed() */ writel(data, ctrl->link_base + offset); } static int msm_dp_aux_link_configure(struct drm_dp_aux *aux, struct msm_dp_link_info *link) { u8 lane_count, bw_code; int err; lane_count = link->num_lanes; if (link->capabilities & DP_LINK_CAP_ENHANCED_FRAMING) lane_count |= DP_LANE_COUNT_ENHANCED_FRAME_EN; err = drm_dp_dpcd_writeb(aux, DP_LANE_COUNT_SET, lane_count); if (err < 0) return err; if (link->use_rate_set) { DRM_DEBUG_DP("using LINK_RATE_SET: 0x%02x", link->rate_set); err = drm_dp_dpcd_writeb(aux, DP_LINK_RATE_SET, link->rate_set); } else { bw_code = drm_dp_link_rate_to_bw_code(link->rate); DRM_DEBUG_DP("using LINK_BW_SET: 0x%02x", bw_code); err = drm_dp_dpcd_writeb(aux, DP_LINK_BW_SET, bw_code); } return err; } /* * NOTE: resetting DP controller will also clear any pending HPD related interrupts */ void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel) { struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); u32 sw_reset; sw_reset = msm_dp_read_ahb(ctrl, REG_DP_SW_RESET); sw_reset |= DP_SW_RESET; msm_dp_write_ahb(ctrl, REG_DP_SW_RESET, sw_reset); usleep_range(1000, 1100); /* h/w recommended delay */ sw_reset &= ~DP_SW_RESET; msm_dp_write_ahb(ctrl, REG_DP_SW_RESET, sw_reset); if (!ctrl->hw_revision) ctrl->hw_revision = msm_dp_read_ahb(ctrl, REG_DP_HW_VERSION); panel->hw_revision = ctrl->hw_revision; } static u32 msm_dp_ctrl_get_aux_interrupt(struct msm_dp_ctrl_private *ctrl) { u32 intr, intr_ack; intr = msm_dp_read_ahb(ctrl, REG_DP_INTR_STATUS); intr &= ~DP_INTERRUPT_STATUS1_MASK; intr_ack = (intr & DP_INTERRUPT_STATUS1) << DP_INTERRUPT_STATUS_ACK_SHIFT; msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS, intr_ack | DP_INTERRUPT_STATUS1_MASK); return intr; } static u32 msm_dp_ctrl_get_interrupt(struct msm_dp_ctrl_private *ctrl) { u32 intr, intr_ack; intr = msm_dp_read_ahb(ctrl, REG_DP_INTR_STATUS2); intr &= ~DP_INTERRUPT_STATUS2_MASK; intr_ack = (intr & DP_INTERRUPT_STATUS2) << DP_INTERRUPT_STATUS_ACK_SHIFT; msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS2, intr_ack | DP_INTERRUPT_STATUS2_MASK); return intr; } void msm_dp_ctrl_enable_irq(struct msm_dp_ctrl *msm_dp_ctrl) { struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS, DP_INTERRUPT_STATUS1_MASK); msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS2, DP_INTERRUPT_STATUS2_MASK); } void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl) { struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS, 0x00); msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS2, 0x00); } static u32 msm_dp_ctrl_get_psr_interrupt(struct msm_dp_ctrl_private *ctrl) { u32 intr, intr_ack; intr = msm_dp_read_ahb(ctrl, REG_DP_INTR_STATUS4); intr_ack = (intr & DP_INTERRUPT_STATUS4) << DP_INTERRUPT_STATUS_ACK_SHIFT; msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS4, intr_ack); return intr; } static void msm_dp_ctrl_config_psr_interrupt(struct msm_dp_ctrl_private *ctrl) { msm_dp_write_ahb(ctrl, REG_DP_INTR_MASK4, DP_INTERRUPT_MASK4); } static void msm_dp_ctrl_psr_mainlink_enable(struct msm_dp_ctrl_private *ctrl) { u32 val; val = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL); val |= DP_MAINLINK_CTRL_ENABLE; msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, val); } static void msm_dp_ctrl_psr_mainlink_disable(struct msm_dp_ctrl_private *ctrl) { u32 val; val = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL); val &= ~DP_MAINLINK_CTRL_ENABLE; msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, val); } static void msm_dp_ctrl_mainlink_enable(struct msm_dp_ctrl_private *ctrl) { u32 mainlink_ctrl; drm_dbg_dp(ctrl->drm_dev, "enable\n"); mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL); mainlink_ctrl &= ~(DP_MAINLINK_CTRL_RESET | DP_MAINLINK_CTRL_ENABLE); msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl); mainlink_ctrl |= DP_MAINLINK_CTRL_RESET; msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl); mainlink_ctrl &= ~DP_MAINLINK_CTRL_RESET; msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl); mainlink_ctrl |= (DP_MAINLINK_CTRL_ENABLE | DP_MAINLINK_FB_BOUNDARY_SEL); msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl); } static void msm_dp_ctrl_mainlink_disable(struct msm_dp_ctrl_private *ctrl) { u32 mainlink_ctrl; drm_dbg_dp(ctrl->drm_dev, "disable\n"); mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL); mainlink_ctrl &= ~DP_MAINLINK_CTRL_ENABLE; msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl); } static void msm_dp_setup_peripheral_flush(struct msm_dp_ctrl_private *ctrl) { u32 mainlink_ctrl; mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL); if (ctrl->hw_revision >= DP_HW_VERSION_1_2) mainlink_ctrl |= DP_MAINLINK_FLUSH_MODE_SDE_PERIPH_UPDATE; else mainlink_ctrl |= DP_MAINLINK_FLUSH_MODE_UPDATE_SDP; msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl); } static bool msm_dp_ctrl_mainlink_ready(struct msm_dp_ctrl_private *ctrl) { u32 data; int ret; /* Poll for mainlink ready status */ ret = readl_poll_timeout(ctrl->link_base + REG_DP_MAINLINK_READY, data, data & DP_MAINLINK_READY_FOR_VIDEO, POLLING_SLEEP_US, POLLING_TIMEOUT_US); if (ret < 0) { DRM_ERROR("mainlink not ready\n"); return false; } return true; } void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl) { struct msm_dp_ctrl_private *ctrl; ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); reinit_completion(&ctrl->idle_comp); msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_PUSH_IDLE); if (!wait_for_completion_timeout(&ctrl->idle_comp, IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES)) pr_warn("PUSH_IDLE pattern timedout\n"); drm_dbg_dp(ctrl->drm_dev, "mainlink off\n"); } static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl, struct msm_dp_panel *msm_dp_panel) { u32 config = 0, tbd; /* * RMW: called from atomic_enable(), serialized by the DRM atomic framework. * Clear stream-specific fields before OR-ing to avoid bit accumulation. */ config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL); config &= ~(DP_CONFIGURATION_CTRL_RGB_YUV_MASK | DP_CONFIGURATION_CTRL_BPC_MASK | DP_CONFIGURATION_CTRL_SEND_VSC); if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420) config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */ tbd = msm_dp_link_get_test_bits_depth(ctrl->link, msm_dp_panel->msm_dp_mode.bpp); config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT; if (msm_dp_panel->psr_cap.version) config |= DP_CONFIGURATION_CTRL_SEND_VSC; drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config); msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config); } static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl, struct msm_dp_panel *panel) { u32 config = 0; const u8 *dpcd = panel->dpcd; /* Default-> LSCLK DIV: 1/4 LCLK */ config |= (2 << DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT); /* Scrambler reset enable */ if (drm_dp_alternate_scrambler_reset_cap(dpcd)) config |= DP_CONFIGURATION_CTRL_ASSR; /* Num of Lanes */ config |= ((ctrl->link->link_params.num_lanes - 1) << DP_CONFIGURATION_CTRL_NUM_OF_LANES_SHIFT); if (drm_dp_enhanced_frame_cap(dpcd)) config |= DP_CONFIGURATION_CTRL_ENHANCED_FRAMING; config |= DP_CONFIGURATION_CTRL_P_INTERLACED; /* progressive video */ /* sync clock & static Mvid */ config |= DP_CONFIGURATION_CTRL_STATIC_DYNAMIC_CN; config |= DP_CONFIGURATION_CTRL_SYNC_ASYNC_CLK; drm_dbg_dp(ctrl->drm_dev, "link DP_CONFIGURATION_CTRL=0x%x\n", config); msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config); } static void msm_dp_ctrl_lane_mapping(struct msm_dp_ctrl_private *ctrl) { u32 *lane_map = ctrl->link->lane_map; u32 ln_mapping; ln_mapping = lane_map[0] << LANE0_MAPPING_SHIFT; ln_mapping |= lane_map[1] << LANE1_MAPPING_SHIFT; ln_mapping |= lane_map[2] << LANE2_MAPPING_SHIFT; ln_mapping |= lane_map[3] << LANE3_MAPPING_SHIFT; msm_dp_write_link(ctrl, REG_DP_LOGICAL2PHYSICAL_LANE_MAPPING, ln_mapping); } static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl, struct msm_dp_panel *msm_dp_panel) { u32 colorimetry_cfg, test_bits_depth, misc_val; test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link, msm_dp_panel->msm_dp_mode.bpp); colorimetry_cfg = msm_dp_link_get_colorimetry_config(ctrl->link); misc_val = msm_dp_read_link(ctrl, REG_DP_MISC1_MISC0); /* clear bpp bits */ misc_val &= ~(0x07 << DP_MISC0_TEST_BITS_DEPTH_SHIFT); misc_val |= colorimetry_cfg << DP_MISC0_COLORIMETRY_CFG_SHIFT; misc_val |= test_bits_depth << DP_MISC0_TEST_BITS_DEPTH_SHIFT; /* Configure clock to synchronous mode */ misc_val |= DP_MISC0_SYNCHRONOUS_CLK; drm_dbg_dp(ctrl->drm_dev, "misc settings = 0x%x\n", misc_val); msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0, misc_val); } static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl, struct msm_dp_panel *panel) { msm_dp_ctrl_config_ctrl_streams(ctrl, panel); msm_dp_ctrl_config_misc1_misc0(ctrl, panel); msm_dp_panel_timing_cfg(panel, ctrl->msm_dp_ctrl.wide_bus_en); } /* * The structure and few functions present below are IP/Hardware * specific implementation. Most of the implementation will not * have coding comments