blob: 22906cbd6d4d2cbb034e20e0d3528bbc273831cd [file] [log] [blame]
#include <common.h>
#include <linux/stddef.h>
#include <amlogic/media/vout/hdmitx.h>
#define CEA_DATA_BLOCK_COLLECTION_ADDR_1StP 0x04
#define VIDEO_TAG 0x40
#define AUDIO_TAG 0x20
#define VENDOR_TAG 0x60
#define SPEAKER_TAG 0x80
#define HDMI_EDID_BLOCK_TYPE_RESERVED 0
#define HDMI_EDID_BLOCK_TYPE_AUDIO 1
#define HDMI_EDID_BLOCK_TYPE_VIDEO 2
#define HDMI_EDID_BLOCK_TYPE_VENDER 3
#define HDMI_EDID_BLOCK_TYPE_SPEAKER 4
#define HDMI_EDID_BLOCK_TYPE_VESA 5
#define HDMI_EDID_BLOCK_TYPE_RESERVED2 6
#define HDMI_EDID_BLOCK_TYPE_EXTENDED_TAG 7
#define EXTENSION_VENDOR_SPECIFIC 0x1
#define EXTENSION_COLORMETRY_TAG 0x5
/* DRM stands for "Dynamic Range and Mastering " */
#define EXTENSION_DRM_TAG 0x6
/* Video Format Preference Data block */
#define EXTENSION_VFPDB_TAG 0xd
#define EXTENSION_Y420_VDB_TAG 0xe
#define EXTENSION_Y420_CMDB_TAG 0xf
#define EDID_DETAILED_TIMING_DES_BLOCK0_POS 0x36
#define EDID_DETAILED_TIMING_DES_BLOCK1_POS 0x48
#define EDID_DETAILED_TIMING_DES_BLOCK2_POS 0x5A
#define EDID_DETAILED_TIMING_DES_BLOCK3_POS 0x6C
/* EDID Descrptor Tag */
#define TAG_PRODUCT_SERIAL_NUMBER 0xFF
#define TAG_ALPHA_DATA_STRING 0xFE
#define TAG_RANGE_LIMITS 0xFD
#define TAG_DISPLAY_PRODUCT_NAME_STRING 0xFC /* MONITOR NAME */
#define TAG_COLOR_POINT_DATA 0xFB
#define TAG_STANDARD_TIMINGS 0xFA
#define TAG_DISPLAY_COLOR_MANAGEMENT 0xF9
#define TAG_CVT_TIMING_CODES 0xF8
#define TAG_ESTABLISHED_TIMING_III 0xF7
#define TAG_DUMMY_DES 0x10
/* retrun 1 valid edid */
static int check_dvi_hdmi_edid_valid(unsigned char *buf)
{
unsigned int chksum = 0;
unsigned int i = 0;
/* check block 0 first 8 bytes */
if ((buf[0] != 0) && (buf[7] != 0))
return 0;
for (i = 1; i < 7; i++) {
if (buf[i] != 0xff)
return 0;
}
/* check block 0 checksum */
for (chksum = 0, i = 0; i < 0x80; i++)
chksum += buf[i];
if ((chksum & 0xff) != 0)
return 0;
if (buf[0x7e] == 0)/* check Extension flag at block 0 */
return 1;
/* check block 1 extension tag */
else if (!((buf[0x80] == 0x2) || (buf[0x80] == 0xf0)))
return 0;
/* check block 1 checksum */
for (chksum = 0, i = 0x80; i < 0x100; i++)
chksum += buf[i];
if ((chksum & 0xff) != 0)
return 0;
/* check block 2 checksum */
if (buf[0x7e] > 1) {
for (chksum = 0, i = 0x100; i < 0x180; i++)
chksum += buf[i];
if ((chksum & 0xff) != 0)
return 0;
}
/* check block 3 checksum */
if (buf[0x7e] > 2) {
for (chksum = 0, i = 0x180; i < 0x200; i++)
chksum += buf[i];
if ((chksum & 0xff) != 0)
return 0;
}
return 1;
}
static void dump_dtd_info(struct dtd *t)
{
return; /* debug only */
printk("%s[%d]\n", __func__, __LINE__);
#define PR(a) pr_info("%s %d\n", #a, t->a)
PR(pixel_clock);
PR(h_active);
PR(h_blank);
PR(v_active);
PR(v_blank);
PR(h_sync_offset);
PR(h_sync);
PR(v_sync_offset);
PR(v_sync);
}
static void Edid_DTD_parsing(struct rx_cap *pRXCap, unsigned char *data)
{
struct hdmi_format_para *para = NULL;
struct dtd *t = &pRXCap->dtd[pRXCap->dtd_idx];
memset(t, 0, sizeof(struct dtd));
t->pixel_clock = data[0] + (data[1] << 8);
t->h_active = (((data[4] >> 4) & 0xf) << 8) + data[2];
t->h_blank = ((data[4] & 0xf) << 8) + data[3];
t->v_active = (((data[7] >> 4) & 0xf) << 8) + data[5];
t->v_blank = ((data[7] & 0xf) << 8) + data[6];
t->h_sync_offset = (((data[11] >> 6) & 0x3) << 8) + data[8];
t->h_sync = (((data[11] >> 4) & 0x3) << 8) + data[9];
t->v_sync_offset = (((data[11] >> 2) & 0x3) << 4) +
((data[10] >> 4) & 0xf);
t->v_sync = (((data[11] >> 0) & 0x3) << 4) + ((data[10] >> 0) & 0xf);
/*
* Special handling of 1080i60hz, 1080i50hz
*/
if ((t->pixel_clock == 7425) && (t->h_active == 1920) &&
(t->v_active == 1080)) {
t->v_active = t->v_active / 2;
t->v_blank = t->v_blank / 2;
}
/*
* Special handling of 480i60hz, 576i50hz
*/
if (((((t->flags) >> 1) & 0x3) == 0) && (t->h_active == 1440)) {
if (t->pixel_clock == 2700) /* 576i50hz */
goto next;
if ((t->pixel_clock - 2700) < 10) /* 480i60hz */
t->pixel_clock = 2702;
next:
t->v_active = t->v_active / 2;
t->v_blank = t->v_blank / 2;
}
/*
* call hdmi_match_dtd_paras() to check t is matched with VIC
*/
para = hdmi_match_dtd_paras(t);
if (para) {
t->vic = para->vic;
pRXCap->preferred_mode = pRXCap->dtd[0].vic; /* Select dtd0 */
if (0) /* debug only */
pr_info("hdmitx: get dtd%d vic: %d\n",
pRXCap->dtd_idx, para->vic);
pRXCap->dtd_idx++;
} else
dump_dtd_info(t);
}
/* parse Sink 4k2k information */
static void hdmitx_edid_4k2k_parse(struct rx_cap *pRXCap, unsigned char *dat,
unsigned size)
{
if ((size > 4) || (size == 0)) {
return;
}
while (size--) {
if (*dat == 1)
pRXCap->VIC[pRXCap->VIC_count] = HDMI_3840x2160p30_16x9;
else if (*dat == 2)
pRXCap->VIC[pRXCap->VIC_count] = HDMI_3840x2160p25_16x9;
else if (*dat == 3)
pRXCap->VIC[pRXCap->VIC_count] = HDMI_3840x2160p24_16x9;
else if (*dat == 4)
pRXCap->VIC[pRXCap->VIC_count] = HDMI_4096x2160p24_256x135;
else
;
dat++;
pRXCap->VIC_count++;
}
}
static void set_vsdb_dc_420_cap(struct rx_cap *pRXCap,
unsigned char *edid_offset)
{
pRXCap->dc_30bit_420 = !!(edid_offset[6] & (1 << 0));
pRXCap->dc_36bit_420 = !!(edid_offset[6] & (1 << 1));
pRXCap->dc_48bit_420 = !!(edid_offset[6] & (1 << 2));
}
static int Edid_ParsingY420VDBBlock(struct rx_cap *pRXCap,
unsigned char *buf)
{
unsigned char tag = 0, ext_tag = 0, data_end = 0;
unsigned int pos = 0;
int i = 0, found = 0;
tag = (buf[pos] >> 5) & 0x7;
data_end = (buf[pos] & 0x1f)+1;
pos++;
ext_tag = buf[pos];
if ((tag != 0x7) || (ext_tag != 0xe))
goto INVALID_Y420VDB;
pos++;
while (pos < data_end) {
if (pRXCap->VIC_count < VIC_MAX_NUM) {
for (i = 0; i < pRXCap->VIC_count; i++) {
if (pRXCap->VIC[i] == buf[pos]) {
pRXCap->VIC[i] =
HDMITX_VIC420_OFFSET + buf[pos];
found = 1;
/* Here we do not break,because
some EDID may have the same
repeated VICs
*/
}
}
if (0 == found) {
pRXCap->VIC[pRXCap->VIC_count] =
HDMITX_VIC420_OFFSET + buf[pos];
pRXCap->VIC_count++;
}
}
pos++;
}
return 0;
INVALID_Y420VDB:
pr_info("[%s] it's not a valid y420vdb!\n", __func__);
return -1;
}
static int Edid_ParsingY420CMDBBlock(struct rx_cap *pRXCap,
unsigned char *buf)
{
/* TODO */
return 0;
}
static int Edid_Y420CMDB_PostProcess(struct rx_cap *pRXCap)
{
/* TODO */
return 0;
}
static int Edid_ParsingVFPDB(struct rx_cap *pRXCap, unsigned char *buf)
{
unsigned int len = buf[0] & 0x1f;
enum hdmi_vic svr = HDMI_unkown;
if (buf[1] != EXTENSION_VFPDB_TAG)
return 0;
if (len < 2)
return 0;
svr = buf[2];
if (((svr >= 1) && (svr <= 127)) ||
((svr >= 193) && (svr <= 253))) {
pRXCap->flag_vfpdb = 1;
pRXCap->preferred_mode = svr;
pr_info("preferred mode 0 srv %d\n", pRXCap->preferred_mode);
return 1;
}
if ((svr >= 129) && (svr <= 144)) {
pRXCap->flag_vfpdb = 1;
pRXCap->preferred_mode = pRXCap->dtd[svr - 129].vic;
pr_info("preferred mode 0 dtd %d\n", pRXCap->preferred_mode);
return 1;
}
return 0;
}
static int hdmitx_edid_block_parse(struct rx_cap *pRXCap,
unsigned char *BlockBuf)
{
unsigned char offset, End;
unsigned char count;
unsigned char tag;
int i, tmp, idx;
unsigned char *vfpdb_offset = NULL;
if (BlockBuf[0] != 0x02)
return -1; /* not a CEA BLOCK. */
End = BlockBuf[2]; /* CEA description. */
pRXCap->native_Mode = BlockBuf[3];
pRXCap->number_of_dtd += BlockBuf[3] & 0xf;
/* bit 5 (YCBCR 4:4:4) = 1 if sink device supports YCBCR 4:4:4
* in addition to RGB;
* bit 4 (YCBCR 4:2:2) = 1 if sink device supports YCBCR 4:2:2
* in addition to RGB
*/
pRXCap->pref_colorspace = BlockBuf[3] & 0x30;
pRXCap->native_VIC = 0xff;
for (offset = 4 ; offset < End ; ) {
tag = BlockBuf[offset] >> 5;
count = BlockBuf[offset] & 0x1f;
switch (tag) {
case HDMI_EDID_BLOCK_TYPE_AUDIO:
offset++;
offset += count;
break;
case HDMI_EDID_BLOCK_TYPE_VIDEO:
offset++;
for (i = 0 ; i < count ; i++) {
unsigned char VIC;
VIC = BlockBuf[offset + i] & (~0x80);
pRXCap->VIC[pRXCap->VIC_count] = VIC;
if (BlockBuf[offset + i] & 0x80)
pRXCap->native_VIC = VIC;
pRXCap->VIC_count++;
}
offset += count;
break;
case HDMI_EDID_BLOCK_TYPE_VENDER:
offset++;
if ((BlockBuf[offset] == 0x03) &&
(BlockBuf[offset+1] == 0x0c) &&
(BlockBuf[offset+2] == 0x00))
pRXCap->IEEEOUI = 0x000c03;
else
goto case_hf;
pRXCap->ColorDeepSupport =
(unsigned long)BlockBuf[offset+5];
pRXCap->Max_TMDS_Clock1 =
(unsigned long)BlockBuf[offset+6];
if (count > 7) {
tmp = BlockBuf[offset+7];
idx = offset + 8;
if (tmp & (1<<6))
idx += 2;
if (tmp & (1<<7))
idx += 2;
if (tmp & (1<<5)) {
idx += 1;
/* valid 4k */
if (BlockBuf[idx] & 0xe0) {
hdmitx_edid_4k2k_parse(
pRXCap,
&BlockBuf[idx + 1],
BlockBuf[idx] >> 5);
}
}
}
goto case_next;
case_hf:
if ((BlockBuf[offset] == 0xd8) &&
(BlockBuf[offset+1] == 0x5d) &&
(BlockBuf[offset+2] == 0xc4))
pRXCap->HF_IEEEOUI = 0xd85dc4;
pRXCap->Max_TMDS_Clock2 = BlockBuf[offset+4];
pRXCap->scdc_present =
!!(BlockBuf[offset+5] & (1 << 7));
pRXCap->scdc_rr_capable =
!!(BlockBuf[offset+5] & (1 << 6));
pRXCap->lte_340mcsc_scramble =
!!(BlockBuf[offset+5] & (1 << 3));
set_vsdb_dc_420_cap(pRXCap,
&BlockBuf[offset]);
case_next:
offset += count; /* ignore the remaind. */
break;
case HDMI_EDID_BLOCK_TYPE_SPEAKER:
offset++;
offset += count;
break;
case HDMI_EDID_BLOCK_TYPE_VESA:
offset++;
offset += count;
break;
case HDMI_EDID_BLOCK_TYPE_EXTENDED_TAG:
{
unsigned char ext_tag = 0;
ext_tag = BlockBuf[offset+1];
switch (ext_tag) {
case EXTENSION_VENDOR_SPECIFIC:
break;
case EXTENSION_COLORMETRY_TAG:
pRXCap->colorimetry_data =
BlockBuf[offset + 2];
break;
case EXTENSION_DRM_TAG:
break;
case EXTENSION_VFPDB_TAG:
/* Just record VFPDB offset address, call Edid_ParsingVFPDB() after DTD
* parsing, in case that
* SVR >=129 and SVR <=144, Interpret as the Kth DTD in the EDID,
* where K = SVR – 128 (for K=1 to 16)
*/
vfpdb_offset = &BlockBuf[offset];
break;
case EXTENSION_Y420_VDB_TAG:
Edid_ParsingY420VDBBlock(pRXCap,
&BlockBuf[offset]);
break;
case EXTENSION_Y420_CMDB_TAG:
Edid_ParsingY420CMDBBlock(pRXCap,
&BlockBuf[offset]);
break;
default:
break;
}
}
offset += count+1;
break;
case HDMI_EDID_BLOCK_TYPE_RESERVED:
offset++;
offset += count;
break;
case HDMI_EDID_BLOCK_TYPE_RESERVED2:
offset++;
offset += count;
break;
default:
break;
}
}
Edid_Y420CMDB_PostProcess(pRXCap);
idx = BlockBuf[3] & 0xf;
for (i = 0; i < idx; i++)
Edid_DTD_parsing(pRXCap, &BlockBuf[BlockBuf[2] + i * 18]);
if (vfpdb_offset)
Edid_ParsingVFPDB(pRXCap, vfpdb_offset);
return 0;
}
/*
* Parsing RAW EDID data from edid to pRXCap
*/
unsigned int hdmi_edid_parsing(unsigned char *EDID_buf, struct rx_cap *pRXCap)
{
int i;
int BlockCount = EDID_buf[126] + 1;
int idx[4];
/* Clear all parsing data */
memset(pRXCap, 0, sizeof(struct rx_cap));
pRXCap->IEEEOUI = 0x000c03; /* Default is HDMI device */
/* If edid data corrupted, no parse */
if (check_dvi_hdmi_edid_valid(EDID_buf) == 0)
return 0;
idx[0] = EDID_DETAILED_TIMING_DES_BLOCK0_POS;
idx[1] = EDID_DETAILED_TIMING_DES_BLOCK1_POS;
idx[2] = EDID_DETAILED_TIMING_DES_BLOCK2_POS;
idx[3] = EDID_DETAILED_TIMING_DES_BLOCK3_POS;
for (i = 0; i < 4; i++) {
if ((EDID_buf[idx[i]]) && (EDID_buf[idx[i] + 1]))
Edid_DTD_parsing(pRXCap, &EDID_buf[idx[i]]);
}
if (BlockCount == 1)
pRXCap->IEEEOUI = 0;
for (i = 1 ; i <= BlockCount ; i++) {
if (EDID_buf[i*128+0] == 0x2)
hdmitx_edid_block_parse(pRXCap, &(EDID_buf[i*128]));
}
/*
* Because DTDs are not able to represent some Video Formats, which can be
* represented as SVDs and might be preferred by Sinks, the first DTD in the
* base EDID data structure and the first SVD in the first CEA Extension can
* differ. When the first DTD and SVD do not match and the total number of
* DTDs defining Native Video Formats in the whole EDID is zero, the first
* SVD shall take precedence.
*/
if (!pRXCap->flag_vfpdb && (pRXCap->preferred_mode != pRXCap->VIC[0]) &&
(pRXCap->number_of_dtd == 0)) {
pr_info("hdmitx: edid: change preferred_mode from %d to %d\n",
pRXCap->preferred_mode, pRXCap->VIC[0]);
pRXCap->preferred_mode = pRXCap->VIC[0];
}
return 1;
}