2026-01-29 22:25:33 +08:00

2638 lines
79 KiB
C

// SPDX-License-Identifier: GPL-2.0
/* Phytium display drm driver
*
* Copyright (C) 2021-2023, Phytium Technology Co., Ltd.
*/
#include <drm/drm_atomic_helper.h>
#include <drm/drm_atomic.h>
#include <drm/display/drm_dp.h>
#include <drm/display/drm_dp_helper.h>
#include <drm/drm_encoder.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_modes.h>
#include <sound/hdmi-codec.h>
#include <drm/drm_probe_helper.h>
#include "phytium_display_drv.h"
#include "phytium_dp.h"
#include "phytium_debugfs.h"
#include "px210_dp.h"
#include "pe220x_dp.h"
#include "phytium_panel.h"
#include "phytium_reg.h"
static void phytium_dp_aux_init(struct phytium_dp_device *phytium_dp);
static void handle_plugged_change(struct phytium_dp_device *phytium_dp, bool plugged);
static bool phytium_edp_init_connector(struct phytium_dp_device *phytium_dp);
static void phytium_edp_fini_connector(struct phytium_dp_device *phytium_dp);
static void phytium_edp_panel_poweroff(struct phytium_dp_device *phytium_dp);
static void phytium_dp_audio_codec_fini(struct phytium_dp_device *phytium_dp);
static int phytium_rate[] = {162000, 270000, 540000, 810000};
static int codec_id = PHYTIUM_DP_AUDIO_ID;
void phytium_phy_writel(struct phytium_dp_device *phytium_dp, uint32_t address, uint32_t data)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
uint32_t group_offset = priv->phy_access_base[port];
#if DEBUG_LOG
pr_info("phy address write: 0x%x data:0x%x\n", address, data);
#endif
phytium_writel_reg(priv, address, group_offset, PHYTIUM_PHY_ACCESS_ADDRESS);
phytium_writel_reg(priv, data, group_offset, PHYTIUM_PHY_WRITE_DATA);
phytium_writel_reg(priv, ACCESS_WRITE, group_offset, PHYTIUM_PHY_ACCESS_CTRL);
udelay(10);
}
uint32_t phytium_phy_readl(struct phytium_dp_device *phytium_dp, uint32_t address)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
uint32_t group_offset = priv->phy_access_base[port];
uint32_t data;
phytium_writel_reg(priv, address, group_offset, PHYTIUM_PHY_ACCESS_ADDRESS);
phytium_writel_reg(priv, ACCESS_READ, group_offset, PHYTIUM_PHY_ACCESS_CTRL);
udelay(10);
data = phytium_readl_reg(priv, group_offset, PHYTIUM_PHY_READ_DATA);
#if DEBUG_LOG
pr_info("phy address read: 0x%x data:0x%x\n", address, data);
#endif
return data;
}
static int
phytium_dp_hw_aux_transfer_write(struct phytium_dp_device *phytium_dp, struct drm_dp_aux_msg *msg)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
uint32_t group_offset = priv->dp_reg_base[port];
unsigned int i = 0, j = 0;
unsigned int cmd = 0;
unsigned int aux_status = 0, interrupt_status = 0;
unsigned char *data = msg->buffer;
int count_timeout = 0;
long ret = 0;
for (i = 0; i < 3; i++) {
/* clear PX210_DP_INTERRUPT_RAW_STATUS */
phytium_readl_reg(priv, group_offset, PHYTIUM_DP_INTERRUPT_STATUS);
phytium_writel_reg(priv, msg->address, group_offset, PHYTIUM_DP_AUX_ADDRESS);
for (j = 0; j < msg->size; j++)
phytium_writeb_reg(priv, data[j], group_offset, PHYTIUM_DP_AUX_WRITE_FIFO);
cmd = ((msg->request & COMMAND_MASK) << COMMAND_SHIFT);
if (msg->size == 0)
cmd |= ADDRESS_ONLY;
else
cmd |= (msg->size-1) & BYTE_COUNT_MASK;
phytium_writel_reg(priv, cmd, group_offset, PHYTIUM_DP_AUX_COMMAND);
count_timeout = 0;
do {
mdelay(5);
interrupt_status = phytium_readl_reg(priv, group_offset,
PHYTIUM_DP_INTERRUPT_RAW_STATUS);
aux_status = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_AUX_STATUS);
if ((aux_status & REPLY_RECEIVED) || (aux_status & REPLY_ERROR)
|| (interrupt_status & REPLY_TIMEOUT)) {
DRM_DEBUG_KMS("aux wait exit\n");
break;
}
count_timeout++;
} while (count_timeout < 6);
phytium_readl_reg(priv, group_offset, PHYTIUM_DP_INTERRUPT_STATUS);
if (interrupt_status & REPLY_TIMEOUT) {
DRM_DEBUG_KMS("aux write reply timeout\n");
continue;
} else if (aux_status & REPLY_ERROR) {
DRM_DEBUG_KMS("aux write reply error\n");
continue;
} else if (aux_status & REPLY_RECEIVED) {
DRM_DEBUG_KMS("aux write reply received succussful\n");
break;
}
}
if (interrupt_status & REPLY_TIMEOUT) {
DRM_NOTE("aux(%d) write reply timeout\n", phytium_dp->port);
ret = -EIO;
goto out;
} else if (aux_status & REPLY_ERROR) {
DRM_ERROR("aux(%d) write reply error\n", phytium_dp->port);
ret = -EIO;
goto out;
} else if ((aux_status & REPLY_RECEIVED) != REPLY_RECEIVED) {
DRM_ERROR("aux(%d) write reply no response\n", phytium_dp->port);
ret = -EIO;
goto out;
}
msg->reply = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_AUX_REPLY_CODE);
ret = msg->size;
out:
return ret;
}
static int
phytium_dp_hw_aux_transfer_read(struct phytium_dp_device *phytium_dp, struct drm_dp_aux_msg *msg)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
uint32_t group_offset = priv->dp_reg_base[port];
unsigned int i = 0;
unsigned int cmd = 0;
unsigned int aux_status = 0, interrupt_status = 0;
unsigned char *data = msg->buffer;
int count_timeout = 0;
long ret = 0;
for (i = 0; i < 3; i++) {
phytium_readl_reg(priv, group_offset, PHYTIUM_DP_INTERRUPT_STATUS);
phytium_writel_reg(priv, msg->address, group_offset, PHYTIUM_DP_AUX_ADDRESS);
cmd = ((msg->request & COMMAND_MASK) << COMMAND_SHIFT);
if (msg->size == 0)
cmd |= ADDRESS_ONLY;
else
cmd |= ((msg->size-1) & BYTE_COUNT_MASK);
phytium_writel_reg(priv, cmd, group_offset, PHYTIUM_DP_AUX_COMMAND);
count_timeout = 0;
do {
mdelay(5);
interrupt_status = phytium_readl_reg(priv, group_offset,
PHYTIUM_DP_INTERRUPT_RAW_STATUS);
aux_status = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_AUX_STATUS);
if ((aux_status & REPLY_RECEIVED) || (aux_status & REPLY_ERROR)
|| (interrupt_status & REPLY_TIMEOUT)) {
DRM_DEBUG_KMS("aux wait exit\n");
break;
}
count_timeout++;
} while (count_timeout < 6);
phytium_readl_reg(priv, group_offset, PHYTIUM_DP_INTERRUPT_STATUS);
if (interrupt_status & REPLY_TIMEOUT) {
DRM_DEBUG_KMS("aux read reply timeout\n");
continue;
} else if (aux_status & REPLY_ERROR) {
DRM_DEBUG_KMS("aux read reply error\n");
continue;
} else if (aux_status & REPLY_RECEIVED) {
DRM_DEBUG_KMS("aux read reply received succussful\n");
break;
}
}
if (interrupt_status & REPLY_TIMEOUT) {
DRM_NOTE("aux(%d) read reply timeout\n", phytium_dp->port);
ret = -EIO;
goto out;
} else if (aux_status & REPLY_ERROR) {
DRM_ERROR("aux(%d) read reply error\n", phytium_dp->port);
ret = -EIO;
goto out;
} else if ((aux_status & REPLY_RECEIVED) != REPLY_RECEIVED) {
DRM_ERROR("aux(%d) read reply no response\n", phytium_dp->port);
ret = -EIO;
goto out;
}
msg->reply = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_AUX_REPLY_CODE);
ret = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_AUX_REPLY_DATA_COUNT);
if (ret > msg->size) {
ret = msg->size;
} else if (ret != msg->size) {
DRM_DEBUG_KMS("aux read count error(ret:0x%lx != 0x%lx)\n", ret, msg->size);
ret = -EBUSY;
goto out;
}
for (i = 0; i < ret; i++)
data[i] = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_AUX_REPLY_DATA);
out:
return ret;
}
static void phytium_get_native_mode(struct phytium_dp_device *phytium_dp)
{
struct drm_display_mode *t, *mode;
struct drm_connector *connector = &phytium_dp->connector;
struct drm_display_mode *native_mode = &phytium_dp->native_mode;
list_for_each_entry_safe(mode, t, &connector->probed_modes, head) {
if (mode->type & DRM_MODE_TYPE_PREFERRED) {
if (mode->hdisplay != native_mode->hdisplay ||
mode->vdisplay != native_mode->vdisplay) {
memcpy(native_mode, mode, sizeof(*mode));
drm_mode_set_crtcinfo(native_mode, 0);
}
break;
}
}
if (&mode->head == &connector->probed_modes)
native_mode->clock = 0;
}
static int phytium_connector_add_common_modes(struct phytium_dp_device *phytium_dp)
{
int i = 0, ret = 0;
struct drm_device *dev = phytium_dp->dev;
struct drm_display_mode *mode = NULL, *current_mode = NULL;
struct drm_display_mode *native_mode = &phytium_dp->native_mode;
bool mode_existed = false;
struct mode_size {
char name[DRM_DISPLAY_MODE_LEN];
int w;
int h;
} common_mode[] = {
{ "640x480", 640, 480},
{ "800x600", 800, 600},
{ "1024x768", 1024, 768},
{ "1280x720", 1280, 720},
{ "1280x800", 1280, 800},
{"1280x1024", 1280, 1024},
{ "1440x900", 1440, 900},
{"1680x1050", 1680, 1050},
{"1600x1200", 1600, 1200},
{"1920x1080", 1920, 1080},
{"1920x1200", 1920, 1200}
};
if (native_mode->clock == 0)
return ret;
for (i = 0; i < ARRAY_SIZE(common_mode); i++) {
mode_existed = false;
if (common_mode[i].w > native_mode->hdisplay ||
common_mode[i].h > native_mode->vdisplay ||
(common_mode[i].w == native_mode->hdisplay &&
common_mode[i].h == native_mode->vdisplay))
continue;
list_for_each_entry(current_mode, &phytium_dp->connector.probed_modes, head) {
if (common_mode[i].w == current_mode->hdisplay &&
common_mode[i].h == current_mode->vdisplay) {
mode_existed = true;
break;
}
}
if (mode_existed)
continue;
mode = drm_mode_duplicate(dev, native_mode);
if (mode == NULL)
continue;
mode->hdisplay = common_mode[i].w;
mode->vdisplay = common_mode[i].h;
mode->type &= ~DRM_MODE_TYPE_PREFERRED;
strscpy(mode->name, common_mode[i].name, DRM_DISPLAY_MODE_LEN);
drm_mode_probed_add(&phytium_dp->connector, mode);
ret++;
}
return ret;
}
static int phytium_connector_get_modes(struct drm_connector *connector)
{
struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector);
struct edid *edid;
int ret = 0;
if (phytium_dp->is_edp)
edid = phytium_dp->edp_edid;
else
edid = drm_get_edid(connector, &phytium_dp->aux.ddc);
if (edid && drm_edid_is_valid(edid)) {
drm_connector_update_edid_property(connector, edid);
ret = drm_add_edid_modes(connector, edid);
phytium_dp->has_audio = drm_detect_monitor_audio(edid);
phytium_get_native_mode(phytium_dp);
if (dc_fake_mode_enable)
ret += phytium_connector_add_common_modes(phytium_dp);
} else {
drm_connector_update_edid_property(connector, NULL);
phytium_dp->has_audio = false;
}
if (!phytium_dp->is_edp)
kfree(edid);
return ret;
}
static struct drm_encoder *phytium_dp_best_encoder(struct drm_connector *connector)
{
struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector);
return &phytium_dp->encoder;
}
static const
struct drm_connector_helper_funcs phytium_connector_helper_funcs = {
.get_modes = phytium_connector_get_modes,
.best_encoder = phytium_dp_best_encoder,
};
static void phytium_dp_set_sink_rates(struct phytium_dp_device *phytium_dp)
{
static const int dp_rates[] = {162000, 270000, 540000, 810000};
int i, max_rate;
max_rate = drm_dp_bw_code_to_link_rate(phytium_dp->dpcd[DP_MAX_LINK_RATE]);
for (i = 0; i < ARRAY_SIZE(dp_rates); i++) {
if (dp_rates[i] > max_rate)
break;
phytium_dp->sink_rates[i] = dp_rates[i];
}
phytium_dp->num_sink_rates = i;
}
static int get_common_rates(const int *source_rates, int source_len, const int *sink_rates,
int sink_len, int *common_rates)
{
int i = 0, j = 0, k = 0;
while (i < source_len && j < sink_len) {
if (source_rates[i] == sink_rates[j]) {
if (WARN_ON(k >= DP_MAX_SUPPORTED_RATES))
return k;
common_rates[k] = source_rates[i];
++k;
++i;
++j;
} else if (source_rates[i] < sink_rates[j]) {
++i;
} else {
++j;
}
}
return k;
}
static void phytium_dp_set_common_rates(struct phytium_dp_device *phytium_dp)
{
WARN_ON(!phytium_dp->num_source_rates || !phytium_dp->num_sink_rates);
phytium_dp->num_common_rates = get_common_rates(phytium_dp->source_rates,
phytium_dp->num_source_rates,
phytium_dp->sink_rates,
phytium_dp->num_sink_rates,
phytium_dp->common_rates);
if (WARN_ON(phytium_dp->num_common_rates == 0)) {
phytium_dp->common_rates[0] = 162000;
phytium_dp->num_common_rates = 1;
}
}
static bool phytium_dp_get_dpcd(struct phytium_dp_device *phytium_dp)
{
int ret;
unsigned char sink_count = 0;
/* get dpcd capability,but don't check data error; so check revision */
ret = drm_dp_dpcd_read(&phytium_dp->aux, 0x00, phytium_dp->dpcd,
sizeof(phytium_dp->dpcd));
if (ret < 0) {
DRM_ERROR("port %d get DPCD capability fail\n", phytium_dp->port);
return false;
}
if (phytium_dp->dpcd[DP_DPCD_REV] == 0) {
DRM_ERROR("DPCD data error: 0x%x\n", phytium_dp->dpcd[DP_DPCD_REV]);
return false;
}
/* parse sink support link */
phytium_dp_set_sink_rates(phytium_dp);
phytium_dp_set_common_rates(phytium_dp);
phytium_dp->sink_max_lane_count = drm_dp_max_lane_count(phytium_dp->dpcd);
phytium_dp->common_max_lane_count = min(phytium_dp->source_max_lane_count,
phytium_dp->sink_max_lane_count);
/* get dpcd sink count */
if (drm_dp_dpcd_readb(&phytium_dp->aux, DP_SINK_COUNT, &sink_count) <= 0) {
DRM_ERROR("get DPCD sink_count fail\n");
return false;
}
phytium_dp->sink_count = DP_GET_SINK_COUNT(sink_count);
if (!phytium_dp->sink_count) {
DRM_ERROR("DPCD sink_count should not be zero\n");
return false;
}
if (!drm_dp_is_branch(phytium_dp->dpcd))
return true;
if (phytium_dp->dpcd[DP_DPCD_REV] == 0x10)
return true;
/* get downstream port for branch device */
ret = drm_dp_dpcd_read(&phytium_dp->aux, DP_DOWNSTREAM_PORT_0,
phytium_dp->downstream_ports, DP_MAX_DOWNSTREAM_PORTS);
if (ret < 0) {
DRM_ERROR("get DPCD DFP fail\n");
return false;
}
return true;
}
static enum drm_connector_status
phytium_dp_detect_dpcd(struct phytium_dp_device *phytium_dp)
{
if (!phytium_dp_get_dpcd(phytium_dp))
return connector_status_disconnected;
if (!drm_dp_is_branch(phytium_dp->dpcd))
return connector_status_connected;
if (phytium_dp->downstream_ports[0] & DP_DS_PORT_HPD) {
return phytium_dp->sink_count ? connector_status_connected
: connector_status_disconnected;
}
return connector_status_connected;
}
static void phytium_get_adjust_train(struct phytium_dp_device *phytium_dp,
const uint8_t link_status[DP_LINK_STATUS_SIZE], uint8_t lane_count)
{
unsigned char v = 0;
unsigned char p = 0;
int lane;
unsigned char voltage_max;
unsigned char preemph_max;
/* find max value */
for (lane = 0; lane < lane_count; lane++) {
uint8_t this_v = drm_dp_get_adjust_request_voltage(link_status, lane);
uint8_t this_p = drm_dp_get_adjust_request_pre_emphasis(link_status, lane);
if (this_v > v)
v = this_v;
if (this_p > p)
p = this_p;
}
voltage_max = DP_TRAIN_VOLTAGE_SWING_LEVEL_3;
if (v >= voltage_max)
v = voltage_max | DP_TRAIN_MAX_SWING_REACHED;
preemph_max = DP_TRAIN_PRE_EMPH_LEVEL_3;
if (p >= preemph_max)
p = preemph_max | DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
for (lane = 0; lane < 4; lane++)
phytium_dp->train_set[lane] = v | p;
}
bool phytium_dp_coding_8b10b_need_enable(unsigned char test_pattern)
{
switch (test_pattern) {
case PHYTIUM_PHY_TP_D10_2:
case PHYTIUM_PHY_TP_SYMBOL_ERROR:
case PHYTIUM_PHY_TP_CP2520_1:
case PHYTIUM_PHY_TP_CP2520_2:
case PHYTIUM_PHY_TP_CP2520_3:
return true;
case PHYTIUM_PHY_TP_PRBS7:
case PHYTIUM_PHY_TP_80BIT_CUSTOM:
return false;
default:
return false;
}
}
bool phytium_dp_scrambled_need_enable(unsigned char test_pattern)
{
switch (test_pattern) {
case PHYTIUM_PHY_TP_SYMBOL_ERROR:
case PHYTIUM_PHY_TP_CP2520_1:
case PHYTIUM_PHY_TP_CP2520_2:
case PHYTIUM_PHY_TP_CP2520_3:
return true;
case PHYTIUM_PHY_TP_D10_2:
case PHYTIUM_PHY_TP_PRBS7:
case PHYTIUM_PHY_TP_80BIT_CUSTOM:
return false;
default:
return false;
}
}
static void phytium_dp_hw_set_lane_setting(struct phytium_dp_device *phytium_dp,
uint32_t link_rate,
uint8_t train_set)
{
phytium_dp->funcs->dp_hw_set_phy_lane_setting(phytium_dp, link_rate, train_set);
}
static void phytium_dp_hw_set_link(struct phytium_dp_device *phytium_dp,
uint8_t lane_count,
uint32_t link_rate)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port, ret = 0, retry = 3;
uint32_t group_offset = priv->dp_reg_base[port];
phytium_writel_reg(priv, lane_count,
group_offset, PHYTIUM_DP_LANE_COUNT_SET);
phytium_writel_reg(priv,
drm_dp_link_rate_to_bw_code(link_rate),
group_offset, PHYTIUM_DP_LINK_BW_SET);
if (drm_dp_enhanced_frame_cap(phytium_dp->dpcd))
phytium_writel_reg(priv, ENHANCED_FRAME_ENABLE,
group_offset, PHYTIUM_DP_ENHANCED_FRAME_EN);
else
phytium_writel_reg(priv, ENHANCED_FRAME_DISABLE,
group_offset, PHYTIUM_DP_ENHANCED_FRAME_EN);
try_again:
ret = phytium_dp->funcs->dp_hw_set_phy_lane_and_rate(phytium_dp, lane_count, link_rate);
if ((ret < 0) && retry) {
retry--;
goto try_again;
}
}
static void phytium_dp_hw_set_test_pattern(struct phytium_dp_device *phytium_dp,
uint8_t lane_count,
uint8_t test_pattern,
uint8_t *custom_pattern,
uint32_t custom_pattern_size)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port, val = 0, tmp = 0, i;
uint32_t group_offset = priv->dp_reg_base[port];
if ((test_pattern == PHYTIUM_PHY_TP_80BIT_CUSTOM)
&& custom_pattern && (custom_pattern_size > 0)) {
val = *(int *)custom_pattern;
phytium_writel_reg(priv, val, group_offset, PHYTIUM_DP_CUSTOM_80BIT_PATTERN_0);
val = *(int *)(custom_pattern + 4);
phytium_writel_reg(priv, val, group_offset, PHYTIUM_DP_CUSTOM_80BIT_PATTERN_1);
val = *(short int *)(custom_pattern + 8);
phytium_writel_reg(priv, val, group_offset, PHYTIUM_DP_CUSTOM_80BIT_PATTERN_2);
}
if (test_pattern == PHYTIUM_PHY_TP_D10_2 || test_pattern == PHYTIUM_PHY_TP_PRBS7
|| test_pattern == PHYTIUM_PHY_TP_80BIT_CUSTOM)
phytium_writel_reg(priv, SCRAMBLING_DISABLE, group_offset,
PHYTIUM_DP_SCRAMBLING_DISABLE);
else
phytium_writel_reg(priv, SCRAMBLING_ENABLE, group_offset,
PHYTIUM_DP_SCRAMBLING_DISABLE);
tmp = test_pattern - PHYTIUM_PHY_TP_NONE + TEST_PATTERN_NONE;
val = 0;
for (i = 0; i < lane_count; i++)
val |= (tmp << (TEST_PATTERN_LANE_SHIFT * i));
phytium_writel_reg(priv, val, group_offset, PHYTIUM_DP_LINK_QUAL_PATTERN_SET);
}
static void phytium_dp_hw_set_train_pattern(struct phytium_dp_device *phytium_dp,
uint8_t train_pattern)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port, tmp = 0;
uint32_t group_offset = priv->dp_reg_base[port];
/* Scrambling is disabled for TPS1/TPS2/3 and enabled for TPS4 */
if (train_pattern == DP_TRAINING_PATTERN_4
|| train_pattern == DP_TRAINING_PATTERN_DISABLE) {
phytium_writel_reg(priv, SCRAMBLING_ENABLE, group_offset,
PHYTIUM_DP_SCRAMBLING_DISABLE);
phytium_writel_reg(priv, SCRAMBLER_RESET, group_offset,
PHYTIUM_DP_FORCE_SCRAMBLER_RESET);
} else {
phytium_writel_reg(priv, SCRAMBLING_DISABLE, group_offset,
PHYTIUM_DP_SCRAMBLING_DISABLE);
}
switch (train_pattern) {
case DP_TRAINING_PATTERN_DISABLE:
tmp = TRAINING_OFF;
break;
case DP_TRAINING_PATTERN_1:
tmp = TRAINING_PATTERN_1;
break;
case DP_TRAINING_PATTERN_2:
tmp = TRAINING_PATTERN_2;
break;
case DP_TRAINING_PATTERN_3:
tmp = TRAINING_PATTERN_3;
break;
case DP_TRAINING_PATTERN_4:
tmp = TRAINING_PATTERN_4;
break;
default:
tmp = TRAINING_OFF;
break;
}
phytium_writel_reg(priv, tmp, group_offset, PHYTIUM_DP_TRAINING_PATTERN_SET);
}
void phytium_dp_hw_enable_audio(struct phytium_dp_device *phytium_dp)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
int config = 0, config1, data_window = 0;
const struct dp_audio_n_m *n_m = NULL;
uint32_t group_offset = priv->dp_reg_base[port];
config = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SEC_AUDIO_ENABLE);
phytium_writel_reg(priv, CHANNEL_MUTE_ENABLE, group_offset, PHYTIUM_DP_SEC_AUDIO_ENABLE);
data_window = 90*(phytium_dp->link_rate)/100
*(phytium_dp->mode.htotal - phytium_dp->mode.hdisplay)
/phytium_dp->mode.clock/4;
phytium_writel_reg(priv, data_window, group_offset, PHYTIUM_DP_SEC_DATA_WINDOW);
n_m = phytium_dp_audio_get_n_m(phytium_dp->link_rate, phytium_dp->audio_info.sample_rate);
if (n_m == NULL) {
DRM_NOTE("can not get n_m for link_rate(%d) and sample_rate(%d)\n",
phytium_dp->link_rate, phytium_dp->audio_info.sample_rate);
phytium_writel_reg(priv, 0, group_offset, PHYTIUM_DP_SEC_MAUD);
phytium_writel_reg(priv, 0, group_offset, PHYTIUM_DP_SEC_NAUD);
} else {
phytium_writel_reg(priv, n_m->m, group_offset, PHYTIUM_DP_SEC_MAUD);
phytium_writel_reg(priv, n_m->n, group_offset, PHYTIUM_DP_SEC_NAUD);
}
config1 = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SECONDARY_STREAM_ENABLE);
phytium_writel_reg(priv, SECONDARY_STREAM_DISABLE,
group_offset, PHYTIUM_DP_SECONDARY_STREAM_ENABLE);
phytium_writel_reg(priv, config1, group_offset, PHYTIUM_DP_SECONDARY_STREAM_ENABLE);
phytium_writel_reg(priv, config, group_offset, PHYTIUM_DP_SEC_AUDIO_ENABLE);
}
static void phytium_dp_hw_audio_shutdown(struct phytium_dp_device *phytium_dp)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
uint32_t group_offset = priv->dp_reg_base[port];
phytium_writel_reg(priv, SECONDARY_STREAM_DISABLE,
group_offset, PHYTIUM_DP_SECONDARY_STREAM_ENABLE);
}
static void phytium_dp_hw_audio_digital_mute(struct phytium_dp_device *phytium_dp, bool enable)
{
struct phytium_display_private *priv = phytium_dp->dev->dev_private;
int port = phytium_dp->port;
uint32_t group_offset = priv->dp_reg_base[port];
if (enable)
phytium_writel_reg(priv, CHANNEL_MUTE_ENABLE,
group_offset, PHYTIUM_DP_SEC_AUDIO_ENABLE);
else
phytium_writel_reg(priv, SEC_AUDIO_ENABLE,
group_offset, PHYTIUM_DP_SEC_AUDIO_ENABLE);
}
static int
phytium_dp_hw_audio_hw_params(struct phytium_dp_device *phytium_dp, struct audio_info audio_info)
{
struct phytium_display_private *priv = phytium_dp->dev->dev_private;
int port = phytium_dp->port;
int ret = 0, data_window = 0;
const struct dp_audio_n_m *n_m = NULL;
uint32_t fs, ws, fs_accurac;
uint32_t group_offset = priv->dp_reg_base[port];
DRM_DEBUG_KMS("%s:set port%d sample_rate(%d) channels(%d) sample_width(%d)\n",
__func__, phytium_dp->port, audio_info.sample_rate,
audio_info.channels, audio_info.sample_width);
phytium_writel_reg(priv, INPUT_SELECT_I2S, group_offset, PHYTIUM_DP_SEC_INPUT_SELECT);
phytium_writel_reg(priv, APB_CLOCK/audio_info.sample_rate,
group_offset, PHYTIUM_DP_SEC_DIRECT_CLKDIV);
phytium_writel_reg(priv, audio_info.channels & CHANNEL_MASK,
group_offset, PHYTIUM_DP_SEC_CHANNEL_COUNT);
phytium_writel_reg(priv, CHANNEL_MAP_DEFAULT, group_offset, PHYTIUM_DP_SEC_CHANNEL_MAP);
data_window = 90*(phytium_dp->link_rate)/100
*(phytium_dp->mode.htotal - phytium_dp->mode.hdisplay)
/phytium_dp->mode.clock/4;
phytium_writel_reg(priv, data_window, group_offset, PHYTIUM_DP_SEC_DATA_WINDOW);
phytium_writel_reg(priv, 0xb5, group_offset, PHYTIUM_DP_SEC_CS_CATEGORY_CODE);
phytium_writel_reg(priv, CLOCK_MODE_SYNC, group_offset, PHYTIUM_DP_SEC_CLOCK_MODE);
phytium_writel_reg(priv, CS_SOURCE_FORMAT_DEFAULT,
group_offset, PHYTIUM_DP_SEC_CS_SOURCE_FORMAT);
switch (audio_info.sample_rate) {
case 32000:
fs = ORIG_FREQ_32000;
fs_accurac = SAMPLING_FREQ_32000;
break;
case 44100:
fs = ORIG_FREQ_44100;
fs_accurac = SAMPLING_FREQ_44100;
break;
case 48000:
fs = ORIG_FREQ_48000;
fs_accurac = SAMPLING_FREQ_48000;
break;
case 96000:
fs = ORIG_FREQ_96000;
fs_accurac = SAMPLING_FREQ_96000;
break;
case 176400:
fs = ORIG_FREQ_176400;
fs_accurac = SAMPLING_FREQ_176400;
break;
case 192000:
fs = ORIG_FREQ_192000;
fs_accurac = SAMPLING_FREQ_192000;
break;
default:
DRM_ERROR("dp not support sample_rate %d\n", audio_info.sample_rate);
goto out;
}
switch (audio_info.sample_width) {
case 16:
ws = WORD_LENGTH_16;
break;
case 18:
ws = WORD_LENGTH_18;
break;
case 20:
ws = WORD_LENGTH_20;
break;
case 24:
ws = WORD_LENGTH_24;
break;
default:
DRM_ERROR("dp not support sample_width %d\n", audio_info.sample_width);
goto out;
}
phytium_writel_reg(priv, ((fs&ORIG_FREQ_MASK)<<ORIG_FREQ_SHIFT)
| ((ws&WORD_LENGTH_MASK) << WORD_LENGTH_SHIFT),
group_offset, PHYTIUM_DP_SEC_CS_LENGTH_ORIG_FREQ);
phytium_writel_reg(priv, (fs_accurac&SAMPLING_FREQ_MASK) << SAMPLING_FREQ_SHIFT,
group_offset, PHYTIUM_DP_SEC_CS_FREQ_CLOCK_ACCURACY);
n_m = phytium_dp_audio_get_n_m(phytium_dp->link_rate, audio_info.sample_rate);
if (n_m == NULL) {
DRM_NOTE("can not get n_m for link_rate(%d) and sample_rate(%d)\n",
phytium_dp->link_rate, audio_info.sample_rate);
phytium_writel_reg(priv, 0, group_offset, PHYTIUM_DP_SEC_MAUD);
phytium_writel_reg(priv, 0, group_offset, PHYTIUM_DP_SEC_NAUD);
} else {
phytium_writel_reg(priv, n_m->m, group_offset, PHYTIUM_DP_SEC_MAUD);
phytium_writel_reg(priv, n_m->n, group_offset, PHYTIUM_DP_SEC_NAUD);
}
phytium_writel_reg(priv, SECONDARY_STREAM_ENABLE,
group_offset, PHYTIUM_DP_SECONDARY_STREAM_ENABLE);
phytium_dp->audio_info = audio_info;
return 0;
out:
phytium_writel_reg(priv, SECONDARY_STREAM_DISABLE,
group_offset, PHYTIUM_DP_SECONDARY_STREAM_ENABLE);
return ret;
}
void phytium_dp_hw_disable_video(struct phytium_dp_device *phytium_dp)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
uint32_t group_offset = priv->dp_reg_base[port];
phytium_writel_reg(priv, SST_MST_SOURCE_0_DISABLE,
group_offset, PHYTIUM_DP_VIDEO_STREAM_ENABLE);
}
bool phytium_dp_hw_video_is_enable(struct phytium_dp_device *phytium_dp)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port, config;
uint32_t group_offset = priv->dp_reg_base[port];
config = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_VIDEO_STREAM_ENABLE);
return config ? true : false;
}
void phytium_dp_hw_enable_video(struct phytium_dp_device *phytium_dp)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
uint32_t group_offset = priv->dp_reg_base[port];
phytium_writel_reg(priv, SST_MST_SOURCE_0_ENABLE,
group_offset, PHYTIUM_DP_VIDEO_STREAM_ENABLE);
phytium_writel_reg(priv, LINK_SOFT_RESET, group_offset, PHYTIUM_DP_SOFT_RESET);
}
void phytium_dp_hw_config_video(struct phytium_dp_device *phytium_dp)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
uint32_t group_offset = priv->dp_reg_base[port];
unsigned long link_bw, date_rate = 0;
struct drm_display_info *display_info = &phytium_dp->connector.display_info;
unsigned char tu_size = 64;
unsigned long data_per_tu = 0;
int symbols_per_tu, frac_symbols_per_tu, symbol_count, udc, value;
/* cal M/N and tu_size */
phytium_writel_reg(priv, phytium_dp->mode.crtc_clock/10, group_offset, PHYTIUM_DP_M_VID);
phytium_writel_reg(priv, phytium_dp->link_rate/10, group_offset, PHYTIUM_DP_N_VID);
link_bw = phytium_dp->link_rate * phytium_dp->link_lane_count;
date_rate = (phytium_dp->mode.crtc_clock * display_info->bpc * 3)/8;
/* mul 10 for register setting */
data_per_tu = 10*tu_size * date_rate/link_bw;
symbols_per_tu = (data_per_tu/10)&0xff;
frac_symbols_per_tu = (data_per_tu%10*16/10) & 0xf;
phytium_writel_reg(priv, frac_symbols_per_tu<<24 | symbols_per_tu<<16 | tu_size,
group_offset, PHYTIUM_DP_TRANSFER_UNIT_SIZE);
symbol_count = (phytium_dp->mode.crtc_hdisplay*display_info->bpc*3 + 7)/8;
udc = (symbol_count + phytium_dp->link_lane_count - 1)/phytium_dp->link_lane_count;
phytium_writel_reg(priv, udc, group_offset, PHYTIUM_DP_DATA_COUNT);
/* config main stream attributes */
phytium_writel_reg(priv, phytium_dp->mode.crtc_htotal,
group_offset, PHYTIUM_DP_MAIN_LINK_HTOTAL);
phytium_writel_reg(priv, phytium_dp->mode.crtc_hdisplay,
group_offset, PHYTIUM_DP_MAIN_LINK_HRES);
phytium_writel_reg(priv,
phytium_dp->mode.crtc_hsync_end - phytium_dp->mode.crtc_hsync_start,
group_offset, PHYTIUM_DP_MAIN_LINK_HSWIDTH);
phytium_writel_reg(priv, phytium_dp->mode.crtc_htotal - phytium_dp->mode.crtc_hsync_start,
group_offset, PHYTIUM_DP_MAIN_LINK_HSTART);
phytium_writel_reg(priv, phytium_dp->mode.crtc_vtotal,
group_offset, PHYTIUM_DP_MAIN_LINK_VTOTAL);
phytium_writel_reg(priv, phytium_dp->mode.crtc_vdisplay,
group_offset, PHYTIUM_DP_MAIN_LINK_VRES);
phytium_writel_reg(priv,
phytium_dp->mode.crtc_vsync_end - phytium_dp->mode.crtc_vsync_start,
group_offset, PHYTIUM_DP_MAIN_LINK_VSWIDTH);
phytium_writel_reg(priv, phytium_dp->mode.crtc_vtotal - phytium_dp->mode.crtc_vsync_start,
group_offset, PHYTIUM_DP_MAIN_LINK_VSTART);
value = 0;
if (phytium_dp->mode.flags & DRM_MODE_FLAG_PHSYNC)
value = value & (~HSYNC_POLARITY_LOW);
else
value = value | HSYNC_POLARITY_LOW;
if (phytium_dp->mode.flags & DRM_MODE_FLAG_PVSYNC)
value = value & (~VSYNC_POLARITY_LOW);
else
value = value | VSYNC_POLARITY_LOW;
phytium_writel_reg(priv, value, group_offset, PHYTIUM_DP_MAIN_LINK_POLARITY);
switch (display_info->bpc) {
case 10:
value = (MISC0_BIT_DEPTH_10BIT << MISC0_BIT_DEPTH_OFFSET);
break;
case 6:
value = (MISC0_BIT_DEPTH_6BIT << MISC0_BIT_DEPTH_OFFSET);
break;
default:
value = (MISC0_BIT_DEPTH_8BIT << MISC0_BIT_DEPTH_OFFSET);
break;
}
value |= (MISC0_COMPONENT_FORMAT_RGB << MISC0_COMPONENT_FORMAT_SHIFT)
| MISC0_SYNCHRONOUS_CLOCK;
phytium_writel_reg(priv, value, group_offset, PHYTIUM_DP_MAIN_LINK_MISC0);
phytium_writel_reg(priv, 0, group_offset, PHYTIUM_DP_MAIN_LINK_MISC1);
value = USER_ODDEVEN_POLARITY_HIGH | USER_DATA_ENABLE_POLARITY_HIGH;
if (phytium_dp->mode.flags & DRM_MODE_FLAG_PHSYNC)
value = value | USER_HSYNC_POLARITY_HIGH;
else
value = value & (~USER_HSYNC_POLARITY_HIGH);
if (phytium_dp->mode.flags & DRM_MODE_FLAG_PVSYNC)
value = value | USER_VSYNC_POLARITY_HIGH;
else
value = value & (~USER_VSYNC_POLARITY_HIGH);
phytium_writel_reg(priv, value, group_offset, PHYTIUM_DP_USER_SYNC_POLARITY);
}
void phytium_dp_hw_disable_output(struct phytium_dp_device *phytium_dp)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
uint32_t group_offset = priv->dp_reg_base[port];
phytium_writel_reg(priv, TRANSMITTER_OUTPUT_DISABLE,
group_offset, PHYTIUM_DP_TRANSMITTER_OUTPUT_ENABLE);
phytium_writel_reg(priv, LINK_SOFT_RESET, group_offset, PHYTIUM_DP_SOFT_RESET);
}
void phytium_dp_hw_enable_output(struct phytium_dp_device *phytium_dp)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
uint32_t group_offset = priv->dp_reg_base[port];
phytium_writel_reg(priv, LINK_SOFT_RESET, group_offset, PHYTIUM_DP_SOFT_RESET);
phytium_writel_reg(priv, TRANSMITTER_OUTPUT_ENABLE,
group_offset, PHYTIUM_DP_TRANSMITTER_OUTPUT_ENABLE);
}
void phytium_dp_hw_enable_input_source(struct phytium_dp_device *phytium_dp)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
uint32_t group_offset = priv->dp_reg_base[port];
phytium_writel_reg(priv, VIRTUAL_SOURCE_0_ENABLE,
group_offset, PHYTIUM_INPUT_SOURCE_ENABLE);
}
void phytium_dp_hw_disable_input_source(struct phytium_dp_device *phytium_dp)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
phytium_writel_reg(priv, (~VIRTUAL_SOURCE_0_ENABLE)&VIRTUAL_SOURCE_0_ENABLE_MASK,
priv->dp_reg_base[port], PHYTIUM_INPUT_SOURCE_ENABLE);
}
bool phytium_dp_hw_output_is_enable(struct phytium_dp_device *phytium_dp)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
uint32_t group_offset = priv->dp_reg_base[port];
int config = 0;
config = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_TRANSMITTER_OUTPUT_ENABLE);
return config ? true : false;
}
static void phytium_dp_hw_get_hpd_state(struct phytium_dp_device *phytium_dp)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
uint32_t val = 0, raw_state = 0;
uint32_t group_offset = priv->dp_reg_base[port];
val = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_INTERRUPT_RAW_STATUS);
/* maybe miss hpd, so used for clear PHYTIUM_DP_INTERRUPT_RAW_STATUS */
phytium_readl_reg(priv, group_offset, PHYTIUM_DP_INTERRUPT_STATUS);
raw_state = phytium_readl_reg(priv, group_offset, PHYTIUM_DP_SINK_HPD_STATE);
if (val & HPD_EVENT)
phytium_dp->dp_hpd_state.hpd_event_state = true;
if (val & HPD_IRQ)
phytium_dp->dp_hpd_state.hpd_irq_state = true;
if (raw_state & HPD_CONNECT)
phytium_dp->dp_hpd_state.hpd_raw_state = true;
else
phytium_dp->dp_hpd_state.hpd_raw_state = false;
}
void phytium_dp_hw_hpd_irq_setup(struct phytium_dp_device *phytium_dp, bool enable)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
uint32_t group_offset = priv->dp_reg_base[port];
phytium_dp->dp_hpd_state.hpd_irq_enable = enable;
if (enable)
phytium_writel_reg(priv, HPD_OTHER_MASK, group_offset, PHYTIUM_DP_INTERRUPT_MASK);
else
phytium_writel_reg(priv, HPD_IRQ_MASK|HPD_EVENT_MASK|HPD_OTHER_MASK,
group_offset, PHYTIUM_DP_INTERRUPT_MASK);
}
int phytium_dp_hw_init(struct phytium_dp_device *phytium_dp)
{
int ret = 0;
uint8_t count = 0;
phytium_dp->source_rates = phytium_rate;
phytium_dp->num_source_rates = num_source_rates;
count = phytium_dp->funcs->dp_hw_get_source_lane_count(phytium_dp);
phytium_dp->source_max_lane_count = count;
ret = phytium_dp->funcs->dp_hw_reset(phytium_dp);
if (ret)
goto out;
ret = phytium_dp->funcs->dp_hw_init_phy(phytium_dp);
if (ret)
goto out;
phytium_dp->fast_train_support = false;
phytium_dp->hw_spread_enable = phytium_dp->funcs->dp_hw_spread_is_enable(phytium_dp);
out:
return ret;
}
static int phytium_dp_dpcd_get_tp_link(struct phytium_dp_device *phytium_dp,
uint8_t *test_lane_count,
uint32_t *test_link_rate)
{
uint8_t test_link_bw;
int ret;
ret = drm_dp_dpcd_readb(&phytium_dp->aux, DP_TEST_LANE_COUNT,
test_lane_count);
if (ret <= 0) {
DRM_DEBUG_KMS("test pattern Lane count read failed(%d)\n", ret);
goto failed;
}
ret = drm_dp_dpcd_readb(&phytium_dp->aux, DP_TEST_LINK_RATE,
&test_link_bw);
if (ret <= 0) {
DRM_DEBUG_KMS("test pattern link rate read failed(%d)\n", ret);
goto failed;
}
*test_link_rate = drm_dp_bw_code_to_link_rate(test_link_bw);
return 0;
failed:
return ret;
}
static int phytium_dp_dpcd_set_link(struct phytium_dp_device *phytium_dp,
uint8_t lane_count, uint32_t link_rate)
{
uint8_t link_config[2];
int ret = 0;
link_config[0] = drm_dp_link_rate_to_bw_code(link_rate);
link_config[1] = lane_count;
if (drm_dp_enhanced_frame_cap(phytium_dp->dpcd))
link_config[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
ret = drm_dp_dpcd_write(&phytium_dp->aux, DP_LINK_BW_SET, link_config, 2);
if (ret < 0) {
DRM_NOTE("write dpcd DP_LINK_BW_SET fail: ret:%d\n", ret);
goto failed;
}
if (phytium_dp->hw_spread_enable)
link_config[0] = DP_SPREAD_AMP_0_5;
else
link_config[0] = 0;
link_config[1] = DP_SET_ANSI_8B10B;
ret = drm_dp_dpcd_write(&phytium_dp->aux, DP_DOWNSPREAD_CTRL, link_config, 2);
if (ret < 0) {
DRM_ERROR("write DP_DOWNSPREAD_CTRL fail: ret:%d\n", ret);
goto failed;
}
return 0;
failed:
return ret;
}
static int phytium_dp_dpcd_set_test_pattern(struct phytium_dp_device *phytium_dp,
uint8_t test_pattern)
{
unsigned char value;
int ret;
if (phytium_dp_coding_8b10b_need_enable(test_pattern))
value = DP_SET_ANSI_8B10B;
else
value = 0;
ret = drm_dp_dpcd_writeb(&phytium_dp->aux, DP_MAIN_LINK_CHANNEL_CODING_SET, value);
if (ret < 0) {
DRM_ERROR("write DP_MAIN_LINK_CHANNEL_CODING_SET fail: ret:%d\n", ret);
goto failed;
}
if (phytium_dp_scrambled_need_enable(test_pattern))
value = DP_TRAINING_PATTERN_DISABLE;
else
value = (DP_TRAINING_PATTERN_DISABLE | DP_LINK_SCRAMBLING_DISABLE);
ret = drm_dp_dpcd_writeb(&phytium_dp->aux, DP_TRAINING_PATTERN_SET, value);
if (ret < 0) {
DRM_ERROR("write DP_TRAINING_PATTERN_SET fail: ret:%d\n", ret);
goto failed;
}
ret = drm_dp_dpcd_writeb(&phytium_dp->aux, DP_LINK_QUAL_LANE0_SET, test_pattern);
if (ret < 0) {
DRM_ERROR("write DP_TRAINING_PATTERN_SET fail: ret:%d\n", ret);
goto failed;
}
return 0;
failed:
return ret;
}
static int phytium_dp_dpcd_set_train_pattern(struct phytium_dp_device *phytium_dp,
uint8_t train_pattern)
{
uint8_t value;
int ret;
/* Scrambling is disabled for TPS1/2/3 and enabled for TPS4 */
if (train_pattern == DP_TRAINING_PATTERN_4 || train_pattern == DP_TRAINING_PATTERN_DISABLE)
value = train_pattern;
else
value = (train_pattern | DP_LINK_SCRAMBLING_DISABLE);
ret = drm_dp_dpcd_writeb(&phytium_dp->aux, DP_TRAINING_PATTERN_SET, value);
if (ret < 0) {
DRM_NOTE("write DP_TRAINING_PATTERN_SET fail: ret:%d\n", ret);
goto failed;
}
return 0;
failed:
return ret;
}
static int
phytium_dp_dpcd_set_lane_setting(struct phytium_dp_device *phytium_dp, uint8_t *train_set)
{
int ret = 0;
ret = drm_dp_dpcd_write(&phytium_dp->aux, DP_TRAINING_LANE0_SET,
phytium_dp->train_set, 4);
if (ret < 0) {
DRM_ERROR("write DP_TRAINING_LANE0_SET fail: ret:%d\n", ret);
return ret;
}
return 0;
}
static int
phytium_dp_dpcd_get_adjust_request(struct phytium_dp_device *phytium_dp, uint8_t lane_count)
{
int ret = 0;
uint8_t link_status[DP_LINK_STATUS_SIZE];
ret = drm_dp_dpcd_read(&phytium_dp->aux, DP_LANE0_1_STATUS,
link_status, DP_LINK_STATUS_SIZE);
if (ret < 0) {
DRM_ERROR("failed to get link status(DP_LANE0_1_STATUS)\n");
goto failed;
}
phytium_get_adjust_train(phytium_dp, link_status, lane_count);
return 0;
failed:
return ret;
}
void phytium_dp_dpcd_sink_dpms(struct phytium_dp_device *phytium_dp, int mode)
{
int ret, i;
if (phytium_dp->dpcd[DP_DPCD_REV] < 0x11)
return;
if (mode != DRM_MODE_DPMS_ON) {
ret = drm_dp_dpcd_writeb(&phytium_dp->aux, DP_SET_POWER, DP_SET_POWER_D3);
} else {
for (i = 0; i < 3; i++) {
ret = drm_dp_dpcd_writeb(&phytium_dp->aux, DP_SET_POWER, DP_SET_POWER_D0);
if (ret == 1)
break;
msleep(20);
}
}
if (ret != 1)
DRM_DEBUG_KMS("failed to %s sink power state\n",
mode == DRM_MODE_DPMS_ON ? "enable" : "disable");
}
static bool phytium_dp_link_training_clock_recovery(struct phytium_dp_device *phytium_dp)
{
int ret;
unsigned char voltage, max_vswing_tries;
int voltage_tries;
/* clear the test pattern */
phytium_dp_hw_set_test_pattern(phytium_dp, phytium_dp->link_lane_count,
PHYTIUM_PHY_TP_NONE, NULL, 0);
/* config source and sink's link rate and lane count */
phytium_dp_hw_set_link(phytium_dp, phytium_dp->link_lane_count, phytium_dp->link_rate);
ret = phytium_dp_dpcd_set_link(phytium_dp, phytium_dp->link_lane_count,
phytium_dp->link_rate);
if (ret < 0) {
DRM_NOTE("phytium_dp_dpcd_set_link failed(ret=%d)\n", ret);
return false;
}
/* config source's voltage swing and pre-emphasis(103-106) */
memset(phytium_dp->train_set, 0, sizeof(phytium_dp->train_set));
phytium_dp_hw_set_lane_setting(phytium_dp, phytium_dp->link_rate,
phytium_dp->train_set[0]);
/* config train pattern */
phytium_dp_hw_set_train_pattern(phytium_dp, DP_TRAINING_PATTERN_1);
ret = phytium_dp_dpcd_set_train_pattern(phytium_dp, DP_TRAINING_PATTERN_1);
if (ret < 0) {
DRM_ERROR("phytium_dp_dpcd_set_train_pattern fail: ret:%d\n", ret);
return false;
}
/* config sink's voltage swing and pre-emphasis(103-106) */
ret = phytium_dp_dpcd_set_lane_setting(phytium_dp, phytium_dp->train_set);
if (ret < 0) {
DRM_ERROR("phytium_dp_dpcd_set_lane_setting fail: ret:%d\n", ret);
return false;
}
voltage_tries = 1;
max_vswing_tries = 0;
for (;;) {
unsigned char link_status[DP_LINK_STATUS_SIZE];
drm_dp_link_train_clock_recovery_delay(&phytium_dp->aux, phytium_dp->dpcd);
/* get link status 0x202-0x207 */
ret = drm_dp_dpcd_read(&phytium_dp->aux, DP_LANE0_1_STATUS,
link_status, DP_LINK_STATUS_SIZE);
if (ret < 0) {
DRM_ERROR("failed to get link status(DP_LANE0_1_STATUS)\n");
return false;
}
if (drm_dp_clock_recovery_ok(link_status, phytium_dp->link_lane_count)) {
DRM_DEBUG_KMS("clock revorery ok\n");
return true;
}
if (voltage_tries == 5) {
DRM_DEBUG_KMS("Same voltage tried 5 times\n");
return false;
}
if (max_vswing_tries == 1) {
DRM_DEBUG_KMS("Max Voltage Swing reached\n");
return false;
}
voltage = phytium_dp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK;
/* config source and sink's voltage swing and pre-emphasis(103-106) */
phytium_get_adjust_train(phytium_dp, link_status, phytium_dp->link_lane_count);
phytium_dp_hw_set_lane_setting(phytium_dp, phytium_dp->link_rate,
phytium_dp->train_set[0]);
ret = phytium_dp_dpcd_set_lane_setting(phytium_dp, phytium_dp->train_set);
if (ret < 0) {
DRM_ERROR("phytium_dp_dpcd_set_lane_setting fail: ret:%d\n", ret);
return false;
}
if ((phytium_dp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK) == voltage)
++voltage_tries;
else
voltage_tries = 1;
if (phytium_dp->train_set[0] & DP_TRAIN_MAX_SWING_REACHED)
++max_vswing_tries;
DRM_DEBUG_KMS("try train_set:0x%x voltage_tries:%d max_vswing_tries:%d\n",
phytium_dp->train_set[0], voltage_tries, max_vswing_tries);
}
}
static unsigned int phytium_dp_get_training_pattern(struct phytium_dp_device *phytium_dp)
{
bool sink_tps3, sink_tps4;
sink_tps4 = drm_dp_tps4_supported(phytium_dp->dpcd);
if (sink_tps4)
return DP_TRAINING_PATTERN_4;
else if (phytium_dp->link_rate == 810000)
DRM_DEBUG_KMS("8.1 Gbps link rate without sink TPS4 support\n");
sink_tps3 = drm_dp_tps3_supported(phytium_dp->dpcd);
if (sink_tps3)
return DP_TRAINING_PATTERN_3;
else if (phytium_dp->link_rate >= 540000)
DRM_DEBUG_KMS(">=5.4/6.48 Gbps link rate without sink TPS3 support\n");
return DP_TRAINING_PATTERN_2;
}
static bool phytium_dp_link_training_channel_equalization(struct phytium_dp_device *phytium_dp)
{
unsigned int training_pattern;
int tries, ret;
unsigned char link_status[DP_LINK_STATUS_SIZE];
bool channel_eq = false;
/* config source and sink's voltage swing and pre-emphasis(103-106), from clock recovery */
phytium_dp_hw_set_lane_setting(phytium_dp, phytium_dp->link_rate,
phytium_dp->train_set[0]);
ret = phytium_dp_dpcd_set_lane_setting(phytium_dp, phytium_dp->train_set);
if (ret < 0) {
DRM_ERROR("phytium_dp_dpcd_set_lane_setting fail: ret:%d\n", ret);
return channel_eq;
}
/* config source and sink's train_pattern x */
training_pattern = phytium_dp_get_training_pattern(phytium_dp);
phytium_dp_hw_set_train_pattern(phytium_dp, training_pattern);
ret = phytium_dp_dpcd_set_train_pattern(phytium_dp, training_pattern);
if (ret < 0) {
DRM_ERROR("phytium_dp_dpcd_set_train_pattern fail: ret:%d\n", ret);
return channel_eq;
}
for (tries = 0; tries < 5; tries++) {
drm_dp_link_train_channel_eq_delay(&phytium_dp->aux, phytium_dp->dpcd);
/* get link status 0x202-0x207 */
ret = drm_dp_dpcd_read(&phytium_dp->aux, DP_LANE0_1_STATUS,
link_status, DP_LINK_STATUS_SIZE);
if (ret < 0) {
DRM_ERROR("failed to get link status(DP_LANE0_1_STATUS)\n");
break;
}
/* Make sure clock is still ok */
if (!drm_dp_clock_recovery_ok(link_status, phytium_dp->link_lane_count)) {
DRM_DEBUG_KMS("CR check failed, cannot continue channel equalization\n");
break;
}
if (drm_dp_channel_eq_ok(link_status, phytium_dp->link_lane_count)) {
channel_eq = true;
DRM_DEBUG_KMS("Channel EQ done. DP Training successful\n");
break;
}
/* config source and sink's voltage swing and pre-emphasis(103-106) */
phytium_get_adjust_train(phytium_dp, link_status, phytium_dp->link_lane_count);
phytium_dp_hw_set_lane_setting(phytium_dp, phytium_dp->link_rate,
phytium_dp->train_set[0]);
ret = phytium_dp_dpcd_set_lane_setting(phytium_dp, phytium_dp->train_set);
if (ret < 0) {
DRM_ERROR("phytium_dp_dpcd_set_lane_setting fail: ret:%d\n", ret);
break;
}
}
/* Try 5 times, else fail and try at lower BW */
if (tries == 5)
DRM_DEBUG_KMS("Channel equalization failed 5 times\n");
return channel_eq;
}
static void phytium_dp_train_retry_work_fn(struct work_struct *work)
{
struct phytium_dp_device *phytium_dp = train_retry_to_dp_device(work);
struct drm_connector *connector;
connector = &phytium_dp->connector;
DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", connector->base.id, connector->name);
mutex_lock(&connector->dev->mode_config.mutex);
drm_connector_set_link_status_property(connector, DRM_MODE_LINK_STATUS_BAD);
mutex_unlock(&connector->dev->mode_config.mutex);
drm_kms_helper_hotplug_event(connector->dev);
}
/* return index of rate in rates array, or -1 if not found */
static int phytium_dp_rate_index(const int *rates, int len, int rate)
{
int i;
for (i = 0; i < len; i++)
if (rate == rates[i])
return i;
return -1;
}
int phytium_dp_get_link_train_fallback_values(struct phytium_dp_device *phytium_dp)
{
int index, ret = 0;
if (phytium_dp->is_edp) {
phytium_dp->train_retry_count++;
DRM_INFO("Retrying Link training for eDP(%d) with same parameters\n",
phytium_dp->port);
goto out;
} else {
index = phytium_dp_rate_index(phytium_dp->common_rates,
phytium_dp->num_common_rates,
phytium_dp->link_rate);
if (index > 0) {
phytium_dp->link_rate = phytium_dp->common_rates[index - 1];
} else if (phytium_dp->link_lane_count > 1) {
phytium_dp->link_rate = phytium_dp->max_link_rate;
phytium_dp->link_lane_count = phytium_dp->link_lane_count >> 1;
} else {
phytium_dp->train_retry_count++;
phytium_dp->link_rate = phytium_dp->max_link_rate;
phytium_dp->link_lane_count = phytium_dp->max_link_lane_count;
DRM_INFO("Retrying Link training for DP(%d) with maximal parameters\n",
phytium_dp->port);
ret = -1;
}
}
out:
return ret;
}
static int
phytium_dp_stop_link_train(struct phytium_dp_device *phytium_dp)
{
int ret;
/* config source and sink's train_pattern x: DP_TRAINING_PATTERN_DISABLE */
phytium_dp_hw_set_train_pattern(phytium_dp, DP_TRAINING_PATTERN_DISABLE);
ret = phytium_dp_dpcd_set_train_pattern(phytium_dp, DP_TRAINING_PATTERN_DISABLE);
if (ret < 0) {
DRM_NOTE("phytium_dp_dpcd_set_train_pattern fail: ret:%d\n", ret);
return ret;
}
return 0;
}
int phytium_dp_start_link_train(struct phytium_dp_device *phytium_dp)
{
int ret = 0;
phytium_dp_hw_disable_output(phytium_dp);
phytium_dp_hw_disable_input_source(phytium_dp);
phytium_dp_hw_disable_video(phytium_dp);
phytium_dp_hw_enable_input_source(phytium_dp);
phytium_dp_hw_enable_output(phytium_dp);
phytium_dp_dpcd_sink_dpms(phytium_dp, DRM_MODE_DPMS_OFF);
phytium_dp_dpcd_sink_dpms(phytium_dp, DRM_MODE_DPMS_ON);
if (!phytium_dp_link_training_clock_recovery(phytium_dp))
goto failure_handling;
if (!phytium_dp_link_training_channel_equalization(phytium_dp))
goto failure_handling;
ret = phytium_dp_stop_link_train(phytium_dp);
if (ret < 0) {
DRM_NOTE("phytium_dp_stop_link_train failed: ret = %d\n", ret);
goto out;
}
if (phytium_dp->trigger_train_fail) {
phytium_dp->trigger_train_fail--;
goto failure_handling;
}
phytium_dp->train_retry_count = 0;
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] Link Training Pass at Link Rate = %d, Lane count = %d\n",
phytium_dp->connector.base.id,
phytium_dp->connector.name, phytium_dp->link_rate,
phytium_dp->link_lane_count);
return 0;
failure_handling:
DRM_INFO("[CONNECTOR:%d:%s] Link Training failed at Link Rate = %d, Lane count = %d",
phytium_dp->connector.base.id,
phytium_dp->connector.name,
phytium_dp->link_rate, phytium_dp->link_lane_count);
ret = phytium_dp_stop_link_train(phytium_dp);
if (ret < 0) {
DRM_NOTE("phytium_dp_stop_link_train failed: ret = %d\n", ret);
goto out;
}
phytium_dp_get_link_train_fallback_values(phytium_dp);
if (phytium_dp->train_retry_count < 5)
schedule_work(&phytium_dp->train_retry_work);
else
DRM_ERROR("DP(%d) Link Training Unsuccessful, and stop Training\n",
phytium_dp->port);
out:
return -1;
}
static bool phytium_dp_needs_link_retrain(struct phytium_dp_device *phytium_dp)
{
unsigned char link_status[DP_LINK_STATUS_SIZE];
int ret = 0;
/* get link status 0x202-0x207 */
ret = drm_dp_dpcd_read(&phytium_dp->aux, DP_LANE0_1_STATUS,
link_status, DP_LINK_STATUS_SIZE);
if (ret < 0) {
DRM_ERROR("failed to get link status(DP_LANE0_1_STATUS)\n");
return true;
}
if ((phytium_dp->link_rate == 0) || (phytium_dp->link_lane_count == 0)) {
DRM_DEBUG_KMS("link_rate(%d) or lane_count(%d) is invalid\n",
phytium_dp->link_rate, phytium_dp->link_lane_count);
return true;
}
/* Make sure clock is still ok */
if (!drm_dp_clock_recovery_ok(link_status, phytium_dp->link_lane_count)) {
DRM_DEBUG_KMS("Clock recovery check failed\n");
return true;
}
if (!drm_dp_channel_eq_ok(link_status, phytium_dp->link_lane_count)) {
DRM_DEBUG_KMS("Channel EQ check failed\n");
return true;
}
if (!phytium_dp_hw_output_is_enable(phytium_dp)) {
DRM_DEBUG_KMS("check DP output enable failed\n");
return true;
}
return false;
}
static bool
phytium_dp_get_sink_irq(struct phytium_dp_device *phytium_dp, u8 *sink_irq_vector)
{
return drm_dp_dpcd_readb(&phytium_dp->aux, DP_DEVICE_SERVICE_IRQ_VECTOR,
sink_irq_vector) == 1;
}
static uint8_t phytium_dp_autotest_phy_pattern(struct phytium_dp_device *phytium_dp)
{
union phytium_phy_tp phytium_phy_tp;
int ret;
unsigned char test_80_bit_pattern[
(DP_TEST_80BIT_CUSTOM_PATTERN_79_72 -
DP_TEST_80BIT_CUSTOM_PATTERN_7_0)+1] = {0};
unsigned char test_pattern;
unsigned int offset;
offset = DP_PHY_TEST_PATTERN;
ret = drm_dp_dpcd_read(&phytium_dp->aux, offset,
&phytium_phy_tp.raw,
sizeof(phytium_phy_tp));
if (ret <= 0) {
DRM_DEBUG_KMS("Could not read DP_TEST_PHY_PATTERN\n");
goto failed;
}
test_pattern = phytium_phy_tp.bits.PATTERN;
if (test_pattern == PHYTIUM_PHY_TP_80BIT_CUSTOM) {
ret = drm_dp_dpcd_read(&phytium_dp->aux, DP_TEST_80BIT_CUSTOM_PATTERN_7_0,
test_80_bit_pattern,
sizeof(test_80_bit_pattern));
if (ret <= 0) {
DRM_DEBUG_KMS("Could not read DP_TEST_PHY_PATTERN\n");
goto failed;
}
}
/* config source and sink's link rate and link count */
ret = phytium_dp_dpcd_get_tp_link(phytium_dp, &phytium_dp->compliance.test_lane_count,
&phytium_dp->compliance.test_link_rate);
if (ret < 0) {
DRM_ERROR("phytium_dp_dpcd_get_tp_link fail: ret:%d\n", ret);
goto failed;
}
phytium_dp_hw_set_link(phytium_dp, phytium_dp->compliance.test_lane_count,
phytium_dp->compliance.test_link_rate);
ret = phytium_dp_dpcd_set_link(phytium_dp, phytium_dp->compliance.test_lane_count,
phytium_dp->compliance.test_link_rate);
if (ret < 0) {
DRM_ERROR("phytium_dp_dpcd_set_link fail: ret:%d\n", ret);
goto failed_dpcd_set_link;
}
/* config source and sink's lane setting: voltage swing and pre-emphasis */
ret = phytium_dp_dpcd_get_adjust_request(phytium_dp,
phytium_dp->compliance.test_lane_count);
if (ret < 0) {
DRM_ERROR("phytium_dp_dpcd_get_adjust_request fail: ret:%d\n", ret);
goto failed_dpcd_get_adjust_request;
}
phytium_dp_hw_set_lane_setting(phytium_dp, phytium_dp->compliance.test_link_rate,
phytium_dp->train_set[0]);
ret = phytium_dp_dpcd_set_lane_setting(phytium_dp, phytium_dp->train_set);
if (ret < 0) {
DRM_ERROR("phytium_dp_dpcd_set_lane_setting fail: ret:%d\n", ret);
goto failed_dpcd_set_lane_setting;
}
/* config test pattern */
phytium_dp_hw_set_test_pattern(phytium_dp, phytium_dp->compliance.test_lane_count,
test_pattern, test_80_bit_pattern,
sizeof(test_80_bit_pattern));
ret = phytium_dp_dpcd_set_test_pattern(phytium_dp, test_pattern);
if (ret < 0) {
DRM_ERROR("phytium_dp_dpcd_set_test_pattern fail: ret:%d\n", ret);
goto failed_dpcd_set_tp;
}
return DP_TEST_ACK;
failed_dpcd_set_tp:
phytium_dp_hw_set_test_pattern(phytium_dp, phytium_dp->compliance.test_lane_count,
PHYTIUM_PHY_TP_NONE, test_80_bit_pattern,
sizeof(test_80_bit_pattern));
failed_dpcd_set_link:
failed_dpcd_set_lane_setting:
failed_dpcd_get_adjust_request:
failed:
return DP_TEST_NAK;
}
static void phytium_dp_handle_test_request(struct phytium_dp_device *phytium_dp)
{
uint8_t response = DP_TEST_NAK;
uint8_t request = 0;
int status;
status = drm_dp_dpcd_readb(&phytium_dp->aux, DP_TEST_REQUEST, &request);
if (status <= 0) {
DRM_DEBUG_KMS("Could not read test request from sink\n");
goto update_status;
}
switch (request) {
case DP_TEST_LINK_TRAINING:
case DP_TEST_LINK_VIDEO_PATTERN:
case DP_TEST_LINK_EDID_READ:
DRM_DEBUG_KMS("Not support test request '%02x'\n", request);
response = DP_TEST_NAK;
break;
case DP_TEST_LINK_PHY_TEST_PATTERN:
DRM_DEBUG_KMS("PHY_PATTERN test requested\n");
response = phytium_dp_autotest_phy_pattern(phytium_dp);
break;
default:
DRM_DEBUG_KMS("Invalid test request '%02x'\n", request);
break;
}
update_status:
status = drm_dp_dpcd_writeb(&phytium_dp->aux, DP_TEST_RESPONSE, response);
if (status <= 0)
DRM_DEBUG_KMS("Could not write test response to sink\n");
}
static int phytium_dp_long_pulse(struct drm_connector *connector, bool hpd_raw_state)
{
struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector);
enum drm_connector_status status = connector->status;
bool video_enable = false;
uint32_t index = 0;
if (phytium_dp->is_edp)
status = connector_status_connected;
else if (hpd_raw_state) {
if (!phytium_dp_needs_link_retrain(phytium_dp)) {
status = connector_status_connected;
goto out;
}
} else {
status = connector_status_disconnected;
goto out;
}
if (!phytium_dp->is_edp) {
status = phytium_dp_detect_dpcd(phytium_dp);
if (status == connector_status_disconnected)
goto out;
index = phytium_dp->num_common_rates-1;
phytium_dp->max_link_rate = phytium_dp->common_rates[index];
phytium_dp->max_link_lane_count = phytium_dp->common_max_lane_count;
phytium_dp->link_rate = phytium_dp->max_link_rate;
phytium_dp->link_lane_count = phytium_dp->max_link_lane_count;
DRM_DEBUG_KMS("common_max_lane_count: %d, common_max_rate:%d\n",
phytium_dp->max_link_lane_count, phytium_dp->max_link_rate);
video_enable = phytium_dp_hw_video_is_enable(phytium_dp);
phytium_dp_start_link_train(phytium_dp);
if (video_enable) {
mdelay(2);
phytium_dp_hw_enable_video(phytium_dp);
}
}
out:
return status;
}
static int phytium_dp_short_pulse(struct drm_connector *connector)
{
struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector);
enum drm_connector_status status = connector->status;
u8 sink_irq_vector = 0;
bool video_enable = false;
/* handle the test pattern */
if (phytium_dp_get_sink_irq(phytium_dp, &sink_irq_vector) &&
sink_irq_vector != 0) {
drm_dp_dpcd_writeb(&phytium_dp->aux,
DP_DEVICE_SERVICE_IRQ_VECTOR,
sink_irq_vector);
if (sink_irq_vector & DP_AUTOMATED_TEST_REQUEST)
phytium_dp_handle_test_request(phytium_dp);
if (sink_irq_vector & (DP_CP_IRQ | DP_SINK_SPECIFIC_IRQ))
DRM_DEBUG_DRIVER("CP or sink specific irq unhandled\n");
}
if (!phytium_dp_needs_link_retrain(phytium_dp)) {
status = connector_status_connected;
goto out;
}
video_enable = phytium_dp_hw_video_is_enable(phytium_dp);
phytium_dp_start_link_train(phytium_dp);
if (video_enable) {
mdelay(2);
phytium_dp_hw_enable_video(phytium_dp);
}
out:
return status;
}
void phytium_dp_hpd_poll_handler(struct phytium_display_private *priv)
{
struct drm_device *dev = priv->dev;
struct drm_connector_list_iter conn_iter;
struct drm_connector *connector;
enum drm_connector_status old_status;
bool changed = false;
mutex_lock(&dev->mode_config.mutex);
DRM_DEBUG_KMS("running encoder hotplug poll functions\n");
drm_connector_list_iter_begin(dev, &conn_iter);
drm_for_each_connector_iter(connector, &conn_iter) {
if (connector->force)
continue;
old_status = connector->status;
connector->status = drm_helper_probe_detect(connector, NULL, false);
if (old_status != connector->status) {
const char *old, *new;
old = drm_get_connector_status_name(old_status);
new = drm_get_connector_status_name(connector->status);
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] status updated from %s to %s\n",
connector->base.id,
connector->name,
old, new);
changed = true;
}
}
drm_connector_list_iter_end(&conn_iter);
mutex_unlock(&dev->mode_config.mutex);
if (changed)
drm_kms_helper_hotplug_event(dev);
}
void phytium_dp_hpd_irq_setup(struct drm_device *dev, bool enable)
{
struct phytium_dp_device *phytium_dp;
struct drm_encoder *encoder;
struct phytium_display_private *priv = dev->dev_private;
bool handler = false;
bool hpd_raw_state_old = false;
/* We might have missed any hotplugs that happened, so polling and handler */
if (enable) {
spin_lock_irq(&priv->hotplug_irq_lock);
drm_for_each_encoder(encoder, dev) {
phytium_dp = encoder_to_dp_device(encoder);
if (!phytium_dp->dp_hpd_state.hpd_irq_enable) {
hpd_raw_state_old = phytium_dp->dp_hpd_state.hpd_raw_state;
phytium_dp_hw_get_hpd_state(phytium_dp);
if (phytium_dp->dp_hpd_state.hpd_event_state
|| phytium_dp->dp_hpd_state.hpd_irq_state
|| (hpd_raw_state_old != phytium_dp->dp_hpd_state.hpd_raw_state)) {
handler = true;
}
}
}
spin_unlock_irq(&priv->hotplug_irq_lock);
if (handler)
phytium_dp_hpd_poll_handler(priv);
}
drm_for_each_encoder(encoder, dev) {
phytium_dp = encoder_to_dp_device(encoder);
phytium_dp_hw_hpd_irq_setup(phytium_dp, enable);
}
}
void phytium_dp_hpd_work_func(struct work_struct *work)
{
struct phytium_display_private *priv =
container_of(work, struct phytium_display_private, hotplug_work);
struct drm_device *dev = priv->dev;
struct drm_connector_list_iter conn_iter;
struct drm_connector *connector;
enum drm_connector_status old_status;
bool changed = false;
mutex_lock(&dev->mode_config.mutex);
DRM_DEBUG_KMS("running encoder hotplug work functions\n");
drm_connector_list_iter_begin(dev, &conn_iter);
drm_for_each_connector_iter(connector, &conn_iter) {
if (connector->force)
continue;
old_status = connector->status;
connector->status = drm_helper_probe_detect(connector, NULL, false);
if (old_status != connector->status) {
const char *old, *new;
old = drm_get_connector_status_name(old_status);
new = drm_get_connector_status_name(connector->status);
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] status updated from %s to %s\n",
connector->base.id,
connector->name,
old, new);
changed = true;
}
}
drm_connector_list_iter_end(&conn_iter);
mutex_unlock(&dev->mode_config.mutex);
if (changed)
drm_kms_helper_hotplug_event(dev);
phytium_dp_hpd_irq_setup(dev, true);
}
irqreturn_t phytium_dp_hpd_irq_handler(struct phytium_display_private *priv)
{
struct drm_encoder *encoder = NULL;
struct phytium_dp_device *phytium_dp = NULL;
struct drm_device *dev = priv->dev;
bool handler = false;
spin_lock(&priv->hotplug_irq_lock);
drm_for_each_encoder(encoder, dev) {
phytium_dp = encoder_to_dp_device(encoder);
if (phytium_dp->dp_hpd_state.hpd_irq_enable) {
phytium_dp_hw_get_hpd_state(phytium_dp);
if (phytium_dp->dp_hpd_state.hpd_event_state
|| phytium_dp->dp_hpd_state.hpd_irq_state) {
handler = true;
}
}
}
spin_unlock(&priv->hotplug_irq_lock);
if (handler) {
phytium_dp_hpd_irq_setup(dev, false);
schedule_work(&priv->hotplug_work);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static void phytium_dp_fast_link_train_detect(struct phytium_dp_device *phytium_dp)
{
phytium_dp->fast_train_support = !!(phytium_dp->dpcd[DP_MAX_DOWNSPREAD]
& DP_NO_AUX_HANDSHAKE_LINK_TRAINING);
DRM_DEBUG_KMS("fast link training %s\n",
phytium_dp->fast_train_support ? "supported" : "unsupported");
}
bool phytium_dp_fast_link_train(struct phytium_dp_device *phytium_dp)
{
int ret = 0;
unsigned int training_pattern;
/* clear the test pattern */
phytium_dp_hw_set_test_pattern(phytium_dp, phytium_dp->link_lane_count,
PHYTIUM_PHY_TP_NONE, NULL, 0);
/* config source and sink's link rate and lane count */
phytium_dp_hw_set_link(phytium_dp, phytium_dp->link_lane_count, phytium_dp->link_rate);
/* config source and sink's voltage swing and pre-emphasis(103-106) */
phytium_dp_hw_set_lane_setting(phytium_dp, phytium_dp->link_rate,
phytium_dp->train_set[0]);
/* config train pattern */
phytium_dp_hw_set_train_pattern(phytium_dp, DP_TRAINING_PATTERN_1);
usleep_range(500, 600);
training_pattern = phytium_dp_get_training_pattern(phytium_dp);
phytium_dp_hw_set_train_pattern(phytium_dp, training_pattern);
usleep_range(500, 600);
phytium_dp_hw_set_train_pattern(phytium_dp, DP_TRAINING_PATTERN_DISABLE);
if (dc_fast_training_check) {
unsigned char link_status[DP_LINK_STATUS_SIZE];
ret = drm_dp_dpcd_read(&phytium_dp->aux, DP_LANE0_1_STATUS,
link_status, DP_LINK_STATUS_SIZE);
if (ret < 0) {
DRM_ERROR("failed to get link status(DP_LANE0_1_STATUS)\n");
return false;
}
if (!drm_dp_clock_recovery_ok(link_status, phytium_dp->link_lane_count)) {
DRM_DEBUG_KMS("check clock recovery failed\n");
return false;
}
if (!drm_dp_channel_eq_ok(link_status, phytium_dp->link_lane_count)) {
DRM_DEBUG_KMS("check channel equalization failed\n");
return false;
}
}
return true;
}
static enum drm_connector_status
phytium_connector_detect(struct drm_connector *connector, bool force)
{
enum drm_connector_status status = connector->status;
struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector);
bool hpd_event_state, hpd_irq_state, hpd_raw_state;
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
bool plugged = true;
spin_lock_irq(&priv->hotplug_irq_lock);
hpd_event_state = phytium_dp->dp_hpd_state.hpd_event_state;
hpd_irq_state = phytium_dp->dp_hpd_state.hpd_irq_state;
hpd_raw_state = phytium_dp->dp_hpd_state.hpd_raw_state;
phytium_dp->dp_hpd_state.hpd_event_state = false;
phytium_dp->dp_hpd_state.hpd_irq_state = false;
spin_unlock_irq(&priv->hotplug_irq_lock);
if (hpd_event_state)
status = phytium_dp_long_pulse(connector, hpd_raw_state);
if (hpd_irq_state)
status = phytium_dp_short_pulse(connector);
if (status == connector_status_unknown)
status = connector_status_disconnected;
if ((!phytium_dp->is_edp) && (!hpd_raw_state))
status = connector_status_disconnected;
if (connector->status != status) {
if ((status == connector_status_connected) && phytium_dp->has_audio)
plugged = true;
else
plugged = false;
handle_plugged_change(phytium_dp, plugged);
}
return status;
}
static void
phytium_connector_destroy(struct drm_connector *connector)
{
struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector);
drm_connector_cleanup(connector);
kfree(phytium_dp);
}
static int
phytium_dp_connector_register(struct drm_connector *connector)
{
int ret;
struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector);
phytium_dp_aux_init(phytium_dp);
if (phytium_dp->is_edp) {
phytium_edp_init_connector(phytium_dp);
ret = phytium_edp_backlight_device_register(phytium_dp);
if (ret)
DRM_ERROR("failed to register port(%d) backlight device(ret=%d)\n",
phytium_dp->port, ret);
}
ret = phytium_debugfs_connector_add(connector);
if (ret)
DRM_ERROR("failed to register phytium connector debugfs(ret=%d)\n", ret);
return 0;
}
static void
phytium_dp_connector_unregister(struct drm_connector *connector)
{
struct phytium_dp_device *phytium_dp = connector_to_dp_device(connector);
if (phytium_dp->is_edp) {
phytium_edp_backlight_device_unregister(phytium_dp);
phytium_edp_fini_connector(phytium_dp);
}
drm_dp_aux_unregister(&phytium_dp->aux);
}
static const struct drm_connector_funcs phytium_connector_funcs = {
.dpms = drm_helper_connector_dpms,
.detect = phytium_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = phytium_connector_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,
.late_register = phytium_dp_connector_register,
.early_unregister = phytium_dp_connector_unregister,
};
static void phytium_dp_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted)
{
struct phytium_dp_device *dp = encoder_to_dp_device(encoder);
drm_mode_copy(&dp->mode, adjusted);
}
static void phytium_edp_panel_poweron(struct phytium_dp_device *phytium_dp)
{
phytium_panel_poweron(&phytium_dp->panel);
}
static void phytium_edp_panel_poweroff(struct phytium_dp_device *phytium_dp)
{
phytium_panel_poweroff(&phytium_dp->panel);
}
static void phytium_edp_backlight_on(struct phytium_dp_device *phytium_dp)
{
phytium_panel_enable_backlight(&phytium_dp->panel);
}
static void phytium_edp_backlight_off(struct phytium_dp_device *phytium_dp)
{
phytium_panel_disable_backlight(&phytium_dp->panel);
}
static void phytium_encoder_disable(struct drm_encoder *encoder)
{
struct phytium_dp_device *phytium_dp = encoder_to_dp_device(encoder);
if (phytium_dp->is_edp)
phytium_edp_backlight_off(phytium_dp);
phytium_dp_hw_disable_video(phytium_dp);
mdelay(50);
if (phytium_dp->is_edp)
phytium_edp_panel_poweroff(phytium_dp);
}
void phytium_dp_adjust_link_train_parameter(struct phytium_dp_device *phytium_dp)
{
struct drm_display_info *display_info = &phytium_dp->connector.display_info;
unsigned long link_bw, date_rate = 0, bs_limit, bs_request;
int rate = 0;
bs_request = phytium_dp->mode.crtc_htotal/(phytium_dp->mode.crtc_clock/1000);
date_rate = (phytium_dp->mode.crtc_clock * display_info->bpc * 3)/8;
for (;;) {
bs_limit = 8192 / (phytium_dp->link_rate/1000);
link_bw = phytium_dp->link_rate * phytium_dp->link_lane_count;
rate = 10 * date_rate / link_bw;
DRM_DEBUG_KMS("adjust link rate(%d), lane count(%d)\n",
phytium_dp->link_rate, phytium_dp->link_lane_count);
DRM_DEBUG_KMS("for crtc_clock(%d) bs_request(%ld) bs_limit(%ld) rate(%d)\n",
phytium_dp->mode.crtc_clock, bs_request, bs_limit, rate);
if ((link_dynamic_adjust && (bs_request < bs_limit) && rate < 10) ||
((!link_dynamic_adjust) && (rate < 10)))
break;
phytium_dp_get_link_train_fallback_values(phytium_dp);
}
DRM_DEBUG_KMS("Try link training at Link Rate = %d, Lane count = %d\n",
phytium_dp->link_rate, phytium_dp->link_lane_count);
}
static void phytium_encoder_enable(struct drm_encoder *encoder)
{
struct phytium_dp_device *phytium_dp = encoder_to_dp_device(encoder);
int ret = 0;
phytium_dp_hw_disable_video(phytium_dp);
if (phytium_dp->is_edp) {
phytium_edp_panel_poweron(phytium_dp);
if (phytium_dp->fast_train_support)
phytium_dp_fast_link_train(phytium_dp);
else
ret = phytium_dp_start_link_train(phytium_dp);
mdelay(2);
phytium_dp_fast_link_train_detect(phytium_dp);
} else {
phytium_dp_adjust_link_train_parameter(phytium_dp);
ret = phytium_dp_start_link_train(phytium_dp);
mdelay(2);
}
phytium_dp_hw_config_video(phytium_dp);
if (ret == 0) {
phytium_dp_hw_enable_video(phytium_dp);
if (phytium_dp->has_audio)
phytium_dp_hw_enable_audio(phytium_dp);
}
if (phytium_dp->is_edp)
phytium_edp_backlight_on(phytium_dp);
}
enum drm_mode_status
phytium_encoder_mode_valid(struct drm_encoder *encoder, const struct drm_display_mode *mode)
{
struct phytium_dp_device *phytium_dp = encoder_to_dp_device(encoder);
struct drm_display_info *display_info = &phytium_dp->connector.display_info;
unsigned int requested, actual;
switch (display_info->bpc) {
case 10:
case 6:
case 8:
break;
default:
DRM_INFO("not support bpc(%d)\n", display_info->bpc);
display_info->bpc = 8;
break;
}
if ((display_info->color_formats & DRM_COLOR_FORMAT_RGB444) == 0) {
DRM_INFO("not support color_format(%d)\n", display_info->color_formats);
display_info->color_formats = DRM_COLOR_FORMAT_RGB444;
}
requested = mode->clock * display_info->bpc * 3 / 1000;
actual = phytium_dp->max_link_rate * phytium_dp->max_link_lane_count / 100;
actual = actual * 8 / 10;
if (requested >= actual) {
DRM_DEBUG_KMS("requested=%d, actual=%d, clock=%d\n", requested, actual,
mode->clock);
return MODE_CLOCK_HIGH;
}
if (dc_fake_mode_enable &&
(phytium_dp->native_mode.clock == mode->clock) &&
(phytium_dp->native_mode.htotal == mode->htotal) &&
(phytium_dp->native_mode.vtotal == mode->vtotal))
return MODE_OK;
if ((mode->hdisplay == 1600) && (mode->vdisplay == 900))
return MODE_BAD_HVALUE;
if ((mode->hdisplay == 1024) && (mode->clock > 78000))
return MODE_BAD_HVALUE;
if ((mode->hdisplay < 640) || (mode->vdisplay < 480))
return MODE_BAD_HVALUE;
return MODE_OK;
}
static const struct drm_encoder_helper_funcs phytium_encoder_helper_funcs = {
.mode_set = phytium_dp_encoder_mode_set,
.disable = phytium_encoder_disable,
.enable = phytium_encoder_enable,
.mode_valid = phytium_encoder_mode_valid,
};
void phytium_dp_encoder_destroy(struct drm_encoder *encoder)
{
struct phytium_dp_device *phytium_dp = encoder_to_dp_device(encoder);
phytium_dp_audio_codec_fini(phytium_dp);
drm_encoder_cleanup(encoder);
}
static const struct drm_encoder_funcs phytium_encoder_funcs = {
.destroy = phytium_dp_encoder_destroy,
};
static const struct dp_audio_n_m phytium_dp_audio_n_m[] = {
{ 32000, 162000, 1024, 10125 },
{ 44100, 162000, 784, 5625 },
{ 48000, 162000, 512, 3375 },
{ 64000, 162000, 2048, 10125 },
{ 88200, 162000, 1568, 5625 },
{ 96000, 162000, 1024, 3375 },
{ 128000, 162000, 4096, 10125 },
{ 176400, 162000, 3136, 5625 },
{ 192000, 162000, 2048, 3375 },
{ 32000, 270000, 1024, 16875 },
{ 44100, 270000, 784, 9375 },
{ 48000, 270000, 512, 5625 },
{ 64000, 270000, 2048, 16875 },
{ 88200, 270000, 1568, 9375 },
{ 96000, 270000, 1024, 5625 },
{ 128000, 270000, 4096, 16875 },
{ 176400, 270000, 3136, 9375 },
{ 192000, 270000, 2048, 5625 },
{ 32000, 540000, 1024, 33750 },
{ 44100, 540000, 784, 18750 },
{ 48000, 540000, 512, 11250 },
{ 64000, 540000, 2048, 33750 },
{ 88200, 540000, 1568, 18750 },
{ 96000, 540000, 1024, 11250 },
{ 128000, 540000, 4096, 33750 },
{ 176400, 540000, 3136, 18750 },
{ 192000, 540000, 2048, 11250 },
{ 32000, 810000, 1024, 50625 },
{ 44100, 810000, 784, 28125 },
{ 48000, 810000, 512, 16875 },
{ 64000, 810000, 2048, 50625 },
{ 88200, 810000, 1568, 28125 },
{ 96000, 810000, 1024, 16875 },
{ 128000, 810000, 4096, 50625 },
{ 176400, 810000, 3136, 28125 },
{ 192000, 810000, 2048, 16875 },
};
static int phytium_dp_audio_get_eld(struct device *dev, void *data, u8 *buf, size_t len)
{
struct phytium_dp_device *phytium_dp = data;
memcpy(buf, phytium_dp->connector.eld, min(sizeof(phytium_dp->connector.eld), len));
return 0;
}
static int phytium_dp_audio_mute_stream(struct device *dev, void *data, bool enable, int direction)
{
struct phytium_dp_device *phytium_dp = data;
phytium_dp_hw_audio_digital_mute(phytium_dp, enable);
return 0;
}
const struct dp_audio_n_m *phytium_dp_audio_get_n_m(int link_rate, int sample_rate)
{
int i;
for (i = 0; i < ARRAY_SIZE(phytium_dp_audio_n_m); i++) {
if (sample_rate == phytium_dp_audio_n_m[i].sample_rate
&& link_rate == phytium_dp_audio_n_m[i].link_rate)
return &phytium_dp_audio_n_m[i];
}
return NULL;
}
static int phytium_dp_audio_hw_params(struct device *dev, void *data,
struct hdmi_codec_daifmt *daifmt,
struct hdmi_codec_params *params)
{
struct phytium_dp_device *phytium_dp = data;
int ret = 0;
struct audio_info audio_info = {
.sample_width = params->sample_width,
.sample_rate = params->sample_rate,
.channels = params->channels,
};
if (daifmt->fmt != HDMI_I2S) {
DRM_ERROR("invalid audio format %d\n", daifmt->fmt);
ret = -EINVAL;
goto failed;
}
ret = phytium_dp_hw_audio_hw_params(phytium_dp, audio_info);
failed:
return ret;
}
static void phytium_dp_audio_shutdown(struct device *dev, void *data)
{
struct phytium_dp_device *phytium_dp = data;
phytium_dp_hw_audio_shutdown(phytium_dp);
}
static void handle_plugged_change(struct phytium_dp_device *phytium_dp, bool plugged)
{
if (phytium_dp->plugged_cb && phytium_dp->codec_dev)
phytium_dp->plugged_cb(phytium_dp->codec_dev, plugged);
}
static int phytium_dp_audio_hook_plugged_cb(struct device *dev, void *data,
hdmi_codec_plugged_cb fn,
struct device *codec_dev)
{
struct phytium_dp_device *phytium_dp = data;
bool plugged;
phytium_dp->plugged_cb = fn;
phytium_dp->codec_dev = codec_dev;
if ((phytium_dp->connector.status == connector_status_connected) && phytium_dp->has_audio)
plugged = true;
else
plugged = false;
handle_plugged_change(phytium_dp, plugged);
return 0;
}
static const struct hdmi_codec_ops phytium_audio_codec_ops = {
.hw_params = phytium_dp_audio_hw_params,
.audio_shutdown = phytium_dp_audio_shutdown,
.mute_stream = phytium_dp_audio_mute_stream,
.get_eld = phytium_dp_audio_get_eld,
.hook_plugged_cb = phytium_dp_audio_hook_plugged_cb,
};
static int phytium_dp_audio_codec_init(struct phytium_dp_device *phytium_dp)
{
struct device *dev = phytium_dp->dev->dev;
struct hdmi_codec_pdata codec_data = {
.i2s = 1,
.spdif = 0,
.ops = &phytium_audio_codec_ops,
.max_i2s_channels = 2,
.data = phytium_dp,
};
phytium_dp->audio_pdev = platform_device_register_data(dev, HDMI_CODEC_DRV_NAME,
codec_id,
&codec_data, sizeof(codec_data));
if (!PTR_ERR_OR_ZERO(phytium_dp->audio_pdev))
codec_id += 1;
return PTR_ERR_OR_ZERO(phytium_dp->audio_pdev);
}
static void phytium_dp_audio_codec_fini(struct phytium_dp_device *phytium_dp)
{
if (!PTR_ERR_OR_ZERO(phytium_dp->audio_pdev))
platform_device_unregister(phytium_dp->audio_pdev);
phytium_dp->audio_pdev = NULL;
codec_id -= 1;
}
static long phytium_dp_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg)
{
struct phytium_dp_device *phytium_dp = container_of(aux, struct phytium_dp_device, aux);
long ret = 0;
DRM_DEBUG_KMS("msg->size: 0x%lx\n", msg->size);
if (WARN_ON(msg->size > 16))
return -E2BIG;
switch (msg->request & ~DP_AUX_I2C_MOT) {
case DP_AUX_NATIVE_WRITE:
case DP_AUX_I2C_WRITE:
case DP_AUX_I2C_WRITE_STATUS_UPDATE:
ret = phytium_dp_hw_aux_transfer_write(phytium_dp, msg);
DRM_DEBUG_KMS("aux write reply:0x%x ret:0x%lx\n", msg->reply, ret);
break;
case DP_AUX_NATIVE_READ:
case DP_AUX_I2C_READ:
ret = phytium_dp_hw_aux_transfer_read(phytium_dp, msg);
DRM_DEBUG_KMS("aux read ret:0x%lx\n", ret);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static void phytium_dp_aux_init(struct phytium_dp_device *phytium_dp)
{
drm_dp_aux_init(&phytium_dp->aux);
phytium_dp->aux.name = kasprintf(GFP_KERNEL, "dp-%d", phytium_dp->port);
phytium_dp->aux.transfer = phytium_dp_aux_transfer;
}
int phytium_get_encoder_crtc_mask(struct phytium_dp_device *phytium_dp, int port)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int i, mask = 0;
for_each_pipe_masked(priv, i) {
if (i != port)
mask++;
else
break;
}
return BIT(mask);
}
static bool phytium_dp_is_edp(struct phytium_dp_device *phytium_dp, int port)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
if (priv->info.edp_mask & BIT(port))
return true;
else
return false;
}
static bool phytium_edp_init_connector(struct phytium_dp_device *phytium_dp)
{
enum drm_connector_status status;
struct drm_connector *connector = &phytium_dp->connector;
phytium_edp_panel_poweron(phytium_dp);
status = phytium_dp_detect_dpcd(phytium_dp);
if (status == connector_status_disconnected) {
DRM_ERROR("detect edp dpcd failed\n");
return false;
}
phytium_dp->edp_edid = drm_get_edid(connector, &phytium_dp->aux.ddc);
if (!phytium_dp->edp_edid) {
DRM_ERROR("get edp edid failed\n");
return false;
}
connector->status = status;
phytium_dp->max_link_rate = phytium_dp->common_rates[phytium_dp->num_common_rates-1];
phytium_dp->max_link_lane_count = phytium_dp->common_max_lane_count;
phytium_dp->link_rate = phytium_dp->max_link_rate;
phytium_dp->link_lane_count = phytium_dp->max_link_lane_count;
DRM_DEBUG_KMS("common_max_lane_count: %d, common_max_rate:%d\n",
phytium_dp->max_link_lane_count, phytium_dp->max_link_rate);
return true;
}
static void phytium_edp_fini_connector(struct phytium_dp_device *phytium_dp)
{
kfree(phytium_dp->edp_edid);
phytium_dp->edp_edid = NULL;
phytium_edp_panel_poweroff(phytium_dp);
}
int phytium_dp_resume(struct drm_device *drm_dev)
{
struct phytium_dp_device *phytium_dp;
struct drm_encoder *encoder;
int ret = 0;
drm_for_each_encoder(encoder, drm_dev) {
phytium_dp = encoder_to_dp_device(encoder);
if (phytium_dp->is_edp) {
phytium_edp_backlight_off(phytium_dp);
phytium_edp_panel_poweroff(phytium_dp);
}
ret = phytium_dp_hw_init(phytium_dp);
if (ret) {
DRM_ERROR("failed to initialize dp %d\n", phytium_dp->port);
return -EIO;
}
}
return 0;
}
int phytium_dp_init(struct drm_device *dev, int port)
{
struct phytium_display_private *priv = dev->dev_private;
struct phytium_dp_device *phytium_dp = NULL;
int ret, type;
DRM_DEBUG_KMS("%s: port %d\n", __func__, port);
phytium_dp = kzalloc(sizeof(*phytium_dp), GFP_KERNEL);
if (!phytium_dp) {
ret = -ENOMEM;
goto failed_malloc_dp;
}
phytium_dp->dev = dev;
phytium_dp->port = port;
if (IS_PX210(priv)) {
px210_dp_func_register(phytium_dp);
priv->dp_reg_base[port] = PX210_DP_BASE(port);
priv->phy_access_base[port] = PX210_PHY_ACCESS_BASE(port);
} else if (IS_PE220X(priv)) {
pe220x_dp_func_register(phytium_dp);
priv->dp_reg_base[port] = PE220X_DP_BASE(port);
priv->phy_access_base[port] = PE220X_PHY_ACCESS_BASE(port);
}
if (phytium_dp_is_edp(phytium_dp, port)) {
phytium_dp->is_edp = true;
type = DRM_MODE_CONNECTOR_eDP;
phytium_dp_panel_init_backlight_funcs(phytium_dp);
phytium_edp_backlight_off(phytium_dp);
phytium_edp_panel_poweroff(phytium_dp);
} else {
phytium_dp->is_edp = false;
type = DRM_MODE_CONNECTOR_DisplayPort;
}
ret = phytium_dp_hw_init(phytium_dp);
if (ret) {
DRM_ERROR("failed to initialize dp %d\n", phytium_dp->port);
goto failed_init_dp;
}
ret = drm_encoder_init(dev, &phytium_dp->encoder,
&phytium_encoder_funcs,
DRM_MODE_ENCODER_TMDS, "DP %d", port);
if (ret) {
DRM_ERROR("failed to initialize encoder with drm\n");
goto failed_encoder_init;
}
drm_encoder_helper_add(&phytium_dp->encoder, &phytium_encoder_helper_funcs);
phytium_dp->encoder.possible_crtcs = phytium_get_encoder_crtc_mask(phytium_dp, port);
phytium_dp->connector.dpms = DRM_MODE_DPMS_OFF;
phytium_dp->connector.polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT;
ret = drm_connector_init(dev, &phytium_dp->connector, &phytium_connector_funcs,
type);
if (ret) {
DRM_ERROR("failed to initialize connector with drm\n");
goto failed_connector_init;
}
drm_connector_helper_add(&phytium_dp->connector, &phytium_connector_helper_funcs);
drm_connector_attach_encoder(&phytium_dp->connector, &phytium_dp->encoder);
ret = phytium_dp_audio_codec_init(phytium_dp);
if (ret) {
DRM_ERROR("failed to initialize audio codec\n");
goto failed_connector_init;
}
phytium_dp->train_retry_count = 0;
INIT_WORK(&phytium_dp->train_retry_work, phytium_dp_train_retry_work_fn);
drm_connector_register(&phytium_dp->connector);
return 0;
failed_connector_init:
failed_encoder_init:
failed_init_dp:
kfree(phytium_dp);
failed_malloc_dp:
return ret;
}