2813 lines · 74.8 KB · C
RawDownload
// 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

Lines 1–500 of 2813.