Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,379 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
*/
#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__
#include <linux/platform_device.h>
#include <drm/display/drm_dp_helper.h>
#include <drm/drm_edid.h>
#include "dp_audio.h"
#include "dp_drm.h"
#include "dp_panel.h"
#include "dp_reg.h"
#include "dp_display.h"
#include "dp_utils.h"
struct msm_dp_audio_private {
struct platform_device *pdev;
struct drm_device *drm_dev;
void __iomem *link_base;
u32 channels;
struct msm_dp_audio msm_dp_audio;
};
static inline u32 msm_dp_read_link(struct msm_dp_audio_private *audio, u32 offset)
{
return readl_relaxed(audio->link_base + offset);
}
static inline void msm_dp_write_link(struct msm_dp_audio_private *audio,
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, audio->link_base + offset);
}
static void msm_dp_audio_stream_sdp(struct msm_dp_audio_private *audio)
{
struct dp_sdp_header sdp_hdr = {
.HB0 = 0x00,
.HB1 = 0x02,
.HB2 = 0x00,
.HB3 = audio->channels - 1,
};
u32 header[2];
msm_dp_utils_pack_sdp_header(&sdp_hdr, header);
msm_dp_write_link(audio, MMSS_DP_AUDIO_STREAM_0, header[0]);
msm_dp_write_link(audio, MMSS_DP_AUDIO_STREAM_1, header[1]);
}
static void msm_dp_audio_timestamp_sdp(struct msm_dp_audio_private *audio)
{
struct dp_sdp_header sdp_hdr = {
.HB0 = 0x00,
.HB1 = 0x01,
.HB2 = 0x17,
.HB3 = 0x0 | (0x11 << 2),
};
u32 header[2];
msm_dp_utils_pack_sdp_header(&sdp_hdr, header);
msm_dp_write_link(audio, MMSS_DP_AUDIO_TIMESTAMP_0, header[0]);
msm_dp_write_link(audio, MMSS_DP_AUDIO_TIMESTAMP_1, header[1]);
}
static void msm_dp_audio_infoframe_sdp(struct msm_dp_audio_private *audio)
{
struct dp_sdp_header sdp_hdr = {
.HB0 = 0x00,
.HB1 = 0x84,
.HB2 = 0x1b,
.HB3 = 0x0 | (0x11 << 2),
};
u32 header[2];
msm_dp_utils_pack_sdp_header(&sdp_hdr, header);
msm_dp_write_link(audio, MMSS_DP_AUDIO_INFOFRAME_0, header[0]);
msm_dp_write_link(audio, MMSS_DP_AUDIO_INFOFRAME_1, header[1]);
}
static void msm_dp_audio_copy_management_sdp(struct msm_dp_audio_private *audio)
{
struct dp_sdp_header sdp_hdr = {
.HB0 = 0x00,
.HB1 = 0x05,
.HB2 = 0x0f,
.HB3 = 0x00,
};
u32 header[2];
msm_dp_utils_pack_sdp_header(&sdp_hdr, header);
msm_dp_write_link(audio, MMSS_DP_AUDIO_COPYMANAGEMENT_0, header[0]);
msm_dp_write_link(audio, MMSS_DP_AUDIO_COPYMANAGEMENT_1, header[1]);
}
static void msm_dp_audio_isrc_sdp(struct msm_dp_audio_private *audio)
{
struct dp_sdp_header sdp_hdr = {
.HB0 = 0x00,
.HB1 = 0x06,
.HB2 = 0x0f,
.HB3 = 0x00,
};
u32 header[2];
u32 reg;
/* XXX: is it necessary to preserve this field? */
reg = msm_dp_read_link(audio, MMSS_DP_AUDIO_ISRC_1);
sdp_hdr.HB3 = FIELD_GET(HEADER_3_MASK, reg);
msm_dp_utils_pack_sdp_header(&sdp_hdr, header);
msm_dp_write_link(audio, MMSS_DP_AUDIO_ISRC_0, header[0]);
msm_dp_write_link(audio, MMSS_DP_AUDIO_ISRC_1, header[1]);
}
static void msm_dp_audio_config_sdp(struct msm_dp_audio_private *audio)
{
u32 sdp_cfg, sdp_cfg2;
sdp_cfg = msm_dp_read_link(audio, MMSS_DP_SDP_CFG);
/* 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);
drm_dbg_dp(audio->drm_dev, "sdp_cfg = 0x%x\n", sdp_cfg);
msm_dp_write_link(audio, MMSS_DP_SDP_CFG, sdp_cfg);
sdp_cfg2 = msm_dp_read_link(audio, 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);
drm_dbg_dp(audio->drm_dev, "sdp_cfg2 = 0x%x\n", sdp_cfg2);
msm_dp_write_link(audio, MMSS_DP_SDP_CFG2, sdp_cfg2);
}
static void msm_dp_audio_setup_sdp(struct msm_dp_audio_private *audio)
{
msm_dp_audio_config_sdp(audio);
msm_dp_audio_stream_sdp(audio);
msm_dp_audio_timestamp_sdp(audio);
msm_dp_audio_infoframe_sdp(audio);
msm_dp_audio_copy_management_sdp(audio);
msm_dp_audio_isrc_sdp(audio);
}
static void msm_dp_audio_setup_acr(struct msm_dp_audio_private *audio)
{
u32 select, acr_ctrl;
switch (audio->msm_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:
drm_dbg_dp(audio->drm_dev, "Unknown link rate\n");
select = 0;
break;
}
acr_ctrl = select << 4 | BIT(31) | BIT(8) | BIT(14);
drm_dbg_dp(audio->drm_dev, "select: %#x, acr_ctrl: %#x\n",
select, acr_ctrl);
msm_dp_write_link(audio, MMSS_DP_AUDIO_ACR_CTRL, acr_ctrl);
}
static void msm_dp_audio_safe_to_exit_level(struct msm_dp_audio_private *audio)
{
u32 safe_to_exit_level, mainlink_levels;
switch (audio->msm_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:
safe_to_exit_level = 14;
drm_dbg_dp(audio->drm_dev,
"setting the default safe_to_exit_level = %u\n",
safe_to_exit_level);
break;
}
mainlink_levels = msm_dp_read_link(audio, REG_DP_MAINLINK_LEVELS);
mainlink_levels &= 0xFE0;
mainlink_levels |= safe_to_exit_level;
drm_dbg_dp(audio->drm_dev,
"mainlink_level = 0x%x, safe_to_exit_level = 0x%x\n",
mainlink_levels, safe_to_exit_level);
msm_dp_write_link(audio, REG_DP_MAINLINK_LEVELS, mainlink_levels);
}
static void msm_dp_audio_enable(struct msm_dp_audio_private *audio, bool enable)
{
u32 audio_ctrl;
audio_ctrl = msm_dp_read_link(audio, MMSS_DP_AUDIO_CFG);
if (enable)
audio_ctrl |= BIT(0);
else
audio_ctrl &= ~BIT(0);
drm_dbg_dp(audio->drm_dev, "dp_audio_cfg = 0x%x\n", audio_ctrl);
msm_dp_write_link(audio, MMSS_DP_AUDIO_CFG, audio_ctrl);
/* make sure audio engine is disabled */
wmb();
}
static struct msm_dp_audio_private *msm_dp_audio_get_data(struct msm_dp *msm_dp_display)
{
struct msm_dp_audio *msm_dp_audio;
msm_dp_audio = msm_dp_display->msm_dp_audio;
if (!msm_dp_audio) {
DRM_ERROR("invalid msm_dp_audio data\n");
return ERR_PTR(-EINVAL);
}
return container_of(msm_dp_audio, struct msm_dp_audio_private, msm_dp_audio);
}
int msm_dp_audio_prepare(struct drm_bridge *bridge,
struct drm_connector *connector,
struct hdmi_codec_daifmt *daifmt,
struct hdmi_codec_params *params)
{
int rc = 0;
struct msm_dp_audio_private *audio;
struct msm_dp *msm_dp_display;
msm_dp_display = to_dp_bridge(bridge)->msm_dp_display;
/*
* there could be cases where sound card can be opened even
* before OR even when DP is not connected . This can cause
* unclocked access as the audio subsystem relies on the DP
* driver to maintain the correct state of clocks. To protect
* such cases check for connection status and bail out if not
* connected.
*/
if (!msm_dp_display->power_on) {
rc = -EINVAL;
goto end;
}
audio = msm_dp_audio_get_data(msm_dp_display);
if (IS_ERR(audio)) {
rc = PTR_ERR(audio);
goto end;
}
audio->channels = params->channels;
msm_dp_audio_setup_sdp(audio);
msm_dp_audio_setup_acr(audio);
msm_dp_audio_safe_to_exit_level(audio);
msm_dp_audio_enable(audio, true);
msm_dp_display_signal_audio_start(msm_dp_display);
msm_dp_display->audio_enabled = true;
end:
return rc;
}
void msm_dp_audio_shutdown(struct drm_bridge *bridge,
struct drm_connector *connecter)
{
struct msm_dp_audio_private *audio;
struct msm_dp *msm_dp_display;
msm_dp_display = to_dp_bridge(bridge)->msm_dp_display;
audio = msm_dp_audio_get_data(msm_dp_display);
if (IS_ERR(audio)) {
DRM_ERROR("failed to get audio data\n");
return;
}
/*
* if audio was not enabled there is no need
* to execute the shutdown and we can bail out early.
* This also makes sure that we dont cause an unclocked
* access when audio subsystem calls this without DP being
* connected. is_connected cannot be used here as its set
* to false earlier than this call
*/
if (!msm_dp_display->audio_enabled)
return;
msm_dp_audio_enable(audio, false);
/* signal the dp display to safely shutdown clocks */
msm_dp_display_signal_audio_complete(msm_dp_display);
}
struct msm_dp_audio *msm_dp_audio_get(struct platform_device *pdev,
void __iomem *link_base)
{
int rc = 0;
struct msm_dp_audio_private *audio;
struct msm_dp_audio *msm_dp_audio;
if (!pdev) {
DRM_ERROR("invalid input\n");
rc = -EINVAL;
goto error;
}
audio = devm_kzalloc(&pdev->dev, sizeof(*audio), GFP_KERNEL);
if (!audio) {
rc = -ENOMEM;
goto error;
}
audio->pdev = pdev;
audio->link_base = link_base;
msm_dp_audio = &audio->msm_dp_audio;
return msm_dp_audio;
error:
return ERR_PTR(rc);
}
void msm_dp_audio_put(struct msm_dp_audio *msm_dp_audio)
{
struct msm_dp_audio_private *audio;
if (!msm_dp_audio)
return;
audio = container_of(msm_dp_audio, struct msm_dp_audio_private, msm_dp_audio);
devm_kfree(&audio->pdev->dev, audio);
}
@@ -0,0 +1,57 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
*/
#ifndef _DP_AUDIO_H_
#define _DP_AUDIO_H_
#include <linux/platform_device.h>
#include <sound/hdmi-codec.h>
struct drm_bridge;
/**
* struct msm_dp_audio
* @lane_count: number of lanes configured in current session
* @bw_code: link rate's bandwidth code for current session
*/
struct msm_dp_audio {
u32 lane_count;
u32 bw_code;
};
/**
* msm_dp_audio_get()
*
* Creates and instance of dp audio.
*
* @pdev: caller's platform device instance.
* @link_base: pointer to the msm_dp_link resource.
*
* Returns the error code in case of failure, otherwize
* an instance of newly created msm_dp_module.
*/
struct msm_dp_audio *msm_dp_audio_get(struct platform_device *pdev,
void __iomem *link_base);
/**
* msm_dp_audio_put()
*
* Cleans the msm_dp_audio instance.
*
* @msm_dp_audio: an instance of msm_dp_audio.
*/
void msm_dp_audio_put(struct msm_dp_audio *msm_dp_audio);
int msm_dp_audio_prepare(struct drm_bridge *bridge,
struct drm_connector *connector,
struct hdmi_codec_daifmt *daifmt,
struct hdmi_codec_params *params);
void msm_dp_audio_shutdown(struct drm_bridge *bridge,
struct drm_connector *connector);
#endif /* _DP_AUDIO_H_ */
+729
View File
@@ -0,0 +1,729 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
*/
#include <linux/delay.h>
#include <linux/iopoll.h>
#include <linux/phy/phy.h>
#include <drm/drm_print.h>
#include "dp_reg.h"
#include "dp_aux.h"
enum msm_dp_aux_err {
DP_AUX_ERR_NONE,
DP_AUX_ERR_ADDR,
DP_AUX_ERR_TOUT,
DP_AUX_ERR_NACK,
DP_AUX_ERR_DEFER,
DP_AUX_ERR_NACK_DEFER,
DP_AUX_ERR_PHY,
};
struct msm_dp_aux_private {
struct device *dev;
void __iomem *aux_base;
struct phy *phy;
struct mutex mutex;
struct completion comp;
enum msm_dp_aux_err aux_error_num;
u32 retry_cnt;
bool cmd_busy;
bool native;
bool read;
bool no_send_addr;
bool no_send_stop;
bool initted;
bool is_edp;
bool enable_xfers;
u32 offset;
u32 segment;
struct drm_dp_aux msm_dp_aux;
};
static inline u32 msm_dp_read_aux(struct msm_dp_aux_private *aux, u32 offset)
{
return readl_relaxed(aux->aux_base + offset);
}
static inline void msm_dp_write_aux(struct msm_dp_aux_private *aux,
u32 offset, u32 data)
{
/*
* To make sure aux reg writes happens before any other operation,
* this function uses writel() instread of writel_relaxed()
*/
writel(data, aux->aux_base + offset);
}
static void msm_dp_aux_clear_hw_interrupts(struct msm_dp_aux_private *aux)
{
msm_dp_read_aux(aux, REG_DP_PHY_AUX_INTERRUPT_STATUS);
msm_dp_write_aux(aux, REG_DP_PHY_AUX_INTERRUPT_CLEAR, 0x1f);
msm_dp_write_aux(aux, REG_DP_PHY_AUX_INTERRUPT_CLEAR, 0x9f);
msm_dp_write_aux(aux, REG_DP_PHY_AUX_INTERRUPT_CLEAR, 0);
}
/*
* NOTE: resetting AUX controller will also clear any pending HPD related interrupts
*/
static void msm_dp_aux_reset(struct msm_dp_aux_private *aux)
{
u32 aux_ctrl;
aux_ctrl = msm_dp_read_aux(aux, REG_DP_AUX_CTRL);
aux_ctrl |= DP_AUX_CTRL_RESET;
msm_dp_write_aux(aux, REG_DP_AUX_CTRL, aux_ctrl);
usleep_range(1000, 1100); /* h/w recommended delay */
aux_ctrl &= ~DP_AUX_CTRL_RESET;
msm_dp_write_aux(aux, REG_DP_AUX_CTRL, aux_ctrl);
}
static void msm_dp_aux_enable(struct msm_dp_aux_private *aux)
{
u32 aux_ctrl;
aux_ctrl = msm_dp_read_aux(aux, REG_DP_AUX_CTRL);
msm_dp_write_aux(aux, REG_DP_TIMEOUT_COUNT, 0xffff);
msm_dp_write_aux(aux, REG_DP_AUX_LIMITS, 0xffff);
aux_ctrl |= DP_AUX_CTRL_ENABLE;
msm_dp_write_aux(aux, REG_DP_AUX_CTRL, aux_ctrl);
}
static void msm_dp_aux_disable(struct msm_dp_aux_private *aux)
{
u32 aux_ctrl;
aux_ctrl = msm_dp_read_aux(aux, REG_DP_AUX_CTRL);
aux_ctrl &= ~DP_AUX_CTRL_ENABLE;
msm_dp_write_aux(aux, REG_DP_AUX_CTRL, aux_ctrl);
}
static int msm_dp_aux_wait_for_hpd_connect_state(struct msm_dp_aux_private *aux,
unsigned long wait_us)
{
u32 state;
/* poll for hpd connected status every 2ms and timeout after wait_us */
return readl_poll_timeout(aux->aux_base +
REG_DP_DP_HPD_INT_STATUS,
state, state & DP_DP_HPD_STATE_STATUS_CONNECTED,
min(wait_us, 2000), wait_us);
}
#define MAX_AUX_RETRIES 5
static ssize_t msm_dp_aux_write(struct msm_dp_aux_private *aux,
struct drm_dp_aux_msg *msg)
{
u8 data[4];
u32 reg;
ssize_t len;
u8 *msgdata = msg->buffer;
int const AUX_CMD_FIFO_LEN = 128;
int i = 0;
if (aux->read)
len = 0;
else
len = msg->size;
/*
* cmd fifo only has depth of 144 bytes
* limit buf length to 128 bytes here
*/
if (len > AUX_CMD_FIFO_LEN - 4) {
DRM_ERROR("buf size greater than allowed size of 128 bytes\n");
return -EINVAL;
}
/* 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; /* addr[15:8] */
data[2] = msg->address; /* addr[7:0] */
data[3] = msg->size - 1; /* len[7:0] */
for (i = 0; i < len + 4; i++) {
reg = (i < 4) ? data[i] : msgdata[i - 4];
reg <<= DP_AUX_DATA_OFFSET;
reg &= DP_AUX_DATA_MASK;
reg |= DP_AUX_DATA_WRITE;
/* index = 0, write */
if (i == 0)
reg |= DP_AUX_DATA_INDEX_WRITE;
msm_dp_write_aux(aux, REG_DP_AUX_DATA, reg);
}
msm_dp_write_aux(aux, REG_DP_AUX_TRANS_CTRL, 0);
msm_dp_aux_clear_hw_interrupts(aux);
reg = 0; /* Transaction number == 1 */
if (!aux->native) { /* i2c */
reg |= DP_AUX_TRANS_CTRL_I2C;
if (aux->no_send_addr)
reg |= DP_AUX_TRANS_CTRL_NO_SEND_ADDR;
if (aux->no_send_stop)
reg |= DP_AUX_TRANS_CTRL_NO_SEND_STOP;
}
reg |= DP_AUX_TRANS_CTRL_GO;
msm_dp_write_aux(aux, REG_DP_AUX_TRANS_CTRL, reg);
return len;
}
static ssize_t msm_dp_aux_cmd_fifo_tx(struct msm_dp_aux_private *aux,
struct drm_dp_aux_msg *msg)
{
ssize_t ret;
unsigned long time_left;
reinit_completion(&aux->comp);
ret = msm_dp_aux_write(aux, msg);
if (ret < 0)
return ret;
time_left = wait_for_completion_timeout(&aux->comp,
msecs_to_jiffies(250));
if (!time_left)
return -ETIMEDOUT;
return ret;
}
static ssize_t msm_dp_aux_cmd_fifo_rx(struct msm_dp_aux_private *aux,
struct drm_dp_aux_msg *msg)
{
u32 data;
u8 *dp;
u32 i, actual_i;
u32 len = msg->size;
data = msm_dp_read_aux(aux, REG_DP_AUX_TRANS_CTRL);
data &= ~DP_AUX_TRANS_CTRL_GO;
msm_dp_write_aux(aux, REG_DP_AUX_TRANS_CTRL, data);
data = DP_AUX_DATA_INDEX_WRITE; /* INDEX_WRITE */
data |= DP_AUX_DATA_READ; /* read */
msm_dp_write_aux(aux, REG_DP_AUX_DATA, data);
dp = msg->buffer;
/* discard first byte */
data = msm_dp_read_aux(aux, REG_DP_AUX_DATA);
for (i = 0; i < len; i++) {
data = msm_dp_read_aux(aux, REG_DP_AUX_DATA);
*dp++ = (u8)((data >> DP_AUX_DATA_OFFSET) & 0xff);
actual_i = (data >> DP_AUX_DATA_INDEX_OFFSET) & 0xFF;
if (i != actual_i)
break;
}
return i;
}
static void msm_dp_aux_update_offset_and_segment(struct msm_dp_aux_private *aux,
struct drm_dp_aux_msg *input_msg)
{
u32 edid_address = 0x50;
u32 segment_address = 0x30;
bool i2c_read = input_msg->request &
(DP_AUX_I2C_READ & DP_AUX_NATIVE_READ);
u8 *data;
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;
}
/**
* msm_dp_aux_transfer_helper() - helper function for EDID read transactions
*
* @aux: DP AUX private structure
* @input_msg: input message from DRM upstream APIs
* @send_seg: send the segment to sink
*
* 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 msm_dp_aux_transfer_helper(struct msm_dp_aux_private *aux,
struct drm_dp_aux_msg *input_msg,
bool send_seg)
{
struct drm_dp_aux_msg helper_msg;
u32 message_size = 0x10;
u32 segment_address = 0x30;
u32 const edid_block_length = 0x80;
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;
/*
* Sending the segment value and EDID offset will be performed
* from the DRM upstream EDID driver for each block. Avoid
* duplicate AUX transactions related to this while reading the
* first 16 bytes of each block.
*/
if (!(aux->offset % edid_block_length) || !send_seg)
goto end;
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.
*/
if (aux->segment) {
memset(&helper_msg, 0, sizeof(helper_msg));
helper_msg.address = segment_address;
helper_msg.buffer = &aux->segment;
helper_msg.size = 1;
msm_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;
msm_dp_aux_cmd_fifo_tx(aux, &helper_msg);
end:
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 msm_dp_aux_transfer(struct drm_dp_aux *msm_dp_aux,
struct drm_dp_aux_msg *msg)
{
ssize_t ret;
int const aux_cmd_native_max = 16;
int const aux_cmd_i2c_max = 128;
struct msm_dp_aux_private *aux;
aux = container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux);
aux->native = msg->request & (DP_AUX_NATIVE_WRITE & DP_AUX_NATIVE_READ);
/* Ignore address only message */
if (msg->size == 0 || !msg->buffer) {
msg->reply = aux->native ?
DP_AUX_NATIVE_REPLY_ACK : DP_AUX_I2C_REPLY_ACK;
return msg->size;
}
/* msg sanity check */
if ((aux->native && msg->size > aux_cmd_native_max) ||
msg->size > aux_cmd_i2c_max) {
DRM_ERROR("%s: invalid msg: size(%zu), request(%x)\n",
__func__, msg->size, msg->request);
return -EINVAL;
}
ret = pm_runtime_resume_and_get(msm_dp_aux->dev);
if (ret)
return ret;
mutex_lock(&aux->mutex);
if (!aux->initted) {
ret = -EIO;
goto exit;
}
/*
* If we're using DP and an external display isn't connected then the
* transfer won't succeed. Return right away. If we don't do this we
* can end up with long timeouts if someone tries to access the DP AUX
* character device when no DP device is connected.
*/
if (!aux->is_edp && !aux->enable_xfers) {
ret = -ENXIO;
goto exit;
}
msm_dp_aux_update_offset_and_segment(aux, msg);
msm_dp_aux_transfer_helper(aux, msg, true);
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 = msm_dp_aux_cmd_fifo_tx(aux, msg);
if (ret < 0) {
if (aux->native) {
aux->retry_cnt++;
if (!(aux->retry_cnt % MAX_AUX_RETRIES))
phy_calibrate(aux->phy);
}
/* reset aux if link is in connected state */
if (msm_dp_aux_is_link_connected(msm_dp_aux))
msm_dp_aux_reset(aux);
} else {
aux->retry_cnt = 0;
switch (aux->aux_error_num) {
case DP_AUX_ERR_NONE:
if (aux->read)
ret = msm_dp_aux_cmd_fifo_rx(aux, msg);
msg->reply = aux->native ? DP_AUX_NATIVE_REPLY_ACK : DP_AUX_I2C_REPLY_ACK;
break;
case DP_AUX_ERR_DEFER:
msg->reply = aux->native ? DP_AUX_NATIVE_REPLY_DEFER : DP_AUX_I2C_REPLY_DEFER;
break;
case DP_AUX_ERR_PHY:
case DP_AUX_ERR_ADDR:
case DP_AUX_ERR_NACK:
case DP_AUX_ERR_NACK_DEFER:
msg->reply = aux->native ? DP_AUX_NATIVE_REPLY_NACK : DP_AUX_I2C_REPLY_NACK;
break;
case DP_AUX_ERR_TOUT:
ret = -ETIMEDOUT;
break;
}
}
aux->cmd_busy = false;
exit:
mutex_unlock(&aux->mutex);
pm_runtime_put_sync(msm_dp_aux->dev);
return ret;
}
irqreturn_t msm_dp_aux_isr(struct drm_dp_aux *msm_dp_aux, u32 isr)
{
struct msm_dp_aux_private *aux;
if (!msm_dp_aux) {
DRM_ERROR("invalid input\n");
return IRQ_NONE;
}
aux = container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux);
if (!aux->cmd_busy) {
DRM_ERROR("Unexpected DP AUX IRQ %#010x when not busy\n", isr);
return IRQ_NONE;
}
/*
* The logic below assumes only one error bit is set (other than "done"
* which can apparently be set at the same time as some of the other
* bits). Warn if more than one get set so we know we need to improve
* the logic.
*/
if (hweight32(isr & ~DP_INTR_AUX_XFER_DONE) > 1)
DRM_WARN("Some DP AUX interrupts unhandled: %#010x\n", isr);
if (isr & DP_INTR_AUX_ERROR) {
aux->aux_error_num = DP_AUX_ERR_PHY;
msm_dp_aux_clear_hw_interrupts(aux);
} else if (isr & DP_INTR_NACK_DEFER) {
aux->aux_error_num = DP_AUX_ERR_NACK_DEFER;
} 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;
} else if (!aux->native && (isr & DP_INTR_I2C_NACK)) {
aux->aux_error_num = DP_AUX_ERR_NACK;
} else if (!aux->native && (isr & DP_INTR_I2C_DEFER)) {
if (isr & DP_INTR_AUX_XFER_DONE)
aux->aux_error_num = DP_AUX_ERR_NACK;
else
aux->aux_error_num = DP_AUX_ERR_DEFER;
} else if (isr & DP_INTR_AUX_XFER_DONE) {
aux->aux_error_num = DP_AUX_ERR_NONE;
} else {
DRM_WARN("Unexpected interrupt: %#010x\n", isr);
return IRQ_NONE;
}
complete(&aux->comp);
return IRQ_HANDLED;
}
void msm_dp_aux_enable_xfers(struct drm_dp_aux *msm_dp_aux, bool enabled)
{
struct msm_dp_aux_private *aux;
aux = container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux);
aux->enable_xfers = enabled;
}
void msm_dp_aux_reconfig(struct drm_dp_aux *msm_dp_aux)
{
struct msm_dp_aux_private *aux;
aux = container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux);
phy_calibrate(aux->phy);
msm_dp_aux_reset(aux);
}
void msm_dp_aux_init(struct drm_dp_aux *msm_dp_aux)
{
struct msm_dp_aux_private *aux;
if (!msm_dp_aux) {
DRM_ERROR("invalid input\n");
return;
}
aux = container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux);
mutex_lock(&aux->mutex);
msm_dp_aux_enable(aux);
aux->retry_cnt = 0;
aux->initted = true;
mutex_unlock(&aux->mutex);
}
void msm_dp_aux_deinit(struct drm_dp_aux *msm_dp_aux)
{
struct msm_dp_aux_private *aux;
aux = container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux);
mutex_lock(&aux->mutex);
aux->initted = false;
msm_dp_aux_disable(aux);
mutex_unlock(&aux->mutex);
}
int msm_dp_aux_register(struct drm_dp_aux *msm_dp_aux)
{
int ret;
if (!msm_dp_aux) {
DRM_ERROR("invalid input\n");
return -EINVAL;
}
ret = drm_dp_aux_register(msm_dp_aux);
if (ret) {
DRM_ERROR("%s: failed to register drm aux: %d\n", __func__,
ret);
return ret;
}
return 0;
}
void msm_dp_aux_unregister(struct drm_dp_aux *msm_dp_aux)
{
drm_dp_aux_unregister(msm_dp_aux);
}
static int msm_dp_wait_hpd_asserted(struct drm_dp_aux *msm_dp_aux,
unsigned long wait_us)
{
int ret;
struct msm_dp_aux_private *aux;
aux = container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux);
ret = pm_runtime_resume_and_get(aux->dev);
if (ret)
return ret;
ret = msm_dp_aux_wait_for_hpd_connect_state(aux, wait_us);
pm_runtime_put_sync(aux->dev);
return ret;
}
void msm_dp_aux_hpd_enable(struct drm_dp_aux *msm_dp_aux)
{
struct msm_dp_aux_private *aux =
container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux);
u32 reg;
/* Configure REFTIMER and enable it */
reg = msm_dp_read_aux(aux, REG_DP_DP_HPD_REFTIMER);
reg |= DP_DP_HPD_REFTIMER_ENABLE;
msm_dp_write_aux(aux, REG_DP_DP_HPD_REFTIMER, reg);
/* Enable HPD */
msm_dp_write_aux(aux, REG_DP_DP_HPD_CTRL, DP_DP_HPD_CTRL_HPD_EN);
}
void msm_dp_aux_hpd_disable(struct drm_dp_aux *msm_dp_aux)
{
struct msm_dp_aux_private *aux =
container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux);
u32 reg;
reg = msm_dp_read_aux(aux, REG_DP_DP_HPD_REFTIMER);
reg &= ~DP_DP_HPD_REFTIMER_ENABLE;
msm_dp_write_aux(aux, REG_DP_DP_HPD_REFTIMER, reg);
msm_dp_write_aux(aux, REG_DP_DP_HPD_CTRL, 0);
}
void msm_dp_aux_hpd_intr_enable(struct drm_dp_aux *msm_dp_aux)
{
struct msm_dp_aux_private *aux =
container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux);
u32 reg;
reg = msm_dp_read_aux(aux, REG_DP_DP_HPD_INT_MASK);
reg |= DP_DP_HPD_INT_MASK;
msm_dp_write_aux(aux, REG_DP_DP_HPD_INT_MASK,
reg & DP_DP_HPD_INT_MASK);
}
void msm_dp_aux_hpd_intr_disable(struct drm_dp_aux *msm_dp_aux)
{
struct msm_dp_aux_private *aux =
container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux);
u32 reg;
reg = msm_dp_read_aux(aux, REG_DP_DP_HPD_INT_MASK);
reg &= ~DP_DP_HPD_INT_MASK;
msm_dp_write_aux(aux, REG_DP_DP_HPD_INT_MASK,
reg & DP_DP_HPD_INT_MASK);
}
u32 msm_dp_aux_get_hpd_intr_status(struct drm_dp_aux *msm_dp_aux)
{
struct msm_dp_aux_private *aux =
container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux);
int isr, mask;
isr = msm_dp_read_aux(aux, REG_DP_DP_HPD_INT_STATUS);
msm_dp_write_aux(aux, REG_DP_DP_HPD_INT_ACK,
(isr & DP_DP_HPD_INT_MASK));
mask = msm_dp_read_aux(aux, REG_DP_DP_HPD_INT_MASK);
/*
* We only want to return interrupts that are unmasked to the caller.
* However, the interrupt status field also contains other
* informational bits about the HPD state status, so we only mask
* out the part of the register that tells us about which interrupts
* are pending.
*/
return isr & (mask | ~DP_DP_HPD_INT_MASK);
}
u32 msm_dp_aux_is_link_connected(struct drm_dp_aux *msm_dp_aux)
{
struct msm_dp_aux_private *aux =
container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux);
u32 status;
status = msm_dp_read_aux(aux, REG_DP_DP_HPD_INT_STATUS);
status >>= DP_DP_HPD_STATE_STATUS_BITS_SHIFT;
status &= DP_DP_HPD_STATE_STATUS_BITS_MASK;
return status;
}
struct drm_dp_aux *msm_dp_aux_get(struct device *dev,
struct phy *phy,
bool is_edp,
void __iomem *aux_base)
{
struct msm_dp_aux_private *aux;
aux = devm_kzalloc(dev, sizeof(*aux), GFP_KERNEL);
if (!aux)
return ERR_PTR(-ENOMEM);
init_completion(&aux->comp);
aux->cmd_busy = false;
aux->is_edp = is_edp;
mutex_init(&aux->mutex);
aux->dev = dev;
aux->phy = phy;
aux->retry_cnt = 0;
aux->aux_base = aux_base;
/*
* Use the drm_dp_aux_init() to use the aux adapter
* before registering AUX with the DRM device so that
* msm eDP panel can be detected by generic_dep_panel_probe().
*/
aux->msm_dp_aux.name = "dpu_dp_aux";
aux->msm_dp_aux.dev = dev;
aux->msm_dp_aux.transfer = msm_dp_aux_transfer;
aux->msm_dp_aux.wait_hpd_asserted = msm_dp_wait_hpd_asserted;
drm_dp_aux_init(&aux->msm_dp_aux);
return &aux->msm_dp_aux;
}
void msm_dp_aux_put(struct drm_dp_aux *msm_dp_aux)
{
struct msm_dp_aux_private *aux;
if (!msm_dp_aux)
return;
aux = container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux);
mutex_destroy(&aux->mutex);
devm_kfree(aux->dev, aux);
}
@@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
*/
#ifndef _DP_AUX_H_
#define _DP_AUX_H_
#include <drm/display/drm_dp_helper.h>
int msm_dp_aux_register(struct drm_dp_aux *msm_dp_aux);
void msm_dp_aux_unregister(struct drm_dp_aux *msm_dp_aux);
irqreturn_t msm_dp_aux_isr(struct drm_dp_aux *msm_dp_aux, u32 isr);
void msm_dp_aux_enable_xfers(struct drm_dp_aux *msm_dp_aux, bool enabled);
void msm_dp_aux_init(struct drm_dp_aux *msm_dp_aux);
void msm_dp_aux_deinit(struct drm_dp_aux *msm_dp_aux);
void msm_dp_aux_reconfig(struct drm_dp_aux *msm_dp_aux);
void msm_dp_aux_hpd_enable(struct drm_dp_aux *msm_dp_aux);
void msm_dp_aux_hpd_disable(struct drm_dp_aux *msm_dp_aux);
void msm_dp_aux_hpd_intr_enable(struct drm_dp_aux *msm_dp_aux);
void msm_dp_aux_hpd_intr_disable(struct drm_dp_aux *msm_dp_aux);
u32 msm_dp_aux_get_hpd_intr_status(struct drm_dp_aux *msm_dp_aux);
u32 msm_dp_aux_is_link_connected(struct drm_dp_aux *msm_dp_aux);
struct phy;
struct drm_dp_aux *msm_dp_aux_get(struct device *dev,
struct phy *phy,
bool is_edp,
void __iomem *aux_base);
void msm_dp_aux_put(struct drm_dp_aux *aux);
#endif /*__DP_AUX_H_*/
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
*/
#ifndef _DP_CTRL_H_
#define _DP_CTRL_H_
#include "dp_aux.h"
#include "dp_panel.h"
#include "dp_link.h"
struct msm_dp_ctrl {
bool wide_bus_en;
};
struct phy;
int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl);
int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train);
void msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl);
void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl);
void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl);
irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl);
void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl);
struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev,
struct msm_dp_link *link,
struct msm_dp_panel *panel,
struct drm_dp_aux *aux,
struct phy *phy,
void __iomem *ahb_base,
void __iomem *link_base);
void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl);
void msm_dp_ctrl_phy_init(struct msm_dp_ctrl *msm_dp_ctrl);
void msm_dp_ctrl_phy_exit(struct msm_dp_ctrl *msm_dp_ctrl);
void msm_dp_ctrl_irq_phy_exit(struct msm_dp_ctrl *msm_dp_ctrl);
void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, bool enable);
void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl);
int msm_dp_ctrl_core_clk_enable(struct msm_dp_ctrl *msm_dp_ctrl);
void msm_dp_ctrl_core_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl);
void msm_dp_ctrl_enable_irq(struct msm_dp_ctrl *msm_dp_ctrl);
void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl);
#endif /* _DP_CTRL_H_ */
@@ -0,0 +1,240 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
*/
#define pr_fmt(fmt)"[drm-dp] %s: " fmt, __func__
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
#include <drm/drm_connector.h>
#include <drm/drm_file.h>
#include "dp_aux.h"
#include "dp_ctrl.h"
#include "dp_debug.h"
#include "dp_display.h"
#define DEBUG_NAME "msm_dp"
struct msm_dp_debug_private {
struct msm_dp_link *link;
struct msm_dp_panel *panel;
struct drm_connector *connector;
};
static int msm_dp_debug_show(struct seq_file *seq, void *p)
{
struct msm_dp_debug_private *debug = seq->private;
u64 lclk = 0;
u32 link_params_rate;
const struct drm_display_mode *drm_mode;
if (!debug)
return -ENODEV;
drm_mode = &debug->panel->msm_dp_mode.drm_mode;
seq_printf(seq, "\tname = %s\n", DEBUG_NAME);
seq_printf(seq, "\tdrm_dp_link\n\t\trate = %u\n",
debug->panel->link_info.rate);
seq_printf(seq, "\t\tnum_lanes = %u\n",
debug->panel->link_info.num_lanes);
seq_printf(seq, "\t\tcapabilities = %lu\n",
debug->panel->link_info.capabilities);
seq_printf(seq, "\tdp_panel_info:\n\t\tactive = %dx%d\n",
drm_mode->hdisplay,
drm_mode->vdisplay);
seq_printf(seq, "\t\tback_porch = %dx%d\n",
drm_mode->htotal - drm_mode->hsync_end,
drm_mode->vtotal - drm_mode->vsync_end);
seq_printf(seq, "\t\tfront_porch = %dx%d\n",
drm_mode->hsync_start - drm_mode->hdisplay,
drm_mode->vsync_start - drm_mode->vdisplay);
seq_printf(seq, "\t\tsync_width = %dx%d\n",
drm_mode->hsync_end - drm_mode->hsync_start,
drm_mode->vsync_end - drm_mode->vsync_start);
seq_printf(seq, "\t\tactive_low = %dx%d\n",
debug->panel->msm_dp_mode.h_active_low,
debug->panel->msm_dp_mode.v_active_low);
seq_printf(seq, "\t\th_skew = %d\n",
drm_mode->hskew);
seq_printf(seq, "\t\trefresh rate = %d\n",
drm_mode_vrefresh(drm_mode));
seq_printf(seq, "\t\tpixel clock khz = %d\n",
drm_mode->clock);
seq_printf(seq, "\t\tbpp = %d\n",
debug->panel->msm_dp_mode.bpp);
/* Link Information */
seq_printf(seq, "\tdp_link:\n\t\ttest_requested = %d\n",
debug->link->sink_request);
seq_printf(seq, "\t\tnum_lanes = %d\n",
debug->link->link_params.num_lanes);
link_params_rate = debug->link->link_params.rate;
seq_printf(seq, "\t\tbw_code = %d\n",
drm_dp_link_rate_to_bw_code(link_params_rate));
lclk = debug->link->link_params.rate * 1000;
seq_printf(seq, "\t\tlclk = %lld\n", lclk);
seq_printf(seq, "\t\tv_level = %d\n",
debug->link->phy_params.v_level);
seq_printf(seq, "\t\tp_level = %d\n",
debug->link->phy_params.p_level);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(msm_dp_debug);
static int msm_dp_test_data_show(struct seq_file *m, void *data)
{
const struct msm_dp_debug_private *debug = m->private;
const struct drm_connector *connector = debug->connector;
u32 bpc;
if (connector->status == connector_status_connected) {
bpc = debug->link->test_video.test_bit_depth;
seq_printf(m, "hdisplay: %d\n",
debug->link->test_video.test_h_width);
seq_printf(m, "vdisplay: %d\n",
debug->link->test_video.test_v_height);
seq_printf(m, "bpc: %u\n",
msm_dp_link_bit_depth_to_bpp(bpc) / 3);
} else {
seq_puts(m, "0");
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(msm_dp_test_data);
static int msm_dp_test_type_show(struct seq_file *m, void *data)
{
const struct msm_dp_debug_private *debug = m->private;
const struct drm_connector *connector = debug->connector;
if (connector->status == connector_status_connected)
seq_printf(m, "%02x", DP_TEST_LINK_VIDEO_PATTERN);
else
seq_puts(m, "0");
return 0;
}
DEFINE_SHOW_ATTRIBUTE(msm_dp_test_type);
static ssize_t msm_dp_test_active_write(struct file *file,
const char __user *ubuf,
size_t len, loff_t *offp)
{
char *input_buffer;
int status = 0;
const struct msm_dp_debug_private *debug;
const struct drm_connector *connector;
int val = 0;
debug = ((struct seq_file *)file->private_data)->private;
connector = debug->connector;
if (len == 0)
return 0;
input_buffer = memdup_user_nul(ubuf, len);
if (IS_ERR(input_buffer))
return PTR_ERR(input_buffer);
DRM_DEBUG_DRIVER("Copied %d bytes from user\n", (unsigned int)len);
if (connector->status == connector_status_connected) {
status = kstrtoint(input_buffer, 10, &val);
if (status < 0) {
kfree(input_buffer);
return status;
}
DRM_DEBUG_DRIVER("Got %d for test active\n", val);
/* To prevent erroneous activation of the compliance
* testing code, only accept an actual value of 1 here
*/
if (val == 1)
debug->panel->video_test = true;
else
debug->panel->video_test = false;
}
kfree(input_buffer);
*offp += len;
return len;
}
static int msm_dp_test_active_show(struct seq_file *m, void *data)
{
struct msm_dp_debug_private *debug = m->private;
struct drm_connector *connector = debug->connector;
if (connector->status == connector_status_connected) {
if (debug->panel->video_test)
seq_puts(m, "1");
else
seq_puts(m, "0");
} else {
seq_puts(m, "0");
}
return 0;
}
static int msm_dp_test_active_open(struct inode *inode,
struct file *file)
{
return single_open(file, msm_dp_test_active_show,
inode->i_private);
}
static const struct file_operations test_active_fops = {
.owner = THIS_MODULE,
.open = msm_dp_test_active_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.write = msm_dp_test_active_write
};
int msm_dp_debug_init(struct device *dev, struct msm_dp_panel *panel,
struct msm_dp_link *link,
struct drm_connector *connector,
struct dentry *root, bool is_edp)
{
struct msm_dp_debug_private *debug;
if (!dev || !panel || !link) {
DRM_ERROR("invalid input\n");
return -EINVAL;
}
debug = devm_kzalloc(dev, sizeof(*debug), GFP_KERNEL);
if (!debug)
return -ENOMEM;
debug->link = link;
debug->panel = panel;
debugfs_create_file("dp_debug", 0444, root,
debug, &msm_dp_debug_fops);
if (!is_edp) {
debugfs_create_file("dp_test_active", 0444,
root,
debug, &test_active_fops);
debugfs_create_file("dp_test_data", 0444,
root,
debug, &msm_dp_test_data_fops);
debugfs_create_file("dp_test_type", 0444,
root,
debug, &msm_dp_test_type_fops);
}
return 0;
}
#endif
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
*/
#ifndef _DP_DEBUG_H_
#define _DP_DEBUG_H_
#include "dp_panel.h"
#include "dp_link.h"
#if defined(CONFIG_DEBUG_FS)
/**
* msm_dp_debug_init() - configure and get the DisplayPlot debug module data
*
* @dev: device instance of the caller
* @panel: instance of panel module
* @link: instance of link module
* @connector: double pointer to display connector
* @root: connector's debugfs root
* @is_edp: set for eDP connectors / panels
* 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
*/
int msm_dp_debug_init(struct device *dev, struct msm_dp_panel *panel,
struct msm_dp_link *link,
struct drm_connector *connector,
struct dentry *root,
bool is_edp);
#else
static inline
int msm_dp_debug_init(struct device *dev, struct msm_dp_panel *panel,
struct msm_dp_link *link,
struct drm_connector *connector,
struct dentry *root,
bool is_edp)
{
return -EINVAL;
}
#endif /* defined(CONFIG_DEBUG_FS) */
#endif /* _DP_DEBUG_H_ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,37 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
*/
#ifndef _DP_DISPLAY_H_
#define _DP_DISPLAY_H_
#include "dp_panel.h"
#include "disp/msm_disp_snapshot.h"
#define DP_MAX_PIXEL_CLK_KHZ 675000
struct msm_dp {
struct drm_device *drm_dev;
struct platform_device *pdev;
struct drm_connector *connector;
struct drm_bridge *next_bridge;
struct drm_bridge *bridge;
bool audio_enabled;
bool power_on;
unsigned int connector_type;
bool is_edp;
struct msm_dp_audio *msm_dp_audio;
bool psr_supported;
};
int msm_dp_display_get_modes(struct msm_dp *msm_dp_display);
bool msm_dp_display_check_video_test(struct msm_dp *msm_dp_display);
int msm_dp_display_get_test_bpp(struct msm_dp *msm_dp_display);
void msm_dp_display_signal_audio_start(struct msm_dp *msm_dp_display);
void msm_dp_display_signal_audio_complete(struct msm_dp *msm_dp_display);
void msm_dp_display_set_psr(struct msm_dp *dp, bool enter);
void msm_dp_display_debugfs_init(struct msm_dp *msm_dp_display, struct dentry *dentry, bool is_edp);
#endif /* _DP_DISPLAY_H_ */
+329
View File
@@ -0,0 +1,329 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
*/
#include <linux/string_choices.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_atomic.h>
#include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_crtc.h>
#include "msm_drv.h"
#include "msm_kms.h"
#include "dp_audio.h"
#include "dp_drm.h"
/**
* msm_dp_bridge_get_modes - callback to add drm modes via drm_mode_probed_add()
* @bridge: Poiner to drm bridge
* @connector: Pointer to drm connector structure
* Returns: Number of modes added
*/
static int msm_dp_bridge_get_modes(struct drm_bridge *bridge, struct drm_connector *connector)
{
int rc = 0;
struct msm_dp *dp;
if (!connector)
return 0;
dp = to_dp_bridge(bridge)->msm_dp_display;
/* pluggable case assumes EDID is read when HPD */
rc = msm_dp_display_get_modes(dp);
if (rc <= 0) {
DRM_ERROR("failed to get DP sink modes, rc=%d\n", rc);
return rc;
} else {
drm_dbg_dp(connector->dev, "No sink connected\n");
}
return rc;
}
static void msm_dp_bridge_debugfs_init(struct drm_bridge *bridge, struct dentry *root)
{
struct msm_dp *dp = to_dp_bridge(bridge)->msm_dp_display;
msm_dp_display_debugfs_init(dp, root, false);
}
static const struct drm_bridge_funcs msm_dp_bridge_ops = {
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_reset = drm_atomic_helper_bridge_reset,
.atomic_enable = msm_dp_bridge_atomic_enable,
.atomic_disable = msm_dp_bridge_atomic_disable,
.atomic_post_disable = msm_dp_bridge_atomic_post_disable,
.mode_set = msm_dp_bridge_mode_set,
.mode_valid = msm_dp_bridge_mode_valid,
.get_modes = msm_dp_bridge_get_modes,
.detect = msm_dp_bridge_detect,
.hpd_enable = msm_dp_bridge_hpd_enable,
.hpd_disable = msm_dp_bridge_hpd_disable,
.hpd_notify = msm_dp_bridge_hpd_notify,
.debugfs_init = msm_dp_bridge_debugfs_init,
.dp_audio_prepare = msm_dp_audio_prepare,
.dp_audio_shutdown = msm_dp_audio_shutdown,
};
static int msm_edp_bridge_atomic_check(struct drm_bridge *drm_bridge,
struct drm_bridge_state *bridge_state,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct msm_dp *dp = to_dp_bridge(drm_bridge)->msm_dp_display;
if (WARN_ON(!conn_state))
return -ENODEV;
conn_state->self_refresh_aware = dp->psr_supported;
if (!conn_state->crtc || !crtc_state)
return 0;
if (crtc_state->self_refresh_active && !dp->psr_supported)
return -EINVAL;
return 0;
}
static void msm_edp_bridge_atomic_enable(struct drm_bridge *drm_bridge,
struct drm_atomic_commit *state)
{
struct drm_crtc *crtc;
struct drm_crtc_state *old_crtc_state;
struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge);
struct msm_dp *dp = msm_dp_bridge->msm_dp_display;
/*
* Check the old state of the crtc to determine if the panel
* was put into psr state previously by the msm_edp_bridge_atomic_disable.
* If the panel is in psr, just exit psr state and skip the full
* bridge enable sequence.
*/
crtc = drm_atomic_get_new_crtc_for_encoder(state,
drm_bridge->encoder);
if (!crtc)
return;
old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc);
if (old_crtc_state && old_crtc_state->self_refresh_active) {
msm_dp_display_set_psr(dp, false);
return;
}
msm_dp_bridge_atomic_enable(drm_bridge, state);
}
static void msm_edp_bridge_atomic_disable(struct drm_bridge *drm_bridge,
struct drm_atomic_commit *atomic_state)
{
struct drm_crtc *crtc;
struct drm_crtc_state *new_crtc_state = NULL, *old_crtc_state = NULL;
struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge);
struct msm_dp *dp = msm_dp_bridge->msm_dp_display;
crtc = drm_atomic_get_old_crtc_for_encoder(atomic_state,
drm_bridge->encoder);
if (!crtc)
goto out;
new_crtc_state = drm_atomic_get_new_crtc_state(atomic_state, crtc);
if (!new_crtc_state)
goto out;
old_crtc_state = drm_atomic_get_old_crtc_state(atomic_state, crtc);
if (!old_crtc_state)
goto out;
/*
* Set self refresh mode if current crtc state is active.
*
* If old crtc state is active, then this is a display disable
* call while the sink is in psr state. So, exit psr here.
* The eDP controller will be disabled in the
* msm_edp_bridge_atomic_post_disable function.
*
* We observed sink is stuck in self refresh if psr exit is skipped
* when display disable occurs while the sink is in psr state.
*/
if (new_crtc_state->self_refresh_active) {
msm_dp_display_set_psr(dp, true);
return;
} else if (old_crtc_state->self_refresh_active) {
msm_dp_display_set_psr(dp, false);
return;
}
out:
msm_dp_bridge_atomic_disable(drm_bridge, atomic_state);
}
static void msm_edp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge,
struct drm_atomic_commit *atomic_state)
{
struct drm_crtc *crtc;
struct drm_crtc_state *new_crtc_state = NULL;
crtc = drm_atomic_get_old_crtc_for_encoder(atomic_state,
drm_bridge->encoder);
if (!crtc)
return;
new_crtc_state = drm_atomic_get_new_crtc_state(atomic_state, crtc);
if (!new_crtc_state)
return;
/*
* Self refresh mode is already set in msm_edp_bridge_atomic_disable.
*/
if (new_crtc_state->self_refresh_active)
return;
msm_dp_bridge_atomic_post_disable(drm_bridge, atomic_state);
}
/**
* msm_edp_bridge_mode_valid - callback to determine if specified mode is valid
* @bridge: Pointer to drm bridge structure
* @info: display info
* @mode: Pointer to drm mode structure
* Returns: Validity status for specified mode
*/
static enum drm_mode_status msm_edp_bridge_mode_valid(struct drm_bridge *bridge,
const struct drm_display_info *info,
const struct drm_display_mode *mode)
{
struct msm_dp *dp;
int mode_pclk_khz = mode->clock;
dp = to_dp_bridge(bridge)->msm_dp_display;
if (!dp || !mode_pclk_khz || !dp->connector) {
DRM_ERROR("invalid params\n");
return -EINVAL;
}
if (msm_dp_wide_bus_available(dp))
mode_pclk_khz /= 2;
if (mode_pclk_khz > DP_MAX_PIXEL_CLK_KHZ)
return MODE_CLOCK_HIGH;
/*
* The eDP controller currently does not have a reliable way of
* enabling panel power to read sink capabilities. So, we rely
* on the panel driver to populate only supported modes for now.
*/
return MODE_OK;
}
static void msm_edp_bridge_debugfs_init(struct drm_bridge *bridge, struct dentry *root)
{
struct msm_dp *dp = to_dp_bridge(bridge)->msm_dp_display;
msm_dp_display_debugfs_init(dp, root, true);
}
static const struct drm_bridge_funcs msm_edp_bridge_ops = {
.atomic_enable = msm_edp_bridge_atomic_enable,
.atomic_disable = msm_edp_bridge_atomic_disable,
.atomic_post_disable = msm_edp_bridge_atomic_post_disable,
.mode_set = msm_dp_bridge_mode_set,
.mode_valid = msm_edp_bridge_mode_valid,
.atomic_reset = drm_atomic_helper_bridge_reset,
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_check = msm_edp_bridge_atomic_check,
.debugfs_init = msm_edp_bridge_debugfs_init,
};
int msm_dp_bridge_init(struct msm_dp *msm_dp_display, struct drm_device *dev,
struct drm_encoder *encoder, bool yuv_supported)
{
int rc;
struct msm_dp_bridge *msm_dp_bridge;
struct drm_bridge *bridge;
msm_dp_bridge = devm_drm_bridge_alloc(dev->dev, struct msm_dp_bridge, bridge,
msm_dp_display->is_edp ? &msm_edp_bridge_ops :
&msm_dp_bridge_ops);
if (IS_ERR(msm_dp_bridge))
return PTR_ERR(msm_dp_bridge);
msm_dp_bridge->msm_dp_display = msm_dp_display;
bridge = &msm_dp_bridge->bridge;
bridge->type = msm_dp_display->connector_type;
bridge->ycbcr_420_allowed = yuv_supported;
/*
* Many ops only make sense for DP. Why?
* - Detect/HPD are used by DRM to know if a display is _physically_
* there, not whether the display is powered on / finished initting.
* On eDP we assume the display is always there because you can't
* know until power is applied. If we don't implement the ops DRM will
* assume our display is always there.
* - Currently eDP mode reading is driven by the panel driver. This
* allows the panel driver to properly power itself on to read the
* modes.
*/
if (!msm_dp_display->is_edp) {
bridge->ops =
DRM_BRIDGE_OP_DP_AUDIO |
DRM_BRIDGE_OP_DETECT |
DRM_BRIDGE_OP_HPD |
DRM_BRIDGE_OP_MODES;
bridge->hdmi_audio_dev = &msm_dp_display->pdev->dev;
bridge->hdmi_audio_max_i2s_playback_channels = 8;
bridge->hdmi_audio_dai_port = -1;
}
rc = devm_drm_bridge_add(dev->dev, bridge);
if (rc) {
DRM_ERROR("failed to add bridge, rc=%d\n", rc);
return rc;
}
rc = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (rc) {
DRM_ERROR("failed to attach bridge, rc=%d\n", rc);
return rc;
}
if (msm_dp_display->next_bridge) {
rc = drm_bridge_attach(encoder,
msm_dp_display->next_bridge, bridge,
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (rc < 0) {
DRM_ERROR("failed to attach panel bridge: %d\n", rc);
return rc;
}
}
msm_dp_display->bridge = bridge;
return 0;
}
/* connector initialization */
struct drm_connector *msm_dp_drm_connector_init(struct msm_dp *msm_dp_display,
struct drm_encoder *encoder)
{
struct drm_connector *connector = NULL;
connector = drm_bridge_connector_init(msm_dp_display->drm_dev, encoder);
if (IS_ERR(connector))
return connector;
if (!msm_dp_display->is_edp)
drm_connector_attach_dp_subconnector_property(connector);
return connector;
}
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
*/
#ifndef _DP_DRM_H_
#define _DP_DRM_H_
#include <linux/types.h>
#include <drm/drm_bridge.h>
#include "msm_drv.h"
#include "dp_display.h"
struct msm_dp_bridge {
struct drm_bridge bridge;
struct msm_dp *msm_dp_display;
};
#define to_dp_bridge(x) container_of((x), struct msm_dp_bridge, bridge)
struct drm_connector *msm_dp_drm_connector_init(struct msm_dp *msm_dp_display,
struct drm_encoder *encoder);
int msm_dp_bridge_init(struct msm_dp *msm_dp_display, struct drm_device *dev,
struct drm_encoder *encoder,
bool yuv_supported);
enum drm_connector_status msm_dp_bridge_detect(struct drm_bridge *bridge,
struct drm_connector *connector);
void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge,
struct drm_atomic_commit *state);
void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge,
struct drm_atomic_commit *state);
void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge,
struct drm_atomic_commit *state);
enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge,
const struct drm_display_info *info,
const struct drm_display_mode *mode);
void msm_dp_bridge_mode_set(struct drm_bridge *drm_bridge,
const struct drm_display_mode *mode,
const struct drm_display_mode *adjusted_mode);
void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge);
void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge);
void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge,
struct drm_connector *connector,
enum drm_connector_status status);
#endif /* _DP_DRM_H_ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,133 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
*/
#ifndef _DP_LINK_H_
#define _DP_LINK_H_
#include "dp_aux.h"
#include <drm/display/drm_dp_helper.h>
#define DS_PORT_STATUS_CHANGED 0x200
#define DP_TEST_BIT_DEPTH_UNKNOWN 0xFFFFFFFF
#define DP_LINK_CAP_ENHANCED_FRAMING (1 << 0)
#define DP_MAX_NUM_DP_LANES 4
struct msm_dp_link_info {
unsigned char revision;
unsigned int rate;
unsigned int supported_rates[DP_MAX_SUPPORTED_RATES];
unsigned int rate_set;
bool use_rate_set;
unsigned int num_lanes;
unsigned long capabilities;
};
#define DP_TRAIN_LEVEL_MAX 3
struct msm_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 msm_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 msm_dp_link_phy_params {
u32 phy_test_pattern_sel;
u8 v_level;
u8 p_level;
};
struct msm_dp_link {
u8 lttpr_common_caps[DP_LTTPR_COMMON_CAP_SIZE];
int lttpr_count;
u32 sink_request;
u32 test_response;
u8 sink_count;
struct msm_dp_link_test_video test_video;
struct msm_dp_link_test_audio test_audio;
struct msm_dp_link_phy_params phy_params;
struct msm_dp_link_info link_params;
u32 lane_map[DP_MAX_NUM_DP_LANES];
u32 max_dp_lanes;
u32 max_dp_link_rate;
};
/**
* msm_dp_link_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
* bit depth value. This function assumes that bit depth has
* already been validated.
*/
static inline u32 msm_dp_link_bit_depth_to_bpp(u32 tbd)
{
/*
* 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:
return 18;
case DP_TEST_BIT_DEPTH_8:
return 24;
case DP_TEST_BIT_DEPTH_10:
return 30;
case DP_TEST_BIT_DEPTH_UNKNOWN:
default:
return 0;
}
}
void msm_dp_link_reset_phy_params_vx_px(struct msm_dp_link *msm_dp_link);
u32 msm_dp_link_get_test_bits_depth(struct msm_dp_link *msm_dp_link, u32 bpp);
int msm_dp_link_process_request(struct msm_dp_link *msm_dp_link);
int msm_dp_link_get_colorimetry_config(struct msm_dp_link *msm_dp_link);
int msm_dp_link_adjust_levels(struct msm_dp_link *msm_dp_link, u8 *link_status);
bool msm_dp_link_send_test_response(struct msm_dp_link *msm_dp_link);
int msm_dp_link_psm_config(struct msm_dp_link *msm_dp_link,
struct msm_dp_link_info *link_info, bool enable);
bool msm_dp_link_send_edid_checksum(struct msm_dp_link *msm_dp_link, u8 checksum);
/**
* msm_dp_link_get() - get the functionalities of dp test module
* @dev: kernel device structure
* @aux: DisplayPort AUX channel
*
* return: a pointer to msm_dp_link struct
*/
struct msm_dp_link *msm_dp_link_get(struct device *dev, struct drm_dp_aux *aux);
#endif /* _DP_LINK_H_ */
@@ -0,0 +1,764 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
*/
#include "dp_panel.h"
#include "dp_reg.h"
#include "dp_utils.h"
#include <drm/drm_connector.h>
#include <drm/drm_edid.h>
#include <drm/drm_of.h>
#include <drm/drm_print.h>
#include <linux/io.h>
#include <linux/types.h>
#include <asm/byteorder.h>
#define DP_INTF_CONFIG_DATABUS_WIDEN BIT(4)
struct msm_dp_panel_private {
struct device *dev;
struct drm_device *drm_dev;
struct msm_dp_panel msm_dp_panel;
struct drm_dp_aux *aux;
struct msm_dp_link *link;
void __iomem *link_base;
void __iomem *p0_base;
bool panel_on;
};
static inline u32 msm_dp_read_link(struct msm_dp_panel_private *panel, u32 offset)
{
return readl_relaxed(panel->link_base + offset);
}
static inline void msm_dp_write_link(struct msm_dp_panel_private *panel,
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, panel->link_base + offset);
}
static inline void msm_dp_write_p0(struct msm_dp_panel_private *panel,
u32 offset, u32 data)
{
/*
* To make sure interface reg writes happens before any other operation,
* this function uses writel() instread of writel_relaxed()
*/
writel(data, panel->p0_base + offset);
}
static inline u32 msm_dp_read_p0(struct msm_dp_panel_private *panel,
u32 offset)
{
/*
* To make sure interface reg writes happens before any other operation,
* this function uses writel() instread of writel_relaxed()
*/
return readl_relaxed(panel->p0_base + offset);
}
static void msm_dp_panel_read_psr_cap(struct msm_dp_panel_private *panel)
{
ssize_t rlen;
struct msm_dp_panel *msm_dp_panel;
msm_dp_panel = &panel->msm_dp_panel;
/* edp sink */
if (msm_dp_panel->dpcd[DP_EDP_CONFIGURATION_CAP]) {
rlen = drm_dp_dpcd_read(panel->aux, DP_PSR_SUPPORT,
&msm_dp_panel->psr_cap, sizeof(msm_dp_panel->psr_cap));
if (rlen == sizeof(msm_dp_panel->psr_cap)) {
drm_dbg_dp(panel->drm_dev,
"psr version: 0x%x, psr_cap: 0x%x\n",
msm_dp_panel->psr_cap.version,
msm_dp_panel->psr_cap.capabilities);
} else
DRM_ERROR("failed to read psr info, rlen=%zd\n", rlen);
}
}
static int msm_dp_panel_read_dpcd(struct msm_dp_panel *msm_dp_panel)
{
int rc, max_lttpr_lanes, max_lttpr_rate;
struct msm_dp_panel_private *panel;
struct msm_dp_link_info *link_info;
struct msm_dp_link *link;
u8 *dpcd, major, minor;
panel = container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
dpcd = msm_dp_panel->dpcd;
rc = drm_dp_read_dpcd_caps(panel->aux, dpcd);
if (rc)
return rc;
msm_dp_panel->vsc_sdp_supported = drm_dp_vsc_sdp_supported(panel->aux, dpcd);
link_info = &msm_dp_panel->link_info;
link_info->revision = dpcd[DP_DPCD_REV];
major = (link_info->revision >> 4) & 0x0f;
minor = link_info->revision & 0x0f;
link = panel->link;
drm_dbg_dp(panel->drm_dev, "max_lanes=%d max_link_rate=%d\n",
link->max_dp_lanes, link->max_dp_link_rate);
max_lttpr_lanes = drm_dp_lttpr_max_lane_count(link->lttpr_common_caps);
max_lttpr_rate = drm_dp_lttpr_max_link_rate(link->lttpr_common_caps);
/* eDP sink */
if (msm_dp_panel->dpcd[DP_EDP_CONFIGURATION_CAP]) {
u8 edp_rev;
rc = drm_dp_dpcd_read_byte(panel->aux, DP_EDP_DPCD_REV, &edp_rev);
if (rc)
return rc;
drm_dbg_dp(panel->drm_dev, "edp_rev=0x%x\n", edp_rev);
/* For eDP v1.4+, parse the SUPPORTED_LINK_RATES table */
if (edp_rev >= DP_EDP_14) {
__le16 rates[DP_MAX_SUPPORTED_RATES];
u8 bw_set;
int i;
rc = drm_dp_dpcd_read_data(panel->aux, DP_SUPPORTED_LINK_RATES,
rates, sizeof(rates));
if (rc)
return rc;
rc = drm_dp_dpcd_read_byte(panel->aux, DP_LINK_BW_SET, &bw_set);
if (rc)
return rc;
/* Find index of max supported link rate that does not exceed dtsi limits */
for (i = 0; i < ARRAY_SIZE(rates); i++) {
/*
* The value from the DPCD multiplied by 200 gives
* the link rate in kHz. Divide by 10 to convert to
* symbol rate, accounting for 8b/10b encoding.
*/
u32 rate = (le16_to_cpu(rates[i]) * 200) / 10;
if (!rate)
break;
drm_dbg_dp(panel->drm_dev,
"SUPPORTED_LINK_RATES[%d]: %d\n", i, rate);
/*
* Limit link rate from link-frequencies of endpoint
* property of dtsi
*/
if (rate > link->max_dp_link_rate)
break;
/* Limit link rate from LTTPR capabilities, if any */
if (max_lttpr_rate && rate > max_lttpr_rate)
break;
link_info->rate = rate;
link_info->supported_rates[i] = rate;
link_info->rate_set = i;
}
/* Only use LINK_RATE_SET if LINK_BW_SET hasn't already been written to */
if (!bw_set && link_info->rate)
link_info->use_rate_set = true;
}
}
/* Fall back on MAX_LINK_RATE/LINK_BW_SET (DP, eDP <= v1.3) */
if (!link_info->rate) {
link_info->rate = drm_dp_max_link_rate(dpcd);
/* Limit link rate from link-frequencies of endpoint property of dtsi */
if (link_info->rate > link->max_dp_link_rate)
link_info->rate = link->max_dp_link_rate;
/* Limit link rate from LTTPR capabilities, if any */
if (max_lttpr_rate && max_lttpr_rate < link_info->rate)
link_info->rate = max_lttpr_rate;
}
link_info->num_lanes = drm_dp_max_lane_count(dpcd);
/* Limit data lanes from data-lanes of endpoint property of dtsi */
if (link_info->num_lanes > link->max_dp_lanes)
link_info->num_lanes = link->max_dp_lanes;
/* Limit data lanes from LTTPR capabilities, if any */
if (max_lttpr_lanes && max_lttpr_lanes < link_info->num_lanes)
link_info->num_lanes = max_lttpr_lanes;
drm_dbg_dp(panel->drm_dev, "version: %d.%d\n", major, minor);
drm_dbg_dp(panel->drm_dev, "link_rate=%d\n", link_info->rate);
drm_dbg_dp(panel->drm_dev, "link_rate_set=%d\n", link_info->rate_set);
drm_dbg_dp(panel->drm_dev, "use_rate_set=%d\n", link_info->use_rate_set);
drm_dbg_dp(panel->drm_dev, "lane_count=%d\n", link_info->num_lanes);
if (drm_dp_enhanced_frame_cap(dpcd))
link_info->capabilities |= DP_LINK_CAP_ENHANCED_FRAMING;
msm_dp_panel_read_psr_cap(panel);
return rc;
}
static u32 msm_dp_panel_get_supported_bpp(struct msm_dp_panel *msm_dp_panel,
u32 mode_edid_bpp, u32 mode_pclk_khz)
{
const struct msm_dp_link_info *link_info;
const u32 max_supported_bpp = 30, min_supported_bpp = 18;
u32 bpp, data_rate_khz;
bpp = min(mode_edid_bpp, max_supported_bpp);
link_info = &msm_dp_panel->link_info;
data_rate_khz = link_info->num_lanes * link_info->rate * 8;
do {
if (mode_pclk_khz * bpp <= data_rate_khz)
return bpp;
bpp -= 6;
} while (bpp > min_supported_bpp);
return min_supported_bpp;
}
int msm_dp_panel_read_sink_caps(struct msm_dp_panel *msm_dp_panel,
struct drm_connector *connector)
{
int rc, bw_code;
int count;
struct msm_dp_panel_private *panel;
if (!msm_dp_panel || !connector) {
DRM_ERROR("invalid input\n");
return -EINVAL;
}
panel = container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
rc = msm_dp_panel_read_dpcd(msm_dp_panel);
if (rc) {
DRM_ERROR("read dpcd failed %d\n", rc);
return rc;
}
bw_code = drm_dp_link_rate_to_bw_code(msm_dp_panel->link_info.rate);
if (!is_link_rate_valid(bw_code) ||
!is_lane_count_valid(msm_dp_panel->link_info.num_lanes) ||
(bw_code > msm_dp_panel->max_bw_code)) {
DRM_ERROR("Illegal link rate=%d lane=%d\n", msm_dp_panel->link_info.rate,
msm_dp_panel->link_info.num_lanes);
return -EINVAL;
}
if (drm_dp_is_branch(msm_dp_panel->dpcd)) {
count = drm_dp_read_sink_count(panel->aux);
if (!count) {
panel->link->sink_count = 0;
return -ENOTCONN;
}
}
rc = drm_dp_read_downstream_info(panel->aux, msm_dp_panel->dpcd,
msm_dp_panel->downstream_ports);
if (rc)
return rc;
drm_edid_free(msm_dp_panel->drm_edid);
msm_dp_panel->drm_edid = drm_edid_read_ddc(connector, &panel->aux->ddc);
drm_edid_connector_update(connector, msm_dp_panel->drm_edid);
if (!msm_dp_panel->drm_edid) {
DRM_ERROR("panel edid read failed\n");
/* check edid read fail is due to unplug */
if (!msm_dp_aux_is_link_connected(panel->aux)) {
rc = -ETIMEDOUT;
goto end;
}
}
end:
return rc;
}
void msm_dp_panel_unplugged(struct msm_dp_panel *msm_dp_panel,
struct drm_connector *connector)
{
drm_edid_connector_update(connector, NULL);
drm_edid_free(msm_dp_panel->drm_edid);
msm_dp_panel->drm_edid = NULL;
}
u32 msm_dp_panel_get_mode_bpp(struct msm_dp_panel *msm_dp_panel,
u32 mode_edid_bpp, u32 mode_pclk_khz)
{
struct msm_dp_panel_private *panel;
u32 bpp;
if (!msm_dp_panel || !mode_edid_bpp || !mode_pclk_khz) {
DRM_ERROR("invalid input\n");
return 0;
}
panel = container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
if (msm_dp_panel->video_test)
bpp = msm_dp_link_bit_depth_to_bpp(
panel->link->test_video.test_bit_depth);
else
bpp = msm_dp_panel_get_supported_bpp(msm_dp_panel, mode_edid_bpp,
mode_pclk_khz);
return bpp;
}
int msm_dp_panel_get_modes(struct msm_dp_panel *msm_dp_panel,
struct drm_connector *connector)
{
if (!msm_dp_panel) {
DRM_ERROR("invalid input\n");
return -EINVAL;
}
if (msm_dp_panel->drm_edid)
return drm_edid_connector_add_modes(connector);
return 0;
}
static u8 msm_dp_panel_get_edid_checksum(const struct edid *edid)
{
edid += edid->extensions;
return edid->checksum;
}
void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel)
{
struct msm_dp_panel_private *panel;
if (!msm_dp_panel) {
DRM_ERROR("invalid input\n");
return;
}
panel = container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
if (panel->link->sink_request & DP_TEST_LINK_EDID_READ) {
/* FIXME: get rid of drm_edid_raw() */
const struct edid *edid = drm_edid_raw(msm_dp_panel->drm_edid);
u8 checksum;
if (edid)
checksum = msm_dp_panel_get_edid_checksum(edid);
else
checksum = msm_dp_panel->connector->real_edid_checksum;
msm_dp_link_send_edid_checksum(panel->link, checksum);
msm_dp_link_send_test_response(panel->link);
}
}
static void msm_dp_panel_tpg_enable(struct msm_dp_panel *msm_dp_panel,
struct drm_display_mode *drm_mode)
{
struct msm_dp_panel_private *panel =
container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
u32 hsync_period, vsync_period;
u32 display_v_start, display_v_end;
u32 hsync_start_x, hsync_end_x;
u32 v_sync_width;
u32 hsync_ctl;
u32 display_hctl;
/* TPG config parameters*/
hsync_period = drm_mode->htotal;
vsync_period = drm_mode->vtotal;
display_v_start = ((drm_mode->vtotal - drm_mode->vsync_start) *
hsync_period);
display_v_end = ((vsync_period - (drm_mode->vsync_start -
drm_mode->vdisplay))
* hsync_period) - 1;
display_v_start += drm_mode->htotal - drm_mode->hsync_start;
display_v_end -= (drm_mode->hsync_start - drm_mode->hdisplay);
hsync_start_x = drm_mode->htotal - drm_mode->hsync_start;
hsync_end_x = hsync_period - (drm_mode->hsync_start -
drm_mode->hdisplay) - 1;
v_sync_width = drm_mode->vsync_end - drm_mode->vsync_start;
hsync_ctl = (hsync_period << 16) |
(drm_mode->hsync_end - drm_mode->hsync_start);
display_hctl = (hsync_end_x << 16) | hsync_start_x;
msm_dp_write_p0(panel, MMSS_DP_INTF_HSYNC_CTL, hsync_ctl);
msm_dp_write_p0(panel, MMSS_DP_INTF_VSYNC_PERIOD_F0, vsync_period *
hsync_period);
msm_dp_write_p0(panel, MMSS_DP_INTF_VSYNC_PULSE_WIDTH_F0, v_sync_width *
hsync_period);
msm_dp_write_p0(panel, MMSS_DP_INTF_VSYNC_PERIOD_F1, 0);
msm_dp_write_p0(panel, MMSS_DP_INTF_VSYNC_PULSE_WIDTH_F1, 0);
msm_dp_write_p0(panel, MMSS_DP_INTF_DISPLAY_HCTL, display_hctl);
msm_dp_write_p0(panel, MMSS_DP_INTF_ACTIVE_HCTL, 0);
msm_dp_write_p0(panel, MMSS_INTF_DISPLAY_V_START_F0, display_v_start);
msm_dp_write_p0(panel, MMSS_DP_INTF_DISPLAY_V_END_F0, display_v_end);
msm_dp_write_p0(panel, MMSS_INTF_DISPLAY_V_START_F1, 0);
msm_dp_write_p0(panel, MMSS_DP_INTF_DISPLAY_V_END_F1, 0);
msm_dp_write_p0(panel, MMSS_DP_INTF_ACTIVE_V_START_F0, 0);
msm_dp_write_p0(panel, MMSS_DP_INTF_ACTIVE_V_END_F0, 0);
msm_dp_write_p0(panel, MMSS_DP_INTF_ACTIVE_V_START_F1, 0);
msm_dp_write_p0(panel, MMSS_DP_INTF_ACTIVE_V_END_F1, 0);
msm_dp_write_p0(panel, MMSS_DP_INTF_POLARITY_CTL, 0);
msm_dp_write_p0(panel, MMSS_DP_TPG_MAIN_CONTROL,
DP_TPG_CHECKERED_RECT_PATTERN);
msm_dp_write_p0(panel, MMSS_DP_TPG_VIDEO_CONFIG,
DP_TPG_VIDEO_CONFIG_BPP_8BIT |
DP_TPG_VIDEO_CONFIG_RGB);
msm_dp_write_p0(panel, MMSS_DP_BIST_ENABLE,
DP_BIST_ENABLE_DPBIST_EN);
msm_dp_write_p0(panel, MMSS_DP_TIMING_ENGINE_EN,
DP_TIMING_ENGINE_EN_EN);
drm_dbg_dp(panel->drm_dev, "%s: enabled tpg\n", __func__);
}
static void msm_dp_panel_tpg_disable(struct msm_dp_panel *msm_dp_panel)
{
struct msm_dp_panel_private *panel =
container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
msm_dp_write_p0(panel, MMSS_DP_TPG_MAIN_CONTROL, 0x0);
msm_dp_write_p0(panel, MMSS_DP_BIST_ENABLE, 0x0);
msm_dp_write_p0(panel, MMSS_DP_TIMING_ENGINE_EN, 0x0);
}
void msm_dp_panel_tpg_config(struct msm_dp_panel *msm_dp_panel, bool enable)
{
struct msm_dp_panel_private *panel;
if (!msm_dp_panel) {
DRM_ERROR("invalid input\n");
return;
}
panel = container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
if (!panel->panel_on) {
drm_dbg_dp(panel->drm_dev,
"DP panel not enabled, handle TPG on next on\n");
return;
}
if (!enable) {
msm_dp_panel_tpg_disable(msm_dp_panel);
return;
}
drm_dbg_dp(panel->drm_dev, "calling panel's tpg_enable\n");
msm_dp_panel_tpg_enable(msm_dp_panel, &panel->msm_dp_panel.msm_dp_mode.drm_mode);
}
void msm_dp_panel_clear_dsc_dto(struct msm_dp_panel *msm_dp_panel)
{
struct msm_dp_panel_private *panel =
container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
msm_dp_write_p0(panel, MMSS_DP_DSC_DTO, 0x0);
}
static void msm_dp_panel_send_vsc_sdp(struct msm_dp_panel_private *panel, struct dp_sdp *vsc_sdp)
{
u32 header[2];
u32 val;
int i;
msm_dp_utils_pack_sdp_header(&vsc_sdp->sdp_header, header);
msm_dp_write_link(panel, MMSS_DP_GENERIC0_0, header[0]);
msm_dp_write_link(panel, MMSS_DP_GENERIC0_1, header[1]);
for (i = 0; i < sizeof(vsc_sdp->db); i += 4) {
val = ((vsc_sdp->db[i]) | (vsc_sdp->db[i + 1] << 8) | (vsc_sdp->db[i + 2] << 16) |
(vsc_sdp->db[i + 3] << 24));
msm_dp_write_link(panel, MMSS_DP_GENERIC0_2 + i, val);
}
}
static void msm_dp_panel_update_sdp(struct msm_dp_panel_private *panel)
{
u32 hw_revision = panel->msm_dp_panel.hw_revision;
if (hw_revision >= DP_HW_VERSION_1_0 &&
hw_revision < DP_HW_VERSION_1_2) {
msm_dp_write_link(panel, MMSS_DP_SDP_CFG3, UPDATE_SDP);
msm_dp_write_link(panel, MMSS_DP_SDP_CFG3, 0x0);
}
}
void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sdp *vsc_sdp)
{
struct msm_dp_panel_private *panel =
container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
u32 cfg, cfg2, misc;
cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG);
cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2);
misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0);
cfg |= GEN0_SDP_EN;
msm_dp_write_link(panel, MMSS_DP_SDP_CFG, cfg);
cfg2 |= GENERIC0_SDPSIZE_VALID;
msm_dp_write_link(panel, MMSS_DP_SDP_CFG2, cfg2);
msm_dp_panel_send_vsc_sdp(panel, vsc_sdp);
/* indicates presence of VSC (BIT(6) of MISC1) */
misc |= DP_MISC1_VSC_SDP;
drm_dbg_dp(panel->drm_dev, "vsc sdp enable=1\n");
pr_debug("misc settings = 0x%x\n", misc);
msm_dp_write_link(panel, REG_DP_MISC1_MISC0, misc);
msm_dp_panel_update_sdp(panel);
}
void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel)
{
struct msm_dp_panel_private *panel =
container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
u32 cfg, cfg2, misc;
cfg = msm_dp_read_link(panel, MMSS_DP_SDP_CFG);
cfg2 = msm_dp_read_link(panel, MMSS_DP_SDP_CFG2);
misc = msm_dp_read_link(panel, REG_DP_MISC1_MISC0);
cfg &= ~GEN0_SDP_EN;
msm_dp_write_link(panel, MMSS_DP_SDP_CFG, cfg);
cfg2 &= ~GENERIC0_SDPSIZE_VALID;
msm_dp_write_link(panel, MMSS_DP_SDP_CFG2, cfg2);
/* switch back to MSA */
misc &= ~DP_MISC1_VSC_SDP;
drm_dbg_dp(panel->drm_dev, "vsc sdp enable=0\n");
pr_debug("misc settings = 0x%x\n", misc);
msm_dp_write_link(panel, REG_DP_MISC1_MISC0, misc);
msm_dp_panel_update_sdp(panel);
}
static int msm_dp_panel_setup_vsc_sdp_yuv_420(struct msm_dp_panel *msm_dp_panel)
{
struct msm_dp_display_mode *msm_dp_mode;
struct drm_dp_vsc_sdp vsc_sdp_data;
struct dp_sdp vsc_sdp;
ssize_t len;
if (!msm_dp_panel) {
DRM_ERROR("invalid input\n");
return -EINVAL;
}
msm_dp_mode = &msm_dp_panel->msm_dp_mode;
memset(&vsc_sdp_data, 0, sizeof(vsc_sdp_data));
/* VSC SDP header as per table 2-118 of DP 1.4 specification */
vsc_sdp_data.sdp_type = DP_SDP_VSC;
vsc_sdp_data.revision = 0x05;
vsc_sdp_data.length = 0x13;
/* VSC SDP Payload for DB16 */
vsc_sdp_data.pixelformat = DP_PIXELFORMAT_YUV420;
vsc_sdp_data.colorimetry = DP_COLORIMETRY_DEFAULT;
/* VSC SDP Payload for DB17 */
vsc_sdp_data.bpc = msm_dp_mode->bpp / 3;
vsc_sdp_data.dynamic_range = DP_DYNAMIC_RANGE_CTA;
/* VSC SDP Payload for DB18 */
vsc_sdp_data.content_type = DP_CONTENT_TYPE_GRAPHICS;
len = drm_dp_vsc_sdp_pack(&vsc_sdp_data, &vsc_sdp);
if (len < 0) {
DRM_ERROR("unable to pack vsc sdp\n");
return len;
}
msm_dp_panel_enable_vsc_sdp(msm_dp_panel, &vsc_sdp);
return 0;
}
int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en)
{
u32 data, total_ver, total_hor;
struct msm_dp_panel_private *panel;
struct drm_display_mode *drm_mode;
u32 width_blanking;
u32 sync_start;
u32 msm_dp_active;
u32 total;
u32 reg;
panel = container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
drm_mode = &panel->msm_dp_panel.msm_dp_mode.drm_mode;
drm_dbg_dp(panel->drm_dev, "width=%d hporch= %d %d %d\n",
drm_mode->hdisplay, drm_mode->htotal - drm_mode->hsync_end,
drm_mode->hsync_start - drm_mode->hdisplay,
drm_mode->hsync_end - drm_mode->hsync_start);
drm_dbg_dp(panel->drm_dev, "height=%d vporch= %d %d %d\n",
drm_mode->vdisplay, drm_mode->vtotal - drm_mode->vsync_end,
drm_mode->vsync_start - drm_mode->vdisplay,
drm_mode->vsync_end - drm_mode->vsync_start);
total_hor = drm_mode->htotal;
total_ver = drm_mode->vtotal;
data = total_ver;
data <<= 16;
data |= total_hor;
total = data;
data = (drm_mode->vtotal - drm_mode->vsync_start);
data <<= 16;
data |= (drm_mode->htotal - drm_mode->hsync_start);
sync_start = data;
data = drm_mode->vsync_end - drm_mode->vsync_start;
data <<= 16;
data |= (panel->msm_dp_panel.msm_dp_mode.v_active_low << 31);
data |= drm_mode->hsync_end - drm_mode->hsync_start;
data |= (panel->msm_dp_panel.msm_dp_mode.h_active_low << 15);
width_blanking = data;
data = drm_mode->vdisplay;
data <<= 16;
data |= drm_mode->hdisplay;
msm_dp_active = data;
msm_dp_write_link(panel, REG_DP_TOTAL_HOR_VER, total);
msm_dp_write_link(panel, REG_DP_START_HOR_VER_FROM_SYNC, sync_start);
msm_dp_write_link(panel, REG_DP_HSYNC_VSYNC_WIDTH_POLARITY, width_blanking);
msm_dp_write_link(panel, REG_DP_ACTIVE_HOR_VER, msm_dp_active);
reg = msm_dp_read_p0(panel, MMSS_DP_INTF_CONFIG);
if (wide_bus_en)
reg |= DP_INTF_CONFIG_DATABUS_WIDEN;
else
reg &= ~DP_INTF_CONFIG_DATABUS_WIDEN;
drm_dbg_dp(panel->drm_dev, "wide_bus_en=%d reg=%#x\n", wide_bus_en, reg);
msm_dp_write_p0(panel, MMSS_DP_INTF_CONFIG, reg);
if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420)
msm_dp_panel_setup_vsc_sdp_yuv_420(msm_dp_panel);
panel->panel_on = true;
return 0;
}
int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel)
{
struct drm_display_mode *drm_mode;
struct msm_dp_panel_private *panel;
drm_mode = &msm_dp_panel->msm_dp_mode.drm_mode;
panel = container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel);
/*
* print resolution info as this is a result
* of user initiated action of cable connection
*/
drm_dbg_dp(panel->drm_dev, "SET NEW RESOLUTION:\n");
drm_dbg_dp(panel->drm_dev, "%dx%d@%dfps\n",
drm_mode->hdisplay, drm_mode->vdisplay, drm_mode_vrefresh(drm_mode));
drm_dbg_dp(panel->drm_dev,
"h_porches(back|front|width) = (%d|%d|%d)\n",
drm_mode->htotal - drm_mode->hsync_end,
drm_mode->hsync_start - drm_mode->hdisplay,
drm_mode->hsync_end - drm_mode->hsync_start);
drm_dbg_dp(panel->drm_dev,
"v_porches(back|front|width) = (%d|%d|%d)\n",
drm_mode->vtotal - drm_mode->vsync_end,
drm_mode->vsync_start - drm_mode->vdisplay,
drm_mode->vsync_end - drm_mode->vsync_start);
drm_dbg_dp(panel->drm_dev, "pixel clock (KHz)=(%d)\n",
drm_mode->clock);
drm_dbg_dp(panel->drm_dev, "bpp = %d\n", msm_dp_panel->msm_dp_mode.bpp);
msm_dp_panel->msm_dp_mode.bpp = msm_dp_panel_get_mode_bpp(msm_dp_panel, msm_dp_panel->msm_dp_mode.bpp,
msm_dp_panel->msm_dp_mode.drm_mode.clock);
drm_dbg_dp(panel->drm_dev, "updated bpp = %d\n",
msm_dp_panel->msm_dp_mode.bpp);
return 0;
}
struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux,
struct msm_dp_link *link,
void __iomem *link_base,
void __iomem *p0_base)
{
struct msm_dp_panel_private *panel;
struct msm_dp_panel *msm_dp_panel;
if (!dev || !aux || !link) {
DRM_ERROR("invalid input\n");
return ERR_PTR(-EINVAL);
}
panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
if (!panel)
return ERR_PTR(-ENOMEM);
panel->dev = dev;
panel->aux = aux;
panel->link = link;
panel->link_base = link_base;
panel->p0_base = p0_base;
msm_dp_panel = &panel->msm_dp_panel;
msm_dp_panel->max_bw_code = DP_LINK_BW_8_1;
return msm_dp_panel;
}
void msm_dp_panel_put(struct msm_dp_panel *msm_dp_panel)
{
if (!msm_dp_panel)
return;
drm_edid_free(msm_dp_panel->drm_edid);
}
@@ -0,0 +1,98 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
*/
#ifndef _DP_PANEL_H_
#define _DP_PANEL_H_
#include <drm/drm_modes.h>
#include <drm/msm_drm.h>
#include "dp_aux.h"
#include "dp_link.h"
struct edid;
struct msm_dp_display_mode {
struct drm_display_mode drm_mode;
u32 bpp;
u32 h_active_low;
u32 v_active_low;
bool out_fmt_is_yuv_420;
};
struct msm_dp_panel_psr {
u8 version;
u8 capabilities;
};
struct msm_dp_panel {
/* dpcd raw data */
u8 dpcd[DP_RECEIVER_CAP_SIZE];
u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS];
struct msm_dp_link_info link_info;
const struct drm_edid *drm_edid;
struct drm_connector *connector;
struct msm_dp_display_mode msm_dp_mode;
struct msm_dp_panel_psr psr_cap;
bool video_test;
bool vsc_sdp_supported;
u32 hw_revision;
u32 max_bw_code;
};
int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel);
int msm_dp_panel_deinit(struct msm_dp_panel *msm_dp_panel);
int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en);
int msm_dp_panel_read_sink_caps(struct msm_dp_panel *msm_dp_panel,
struct drm_connector *connector);
void msm_dp_panel_unplugged(struct msm_dp_panel *msm_dp_panel,
struct drm_connector *connector);
u32 msm_dp_panel_get_mode_bpp(struct msm_dp_panel *msm_dp_panel, u32 mode_max_bpp,
u32 mode_pclk_khz);
int msm_dp_panel_get_modes(struct msm_dp_panel *msm_dp_panel,
struct drm_connector *connector);
void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel);
void msm_dp_panel_tpg_config(struct msm_dp_panel *msm_dp_panel, bool enable);
void msm_dp_panel_clear_dsc_dto(struct msm_dp_panel *msm_dp_panel);
void msm_dp_panel_enable_vsc_sdp(struct msm_dp_panel *msm_dp_panel, struct dp_sdp *vsc_sdp);
void msm_dp_panel_disable_vsc_sdp(struct msm_dp_panel *msm_dp_panel);
/**
* is_link_rate_valid() - validates the link rate
* @bw_code: 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);
}
/**
* 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 == 1 ||
lane_count == 2 ||
lane_count == 4);
}
struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux,
struct msm_dp_link *link,
void __iomem *link_base,
void __iomem *p0_base);
void msm_dp_panel_put(struct msm_dp_panel *msm_dp_panel);
#endif /* _DP_PANEL_H_ */
+366
View File
@@ -0,0 +1,366 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
*/
#ifndef _DP_REG_H_
#define _DP_REG_H_
#include <linux/bitfield.h>
#include <linux/bits.h>
/* DP_TX Registers */
#define REG_DP_HW_VERSION (0x00000000)
#define DP_HW_VERSION_1_0 0x10000000
#define DP_HW_VERSION_1_2 0x10020000
#define REG_DP_SW_RESET (0x00000010)
#define DP_SW_RESET (0x00000001)
#define REG_DP_PHY_CTRL (0x00000014)
#define DP_PHY_CTRL_SW_RESET_PLL (0x00000001)
#define DP_PHY_CTRL_SW_RESET (0x00000004)
#define REG_DP_CLK_CTRL (0x00000018)
#define REG_DP_CLK_ACTIVE (0x0000001C)
#define REG_DP_INTR_STATUS (0x00000020)
#define DP_INTR_HPD BIT(0)
#define DP_INTR_AUX_XFER_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 REG_DP_INTR_STATUS2 (0x00000024)
#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 REG_DP_INTR_STATUS3 (0x00000028)
#define REG_DP_INTR_STATUS4 (0x0000002C)
#define PSR_UPDATE_INT (0x00000001)
#define PSR_CAPTURE_INT (0x00000004)
#define PSR_EXIT_INT (0x00000010)
#define PSR_UPDATE_ERROR_INT (0x00000040)
#define PSR_WAKE_ERROR_INT (0x00000100)
#define REG_DP_INTR_MASK4 (0x00000030)
#define PSR_UPDATE_MASK (0x00000001)
#define PSR_CAPTURE_MASK (0x00000002)
#define PSR_EXIT_MASK (0x00000004)
#define PSR_UPDATE_ERROR_MASK (0x00000008)
#define PSR_WAKE_ERROR_MASK (0x00000010)
#define REG_DP_DP_HPD_CTRL (0x00000000)
#define DP_DP_HPD_CTRL_HPD_EN (0x00000001)
#define REG_DP_DP_HPD_INT_STATUS (0x00000004)
#define REG_DP_DP_HPD_INT_ACK (0x00000008)
#define DP_DP_HPD_PLUG_INT_ACK (0x00000001)
#define DP_DP_IRQ_HPD_INT_ACK (0x00000002)
#define DP_DP_HPD_REPLUG_INT_ACK (0x00000004)
#define DP_DP_HPD_UNPLUG_INT_ACK (0x00000008)
#define DP_DP_HPD_STATE_STATUS_BITS_MASK (0x00000007)
#define DP_DP_HPD_STATE_STATUS_BITS_SHIFT (0x1D)
#define REG_DP_DP_HPD_INT_MASK (0x0000000C)
#define DP_DP_HPD_PLUG_INT_MASK (0x00000001)
#define DP_DP_IRQ_HPD_INT_MASK (0x00000002)
#define DP_DP_HPD_REPLUG_INT_MASK (0x00000004)
#define DP_DP_HPD_UNPLUG_INT_MASK (0x00000008)
#define DP_DP_HPD_INT_MASK (DP_DP_HPD_PLUG_INT_MASK | \
DP_DP_IRQ_HPD_INT_MASK | \
DP_DP_HPD_REPLUG_INT_MASK | \
DP_DP_HPD_UNPLUG_INT_MASK)
#define DP_DP_HPD_STATE_STATUS_CONNECTED (0x40000000)
#define DP_DP_HPD_STATE_STATUS_PENDING (0x20000000)
#define DP_DP_HPD_STATE_STATUS_DISCONNECTED (0x00000000)
#define DP_DP_HPD_STATE_STATUS_MASK (0xE0000000)
#define REG_DP_DP_HPD_REFTIMER (0x00000018)
#define DP_DP_HPD_REFTIMER_ENABLE (1 << 16)
#define REG_DP_DP_HPD_EVENT_TIME_0 (0x0000001C)
#define REG_DP_DP_HPD_EVENT_TIME_1 (0x00000020)
#define DP_DP_HPD_EVENT_TIME_0_VAL (0x3E800FA)
#define DP_DP_HPD_EVENT_TIME_1_VAL (0x1F407D0)
#define REG_DP_AUX_CTRL (0x00000030)
#define DP_AUX_CTRL_ENABLE (0x00000001)
#define DP_AUX_CTRL_RESET (0x00000002)
#define REG_DP_AUX_DATA (0x00000034)
#define DP_AUX_DATA_READ (0x00000001)
#define DP_AUX_DATA_WRITE (0x00000000)
#define DP_AUX_DATA_OFFSET (0x00000008)
#define DP_AUX_DATA_INDEX_OFFSET (0x00000010)
#define DP_AUX_DATA_MASK (0x0000ff00)
#define DP_AUX_DATA_INDEX_WRITE (0x80000000)
#define REG_DP_AUX_TRANS_CTRL (0x00000038)
#define DP_AUX_TRANS_CTRL_I2C (0x00000100)
#define DP_AUX_TRANS_CTRL_GO (0x00000200)
#define DP_AUX_TRANS_CTRL_NO_SEND_ADDR (0x00000400)
#define DP_AUX_TRANS_CTRL_NO_SEND_STOP (0x00000800)
#define REG_DP_TIMEOUT_COUNT (0x0000003C)
#define REG_DP_AUX_LIMITS (0x00000040)
#define REG_DP_AUX_STATUS (0x00000044)
#define DP_DPCD_CP_IRQ (0x201)
#define DP_DPCD_RXSTATUS (0x69493)
#define DP_INTERRUPT_TRANS_NUM (0x000000A0)
#define REG_DP_MAINLINK_CTRL (0x00000000)
#define DP_MAINLINK_CTRL_ENABLE (0x00000001)
#define DP_MAINLINK_CTRL_RESET (0x00000002)
#define DP_MAINLINK_CTRL_SW_BYPASS_SCRAMBLER (0x00000010)
#define DP_MAINLINK_CTRL_FLUSH_MODE_MASK GENMASK(24, 23)
#define DP_MAINLINK_FLUSH_MODE_UPDATE_SDP FIELD_PREP(DP_MAINLINK_CTRL_FLUSH_MODE_MASK, 1)
#define DP_MAINLINK_FLUSH_MODE_SDE_PERIPH_UPDATE FIELD_PREP(DP_MAINLINK_CTRL_FLUSH_MODE_MASK, 3)
#define DP_MAINLINK_FB_BOUNDARY_SEL (0x02000000)
#define REG_DP_STATE_CTRL (0x00000004)
#define DP_STATE_CTRL_LINK_TRAINING_PATTERN1 (0x00000001)
#define DP_STATE_CTRL_LINK_TRAINING_PATTERN2 (0x00000002)
#define DP_STATE_CTRL_LINK_TRAINING_PATTERN3 (0x00000004)
#define DP_STATE_CTRL_LINK_TRAINING_PATTERN4 (0x00000008)
#define DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE (0x00000010)
#define DP_STATE_CTRL_LINK_PRBS7 (0x00000020)
#define DP_STATE_CTRL_LINK_TEST_CUSTOM_PATTERN (0x00000040)
#define DP_STATE_CTRL_SEND_VIDEO (0x00000080)
#define DP_STATE_CTRL_PUSH_IDLE (0x00000100)
#define REG_DP_CONFIGURATION_CTRL (0x00000008)
#define DP_CONFIGURATION_CTRL_SYNC_ASYNC_CLK (0x00000001)
#define DP_CONFIGURATION_CTRL_STATIC_DYNAMIC_CN (0x00000002)
#define DP_CONFIGURATION_CTRL_P_INTERLACED (0x00000004)
#define DP_CONFIGURATION_CTRL_INTERLACED_BTF (0x00000008)
#define DP_CONFIGURATION_CTRL_NUM_OF_LANES (0x00000010)
#define DP_CONFIGURATION_CTRL_ENHANCED_FRAMING (0x00000040)
#define DP_CONFIGURATION_CTRL_SEND_VSC (0x00000080)
#define DP_CONFIGURATION_CTRL_BPC (0x00000100)
#define DP_CONFIGURATION_CTRL_ASSR (0x00000400)
#define DP_CONFIGURATION_CTRL_RGB_YUV (0x00000800)
#define DP_CONFIGURATION_CTRL_LSCLK_DIV (0x00002000)
#define DP_CONFIGURATION_CTRL_NUM_OF_LANES_SHIFT (0x04)
#define DP_CONFIGURATION_CTRL_BPC_SHIFT (0x08)
#define DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT (0x0D)
#define REG_DP_SOFTWARE_MVID (0x00000010)
#define REG_DP_SOFTWARE_NVID (0x00000018)
#define REG_DP_TOTAL_HOR_VER (0x0000001C)
#define REG_DP_START_HOR_VER_FROM_SYNC (0x00000020)
#define REG_DP_HSYNC_VSYNC_WIDTH_POLARITY (0x00000024)
#define REG_DP_ACTIVE_HOR_VER (0x00000028)
#define REG_DP_MISC1_MISC0 (0x0000002C)
#define DP_MISC0_SYNCHRONOUS_CLK (0x00000001)
#define DP_MISC0_COLORIMETRY_CFG_SHIFT (0x00000001)
#define DP_MISC0_TEST_BITS_DEPTH_SHIFT (0x00000005)
#define DP_MISC1_VSC_SDP (0x00004000)
#define DP_MISC0_COLORIMERY_CFG_LEGACY_RGB (0)
#define DP_MISC0_COLORIMERY_CFG_CEA_RGB (0x04)
#define REG_DP_VALID_BOUNDARY (0x00000030)
#define REG_DP_VALID_BOUNDARY_2 (0x00000034)
#define REG_DP_LOGICAL2PHYSICAL_LANE_MAPPING (0x00000038)
#define LANE0_MAPPING_SHIFT (0x00000000)
#define LANE1_MAPPING_SHIFT (0x00000002)
#define LANE2_MAPPING_SHIFT (0x00000004)
#define LANE3_MAPPING_SHIFT (0x00000006)
#define REG_DP_MAINLINK_READY (0x00000040)
#define DP_MAINLINK_READY_FOR_VIDEO (0x00000001)
#define DP_MAINLINK_READY_LINK_TRAINING_SHIFT (0x00000003)
#define REG_DP_MAINLINK_LEVELS (0x00000044)
#define DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2 (0x00000002)
#define REG_DP_TU (0x0000004C)
#define REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET (0x00000054)
#define DP_HBR2_ERM_PATTERN (0x00010000)
#define REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG0 (0x000000C0)
#define REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG1 (0x000000C4)
#define REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG2 (0x000000C8)
#define MMSS_DP_MISC1_MISC0 (0x0000002C)
#define MMSS_DP_AUDIO_TIMING_GEN (0x00000080)
#define MMSS_DP_AUDIO_TIMING_RBR_32 (0x00000084)
#define MMSS_DP_AUDIO_TIMING_HBR_32 (0x00000088)
#define MMSS_DP_AUDIO_TIMING_RBR_44 (0x0000008C)
#define MMSS_DP_AUDIO_TIMING_HBR_44 (0x00000090)
#define MMSS_DP_AUDIO_TIMING_RBR_48 (0x00000094)
#define MMSS_DP_AUDIO_TIMING_HBR_48 (0x00000098)
#define REG_PSR_CONFIG (0x00000100)
#define DISABLE_PSR (0x00000000)
#define PSR1_SUPPORTED (0x00000001)
#define PSR2_WITHOUT_FRAMESYNC (0x00000002)
#define PSR2_WITH_FRAMESYNC (0x00000003)
#define REG_PSR_CMD (0x00000110)
#define PSR_ENTER (0x00000001)
#define PSR_EXIT (0x00000002)
#define MMSS_DP_PSR_CRC_RG (0x00000154)
#define MMSS_DP_PSR_CRC_B (0x00000158)
#define REG_DP_COMPRESSION_MODE_CTRL (0x00000180)
#define MMSS_DP_AUDIO_CFG (0x00000200)
#define MMSS_DP_AUDIO_STATUS (0x00000204)
#define MMSS_DP_AUDIO_PKT_CTRL (0x00000208)
#define MMSS_DP_AUDIO_PKT_CTRL2 (0x0000020C)
#define MMSS_DP_AUDIO_ACR_CTRL (0x00000210)
#define MMSS_DP_AUDIO_CTRL_RESET (0x00000214)
#define MMSS_DP_SDP_CFG (0x00000228)
#define GEN0_SDP_EN (0x00020000)
#define MMSS_DP_SDP_CFG2 (0x0000022C)
#define MMSS_DP_AUDIO_TIMESTAMP_0 (0x00000230)
#define MMSS_DP_AUDIO_TIMESTAMP_1 (0x00000234)
#define GENERIC0_SDPSIZE_VALID (0x00010000)
#define MMSS_DP_AUDIO_STREAM_0 (0x00000240)
#define MMSS_DP_AUDIO_STREAM_1 (0x00000244)
#define MMSS_DP_SDP_CFG3 (0x0000024c)
#define UPDATE_SDP (0x00000001)
#define MMSS_DP_EXTENSION_0 (0x00000250)
#define MMSS_DP_EXTENSION_1 (0x00000254)
#define MMSS_DP_EXTENSION_2 (0x00000258)
#define MMSS_DP_EXTENSION_3 (0x0000025C)
#define MMSS_DP_EXTENSION_4 (0x00000260)
#define MMSS_DP_EXTENSION_5 (0x00000264)
#define MMSS_DP_EXTENSION_6 (0x00000268)
#define MMSS_DP_EXTENSION_7 (0x0000026C)
#define MMSS_DP_EXTENSION_8 (0x00000270)
#define MMSS_DP_EXTENSION_9 (0x00000274)
#define MMSS_DP_AUDIO_COPYMANAGEMENT_0 (0x00000278)
#define MMSS_DP_AUDIO_COPYMANAGEMENT_1 (0x0000027C)
#define MMSS_DP_AUDIO_COPYMANAGEMENT_2 (0x00000280)
#define MMSS_DP_AUDIO_COPYMANAGEMENT_3 (0x00000284)
#define MMSS_DP_AUDIO_COPYMANAGEMENT_4 (0x00000288)
#define MMSS_DP_AUDIO_COPYMANAGEMENT_5 (0x0000028C)
#define MMSS_DP_AUDIO_ISRC_0 (0x00000290)
#define MMSS_DP_AUDIO_ISRC_1 (0x00000294)
#define MMSS_DP_AUDIO_ISRC_2 (0x00000298)
#define MMSS_DP_AUDIO_ISRC_3 (0x0000029C)
#define MMSS_DP_AUDIO_ISRC_4 (0x000002A0)
#define MMSS_DP_AUDIO_ISRC_5 (0x000002A4)
#define MMSS_DP_AUDIO_INFOFRAME_0 (0x000002A8)
#define MMSS_DP_AUDIO_INFOFRAME_1 (0x000002AC)
#define MMSS_DP_AUDIO_INFOFRAME_2 (0x000002B0)
#define MMSS_DP_GENERIC0_0 (0x00000300)
#define MMSS_DP_GENERIC0_1 (0x00000304)
#define MMSS_DP_GENERIC0_2 (0x00000308)
#define MMSS_DP_GENERIC0_3 (0x0000030C)
#define MMSS_DP_GENERIC0_4 (0x00000310)
#define MMSS_DP_GENERIC0_5 (0x00000314)
#define MMSS_DP_GENERIC0_6 (0x00000318)
#define MMSS_DP_GENERIC0_7 (0x0000031C)
#define MMSS_DP_GENERIC0_8 (0x00000320)
#define MMSS_DP_GENERIC0_9 (0x00000324)
#define MMSS_DP_GENERIC1_0 (0x00000328)
#define MMSS_DP_GENERIC1_1 (0x0000032C)
#define MMSS_DP_GENERIC1_2 (0x00000330)
#define MMSS_DP_GENERIC1_3 (0x00000334)
#define MMSS_DP_GENERIC1_4 (0x00000338)
#define MMSS_DP_GENERIC1_5 (0x0000033C)
#define MMSS_DP_GENERIC1_6 (0x00000340)
#define MMSS_DP_GENERIC1_7 (0x00000344)
#define MMSS_DP_GENERIC1_8 (0x00000348)
#define MMSS_DP_GENERIC1_9 (0x0000034C)
#define MMSS_DP_VSCEXT_0 (0x000002D0)
#define MMSS_DP_VSCEXT_1 (0x000002D4)
#define MMSS_DP_VSCEXT_2 (0x000002D8)
#define MMSS_DP_VSCEXT_3 (0x000002DC)
#define MMSS_DP_VSCEXT_4 (0x000002E0)
#define MMSS_DP_VSCEXT_5 (0x000002E4)
#define MMSS_DP_VSCEXT_6 (0x000002E8)
#define MMSS_DP_VSCEXT_7 (0x000002EC)
#define MMSS_DP_VSCEXT_8 (0x000002F0)
#define MMSS_DP_VSCEXT_9 (0x000002F4)
#define MMSS_DP_BIST_ENABLE (0x00000000)
#define DP_BIST_ENABLE_DPBIST_EN (0x00000001)
#define MMSS_DP_TIMING_ENGINE_EN (0x00000010)
#define DP_TIMING_ENGINE_EN_EN (0x00000001)
#define MMSS_DP_INTF_CONFIG (0x00000014)
#define MMSS_DP_INTF_HSYNC_CTL (0x00000018)
#define MMSS_DP_INTF_VSYNC_PERIOD_F0 (0x0000001C)
#define MMSS_DP_INTF_VSYNC_PERIOD_F1 (0x00000020)
#define MMSS_DP_INTF_VSYNC_PULSE_WIDTH_F0 (0x00000024)
#define MMSS_DP_INTF_VSYNC_PULSE_WIDTH_F1 (0x00000028)
#define MMSS_INTF_DISPLAY_V_START_F0 (0x0000002C)
#define MMSS_INTF_DISPLAY_V_START_F1 (0x00000030)
#define MMSS_DP_INTF_DISPLAY_V_END_F0 (0x00000034)
#define MMSS_DP_INTF_DISPLAY_V_END_F1 (0x00000038)
#define MMSS_DP_INTF_ACTIVE_V_START_F0 (0x0000003C)
#define MMSS_DP_INTF_ACTIVE_V_START_F1 (0x00000040)
#define MMSS_DP_INTF_ACTIVE_V_END_F0 (0x00000044)
#define MMSS_DP_INTF_ACTIVE_V_END_F1 (0x00000048)
#define MMSS_DP_INTF_DISPLAY_HCTL (0x0000004C)
#define MMSS_DP_INTF_ACTIVE_HCTL (0x00000050)
#define MMSS_DP_INTF_POLARITY_CTL (0x00000058)
#define MMSS_DP_TPG_MAIN_CONTROL (0x00000060)
#define MMSS_DP_DSC_DTO (0x0000007C)
#define DP_TPG_CHECKERED_RECT_PATTERN (0x00000100)
#define MMSS_DP_TPG_VIDEO_CONFIG (0x00000064)
#define DP_TPG_VIDEO_CONFIG_BPP_8BIT (0x00000001)
#define DP_TPG_VIDEO_CONFIG_RGB (0x00000004)
#define MMSS_DP_ASYNC_FIFO_CONFIG (0x00000088)
#define REG_DP_PHY_AUX_INTERRUPT_CLEAR (0x0000004C)
#define REG_DP_PHY_AUX_BIST_CFG (0x00000050)
#define REG_DP_PHY_AUX_INTERRUPT_STATUS (0x000000BC)
/* DP HDCP 1.3 registers */
#define DP_HDCP_CTRL (0x0A0)
#define DP_HDCP_STATUS (0x0A4)
#define DP_HDCP_SW_UPPER_AKSV (0x098)
#define DP_HDCP_SW_LOWER_AKSV (0x09C)
#define DP_HDCP_ENTROPY_CTRL0 (0x350)
#define DP_HDCP_ENTROPY_CTRL1 (0x35C)
#define DP_HDCP_SHA_STATUS (0x0C8)
#define DP_HDCP_RCVPORT_DATA2_0 (0x0B0)
#define DP_HDCP_RCVPORT_DATA3 (0x0A4)
#define DP_HDCP_RCVPORT_DATA4 (0x0A8)
#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)
#endif /* _DP_REG_H_ */
@@ -0,0 +1,88 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2024, The Linux Foundation. All rights reserved.
*/
#include <linux/types.h>
#include "dp_utils.h"
#define DP_SDP_HEADER_SIZE 8
u8 msm_dp_utils_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;
}
u8 msm_dp_utils_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;
}
u8 msm_dp_utils_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 ^ msm_dp_utils_get_g1_value(ci);
x0 = msm_dp_utils_get_g0_value(ci);
}
parity_byte = x1 | (x0 << 4);
return parity_byte;
}
void msm_dp_utils_pack_sdp_header(struct dp_sdp_header *sdp_header, u32 header_buff[2])
{
header_buff[0] = FIELD_PREP(HEADER_0_MASK, sdp_header->HB0) |
FIELD_PREP(PARITY_0_MASK, msm_dp_utils_calculate_parity(sdp_header->HB0)) |
FIELD_PREP(HEADER_1_MASK, sdp_header->HB1) |
FIELD_PREP(PARITY_1_MASK, msm_dp_utils_calculate_parity(sdp_header->HB1));
header_buff[1] = FIELD_PREP(HEADER_2_MASK, sdp_header->HB2) |
FIELD_PREP(PARITY_2_MASK, msm_dp_utils_calculate_parity(sdp_header->HB2)) |
FIELD_PREP(HEADER_3_MASK, sdp_header->HB3) |
FIELD_PREP(PARITY_3_MASK, msm_dp_utils_calculate_parity(sdp_header->HB3));
}
@@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2024, The Linux Foundation. All rights reserved.
*/
#ifndef _DP_UTILS_H_
#define _DP_UTILS_H_
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <drm/display/drm_dp_helper.h>
#define HEADER_BYTE_0_BIT 0
#define PARITY_BYTE_0_BIT 8
#define HEADER_BYTE_1_BIT 16
#define PARITY_BYTE_1_BIT 24
#define HEADER_BYTE_2_BIT 0
#define PARITY_BYTE_2_BIT 8
#define HEADER_BYTE_3_BIT 16
#define PARITY_BYTE_3_BIT 24
#define HEADER_0_MASK GENMASK(7, 0)
#define PARITY_0_MASK GENMASK(15, 8)
#define HEADER_1_MASK GENMASK(23, 16)
#define PARITY_1_MASK GENMASK(31, 24)
#define HEADER_2_MASK GENMASK(7, 0)
#define PARITY_2_MASK GENMASK(15, 8)
#define HEADER_3_MASK GENMASK(23, 16)
#define PARITY_3_MASK GENMASK(31, 24)
u8 msm_dp_utils_get_g0_value(u8 data);
u8 msm_dp_utils_get_g1_value(u8 data);
u8 msm_dp_utils_calculate_parity(u32 data);
void msm_dp_utils_pack_sdp_header(struct dp_sdp_header *sdp_header, u32 header_buff[2]);
#endif /* _DP_UTILS_H_ */