mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-07 19:30:30 +09:00
drm/rockchip: dw-dp: support DP SI auto test
Use the DPCD Automated Testing Filed to auto test DP SI. Signed-off-by: Zhang Yubing <yubing.zhang@rock-chips.com> Change-Id: Ic3c01cd2068125ff415a205e57a0b873cc2541fc
This commit is contained in:
@@ -246,6 +246,16 @@ struct dw_dp_hotplug {
|
||||
bool status;
|
||||
};
|
||||
|
||||
struct dw_dp_compliance_data {
|
||||
struct drm_dp_phy_test_params phytest;
|
||||
};
|
||||
|
||||
struct dw_dp_compliance {
|
||||
unsigned long test_type;
|
||||
struct dw_dp_compliance_data test_data;
|
||||
bool test_active;
|
||||
};
|
||||
|
||||
struct dw_dp {
|
||||
struct device *dev;
|
||||
struct regmap *regmap;
|
||||
@@ -275,6 +285,7 @@ struct dw_dp {
|
||||
struct dw_dp_link link;
|
||||
struct dw_dp_video video;
|
||||
struct dw_dp_audio audio;
|
||||
struct dw_dp_compliance compliance;
|
||||
|
||||
DECLARE_BITMAP(sdp_reg_bank, SDP_REG_BANK_SIZE);
|
||||
|
||||
@@ -865,27 +876,28 @@ static int dw_dp_link_probe(struct dw_dp *dp)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_dp_link_train_update_vs_emph(struct dw_dp *dp)
|
||||
static int dw_dp_phy_update_vs_emph(struct dw_dp *dp, unsigned int rate, unsigned int lanes,
|
||||
struct drm_dp_link_train_set *train_set)
|
||||
{
|
||||
struct dw_dp_link *link = &dp->link;
|
||||
struct drm_dp_link_train_set *request = &link->train.request;
|
||||
union phy_configure_opts phy_cfg;
|
||||
unsigned int lanes = link->lanes, *vs, *pe;
|
||||
unsigned int *vs, *pe;
|
||||
u8 buf[4];
|
||||
int i, ret;
|
||||
|
||||
vs = request->voltage_swing;
|
||||
pe = request->pre_emphasis;
|
||||
vs = train_set->voltage_swing;
|
||||
pe = train_set->pre_emphasis;
|
||||
|
||||
for (i = 0; i < lanes; i++) {
|
||||
phy_cfg.dp.voltage[i] = vs[i];
|
||||
phy_cfg.dp.pre[i] = pe[i];
|
||||
}
|
||||
|
||||
phy_cfg.dp.lanes = lanes;
|
||||
phy_cfg.dp.link_rate = link->rate / 100;
|
||||
phy_cfg.dp.link_rate = rate / 100;
|
||||
phy_cfg.dp.set_lanes = false;
|
||||
phy_cfg.dp.set_rate = false;
|
||||
phy_cfg.dp.set_voltages = true;
|
||||
|
||||
ret = phy_configure(dp->phy, &phy_cfg);
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -900,20 +912,27 @@ static int dw_dp_link_train_update_vs_emph(struct dw_dp *dp)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_dp_link_configure(struct dw_dp *dp)
|
||||
static int dw_dp_link_train_update_vs_emph(struct dw_dp *dp)
|
||||
{
|
||||
struct dw_dp_link *link = &dp->link;
|
||||
struct drm_dp_link_train_set *request = &link->train.request;
|
||||
|
||||
return dw_dp_phy_update_vs_emph(dp, dp->link.rate, dp->link.lanes, request);
|
||||
}
|
||||
|
||||
static int dw_dp_phy_configure(struct dw_dp *dp, unsigned int rate,
|
||||
unsigned int lanes, bool ssc)
|
||||
{
|
||||
union phy_configure_opts phy_cfg;
|
||||
u8 buf[2];
|
||||
int ret;
|
||||
|
||||
/* Move PHY to P3 */
|
||||
regmap_update_bits(dp->regmap, DPTX_PHYIF_CTRL, PHY_POWERDOWN,
|
||||
FIELD_PREP(PHY_POWERDOWN, 0x3));
|
||||
|
||||
phy_cfg.dp.lanes = link->lanes;
|
||||
phy_cfg.dp.link_rate = link->rate / 100;
|
||||
phy_cfg.dp.ssc = link->caps.ssc;
|
||||
phy_cfg.dp.lanes = lanes;
|
||||
phy_cfg.dp.link_rate = rate / 100;
|
||||
phy_cfg.dp.ssc = ssc;
|
||||
phy_cfg.dp.set_lanes = true;
|
||||
phy_cfg.dp.set_rate = true;
|
||||
phy_cfg.dp.set_voltages = false;
|
||||
@@ -922,14 +941,26 @@ static int dw_dp_link_configure(struct dw_dp *dp)
|
||||
return ret;
|
||||
|
||||
regmap_update_bits(dp->regmap, DPTX_PHYIF_CTRL, PHY_LANES,
|
||||
FIELD_PREP(PHY_LANES, link->lanes / 2));
|
||||
FIELD_PREP(PHY_LANES, lanes / 2));
|
||||
|
||||
/* Move PHY to P0 */
|
||||
regmap_update_bits(dp->regmap, DPTX_PHYIF_CTRL, PHY_POWERDOWN,
|
||||
FIELD_PREP(PHY_POWERDOWN, 0x0));
|
||||
|
||||
dw_dp_phy_xmit_enable(dp, link->lanes);
|
||||
dw_dp_phy_xmit_enable(dp, lanes);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_dp_link_configure(struct dw_dp *dp)
|
||||
{
|
||||
struct dw_dp_link *link = &dp->link;
|
||||
u8 buf[2];
|
||||
int ret;
|
||||
|
||||
ret = dw_dp_phy_configure(dp, link->rate, link->lanes, link->caps.ssc);
|
||||
if (ret)
|
||||
return ret;
|
||||
buf[0] = drm_dp_link_rate_to_bw_code(link->rate);
|
||||
buf[1] = link->lanes;
|
||||
|
||||
@@ -2330,6 +2361,198 @@ static int dw_dp_link_retrain(struct dw_dp *dp)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static u8 dw_dp_autotest_phy_pattern(struct dw_dp *dp)
|
||||
{
|
||||
struct drm_dp_phy_test_params *data = &dp->compliance.test_data.phytest;
|
||||
|
||||
if (drm_dp_get_phy_test_pattern(&dp->aux, data)) {
|
||||
dev_err(dp->dev, "DP Phy Test pattern AUX read failure\n");
|
||||
return DP_TEST_NAK;
|
||||
}
|
||||
|
||||
/* Set test active flag here so userspace doesn't interrupt things */
|
||||
dp->compliance.test_active = true;
|
||||
|
||||
return DP_TEST_ACK;
|
||||
}
|
||||
|
||||
static void dw_dp_handle_test_request(struct dw_dp *dp)
|
||||
{
|
||||
u8 response = DP_TEST_NAK;
|
||||
u8 request = 0;
|
||||
int status;
|
||||
|
||||
status = drm_dp_dpcd_readb(&dp->aux, DP_TEST_REQUEST, &request);
|
||||
if (status <= 0) {
|
||||
dev_err(dp->dev, "Could not read test request from sink\n");
|
||||
goto update_status;
|
||||
}
|
||||
|
||||
switch (request) {
|
||||
case DP_TEST_LINK_PHY_TEST_PATTERN:
|
||||
dev_dbg(dp->dev, "PHY_PATTERN test requested\n");
|
||||
response = dw_dp_autotest_phy_pattern(dp);
|
||||
break;
|
||||
default:
|
||||
dev_warn(dp->dev, "Invalid test request '%02x'\n", request);
|
||||
break;
|
||||
}
|
||||
|
||||
if (response & DP_TEST_ACK)
|
||||
dp->compliance.test_type = request;
|
||||
|
||||
update_status:
|
||||
status = drm_dp_dpcd_writeb(&dp->aux, DP_TEST_RESPONSE, response);
|
||||
if (status <= 0)
|
||||
dev_warn(dp->dev, "Could not write test response to sink\n");
|
||||
}
|
||||
|
||||
static void dw_dp_check_service_irq(struct dw_dp *dp)
|
||||
{
|
||||
struct dw_dp_link *link = &dp->link;
|
||||
u8 val;
|
||||
|
||||
if (link->dpcd[DP_DPCD_REV] < 0x11)
|
||||
return;
|
||||
|
||||
if (drm_dp_dpcd_readb(&dp->aux, DP_DEVICE_SERVICE_IRQ_VECTOR, &val) != 1 || !val)
|
||||
return;
|
||||
|
||||
drm_dp_dpcd_writeb(&dp->aux, DP_DEVICE_SERVICE_IRQ_VECTOR, val);
|
||||
|
||||
if (val & DP_AUTOMATED_TEST_REQUEST)
|
||||
dw_dp_handle_test_request(dp);
|
||||
|
||||
if (val & DP_SINK_SPECIFIC_IRQ)
|
||||
dev_info(dp->dev, "Sink specific irq unhandled\n");
|
||||
}
|
||||
|
||||
static void dw_dp_phy_pattern_update(struct dw_dp *dp)
|
||||
{
|
||||
struct drm_dp_phy_test_params *data = &dp->compliance.test_data.phytest;
|
||||
|
||||
switch (data->phy_pattern) {
|
||||
case DP_PHY_TEST_PATTERN_NONE:
|
||||
dev_dbg(dp->dev, "Disable Phy Test Pattern\n");
|
||||
regmap_update_bits(dp->regmap, DPTX_CCTL, SCRAMBLE_DIS,
|
||||
FIELD_PREP(SCRAMBLE_DIS, 1));
|
||||
dw_dp_phy_set_pattern(dp, DPTX_PHY_PATTERN_NONE);
|
||||
break;
|
||||
case DP_PHY_TEST_PATTERN_D10_2:
|
||||
dev_dbg(dp->dev, "Set D10.2 Phy Test Pattern\n");
|
||||
regmap_update_bits(dp->regmap, DPTX_CCTL, SCRAMBLE_DIS,
|
||||
FIELD_PREP(SCRAMBLE_DIS, 1));
|
||||
dw_dp_phy_set_pattern(dp, DPTX_PHY_PATTERN_TPS_1);
|
||||
break;
|
||||
case DP_PHY_TEST_PATTERN_ERROR_COUNT:
|
||||
regmap_update_bits(dp->regmap, DPTX_CCTL, SCRAMBLE_DIS,
|
||||
FIELD_PREP(SCRAMBLE_DIS, 0));
|
||||
dev_dbg(dp->dev, "Set Error Count Phy Test Pattern\n");
|
||||
dw_dp_phy_set_pattern(dp, DPTX_PHY_PATTERN_SERM);
|
||||
break;
|
||||
case DP_PHY_TEST_PATTERN_PRBS7:
|
||||
dev_dbg(dp->dev, "Set PRBS7 Phy Test Pattern\n");
|
||||
regmap_update_bits(dp->regmap, DPTX_CCTL, SCRAMBLE_DIS,
|
||||
FIELD_PREP(SCRAMBLE_DIS, 1));
|
||||
dw_dp_phy_set_pattern(dp, DPTX_PHY_PATTERN_PBRS7);
|
||||
break;
|
||||
case DP_PHY_TEST_PATTERN_80BIT_CUSTOM:
|
||||
dev_dbg(dp->dev, "Set 80Bit Custom Phy Test Pattern\n");
|
||||
regmap_update_bits(dp->regmap, DPTX_CCTL, SCRAMBLE_DIS,
|
||||
FIELD_PREP(SCRAMBLE_DIS, 1));
|
||||
regmap_write(dp->regmap, DPTX_CUSTOMPAT0, 0x3e0f83e0);
|
||||
regmap_write(dp->regmap, DPTX_CUSTOMPAT1, 0x3e0f83e0);
|
||||
regmap_write(dp->regmap, DPTX_CUSTOMPAT2, 0x000f83e0);
|
||||
dw_dp_phy_set_pattern(dp, DPTX_PHY_PATTERN_CUSTOM_80BIT);
|
||||
break;
|
||||
case DP_PHY_TEST_PATTERN_CP2520:
|
||||
dev_dbg(dp->dev, "Set HBR2 compliance Phy Test Pattern\n");
|
||||
regmap_update_bits(dp->regmap, DPTX_CCTL, SCRAMBLE_DIS,
|
||||
FIELD_PREP(SCRAMBLE_DIS, 0));
|
||||
dw_dp_phy_set_pattern(dp, DPTX_PHY_PATTERN_CP2520_1);
|
||||
break;
|
||||
case DP_PHY_TEST_PATTERN_SEL_MASK:
|
||||
dev_dbg(dp->dev, "Set TPS4 Phy Test Pattern\n");
|
||||
regmap_update_bits(dp->regmap, DPTX_CCTL, SCRAMBLE_DIS,
|
||||
FIELD_PREP(SCRAMBLE_DIS, 0));
|
||||
dw_dp_phy_set_pattern(dp, DPTX_PHY_PATTERN_TPS_4);
|
||||
break;
|
||||
default:
|
||||
WARN(1, "Invalid Phy Test Pattern\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void dw_dp_process_phy_request(struct dw_dp *dp)
|
||||
{
|
||||
struct drm_dp_phy_test_params *data = &dp->compliance.test_data.phytest;
|
||||
u8 link_status[DP_LINK_STATUS_SIZE], spread;
|
||||
int ret;
|
||||
|
||||
ret = drm_dp_dpcd_read(&dp->aux, DP_LANE0_1_STATUS, link_status, DP_LINK_STATUS_SIZE);
|
||||
if (ret < 0) {
|
||||
dev_err(dp->dev, "failed to get link status\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = drm_dp_dpcd_readb(&dp->aux, DP_MAX_DOWNSPREAD, &spread);
|
||||
if (ret < 0) {
|
||||
dev_err(dp->dev, "failed to get spread\n");
|
||||
return;
|
||||
}
|
||||
|
||||
dw_dp_phy_configure(dp, data->link_rate, data->num_lanes,
|
||||
!!(spread & DP_MAX_DOWNSPREAD_0_5));
|
||||
dw_dp_link_get_adjustments(&dp->link, link_status);
|
||||
dw_dp_phy_update_vs_emph(dp, data->link_rate, data->num_lanes, &dp->link.train.adjust);
|
||||
dw_dp_phy_pattern_update(dp);
|
||||
drm_dp_set_phy_test_pattern(&dp->aux, data, link_status[DP_DPCD_REV]);
|
||||
|
||||
dev_dbg(dp->dev, "phy test rate:%d, lane count:%d, ssc:%d, vs:%d, pe: %d\n",
|
||||
data->link_rate, data->num_lanes, spread, dp->link.train.adjust.voltage_swing[0],
|
||||
dp->link.train.adjust.pre_emphasis[0]);
|
||||
}
|
||||
|
||||
static void dw_dp_phy_test(struct dw_dp *dp)
|
||||
{
|
||||
struct drm_device *dev = dp->bridge.dev;
|
||||
struct drm_modeset_acquire_ctx ctx;
|
||||
int ret;
|
||||
|
||||
drm_modeset_acquire_init(&ctx, 0);
|
||||
|
||||
for (;;) {
|
||||
ret = drm_modeset_lock(&dev->mode_config.connection_mutex, &ctx);
|
||||
if (ret != -EDEADLK)
|
||||
break;
|
||||
|
||||
drm_modeset_backoff(&ctx);
|
||||
}
|
||||
|
||||
dw_dp_process_phy_request(dp);
|
||||
drm_modeset_drop_locks(&ctx);
|
||||
drm_modeset_acquire_fini(&ctx);
|
||||
}
|
||||
|
||||
static bool dw_dp_hpd_short_pulse(struct dw_dp *dp)
|
||||
{
|
||||
memset(&dp->compliance, 0, sizeof(dp->compliance));
|
||||
|
||||
dw_dp_check_service_irq(dp);
|
||||
|
||||
if (dw_dp_needs_link_retrain(dp))
|
||||
return false;
|
||||
|
||||
switch (dp->compliance.test_type) {
|
||||
case DP_TEST_LINK_PHY_TEST_PATTERN:
|
||||
return false;
|
||||
default:
|
||||
dev_warn(dp->dev, "test_type%lu is not support\n", dp->compliance.test_type);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void dw_dp_hpd_work(struct work_struct *work)
|
||||
{
|
||||
struct dw_dp *dp = container_of(work, struct dw_dp, hpd_work);
|
||||
@@ -2343,6 +2566,16 @@ static void dw_dp_hpd_work(struct work_struct *work)
|
||||
dev_dbg(dp->dev, "got hpd irq - %s\n", long_hpd ? "long" : "short");
|
||||
|
||||
if (!long_hpd) {
|
||||
if (dw_dp_hpd_short_pulse(dp))
|
||||
return;
|
||||
|
||||
if (dp->compliance.test_active &&
|
||||
dp->compliance.test_type == DP_TEST_LINK_PHY_TEST_PATTERN) {
|
||||
dw_dp_phy_test(dp);
|
||||
/* just do the PHY test and nothing else */
|
||||
return;
|
||||
}
|
||||
|
||||
ret = dw_dp_link_retrain(dp);
|
||||
if (ret)
|
||||
dev_warn(dp->dev, "Retrain link failed\n");
|
||||
|
||||
Reference in New Issue
Block a user