blob: be7f92cc862022b66601b395a0c235f9476d5cf7 [file] [log] [blame]
/*
* Copyright 2017-2018 NXP
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/clk.h>
#include <linux/kernel.h>
#include <drm/drm_dp_helper.h>
#ifdef DEBUG_FW_LOAD
#include "mhdp_firmware.h"
#endif
#include "imx-hdp.h"
#include "imx-hdmi.h"
#include "imx-dp.h"
int dp_phy_init(state_struct *state, struct drm_display_mode *mode, int format,
int color_depth)
{
struct imx_hdp *hdp = state_to_imx_hdp(state);
int max_link_rate = hdp->link_rate;
int num_lanes = 4;
int ret;
/* reset phy */
imx_hdp_call(hdp, phy_reset, hdp->ipcHndl, NULL, 0);
/* PHY initialization while phy reset pin is active */
AFE_init(state, num_lanes, (ENUM_AFE_LINK_RATE)max_link_rate);
DRM_INFO("AFE_init\n");
/* In this point the phy reset should be deactivated */
imx_hdp_call(hdp, phy_reset, hdp->ipcHndl, NULL, 1);
DRM_INFO("deasserted reset\n");
/* PHY power set */
AFE_power(state, num_lanes, (ENUM_AFE_LINK_RATE)max_link_rate);
DRM_INFO("AFE_power exit\n");
/* Video off */
ret = CDN_API_DPTX_SetVideo_blocking(state, 0);
DRM_INFO("CDN_API_DPTX_SetVideo_blocking (ret = %d)\n", ret);
return true;
}
#ifdef DEBUG
void print_header(void)
{
/* "0x00000000: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f"*/
DRM_INFO(" : 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n"
);
DRM_INFO("-----------------------------------------------------------\n"
);
}
static void print_bytes(unsigned int addr, unsigned char *buf, unsigned int size)
{
int i, index = 0;
char line[160];
if (((size + 11) * 3) > sizeof(line))
return;
index += sprintf(line, "0x%08x:", addr);
for (i = 0; i < size; i++)
index += sprintf(&line[index], " %02x", buf[i]);
DRM_INFO("%s\n", line);
}
static int dump_dpcd(state_struct *state)
{
int ret;
DPTX_Read_DPCD_response resp_dpcd;
print_header();
ret = CDN_API_DPTX_Read_DPCD_blocking(state, 0x10, 0x0, &resp_dpcd,
CDN_BUS_TYPE_APB);
if (ret) {
DRM_INFO("_debug: function returned with status %d\n", ret);
return -1;
}
print_bytes(resp_dpcd.addr, resp_dpcd.buff, resp_dpcd.size);
ret = CDN_API_DPTX_Read_DPCD_blocking(state, 0x10, 0x100, &resp_dpcd,
CDN_BUS_TYPE_APB);
if (ret) {
DRM_INFO("_debug: function returned with status %d\n", ret);
return -1;
}
print_bytes(resp_dpcd.addr, resp_dpcd.buff, resp_dpcd.size);
ret = CDN_API_DPTX_Read_DPCD_blocking(state, 0x10, 0x110, &resp_dpcd,
CDN_BUS_TYPE_APB);
if (ret) {
DRM_INFO("_debug: function returned with status %d\n", ret);
return -1;
}
print_bytes(resp_dpcd.addr, resp_dpcd.buff, resp_dpcd.size);
ret = CDN_API_DPTX_Read_DPCD_blocking(state, 0x10, 0x200, &resp_dpcd,
CDN_BUS_TYPE_APB);
if (ret) {
DRM_INFO("_debug: function returned with status %d\n", ret);
return -1;
}
print_bytes(resp_dpcd.addr, resp_dpcd.buff, resp_dpcd.size);
ret = CDN_API_DPTX_Read_DPCD_blocking(state, 0x10, 0x210, &resp_dpcd,
CDN_BUS_TYPE_APB);
if (ret) {
DRM_INFO("_debug: function returned with status %d\n", ret);
return -1;
}
print_bytes(resp_dpcd.addr, resp_dpcd.buff, resp_dpcd.size);
ret = CDN_API_DPTX_Read_DPCD_blocking(state, 0x10, 0x220, &resp_dpcd,
CDN_BUS_TYPE_APB);
if (ret) {
DRM_INFO("_debug: function returned with status %d\n", ret);
return -1;
}
print_bytes(resp_dpcd.addr, resp_dpcd.buff, resp_dpcd.size);
ret = CDN_API_DPTX_Read_DPCD_blocking(state, 0x10, 0x700, &resp_dpcd,
CDN_BUS_TYPE_APB);
if (ret) {
DRM_INFO("_debug: function returned with status %d\n", ret);
return -1;
}
print_bytes(resp_dpcd.addr, resp_dpcd.buff, resp_dpcd.size);
ret = CDN_API_DPTX_Read_DPCD_blocking(state, 0x10, 0x710, &resp_dpcd,
CDN_BUS_TYPE_APB);
if (ret) {
DRM_INFO("_debug: function returned with status %d\n", ret);
return -1;
}
print_bytes(resp_dpcd.addr, resp_dpcd.buff, resp_dpcd.size);
ret = CDN_API_DPTX_Read_DPCD_blocking(state, 0x10, 0x720, &resp_dpcd,
CDN_BUS_TYPE_APB);
if (ret) {
DRM_INFO("_debug: function returned with status %d\n", ret);
return -1;
}
print_bytes(resp_dpcd.addr, resp_dpcd.buff, resp_dpcd.size);
ret = CDN_API_DPTX_Read_DPCD_blocking(state, 0x10, 0x730, &resp_dpcd,
CDN_BUS_TYPE_APB);
if (ret) {
DRM_INFO("_debug: function returned with status %d\n", ret);
return -1;
}
print_bytes(resp_dpcd.addr, resp_dpcd.buff, resp_dpcd.size);
return 0;
}
#endif
static bool dp_check_link_status(state_struct *state, u8 num_lanes)
{
u8 link_status[DP_LINK_STATUS_SIZE];
DPTX_Read_DPCD_response read_resp;
CDN_API_STATUS status;
status = CDN_API_DPTX_Read_DPCD_blocking(state,
DP_LINK_STATUS_SIZE,
DP_LANE0_1_STATUS,
&read_resp,
CDN_BUS_TYPE_APB);
memcpy(link_status, read_resp.buff, DP_LINK_STATUS_SIZE);
if (status != CDN_OK) {
return false;
}
DRM_DEBUG("link status 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n",
link_status[0],link_status[1],link_status[2],
link_status[3],link_status[4],link_status[5]);
/* if link training is requested we should perform it always */
return drm_dp_channel_eq_ok(link_status, num_lanes);
}
static int dp_get_training_status(state_struct *state)
{
uint32_t evt;
uint8_t eventId;
uint8_t HPDevents;
do {
do {
CDN_API_Get_Event(state, &evt);
if (evt != 0)
DRM_DEBUG("_Get_Event %d\n", evt);
} while ((evt & 2) == 0);
CDN_API_DPTX_ReadEvent_blocking(state, &eventId, &HPDevents);
DRM_DEBUG("ReadEvent ID = %d HPD = %d\n", eventId, HPDevents);
switch (eventId) {
case 0x01:
DRM_INFO("INFO: Full link training started\n");
break;
case 0x02:
DRM_INFO("INFO: Fast link training started\n");
break;
case 0x04:
DRM_INFO("INFO: Clock recovery phase finished\n");
break;
case 0x08:
DRM_INFO("INFO: Channel equalization phase finished (this is last part meaning training finished)\n");
break;
case 0x10:
DRM_INFO("INFO: Fast link training finished\n");
break;
case 0x20:
DRM_INFO("ERROR: Clock recovery phase failed\n");
return -1;
case 0x40:
DRM_INFO("ERROR: Channel equalization phase failed\n");
return -1;
case 0x80:
DRM_INFO("ERROR: Fast link training failed\n");
return -1;
default:
DRM_INFO("ERROR: Invalid ID:%x\n", eventId);
return -1;
}
} while (eventId != 0x08 && eventId != 0x10);
return 0;
}
#define aux_to_hdp(x) container_of(x, struct imx_hdp, aux)
/*
* This function only implements native DPDC reads and writes
*/
static ssize_t dp_aux_transfer(struct drm_dp_aux *aux,
struct drm_dp_aux_msg *msg)
{
struct imx_hdp *hdp = aux_to_hdp(aux);
bool native = msg->request & (DP_AUX_NATIVE_WRITE & DP_AUX_NATIVE_READ);
CDN_API_STATUS status;
DRM_DEBUG("\n");
DRM_INFO("%s() msg->request 0x%x msg->size 0x%x\n",
__func__, msg->request, (unsigned int)msg->size);
/* Ignore address only message */
if ((msg->size == 0) || (msg->buffer == NULL)) {
msg->reply = native ?
DP_AUX_NATIVE_REPLY_ACK : DP_AUX_I2C_REPLY_ACK;
return msg->size;
}
if (!native) {
pr_err("%s: only native messages supported\n",
__func__);
return -EINVAL;
}
/* msg sanity check */
if (msg->size > DP_AUX_MAX_PAYLOAD_BYTES) {
pr_err("%s: invalid msg: size(%zu), request(%x)\n",
__func__, msg->size, (unsigned int)msg->request);
return -EINVAL;
}
if (msg->request == DP_AUX_NATIVE_WRITE) {
DPTX_Write_DPCD_response write_resp;
status = CDN_API_DPTX_Write_DPCD_blocking(&hdp->state,
msg->size,
msg->address,
(u8 *)msg->buffer,
&write_resp,
CDN_BUS_TYPE_APB);
if (status != CDN_OK)
return -EIO;
/* fixme: is this right? */
//return msg->size;
}
if (msg->request == DP_AUX_NATIVE_READ) {
DPTX_Read_DPCD_response read_resp;
status = CDN_API_DPTX_Read_DPCD_blocking(&hdp->state,
msg->size,
msg->address,
&read_resp,
CDN_BUS_TYPE_APB);
if (status != CDN_OK)
return -EIO;
memcpy(msg->buffer, read_resp.buff, read_resp.size);
msg->reply = DP_AUX_NATIVE_REPLY_ACK;
#ifdef DEBUG
print_bytes(read_resp.addr, read_resp.buff, read_resp.size);
#endif
return read_resp.size;
}
return 0;
}
/* Max Link Rate: 06h (1.62Gbps), 0Ah (2.7Gbps), 14h (5.4Gbps),
* 1Eh (8.1Gbps)--N/A
*/
void dp_mode_set(state_struct *state,
struct drm_display_mode *mode,
int format,
int color_depth,
int max_link_rate)
{
struct imx_hdp *hdp = state_to_imx_hdp(state);
int ret;
u8 training_retries = 10, training_restarts = 10;
/* Set Host capabilities */
/* Number of lanes and SSC */
u8 num_lanes = 4;
u8 ssc = 0;
u8 scrambler = 0;
/* Max voltage swing */
u8 max_vswing = 3;
u8 force_max_vswing = 0;
/* Max pre-emphasis */
u8 max_preemph = 2;
u8 force_max_preemph = 0;
/* Supported test patterns mask */
u8 supp_test_patterns = 0x0F;
/* AUX training? */
u8 no_aux_training = 0;
/* Lane mapping */
u8 lane_mapping = hdp->dp_lane_mapping;
/* Extended Host capabilities */
u8 ext_host_cap = 1;
/* Bits per sub-pixel */
u8 bits_per_subpixel = 8;
/* Stereoscopic video */
STEREO_VIDEO_ATTR stereo = 0;
/* B/W Balance Type: 0 no data, 1 IT601, 2 ITU709 */
BT_TYPE bt_type = 0;
/* Transfer Unit */
u8 transfer_unit = 64;
VIC_SYMBOL_RATE sym_rate;
u8 link_rate = RATE_1_6;
struct drm_dp_link link;
#ifdef DEBUG
S_LINK_STAT rls;
#endif
char linkid[6];
DRM_INFO("dp_mode_set()\n");
ret = drm_dp_downstream_id(&hdp->aux, linkid);
if (ret < 0) {
DRM_INFO("Failed to Get DP link ID: %d\n", ret);
return;
}
DRM_INFO("DP link id: %s, 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n",
linkid, linkid[0], linkid[1], linkid[2], linkid[3], linkid[4],
linkid[5]);
/* Check dp link */
ret = drm_dp_link_probe(&hdp->aux, &link);
if (ret < 0) {
DRM_INFO("Failed to probe DP link: %d\n", ret);
return;
}
DRM_INFO("DP revision: 0x%x\n", link.revision);
DRM_INFO("DP rate: %d Mbps\n", link.rate/100);
DRM_INFO("DP number of lanes: %d\n", link.num_lanes);
DRM_INFO("DP capabilities: 0x%lx\n", link.capabilities);
/* always use the number of lanes from the display*/
num_lanes = link.num_lanes;
/* Use the lower link rate if dp_link_rate is set */
if (hdp->dp_link_rate != 0) {
link_rate = min(hdp->dp_link_rate,
(u32)(drm_dp_link_rate_to_bw_code(link.rate)));
DRM_INFO("DP actual link rate: 0x%x\n", link_rate);
hdp->link_rate = link_rate;
/* need change the link rate */
hdp->ops->phy_init(state,
mode,
format,
color_depth);
}
if (hdp->is_edp) {
/* use the eDP supported rates */
switch (link_rate) {
case AFE_LINK_RATE_1_6:
sym_rate = RATE_1_6;
break;
case AFE_LINK_RATE_2_1:
sym_rate = RATE_2_1;
break;
case AFE_LINK_RATE_2_4:
sym_rate = RATE_2_4;
break;
case AFE_LINK_RATE_2_7:
sym_rate = RATE_2_7;
break;
case AFE_LINK_RATE_3_2:
sym_rate = RATE_3_2;
break;
case AFE_LINK_RATE_4_3:
sym_rate = RATE_4_3;
break;
case AFE_LINK_RATE_5_4:
sym_rate = RATE_5_4;
break;
/*case AFE_LINK_RATE_8_1: sym_rate = RATE_8_1; break; */
default:
sym_rate = RATE_1_6;
}
} else {
switch (link_rate) {
case 0x0a:
sym_rate = RATE_2_7;
break;
case 0x14:
sym_rate = RATE_5_4;
break;
default:
sym_rate = RATE_1_6;
}
}
ret = CDN_API_DPTX_SetHostCap_blocking(state,
link_rate,
(num_lanes & 0x7) | ((ssc & 1) << 3) | ((scrambler & 1) << 4),
(max_vswing & 0x3) | ((force_max_vswing & 1) << 4),
(max_preemph & 0x3) | ((force_max_preemph & 1) << 4),
supp_test_patterns,
no_aux_training, /* fast link training */
lane_mapping,
ext_host_cap
);
DRM_INFO("CDN_API_DPTX_SetHostCap_blocking (ret = %d)\n", ret);
ret = CDN_API_DPTX_Set_VIC_blocking(state,
mode,
bits_per_subpixel,
num_lanes,
sym_rate,
format,
stereo,
bt_type,
transfer_unit
);
DRM_INFO("CDN_API_DPTX_Set_VIC_blocking (ret = %d)\n", ret);
training_restarts=5;
do {
do {
ret = CDN_API_DPTX_TrainingControl_blocking(state, 1);
DRM_INFO("CDN_API_DPTX_TrainingControl_* (ret = %d) start\n",
ret);
if ((dp_get_training_status(state) == 0) /*&&
dp_check_link_status(state, num_lanes)*/)
break;
training_retries--;
ret = CDN_API_DPTX_TrainingControl_blocking(state, 0);
DRM_INFO("CDN_API_DPTX_TrainingControl_* (ret = %d) stop\n",
ret);
udelay(1000);
} while (training_retries > 0);
udelay(1000);
if (dp_check_link_status(state, num_lanes) == true) {
DRM_INFO("Link is good - Training complete\n");
break;
} else {
DRM_INFO("Link is bad - need to restart training\n");
training_restarts--;
training_retries = 20;
ret = CDN_API_DPTX_TrainingControl_blocking(state, 0);
DRM_INFO("CDN_API_DPTX_TrainingControl_* (ret = %d) stop\n",
ret);
udelay(1000);
}
} while (training_restarts > 0);
DRM_INFO("dp_check_link_status %d\n", dp_check_link_status(state, num_lanes));
/* Set video on */
ret = CDN_API_DPTX_SetVideo_blocking(state, 1);
DRM_INFO("CDN_API_DPTX_SetVideo_blocking (ret = %d)\n", ret);
udelay(1000);
#ifdef DEBUG
ret = CDN_API_DPTX_ReadLinkStat_blocking(state, &rls);
DRM_INFO("INFO: Get Read Link Status (ret = %d resp: rate: %d, lanes: %d, vswing 0..3: %d %d %d, preemp 0..3: %d %d %d\n",
ret, rls.rate, rls.lanes,
rls.swing[0], rls.swing[1], rls.swing[2],
rls.preemphasis[0], rls.preemphasis[1],
rls.preemphasis[2]);
dump_dpcd(state);
#endif
}
int dp_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len)
{
DPTX_Read_EDID_response edidResp;
state_struct *state = data;
CDN_API_STATUS ret = CDN_ERROR_NOT_SUPPORTED;
if (buf == NULL) {
return -EINVAL;
}
memset(&edidResp, 0, sizeof(edidResp));
switch (block) {
case 0:
ret = CDN_API_DPTX_Read_EDID_blocking(state, 0, 0, &edidResp);
break;
case 1:
ret = CDN_API_DPTX_Read_EDID_blocking(state, 0, 1, &edidResp);
break;
case 2:
ret = CDN_API_DPTX_Read_EDID_blocking(state, 1, 0, &edidResp);
break;
case 3:
ret = CDN_API_DPTX_Read_EDID_blocking(state, 1, 1, &edidResp);
break;
default:
DRM_WARN("EDID block %x read not support\n", block);
}
DRM_INFO("dp_get_edid_block (ret = %d) block %d\n", ret, block);
if (ret == CDN_OK) {
memcpy(buf, edidResp.buff, 128);
return 0;
}
memset(buf, 0, 128);
return -EIO;
}
int dp_get_hpd_state(state_struct *state, u8 *hpd)
{
int ret;
ret = CDN_API_DPTX_GetHpdStatus_blocking(state, hpd);
return ret;
}
void dp_phy_pix_engine_reset_t28hpc(state_struct *state)
{
GENERAL_Read_Register_response regresp;
CDN_API_General_Read_Register_blocking(state, ADDR_SOURCE_CAR +
(SOURCE_HDTX_CAR << 2),
&regresp);
CDN_API_General_Write_Register_blocking(state, ADDR_SOURCE_CAR +
(SOURCE_HDTX_CAR << 2),
regresp.val & 0xFD);
CDN_API_General_Write_Register_blocking(state, ADDR_SOURCE_CAR +
(SOURCE_HDTX_CAR << 2),
regresp.val);
}
int dp_phy_init_t28hpc(state_struct *state,
struct drm_display_mode *mode,
int format,
int color_depth)
{
struct imx_hdp *hdp = state_to_imx_hdp(state);
int max_link_rate = hdp->link_rate;
int num_lanes = 4;
int ret;
u8 lane_mapping = hdp->dp_lane_mapping;
/* reset phy */
imx_hdp_call(hdp, phy_reset, 0, &hdp->mem, 0);
DRM_INFO("asserted HDP PHY reset\n");
dp_phy_pix_engine_reset_t28hpc(state);
DRM_INFO("pixel engine reset\n");
/* Line swaping */
CDN_API_General_Write_Register_blocking(state,
ADDR_SOURCD_PHY +
(LANES_CONFIG << 2),
0x00400000 | lane_mapping);
DRM_INFO("CDN_*_Write_Register_blocking ... setting LANES_CONFIG %x\n",
lane_mapping);
/* PHY initialization while phy reset pin is active */
afe_init_t28hpc(state, num_lanes, (ENUM_AFE_LINK_RATE)max_link_rate);
DRM_INFO("AFE_init\n");
/* In this point the phy reset should be deactivated */
imx_hdp_call(hdp, phy_reset, 0, &hdp->mem, 1);
DRM_INFO("deasserted HDP PHY reset\n");
/* PHY power set */
afe_power_t28hpc(state, num_lanes, (ENUM_AFE_LINK_RATE)max_link_rate);
DRM_INFO("AFE_power exit\n");
/* Video off */
ret = CDN_API_DPTX_SetVideo_blocking(state, 0);
DRM_INFO("CDN_API_DPTX_SetVideo_blocking (ret = %d)\n", ret);
return true;
}
int dp_aux_init(state_struct *state,
struct device *dev)
{
struct imx_hdp *hdp = state_to_imx_hdp(state);
int ret;
hdp->aux.name = "imx_dp_aux";
hdp->aux.dev = dev;
hdp->aux.transfer = dp_aux_transfer;
ret = drm_dp_aux_register(&hdp->aux);
return ret;
}
int dp_aux_destroy(state_struct *state)
{
struct imx_hdp *hdp = state_to_imx_hdp(state);
drm_dp_aux_unregister(&hdp->aux);
return 0;
}