c05564c4d8
Android 13
3965 lines
102 KiB
C
Executable file
3965 lines
102 KiB
C
Executable file
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* Copyright (c) 2019 MediaTek Inc.
|
|
*/
|
|
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/arm-smccc.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/of_graph.h>
|
|
#include <linux/phy/phy.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/component.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/extcon.h>
|
|
#include <../../../extcon/extcon.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/errno.h>
|
|
|
|
#include <drm/drmP.h>
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_crtc.h>
|
|
#include <drm/drm_crtc_helper.h>
|
|
#include <drm/drm_edid.h>
|
|
#include <drm/drm_dp_helper.h>
|
|
#include <drm/mediatek_drm.h>
|
|
|
|
#include "mtk_dp.h"
|
|
#include "mtk_dp_hal.h"
|
|
#include "mtk_drm_drv.h"
|
|
#include "mtk_dp_api.h"
|
|
#include "mtk_dp_reg.h"
|
|
#ifdef DPTX_HDCP_ENABLE
|
|
#include "mtk_dp_hdcp1x.h"
|
|
#include "mtk_dp_hdcp2.h"
|
|
#include "ca/tlcDpHdcp.h"
|
|
#endif
|
|
|
|
static struct mtk_dp *g_mtk_dp;
|
|
static bool fakecablein;
|
|
static int fakeres = FAKE_DEFAULT_RES;
|
|
static int fakebpc = DP_COLOR_DEPTH_8BIT;
|
|
struct mutex dp_lock;
|
|
static bool g_hdcp_on = 1;
|
|
|
|
static const struct drm_display_mode dptx_est_modes[] = {
|
|
/* 2160x3840@60Hz */
|
|
{ DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, 3840, 3860, 3860,
|
|
3880, 0, 2160, 2180, 2180, 2200, 0,
|
|
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), .vrefresh = 60, },
|
|
/* 2160x3840@30Hz */
|
|
{ DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 297000, 3840, 3860, 3860,
|
|
3880, 0, 2160, 2180, 2180, 2200, 0,
|
|
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), .vrefresh = 30, },
|
|
/* 1080x1920@60Hz */
|
|
{ DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
|
|
148500, 1920, 1940, 1940, 1980, 0, 1080, 1120, 1120, 1220, 0,
|
|
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), .vrefresh = 60,},
|
|
/* 1280x720@60Hz */
|
|
{ DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1300, 1300,
|
|
1320, 0, 720, 740, 740, 760, 0,
|
|
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), .vrefresh = 60, }
|
|
};
|
|
|
|
enum DPTX_STATE {
|
|
DPTX_STATE_NO_DEVICE,
|
|
DPTX_STATE_ACTIVE,
|
|
};
|
|
|
|
struct notify_dev {
|
|
const char *name;
|
|
struct device *dev;
|
|
int index;
|
|
int state;
|
|
|
|
ssize_t (*print_name)(struct notify_dev *sdev, char *buf);
|
|
ssize_t (*print_state)(struct notify_dev *sdev, char *buf);
|
|
};
|
|
|
|
struct extcon_dev *dptx_extcon;
|
|
static const unsigned int dptx_cable[] = {
|
|
EXTCON_DISP_HDMI,// audio framework not support DP
|
|
EXTCON_NONE,
|
|
};
|
|
|
|
struct notify_dev dptx_notify_data;
|
|
struct class *switch_class;
|
|
static atomic_t device_count;
|
|
|
|
static ssize_t state_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int ret;
|
|
struct notify_dev *sdev = (struct notify_dev *)
|
|
dev_get_drvdata(dev);
|
|
|
|
if (sdev->print_state) {
|
|
ret = sdev->print_state(sdev, buf);
|
|
if (ret >= 0)
|
|
return ret;
|
|
}
|
|
return sprintf(buf, "%d\n", sdev->state);
|
|
}
|
|
|
|
static ssize_t name_show(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
int ret;
|
|
struct notify_dev *sdev = (struct notify_dev *)
|
|
dev_get_drvdata(dev);
|
|
|
|
if (sdev->print_name) {
|
|
ret = sdev->print_name(sdev, buf);
|
|
if (ret >= 0)
|
|
return ret;
|
|
}
|
|
return sprintf(buf, "%s\n", sdev->name);
|
|
}
|
|
|
|
static DEVICE_ATTR(state, 0444, state_show, NULL);
|
|
static DEVICE_ATTR(name, 0444, name_show, NULL);
|
|
|
|
static int create_switch_class(void)
|
|
{
|
|
if (!switch_class) {
|
|
switch_class = class_create(THIS_MODULE, "switch");
|
|
if (IS_ERR(switch_class))
|
|
return PTR_ERR(switch_class);
|
|
atomic_set(&device_count, 0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int dptx_uevent_dev_register(struct notify_dev *sdev)
|
|
{
|
|
int ret;
|
|
|
|
if (!switch_class) {
|
|
ret = create_switch_class();
|
|
|
|
if (ret == 0)
|
|
DPTXMSG("create_switch_class susesess\n");
|
|
else {
|
|
DPTXERR("create_switch_class fail\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
sdev->index = atomic_inc_return(&device_count);
|
|
sdev->dev = device_create(switch_class, NULL,
|
|
MKDEV(0, sdev->index), NULL, sdev->name);
|
|
|
|
if (sdev->dev) {
|
|
DPTXMSG("device create ok,index:0x%x\n", sdev->index);
|
|
ret = 0;
|
|
} else {
|
|
DPTXERR("device create fail,index:0x%x\n", sdev->index);
|
|
ret = -1;
|
|
}
|
|
|
|
ret = device_create_file(sdev->dev, &dev_attr_state);
|
|
if (ret < 0) {
|
|
device_destroy(switch_class, MKDEV(0, sdev->index));
|
|
DPTXERR("switch: Failed to register driver %s\n",
|
|
sdev->name);
|
|
}
|
|
|
|
ret = device_create_file(sdev->dev, &dev_attr_name);
|
|
if (ret < 0) {
|
|
device_remove_file(sdev->dev, &dev_attr_state);
|
|
DPTXERR("switch: Failed to register driver %s\n",
|
|
sdev->name);
|
|
}
|
|
|
|
dev_set_drvdata(sdev->dev, sdev);
|
|
sdev->state = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int notify_uevent_user(struct notify_dev *sdev, int state)
|
|
{
|
|
char *envp[3];
|
|
char name_buf[120];
|
|
char state_buf[120];
|
|
|
|
if (sdev == NULL)
|
|
return -1;
|
|
|
|
if (sdev->state != state)
|
|
sdev->state = state;
|
|
|
|
snprintf(name_buf, sizeof(name_buf), "SWITCH_NAME=%s", sdev->name);
|
|
envp[0] = name_buf;
|
|
snprintf(state_buf, sizeof(state_buf), "SWITCH_STATE=%d", sdev->state);
|
|
envp[1] = state_buf;
|
|
envp[2] = NULL;
|
|
DPTXMSG("uevent name:%s ,state:%s\n", envp[0], envp[1]);
|
|
|
|
kobject_uevent_env(&sdev->dev->kobj, KOBJ_CHANGE, envp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool mdrv_DPTx_AuxWrite_Bytes(struct mtk_dp *mtk_dp, u8 ubCmd,
|
|
u32 usDPCDADDR, size_t ubLength, BYTE *pData)
|
|
{
|
|
bool bReplyStatus = false;
|
|
u8 ubRetryLimit = 0x7;
|
|
|
|
if (!mtk_dp->training_info.bCablePlugIn ||
|
|
((mtk_dp->training_info.usPHY_STS & (HPD_DISCONNECT)) != 0x0)) {
|
|
|
|
if (!mtk_dp->training_info.bDPTxAutoTest_EN)
|
|
mtk_dp->training_state = DPTX_NTSTATE_CHECKCAP;
|
|
return false;
|
|
}
|
|
|
|
do {
|
|
bReplyStatus = mhal_DPTx_AuxWrite_Bytes(mtk_dp, ubCmd,
|
|
usDPCDADDR, ubLength, pData);
|
|
ubRetryLimit--;
|
|
if (!bReplyStatus) {
|
|
udelay(50);
|
|
DPTXFUNC("Retry Num = %d\n", ubRetryLimit);
|
|
} else
|
|
return true;
|
|
} while (ubRetryLimit > 0);
|
|
|
|
DPTXERR("Aux Write Fail: cmd = %d, addr = 0x%x, len = %d\n",
|
|
ubCmd, usDPCDADDR, ubLength);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool mdrv_DPTx_AuxWrite_DPCD(struct mtk_dp *mtk_dp, u8 ubCmd,
|
|
u32 usDPCDADDR, size_t ubLength, BYTE *pData)
|
|
{
|
|
bool bRet = true;
|
|
size_t times = 0;
|
|
size_t remain = 0;
|
|
size_t loop = 0;
|
|
|
|
if (ubLength > DP_AUX_MAX_PAYLOAD_BYTES) {
|
|
times = ubLength / DP_AUX_MAX_PAYLOAD_BYTES;
|
|
remain = ubLength % DP_AUX_MAX_PAYLOAD_BYTES;
|
|
|
|
for (loop = 0; loop < times; loop++)
|
|
bRet &= mdrv_DPTx_AuxWrite_Bytes(mtk_dp,
|
|
ubCmd,
|
|
usDPCDADDR + (loop * DP_AUX_MAX_PAYLOAD_BYTES),
|
|
DP_AUX_MAX_PAYLOAD_BYTES,
|
|
pData + (loop * DP_AUX_MAX_PAYLOAD_BYTES));
|
|
|
|
if (remain > 0)
|
|
bRet &= mdrv_DPTx_AuxWrite_Bytes(mtk_dp,
|
|
ubCmd,
|
|
usDPCDADDR + (times * DP_AUX_MAX_PAYLOAD_BYTES),
|
|
remain,
|
|
pData + (times * DP_AUX_MAX_PAYLOAD_BYTES));
|
|
} else
|
|
bRet &= mdrv_DPTx_AuxWrite_Bytes(mtk_dp,
|
|
ubCmd,
|
|
usDPCDADDR,
|
|
ubLength,
|
|
pData);
|
|
|
|
if (mtk_dp_debug_get()) {
|
|
DPTXDBG("Aux write cmd = %d, addr = 0x%x, len = %d, %s\n",
|
|
ubCmd, usDPCDADDR, ubLength, bRet ? "Success" : "Fail");
|
|
for (loop = 0; loop < ubLength; loop++)
|
|
DPTXDBG("DPCD%x:0x%x", usDPCDADDR + loop, pData[loop]);
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
bool mdrv_DPTx_AuxRead_Bytes(struct mtk_dp *mtk_dp, u8 ubCmd,
|
|
u32 usDPCDADDR, size_t ubLength, BYTE *pData)
|
|
{
|
|
bool bReplyStatus = false;
|
|
u8 ubRetryLimit = 7;
|
|
|
|
if (!mtk_dp->training_info.bCablePlugIn ||
|
|
((mtk_dp->training_info.usPHY_STS & (HPD_DISCONNECT)) != 0x0)) {
|
|
if (!mtk_dp->training_info.bDPTxAutoTest_EN)
|
|
mtk_dp->training_state = DPTX_NTSTATE_CHECKCAP;
|
|
return false;
|
|
}
|
|
|
|
|
|
do {
|
|
bReplyStatus = mhal_DPTx_AuxRead_Bytes(mtk_dp, ubCmd,
|
|
usDPCDADDR, ubLength, pData);
|
|
if (!bReplyStatus) {
|
|
udelay(50);
|
|
DPTXFUNC("Retry Num = %d\n", ubRetryLimit);
|
|
} else
|
|
return true;
|
|
|
|
ubRetryLimit--;
|
|
} while (ubRetryLimit > 0);
|
|
|
|
DPTXERR("Aux Read Fail: cmd = %d, addr = 0x%x, len = %d\n",
|
|
ubCmd, usDPCDADDR, ubLength);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool mdrv_DPTx_AuxRead_DPCD(struct mtk_dp *mtk_dp, u8 ubCmd,
|
|
u32 usDPCDADDR, size_t ubLength, BYTE *pRxBuf)
|
|
{
|
|
bool bRet = true;
|
|
size_t times = 0;
|
|
size_t remain = 0;
|
|
size_t loop = 0;
|
|
|
|
memset(pRxBuf, 0, ubLength);
|
|
|
|
if (ubLength > DP_AUX_MAX_PAYLOAD_BYTES) {
|
|
times = ubLength / DP_AUX_MAX_PAYLOAD_BYTES;
|
|
remain = ubLength % DP_AUX_MAX_PAYLOAD_BYTES;
|
|
|
|
for (loop = 0; loop < times; loop++)
|
|
bRet &= mdrv_DPTx_AuxRead_Bytes(mtk_dp,
|
|
ubCmd,
|
|
usDPCDADDR + (loop * DP_AUX_MAX_PAYLOAD_BYTES),
|
|
DP_AUX_MAX_PAYLOAD_BYTES,
|
|
pRxBuf + (loop * DP_AUX_MAX_PAYLOAD_BYTES));
|
|
|
|
if (remain > 0)
|
|
bRet &= mdrv_DPTx_AuxRead_Bytes(mtk_dp,
|
|
ubCmd,
|
|
usDPCDADDR + (times * DP_AUX_MAX_PAYLOAD_BYTES),
|
|
remain,
|
|
pRxBuf + (times * DP_AUX_MAX_PAYLOAD_BYTES));
|
|
} else
|
|
bRet &= mdrv_DPTx_AuxRead_Bytes(mtk_dp,
|
|
ubCmd,
|
|
usDPCDADDR,
|
|
ubLength,
|
|
pRxBuf);
|
|
|
|
if (mtk_dp_debug_get()) {
|
|
DPTXDBG("Aux Read cmd = %d, addr = 0x%x, len = %d, %s\n",
|
|
ubCmd, usDPCDADDR, ubLength, bRet ? "Success" : "Fail");
|
|
for (loop = 0; loop < ubLength; loop++)
|
|
DPTXDBG("DPCD%x:0x%x", usDPCDADDR + loop, pRxBuf[loop]);
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
void mdrv_DPTx_deinit(struct mtk_dp *mtk_dp)
|
|
{
|
|
mdrv_DPTx_VideoMute(mtk_dp, true);
|
|
mdrv_DPTx_AudioMute(mtk_dp, true);
|
|
mhal_DPTx_VideoMuteSW(mtk_dp, true);
|
|
cancel_work(&mtk_dp->hdcp_work);
|
|
|
|
mtk_dp->training_info.ucCheckCapTimes = 0;
|
|
mtk_dp->video_enable = false;
|
|
mtk_dp->dp_ready = false;
|
|
mhal_DPTx_PHY_SetIdlePattern(mtk_dp, true);
|
|
if (mtk_dp->has_fec) {
|
|
mhal_DPTx_EnableFEC(mtk_dp, false);
|
|
mtk_dp->has_fec = false;
|
|
}
|
|
|
|
if (mtk_dp->edid != NULL) {
|
|
kfree(mtk_dp->edid);
|
|
mtk_dp->edid = NULL;
|
|
}
|
|
}
|
|
|
|
void mdrv_DPTx_InitVariable(struct mtk_dp *mtk_dp)
|
|
{
|
|
DPTXFUNC();
|
|
mtk_dp->training_info.ubDPSysVersion = DP_VERSION_14;
|
|
mtk_dp->training_info.ubLinkRate = DP_LINKRATE_HBR3;
|
|
mtk_dp->training_info.ubLinkLaneCount = DP_LANECOUNT_2;
|
|
mtk_dp->training_info.bSinkEXTCAP_En = false;
|
|
mtk_dp->training_info.bSinkSSC_En = false;
|
|
mtk_dp->training_info.bTPS3 = true;
|
|
mtk_dp->training_info.bTPS4 = true;
|
|
mtk_dp->training_info.usPHY_STS = HPD_INITIAL_STATE;
|
|
mtk_dp->training_info.bCablePlugIn = false;
|
|
mtk_dp->training_info.bCableStateChange = false;
|
|
mtk_dp->training_state = DPTX_NTSTATE_STARTUP;
|
|
mtk_dp->training_state_pre = DPTX_NTSTATE_STARTUP;
|
|
mtk_dp->state = DPTXSTATE_INITIAL;
|
|
mtk_dp->state_pre = DPTXSTATE_INITIAL;
|
|
|
|
mtk_dp->info.input_src = DPTX_SRC_DPINTF;
|
|
mtk_dp->info.format = DP_COLOR_FORMAT_RGB_444;
|
|
mtk_dp->info.depth = DP_COLOR_DEPTH_8BIT;
|
|
if (!mtk_dp->info.bPatternGen)
|
|
mtk_dp->info.resolution = SINK_1920_1080;
|
|
mtk_dp->info.bSetAudioMute = false;
|
|
mtk_dp->info.bSetVideoMute = false;
|
|
memset(&mtk_dp->info.DPTX_OUTBL, 0,
|
|
sizeof(struct DPTX_TIMING_PARAMETER));
|
|
mtk_dp->info.DPTX_OUTBL.FrameRate = 60;
|
|
mtk_dp->bPowerOn = false;
|
|
mtk_dp->video_enable = false;
|
|
mtk_dp->dp_ready = false;
|
|
mtk_dp->has_dsc = false;
|
|
mtk_dp->has_fec = false;
|
|
mtk_dp->dsc_enable = false;
|
|
|
|
if (!mtk_dp->training_info.set_max_linkrate)
|
|
mdrv_DPTx_CheckMaxLinkRate(mtk_dp);
|
|
}
|
|
|
|
void mdrv_DPTx_SetSDP_DownCntinit(struct mtk_dp *mtk_dp,
|
|
u16 Sram_Read_Start)
|
|
{
|
|
u16 SDP_Down_Cnt_Init = 0x0000;
|
|
u8 ucDCOffset;
|
|
|
|
if (mtk_dp->info.DPTX_OUTBL.PixRateKhz > 0)
|
|
SDP_Down_Cnt_Init = (Sram_Read_Start *
|
|
mtk_dp->training_info.ubLinkRate * 2700 * 8)
|
|
/ (mtk_dp->info.DPTX_OUTBL.PixRateKhz * 4);
|
|
|
|
switch (mtk_dp->training_info.ubLinkLaneCount) {
|
|
case DP_LANECOUNT_1:
|
|
SDP_Down_Cnt_Init = (SDP_Down_Cnt_Init > 0x1A) ?
|
|
SDP_Down_Cnt_Init : 0x1A; //26
|
|
break;
|
|
case DP_LANECOUNT_2:
|
|
// case for LowResolution && High Audio Sample Rate
|
|
ucDCOffset = (mtk_dp->info.DPTX_OUTBL.Vtt <= 525) ?
|
|
0x04 : 0x00;
|
|
SDP_Down_Cnt_Init = (SDP_Down_Cnt_Init > 0x10) ?
|
|
SDP_Down_Cnt_Init : 0x10 + ucDCOffset; //20 or 16
|
|
break;
|
|
case DP_LANECOUNT_4:
|
|
SDP_Down_Cnt_Init = (SDP_Down_Cnt_Init > 0x06) ?
|
|
SDP_Down_Cnt_Init : 0x06; //6
|
|
break;
|
|
default:
|
|
SDP_Down_Cnt_Init = (SDP_Down_Cnt_Init > 0x06) ?
|
|
SDP_Down_Cnt_Init : 0x06;
|
|
break;
|
|
}
|
|
|
|
DPTXMSG("PixRateKhz = %d SDP_DC_Init = %x\n",
|
|
mtk_dp->info.DPTX_OUTBL.PixRateKhz, SDP_Down_Cnt_Init);
|
|
|
|
mhal_DPTx_SetSDP_DownCntinit(mtk_dp, SDP_Down_Cnt_Init);
|
|
}
|
|
|
|
void mdrv_DPTx_SetSDP_DownCntinitInHblanking(struct mtk_dp *mtk_dp)
|
|
{
|
|
int PixClkMhz;
|
|
u8 ucDCOffset;
|
|
|
|
PixClkMhz = (mtk_dp->info.format == DP_COLOR_FORMAT_YUV_420) ?
|
|
mtk_dp->info.DPTX_OUTBL.PixRateKhz/2000 :
|
|
mtk_dp->info.DPTX_OUTBL.PixRateKhz/1000;
|
|
|
|
switch (mtk_dp->training_info.ubLinkLaneCount) {
|
|
case DP_LANECOUNT_1:
|
|
mhal_DPTx_SetSDP_DownCntinitInHblanking(mtk_dp, 0x0020);
|
|
break;
|
|
case DP_LANECOUNT_2:
|
|
ucDCOffset = (mtk_dp->info.DPTX_OUTBL.Vtt <= 525) ? 0x14 : 0x00;
|
|
mhal_DPTx_SetSDP_DownCntinitInHblanking(mtk_dp,
|
|
0x0018 + ucDCOffset);
|
|
break;
|
|
case DP_LANECOUNT_4:
|
|
ucDCOffset = (mtk_dp->info.DPTX_OUTBL.Vtt <= 525) ? 0x08 : 0x00;
|
|
if (PixClkMhz > (mtk_dp->training_info.ubLinkRate * 27)) {
|
|
mhal_DPTx_SetSDP_DownCntinitInHblanking(mtk_dp, 0x0008);
|
|
DPTXMSG("Pixclk > LinkRateChange\n");
|
|
} else {
|
|
mhal_DPTx_SetSDP_DownCntinitInHblanking(mtk_dp,
|
|
0x0010 + ucDCOffset);
|
|
}
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
void mdrv_DPTx_SetTU(struct mtk_dp *mtk_dp)
|
|
{
|
|
int TU_size = 0;
|
|
int NValue = 0;
|
|
int FValue = 0;
|
|
int PixRateMhz = 0;
|
|
u8 ColorBpp;
|
|
u16 Sram_Read_Start = DPTX_TBC_BUF_ReadStartAdrThrd;
|
|
|
|
ColorBpp = mhal_DPTx_GetColorBpp(mtk_dp);
|
|
PixRateMhz = mtk_dp->info.DPTX_OUTBL.PixRateKhz/1000;
|
|
TU_size = (640*(PixRateMhz)*ColorBpp)/
|
|
(mtk_dp->training_info.ubLinkRate * 27 *
|
|
mtk_dp->training_info.ubLinkLaneCount * 8);
|
|
|
|
NValue = TU_size / 10;
|
|
FValue = TU_size-NValue * 10;
|
|
|
|
DPTXDBG("TU_size %d,\n", TU_size);
|
|
if (mtk_dp->training_info.ubLinkLaneCount > 0) {
|
|
Sram_Read_Start = mtk_dp->info.DPTX_OUTBL.Hde /
|
|
(mtk_dp->training_info.ubLinkLaneCount*4*2*2);
|
|
Sram_Read_Start =
|
|
(Sram_Read_Start < DPTX_TBC_BUF_ReadStartAdrThrd) ?
|
|
Sram_Read_Start : DPTX_TBC_BUF_ReadStartAdrThrd;
|
|
mhal_DPTx_SetTU_SramRdStart(mtk_dp, Sram_Read_Start);
|
|
}
|
|
|
|
mhal_DPTx_SetTU_SetEncoder(mtk_dp);
|
|
mdrv_DPTx_SetSDP_DownCntinitInHblanking(mtk_dp);
|
|
mdrv_DPTx_SetSDP_DownCntinit(mtk_dp, Sram_Read_Start);
|
|
}
|
|
|
|
void mdrv_DPTx_CalculateMN(struct mtk_dp *mtk_dp)
|
|
{
|
|
int ubTargetFrameRate = 60;
|
|
int ulTargetPixelclk = 148500000; // default set FHD
|
|
|
|
if (mtk_dp->info.DPTX_OUTBL.FrameRate > 0) {
|
|
ubTargetFrameRate = mtk_dp->info.DPTX_OUTBL.FrameRate;
|
|
ulTargetPixelclk = (int)mtk_dp->info.DPTX_OUTBL.Htt*
|
|
(int)mtk_dp->info.DPTX_OUTBL.Vtt*ubTargetFrameRate;
|
|
} else if (mtk_dp->info.DPTX_OUTBL.PixRateKhz > 0) {
|
|
ulTargetPixelclk = mtk_dp->info.DPTX_OUTBL.PixRateKhz * 1000;
|
|
} else {
|
|
ulTargetPixelclk = (int)mtk_dp->info.DPTX_OUTBL.Htt *
|
|
(int)mtk_dp->info.DPTX_OUTBL.Vtt * ubTargetFrameRate;
|
|
}
|
|
|
|
if (ulTargetPixelclk > 0)
|
|
mtk_dp->info.DPTX_OUTBL.PixRateKhz = ulTargetPixelclk / 1000;
|
|
}
|
|
|
|
void mdrv_DPTx_Set_MISC(struct mtk_dp *mtk_dp)
|
|
{
|
|
u8 format, depth;
|
|
union MISC_T DPTX_MISC;
|
|
|
|
format = mtk_dp->info.format;
|
|
depth = mtk_dp->info.depth;
|
|
|
|
// MISC 0/1 refernce to spec 1.4a p143 Table 2-96
|
|
// MISC0[7:5] color depth
|
|
switch (depth) {
|
|
case DP_COLOR_DEPTH_6BIT:
|
|
case DP_COLOR_DEPTH_8BIT:
|
|
case DP_COLOR_DEPTH_10BIT:
|
|
case DP_COLOR_DEPTH_12BIT:
|
|
case DP_COLOR_DEPTH_16BIT:
|
|
default:
|
|
DPTX_MISC.dp_misc.color_depth = depth;
|
|
break;
|
|
}
|
|
|
|
// MISC0[3]: 0->RGB, 1->YUV
|
|
// MISC0[2:1]: 01b->4:2:2, 10b->4:4:4
|
|
switch (format) {
|
|
case DP_COLOR_FORMAT_YUV_444:
|
|
DPTX_MISC.dp_misc.color_format = 0x1; //01'b
|
|
break;
|
|
|
|
case DP_COLOR_FORMAT_YUV_422:
|
|
DPTX_MISC.dp_misc.color_format = 0x2; //10'b
|
|
break;
|
|
|
|
case DP_COLOR_FORMAT_YUV_420:
|
|
//not support
|
|
break;
|
|
|
|
case DP_COLOR_FORMAT_RAW:
|
|
DPTX_MISC.dp_misc.color_format = 0x1;
|
|
DPTX_MISC.dp_misc.spec_def2 = 0x1;
|
|
break;
|
|
case DP_COLOR_FORMAT_YONLY:
|
|
DPTX_MISC.dp_misc.color_format = 0x0;
|
|
DPTX_MISC.dp_misc.spec_def2 = 0x1;
|
|
break;
|
|
|
|
case DP_COLOR_FORMAT_RGB_444:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
mhal_DPTx_SetMISC(mtk_dp, DPTX_MISC.ucMISC);
|
|
}
|
|
|
|
void mdrv_DPTx_SetDPTXOut(struct mtk_dp *mtk_dp)
|
|
{
|
|
mhal_DPTx_EnableBypassMSA(mtk_dp, false);
|
|
mdrv_DPTx_CalculateMN(mtk_dp);
|
|
|
|
switch (mtk_dp->info.input_src) {
|
|
case DPTX_SRC_PG:
|
|
mhal_DPTx_VideoClock(true, mtk_dp->info.resolution);
|
|
mhal_DPTx_PGEnable(mtk_dp, true);
|
|
mhal_DPTx_Set_MVIDx2(mtk_dp, false);
|
|
DPTXMSG("Set Pattern Gen output\n");
|
|
break;
|
|
|
|
case DPTX_SRC_DPINTF:
|
|
mhal_DPTx_PGEnable(mtk_dp, false);
|
|
DPTXMSG("Set dpintf output\n");
|
|
break;
|
|
|
|
default:
|
|
mhal_DPTx_PGEnable(mtk_dp, true);
|
|
break;
|
|
}
|
|
|
|
mdrv_DPTx_SetTU(mtk_dp);
|
|
}
|
|
|
|
bool mdrv_DPTx_CheckSinkLock(struct mtk_dp *mtk_dp, u8 *pDPCD20x, u8 *pDPCD200C)
|
|
{
|
|
bool bLocked = true;
|
|
|
|
if (mtk_dp->training_info.bSinkEXTCAP_En) {
|
|
switch (mtk_dp->training_info.ubLinkLaneCount) {
|
|
case DP_LANECOUNT_1:
|
|
if ((pDPCD200C[0] & 0x07) != 0x07) {
|
|
bLocked = false;
|
|
DPTXMSG("2L Lose LCOK\n");
|
|
}
|
|
break;
|
|
case DP_LANECOUNT_2:
|
|
if ((pDPCD200C[0] & 0x77) != 0x77) {
|
|
bLocked = false;
|
|
DPTXMSG("2L Lose LCOK\n");
|
|
}
|
|
break;
|
|
case DP_LANECOUNT_4:
|
|
if ((pDPCD200C[0] != 0x77) || (pDPCD200C[1] != 0x77)) {
|
|
bLocked = false;
|
|
DPTXMSG("4L Lose LCOK\n");
|
|
}
|
|
break;
|
|
}
|
|
|
|
if ((pDPCD200C[2]&BIT0) == 0) {
|
|
bLocked = false;
|
|
DPTXMSG("Interskew Lose LCOK\n");
|
|
}
|
|
} else {
|
|
switch (mtk_dp->training_info.ubLinkLaneCount) {
|
|
case DP_LANECOUNT_1:
|
|
if ((pDPCD20x[2] & 0x07) != 0x07) {
|
|
bLocked = false;
|
|
DPTXMSG("1L Lose LCOK\n");
|
|
}
|
|
break;
|
|
case DP_LANECOUNT_2:
|
|
if ((pDPCD20x[2] & 0x77) != 0x77) {
|
|
bLocked = false;
|
|
DPTXMSG("2L Lose LCOK\n");
|
|
}
|
|
break;
|
|
case DP_LANECOUNT_4:
|
|
if (((pDPCD20x[2] != 0x77) || (pDPCD20x[3] != 0x77))) {
|
|
bLocked = false;
|
|
DPTXMSG("4L Lose LCOK\n");
|
|
}
|
|
break;
|
|
}
|
|
|
|
if ((pDPCD20x[4]&BIT0) == 0) {
|
|
bLocked = false;
|
|
DPTXMSG("Interskew Lose LCOK\n");
|
|
}
|
|
}
|
|
|
|
|
|
if (!bLocked) {
|
|
if (!mtk_dp->dp_ready)
|
|
mtk_dp->training_state = DPTX_NTSTATE_CHECKCAP;
|
|
else if (mtk_dp->training_state > DPTX_NTSTATE_TRAINING_PRE)
|
|
mtk_dp->training_state = DPTX_NTSTATE_TRAINING_PRE;
|
|
}
|
|
|
|
return bLocked;
|
|
}
|
|
|
|
void mdrv_DPTx_CheckSinkESI(struct mtk_dp *mtk_dp, u8 *pDPCD20x, u8 *pDPCD2002)
|
|
{
|
|
u8 ubTempValue;
|
|
|
|
if ((pDPCD20x[0x1]&BIT1) || (pDPCD2002[0x1]&BIT1)) {
|
|
#if (DPTX_AutoTest_ENABLE == 0x1)
|
|
if (!mdrv_DPTx_PHY_AutoTest(mtk_dp,
|
|
pDPCD20x[0x1] | pDPCD2002[0x1])) {
|
|
if (mtk_dp->training_state > DPTX_NTSTATE_TRAINING_PRE)
|
|
mtk_dp->training_state
|
|
= DPTX_NTSTATE_TRAINING_PRE;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (pDPCD20x[0x1]&BIT0) { // not support, clrear it.
|
|
ubTempValue = BIT0;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00201, &ubTempValue, 0x1);
|
|
}
|
|
|
|
#ifdef DPTX_HDCP_ENABLE
|
|
if (!(pDPCD20x[0x1]&BIT2) && !(pDPCD2002[0x1]&BIT2))
|
|
return;
|
|
|
|
if (mtk_dp->info.hdcp2_info.bEnable)
|
|
mdrv_DPTx_HDCP2_irq(mtk_dp);
|
|
else if (mtk_dp->info.hdcp1x_info.bEnable)
|
|
mdrv_DPTx_HDCP1X_irq(mtk_dp);
|
|
#endif
|
|
}
|
|
|
|
|
|
#if (DPTX_AutoTest_ENABLE == 1)
|
|
bool mdrv_DPTx_CheckSSC(struct mtk_dp *mtk_dp)
|
|
{
|
|
#if (ENABLE_DPTX_SSC_OUTPUT == 0x1)
|
|
BYTE ubTempBuffer[0x2];
|
|
|
|
drm_dp_dpcd_read(&mtk_dp->aux,
|
|
DPCD_00003 + DPCD_02200*mtk_dp->training_info.bSinkEXTCAP_En,
|
|
ubTempBuffer, 0x1);
|
|
|
|
if (ubTempBuffer[0x0] & 0x1) {
|
|
ubTempBuffer[0x0] = 0x10;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00107, ubTempBuffer, 0x1);
|
|
mtk_dp->info.bSinkSSC_En = true;
|
|
mhal_DPTx_SSCOnOffSetting(mtk_dp, true);
|
|
} else {
|
|
mtk_dp->info.bSinkSSC_En = false;
|
|
mhal_DPTx_SSCOnOffSetting(mtk_dp, false);
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
|
|
#if (DPTX_AutoTest_ENABLE == 0x1) && (DPTX_PHY_TEST_PATTERN_EN == 0x1)
|
|
bool mdrv_DPTx_PHY_AdjustSwingPre(struct mtk_dp *mtk_dp, BYTE ubLaneCount)
|
|
{
|
|
BYTE ubSwingValue;
|
|
BYTE ubPreemphasis;
|
|
BYTE ubTempBuf1[0x2];
|
|
BYTE ubDPCP_Buffer1[0x4];
|
|
bool bReplyStatus = false;
|
|
|
|
memset(ubDPCP_Buffer1, 0x0, sizeof(ubDPCP_Buffer1));
|
|
bReplyStatus = drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00206,
|
|
ubTempBuf1, 0x2);
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00103, ubDPCP_Buffer1, 0x4);
|
|
if (bReplyStatus) {
|
|
if (ubLaneCount >= 0x1) {
|
|
ubSwingValue = (ubTempBuf1[0x0]&0x3);
|
|
ubPreemphasis = ((ubTempBuf1[0x0]&0x0C)>>2);
|
|
//Adjust the swing and pre-emphasis
|
|
mhal_DPTx_SetSwingtPreEmphasis(mtk_dp, DPTx_LANE0,
|
|
ubSwingValue, ubPreemphasis);
|
|
//Adjust the swing and pre-emphasis done,
|
|
//notify Sink Side
|
|
ubDPCP_Buffer1[0x0] =
|
|
ubSwingValue | (ubPreemphasis << 3);
|
|
//MAX_SWING_REACHED
|
|
if (ubSwingValue == DPTx_SWING3)
|
|
ubDPCP_Buffer1[0x0] |= BIT(2);
|
|
|
|
//MAX_PRE-EMPHASIS_REACHED
|
|
if (ubPreemphasis == DPTx_PREEMPHASIS3)
|
|
ubDPCP_Buffer1[0x0] |= BIT(5);
|
|
|
|
}
|
|
|
|
if (ubLaneCount >= 0x2) {
|
|
ubSwingValue = (ubTempBuf1[0x0]&0x30) >> 4;
|
|
ubPreemphasis = ((ubTempBuf1[0x0]&0xC0)>>6);
|
|
//Adjust the swing and pre-emphasis
|
|
mhal_DPTx_SetSwingtPreEmphasis(mtk_dp, DPTx_LANE1,
|
|
ubSwingValue, ubPreemphasis);
|
|
//Adjust the swing and pre-emphasis done,
|
|
//notify Sink Side
|
|
ubDPCP_Buffer1[0x1] =
|
|
ubSwingValue | (ubPreemphasis << 3);
|
|
if (ubSwingValue == DPTx_SWING3) { //MAX_SWING_REACHED
|
|
ubDPCP_Buffer1[0x1] |= BIT(2);
|
|
}
|
|
//MAX_PRE-EMPHASIS_REACHED
|
|
if (ubPreemphasis == DPTx_PREEMPHASIS3)
|
|
ubDPCP_Buffer1[0x1] |= BIT(5);
|
|
|
|
}
|
|
|
|
if (ubLaneCount == 0x4) {
|
|
ubSwingValue = (ubTempBuf1[0x1]&0x3);
|
|
ubPreemphasis = ((ubTempBuf1[0x1]&0x0C)>>2);
|
|
|
|
//Adjust the swing and pre-emphasis
|
|
mhal_DPTx_SetSwingtPreEmphasis(mtk_dp, DPTx_LANE2,
|
|
ubSwingValue, ubPreemphasis);
|
|
//Adjust the swing and pre-emphasis done,
|
|
//notify Sink Side
|
|
ubDPCP_Buffer1[0x2] =
|
|
ubSwingValue | (ubPreemphasis << 3);
|
|
if (ubSwingValue == DPTx_SWING3) { //MAX_SWING_REACHED
|
|
ubDPCP_Buffer1[0x2] |= BIT(2);
|
|
}
|
|
//MAX_PRE-EMPHASIS_REACHED
|
|
if (ubPreemphasis == DPTx_PREEMPHASIS3)
|
|
ubDPCP_Buffer1[0x2] |= BIT(5);
|
|
|
|
|
|
ubSwingValue = (ubTempBuf1[0x1]&0x30) >> 4;
|
|
ubPreemphasis = ((ubTempBuf1[0x1]&0xC0)>>6);
|
|
|
|
//Adjust the swing and pre-emphasis
|
|
mhal_DPTx_SetSwingtPreEmphasis(mtk_dp, DPTx_LANE3,
|
|
ubSwingValue, ubPreemphasis);
|
|
//Adjust the swing and pre-emphasis done,
|
|
//notify Sink Side
|
|
ubDPCP_Buffer1[0x3] =
|
|
ubSwingValue | (ubPreemphasis << 3);
|
|
if (ubSwingValue == DPTx_SWING3) { //MAX_SWING_REACHED
|
|
ubDPCP_Buffer1[0x3] |= BIT(2);
|
|
}
|
|
//MAX_PRE-EMPHASIS_REACHED
|
|
if (ubPreemphasis == DPTx_PREEMPHASIS3)
|
|
ubDPCP_Buffer1[0x3] |= BIT(5);
|
|
|
|
}
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00103,
|
|
ubDPCP_Buffer1, 0x4);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool mdrv_DPTx_PHY_PatternSetting(struct mtk_dp *mtk_dp, BYTE ubPatternType,
|
|
BYTE ubTEST_LANE_COUNT)
|
|
{
|
|
BYTE i;
|
|
BYTE PGdata[5] = {0x1F, 0x7C, 0xF0, 0xC1, 0x07};
|
|
|
|
mhal_DPTx_DataLanePNSwap(mtk_dp, false);
|
|
mhal_DPTx_PHY_ResetPattern(mtk_dp);
|
|
switch (ubPatternType) {
|
|
case PATTERN_NONE:
|
|
//Disable U02 patch code, add interskew for test pattern,
|
|
//lane1/2/3 select lane0 pipe delay
|
|
break;
|
|
case PATTERN_D10_2:
|
|
mhal_DPTx_SetTxTrainingPattern(mtk_dp, BIT(4));
|
|
mhal_DPTx_SetTxLane(mtk_dp, (ubTEST_LANE_COUNT/2));
|
|
break;
|
|
case PATTERN_SYMBOL_ERR:
|
|
break;
|
|
case PATTERN_PRBS7:
|
|
mhal_DPTx_DataLanePNSwap(mtk_dp, true);
|
|
mhal_DPTx_ProgramPatternEnable(mtk_dp, true);
|
|
mhal_DPTx_PRBSEnable(mtk_dp, true);
|
|
mhal_DPTx_ProgramPatternEnable(mtk_dp, true);
|
|
mhal_DPTx_PRBSEnable(mtk_dp, true);
|
|
mhal_DPTx_PatternSelect(mtk_dp, DPTx_PG_PRBS7);
|
|
break;
|
|
case PATTERN_80B:
|
|
for (i = 0x0; i < 4; i++)
|
|
mhal_DPTx_SetProgramPattern(mtk_dp, i, PGdata);
|
|
|
|
mhal_DPTx_ProgramPatternEnable(mtk_dp, true);
|
|
mhal_DPTx_PatternSelect(mtk_dp, DPTx_PG_80bit);
|
|
break;
|
|
case PATTERN_HBR2_COM_EYE:
|
|
mhal_DPTx_ComplianceEyeEnSetting(mtk_dp, true);
|
|
break;
|
|
};
|
|
return true;
|
|
}
|
|
|
|
|
|
struct DP_CTS_AUTO_REQ cts_req;
|
|
bool mdrv_DPTx_Video_PG_AutoTest(struct mtk_dp *mtk_dp)//, BYTE ubDPCD_201)
|
|
{
|
|
BYTE i;
|
|
BYTE dpcd22x[16]; //220~22F
|
|
BYTE dpcd23x[5]; //230~234
|
|
BYTE dpcd27x[10]; //220~22F
|
|
BYTE ucMISC[2] = {0x00};
|
|
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00220, dpcd22x, 16);
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00230, dpcd23x, 5);
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00271, dpcd27x, 10);
|
|
|
|
for (i = 0; i < 10; i++)
|
|
DPTXMSG("dpcd27%d = 0x%x\n", i+1, dpcd27x[i]);
|
|
|
|
cts_req.test_aduio_channel_count = (((dpcd27x[0]) & 0xF0) >> 4) + 1;
|
|
cts_req.test_aduio_samling_rate = ((dpcd27x[0]) & 0x0F) + 1;
|
|
DPTXMSG("channel = %d, sr = %d\n",
|
|
cts_req.test_aduio_channel_count,
|
|
cts_req.test_aduio_samling_rate);
|
|
cts_req.test_pattern = (dpcd22x[1]);
|
|
cts_req.test_h_total = ((dpcd22x[2]<<8) + (dpcd22x[3]));
|
|
cts_req.test_v_total = ((dpcd22x[4]<<8) + (dpcd22x[5]));
|
|
cts_req.test_h_start = ((dpcd22x[6]<<8) + (dpcd22x[7]));
|
|
cts_req.test_v_start = ((dpcd22x[8]<<8) + (dpcd22x[9]));
|
|
cts_req.test_hsync_polarity = ((dpcd22x[0xa] & 0x80)>>7);
|
|
cts_req.test_hsync_width =
|
|
(((dpcd22x[0xa] & 0x7f)<<8) + (dpcd22x[0xb]));
|
|
cts_req.test_vsync_polarity = ((dpcd22x[0xc] & 0x80)>>7);
|
|
cts_req.test_vsync_width =
|
|
(((dpcd22x[0xc] & 0x7f)<<8) + (dpcd22x[0xd]));
|
|
cts_req.test_h_width = ((dpcd22x[0xe]<<8) + (dpcd22x[0xf]));
|
|
cts_req.test_v_height = ((dpcd23x[0]<<8) + (dpcd23x[1]));
|
|
cts_req.test_sync_clk = (dpcd23x[2] & 0x1);
|
|
cts_req.test_color_fmt = ((dpcd23x[2] & 0x6)>>1);
|
|
cts_req.test_dynamic_range = ((dpcd23x[2] & 0x8)>>3);
|
|
cts_req.test_YCbCr_coefficient = ((dpcd23x[2] & 0x10)>>4);
|
|
cts_req.test_bit_depth = ((dpcd23x[2] & 0xe0)>>5);
|
|
cts_req.test_refresh_denominator = (dpcd23x[3] & 0x1);
|
|
cts_req.test_interlaced = ((dpcd23x[3] & 0x2)>>1);
|
|
cts_req.test_refresh_rate_numerator = (dpcd23x[4]);
|
|
|
|
DPTXMSG("[DPTXCTS] test request:\n");
|
|
DPTXMSG("req.test_pattern = %d\n", cts_req.test_pattern);
|
|
DPTXMSG("req.test_h_total = %d\n", cts_req.test_h_total);
|
|
DPTXMSG("req.test_v_total = %d\n", cts_req.test_v_total);
|
|
DPTXMSG("req.test_h_start = %d\n", cts_req.test_h_start);
|
|
DPTXMSG("req.test_v_start = %d\n", cts_req.test_v_start);
|
|
DPTXMSG("req.test_hsync_polarity = %d\n",
|
|
cts_req.test_hsync_polarity);
|
|
DPTXMSG("req.test_hsync_width = d\n",
|
|
cts_req.test_hsync_width);
|
|
DPTXMSG("req.test_vsync_polarity = %d\n",
|
|
cts_req.test_vsync_polarity);
|
|
DPTXMSG("req.test_vsync_width = %d\n",
|
|
cts_req.test_vsync_width);
|
|
DPTXMSG("req.test_h_width = %d\n", cts_req.test_h_width);
|
|
DPTXMSG("req.test_v_height = %d\n", cts_req.test_v_height);
|
|
DPTXMSG("req.test_sync_clk = %d\n", cts_req.test_sync_clk);
|
|
DPTXMSG("req.test_color_fmt = %d\n", cts_req.test_color_fmt);
|
|
DPTXMSG("req.test_dynamic_range = %d\n",
|
|
cts_req.test_dynamic_range);
|
|
DPTXMSG("req.test_YCbCr_coefficient = %d\n",
|
|
cts_req.test_YCbCr_coefficient);
|
|
DPTXMSG("req.test_bit_depth = %d\n", cts_req.test_bit_depth);
|
|
DPTXMSG("req.test_refresh_denominator = %d\n",
|
|
cts_req.test_refresh_denominator);
|
|
DPTXMSG("req.test_interlaced = %d\n",
|
|
cts_req.test_interlaced);
|
|
DPTXMSG("req.test_refresh_rate_numerator = %d\n",
|
|
cts_req.test_refresh_rate_numerator);
|
|
|
|
mtk_dp->info.DPTX_OUTBL.Htt = cts_req.test_h_total;
|
|
mtk_dp->info.DPTX_OUTBL.Hde = cts_req.test_h_width;
|
|
mtk_dp->info.DPTX_OUTBL.Hfp = cts_req.test_h_total
|
|
- cts_req.test_h_start - cts_req.test_h_width;
|
|
mtk_dp->info.DPTX_OUTBL.Hsw = cts_req.test_hsync_width;
|
|
mtk_dp->info.DPTX_OUTBL.bHsp = cts_req.test_hsync_polarity;
|
|
mtk_dp->info.DPTX_OUTBL.Hbp = cts_req.test_h_start
|
|
- cts_req.test_hsync_width;
|
|
mtk_dp->info.DPTX_OUTBL.Vtt = cts_req.test_v_total;
|
|
mtk_dp->info.DPTX_OUTBL.Vde = cts_req.test_v_height;
|
|
mtk_dp->info.DPTX_OUTBL.Vfp = cts_req.test_v_total
|
|
- cts_req.test_v_start - cts_req.test_v_height;
|
|
mtk_dp->info.DPTX_OUTBL.Vsw = cts_req.test_vsync_width;
|
|
mtk_dp->info.DPTX_OUTBL.bVsp = cts_req.test_vsync_polarity;
|
|
mtk_dp->info.DPTX_OUTBL.Vbp = cts_req.test_v_start
|
|
- cts_req.test_vsync_width;
|
|
mtk_dp->info.DPTX_OUTBL.Hbk = cts_req.test_h_total
|
|
- cts_req.test_h_width;
|
|
mtk_dp->info.DPTX_OUTBL.FrameRate = cts_req.test_refresh_rate_numerator;
|
|
mtk_dp->info.DPTX_OUTBL.PixRateKhz = 0;
|
|
mtk_dp->info.DPTX_OUTBL.Video_ip_mode = DPTX_VIDEO_PROGRESSIVE;
|
|
if (cts_req.test_interlaced)
|
|
DPTXMSG("Warning: not support interlace\n");
|
|
|
|
//Clear MISC1&0 except [2:1]Colorfmt & [7:5]ColorDepth
|
|
mhal_DPTx_SetMISC(mtk_dp, ucMISC);
|
|
|
|
mtk_dp->info.format = cts_req.test_color_fmt;
|
|
mtk_dp->info.depth = cts_req.test_bit_depth;
|
|
//mtk_dp->info.DPTX_MISC.dp_misc.spec_def1 = cts_req.test_dynamic_range;
|
|
|
|
mhal_DPTx_SetColorFormat(mtk_dp, mtk_dp->info.format);
|
|
mhal_DPTx_SetColorDepth(mtk_dp, mtk_dp->info.depth);
|
|
mhal_DPTx_SetMSA(mtk_dp);
|
|
mdrv_DPTx_SetDPTXOut(mtk_dp);
|
|
mdrv_DPTx_VideoMute(mtk_dp, false);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void mdrv_DPTx_Audio_PG_AutoTest(struct mtk_dp *mtk_dp)
|
|
{
|
|
BYTE SDP_DB[32] = {0};
|
|
BYTE SDP_HB[4] = {0};
|
|
BYTE ucWordlength = WL_24bit;
|
|
|
|
mhal_DPTx_Audio_SDP_Setting(mtk_dp, cts_req.test_aduio_channel_count);
|
|
mhal_DPTx_Audio_Ch_Status_Set(mtk_dp, cts_req.test_aduio_channel_count,
|
|
cts_req.test_aduio_samling_rate, ucWordlength);
|
|
mdrv_DPTx_I2S_Audio_Enable(mtk_dp, false);
|
|
|
|
SDP_HB[1] = DP_SPEC_SDPTYP_AINFO;
|
|
SDP_HB[2] = 0x1B;
|
|
SDP_HB[3] = 0x48;
|
|
|
|
mdrv_DPTx_SPKG_SDP(mtk_dp, 1, 4, SDP_HB, SDP_DB);
|
|
}
|
|
|
|
bool mdrv_DPTx_PHY_AutoTest(struct mtk_dp *mtk_dp, BYTE ubDPCD_201)
|
|
{
|
|
bool bAutoTestIRQ = false;
|
|
BYTE ubDPCD_218 = 0x0;
|
|
BYTE ubTempBuffer[0x10];
|
|
|
|
#if DPTX_PHY_TEST_PATTERN_EN
|
|
BYTE ubDPCD_248;
|
|
#if (DPTX_TEST_SYMBERR_EN)
|
|
WORD usSYMERRCNT_N; // for sym Error Count
|
|
#endif
|
|
#if (DPTX_TEST_SYMBERR_EN | DPTX_TEST_PRBS7_EN | DPTX_TEST_HBR2EYE_EN | \
|
|
DPTX_TEST_PHY80B_EN)
|
|
// for 1.sym Error Count (DPCD0210-0217) / 2.PHY 80b(DPCD_0250-0259)
|
|
BYTE ubDPCDRDBUF[10];
|
|
#endif
|
|
#endif
|
|
|
|
BYTE ubTEST_LINK_RATE; // DPCD_219
|
|
BYTE ubTEST_LANE_COUNT; // DPCD_220
|
|
|
|
memset(ubTempBuffer, 0x0, sizeof(ubTempBuffer));
|
|
DPTXMSG("PHY_AutoTest Start\n");
|
|
DPTXMSG("DPCD 201 = 0x%x\n", ubDPCD_201);
|
|
|
|
if (ubDPCD_201 & BIT(1)) {
|
|
bAutoTestIRQ = true;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00201, &ubDPCD_201, 0x1);
|
|
mdelay(1);
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00218, &ubDPCD_218, 0x1);
|
|
DPTXMSG("DPCD 218 = 0x%x\n", ubDPCD_218);
|
|
|
|
switch (ubDPCD_218 & 0xFF) {
|
|
case BIT0://TEST_LINK_TRAINING:
|
|
#if DPTX_TEST_LINK_TRAINING_EN
|
|
DPTXMSG("TEST_LINK_TRAINING\n");
|
|
ubTempBuffer[0x0] = 0x01;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00260,
|
|
ubTempBuffer, 0x1);
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00219,
|
|
&ubTEST_LINK_RATE, 0x1);
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00220,
|
|
&ubTEST_LANE_COUNT, 0x1);
|
|
mtk_dp->training_info.bDPTxAutoTest_EN = true;
|
|
|
|
if ((ubTEST_LINK_RATE != 0x0) &&
|
|
(ubTEST_LANE_COUNT != 0x0)) {
|
|
mtk_dp->training_info.ubLinkRate
|
|
= ubTEST_LINK_RATE;
|
|
mtk_dp->training_info.ubLinkLaneCount =
|
|
ubTEST_LANE_COUNT;
|
|
ubTempBuffer[0x0] = 0x0;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00102,
|
|
ubTempBuffer, 0x1);
|
|
ubTempBuffer[0x0] = 0x2;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00600,
|
|
ubTempBuffer, 0x1);
|
|
mdrv_DPTx_SetTrainingStart(mtk_dp);
|
|
}
|
|
return true;
|
|
#endif
|
|
break;
|
|
|
|
case BIT1: //TEST_VIDEO_PATTERN
|
|
#if DPTX_TEST_PATTERN_EN
|
|
DPTXMSG("TEST_PATTERN\n");
|
|
mtk_dp->training_info.bDPTxAutoTest_EN = true;
|
|
mdrv_DPTx_Video_PG_AutoTest(mtk_dp);
|
|
/*
|
|
* ubTempBuffer[0x0] = 0x01;
|
|
* drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00260,
|
|
* ubTempBuffer, 0x1);
|
|
*/
|
|
return true;
|
|
#else
|
|
ubTempBuffer[0x0] = 0x01;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00260,
|
|
ubTempBuffer, 0x1);
|
|
#endif
|
|
break;
|
|
|
|
case (BIT1 | BIT5)://(TEST_VIDEO_PATTERN | TEST_AUDIO_PATTERN)
|
|
DPTXMSG("TEST VIDEO/AUDIO PATTERN\n");
|
|
mtk_dp->training_info.bDPTxAutoTest_EN = true;
|
|
mdrv_DPTx_Video_PG_AutoTest(mtk_dp);
|
|
mdrv_DPTx_Audio_PG_AutoTest(mtk_dp);
|
|
ubTempBuffer[0x0] = 0x01;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00260,
|
|
ubTempBuffer, 0x1);
|
|
return true;
|
|
|
|
case BIT2: //TEST_EDID_READ
|
|
#if DPTX_TEST_EDID_READ_EN
|
|
DPTXMSG("TEST_EDID_R\n");
|
|
if (mtk_dp->edid)
|
|
kfree(mtk_dp->edid)
|
|
mtk_dp->edid = mtk_dp_handle_edid(mtk_dp);
|
|
mdelay(10);
|
|
ubTempBuffer[0x0] = mtk_dp->edid->checksum;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00261,
|
|
ubTempBuffer[0x0] = 0x05;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00260,
|
|
ubTempBuffer, 0x1);
|
|
return true;
|
|
#else
|
|
ubTempBuffer[0x0] = 0x01;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00260,
|
|
ubTempBuffer, 0x1);
|
|
#endif
|
|
break;
|
|
|
|
case BIT3://TEST_PHY_PATTERN
|
|
#if DPTX_PHY_TEST_PATTERN_EN
|
|
DPTXMSG("PHY_TEST_PATTERN");
|
|
drm_dp_dpcd_read(&mtk_dp->aux, 0x00248,
|
|
&ubDPCD_248, 0x1);
|
|
drm_dp_dpcd_read(&mtk_dp->aux, 0x00220,
|
|
&ubTEST_LANE_COUNT, 0x1);
|
|
mdrv_DPTx_PHY_AdjustSwingPre(mtk_dp, ubTEST_LANE_COUNT);
|
|
switch (ubDPCD_248&0x07) {
|
|
case PATTERN_NONE:
|
|
mdrv_DPTx_PHY_PatternSetting(mtk_dp,
|
|
PATTERN_NONE, ubTEST_LANE_COUNT);
|
|
ubTempBuffer[0x0] = 0x01;
|
|
drm_dp_dpcd_write(&mtk_dp->aux,
|
|
DPCD_00260, ubTempBuffer, 0x1);
|
|
break;
|
|
case PATTERN_D10_2:
|
|
#if DPTX_TEST_D10_2_EN
|
|
mdrv_DPTx_PHY_PatternSetting(mtk_dp,
|
|
PATTERN_D10_2, ubTEST_LANE_COUNT);
|
|
mdelay(2);
|
|
ubTempBuffer[0x0] = 0x01;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00260,
|
|
ubTempBuffer, 0x1);
|
|
#else
|
|
ubTempBuffer[0x0] = 0x01;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00260,
|
|
ubTempBuffer, 0x1);
|
|
DPTXMSG("NOT SUPPORT D10.2\n");
|
|
#endif
|
|
break;
|
|
case PATTERN_SYMBOL_ERR:
|
|
#if DPTX_TEST_SYMBERR_EN
|
|
mdrv_DPTx_PHY_PatternSetting(mtk_dp,
|
|
PATTERN_SYMBOL_ERR, ubTEST_LANE_COUNT);
|
|
ubTempBuffer[0x0] = 0x01;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00260,
|
|
ubTempBuffer, 0x1);
|
|
usSYMERRCNT_N = 0x0005;
|
|
mdelay(1000);
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00210,
|
|
ubDPCDRDBUF, 8);
|
|
#else
|
|
ubTempBuffer[0x0] = 0x01;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00260,
|
|
ubTempBuffer, 0x1);
|
|
#endif
|
|
break;
|
|
case PATTERN_PRBS7:
|
|
#if DPTX_TEST_PRBS7_EN
|
|
mdrv_DPTx_PHY_PatternSetting(mtk_dp,
|
|
PATTERN_PRBS7, ubTEST_LANE_COUNT);
|
|
ubTempBuffer[0x0] = 0x01;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00260,
|
|
ubTempBuffer, 0x1);
|
|
mdelay(1000);
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00210,
|
|
ubDPCDRDBUF, 8);
|
|
#else
|
|
ubTempBuffer[0x0] = 0x01;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00260,
|
|
ubTempBuffer, 0x1);
|
|
#endif
|
|
break;
|
|
case PATTERN_80B:
|
|
#if DPTX_TEST_PHY80B_EN
|
|
mdrv_DPTx_PHY_PatternSetting(mtk_dp,
|
|
PATTERN_80B, ubTEST_LANE_COUNT);
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00250,
|
|
ubDPCDRDBUF, 10);
|
|
ubTempBuffer[0x0] = 0x01;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00260,
|
|
ubTempBuffer, 0x1);
|
|
#else
|
|
ubTempBuffer[0x0] = 0x01;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00260,
|
|
ubTempBuffer, 0x1);
|
|
#endif
|
|
break;
|
|
|
|
case PATTERN_HBR2_COM_EYE:
|
|
#if DPTX_TEST_HBR2EYE_EN
|
|
mdrv_DPTx_PHY_PatternSetting(mtk_dp,
|
|
PATTERN_HBR2_COM_EYE,
|
|
ubTEST_LANE_COUNT);
|
|
ubTempBuffer[0x0] = 0x01;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00260,
|
|
ubTempBuffer, 0x1);
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00210,
|
|
ubDPCDRDBUF, 8);
|
|
ubTempBuffer[0x0] = 0x01;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00260,
|
|
ubTempBuffer, 0x1);
|
|
#endif
|
|
break;
|
|
case CP2520_PATTERN3:
|
|
#if DPTX_TEST_CP2520_P3_EN
|
|
mhal_DPTx_SetTxTrainingPattern(mtk_dp, BIT(7));
|
|
mhal_DPTx_SetTxLane(mtk_dp,
|
|
ubTEST_LANE_COUNT / 2);
|
|
#else
|
|
ubTempBuffer[0x0] = 0x01;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00260,
|
|
ubTempBuffer, 0x1);
|
|
#endif
|
|
break;
|
|
default:
|
|
break;
|
|
};
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
DPTXMSG("DPCD 218 Not support\n");
|
|
return false;
|
|
}
|
|
} else {
|
|
bAutoTestIRQ = false;
|
|
return false;
|
|
}
|
|
|
|
mdelay(1);
|
|
mdrv_DPTx_CheckSSC(mtk_dp);
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
uint8_t mdrv_DPTx_getSinkCount(struct mtk_dp *mtk_dp)
|
|
{
|
|
uint8_t temp;
|
|
|
|
if (mtk_dp->training_info.bSinkEXTCAP_En)
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_02002, &temp, 0x1);
|
|
else
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00200, &temp, 0x1);
|
|
|
|
DPTXMSG("Sink Count = %d\n", DP_GET_SINK_COUNT(temp));
|
|
return DP_GET_SINK_COUNT(temp);
|
|
}
|
|
|
|
void mdrv_DPTx_CheckSinkHPDEvent(struct mtk_dp *mtk_dp)
|
|
{
|
|
u8 ubDPCD20x[6];
|
|
u8 ubDPCD2002[2];
|
|
u8 ubDPCD200C[4];
|
|
u8 sink_cnt = 0;
|
|
bool ret;
|
|
|
|
memset(ubDPCD20x, 0x0, sizeof(ubDPCD20x));
|
|
memset(ubDPCD2002, 0x0, sizeof(ubDPCD2002));
|
|
memset(ubDPCD200C, 0x0, sizeof(ubDPCD200C));
|
|
|
|
if (mtk_dp->training_info.bSinkEXTCAP_En) {
|
|
ret = drm_dp_dpcd_read(&mtk_dp->aux, DPCD_02002,
|
|
ubDPCD2002, 0x2);
|
|
if (!ret) {
|
|
DPTXMSG("Read DPCD_02002 Fail\n");
|
|
return;
|
|
}
|
|
|
|
ret = drm_dp_dpcd_read(&mtk_dp->aux, DPCD_0200C,
|
|
ubDPCD200C, 0x4);
|
|
if (!ret) {
|
|
DPTXMSG("Read DPCD_0200C Fail\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
ret = drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00200, ubDPCD20x, 0x6);
|
|
if (!ret) {
|
|
DPTXMSG("Read DPCD200 Fail\n");
|
|
return;
|
|
}
|
|
|
|
sink_cnt = mdrv_DPTx_getSinkCount(mtk_dp);
|
|
if ((sink_cnt != mtk_dp->training_info.ubSinkCountNum) ||
|
|
(ubDPCD200C[0x2] & BIT6 || ubDPCD20x[0x4] & BIT6)) {
|
|
DPTXMSG("New Branch Device Detection!!\n");
|
|
|
|
if (!mtk_dp->bUeventToHwc) {
|
|
mtk_dp->disp_status = DPTX_DISP_NONE;
|
|
mtk_dp_hotplug_uevent(0);
|
|
mtk_dp->bUeventToHwc = true;
|
|
}
|
|
|
|
mdrv_DPTx_deinit(mtk_dp);
|
|
mtk_dp->training_info.ubSinkCountNum = sink_cnt;
|
|
mtk_dp->training_state = DPTX_NTSTATE_STARTUP;
|
|
mdelay(20);
|
|
return;
|
|
}
|
|
|
|
if (sink_cnt == 0) {
|
|
mtk_dp->training_state = DPTX_NTSTATE_STARTUP;
|
|
mdelay(200);
|
|
return;
|
|
}
|
|
|
|
mdrv_DPTx_CheckSinkLock(mtk_dp, ubDPCD20x, ubDPCD200C);
|
|
mdrv_DPTx_CheckSinkESI(mtk_dp, ubDPCD20x, ubDPCD2002);
|
|
}
|
|
|
|
void mdrv_DPTx_CheckMaxLinkRate(struct mtk_dp *mtk_dp)
|
|
{
|
|
switch (mtk_dp->training_info.ubDPSysVersion) {
|
|
case DP_VERSION_11:
|
|
mtk_dp->training_info.ubSysMaxLinkRate = DP_LINKRATE_HBR;
|
|
break;
|
|
case DP_VERSION_12:
|
|
mtk_dp->training_info.ubSysMaxLinkRate = DP_LINKRATE_HBR2;
|
|
break;
|
|
case DP_VERSION_14:
|
|
mtk_dp->training_info.ubSysMaxLinkRate = DP_LINKRATE_HBR3;
|
|
break;
|
|
default:
|
|
mtk_dp->training_info.ubSysMaxLinkRate = DP_LINKRATE_HBR2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void mdrv_DPTx_DisconnetInit(struct mtk_dp *mtk_dp)
|
|
{
|
|
mdrv_DPTx_CheckMaxLinkRate(mtk_dp);
|
|
}
|
|
|
|
void mdrv_DPTx_SPKG_SDP(struct mtk_dp *mtk_dp, bool bEnable, u8 ucSDPType,
|
|
u8 *pHB, u8 *pDB)
|
|
{
|
|
mhal_DPTx_SPKG_SDP(mtk_dp, bEnable, ucSDPType, pHB, pDB);
|
|
}
|
|
|
|
void mdrv_DPTx_PatternSet(bool enable, int resolution)
|
|
{
|
|
g_mtk_dp->info.bPatternGen = enable;
|
|
g_mtk_dp->info.resolution = resolution;
|
|
}
|
|
|
|
void mdrv_DPTx_set_maxlinkrate(bool enable, int maxlinkrate)
|
|
{
|
|
g_mtk_dp->training_info.set_max_linkrate = enable;
|
|
g_mtk_dp->training_info.ubSysMaxLinkRate = maxlinkrate;
|
|
}
|
|
|
|
void mdrv_DPTx_PatternGenTypeSel(struct mtk_dp *mtk_dp, int patternType,
|
|
BYTE BGR, DWORD ColorDepth, BYTE Location)
|
|
{
|
|
WORD Hde, Vde;
|
|
|
|
Hde = mtk_dp->info.DPTX_OUTBL.Hde;
|
|
Vde = mtk_dp->info.DPTX_OUTBL.Vde;
|
|
|
|
switch (patternType) {
|
|
case DPTX_PG_PURE_COLOR:
|
|
mhal_DPTx_PG_Pure_Color(mtk_dp, BGR, ColorDepth);
|
|
break;
|
|
|
|
case DPTX_PG_VERTICAL_RAMPING:
|
|
mhal_DPTx_PG_VerticalRamping(mtk_dp, BGR, ColorDepth, Location);
|
|
break;
|
|
|
|
case DPTX_PG_HORIZONTAL_RAMPING:
|
|
mhal_DPTx_PG_HorizontalRamping(mtk_dp, BGR,
|
|
ColorDepth, Location);
|
|
break;
|
|
|
|
case DPTX_PG_VERTICAL_COLOR_BAR:
|
|
mhal_DPTx_PG_VerticalColorBar(mtk_dp, Location);
|
|
break;
|
|
|
|
case DPTX_PG_HORIZONTAL_COLOR_BAR:
|
|
mhal_DPTx_PG_HorizontalColorBar(mtk_dp, Location);
|
|
break;
|
|
|
|
case DPTX_PG_CHESSBOARD_PATTERN:
|
|
mhal_DPTx_PG_Chessboard(mtk_dp, Location, Hde, Vde);
|
|
break;
|
|
|
|
case DPTX_PG_SUB_PIXEL_PATTERN:
|
|
mhal_DPTx_PG_SubPixel(mtk_dp, Location);
|
|
break;
|
|
|
|
case DPTX_PG_FRAME_PATTERN:
|
|
mhal_DPTx_PG_Frame(mtk_dp, Location, Hde, Vde);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void mdrv_DPTx_StopSentSDP(struct mtk_dp *mtk_dp)
|
|
{
|
|
u8 ubPkgType;
|
|
|
|
for (ubPkgType = DPTx_SDPTYP_ACM ; ubPkgType < DPTx_SDPTYP_MAX_NUM;
|
|
ubPkgType++)
|
|
mhal_DPTx_SPKG_SDP(mtk_dp, false, ubPkgType, NULL, NULL);
|
|
|
|
mhal_DPTx_SPKG_VSC_EXT_VESA(mtk_dp, false, 0x00, NULL);
|
|
mhal_DPTx_SPKG_VSC_EXT_CEA(mtk_dp, false, 0x00, NULL);
|
|
|
|
DPTXFUNC();
|
|
}
|
|
|
|
int mdrv_DPTx_HPD_HandleInThread(struct mtk_dp *mtk_dp)
|
|
{
|
|
int ret = DPTX_NOERR;
|
|
|
|
if (mtk_dp->training_info.bCableStateChange) {
|
|
bool ubCurrentHPD = mhal_DPTx_GetHPDPinLevel(mtk_dp);
|
|
|
|
mtk_dp->training_info.bCableStateChange = false;
|
|
|
|
if (mtk_dp->training_info.bCablePlugIn && ubCurrentHPD) {
|
|
DPTXMSG("HPD_CON\n");
|
|
} else {
|
|
DPTXMSG("HPD_DISCON\n");
|
|
mdrv_DPTx_VideoMute(mtk_dp, true);
|
|
mdrv_DPTx_AudioMute(mtk_dp, true);
|
|
|
|
if (mtk_dp->bUeventToHwc) {
|
|
mtk_dp_hotplug_uevent(0);
|
|
mtk_dp->bUeventToHwc = false;
|
|
mtk_dp->disp_status = DPTX_DISP_NONE;
|
|
} else
|
|
DPTXMSG("Skip uevent(0)\n");
|
|
|
|
cancel_work(&mtk_dp->hdcp_work);
|
|
|
|
#ifdef DPTX_HDCP_ENABLE
|
|
if (mtk_dp->info.hdcp2_info.bEnable)
|
|
mdrv_DPTx_HDCP2_SetStartAuth(mtk_dp, false);
|
|
else if (mtk_dp->info.hdcp1x_info.bEnable)
|
|
mdrv_DPTx_HDCP1X_SetStartAuth(mtk_dp, false);
|
|
|
|
tee_removeDevice();
|
|
#endif
|
|
|
|
mdrv_DPTx_InitVariable(mtk_dp);
|
|
mhal_DPTx_PHY_SetIdlePattern(mtk_dp, true);
|
|
if (mtk_dp->has_fec)
|
|
mhal_DPTx_EnableFEC(mtk_dp, false);
|
|
mdrv_DPTx_StopSentSDP(mtk_dp);
|
|
mhal_DPTx_AnalogPowerOnOff(mtk_dp, false);
|
|
|
|
DPTXMSG("Power OFF %d", mtk_dp->bPowerOn);
|
|
clk_disable_unprepare(mtk_dp->dp_tx_clk);
|
|
if (mtk_dp->info.bPatternGen)
|
|
mhal_DPTx_VideoClock(false,
|
|
mtk_dp->info.resolution);
|
|
|
|
fakecablein = false;
|
|
fakeres = FAKE_DEFAULT_RES;
|
|
fakebpc = DP_COLOR_DEPTH_8BIT;
|
|
|
|
kfree(mtk_dp->edid);
|
|
mtk_dp->edid = NULL;
|
|
ret = DPTX_PLUG_OUT;
|
|
}
|
|
}
|
|
|
|
if (mtk_dp->training_info.usPHY_STS & HPD_INT_EVNET) {
|
|
DPTXMSG("HPD_INT_EVNET\n");
|
|
mtk_dp->training_info.usPHY_STS &= ~HPD_INT_EVNET;
|
|
mdrv_DPTx_CheckSinkHPDEvent(mtk_dp);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void mdrv_DPTx_VideoMute(struct mtk_dp *mtk_dp, bool bENABLE)
|
|
{
|
|
mtk_dp->info.bVideoMute = (mtk_dp->info.bSetVideoMute) ?
|
|
true : bENABLE;
|
|
mhal_DPTx_VideoMute(mtk_dp, mtk_dp->info.bVideoMute);
|
|
}
|
|
|
|
void mdrv_DPTx_AudioMute(struct mtk_dp *mtk_dp, bool bENABLE)
|
|
{
|
|
mtk_dp->info.bAudioMute = (mtk_dp->info.bSetAudioMute) ?
|
|
true : bENABLE;
|
|
mhal_DPTx_AudioMute(mtk_dp, mtk_dp->info.bAudioMute);
|
|
}
|
|
|
|
bool mdrv_DPTx_TrainingCheckSwingPre(struct mtk_dp *mtk_dp,
|
|
u8 ubTargetLaneCount, u8 *ubDPCP202_x, u8 *ubDPCP_Buffer1)
|
|
{
|
|
u8 ubSwingValue;
|
|
u8 ubPreemphasis;
|
|
|
|
if (ubTargetLaneCount >= 0x1) { //lane0
|
|
ubSwingValue = (ubDPCP202_x[0x4]&0x3);
|
|
ubPreemphasis = ((ubDPCP202_x[0x4]&0x0C)>>2);
|
|
//Adjust the swing and pre-emphasis
|
|
mhal_DPTx_SetSwingtPreEmphasis(mtk_dp, DPTx_LANE0,
|
|
ubSwingValue, ubPreemphasis);
|
|
//Adjust the swing and pre-emphasis done, notify Sink Side
|
|
ubDPCP_Buffer1[0x0] = ubSwingValue | (ubPreemphasis << 3);
|
|
if (ubSwingValue == DPTx_SWING3) { //MAX_SWING_REACHED
|
|
ubDPCP_Buffer1[0x0] |= BIT2;
|
|
}
|
|
//MAX_PRE-EMPHASIS_REACHED
|
|
if (ubPreemphasis == DPTx_PREEMPHASIS3)
|
|
ubDPCP_Buffer1[0x0] |= BIT5;
|
|
|
|
}
|
|
|
|
if (ubTargetLaneCount >= 0x2) { //lane1
|
|
ubSwingValue = (ubDPCP202_x[0x4]&0x30) >> 4;
|
|
ubPreemphasis = ((ubDPCP202_x[0x4]&0xC0)>>6);
|
|
//Adjust the swing and pre-emphasis
|
|
mhal_DPTx_SetSwingtPreEmphasis(mtk_dp, DPTx_LANE1,
|
|
ubSwingValue, ubPreemphasis);
|
|
//Adjust the swing and pre-emphasis done, notify Sink Side
|
|
ubDPCP_Buffer1[0x1] = ubSwingValue | (ubPreemphasis << 3);
|
|
if (ubSwingValue == DPTx_SWING3) { //MAX_SWING_REACHED
|
|
ubDPCP_Buffer1[0x1] |= BIT2;
|
|
}
|
|
//MAX_PRE-EMPHASIS_REACHED
|
|
if (ubPreemphasis == DPTx_PREEMPHASIS3)
|
|
ubDPCP_Buffer1[0x1] |= BIT5;
|
|
|
|
}
|
|
|
|
if (ubTargetLaneCount == 0x4) { //lane 2,3
|
|
ubSwingValue = (ubDPCP202_x[0x5]&0x3);
|
|
ubPreemphasis = ((ubDPCP202_x[0x5]&0x0C)>>2);
|
|
|
|
//Adjust the swing and pre-emphasis
|
|
mhal_DPTx_SetSwingtPreEmphasis(mtk_dp, DPTx_LANE2,
|
|
(ubDPCP202_x[0x5]&0x3), ((ubDPCP202_x[0x5]&0x0C)>>2));
|
|
//Adjust the swing and pre-emphasis done, notify Sink Side
|
|
ubDPCP_Buffer1[0x2] = ubSwingValue | (ubPreemphasis << 3);
|
|
if (ubSwingValue == DPTx_SWING3) { //MAX_SWING_REACHED
|
|
ubDPCP_Buffer1[0x2] |= BIT2;
|
|
}
|
|
//MAX_PRE-EMPHASIS_REACHED
|
|
if (ubPreemphasis == DPTx_PREEMPHASIS3)
|
|
ubDPCP_Buffer1[0x2] |= BIT5;
|
|
|
|
|
|
ubSwingValue = (ubDPCP202_x[0x5]&0x30) >> 4;
|
|
ubPreemphasis = ((ubDPCP202_x[0x5]&0xC0)>>6);
|
|
|
|
//Adjust the swing and pre-emphasis
|
|
mhal_DPTx_SetSwingtPreEmphasis(mtk_dp, DPTx_LANE3,
|
|
((ubDPCP202_x[0x5]&0x30)>>4),
|
|
((ubDPCP202_x[0x5]&0xC0)>>6));
|
|
//Adjust the swing and pre-emphasis done, notify Sink Side
|
|
ubDPCP_Buffer1[0x3] = ubSwingValue | (ubPreemphasis << 3);
|
|
if (ubSwingValue == DPTx_SWING3) { //MAX_SWING_REACHED
|
|
ubDPCP_Buffer1[0x3] |= BIT2;
|
|
}
|
|
//MAX_PRE-EMPHASIS_REACHED
|
|
if (ubPreemphasis == DPTx_PREEMPHASIS3)
|
|
ubDPCP_Buffer1[0x3] |= BIT5;
|
|
}
|
|
|
|
//Wait signal stable enough
|
|
mdelay(2);
|
|
return true;
|
|
}
|
|
|
|
void mdrv_DPTx_Print_TrainingState(u8 state)
|
|
{
|
|
switch (state) {
|
|
case DPTX_NTSTATE_STARTUP:
|
|
DPTXMSG("NTSTATE_STARTUP!\n");
|
|
break;
|
|
case DPTX_NTSTATE_CHECKCAP:
|
|
DPTXMSG("NTSTATE_CHECKCAP!\n");
|
|
break;
|
|
case DPTX_NTSTATE_CHECKEDID:
|
|
DPTXMSG("NTSTATE_CHECKEDID!\n");
|
|
break;
|
|
case DPTX_NTSTATE_TRAINING_PRE:
|
|
DPTXMSG("NTSTATE_TRAINING_PRE!\n");
|
|
break;
|
|
case DPTX_NTSTATE_TRAINING:
|
|
DPTXMSG("NTSTATE_TRAINING!\n");
|
|
break;
|
|
case DPTX_NTSTATE_CHECKTIMING:
|
|
DPTXMSG("NTSTATE_CHECKTIMING!\n");
|
|
break;
|
|
case DPTX_NTSTATE_NORMAL:
|
|
DPTXMSG("NTSTATE_NORMAL!\n");
|
|
break;
|
|
case DPTX_NTSTATE_POWERSAVE:
|
|
DPTXMSG("NTSTATE_POWERSAVE!\n");
|
|
break;
|
|
case DPTX_NTSTATE_DPIDLE:
|
|
DPTXMSG("NTSTATE_DPIDLE!\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
int mdrv_DPTx_TrainingFlow(struct mtk_dp *mtk_dp, u8 ubLaneRate, u8 ubLaneCount)
|
|
{
|
|
u8 ubTempValue[0x6];
|
|
u8 ubDPCD200C[0x3];
|
|
u8 ubTargetLinkRate = ubLaneRate;
|
|
u8 ubTargetLaneCount = ubLaneCount;
|
|
u8 ubDPCP_Buffer1[0x4];
|
|
u8 bPassTPS1 = false;
|
|
u8 bPassTPS2_3 = false;
|
|
u8 ubTrainRetryTimes;
|
|
u8 ubStatusControl;
|
|
u8 ubIterationCount;
|
|
u8 ubDPCD206;
|
|
|
|
memset(ubTempValue, 0x0, sizeof(ubTempValue));
|
|
memset(ubDPCP_Buffer1, 0x0, sizeof(ubDPCP_Buffer1));
|
|
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00600, ubTempValue, 0x1);
|
|
if (ubTempValue[0] != 0x01) {
|
|
ubTempValue[0] = 0x01;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00600, ubTempValue, 0x1);
|
|
mdelay(1);
|
|
}
|
|
|
|
ubTempValue[0] = ubTargetLinkRate;
|
|
ubTempValue[1] = ubTargetLaneCount | DPTX_AUX_SET_ENAHNCED_FRAME;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00100, ubTempValue, 0x2);
|
|
|
|
if (mtk_dp->training_info.bSinkSSC_En) {
|
|
ubTempValue[0x0] = 0x10;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00107, ubTempValue, 0x1);
|
|
}
|
|
|
|
ubTrainRetryTimes = 0x0;
|
|
ubStatusControl = 0x0;
|
|
ubIterationCount = 0x1;
|
|
ubDPCD206 = 0xFF;
|
|
|
|
mhal_DPTx_SetTxLane(mtk_dp, ubTargetLaneCount/2);
|
|
mhal_DPTx_SetTxRate(mtk_dp, ubTargetLinkRate);
|
|
|
|
do {
|
|
ubTrainRetryTimes++;
|
|
if (!mtk_dp->training_info.bCablePlugIn ||
|
|
((mtk_dp->training_info.usPHY_STS & HPD_DISCONNECT)
|
|
!= 0x0)) {
|
|
DPTXMSG("Training Abort! HPD is low\n");
|
|
return DPTX_PLUG_OUT;
|
|
}
|
|
|
|
if (mtk_dp->training_info.usPHY_STS & HPD_INT_EVNET)
|
|
mdrv_DPTx_HPD_HandleInThread(mtk_dp);
|
|
|
|
if (mtk_dp->training_state < DPTX_NTSTATE_TRAINING)
|
|
return DPTX_RETRANING;
|
|
|
|
if (!bPassTPS1) {
|
|
DPTXMSG("CR Training START\n");
|
|
mhal_DPTx_SetScramble(mtk_dp, false);
|
|
|
|
if (ubStatusControl == 0x0) {
|
|
mhal_DPTx_SetTxTrainingPattern(mtk_dp, BIT4);
|
|
ubStatusControl = 0x1;
|
|
ubTempValue[0] = 0x21;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00102,
|
|
ubTempValue, 0x1);
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00206,
|
|
(ubTempValue+4), 0x2);
|
|
ubIterationCount++;
|
|
|
|
mdrv_DPTx_TrainingCheckSwingPre(mtk_dp,
|
|
ubTargetLaneCount, ubTempValue,
|
|
ubDPCP_Buffer1);
|
|
}
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00103,
|
|
ubDPCP_Buffer1, ubTargetLaneCount);
|
|
|
|
drm_dp_link_train_clock_recovery_delay(mtk_dp->rx_cap);
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00202,
|
|
ubTempValue, 0x6);
|
|
|
|
if (mtk_dp->training_info.bSinkEXTCAP_En) {
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_0200C,
|
|
ubDPCD200C, 0x3);
|
|
ubTempValue[0] = ubDPCD200C[0];
|
|
ubTempValue[1] = ubDPCD200C[1];
|
|
ubTempValue[2] = ubDPCD200C[2];
|
|
}
|
|
|
|
if (drm_dp_clock_recovery_ok(ubTempValue,
|
|
ubTargetLaneCount)) {
|
|
DPTXMSG("CR Training Success\n");
|
|
mtk_dp->training_info.cr_done = true;
|
|
bPassTPS1 = true;
|
|
ubTrainRetryTimes = 0x0;
|
|
ubIterationCount = 0x1;
|
|
} else {
|
|
//request swing & emp is the same eith last time
|
|
if (ubDPCD206 == ubTempValue[0x4]) {
|
|
ubIterationCount++;
|
|
if (ubDPCD206&0x3)
|
|
ubIterationCount =
|
|
DPTX_TRAIN_MAX_ITERATION;
|
|
} else {
|
|
ubDPCD206 = ubTempValue[0x4];
|
|
}
|
|
|
|
DPTXMSG("CR Training Fail\n");
|
|
}
|
|
} else if (bPassTPS1 && !bPassTPS2_3) {
|
|
DPTXMSG("EQ Training START\n");
|
|
if (ubStatusControl == 0x1) {
|
|
if (mtk_dp->training_info.bTPS4) {
|
|
mhal_DPTx_SetTxTrainingPattern(mtk_dp,
|
|
BIT7);
|
|
DPTXMSG("LT TPS4\n");
|
|
} else if (mtk_dp->training_info.bTPS3) {
|
|
mhal_DPTx_SetTxTrainingPattern(mtk_dp,
|
|
BIT6);
|
|
DPTXMSG("LT TP3\n");
|
|
} else {
|
|
mhal_DPTx_SetTxTrainingPattern(mtk_dp,
|
|
BIT5);
|
|
DPTXMSG("LT TPS2\n");
|
|
}
|
|
|
|
if (mtk_dp->training_info.bTPS4) {
|
|
ubTempValue[0] = 0x07;
|
|
drm_dp_dpcd_write(&mtk_dp->aux,
|
|
DPCD_00102, ubTempValue, 0x1);
|
|
} else if (mtk_dp->training_info.bTPS3) {
|
|
ubTempValue[0] = 0x23;
|
|
drm_dp_dpcd_write(&mtk_dp->aux,
|
|
DPCD_00102, ubTempValue, 0x1);
|
|
} else {
|
|
ubTempValue[0] = 0x22;
|
|
drm_dp_dpcd_write(&mtk_dp->aux,
|
|
DPCD_00102, ubTempValue, 0x1);
|
|
}
|
|
|
|
ubStatusControl = 0x2;
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00206,
|
|
(ubTempValue+4), 0x2);
|
|
|
|
ubIterationCount++;
|
|
mdrv_DPTx_TrainingCheckSwingPre(mtk_dp,
|
|
ubTargetLaneCount, ubTempValue,
|
|
ubDPCP_Buffer1);
|
|
}
|
|
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00103,
|
|
ubDPCP_Buffer1, ubTargetLaneCount);
|
|
drm_dp_link_train_channel_eq_delay(mtk_dp->rx_cap);
|
|
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00202,
|
|
ubTempValue, 0x6);
|
|
if (mtk_dp->training_info.bSinkEXTCAP_En) {
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_0200C,
|
|
ubDPCD200C, 0x3);
|
|
ubTempValue[0] |= ubDPCD200C[0];
|
|
ubTempValue[1] |= ubDPCD200C[1];
|
|
ubTempValue[2] |= ubDPCD200C[2];
|
|
}
|
|
|
|
if (!drm_dp_clock_recovery_ok(ubTempValue,
|
|
ubTargetLaneCount)) {
|
|
mtk_dp->training_info.cr_done = false;
|
|
mtk_dp->training_info.eq_done = false;
|
|
break;
|
|
}
|
|
|
|
if (drm_dp_channel_eq_ok(ubTempValue,
|
|
ubTargetLaneCount)) {
|
|
mtk_dp->training_info.eq_done = true;
|
|
bPassTPS2_3 = true;
|
|
DPTXMSG("EQ Training Success\n");
|
|
break;
|
|
} else
|
|
DPTXMSG("EQ Training Fail\n");
|
|
|
|
if (ubDPCD206 == ubTempValue[0x4])
|
|
ubIterationCount++;
|
|
else
|
|
ubDPCD206 = ubTempValue[0x4];
|
|
}
|
|
|
|
mdrv_DPTx_TrainingCheckSwingPre(mtk_dp, ubTargetLaneCount,
|
|
ubTempValue, ubDPCP_Buffer1);
|
|
DPTXMSG("ubTrainRetryTimes = %d, ubIterationCount = %d\n",
|
|
ubTrainRetryTimes, ubIterationCount);
|
|
|
|
} while ((ubTrainRetryTimes < DPTX_TRAIN_RETRY_LIMIT) &&
|
|
(ubIterationCount < DPTX_TRAIN_MAX_ITERATION));
|
|
|
|
ubTempValue[0] = 0x0;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00102, ubTempValue, 0x1);
|
|
mhal_DPTx_SetTxTrainingPattern(mtk_dp, 0);
|
|
|
|
if (bPassTPS2_3) {
|
|
mtk_dp->training_info.ubLinkRate = ubTargetLinkRate;
|
|
mtk_dp->training_info.ubLinkLaneCount = ubTargetLaneCount;
|
|
|
|
mhal_DPTx_SetScramble(mtk_dp, true);
|
|
|
|
ubTempValue[0] = ubTargetLaneCount
|
|
| DPTX_AUX_SET_ENAHNCED_FRAME;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00101, ubTempValue, 0x1);
|
|
mhal_DPTx_SetEF_Mode(mtk_dp, ENABLE_DPTX_EF_MODE);
|
|
|
|
DPTXMSG("Link Training PASS\n");
|
|
return DPTX_NOERR;
|
|
}
|
|
|
|
DPTXMSG("Link Training Fail\n");
|
|
return DPTX_TRANING_FAIL;
|
|
}
|
|
|
|
bool mdrv_DPTx_CheckSinkCap(struct mtk_dp *mtk_dp)
|
|
{
|
|
u8 bTempBuffer[0x10];
|
|
|
|
if (!mhal_DPTx_GetHPDPinLevel(mtk_dp))
|
|
return false;
|
|
|
|
memset(bTempBuffer, 0x0, sizeof(bTempBuffer));
|
|
|
|
bTempBuffer[0x0] = 0x1;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00600, bTempBuffer, 0x1);
|
|
mdelay(2);
|
|
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00000, bTempBuffer, 0x10);
|
|
|
|
mtk_dp->training_info.bSinkEXTCAP_En = (bTempBuffer[0x0E]&BIT7) ?
|
|
true : false;
|
|
if (mtk_dp->training_info.bSinkEXTCAP_En)
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_02200, bTempBuffer, 0x10);
|
|
|
|
mtk_dp->training_info.ubDPCD_REV = bTempBuffer[0x0];
|
|
DPTXMSG("SINK DPCD version:0x%x\n", mtk_dp->training_info.ubDPCD_REV);
|
|
|
|
memcpy(mtk_dp->rx_cap, bTempBuffer, 0x10);
|
|
mtk_dp->rx_cap[0xe] &= 0x7F;
|
|
|
|
if (mtk_dp->training_info.ubDPCD_REV >= 0x14) {
|
|
mdrv_DPTx_FEC_Ready(mtk_dp, FEC_BIT_ERROR_COUNT);
|
|
mdrv_DPTx_DSC_Support(mtk_dp);
|
|
}
|
|
|
|
if (!mtk_dp->has_dsc || !mtk_dp->has_fec)
|
|
mtk_dp_enable_4k60(false);
|
|
else
|
|
mtk_dp_enable_4k60(true);
|
|
|
|
#if !ENABLE_DPTX_FIX_LRLC
|
|
mtk_dp->training_info.ubLinkRate =
|
|
(bTempBuffer[0x1] >= mtk_dp->training_info.ubSysMaxLinkRate) ?
|
|
mtk_dp->training_info.ubSysMaxLinkRate : bTempBuffer[0x1];
|
|
mtk_dp->training_info.ubLinkLaneCount =
|
|
((bTempBuffer[0x2]&0x1F) >= MAX_LANECOUNT) ?
|
|
MAX_LANECOUNT : (bTempBuffer[0x2]&0x1F);
|
|
#endif
|
|
|
|
#if ENABLE_DPTX_FIX_TPS2
|
|
mtk_dp->training_info.bTPS3 = 0;
|
|
mtk_dp->training_info.bTPS4 = 0;
|
|
#else
|
|
mtk_dp->training_info.bTPS3 = (bTempBuffer[0x2]&BIT6)>>0x6;
|
|
mtk_dp->training_info.bTPS4 = (bTempBuffer[0x3]&BIT7)>>0x7;
|
|
#endif
|
|
mtk_dp->training_info.bDWN_STRM_PORT_PRESENT
|
|
= (bTempBuffer[0x5] & BIT0);
|
|
|
|
#if (ENABLE_DPTX_SSC_OUTPUT == 0x1)
|
|
if ((bTempBuffer[0x3] & BIT0) == 0x1) {
|
|
mtk_dp->training_info.bSinkSSC_En = true;
|
|
DPTXMSG("SINK SUPPORT SSC!\n");
|
|
} else {
|
|
mtk_dp->training_info.bSinkSSC_En = false;
|
|
DPTXMSG("SINK NOT SUPPORT SSC!\n");
|
|
}
|
|
#endif
|
|
|
|
#if ENABLE_DPTX_SSC_FORCEON
|
|
DPTXMSG("FORCE SSC ON !!!\n");
|
|
mtk_dp->training_info.bSinkSSC_En = true;
|
|
#endif
|
|
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00021, bTempBuffer, 0x1);
|
|
mtk_dp->training_info.bDPMstCAP = (bTempBuffer[0x0] & BIT0);
|
|
mtk_dp->training_info.bDPMstBranch = false;
|
|
|
|
if (mtk_dp->training_info.bDPMstCAP == BIT0) {
|
|
if (mtk_dp->training_info.bDWN_STRM_PORT_PRESENT == 0x1)
|
|
mtk_dp->training_info.bDPMstBranch = true;
|
|
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_02003, bTempBuffer, 0x1);
|
|
|
|
if (bTempBuffer[0x0] != 0x0)
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_02003,
|
|
bTempBuffer, 0x1);
|
|
}
|
|
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00600, bTempBuffer, 0x1);
|
|
if (bTempBuffer[0x0] != 0x1) {
|
|
bTempBuffer[0x0] = 0x1;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00600, bTempBuffer, 0x1);
|
|
}
|
|
|
|
mtk_dp->training_info.ubSinkCountNum = mdrv_DPTx_getSinkCount(mtk_dp);
|
|
//if (mtk_dp->training_info.ubSinkCountNum == 0)
|
|
// return false;
|
|
|
|
if (!mtk_dp->training_info.bDPMstBranch) {
|
|
u8 ubDPCD_201;
|
|
|
|
drm_dp_dpcd_read(&mtk_dp->aux, DPCD_00201, &ubDPCD_201, 1);
|
|
if (ubDPCD_201 & BIT1) {
|
|
#if (DPTX_AutoTest_ENABLE == 0x1)
|
|
mdrv_DPTx_PHY_AutoTest(mtk_dp, ubDPCD_201);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
unsigned int force_ch, force_fs, force_len;
|
|
unsigned int mdrv_DPTx_getAudioCaps(struct mtk_dp *mtk_dp)
|
|
{
|
|
struct cea_sad *sads;
|
|
int sad_count, i, j;
|
|
unsigned int caps = 0;
|
|
|
|
if (mtk_dp->edid == NULL) {
|
|
DPTXERR("EDID not found!\n");
|
|
return 0;
|
|
}
|
|
|
|
sad_count = drm_edid_to_sad(mtk_dp->edid, &sads);
|
|
if (sad_count <= 0) {
|
|
DPTXMSG("The SADs is NULL\n");
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < sad_count; i++) {
|
|
if (sads[i].format == 0x01) {
|
|
for (j = 0; j < sads[i].channels; j++)
|
|
caps |= ((1 << j) <<
|
|
DP_CAPABILITY_CHANNEL_SFT) &
|
|
(DP_CAPABILITY_CHANNEL_MASK <<
|
|
DP_CAPABILITY_CHANNEL_SFT);
|
|
|
|
caps |= (sads[i].freq << DP_CAPABILITY_SAMPLERATE_SFT) &
|
|
(DP_CAPABILITY_SAMPLERATE_MASK <<
|
|
DP_CAPABILITY_SAMPLERATE_SFT);
|
|
caps |= (sads[i].byte2 << DP_CAPABILITY_BITWIDTH_SFT) &
|
|
(DP_CAPABILITY_BITWIDTH_MASK <<
|
|
DP_CAPABILITY_BITWIDTH_SFT);
|
|
}
|
|
}
|
|
|
|
DPTXMSG("audio caps:0x%x", caps);
|
|
return caps;
|
|
}
|
|
|
|
bool mdrv_DPTx_TrainingChangeMode(struct mtk_dp *mtk_dp)
|
|
{
|
|
mhal_DPTx_PHYD_Reset(mtk_dp);
|
|
mhal_DPTx_ResetSwingtPreEmphasis(mtk_dp);
|
|
mhal_DPTx_SSCOnOffSetting(mtk_dp, mtk_dp->training_info.bSinkSSC_En);
|
|
|
|
mdelay(2);
|
|
return true;
|
|
}
|
|
|
|
int mdrv_DPTx_SetTrainingStart(struct mtk_dp *mtk_dp)
|
|
{
|
|
u8 ret = DPTX_NOERR;
|
|
u8 ubLaneCount;
|
|
u8 ubLinkRate;
|
|
u8 ubTemp[16];
|
|
u8 ubTrainTimeLimits;
|
|
#if !ENABLE_DPTX_FIX_LRLC
|
|
u8 maxLinkRate;
|
|
#endif
|
|
|
|
if (!mhal_DPTx_GetHPDPinLevel(mtk_dp)) {
|
|
DPTXMSG("Start Training Abort!=> HPD low !\n");
|
|
|
|
ubTrainTimeLimits = 6;
|
|
while (ubTrainTimeLimits > 6) {
|
|
if (mhal_DPTx_GetHPDPinLevel(mtk_dp))
|
|
break;
|
|
|
|
ubTrainTimeLimits--;
|
|
mdelay(1);
|
|
}
|
|
|
|
if (ubTrainTimeLimits == 0) {
|
|
mtk_dp->training_state = DPTX_NTSTATE_DPIDLE;
|
|
return DPTX_PLUG_OUT;
|
|
}
|
|
}
|
|
|
|
if (!mtk_dp->training_info.bDPTxAutoTest_EN) {
|
|
ubTemp[0] = 0x1;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, DPCD_00600, ubTemp, 0x1);
|
|
|
|
ubLinkRate = mtk_dp->rx_cap[1];
|
|
ubLaneCount = mtk_dp->rx_cap[2] & 0x1F;
|
|
DPTXMSG("RX support ubLinkRate = 0x%x,ubLaneCount = %x",
|
|
ubLinkRate, ubLaneCount);
|
|
|
|
#if !ENABLE_DPTX_FIX_LRLC
|
|
mtk_dp->training_info.ubLinkRate =
|
|
(ubLinkRate >= mtk_dp->training_info.ubSysMaxLinkRate) ?
|
|
mtk_dp->training_info.ubSysMaxLinkRate : ubLinkRate;
|
|
mtk_dp->training_info.ubLinkLaneCount =
|
|
(ubLaneCount >= MAX_LANECOUNT) ?
|
|
MAX_LANECOUNT : ubLaneCount;
|
|
#endif
|
|
}
|
|
|
|
ubLinkRate = mtk_dp->training_info.ubLinkRate;
|
|
ubLaneCount = mtk_dp->training_info.ubLinkLaneCount;
|
|
|
|
switch (ubLinkRate) {
|
|
case DP_LINKRATE_RBR:
|
|
case DP_LINKRATE_HBR:
|
|
case DP_LINKRATE_HBR2:
|
|
case DP_LINKRATE_HBR25:
|
|
case DP_LINKRATE_HBR3:
|
|
break;
|
|
default:
|
|
mtk_dp->training_info.ubLinkRate = DP_LINKRATE_HBR3;
|
|
break;
|
|
};
|
|
|
|
#if !ENABLE_DPTX_FIX_LRLC
|
|
maxLinkRate = ubLinkRate;
|
|
ubTrainTimeLimits = 0x6;
|
|
#endif
|
|
do {
|
|
DPTXMSG("LinkRate:0x%x, LaneCount:%x", ubLinkRate, ubLaneCount);
|
|
|
|
mtk_dp->training_info.cr_done = false;
|
|
mtk_dp->training_info.eq_done = false;
|
|
|
|
mdrv_DPTx_TrainingChangeMode(mtk_dp);
|
|
ret = mdrv_DPTx_TrainingFlow(mtk_dp, ubLinkRate, ubLaneCount);
|
|
if (ret == DPTX_PLUG_OUT || ret == DPTX_RETRANING)
|
|
return ret;
|
|
|
|
if (!mtk_dp->training_info.cr_done) {
|
|
#if !ENABLE_DPTX_FIX_LRLC
|
|
switch (ubLinkRate) {
|
|
case DP_LINKRATE_RBR:
|
|
ubLaneCount = ubLaneCount/2;
|
|
ubLinkRate = maxLinkRate;
|
|
if (ubLaneCount == 0x0) {
|
|
mtk_dp->training_state
|
|
= DPTX_NTSTATE_DPIDLE;
|
|
return DPTX_TRANING_FAIL;
|
|
}
|
|
break;
|
|
case DP_LINKRATE_HBR:
|
|
ubLinkRate = DP_LINKRATE_RBR;
|
|
break;
|
|
case DP_LINKRATE_HBR2:
|
|
ubLinkRate = DP_LINKRATE_HBR;
|
|
break;
|
|
case DP_LINKRATE_HBR3:
|
|
ubLinkRate = DP_LINKRATE_HBR2;
|
|
break;
|
|
default:
|
|
return DPTX_TRANING_FAIL;
|
|
};
|
|
#endif
|
|
ubTrainTimeLimits--;
|
|
} else if (!mtk_dp->training_info.eq_done) {
|
|
#if !ENABLE_DPTX_FIX_LRLC
|
|
if (ubLaneCount == DP_LANECOUNT_4)
|
|
ubLaneCount = DP_LANECOUNT_2;
|
|
else if (ubLaneCount == DP_LANECOUNT_2)
|
|
ubLaneCount = DP_LANECOUNT_1;
|
|
else
|
|
return DPTX_TRANING_FAIL;
|
|
#endif
|
|
ubTrainTimeLimits--;
|
|
} else
|
|
return DPTX_NOERR;
|
|
|
|
} while (ubTrainTimeLimits > 0);
|
|
|
|
return DPTX_TRANING_FAIL;
|
|
}
|
|
|
|
int mdrv_DPTx_Training_Handler(struct mtk_dp *mtk_dp)
|
|
{
|
|
int ret = DPTX_NOERR;
|
|
|
|
if (!mtk_dp->training_info.bCablePlugIn)
|
|
return DPTX_PLUG_OUT;
|
|
|
|
if (mtk_dp->training_state == DPTX_NTSTATE_NORMAL)
|
|
return ret;
|
|
|
|
if (mtk_dp->training_state_pre != mtk_dp->training_state) {
|
|
mdrv_DPTx_Print_TrainingState(mtk_dp->training_state);
|
|
|
|
mtk_dp->training_state_pre = mtk_dp->training_state;
|
|
}
|
|
|
|
switch (mtk_dp->training_state) {
|
|
case DPTX_NTSTATE_STARTUP:
|
|
mtk_dp->training_state = DPTX_NTSTATE_CHECKCAP;
|
|
break;
|
|
|
|
case DPTX_NTSTATE_CHECKCAP:
|
|
if (mdrv_DPTx_CheckSinkCap(mtk_dp)) {
|
|
mtk_dp->training_info.ucCheckCapTimes = 0;
|
|
mtk_dp->training_state = DPTX_NTSTATE_CHECKEDID;
|
|
} else {
|
|
BYTE uaCheckTimes = 0;
|
|
|
|
mtk_dp->training_info.ucCheckCapTimes++;
|
|
uaCheckTimes = mtk_dp->training_info.ucCheckCapTimes;
|
|
|
|
if (uaCheckTimes > DPTX_CheckSinkCap_TimeOutCnt) {
|
|
mtk_dp->training_info.ucCheckCapTimes = 0;
|
|
DPTXMSG("CheckCap Fail %d times",
|
|
DPTX_CheckSinkCap_TimeOutCnt);
|
|
mtk_dp->training_state = DPTX_NTSTATE_DPIDLE;
|
|
ret = DPTX_TIMEOUT;
|
|
} else
|
|
DPTXMSG("CheckCap Fail %d times", uaCheckTimes);
|
|
}
|
|
break;
|
|
|
|
case DPTX_NTSTATE_CHECKEDID:
|
|
mtk_dp->edid = mtk_dp_handle_edid(mtk_dp);
|
|
if (mtk_dp->edid) {
|
|
DPTXMSG("READ EDID done!\n");
|
|
if (mtk_dp_debug_get()) {
|
|
u8 *raw_edid = (u8 *)mtk_dp->edid;
|
|
|
|
DPTXMSG("Raw EDID:\n");
|
|
print_hex_dump(KERN_NOTICE,
|
|
"\t", DUMP_PREFIX_NONE, 16, 1,
|
|
raw_edid, EDID_LENGTH, false);
|
|
if ((raw_edid[0x7E] & 0x01) == 0x01) {
|
|
print_hex_dump(KERN_NOTICE,
|
|
"\t", DUMP_PREFIX_NONE, 16, 1,
|
|
(raw_edid + 128), EDID_LENGTH,
|
|
false);
|
|
}
|
|
}
|
|
|
|
mtk_dp->info.audio_caps
|
|
= mdrv_DPTx_getAudioCaps(mtk_dp);
|
|
} else
|
|
DPTXMSG("Read EDID Fail!\n");
|
|
|
|
mtk_dp->training_state = DPTX_NTSTATE_TRAINING_PRE;
|
|
break;
|
|
|
|
case DPTX_NTSTATE_TRAINING_PRE:
|
|
mtk_dp->training_state = DPTX_NTSTATE_TRAINING;
|
|
break;
|
|
|
|
case DPTX_NTSTATE_TRAINING:
|
|
ret = mdrv_DPTx_SetTrainingStart(mtk_dp);
|
|
if (ret == DPTX_NOERR) {
|
|
mdrv_DPTx_VideoMute(mtk_dp, true);
|
|
mdrv_DPTx_AudioMute(mtk_dp, true);
|
|
mtk_dp->training_state = DPTX_NTSTATE_CHECKTIMING;
|
|
mtk_dp->dp_ready = true;
|
|
mhal_DPTx_EnableFEC(mtk_dp, mtk_dp->has_fec);
|
|
} else if (ret == DPTX_RETRANING) {
|
|
ret = DPTX_NOERR;
|
|
} else
|
|
DPTXERR("Handle Training Fail 6 times\n");
|
|
break;
|
|
|
|
case DPTX_NTSTATE_CHECKTIMING:
|
|
mtk_dp->training_state = DPTX_NTSTATE_NORMAL;
|
|
|
|
if (mtk_dp->training_info.ubSinkCountNum == 0) {
|
|
DPTXMSG("no sink count, skip uevent\n");
|
|
break;
|
|
}
|
|
|
|
if (mtk_dp->bUeventToHwc) {
|
|
mtk_dp_hotplug_uevent(1);
|
|
mtk_dp->bUeventToHwc = false;
|
|
} else
|
|
DPTXMSG("Skip Uevent(1)\n");
|
|
|
|
break;
|
|
case DPTX_NTSTATE_NORMAL:
|
|
break;
|
|
case DPTX_NTSTATE_POWERSAVE:
|
|
break;
|
|
case DPTX_NTSTATE_DPIDLE:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef DPTX_HDCP_ENABLE
|
|
void mdrv_DPTx_reAuthentication(struct mtk_dp *mtk_dp)
|
|
{
|
|
if (!mtk_dp->training_info.bCablePlugIn || !mtk_dp->dp_ready)
|
|
return;
|
|
|
|
queue_work(mtk_dp->dptx_wq, &mtk_dp->hdcp_work);
|
|
}
|
|
|
|
void mdrv_DPTx_CheckHDCPVersion(struct mtk_dp *mtk_dp, bool only_hdcp1x)
|
|
{
|
|
if (g_hdcp_on) {
|
|
if (!only_hdcp1x && mdrv_DPTx_HDCP2_Support(mtk_dp))
|
|
return;
|
|
|
|
if (mdrv_DPTx_HDCP1x_Support(mtk_dp))
|
|
return;
|
|
} else
|
|
DPTXMSG("Not enable HDCP Function!\n");
|
|
|
|
if (tee_addDevice(HDCP_NONE) != RET_SUCCESS)
|
|
mtk_dp->info.bAuthStatus = AUTH_FAIL;
|
|
}
|
|
|
|
static void mdrv_DPTx_hdcp_handle(struct work_struct *data)
|
|
{
|
|
struct mtk_dp *mtk_dp = container_of(data, struct mtk_dp, hdcp_work);
|
|
|
|
if (!mtk_dp->training_info.bCablePlugIn || !mtk_dp->dp_ready)
|
|
return;
|
|
|
|
if (mtk_dp->info.bAuthStatus == AUTH_ZERO) {
|
|
mdrv_DPTx_CheckHDCPVersion(mtk_dp, false);
|
|
if (mtk_dp->info.hdcp2_info.bEnable)
|
|
mdrv_DPTx_HDCP2_SetStartAuth(mtk_dp, true);
|
|
else if (mtk_dp->info.hdcp1x_info.bEnable)
|
|
mdrv_DPTx_HDCP1X_SetStartAuth(mtk_dp, true);
|
|
}
|
|
|
|
if (mtk_dp->info.hdcp2_info.bEnable) {
|
|
HDCPTx_Hdcp2FSM(mtk_dp);
|
|
|
|
if (mtk_dp->info.bAuthStatus == AUTH_FAIL) {
|
|
tee_removeDevice();
|
|
|
|
mdrv_DPTx_CheckHDCPVersion(mtk_dp, true);
|
|
if (mtk_dp->info.hdcp1x_info.bEnable) {
|
|
mtk_dp->info.hdcp2_info.bEnable = false;
|
|
mdrv_DPTx_HDCP1X_SetStartAuth(mtk_dp, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mtk_dp->info.hdcp1x_info.bEnable)
|
|
mdrv_DPTx_HDCP1X_FSM(mtk_dp);
|
|
|
|
if ((mtk_dp->info.hdcp1x_info.bEnable
|
|
|| mtk_dp->info.hdcp2_info.bEnable)
|
|
&& (mtk_dp->info.bAuthStatus != AUTH_FAIL)
|
|
&& (mtk_dp->info.bAuthStatus != AUTH_PASS))
|
|
queue_work(mtk_dp->dptx_wq, &mtk_dp->hdcp_work);
|
|
}
|
|
#else
|
|
static void mdrv_DPTx_hdcp_handle(struct work_struct *data)
|
|
{
|
|
DPTXMSG("No Support HDCP function\n");
|
|
}
|
|
#endif
|
|
|
|
bool mdrv_DPTx_done(struct mtk_dp *mtk_dp)
|
|
{
|
|
if (mtk_dp->state != DPTXSTATE_NORMAL)
|
|
return false;
|
|
|
|
if (mtk_dp->training_state != DPTX_NTSTATE_NORMAL)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
int mdrv_DPTx_Handle(struct mtk_dp *mtk_dp)
|
|
{
|
|
int ret = DPTX_NOERR;
|
|
|
|
if (!mtk_dp->training_info.bCablePlugIn)
|
|
return DPTX_PLUG_OUT;
|
|
|
|
if (mtk_dp->state != mtk_dp->state_pre) {
|
|
DPTXMSG("m_DPTXState %d, m_DPTXStateTemp %d\n",
|
|
mtk_dp->state, mtk_dp->state_pre);
|
|
mtk_dp->state_pre = mtk_dp->state;
|
|
}
|
|
|
|
switch (mtk_dp->state) {
|
|
case DPTXSTATE_INITIAL:
|
|
mdrv_DPTx_VideoMute(mtk_dp, true);
|
|
mdrv_DPTx_AudioMute(mtk_dp, true);
|
|
mtk_dp->state = DPTXSTATE_IDLE;
|
|
break;
|
|
|
|
case DPTXSTATE_IDLE:
|
|
if (mtk_dp->training_state == DPTX_NTSTATE_NORMAL)
|
|
mtk_dp->state = DPTXSTATE_PREPARE;
|
|
break;
|
|
|
|
case DPTXSTATE_PREPARE:
|
|
if (mtk_dp->video_enable) {
|
|
mtk_dp_video_config(mtk_dp);
|
|
mdrv_DPTx_Video_Enable(mtk_dp, true);
|
|
}
|
|
|
|
if (mtk_dp->audio_enable && (mtk_dp->info.audio_caps != 0)) {
|
|
mdrv_DPTx_I2S_Audio_Config(mtk_dp);
|
|
mdrv_DPTx_I2S_Audio_Enable(mtk_dp, true);
|
|
}
|
|
|
|
mtk_dp->state = DPTXSTATE_NORMAL;
|
|
break;
|
|
|
|
case DPTXSTATE_NORMAL:
|
|
if (mtk_dp->training_state != DPTX_NTSTATE_NORMAL) {
|
|
mdrv_DPTx_VideoMute(mtk_dp, true);
|
|
mdrv_DPTx_AudioMute(mtk_dp, true);
|
|
mdrv_DPTx_StopSentSDP(mtk_dp);
|
|
mtk_dp->state = DPTXSTATE_IDLE;
|
|
DPTXMSG("DPTX Link Status Change!\n");
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void mdrv_DPTx_HPD_HandleInISR(struct mtk_dp *mtk_dp)
|
|
{
|
|
bool ubCurrentHPD = mhal_DPTx_GetHPDPinLevel(mtk_dp);
|
|
|
|
if (mtk_dp->training_info.usPHY_STS == HPD_INITIAL_STATE)
|
|
return;
|
|
|
|
if ((mtk_dp->training_info.usPHY_STS & (HPD_CONNECT|HPD_DISCONNECT))
|
|
== (HPD_CONNECT|HPD_DISCONNECT)) {
|
|
if (ubCurrentHPD)
|
|
mtk_dp->training_info.usPHY_STS &= ~HPD_DISCONNECT;
|
|
else
|
|
mtk_dp->training_info.usPHY_STS &= ~HPD_CONNECT;
|
|
}
|
|
|
|
if ((mtk_dp->training_info.usPHY_STS & (HPD_INT_EVNET|HPD_DISCONNECT))
|
|
== (HPD_INT_EVNET|HPD_DISCONNECT)) {
|
|
if (ubCurrentHPD)
|
|
mtk_dp->training_info.usPHY_STS &= ~HPD_DISCONNECT;
|
|
}
|
|
|
|
#if 0
|
|
if (mtk_dp->training_info.bCablePlugIn)
|
|
mtk_dp->training_info.usPHY_STS &= ~HPD_CONNECT;
|
|
else
|
|
mtk_dp->training_info.usPHY_STS &= ~HPD_DISCONNECT;
|
|
#endif
|
|
|
|
if (mtk_dp->training_info.usPHY_STS & HPD_CONNECT) {
|
|
mtk_dp->training_info.usPHY_STS &= ~HPD_CONNECT;
|
|
mtk_dp->training_info.bCablePlugIn = true;
|
|
mtk_dp->training_info.bCableStateChange = true;
|
|
|
|
DPTXMSG(" HPD_CON_ISR\n");
|
|
}
|
|
|
|
if (mtk_dp->training_info.usPHY_STS & HPD_DISCONNECT) {
|
|
mtk_dp->training_info.usPHY_STS &= ~HPD_DISCONNECT;
|
|
|
|
mtk_dp->training_info.bCablePlugIn = false;
|
|
mtk_dp->training_info.bCableStateChange = true;
|
|
|
|
DPTXMSG("HPD_DISCON_ISR\n");
|
|
}
|
|
}
|
|
|
|
void mdrv_DPTx_USBC_HPD_Event(u16 ubSWStatus)
|
|
{
|
|
struct mtk_dp *mtk_dp = g_mtk_dp;
|
|
|
|
mtk_dp->training_info.usPHY_STS |= ubSWStatus;
|
|
DPTXMSG("SW status = 0x%x, usPHY_STS = 0x%x\n",
|
|
ubSWStatus,
|
|
mtk_dp->training_info.usPHY_STS);
|
|
|
|
mdrv_DPTx_HPD_HandleInISR(mtk_dp);
|
|
|
|
if (mtk_dp->training_info.bCableStateChange
|
|
|| ubSWStatus == HPD_INT_EVNET)
|
|
queue_work(mtk_dp->dptx_wq, &mtk_dp->dptx_work);
|
|
}
|
|
|
|
void mdrv_DPTx_HPD_ISREvent(struct mtk_dp *mtk_dp)
|
|
{
|
|
u8 ubIsrEnable = 0xF1;
|
|
u8 ubHWStatus_undefine = mhal_DPTx_GetHPDIRQStatus(mtk_dp);
|
|
u8 ubHWStatus = ubHWStatus_undefine&(~ubIsrEnable);
|
|
|
|
mtk_dp->training_info.usPHY_STS |= ubHWStatus;
|
|
DPTXMSG("HW status = 0x%x\n", ubHWStatus);
|
|
|
|
mdrv_DPTx_HPD_HandleInISR(mtk_dp);
|
|
|
|
if (ubHWStatus)
|
|
mhal_DPTx_HPDInterruptClr(mtk_dp, ubHWStatus);
|
|
|
|
if (mtk_dp->training_info.bCableStateChange
|
|
|| ubHWStatus == HPD_INT_EVNET)
|
|
queue_work(mtk_dp->dptx_wq, &mtk_dp->dptx_work);
|
|
}
|
|
|
|
void mdrv_DPTx_ISR(struct mtk_dp *mtk_dp)
|
|
{
|
|
mhal_DPTx_ISR(mtk_dp);
|
|
}
|
|
|
|
void mdrv_DPTx_InitPort(struct mtk_dp *mtk_dp)
|
|
{
|
|
mhal_DPTx_PHY_SetIdlePattern(mtk_dp, true);
|
|
mdrv_DPTx_InitVariable(mtk_dp);
|
|
|
|
mhal_DPTx_InitialSetting(mtk_dp);
|
|
mhal_DPTx_AuxSetting(mtk_dp);
|
|
mhal_DPTx_DigitalSetting(mtk_dp);
|
|
mhal_DPTx_AnalogPowerOnOff(mtk_dp, true);
|
|
mhal_DPTx_PHYSetting(mtk_dp);
|
|
mhal_DPTx_HPDDetectSetting(mtk_dp);
|
|
|
|
mhal_DPTx_DigitalSwReset(mtk_dp);
|
|
mhal_DPTx_Set_Efuse_Value(mtk_dp);
|
|
}
|
|
|
|
void mdrv_DPTx_Video_Enable(struct mtk_dp *mtk_dp, bool bEnable)
|
|
{
|
|
DPTXMSG("Output Video %s!\n", bEnable ? "enable" : "disable");
|
|
|
|
if (bEnable) {
|
|
mdrv_DPTx_SetDPTXOut(mtk_dp);
|
|
mdrv_DPTx_VideoMute(mtk_dp, false);
|
|
mhal_DPTx_Verify_Clock(mtk_dp);
|
|
} else
|
|
mdrv_DPTx_VideoMute(mtk_dp, true);
|
|
}
|
|
|
|
void mdrv_DPTx_Set_Color_Format(struct mtk_dp *mtk_dp, u8 ucColorFormat)
|
|
{
|
|
DPTXMSG("Set Color Format = 0x%x\n", ucColorFormat);
|
|
|
|
mtk_dp->info.format = ucColorFormat;
|
|
mhal_DPTx_SetColorFormat(mtk_dp, ucColorFormat);
|
|
}
|
|
|
|
void mdrv_DPTx_Set_Color_Depth(struct mtk_dp *mtk_dp, u8 ucColorDepth)
|
|
{
|
|
DPTXMSG("Set Color Depth = %d (1~4=6/8/10/12bpp)\n", ucColorDepth + 1);
|
|
|
|
mtk_dp->info.depth = ucColorDepth;
|
|
mhal_DPTx_SetColorDepth(mtk_dp, ucColorDepth);
|
|
}
|
|
|
|
void mdrv_DPTx_I2S_Audio_Enable(struct mtk_dp *mtk_dp, bool bEnable)
|
|
{
|
|
if (bEnable) {
|
|
mdrv_DPTx_AudioMute(mtk_dp, false);
|
|
DPTXMSG("I2S Audio Enable!\n");
|
|
} else {
|
|
mdrv_DPTx_AudioMute(mtk_dp, true);
|
|
DPTXMSG("I2S Audio Disable!\n");
|
|
}
|
|
}
|
|
|
|
void mdrv_DPTx_I2S_Audio_Set_MDiv(struct mtk_dp *mtk_dp, u8 ucDiv)
|
|
{
|
|
char bTable[7][5] = {"X2", "X4", "X8", "N/A", "/2", "/4", "/8"};
|
|
|
|
DPTXMSG("I2S Set Audio M Divider = %s\n", bTable[ucDiv-1]);
|
|
mhal_DPTx_Audio_M_Divider_Setting(mtk_dp, ucDiv);
|
|
}
|
|
|
|
void mdrv_DPTx_I2S_Audio_Config(struct mtk_dp *mtk_dp)
|
|
{
|
|
u8 ucChannel, ucFs, ucWordlength;
|
|
unsigned int tmp = mtk_dp->info.audio_config;
|
|
|
|
if (!mtk_dp->dp_ready) {
|
|
DPTXERR("%s, DP is not ready!\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (fakecablein) {
|
|
ucChannel = BIT(force_ch);
|
|
ucFs = BIT(force_fs);
|
|
ucWordlength = BIT(force_len);
|
|
} else {
|
|
ucChannel = (tmp >> DP_CAPABILITY_CHANNEL_SFT)
|
|
& DP_CAPABILITY_CHANNEL_MASK;
|
|
ucFs = (tmp >> DP_CAPABILITY_SAMPLERATE_SFT)
|
|
& DP_CAPABILITY_SAMPLERATE_MASK;
|
|
ucWordlength = (tmp >> DP_CAPABILITY_BITWIDTH_SFT)
|
|
& DP_CAPABILITY_BITWIDTH_MASK;
|
|
}
|
|
|
|
switch (ucChannel) {
|
|
case DP_CHANNEL_2:
|
|
ucChannel = 2;
|
|
break;
|
|
case DP_CHANNEL_8:
|
|
ucChannel = 8;
|
|
break;
|
|
default:
|
|
ucChannel = 2;
|
|
break;
|
|
}
|
|
|
|
switch (ucFs) {
|
|
case DP_SAMPLERATE_32:
|
|
ucFs = FS_32K;
|
|
break;
|
|
case DP_SAMPLERATE_44:
|
|
ucFs = FS_44K;
|
|
break;
|
|
case DP_SAMPLERATE_48:
|
|
ucFs = FS_48K;
|
|
break;
|
|
case DP_SAMPLERATE_96:
|
|
ucFs = FS_96K;
|
|
break;
|
|
case DP_SAMPLERATE_192:
|
|
ucFs = FS_192K;
|
|
break;
|
|
default:
|
|
ucFs = FS_48K;
|
|
break;
|
|
}
|
|
|
|
switch (ucWordlength) {
|
|
case DP_BITWIDTH_16:
|
|
ucWordlength = WL_16bit;
|
|
break;
|
|
case DP_BITWIDTH_20:
|
|
ucWordlength = WL_20bit;
|
|
break;
|
|
case DP_BITWIDTH_24:
|
|
ucWordlength = WL_24bit;
|
|
break;
|
|
default:
|
|
ucWordlength = WL_24bit;
|
|
break;
|
|
}
|
|
|
|
mdrv_DPTx_I2S_Audio_SDP_Channel_Setting(mtk_dp, ucChannel,
|
|
ucFs, ucWordlength);
|
|
mdrv_DPTx_I2S_Audio_Ch_Status_Set(mtk_dp, ucChannel,
|
|
ucFs, ucWordlength);
|
|
|
|
mhal_DPTx_Audio_PG_EN(mtk_dp, ucChannel, ucFs, false);
|
|
mdrv_DPTx_I2S_Audio_Set_MDiv(mtk_dp, 5);
|
|
}
|
|
|
|
void mdrv_DPTx_I2S_Audio_SDP_Channel_Setting(struct mtk_dp *mtk_dp,
|
|
u8 ucChannel, u8 ucFs, u8 ucWordlength)
|
|
{
|
|
u8 SDP_DB[32] = {0};
|
|
u8 SDP_HB[4] = {0};
|
|
|
|
SDP_HB[1] = DP_SPEC_SDPTYP_AINFO;
|
|
SDP_HB[2] = 0x1B;
|
|
SDP_HB[3] = 0x48;
|
|
|
|
SDP_DB[0x0] = 0x10 | (ucChannel-1); //L-PCM[7:4], channel-1[2:0]
|
|
SDP_DB[0x1] = ucFs << 2 | ucWordlength; // fs[4:2], len[1:0]
|
|
SDP_DB[0x2] = 0x0;
|
|
|
|
if (ucChannel == 8)
|
|
SDP_DB[0x3] = 0x13;
|
|
else
|
|
SDP_DB[0x3] = 0x00;
|
|
|
|
mhal_DPTx_Audio_SDP_Setting(mtk_dp, ucChannel);
|
|
DPTXMSG("I2S Set Audio Channel = %d\n", ucChannel);
|
|
mdrv_DPTx_SPKG_SDP(mtk_dp, true, DPTx_SDPTYP_AUI, SDP_HB, SDP_DB);
|
|
}
|
|
|
|
void mdrv_DPTx_I2S_Audio_Ch_Status_Set(struct mtk_dp *mtk_dp, u8 ucChannel,
|
|
u8 ucFs, u8 ucWordlength)
|
|
{
|
|
mhal_DPTx_Audio_Ch_Status_Set(mtk_dp, ucChannel, ucFs, ucWordlength);
|
|
}
|
|
|
|
void mdrv_DPTx_DSC_SetParam(struct mtk_dp *mtk_dp, u8 slice_num, u16 chunk_num)
|
|
{
|
|
u8 r8, r16;
|
|
u8 q16[16] = {0x6, 0x01, 0x01, 0x03, 0x03, 0x05, 0x05, 0x07,
|
|
0x07, 0x00, 0x00, 0x02, 0x02, 0x04, 0x04, 0x06};
|
|
u8 q8[8] = {0x6, 0x01, 0x03, 0x05, 0x07, 0x00, 0x02, 0x04};
|
|
u8 hde_last_num, hde_num_even;
|
|
|
|
DPTXMSG("lane count = %d\n", mtk_dp->training_info.ubLinkLaneCount);
|
|
if (mtk_dp->training_info.ubLinkLaneCount == DP_LANECOUNT_2) {
|
|
if (chunk_num % 2)
|
|
//r16 = (int) ceil((chunk_num + 1 + 2) *
|
|
//slice_num / 3) % 16;
|
|
r16 = ((chunk_num + 1 + 2) * slice_num / 3) % 16;
|
|
else
|
|
//r16 = (int) ceil((chunk_num + 2) *
|
|
//slice_num / 3) % 16;
|
|
r16 = ((chunk_num + 2) * slice_num / 3) % 16;
|
|
DPTXMSG("r16 = %d\n", r16);
|
|
//r16 = 1; //test for 1080p
|
|
hde_last_num = (q16[r16] & (BIT1|BIT2)) >> 1;
|
|
hde_num_even = q16[r16] & BIT0;
|
|
} else {
|
|
//r8 = (int) ceil((chunk_num + 1) * slice_num / 3) % 8;
|
|
r8 = ((chunk_num + 1) * slice_num / 3) % 8;
|
|
DPTXMSG("r8 = %d\n", r8);
|
|
//r8 = 1; //test for 1080p
|
|
hde_last_num = (q8[r8] & (BIT1|BIT2)) >> 1;
|
|
hde_num_even = q8[r8] & BIT0;
|
|
}
|
|
|
|
mhal_DPTx_SetChunkSize(mtk_dp, slice_num-1, chunk_num, chunk_num%12,
|
|
mtk_dp->training_info.ubLinkLaneCount,
|
|
hde_last_num, hde_num_even);
|
|
|
|
}
|
|
|
|
void mdrv_DPTx_DSC_SetPPS(struct mtk_dp *mtk_dp, u8 *PPS, bool enable)
|
|
{
|
|
u8 HB[4] = {0x0, 0x10, 0x7F, 0x0};
|
|
|
|
mdrv_DPTx_SPKG_SDP(mtk_dp, enable, DPTx_SDPTYP_PPS0, HB, PPS + 0);
|
|
mdrv_DPTx_SPKG_SDP(mtk_dp, enable, DPTx_SDPTYP_PPS1, HB, PPS + 32);
|
|
mdrv_DPTx_SPKG_SDP(mtk_dp, enable, DPTx_SDPTYP_PPS2, HB, PPS + 64);
|
|
mdrv_DPTx_SPKG_SDP(mtk_dp, enable, DPTx_SDPTYP_PPS3, HB, PPS + 96);
|
|
}
|
|
|
|
void mdrv_DPTx_DSC_Support(struct mtk_dp *mtk_dp)
|
|
{
|
|
#if DPTX_SUPPORT_DSC
|
|
u8 Data[3];
|
|
|
|
drm_dp_dpcd_read(&mtk_dp->aux, 0x60, Data, 1);
|
|
if (Data[0] & BIT0)
|
|
mtk_dp->has_dsc = true;
|
|
else
|
|
mtk_dp->has_dsc = false;
|
|
|
|
DPTXMSG("Sink has_dsc = %d\n", mtk_dp->has_dsc);
|
|
#endif
|
|
}
|
|
|
|
void mdrv_DPTx_FEC_Ready(struct mtk_dp *mtk_dp, u8 err_cnt_sel)
|
|
{
|
|
u8 i, Data[3];
|
|
|
|
drm_dp_dpcd_read(&mtk_dp->aux, 0x90, Data, 0x1);
|
|
|
|
/* FEC error count select 120[3:1]: *
|
|
* 000b: FEC_ERROR_COUNT_DIS *
|
|
* 001b: UNCORRECTED_BLOCK_ERROR_COUNT *
|
|
* 010b: CORRECTED_BLOCK_ERROR_COUNT *
|
|
* 011b: BIT_ERROR_COUNT *
|
|
* 100b: PARITY_BLOCK_ERROR_COUNT *
|
|
* 101b: PARITY_BIT_ERROR_COUNT *
|
|
*/
|
|
if (Data[0] & BIT0) {
|
|
mtk_dp->has_fec = true;
|
|
Data[0] = (err_cnt_sel << 1) | 0x1; //FEC Ready
|
|
drm_dp_dpcd_write(&mtk_dp->aux, 0x120, Data, 0x1);
|
|
drm_dp_dpcd_read(&mtk_dp->aux, 0x280, Data, 0x3);
|
|
for (i = 0; i < 3; i++)
|
|
DPTXDBG("FEC status & error Count: 0x%x\n", Data[i]);
|
|
}
|
|
|
|
DPTXMSG("SINK has fec (%d)\n", mtk_dp->has_fec);
|
|
}
|
|
|
|
DWORD getTimeDiff(DWORD dwPreTime)
|
|
{
|
|
DWORD dwPostTime = getSystemTime();
|
|
|
|
if (dwPreTime > dwPostTime)
|
|
return ((1000000 - dwPreTime) + dwPostTime);
|
|
else
|
|
return (dwPostTime - dwPreTime);
|
|
}
|
|
|
|
DWORD getSystemTime(void)
|
|
{
|
|
DWORD tms = (DWORD)((sched_clock() / 1000000) % 1000000);
|
|
return tms;
|
|
}
|
|
|
|
static void mdrv_DPTx_main_handle(struct work_struct *data)
|
|
{
|
|
struct mtk_dp *mtk_dp = container_of(data, struct mtk_dp, dptx_work);
|
|
unsigned long long starttime = sched_clock();
|
|
|
|
do {
|
|
if (abs(sched_clock() - starttime) > 5000000000ULL) {
|
|
DPTXERR("Handle time over 5s\n");
|
|
break;
|
|
}
|
|
|
|
if (mdrv_DPTx_HPD_HandleInThread(mtk_dp) != DPTX_NOERR)
|
|
break;
|
|
|
|
if (mdrv_DPTx_Training_Handler(mtk_dp) != DPTX_NOERR)
|
|
break;
|
|
|
|
if (mdrv_DPTx_Handle(mtk_dp) != DPTX_NOERR)
|
|
break;
|
|
} while (!mdrv_DPTx_done(mtk_dp));
|
|
}
|
|
|
|
u8 PPS_4k60[128] = {
|
|
0x12, 0x00, 0x00, 0x8d, 0x30, 0x80, 0x08, 0x70, 0x0f, 0x00, 0x00, 0x08,
|
|
0x07, 0x80, 0x07, 0x80, 0x02, 0x00, 0x04, 0xc0, 0x00, 0x20, 0x01, 0x1e,
|
|
0x00, 0x1a, 0x00, 0x0c, 0x0d, 0xb7, 0x03, 0x94, 0x18, 0x00, 0x10, 0xf0,
|
|
0x03, 0x0c, 0x20, 0x00, 0x06, 0x0b, 0x0b, 0x33, 0x0e, 0x1c, 0x2a, 0x38,
|
|
0x46, 0x54, 0x62, 0x69, 0x70, 0x77, 0x79, 0x7b, 0x7d, 0x7e, 0x01, 0x02,
|
|
0x01, 0x00, 0x09, 0x40, 0x09, 0xbe, 0x19, 0xfc, 0x19, 0xfa, 0x19, 0xf8,
|
|
0x1a, 0x38, 0x1a, 0x78, 0x22, 0xb6, 0x2a, 0xb6, 0x2a, 0xf6, 0x2a, 0xf4,
|
|
0x43, 0x34, 0x63, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
|
|
void mtk_dp_video_config(struct mtk_dp *mtk_dp)
|
|
{
|
|
struct DPTX_TIMING_PARAMETER *DPTX_TBL = &mtk_dp->info.DPTX_OUTBL;
|
|
u32 mvid = 0;
|
|
bool overwrite = false;
|
|
|
|
if (!mtk_dp->dp_ready) {
|
|
DPTXERR("%s, DP is not ready!\n", __func__);
|
|
return;
|
|
}
|
|
|
|
if (mtk_dp->info.resolution >= SINK_MAX) {
|
|
DPTXERR("DPTX doesn't support this resolution(%d)!\n",
|
|
mtk_dp->info.resolution);
|
|
return;
|
|
}
|
|
|
|
if (fakecablein) {
|
|
if (mtk_dp->info.resolution == SINK_1280_720) {
|
|
// patch for LLCTS 4.4.4.5
|
|
switch (mtk_dp->training_info.ubLinkRate) {
|
|
case DP_LINKRATE_RBR:
|
|
mvid = 0x3AAB;
|
|
break;
|
|
case DP_LINKRATE_HBR:
|
|
mvid = 0x2333;
|
|
break;
|
|
case DP_LINKRATE_HBR2:
|
|
mvid = 0x1199;
|
|
break;
|
|
case DP_LINKRATE_HBR3:
|
|
mvid = 0xBBB;
|
|
break;
|
|
}
|
|
overwrite = true;
|
|
}
|
|
mtk_dp->info.depth = fakebpc;
|
|
}
|
|
|
|
switch (mtk_dp->info.resolution) {
|
|
case SINK_7680_4320:
|
|
DPTX_TBL->FrameRate = 60;
|
|
DPTX_TBL->Htt = 8040; DPTX_TBL->Hbp = 240; DPTX_TBL->Hsw = 96;
|
|
DPTX_TBL->bHsp = 0; DPTX_TBL->Hfp = 24; DPTX_TBL->Hde = 7680;
|
|
DPTX_TBL->Vtt = 4381; DPTX_TBL->Vbp = 6; DPTX_TBL->Vsw = 8;
|
|
DPTX_TBL->bVsp = 0; DPTX_TBL->Vfp = 47; DPTX_TBL->Vde = 4320;
|
|
break;
|
|
case SINK_3840_2160:
|
|
DPTX_TBL->FrameRate = 60;
|
|
mtk_dp->dsc_enable = true;
|
|
DPTX_TBL->Htt = 4400; DPTX_TBL->Hbp = 296; DPTX_TBL->Hsw = 88;
|
|
DPTX_TBL->bHsp = 0; DPTX_TBL->Hfp = 176; DPTX_TBL->Hde = 3840;
|
|
DPTX_TBL->Vtt = 2250; DPTX_TBL->Vbp = 72; DPTX_TBL->Vsw = 10;
|
|
DPTX_TBL->bVsp = 0; DPTX_TBL->Vfp = 8; DPTX_TBL->Vde = 2160;
|
|
break;
|
|
case SINK_3840_2160_30:
|
|
DPTX_TBL->FrameRate = 30;
|
|
DPTX_TBL->Htt = 4400; DPTX_TBL->Hbp = 296; DPTX_TBL->Hsw = 88;
|
|
DPTX_TBL->bHsp = 0; DPTX_TBL->Hfp = 176; DPTX_TBL->Hde = 3840;
|
|
DPTX_TBL->Vtt = 2250; DPTX_TBL->Vbp = 72; DPTX_TBL->Vsw = 10;
|
|
DPTX_TBL->bVsp = 0; DPTX_TBL->Vfp = 8; DPTX_TBL->Vde = 2160;
|
|
break;
|
|
case SINK_2560_1600:
|
|
DPTX_TBL->FrameRate = 60;
|
|
DPTX_TBL->Htt = 2720; DPTX_TBL->Hbp = 80; DPTX_TBL->Hsw = 32;
|
|
DPTX_TBL->bHsp = 0; DPTX_TBL->Hfp = 48; DPTX_TBL->Hde = 2560;
|
|
DPTX_TBL->Vtt = 1646; DPTX_TBL->Vbp = 3; DPTX_TBL->Vsw = 6;
|
|
DPTX_TBL->bVsp = 1; DPTX_TBL->Vfp = 37; DPTX_TBL->Vde = 1600;
|
|
break;
|
|
case SINK_1920_1440:
|
|
DPTX_TBL->FrameRate = 60;
|
|
DPTX_TBL->Htt = 2600; DPTX_TBL->Hbp = 344; DPTX_TBL->Hsw = 208;
|
|
DPTX_TBL->bHsp = 1; DPTX_TBL->Hfp = 128; DPTX_TBL->Hde = 1920;
|
|
DPTX_TBL->Vtt = 1500; DPTX_TBL->Vbp = 56; DPTX_TBL->Vsw = 3;
|
|
DPTX_TBL->bVsp = 0; DPTX_TBL->Vfp = 1; DPTX_TBL->Vde = 1440;
|
|
break;
|
|
case SINK_1920_1200:
|
|
DPTX_TBL->FrameRate = 60;
|
|
DPTX_TBL->Htt = 2080; DPTX_TBL->Hbp = 80; DPTX_TBL->Hsw = 32;
|
|
DPTX_TBL->bHsp = 0; DPTX_TBL->Hfp = 48; DPTX_TBL->Hde = 1920;
|
|
DPTX_TBL->Vtt = 1235; DPTX_TBL->Vbp = 26; DPTX_TBL->Vsw = 6;
|
|
DPTX_TBL->bVsp = 0; DPTX_TBL->Vfp = 3; DPTX_TBL->Vde = 1200;
|
|
break;
|
|
case SINK_1920_1080:
|
|
DPTX_TBL->FrameRate = 60;
|
|
DPTX_TBL->Htt = 2200; DPTX_TBL->Hbp = 148; DPTX_TBL->Hsw = 44;
|
|
DPTX_TBL->bHsp = 0; DPTX_TBL->Hfp = 88; DPTX_TBL->Hde = 1920;
|
|
DPTX_TBL->Vtt = 1125; DPTX_TBL->Vbp = 36; DPTX_TBL->Vsw = 5;
|
|
DPTX_TBL->bVsp = 0; DPTX_TBL->Vfp = 4; DPTX_TBL->Vde = 1080;
|
|
break;
|
|
case SINK_1080_2460:
|
|
DPTX_TBL->FrameRate = 60;
|
|
DPTX_TBL->Htt = 1172; DPTX_TBL->Hbp = 30; DPTX_TBL->Hsw = 32;
|
|
DPTX_TBL->bHsp = 1; DPTX_TBL->Hfp = 30; DPTX_TBL->Hde = 1080;
|
|
DPTX_TBL->Vtt = 2476; DPTX_TBL->Vbp = 5; DPTX_TBL->Vsw = 2;
|
|
DPTX_TBL->bVsp = 0; DPTX_TBL->Vfp = 9; DPTX_TBL->Vde = 2460;
|
|
break;
|
|
case SINK_1280_1024:
|
|
DPTX_TBL->FrameRate = 60;
|
|
DPTX_TBL->Htt = 1560; DPTX_TBL->Hbp = 148; DPTX_TBL->Hsw = 44;
|
|
DPTX_TBL->bHsp = 0; DPTX_TBL->Hfp = 88; DPTX_TBL->Hde = 1280;
|
|
DPTX_TBL->Vtt = 1069; DPTX_TBL->Vbp = 36; DPTX_TBL->Vsw = 5;
|
|
DPTX_TBL->bVsp = 0; DPTX_TBL->Vfp = 4; DPTX_TBL->Vde = 1024;
|
|
break;
|
|
case SINK_1280_960:
|
|
DPTX_TBL->FrameRate = 60;
|
|
DPTX_TBL->Htt = 1800; DPTX_TBL->Hbp = 312; DPTX_TBL->Hsw = 112;
|
|
DPTX_TBL->bHsp = 0; DPTX_TBL->Hfp = 96; DPTX_TBL->Hde = 1280;
|
|
DPTX_TBL->Vtt = 1000; DPTX_TBL->Vbp = 36; DPTX_TBL->Vsw = 3;
|
|
DPTX_TBL->bVsp = 0; DPTX_TBL->Vfp = 1; DPTX_TBL->Vde = 960;
|
|
break;
|
|
case SINK_1280_720:
|
|
DPTX_TBL->FrameRate = 60;
|
|
DPTX_TBL->Htt = 1650; DPTX_TBL->Hbp = 220; DPTX_TBL->Hsw = 40;
|
|
DPTX_TBL->bHsp = 0; DPTX_TBL->Hfp = 110; DPTX_TBL->Hde = 1280;
|
|
DPTX_TBL->Vtt = 750; DPTX_TBL->Vbp = 20; DPTX_TBL->Vsw = 5;
|
|
DPTX_TBL->bVsp = 0; DPTX_TBL->Vfp = 5; DPTX_TBL->Vde = 720;
|
|
break;
|
|
case SINK_800_600:
|
|
DPTX_TBL->FrameRate = 60;
|
|
DPTX_TBL->Htt = 1056; DPTX_TBL->Hbp = 88; DPTX_TBL->Hsw = 128;
|
|
DPTX_TBL->bHsp = 0; DPTX_TBL->Hfp = 40; DPTX_TBL->Hde = 800;
|
|
DPTX_TBL->Vtt = 628; DPTX_TBL->Vbp = 23; DPTX_TBL->Vsw = 4;
|
|
DPTX_TBL->bVsp = 0; DPTX_TBL->Vfp = 16; DPTX_TBL->Vde = 600;
|
|
break;
|
|
case SINK_640_480:
|
|
default:
|
|
DPTX_TBL->FrameRate = 60;
|
|
DPTX_TBL->Htt = 800; DPTX_TBL->Hbp = 48; DPTX_TBL->Hsw = 96;
|
|
DPTX_TBL->bHsp = 1; DPTX_TBL->Hfp = 16; DPTX_TBL->Hde = 640;
|
|
DPTX_TBL->Vtt = 525; DPTX_TBL->Vbp = 33; DPTX_TBL->Vsw = 2;
|
|
DPTX_TBL->bVsp = 1; DPTX_TBL->Vfp = 10; DPTX_TBL->Vde = 480;
|
|
break;
|
|
}
|
|
|
|
if (mtk_dp->info.resolution == SINK_3840_2160) {
|
|
// patch for 4k@60 with DSC 3 times compress
|
|
switch (mtk_dp->training_info.ubLinkRate) {
|
|
case DP_LINKRATE_HBR3:
|
|
mvid = 0x5DDE;
|
|
break;
|
|
case DP_LINKRATE_HBR2:
|
|
mvid = 0x8CCD;
|
|
break;
|
|
}
|
|
overwrite = true;
|
|
}
|
|
|
|
mhal_DPTx_OverWrite_MN(mtk_dp, overwrite, mvid, 0x8000);
|
|
|
|
if (mtk_dp->has_dsc) {
|
|
uint8_t Data[1];
|
|
|
|
Data[0] = (u8) mtk_dp->dsc_enable;
|
|
drm_dp_dpcd_write(&mtk_dp->aux, 0x160, Data, 0x1);
|
|
}
|
|
|
|
//interlace not support
|
|
DPTX_TBL->Video_ip_mode = DPTX_VIDEO_PROGRESSIVE;
|
|
mhal_DPTx_SetMSA(mtk_dp);
|
|
|
|
mdrv_DPTx_Set_MISC(mtk_dp);
|
|
if (mtk_dp->info.bPatternGen)
|
|
mdrv_DPTx_PatternGenTypeSel(mtk_dp,
|
|
DPTX_PG_HORIZONTAL_COLOR_BAR,
|
|
DPTX_PG_PURECOLOR_BLUE,
|
|
0xFFF,
|
|
DPTX_PG_LOCATION_ALL);
|
|
|
|
if (!mtk_dp->dsc_enable) {
|
|
mdrv_DPTx_Set_Color_Depth(mtk_dp, mtk_dp->info.depth);
|
|
mdrv_DPTx_Set_Color_Format(mtk_dp, mtk_dp->info.format);
|
|
} else {
|
|
mtk_dp_dsc_pps_send(PPS_4k60);
|
|
mhal_DPTx_EnableDSC(mtk_dp, true);
|
|
}
|
|
}
|
|
|
|
void mtk_dp_fec_enable(unsigned int status)
|
|
{
|
|
if (status)
|
|
mhal_DPTx_EnableFEC(g_mtk_dp, true);
|
|
else
|
|
mhal_DPTx_EnableFEC(g_mtk_dp, false);
|
|
}
|
|
|
|
void mtk_dp_power_save(unsigned int status)
|
|
{
|
|
u8 data;
|
|
|
|
g_mtk_dp->training_info.bCableStateChange = true;
|
|
if (status == 1) {
|
|
fakecablein = false;
|
|
data = 0x1;
|
|
drm_dp_dpcd_write(&g_mtk_dp->aux, DPCD_00600, &data, 1);
|
|
g_mtk_dp->training_info.bCablePlugIn = true;
|
|
mhal_DPTx_Fake_Plugin(g_mtk_dp, true);
|
|
} else if (status == 0) {
|
|
fakecablein = true;
|
|
data = 0x2;
|
|
drm_dp_dpcd_write(&g_mtk_dp->aux, DPCD_00600, &data, 1);
|
|
g_mtk_dp->training_info.bCablePlugIn = false;
|
|
mhal_DPTx_Fake_Plugin(g_mtk_dp, false);
|
|
}
|
|
|
|
queue_work(g_mtk_dp->dptx_wq, &g_mtk_dp->dptx_work);
|
|
}
|
|
|
|
atomic_t dp_comm_event = ATOMIC_INIT(0);
|
|
void mtk_dp_video_trigger(int res)
|
|
{
|
|
DPTXFUNC("0x%x\n", res);
|
|
|
|
atomic_set(&dp_comm_event, res);
|
|
wake_up_interruptible(&g_mtk_dp->control_wq);
|
|
}
|
|
|
|
static int mtk_dp_control_kthread(void *data)
|
|
{
|
|
struct mtk_dp *mtk_dp = data;
|
|
unsigned int videomute = 0;
|
|
unsigned int res = 0;
|
|
|
|
init_waitqueue_head(&mtk_dp->control_wq);
|
|
|
|
while (!kthread_should_stop()) {
|
|
wait_event_interruptible(mtk_dp->control_wq,
|
|
atomic_read(&dp_comm_event));
|
|
|
|
videomute = atomic_read(&dp_comm_event) >> 16;
|
|
res = atomic_read(&dp_comm_event) & 0xff;
|
|
atomic_set(&dp_comm_event, 0);
|
|
|
|
if (videomute & video_unmute) {
|
|
if (!fakecablein && mtk_dp->state > DPTXSTATE_PREPARE)
|
|
mtk_dp->state = DPTXSTATE_PREPARE;
|
|
|
|
mtk_dp->video_enable = true;
|
|
mtk_dp->info.resolution = res;
|
|
queue_work(mtk_dp->dptx_wq, &mtk_dp->dptx_work);
|
|
queue_work(mtk_dp->dptx_wq, &mtk_dp->hdcp_work);
|
|
|
|
} else if (videomute & video_mute) {
|
|
mtk_dp->video_enable = false;
|
|
if (!mtk_dp->dp_ready)
|
|
continue;
|
|
|
|
mdrv_DPTx_Video_Enable(mtk_dp, false);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mtk_drm_dp_get_dev_info(struct drm_device *dev, void *data,
|
|
struct drm_file *file_priv)
|
|
{
|
|
struct mtk_dispif_info *info = data;
|
|
struct mtk_dp *mtk_dp = g_mtk_dp;
|
|
|
|
info->display_id = mtk_dp->id;
|
|
info->displayFormat = mtk_dp->info.format;
|
|
info->displayHeight = mtk_dp->info.DPTX_OUTBL.Vde;
|
|
info->displayWidth = mtk_dp->info.DPTX_OUTBL.Hde;
|
|
info->displayMode = DISPIF_MODE_VIDEO;
|
|
info->displayType = DISPLAYPORT;
|
|
info->isConnected = (mtk_dp->state == DPTXSTATE_NORMAL) ? true : false;
|
|
info->isHwVsyncAvailable = true;
|
|
info->vsyncFPS = g_mtk_dp->info.DPTX_OUTBL.FrameRate * 100;
|
|
DPTXMSG("%s, %d, fake %d\n", __func__, __LINE__, fakecablein);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mtk_drm_dp_audio_enable(struct drm_device *dev, void *data,
|
|
struct drm_file *file_priv)
|
|
{
|
|
struct mtk_dp *mtk_dp = g_mtk_dp;
|
|
|
|
mtk_dp->audio_enable = *(bool *)data;
|
|
DPTXMSG("audio_enable = %d\n", mtk_dp->audio_enable);
|
|
|
|
if (!mtk_dp->dp_ready) {
|
|
DPTXERR("%s, DP is not ready!\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
mdrv_DPTx_I2S_Audio_Enable(mtk_dp, mtk_dp->audio_enable);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mtk_drm_dp_audio_config(struct drm_device *dev, void *data,
|
|
struct drm_file *file_priv)
|
|
{
|
|
struct mtk_dp *mtk_dp = g_mtk_dp;
|
|
|
|
mtk_dp->info.audio_config = *(unsigned int *)data;
|
|
DPTXMSG("audio_config = 0x%x\n", mtk_dp->info.audio_config);
|
|
|
|
if (!mtk_dp->dp_ready) {
|
|
DPTXERR("%s, DP is not ready!\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
mdrv_DPTx_I2S_Audio_Config(mtk_dp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mtk_drm_dp_get_cap(struct drm_device *dev, void *data,
|
|
struct drm_file *file_priv)
|
|
{
|
|
unsigned int ch[7] = {2, 3, 4, 5, 6, 7, 8};
|
|
unsigned int fs[5] = {32, 44, 48, 96, 192};
|
|
unsigned int len[3] = {16, 20, 24};
|
|
unsigned int *dp_cap = data;
|
|
|
|
if (fakecablein) {
|
|
DPTXMSG("force audio format %dCH, %dkHz, %dbit\n",
|
|
ch[force_ch], fs[force_fs], len[force_len]);
|
|
*dp_cap = ((BIT(force_ch) << DP_CAPABILITY_CHANNEL_SFT)
|
|
| (BIT(force_fs) << DP_CAPABILITY_SAMPLERATE_SFT)
|
|
| (BIT(force_len) << DP_CAPABILITY_BITWIDTH_SFT));
|
|
return 0;
|
|
}
|
|
|
|
if (g_mtk_dp->dp_ready)
|
|
*dp_cap = g_mtk_dp->info.audio_caps;
|
|
|
|
if (*dp_cap == 0)
|
|
*dp_cap = ((DP_CHANNEL_2 << DP_CAPABILITY_CHANNEL_SFT)
|
|
| (DP_SAMPLERATE_192 << DP_CAPABILITY_SAMPLERATE_SFT)
|
|
| (DP_BITWIDTH_24 << DP_CAPABILITY_BITWIDTH_SFT));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
int mtk_drm_dp_get_info(struct drm_device *dev,
|
|
struct drm_mtk_session_info *info)
|
|
{
|
|
DPTXDBG("%s, %d\n", __func__, __LINE__);
|
|
info->physicalWidthUm = 900;
|
|
info->physicalHeightUm = 1000;
|
|
info->vsyncFPS = g_mtk_dp->info.DPTX_OUTBL.FrameRate * 100;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void mtk_dp_get_dsc_capability(u8 *dsc_cap)
|
|
{
|
|
if (!g_mtk_dp->dp_ready) {
|
|
DPTXMSG("%s, DP is not ready!\n", __func__);
|
|
return;
|
|
}
|
|
|
|
drm_dp_dpcd_read(&g_mtk_dp->aux, DPCD_00060, dsc_cap, 16);
|
|
}
|
|
|
|
void mtk_dp_dsc_pps_send(u8 *PPS_128)
|
|
{
|
|
u8 dsc_cap[16];
|
|
u16 chunk_size = PPS_128[14] << 8 | PPS_128[15];
|
|
u16 pic_width = PPS_128[8] << 8 | PPS_128[9];
|
|
u16 slice_width = PPS_128[12] << 8 | PPS_128[13];
|
|
|
|
if (!g_mtk_dp->dp_ready) {
|
|
DPTXMSG("%s, DP is not ready!\n", __func__);
|
|
return;
|
|
}
|
|
|
|
mtk_dp_get_dsc_capability(dsc_cap);
|
|
|
|
PPS_128[0x0]
|
|
= ((dsc_cap[0x1] & 0xf) << 4) | ((dsc_cap[0x1] & 0xf0) >> 4);
|
|
if (dsc_cap[0x6] & BIT(0))
|
|
PPS_128[0x4] |= (0x1 << 5);
|
|
else
|
|
PPS_128[0x4] &= ~(0x1 << 5);
|
|
|
|
mdrv_DPTx_DSC_SetPPS(g_mtk_dp, PPS_128, true);
|
|
mdrv_DPTx_DSC_SetParam(g_mtk_dp, pic_width/slice_width, chunk_size);
|
|
}
|
|
|
|
struct edid *mtk_dp_handle_edid(struct mtk_dp *mtk_dp)
|
|
{
|
|
struct drm_connector *connector = &mtk_dp->conn;
|
|
|
|
/* use cached edid if we have one */
|
|
if (mtk_dp->edid) {
|
|
/* invalid edid */
|
|
if (IS_ERR(mtk_dp->edid))
|
|
return NULL;
|
|
|
|
DPTXMSG("%s, duplicate edid from mtk_dp->edid!\n");
|
|
return drm_edid_duplicate(mtk_dp->edid);
|
|
}
|
|
|
|
DPTXMSG("Get edid from RX!\n");
|
|
return drm_get_edid(connector, &mtk_dp->aux.ddc);
|
|
}
|
|
|
|
irqreturn_t mtk_dp_hpd_event(int hpd, void *dev)
|
|
{
|
|
struct mtk_dp *mtk_dp = dev;
|
|
|
|
mdrv_DPTx_ISR(mtk_dp);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
void mtk_dp_phy_param_init(struct mtk_dp *mtk_dp, uint32_t *buffer, int size)
|
|
{
|
|
int i = 0;
|
|
uint8_t mask = 0x3F;
|
|
|
|
if (buffer == NULL || size != DPTX_PHY_REG_COUNT) {
|
|
DPTXERR("invalid param\n");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < DPTX_PHY_LEVEL_COUNT; i++) {
|
|
mtk_dp->phy_params[i].C0 = (buffer[i/4] >> (8*(i%4))) & mask;
|
|
mtk_dp->phy_params[i].CP1
|
|
= (buffer[i/4 + 3] >> (8*(i%4))) & mask;
|
|
}
|
|
}
|
|
|
|
static int mtk_dp_dt_parse_pdata(struct mtk_dp *mtk_dp,
|
|
struct platform_device *pdev)
|
|
{
|
|
struct resource regs;
|
|
struct device *dev = &pdev->dev;
|
|
int ret = 0;
|
|
uint32_t phy_params_int[DPTX_PHY_REG_COUNT] = {
|
|
0x20181410, 0x20241e18, 0x00003028,
|
|
0x10080400, 0x000c0600, 0x00000008
|
|
};
|
|
uint32_t phy_params_dts[DPTX_PHY_REG_COUNT];
|
|
|
|
if (of_address_to_resource(dev->of_node, 0, ®s) != 0)
|
|
dev_err(dev, "Missing reg in %s node\n",
|
|
dev->of_node->full_name);
|
|
|
|
mtk_dp->regs = of_iomap(dev->of_node, 0);
|
|
mtk_dp->dp_tx_clk = devm_clk_get(dev, "dp_tx_faxi");
|
|
if (IS_ERR(mtk_dp->dp_tx_clk)) {
|
|
ret = PTR_ERR(mtk_dp->dp_tx_clk);
|
|
dev_err(dev, "Failed to get dptx clock: %d\n", ret);
|
|
goto error;
|
|
}
|
|
|
|
ret = of_property_read_u32_array(dev->of_node, "dptx,phy_params",
|
|
phy_params_dts, ARRAY_SIZE(phy_params_dts));
|
|
if (ret) {
|
|
DPTXMSG("get phy_params fail, use default val, ret %d\n", ret);
|
|
mtk_dp_phy_param_init(mtk_dp,
|
|
phy_params_int, ARRAY_SIZE(phy_params_int));
|
|
} else
|
|
mtk_dp_phy_param_init(mtk_dp,
|
|
phy_params_dts, ARRAY_SIZE(phy_params_dts));
|
|
|
|
DPTXMSG("reg and clock get success!\n");
|
|
error:
|
|
return 0;
|
|
}
|
|
|
|
static inline struct mtk_dp *mtk_dp_ctx_from_conn(struct drm_connector *c)
|
|
{
|
|
return container_of(c, struct mtk_dp, conn);
|
|
}
|
|
|
|
static enum drm_connector_status mtk_dp_conn_detect(struct drm_connector *conn,
|
|
bool force)
|
|
{
|
|
struct mtk_dp *mtk_dp = mtk_dp_ctx_from_conn(conn);
|
|
|
|
DPTXFUNC("fakecablein %d\n", fakecablein);
|
|
if (fakecablein)
|
|
return connector_status_connected;
|
|
|
|
return ((mtk_dp->dp_ready) ? connector_status_connected :
|
|
connector_status_disconnected);
|
|
}
|
|
|
|
static void mtk_dp_conn_destroy(struct drm_connector *conn)
|
|
{
|
|
drm_connector_cleanup(conn);
|
|
}
|
|
|
|
static const struct drm_connector_funcs mtk_dp_connector_funcs = {
|
|
.detect = mtk_dp_conn_detect,
|
|
.fill_modes = drm_helper_probe_single_connector_modes,
|
|
.destroy = mtk_dp_conn_destroy,
|
|
.reset = drm_atomic_helper_connector_reset,
|
|
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
|
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
|
};
|
|
|
|
static int mtk_dp_conn_get_modes(struct drm_connector *conn)
|
|
{
|
|
struct mtk_dp *mtk_dp = mtk_dp_ctx_from_conn(conn);
|
|
int ret;
|
|
struct drm_display_mode *mode;
|
|
struct drm_device *dev = conn->dev;
|
|
|
|
DPTXFUNC("fakecablein %d, res %d\n", fakecablein, fakeres);
|
|
if (fakecablein) {
|
|
conn->display_info.width_mm = 900;
|
|
conn->display_info.height_mm = 1100;
|
|
|
|
/* driver figures it out in this case */
|
|
conn->display_info.bpc = 8;
|
|
conn->display_info.color_formats = DRM_COLOR_FORMAT_RGB444;
|
|
conn->display_info.cea_rev = 0;
|
|
conn->display_info.max_tmds_clock = 0;
|
|
conn->display_info.dvi_dual = false;
|
|
|
|
if (fakeres == SINK_3840_2160)
|
|
mode = drm_mode_duplicate(dev, &dptx_est_modes[0]);
|
|
else if (fakeres == SINK_3840_2160_30)
|
|
mode = drm_mode_duplicate(dev, &dptx_est_modes[1]);
|
|
else if (fakeres == SINK_1920_1080)
|
|
mode = drm_mode_duplicate(dev, &dptx_est_modes[2]);
|
|
else if (fakeres == SINK_1280_720)
|
|
mode = drm_mode_duplicate(dev, &dptx_est_modes[3]);
|
|
else
|
|
mode = drm_mode_duplicate(dev, &dptx_est_modes[4]);
|
|
|
|
drm_mode_set_name(mode);
|
|
mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
|
|
drm_mode_probed_add(conn, mode);
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (mtk_dp->edid) {
|
|
drm_mode_connector_update_edid_property(&mtk_dp->conn,
|
|
mtk_dp->edid);
|
|
ret = drm_add_edid_modes(&mtk_dp->conn, mtk_dp->edid);
|
|
drm_edid_to_eld(&mtk_dp->conn, mtk_dp->edid);
|
|
DPTXMSG("%s modes = %d\n", __func__, ret);
|
|
if (ret)
|
|
return ret;
|
|
} else {
|
|
drm_mode_connector_update_edid_property(&mtk_dp->conn, NULL);
|
|
DPTXMSG("%s NULL EDID\n", __func__);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct drm_display_limit_mode {
|
|
int hdisplay;
|
|
int vdisplay;
|
|
int vrefresh;
|
|
int clock;
|
|
int valid;
|
|
};
|
|
|
|
static struct drm_display_limit_mode dp_plat_limit[] = {
|
|
{3840, 2160, 60, 594000, 1},
|
|
{3840, 2160, 30, 297000, 1},
|
|
{1080, 2460, 60, 174110, 1},
|
|
{1920, 1200, 60, 152128, 1},
|
|
{1920, 1080, 60, 148500, 1},
|
|
{1280, 720, 60, 74250, 1},
|
|
{ 640, 480, 60, 25200, 1},
|
|
};
|
|
|
|
void mtk_dp_enable_4k60(int enable)
|
|
{
|
|
#if DPTX_SUPPORT_DSC
|
|
if (enable > 0)
|
|
dp_plat_limit[0].valid = 1;
|
|
else
|
|
dp_plat_limit[0].valid = 0;
|
|
#else
|
|
dp_plat_limit[0].valid = 0;
|
|
#endif
|
|
|
|
DPTXFUNC("enable = %d\n", dp_plat_limit[0].valid);
|
|
}
|
|
|
|
static enum drm_mode_status mtk_dp_conn_mode_valid(struct drm_connector *conn,
|
|
struct drm_display_mode *mode)
|
|
{
|
|
int plat_limit_array = ARRAY_SIZE(dp_plat_limit);
|
|
int i;
|
|
struct mtk_dp *mtk_dp = mtk_dp_ctx_from_conn(conn);
|
|
int bandwidth = mtk_dp->training_info.ubLinkLaneCount *
|
|
mtk_dp->training_info.ubLinkRate * 27000 * 8 / 24;
|
|
|
|
if (mode->hdisplay == 3840 && mode->vdisplay == 2160 &&
|
|
mode->vrefresh == 60 && mtk_dp->has_dsc)
|
|
bandwidth = bandwidth * 594 * 10 / 2025;
|
|
|
|
if (fakecablein == true)
|
|
bandwidth = dp_plat_limit[0].clock;
|
|
|
|
DPTXDBG("Hde:%d,Vde:%d,fps:%d,clk:%d,bandwidth:%d,4k60:%d\n",
|
|
mode->hdisplay, mode->vdisplay, mode->vrefresh, mode->clock,
|
|
bandwidth, dp_plat_limit[0].valid);
|
|
|
|
if (mode->clock > (dp_plat_limit[0].clock + 50000))
|
|
return MODE_CLOCK_HIGH;
|
|
if (mode->clock < (dp_plat_limit[plat_limit_array-1].clock - 5000))
|
|
return MODE_CLOCK_LOW;
|
|
|
|
for (i = 0; i < plat_limit_array; i++) {
|
|
if (mode->hdisplay == 640 && mode->vdisplay == 480)
|
|
break;
|
|
|
|
if (mode->vrefresh == 0 && mode->htotal != 0
|
|
&& mode->vtotal != 0)
|
|
mode->vrefresh
|
|
= mode->clock / mode->htotal / mode->vtotal;
|
|
|
|
if (mode->clock == 0)
|
|
mode->clock
|
|
= mode->htotal * mode->vtotal * mode->vrefresh;
|
|
|
|
if ((abs(dp_plat_limit[i].vrefresh - mode->vrefresh) <= 1)
|
|
&& (mode->vdisplay == dp_plat_limit[i].vdisplay)
|
|
&& (mode->hdisplay == dp_plat_limit[i].hdisplay)
|
|
&& (dp_plat_limit[i].clock < bandwidth)) {
|
|
|
|
if (dp_plat_limit[i].valid)
|
|
break;
|
|
|
|
return MODE_BAD_VSCAN;
|
|
}
|
|
}
|
|
|
|
if (i >= plat_limit_array)
|
|
return MODE_BAD_VSCAN;
|
|
|
|
DPTXDBG("%s xres=%d, yres=%d, refresh=%d, clock=%d\n",
|
|
__func__, mode->hdisplay, mode->vdisplay,
|
|
mode->vrefresh,
|
|
mode->clock);
|
|
|
|
return drm_mode_validate_size(mode, 0x1fff, 0x1fff);
|
|
}
|
|
|
|
static const struct drm_connector_helper_funcs mtk_dp_connector_helper_funcs = {
|
|
.get_modes = mtk_dp_conn_get_modes,
|
|
.mode_valid = mtk_dp_conn_mode_valid,
|
|
};
|
|
|
|
static void mtk_dp_encoder_destroy(struct drm_encoder *encoder)
|
|
{
|
|
drm_encoder_cleanup(encoder);
|
|
kfree(encoder);
|
|
}
|
|
|
|
|
|
static const struct drm_encoder_funcs mtk_dp_enc_funcs = {
|
|
.destroy = mtk_dp_encoder_destroy,
|
|
};
|
|
|
|
static ssize_t mtk_dp_aux_transfer(struct drm_dp_aux *mtk_aux,
|
|
struct drm_dp_aux_msg *msg)
|
|
{
|
|
u8 ubCmd;
|
|
void *pData;
|
|
size_t ubLength, ret = 0;
|
|
u32 usADDR;
|
|
bool mot, ack = false;
|
|
struct mtk_dp *mtk_dp;
|
|
|
|
mtk_dp = container_of(mtk_aux, struct mtk_dp, aux);
|
|
mot = (msg->request & DP_AUX_I2C_MOT) ? true : false;
|
|
ubCmd = msg->request;
|
|
usADDR = msg->address;
|
|
ubLength = msg->size;
|
|
pData = msg->buffer;
|
|
|
|
switch (ubCmd) {
|
|
case DP_AUX_I2C_MOT:
|
|
case DP_AUX_I2C_WRITE:
|
|
case DP_AUX_NATIVE_WRITE:
|
|
case DP_AUX_I2C_WRITE_STATUS_UPDATE:
|
|
case DP_AUX_I2C_WRITE_STATUS_UPDATE | DP_AUX_I2C_MOT:
|
|
ubCmd &= ~DP_AUX_I2C_WRITE_STATUS_UPDATE;
|
|
ack = mdrv_DPTx_AuxWrite_DPCD(mtk_dp, ubCmd,
|
|
usADDR, ubLength, pData);
|
|
break;
|
|
|
|
case DP_AUX_I2C_READ:
|
|
case DP_AUX_NATIVE_READ:
|
|
case DP_AUX_I2C_READ | DP_AUX_I2C_MOT:
|
|
ack = mdrv_DPTx_AuxRead_DPCD(mtk_dp, ubCmd,
|
|
usADDR, ubLength, pData);
|
|
break;
|
|
|
|
default:
|
|
DPTXERR("invalid aux cmd = %d\n", ubCmd);
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
if (ack) {
|
|
msg->reply = DP_AUX_NATIVE_REPLY_ACK | DP_AUX_I2C_REPLY_ACK;
|
|
ret = ubLength;
|
|
} else {
|
|
msg->reply = DP_AUX_NATIVE_REPLY_NACK | DP_AUX_I2C_REPLY_NACK;
|
|
ret = -EAGAIN;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void mtk_dp_aux_init(struct mtk_dp *mtk_dp)
|
|
{
|
|
drm_dp_aux_init(&mtk_dp->aux);
|
|
DPTXMSG("aux hw_mutex = 0x%x\n", &mtk_dp->aux.hw_mutex);
|
|
|
|
mtk_dp->aux.name = kasprintf(GFP_KERNEL, "DPDDC-MTK");
|
|
mtk_dp->aux.transfer = mtk_dp_aux_transfer;
|
|
}
|
|
|
|
void mtk_dp_test(unsigned int status)
|
|
{
|
|
DPTXMSG("g_mtk_dp = 0x%x\n", g_mtk_dp);
|
|
mhal_DPTx_SWInterruptEnable(g_mtk_dp, true);
|
|
mhal_DPTx_SWInterruptSet(g_mtk_dp, status);
|
|
}
|
|
|
|
void mtk_dp_hdcp_enable(bool enable)
|
|
{
|
|
g_hdcp_on = enable;
|
|
}
|
|
|
|
void mtk_dp_force_hdcp1x(bool enable)
|
|
{
|
|
g_mtk_dp->info.bForceHDCP1x = enable;
|
|
}
|
|
|
|
static char *mtk_hdcp_version(void)
|
|
{
|
|
#ifdef DPTX_HDCP_ENABLE
|
|
if (g_mtk_dp->info.hdcp2_info.bEnable) {
|
|
if (g_mtk_dp->info.hdcp2_info.bRepeater)
|
|
return "DP_HDCP2X_REPEATER";
|
|
else
|
|
return "DP_HDCP2X";
|
|
} else if (g_mtk_dp->info.hdcp1x_info.bEnable) {
|
|
if (g_mtk_dp->info.hdcp1x_info.bRepeater)
|
|
return "DP_HDCP1X_REAPEATER";
|
|
else
|
|
return "DP_HDCP1X";
|
|
}
|
|
#endif
|
|
return "DP_HDCP_NONE";
|
|
}
|
|
|
|
static char *mtk_hdcp_status(void)
|
|
{
|
|
#ifdef DPTX_HDCP_ENABLE
|
|
if (g_mtk_dp->info.bAuthStatus == AUTH_PASS)
|
|
return "DP_AUTH_STATUS_PASS";
|
|
else if (g_mtk_dp->info.bAuthStatus == AUTH_FAIL)
|
|
return "DP_AUTH_STATUS_FAIL";
|
|
else if (g_mtk_dp->info.bAuthStatus != AUTH_ZERO)
|
|
return "DP_AUTH_STATUS_DOING";
|
|
#endif
|
|
return "DP_AUTH_STATUS_NONE";
|
|
}
|
|
|
|
int mtk_dp_hdcp_getInfo(char *buffer, int size)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!g_hdcp_on)
|
|
ret = snprintf(buffer, size,
|
|
"HDCP Function is disable!\n");
|
|
else if (g_mtk_dp->info.bForceHDCP1x)
|
|
ret = snprintf(buffer, size,
|
|
"Force HDCP1x is enable, not support HDCP2x!\n"
|
|
"HDCP INFO:%s, %s\n",
|
|
mtk_hdcp_version(), mtk_hdcp_status());
|
|
else
|
|
ret = snprintf(buffer, size, "HDCP HINFO:%s, %s\n",
|
|
mtk_hdcp_version(), mtk_hdcp_status());
|
|
|
|
return ret;
|
|
}
|
|
|
|
int mtk_dp_phy_getInfo(char *buffer, int size)
|
|
{
|
|
int len = 0;
|
|
int i = 0;
|
|
char *phy_names[10] = {
|
|
"L0P0", "L0P1", "L0P2", "L0P3", "L1P0",
|
|
"L1P1", "L1P2", "L2P0", "L2P1", "L3P0"};
|
|
|
|
len = snprintf(buffer, size, "PHY INFO:\n");
|
|
for (i = 0; i < DPTX_PHY_LEVEL_COUNT; i++)
|
|
len += snprintf(buffer + len, size - len,
|
|
"#%d(%s):C0 = %#04X(%2d), CP1 = %#04X(%2d)\n", i,
|
|
phy_names[i],
|
|
g_mtk_dp->phy_params[i].C0, g_mtk_dp->phy_params[i].C0,
|
|
g_mtk_dp->phy_params[i].CP1,
|
|
g_mtk_dp->phy_params[i].CP1);
|
|
|
|
return len;
|
|
}
|
|
|
|
void mtk_dp_set_adjust_phy(uint8_t index, uint8_t c0, uint8_t cp1)
|
|
{
|
|
if (index >= 10) {
|
|
DPTXERR("index(%d) must < 10!", index);
|
|
return;
|
|
}
|
|
|
|
g_mtk_dp->phy_params[index].C0 = c0;
|
|
g_mtk_dp->phy_params[index].CP1 = cp1;
|
|
}
|
|
|
|
void mtk_dp_hotplug_uevent(unsigned int event)
|
|
{
|
|
if (g_mtk_dp->info.bPatternGen)
|
|
return;
|
|
|
|
DPTXFUNC("fake:%d, event:%d\n", fakecablein, event);
|
|
notify_uevent_user(&dptx_notify_data,
|
|
event > 0 ? DPTX_STATE_ACTIVE : DPTX_STATE_NO_DEVICE);
|
|
|
|
if (g_mtk_dp->info.audio_caps != 0)
|
|
extcon_set_state_sync(dptx_extcon, EXTCON_DISP_HDMI,
|
|
event > 0 ? true : false);
|
|
}
|
|
|
|
void mtk_dp_force_audio(unsigned int ch, unsigned int fs, unsigned int len)
|
|
{
|
|
if (ch != 0xff)
|
|
force_ch = ch;
|
|
if (fs != 0xff)
|
|
force_fs = fs;
|
|
if (len != 0xff)
|
|
force_len = len;
|
|
|
|
fakecablein = true;
|
|
}
|
|
|
|
void mtk_dp_force_res(unsigned int res, unsigned int bpc)
|
|
{
|
|
DPTXMSG("status:0x%x->0x%x; bpc:0x%x->0x%x\n",
|
|
fakeres, res, fakebpc, bpc);
|
|
fakeres = res;
|
|
fakebpc = bpc;
|
|
}
|
|
|
|
void mtk_dp_fake_plugin(unsigned int status, unsigned int bpc)
|
|
{
|
|
if (g_mtk_dp->bPowerOn) {
|
|
mdrv_DPTx_VideoMute(g_mtk_dp, true);
|
|
mdrv_DPTx_AudioMute(g_mtk_dp, true);
|
|
}
|
|
|
|
fakeres = FAKE_DEFAULT_RES;
|
|
fakebpc = DP_COLOR_DEPTH_8BIT;
|
|
|
|
mtk_dp_hotplug_uevent(0x0);
|
|
kfree(g_mtk_dp->edid);
|
|
g_mtk_dp->edid = NULL;
|
|
|
|
if (fakeres == SINK_3840_2160)
|
|
mtk_dp_enable_4k60(true);
|
|
else
|
|
mtk_dp_enable_4k60(false);
|
|
|
|
|
|
msleep(100);
|
|
|
|
mtk_dp_force_res(status, bpc);
|
|
if (status < FAKE_DEFAULT_RES)
|
|
fakecablein = true;
|
|
else
|
|
fakecablein = false;
|
|
|
|
g_mtk_dp->state = DPTXSTATE_INITIAL;
|
|
mtk_dp_hotplug_uevent(1);
|
|
}
|
|
|
|
void mtk_dp_HPDInterruptSet(int bstatus)
|
|
{
|
|
DDPFUNC("status:%d[2:DISCONNECT, 4:CONNECT, 8:IRQ] Power:%d\n",
|
|
bstatus, g_mtk_dp->bPowerOn);
|
|
|
|
if ((bstatus == HPD_CONNECT && !g_mtk_dp->bPowerOn)
|
|
|| (bstatus == HPD_DISCONNECT && g_mtk_dp->bPowerOn)
|
|
|| (bstatus == HPD_INT_EVNET && g_mtk_dp->bPowerOn)) {
|
|
|
|
if (bstatus == HPD_CONNECT) {
|
|
int ret;
|
|
|
|
ret = clk_prepare_enable(g_mtk_dp->dp_tx_clk);
|
|
if (ret < 0)
|
|
DPTXERR("Fail to enable dptx clock: %d\n", ret);
|
|
|
|
mdrv_DPTx_InitPort(g_mtk_dp);
|
|
mhal_DPTx_USBC_HPD(g_mtk_dp, true);
|
|
g_mtk_dp->bPowerOn = true;
|
|
} else if (bstatus == HPD_DISCONNECT)
|
|
mhal_DPTx_USBC_HPD(g_mtk_dp, false);
|
|
|
|
mdrv_DPTx_USBC_HPD_Event(bstatus);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void mtk_dp_SWInterruptSet(int bstatus)
|
|
{
|
|
mutex_lock(&dp_lock);
|
|
|
|
if ((bstatus == HPD_DISCONNECT && g_mtk_dp->bPowerOn)
|
|
|| (bstatus == HPD_CONNECT && !g_mtk_dp->bPowerOn))
|
|
g_mtk_dp->bUeventToHwc = true;
|
|
|
|
if (!g_mtk_dp->bPowerOn && bstatus == HPD_DISCONNECT
|
|
&& g_mtk_dp->disp_status == DPTX_DISP_SUSPEND) {
|
|
DPTXMSG("System is sleeping, Plug Out\n");
|
|
mtk_dp_hotplug_uevent(0);
|
|
g_mtk_dp->disp_status = DPTX_DISP_NONE;
|
|
mutex_unlock(&dp_lock);
|
|
return;
|
|
}
|
|
|
|
mtk_dp_HPDInterruptSet(bstatus);
|
|
|
|
mutex_unlock(&dp_lock);
|
|
}
|
|
|
|
void mtk_dp_poweroff(void)
|
|
{
|
|
DPTXFUNC();
|
|
mutex_lock(&dp_lock);
|
|
if (g_mtk_dp->disp_status == DPTX_DISP_NONE) {
|
|
DPTXMSG("DPTX has been powered off\n");
|
|
mutex_unlock(&dp_lock);
|
|
return;
|
|
}
|
|
|
|
g_mtk_dp->disp_status = DPTX_DISP_SUSPEND;
|
|
mtk_dp_HPDInterruptSet(HPD_DISCONNECT);
|
|
mutex_unlock(&dp_lock);
|
|
}
|
|
|
|
void mtk_dp_poweron(void)
|
|
{
|
|
DPTXFUNC();
|
|
mutex_lock(&dp_lock);
|
|
g_mtk_dp->disp_status = DPTX_DISP_RESUME;
|
|
if (g_mtk_dp->bPowerOn) {
|
|
DPTXMSG("DPTX has been powered on\n");
|
|
mutex_unlock(&dp_lock);
|
|
return;
|
|
}
|
|
|
|
mtk_dp_HPDInterruptSet(HPD_CONNECT);
|
|
mutex_unlock(&dp_lock);
|
|
}
|
|
|
|
static int mtk_dp_create_workqueue(struct mtk_dp *mtk_dp)
|
|
{
|
|
mtk_dp->dptx_wq = create_singlethread_workqueue("mtk_dptx_wq");
|
|
if (!mtk_dp->dptx_wq) {
|
|
DPTXERR("Failed to create dptx workqueue\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
INIT_WORK(&mtk_dp->dptx_work, mdrv_DPTx_main_handle);
|
|
INIT_WORK(&mtk_dp->hdcp_work, mdrv_DPTx_hdcp_handle);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mtk_dp_bind(struct device *dev, struct device *master, void *data)
|
|
{
|
|
struct mtk_dp *mtk_dp = dev_get_drvdata(dev);
|
|
struct drm_device *drm = data;
|
|
int ret;
|
|
|
|
mtk_dp->drm_dev = drm;
|
|
|
|
DPTXDBG("%s, %d, mtk_dp 0x%p\n", __func__, __LINE__, mtk_dp);
|
|
|
|
ret = drm_connector_init(drm, &mtk_dp->conn, &mtk_dp_connector_funcs,
|
|
DRM_MODE_CONNECTOR_DisplayPort);
|
|
|
|
if (ret) {
|
|
dev_err(mtk_dp->dev, "Failed to initialize connector: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
drm_connector_helper_add(&mtk_dp->conn, &mtk_dp_connector_helper_funcs);
|
|
|
|
if (drm_encoder_init(drm, &mtk_dp->enc, &mtk_dp_enc_funcs,
|
|
DRM_MODE_ENCODER_DPMST, "DP MST"))
|
|
goto err_encoder_init;
|
|
|
|
mtk_dp->enc.possible_crtcs = 2;
|
|
|
|
drm_connector_attach_encoder(&mtk_dp->conn, &mtk_dp->enc);
|
|
|
|
g_mtk_dp = mtk_dp;
|
|
|
|
mtk_dp->conn.kdev = drm->dev;
|
|
mtk_dp->aux.dev = mtk_dp->conn.kdev;
|
|
if (drm_dp_aux_register(&mtk_dp->aux))
|
|
goto err_encoder_init;
|
|
|
|
DPTXFUNC("Successful\n");
|
|
return 0;
|
|
|
|
err_encoder_init:
|
|
drm_connector_cleanup(&mtk_dp->conn);
|
|
|
|
DPTXERR("%s failed! %d\n", __func__, __LINE__);
|
|
return ret;
|
|
}
|
|
|
|
static void mtk_dp_unbind(struct device *dev, struct device *master,
|
|
void *data)
|
|
{
|
|
DPTXFUNC();
|
|
}
|
|
|
|
static const struct component_ops mtk_dp_component_ops = {
|
|
.bind = mtk_dp_bind, .unbind = mtk_dp_unbind,
|
|
};
|
|
|
|
static int mtk_drm_dp_probe(struct platform_device *pdev)
|
|
{
|
|
struct mtk_dp *mtk_dp;
|
|
struct device *dev = &pdev->dev;
|
|
int ret, irq_num = 0;
|
|
int comp_id;
|
|
struct mtk_drm_private *mtk_priv = dev_get_drvdata(dev);
|
|
|
|
DPTXFUNC();
|
|
mtk_dp = devm_kmalloc(dev, sizeof(*mtk_dp), GFP_KERNEL | __GFP_ZERO);
|
|
if (!mtk_dp)
|
|
return -ENOMEM;
|
|
|
|
memset(mtk_dp, 0, sizeof(struct mtk_dp));
|
|
mtk_dp->id = 0x0;
|
|
mtk_dp->dev = dev;
|
|
mtk_dp->priv = mtk_priv;
|
|
mtk_dp->bUeventToHwc = false;
|
|
mtk_dp->disp_status = DPTX_DISP_NONE;
|
|
|
|
irq_num = platform_get_irq(pdev, 0);
|
|
if (irq_num < 0) {
|
|
dev_err(&pdev->dev, "failed to request dp irq resource\n");
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
ret = mtk_dp_dt_parse_pdata(mtk_dp, pdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
comp_id = mtk_ddp_comp_get_id(dev->of_node, MTK_DISP_DPTX);
|
|
if (comp_id < 0) {
|
|
dev_err(dev, "Failed to identify by alias: %d\n", comp_id);
|
|
ret = comp_id;
|
|
goto error;
|
|
}
|
|
|
|
ret = mtk_ddp_comp_init(dev, dev->of_node, &mtk_dp->ddp_comp, comp_id,
|
|
NULL);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to initialize component: %d\n", ret);
|
|
goto error;
|
|
}
|
|
|
|
mtk_dp_aux_init(mtk_dp);
|
|
|
|
DPTXMSG("comp_id %d, type %d, irq %d\n", comp_id,
|
|
MTK_DISP_DPTX, irq_num);
|
|
|
|
irq_set_status_flags(irq_num, IRQ_TYPE_LEVEL_HIGH);
|
|
ret = devm_request_irq(&pdev->dev, irq_num, mtk_dp_hpd_event,
|
|
IRQ_TYPE_LEVEL_HIGH, dev_name(&pdev->dev), mtk_dp);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "failed to request mediatek dptx irq\n");
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
dptx_notify_data.name = "hdmi"; // now hwc not support DP
|
|
dptx_notify_data.index = 0;
|
|
dptx_notify_data.state = DPTX_STATE_NO_DEVICE;
|
|
ret = dptx_uevent_dev_register(&dptx_notify_data);
|
|
if (ret)
|
|
DPTXERR("switch_dev_register failed, returned:%d!\n", ret);
|
|
|
|
dptx_extcon = devm_extcon_dev_allocate(&pdev->dev, dptx_cable);
|
|
if (IS_ERR(dptx_extcon)) {
|
|
DPTXERR("Couldn't allocate dptx extcon device\n");
|
|
return PTR_ERR(dptx_extcon);
|
|
}
|
|
|
|
dptx_extcon->dev.init_name = "dp_audio";
|
|
ret = devm_extcon_dev_register(&pdev->dev, dptx_extcon);
|
|
if (ret) {
|
|
pr_debug("failed to register dptx extcon: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
g_mtk_dp = mtk_dp;
|
|
mutex_init(&dp_lock);
|
|
|
|
platform_set_drvdata(pdev, mtk_dp);
|
|
|
|
mtk_dp->control_task = kthread_run(mtk_dp_control_kthread,
|
|
(void *)mtk_dp, "mtk_dp_video_trigger");
|
|
|
|
mtk_dp_create_workqueue(mtk_dp);
|
|
|
|
return component_add(&pdev->dev, &mtk_dp_component_ops);
|
|
|
|
error:
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
static int mtk_drm_dp_remove(struct platform_device *pdev)
|
|
{
|
|
struct mtk_dp *mtk_dp = platform_get_drvdata(pdev);
|
|
|
|
if (mtk_dp->dptx_wq)
|
|
destroy_workqueue(mtk_dp->dptx_wq);
|
|
|
|
mutex_destroy(&dp_lock);
|
|
drm_connector_cleanup(&mtk_dp->conn);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int mtk_dp_suspend(struct device *dev)
|
|
{
|
|
struct mtk_dp *mtk_dp = dev_get_drvdata(dev);
|
|
|
|
mutex_lock(&dp_lock);
|
|
if (mtk_dp->bPowerOn) {
|
|
mtk_dp->disp_status = DPTX_DISP_SUSPEND;
|
|
mtk_dp_HPDInterruptSet(HPD_DISCONNECT);
|
|
mdelay(5);
|
|
}
|
|
mutex_unlock(&dp_lock);
|
|
|
|
DPTXFUNC();
|
|
return 0;
|
|
}
|
|
|
|
static int mtk_dp_resume(struct device *dev)
|
|
{
|
|
DPTXFUNC();
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static SIMPLE_DEV_PM_OPS(mtk_dp_pm_ops,
|
|
mtk_dp_suspend, mtk_dp_resume);
|
|
|
|
|
|
static const struct of_device_id mtk_dp_of_match[] = {
|
|
{ .compatible = "mediatek,mt6885-dp_tx", },
|
|
{ },
|
|
};
|
|
|
|
|
|
struct platform_driver mtk_dp_tx_driver = {
|
|
.probe = mtk_drm_dp_probe,
|
|
.remove = mtk_drm_dp_remove,
|
|
.driver = {
|
|
.name = "mediatek-drm-dp",
|
|
.of_match_table = mtk_dp_of_match,
|
|
.pm = &mtk_dp_pm_ops,
|
|
},
|
|
};
|
|
|