Files
2026-08-24 10:03:24 +08:00

502 lines
17 KiB
C

// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <command.h>
#include <env.h>
#include <malloc.h>
#include <asm/byteorder.h>
#include <amlogic/clk_measure.h>
#include <linux/delay.h>
#include <image.h>
#include <linux/libfdt_env.h>
#include <linux/arm-smccc.h>
#include <linux/compat.h>
#include <amlogic/media/vout/meson_tx_connector/meson_tx_edid.h>
#include <amlogic/media/vout/meson_tx_connector/meson_tx_hw_opcode.h>
#include <amlogic/media/vout/meson_tx_connector/dptx_common/dptx_common.h>
#include <amlogic/media/vout/meson_tx_connector/dptx_common/dptx_hw_common.h>
#include <amlogic/media/vout/meson_tx_connector/venc/meson_venc.h>
#include "../../drivers/amlogic/media/vout/meson_tx_connector/dptx/dptx_internal.h"
#include "../../drivers/amlogic/media/vout/meson_tx_connector/dptx/dptx_aux_helper.h"
#include "../../drivers/amlogic/media/vout/meson_tx_connector/dptx/dptx_log.h"
#include "../../drivers/amlogic/media/vout/meson_tx_connector/dptx/dptx_link.h"
#define DP_SINK_COUNT 0x200
//#define msleep(i) udelay((i) * 1000)
//static unsigned char edid_raw_buf[512] = {0};
static unsigned char displayid_test[384] = {
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x1C, 0x54, 0x00, 0x28, 0x01, 0x01, 0x01, 0x01,
0x28, 0x1F, 0x01, 0x03, 0x80, 0x3F, 0x24, 0x78, 0xEE, 0x0E, 0x10, 0xAE, 0x51, 0x44, 0xA2, 0x25,
0x0E, 0x50, 0x54, 0x25, 0x4A, 0x00, 0xD1, 0xC0, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x08, 0xE8, 0x00, 0x30, 0xF2, 0x70, 0x5A, 0x80, 0xB0, 0x58,
0x8A, 0x00, 0xB9, 0x88, 0x21, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x30, 0x90, 0x1E,
0x87, 0x3C, 0x00, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x4D,
0x32, 0x38, 0x55, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xFF,
0x00, 0x32, 0x31, 0x34, 0x30, 0x30, 0x42, 0x30, 0x30, 0x35, 0x30, 0x38, 0x30, 0x0A, 0x02, 0x95,
0x02, 0x03, 0x50, 0xF2, 0x4A, 0x61, 0x76, 0x01, 0x03, 0x04, 0x2F, 0x10, 0x3F, 0x5D, 0x20, 0x23,
0x09, 0x07, 0x07, 0x83, 0x01, 0x00, 0x00, 0x67, 0x03, 0x0C, 0x00, 0x10, 0x00, 0x38, 0x44, 0x6D,
0xD8, 0x5D, 0xC4, 0x01, 0x78, 0x88, 0x33, 0x02, 0x30, 0x90, 0xC3, 0x35, 0x0C, 0x6D, 0x1A, 0x00,
0x00, 0x02, 0x0B, 0x30, 0x90, 0xEC, 0x0F, 0x66, 0x1F, 0x66, 0x1F, 0xE3, 0x05, 0xC3, 0x01, 0xE2,
0x0F, 0x03, 0xE3, 0x0E, 0x61, 0x76, 0xE6, 0x06, 0x05, 0x01, 0x66, 0x66, 0x1F, 0xE2, 0x00, 0xD5,
0x56, 0xC2, 0x00, 0xA0, 0xA0, 0xA0, 0x55, 0x50, 0x30, 0x20, 0x35, 0x00, 0xB9, 0x88, 0x21, 0x00,
0x00, 0x1E, 0x56, 0x5E, 0x00, 0xA0, 0xA0, 0xA0, 0x29, 0x50, 0x30, 0x20, 0x35, 0x00, 0xB9, 0x88,
0x21, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6C,
0x70, 0x12, 0x79, 0x00, 0x00, 0x03, 0x01, 0x3C, 0xBA, 0xE9, 0x01, 0x04, 0xFF, 0x0E, 0x4F, 0x00,
0x07, 0x80, 0x1F, 0x00, 0x6F, 0x08, 0x3C, 0x00, 0x09, 0x00, 0x07, 0x00, 0xC1, 0xB0, 0x00, 0x04,
0x7F, 0x07, 0xEF, 0x02, 0xA7, 0x80, 0xCF, 0x00, 0x37, 0x04, 0x61, 0x00, 0x02, 0x80, 0x04, 0x00,
0x2F, 0xE7, 0x00, 0x04, 0xFF, 0x09, 0xB3, 0x00, 0x1B, 0x80, 0x1F, 0x00, 0x9F, 0x05, 0x3B, 0x00,
0x08, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC7, 0x90
};
static int dptx_load_fixed_edid(struct dptx_common *tx_comm)
{
int i;
if (!tx_comm)
return -1;
for (i = 0; i < 384; i++)
tx_comm->base.edid_buf[i] = displayid_test[i];
meson_tx_edid_parse(&tx_comm->base.rxcap, tx_comm->base.edid_buf, tx_comm->base.edid_parse_mask);
return 0;
}
static int do_dptx_init(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
{
dptx_init();
return 0;
}
/*
* cmd usage: dp_tx output idx cmd
* for example: "dp_tx output 0 test_attr" for dptx0
*/
static int dptx_do_output(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
{
struct dptx_common *tx_comm = NULL;
struct tx_timing *timing_list = NULL;
int cnt = 0;
int ret = 0;
u32 idx = 0;
static char test_color_attr[16];
if (argc < 2)
return cmd_usage(cmdtp);
idx = simple_strtoul(argv[1], NULL, 0);
tx_comm = get_dptx_device(idx & 0xFF);
if (!tx_comm)
return CMD_RET_FAILURE;
if (strcmp(argv[2], "bist") == 0) {
//bist
} else if (strncmp(argv[2], "get_modes", 8) == 0) {
cnt = dptx_get_mode_list(tx_comm, &timing_list);
if (cnt > 0) {
/* meson_dptx_mode_probed_add(count, timing_list, connector); */
kfree(timing_list);
timing_list = NULL;
}
if (tx_comm->base.rxcap.disp_id_cap.version)
display_id_cap_print(&tx_comm->base.rxcap.disp_id_cap);
DPTX_INFO("%s get mode list count: %d\n", __func__, cnt);
} else if (strncmp(argv[2], "load_edid", 9) == 0) {
dptx_load_fixed_edid(tx_comm);
DPTX_INFO("%s load and use fixed edid\n", __func__);
} else if (strncmp(argv[2], "test_attr", 9) == 0) {
/* step1: for pxp test color attribute, for example: "y444,10bit,f" */
if (argc >= 4)
strncpy(test_color_attr, argv[3], sizeof(test_color_attr) - 1);
else
strcpy(test_color_attr, "rgb,8bit");
test_color_attr[15] = '\0';
DPTX_INFO("set test_color_attr: %s\n", test_color_attr);
} else if (strncmp(argv[2], "colorimetry", 11) == 0) {
u32 colorimetry = 0;
if (argc < 4) {
DPTX_ERROR("%s invalid colorimetry\n", __func__);
return CMD_RET_USAGE;
}
colorimetry = simple_strtoul(argv[3], NULL, 0);
tx_comm->base.sw_fmt_para.colorimetry = colorimetry & 0xFF;
DPTX_INFO("set colorimetry: %d\n", colorimetry);
} else if (strncmp(argv[2], "test_timing_idx", 15) == 0) {
/* step2 build format para with test_attr and test_mode */
const struct tx_timing *match_timing = NULL;
struct tx_timing *tmp_timing = NULL;
struct meson_tx_format_para *sw_para = NULL;
struct dptx_hw_fmt_para *hw_para = NULL;
enum hdmi_color_depth cd = COLORDEPTH_24B;
enum hdmi_colorspace cs = HDMI_COLORSPACE_RGB;
enum hdmi_quantization_range cr = HDMI_QUANTIZATION_RANGE_LIMITED;
u32 timing_idx = 0;
if (argc < 4) {
DPTX_ERROR("%s invalid timing mode\n", __func__);
return CMD_RET_USAGE;
}
timing_idx = simple_strtoul(argv[3], NULL, 0);
DPTX_INFO("Using timing_idx: %d\n", timing_idx);
/* return timing in mode list, not check tx/rx cap */
match_timing = meson_tx_mode_index_to_timing(timing_idx);
if (!match_timing) {
DPTX_ERROR("no timing with timing_idx(%d)\n", timing_idx);
return CMD_RET_FAILURE;
}
tmp_timing = kzalloc(sizeof(*tmp_timing), GFP_KERNEL);
if (!tmp_timing)
return CMD_RET_FAILURE;
memcpy(tmp_timing, match_timing, sizeof(struct tx_timing));
if (strstr(test_color_attr, "6bit"))
cd = COLORDEPTH_18B;
else if (strstr(test_color_attr, "8bit"))
cd = COLORDEPTH_24B;
else if (strstr(test_color_attr, "10bit"))
cd = COLORDEPTH_30B;
else if (strstr(test_color_attr, "12bit"))
cd = COLORDEPTH_36B;
else
cd = COLORDEPTH_24B;
if (strstr(test_color_attr, "y444"))
cs = HDMI_COLORSPACE_YUV444;
else if (strstr(test_color_attr, "y422"))
cs = HDMI_COLORSPACE_YUV422;
else if (strstr(test_color_attr, "y420"))
cs = HDMI_COLORSPACE_YUV420;
else
cs = HDMI_COLORSPACE_RGB;
/* "f" means full */
if (strstr(test_color_attr, "f"))
cr = HDMI_QUANTIZATION_RANGE_FULL;
else
cr = HDMI_QUANTIZATION_RANGE_LIMITED;
sw_para = &tx_comm->base.sw_fmt_para;
hw_para = &sw_para->tx_hw_para.dptx_hw_para;
/* build SW/HW format param */
if (meson_tx_format_para_init(&tx_comm->base, tmp_timing, 0, cs, cd, cr, sw_para)) {
DPTX_ERROR("%s build SW para failed\n", __func__);
kfree(tmp_timing);
return CMD_RET_FAILURE;
}
if (dptx_calc_hw_fmt_para(&tx_comm->base, sw_para, hw_para)) {
DPTX_ERROR("%s build HW para failed\n", __func__);
kfree(tmp_timing);
return CMD_RET_FAILURE;
}
DPTX_INFO("%s build test format: %s cs[%d] cd[%d] cr[%d]\n",
__func__, tmp_timing->name, cs, cd, cr);
kfree(tmp_timing);
} else if (strncmp(argv[2], "clk_path", 8) == 0) {
int clk_path;
if (argc < 4) {
DPTX_ERROR("%s invalid clk_path\n", __func__);
return CMD_RET_USAGE;
}
clk_path = simple_strtoul(argv[3], NULL, 0);
DPTX_INFO("Using val: %d\n", clk_path);
/*
* step2.2 build clk path
* bit[3:0]: venc_idx, bit[7:4]: hdmi_if_idx
*/
meson_tx_build_clk_param(&tx_comm->base, &tx_comm->base.sw_fmt_para,
clk_path & 0xF, (clk_path >> 4) & 0xF);
DPTX_INFO("%s build clk_path: enc_idx: %x, hdmi_if_idx: %x\n",
__func__, clk_path & 0xF, (clk_path >> 4) & 0xF);
} else if (strncmp(argv[2], "pre_enable", 10) == 0) {
/* step3: dptx_pre_mode_enable */
dptx_hw_cntl(tx_comm->base.tx_hw_base, MODE_FLOW_PRE_ENABLE_MODE,
&tx_comm->base.sw_fmt_para, NULL);
DPTX_INFO("%s dptx_pre_mode_enable\n", __func__);
} else if (strncmp(argv[2], "force_pattern", 13) == 0) {
int force_pattern;
if (argc < 4) {
DPTX_ERROR("%s force tps pattern\n", __func__);
return CMD_RET_USAGE;
}
force_pattern = simple_strtoul(argv[3], NULL, 0);
tx_comm->hw_comm->force_tps_pattern = force_pattern;
DPTX_INFO("%s force training pattern: 0x%x\n", __func__, force_pattern);
} else if (strncmp(argv[2], "link_train", 10) == 0) {
/* step4: link training */
ret = dptx_link_training(tx_comm->link_train);
DPTX_INFO("%s link training: %s\n", __func__, ret == 0 ? "pass" : "failed");
} else if (strncmp(argv[2], "lt_timeout_ms", 13) == 0) {
int timeout_ms;
if (argc < 4) {
DPTX_ERROR("%s invalid lt_timeout_ms\n", __func__);
return CMD_RET_USAGE;
}
timeout_ms = simple_strtoul(argv[3], NULL, 0);
tx_comm->link_train->timeout_ms = timeout_ms;
DPTX_INFO("%s dptx link train timeout: %dms\n", __func__, timeout_ms);
} else if (strncmp(argv[2], "lt_force_lr", 11) == 0) {
int force_lr;
if (argc < 4) {
DPTX_ERROR("%s invalid lt_force_lr\n", __func__);
return CMD_RET_USAGE;
}
force_lr = simple_strtoul(argv[3], NULL, 0);
tx_comm->link_train->force_lr = force_lr;
DPTX_INFO("%s force link rate: 0x%x\n", __func__, force_lr);
} else if (strncmp(argv[2], "lt_force_lc", 11) == 0) {
int force_lc;
if (argc < 4) {
DPTX_ERROR("%s invalid lt_force_lc\n", __func__);
return 0;
}
force_lc = simple_strtoul(argv[3], NULL, 0);
tx_comm->link_train->force_lc = force_lc;
DPTX_INFO("%s force lane count: %d\n", __func__, force_lc);
} else if (strncmp(argv[2], "vid_clk_sync", 12) == 0) {
int vid_clk_sync_mode;
if (argc < 3) {
DPTX_ERROR("%s invalid vid_clk_sync\n", __func__);
return CMD_RET_USAGE;
}
vid_clk_sync_mode = simple_strtoul(argv[3], NULL, 0);
tx_comm->hw_comm->vid_clk_sync_mode = !!vid_clk_sync_mode;
DPTX_INFO("%s set vid_clk_sync_mode: %d\n", __func__, !!vid_clk_sync_mode);
} else if (strncmp(argv[2], "video_mode_set", 14) == 0) {
/* step5: set VPU format & CORE */
dptx_hw_cntl(tx_comm->base.tx_hw_base, MODE_FLOW_ENABLE_MODE, &tx_comm->base.sw_fmt_para, NULL);
DPTX_INFO("%s dptx_mode_enable\n", __func__);
} else if (strncmp(argv[2], "venc_index_set", 14) == 0) {
u32 venc_index;
if (argc < 4) {
DPTX_ERROR("%s invalid venc_index\n", __func__);
return CMD_RET_USAGE;
}
venc_index = simple_strtoul(argv[3], NULL, 0);
tx_comm->hw_comm->enc_index = venc_index;
DPTX_INFO("%s set venc index = %d\n", __func__, tx_comm->hw_comm->enc_index);
} else if (strncmp(argv[2], "venc_type_set", 13) == 0) {
u32 venc_type;
if (argc < 4) {
DPTX_ERROR("%s invalid venc_type\n", __func__);
return CMD_RET_USAGE;
}
venc_type = simple_strtoul(argv[3], NULL, 0);
if (venc_type == 0)
tx_comm->hw_comm->enc_type = VENC_ENCP;
else if (venc_type == 1)
tx_comm->hw_comm->enc_type = VENC_ENCL;
DPTX_INFO("%s set venc type = %d\n", __func__, tx_comm->hw_comm->enc_type);
} else if (strncmp(argv[2], "venc_set", 8) == 0) {
meson_venc_mode_set(tx_comm->hw_comm->enc_index, tx_comm->hw_comm->enc_type,
VENC_BIST_PTTN_OFF, &tx_comm->base.sw_fmt_para);
DPTX_INFO("%s venc mode set done\n", __func__);
} else if (strncmp(argv[2], "venc_bist_set", 13) == 0) {
enum venc_bist_type venc_bist = VENC_BIST_PTTN_OFF;
if (argc < 4) {
DPTX_ERROR("%s invalid venc_bist pattern\n", __func__);
return CMD_RET_USAGE;
}
venc_bist = simple_strtoul(argv[3], NULL, 0);
meson_venc_bist_mode_set(tx_comm->hw_comm->enc_index,
tx_comm->hw_comm->enc_type, venc_bist);
DPTX_INFO("%s venc bist set done\n", __func__);
} else if (strncmp(argv[2], "venc_disable", 12) == 0) {
meson_venc_mode_disable(tx_comm->hw_comm->enc_index, tx_comm->hw_comm->enc_type);
DPTX_INFO("%s venc disable done\n", __func__);
}
return CMD_RET_SUCCESS;
}
static int dptx_do_debug(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
{
struct dptx_common *tx_comm = NULL;
int cnt = 0;
int ret = 0;
u32 idx = 0;
u32 addr = 0;
u32 len = 0;
if (argc < 2)
return cmd_usage(cmdtp);
idx = simple_strtoul(argv[1], NULL, 0);
tx_comm = get_dptx_device(idx & 0xFF);
if (!tx_comm)
return CMD_RET_FAILURE;
if (strncmp(argv[2], "aux", 3) == 0) {
if (argc < 4) {
DPTX_ERROR("%s invalid aux cmd param\n", __func__);
return CMD_RET_USAGE;
}
if (strncmp(argv[3], "dpcd", 4) == 0) {
ret = dptx_aux_read_dpcd_caps(tx_comm->tx_aux,
tx_comm->base.dpcd_buf, sizeof(tx_comm->base.dpcd_buf));
if (ret < 0) {
DPTX_INFO("read dpcd cap failed, ret%d\n", ret);
} else {
for (cnt = 0; cnt < ARRAY_SIZE(tx_comm->base.dpcd_buf); cnt++)
DPTX_INFO("aux[%d] = 0x%x\n", cnt,
tx_comm->base.dpcd_buf[cnt]);
}
} else if (strncmp(argv[3], "rd", 2) == 0) {
if (argc < 5) {
DPTX_ERROR("%s invalid aux addr\n", __func__);
return CMD_RET_USAGE;
}
addr = simple_strtoul(argv[4], NULL, 16);
if (argc < 6) {
DPTX_ERROR("%s invalid aux read len\n", __func__);
return CMD_RET_USAGE;
}
len = simple_strtoul(argv[5], NULL, 16);
DPTX_INFO("aux read offset: 0x%x, size: 0x%x\n", addr, len);
ret = dptx_aux_read_dpcd(tx_comm->tx_aux, addr,
tx_comm->base.dpcd_buf, len);
if (ret < 0) {
DPTX_INFO("read dpcd cap failed, ret: %d\n", ret);
} else {
DPTX_INFO("read dpcd ret length: %d\n", ret);
for (cnt = 0; cnt < ret; cnt++)
DPTX_INFO("aux[%d] = 0x%x\n", cnt,
tx_comm->base.dpcd_buf[cnt]);
}
}
}
return CMD_RET_SUCCESS;
}
/*
* cmd usage: dp_tx get_parse_edid idx
* for example: dp_tx get_parse_edid 0 for dptx0
*/
static int do_dptx_get_parse_edid(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
{
struct dptx_common *tx_comm = NULL;
int ret = 0;
int i;
u32 idx = 0;
if (argc < 2)
return -1;
DPTX_INFO("%s\n", __func__);
idx = simple_strtoul(argv[1], NULL, 0);
tx_comm = get_dptx_device(idx & 0xFF);
if (!tx_comm)
return -1;
ret = dptx_aux_read_dpcd(tx_comm->tx_aux, DP_SINK_COUNT,
tx_comm->link_sink_status,
sizeof(tx_comm->link_sink_status));
if (ret < 0)
DPTX_ERROR("read dpcd link/sink status failed:%d\n", ret);
ret = dptx_aux_read_dpcd_caps(tx_comm->tx_aux,
tx_comm->base.dpcd_buf, sizeof(tx_comm->base.dpcd_buf));
if (ret < 0)
DPTX_INFO("read dpcd cap failed\n");
for (i = 0; i < EDID_READ_MAX_RETRY; i++) {
ret = dptx_aux_read_edid_data(tx_comm->tx_aux, tx_comm->base.edid_buf,
sizeof(tx_comm->base.edid_buf));
if (ret < 0)
DPTX_INFO("read edid failed\n");
else
break;
}
meson_tx_edid_parse(&tx_comm->base.rxcap, tx_comm->base.edid_buf, tx_comm->base.edid_parse_mask);
return 0;
}
/*
* cmd usage: dp_tx hpd idx
* for example: dp_tx hpd 0 for dptx0, dp_tx dptx_hpd 1 for dptx1
*/
int do_dptx_hpd_detect(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
{
struct dptx_common *tx_comm = NULL;
u32 idx = 0;
if (argc < 2)
return 0;
idx = simple_strtoul(argv[1], NULL, 0);
tx_comm = get_dptx_device(idx & 0xFF);
if (!tx_comm)
return 0;
/* TODO gpio and need pinmux */
tx_comm->base.hpd_state = 1;
DPTX_INFO("dptx hpd state = %d\n", tx_comm->base.hpd_state);
return 1;
}
static cmd_tbl_t cmd_dptx_sub[] = {
U_BOOT_CMD_MKENT(init, 2, 1, do_dptx_init, "", ""),
U_BOOT_CMD_MKENT(hpd, 2, 1, do_dptx_hpd_detect, "", ""),
U_BOOT_CMD_MKENT(output, 4, 1, dptx_do_output, "", ""),
U_BOOT_CMD_MKENT(get_parse_edid, 2, 1, do_dptx_get_parse_edid, "", ""),
U_BOOT_CMD_MKENT(debug, 6, 1, dptx_do_debug, "", ""),
};
static int do_dptx(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
{
cmd_tbl_t *c;
if (argc < 2)
return cmd_usage(cmdtp);
argc--;
argv++;
c = find_cmd_tbl(argv[0], &cmd_dptx_sub[0], ARRAY_SIZE(cmd_dptx_sub));
if (c)
return c->cmd(cmdtp, flag, argc, argv);
else
return cmd_usage(cmdtp);
}
U_BOOT_CMD(dp_tx, CONFIG_SYS_MAXARGS, 0, do_dptx,
"DPTX sub-system",
"dptx version:20251210\n"
"dp_tx hpd dp_core_idx\n"
" Detect dprx plug-in for dptx core of idx\n"
"dp_tx get_parse_edid dp_core_idx\n"
" read & parse EDID of dprx connected to dptx core of idx\n"
"dptx output dp_core_idx cmd\n"
" set output SW/HW param\n"
);