msm: mdss: hdmi: make edid parser an independent module

Currently only HDMI driver is using EDID parser. But this parse
can be utilized by other MDSS drivers. Make this as an independent
module which can be used by any other MDSS driver for EDID parsing.

Change-Id: I62622af6c27927b7cf0390238f311452c03cd262
Signed-off-by: Ajay Singh Parmar <aparmar@codeaurora.org>
This commit is contained in:
Ajay Singh Parmar 2015-03-10 18:21:11 -07:00 committed by David Keitel
parent 5b0fec9e82
commit 46b34d140b
6 changed files with 486 additions and 329 deletions

View file

@ -38,13 +38,13 @@ mdss-dsi-objs += mdss_dsi_panel.o
mdss-dsi-objs += msm_mdss_io_8974.o
obj-$(CONFIG_FB_MSM_MDSS) += mdss-dsi.o
obj-$(CONFIG_FB_MSM_MDSS) += mdss_panel.o
obj-$(CONFIG_FB_MSM_MDSS) += mdss_hdmi_util.o
obj-$(CONFIG_FB_MSM_MDSS) += mdss_hdmi_edid.o
obj-$(CONFIG_FB_MSM_MDSS_EDP_PANEL) += mdss_edp.o
obj-$(CONFIG_FB_MSM_MDSS_EDP_PANEL) += mdss_edp_aux.o
obj-$(CONFIG_FB_MSM_MDSS) += mdss_io_util.o
obj-$(CONFIG_FB_MSM_MDSS_HDMI_PANEL) += mdss_hdmi_tx.o
obj-$(CONFIG_FB_MSM_MDSS_HDMI_PANEL) += mdss_hdmi_util.o
obj-$(CONFIG_FB_MSM_MDSS_HDMI_PANEL) += mdss_hdmi_edid.o
obj-$(CONFIG_FB_MSM_MDSS_HDMI_PANEL) += mdss_hdmi_hdcp.o
obj-$(CONFIG_FB_MSM_MDSS_HDMI_PANEL) += mdss_hdmi_hdcp2p2.o
obj-$(CONFIG_FB_MSM_MDSS_HDMI_PANEL) += mdss_hdmi_cec.o

View file

@ -15,10 +15,11 @@
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/device.h>
#include "mdss_fb.h"
#include "mdss_hdmi_edid.h"
#define DBC_START_OFFSET 4
#define EDID_DTD_LEN 18
/*
* As per CEA-861-E specification 7.5.2, there can be
* upto 31 bytes following any tag (data block type).
@ -48,9 +49,7 @@
#define HDMI_FORUM_IEEE_OUI 0xD85DC4
/* Support for first 5 EDID blocks */
#define MAX_EDID_BLOCK_SIZE (0x80 * 5)
#define MAX_EDID_READ_RETRY 5
#define MAX_EDID_SIZE (EDID_BLOCK_SIZE * MAX_EDID_BLOCKS)
#define BUFF_SIZE_3D 128
@ -60,6 +59,14 @@
#define REVISION_OFFSET 0x13
#define EDID_REVISION_FOUR 0x04
#define EDID_VENDOR_ID_SIZE 4
#define EDID_IEEE_REG_ID 0x0c03
enum edid_sink_mode {
SINK_MODE_DVI,
SINK_MODE_HDMI
};
enum data_block_types {
RESERVED,
AUDIO_DATA_BLOCK,
@ -111,6 +118,7 @@ struct hdmi_edid_ctrl {
u8 pt_scan_info;
u8 it_scan_info;
u8 ce_scan_info;
u8 cea_blks;
u16 physical_address;
u32 video_resolution; /* selected by user */
u32 sink_mode; /* HDMI or DVI */
@ -122,14 +130,91 @@ struct hdmi_edid_ctrl {
int adb_size;
u8 spkr_alloc_data_block[MAX_SPKR_ALLOC_DATA_BLOCK_SIZE];
int sadb_size;
u8 edid_buf[MAX_EDID_BLOCK_SIZE];
u32 max_pclk_khz;
u8 edid_buf[MAX_EDID_SIZE];
char vendor_id[EDID_VENDOR_ID_SIZE];
struct hdmi_edid_sink_data sink_data;
struct hdmi_edid_init_data init_data;
struct hdmi_edid_sink_caps sink_caps;
};
static int hdmi_edid_reset_parser(struct hdmi_edid_ctrl *edid_ctrl)
{
if (!edid_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
return -EINVAL;
}
/* reset res info read page */
edid_ctrl->page_id = MSM_HDMI_INIT_RES_PAGE;
/* reset sink mode to DVI as default */
edid_ctrl->sink_mode = SINK_MODE_DVI;
/* reset scan info data */
edid_ctrl->pt_scan_info = 0;
edid_ctrl->it_scan_info = 0;
edid_ctrl->ce_scan_info = 0;
/* reset 3d data */
edid_ctrl->present_3d = 0;
/* reset number of cea extension blocks to 0 */
edid_ctrl->cea_blks = 0;
/* reset resolution related sink data */
memset(&edid_ctrl->sink_data, 0, sizeof(edid_ctrl->sink_data));
/* reset audio related data */
memset(edid_ctrl->audio_data_block, 0,
sizeof(edid_ctrl->audio_data_block));
memset(edid_ctrl->spkr_alloc_data_block, 0,
sizeof(edid_ctrl->spkr_alloc_data_block));
edid_ctrl->adb_size = 0;
edid_ctrl->sadb_size = 0;
/* reset new resolution details */
hdmi_reset_resv_timing_info();
return 0;
}
static struct hdmi_edid_ctrl *hdmi_edid_get_ctrl(struct device *dev)
{
struct fb_info *fbi;
struct msm_fb_data_type *mfd;
struct mdss_panel_info *pinfo;
if (!dev) {
pr_err("invlid input\n");
goto error;
}
fbi = dev_get_drvdata(dev);
if (!fbi) {
pr_err("invlid fbi\n");
goto error;
}
mfd = (struct msm_fb_data_type *)fbi->par;
if (!mfd) {
pr_err("invlid mfd\n");
goto error;
}
pinfo = mfd->panel_info;
if (!pinfo) {
pr_err("invlid pinfo\n");
goto error;
}
return pinfo->edid_data;
error:
return NULL;
}
static ssize_t hdmi_edid_sysfs_rda_audio_data_block(struct device *dev,
struct device_attribute *attr, char *buf)
{
@ -137,8 +222,7 @@ static ssize_t hdmi_edid_sysfs_rda_audio_data_block(struct device *dev,
ssize_t ret;
char *data = buf;
struct hdmi_edid_ctrl *edid_ctrl =
hdmi_get_featuredata_from_sysfs_dev(dev, HDMI_TX_FEAT_EDID);
struct hdmi_edid_ctrl *edid_ctrl = hdmi_edid_get_ctrl(dev);
if (!edid_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
@ -178,8 +262,7 @@ static ssize_t hdmi_edid_sysfs_rda_spkr_alloc_data_block(struct device *dev,
ssize_t ret;
char *data = buf;
struct hdmi_edid_ctrl *edid_ctrl =
hdmi_get_featuredata_from_sysfs_dev(dev, HDMI_TX_FEAT_EDID);
struct hdmi_edid_ctrl *edid_ctrl = hdmi_edid_get_ctrl(dev);
if (!edid_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
@ -216,8 +299,7 @@ static ssize_t hdmi_edid_sysfs_rda_modes(struct device *dev,
{
ssize_t ret = 0;
int i;
struct hdmi_edid_ctrl *edid_ctrl =
hdmi_get_featuredata_from_sysfs_dev(dev, HDMI_TX_FEAT_EDID);
struct hdmi_edid_ctrl *edid_ctrl = hdmi_edid_get_ctrl(dev);
if (!edid_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
@ -253,8 +335,7 @@ static ssize_t hdmi_edid_sysfs_wta_res_info(struct device *dev,
{
int rc, page_id;
ssize_t ret = strnlen(buf, PAGE_SIZE);
struct hdmi_edid_ctrl *edid_ctrl =
hdmi_get_featuredata_from_sysfs_dev(dev, HDMI_TX_FEAT_EDID);
struct hdmi_edid_ctrl *edid_ctrl = hdmi_edid_get_ctrl(dev);
if (!edid_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
@ -281,8 +362,7 @@ static ssize_t hdmi_edid_sysfs_rda_res_info(struct device *dev,
u32 i = 0, j, page;
char *buf_dbg = buf;
struct msm_hdmi_mode_timing_info info = {0};
struct hdmi_edid_ctrl *edid_ctrl =
hdmi_get_featuredata_from_sysfs_dev(dev, HDMI_TX_FEAT_EDID);
struct hdmi_edid_ctrl *edid_ctrl = hdmi_edid_get_ctrl(dev);
u32 size_to_write = sizeof(info);
struct disp_mode_info *minfo = NULL;
@ -354,8 +434,7 @@ static ssize_t hdmi_edid_sysfs_rda_audio_latency(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t ret;
struct hdmi_edid_ctrl *edid_ctrl =
hdmi_get_featuredata_from_sysfs_dev(dev, HDMI_TX_FEAT_EDID);
struct hdmi_edid_ctrl *edid_ctrl = hdmi_edid_get_ctrl(dev);
if (!edid_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
@ -374,8 +453,7 @@ static ssize_t hdmi_edid_sysfs_rda_video_latency(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t ret;
struct hdmi_edid_ctrl *edid_ctrl =
hdmi_get_featuredata_from_sysfs_dev(dev, HDMI_TX_FEAT_EDID);
struct hdmi_edid_ctrl *edid_ctrl = hdmi_edid_get_ctrl(dev);
if (!edid_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
@ -394,8 +472,7 @@ static ssize_t hdmi_edid_sysfs_rda_physical_address(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t ret;
struct hdmi_edid_ctrl *edid_ctrl =
hdmi_get_featuredata_from_sysfs_dev(dev, HDMI_TX_FEAT_EDID);
struct hdmi_edid_ctrl *edid_ctrl = hdmi_edid_get_ctrl(dev);
if (!edid_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
@ -413,8 +490,7 @@ static ssize_t hdmi_edid_sysfs_rda_scan_info(struct device *dev,
struct device_attribute *attr, char *buf)
{
ssize_t ret;
struct hdmi_edid_ctrl *edid_ctrl =
hdmi_get_featuredata_from_sysfs_dev(dev, HDMI_TX_FEAT_EDID);
struct hdmi_edid_ctrl *edid_ctrl = hdmi_edid_get_ctrl(dev);
if (!edid_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
@ -436,8 +512,7 @@ static ssize_t hdmi_edid_sysfs_rda_3d_modes(struct device *dev,
int i;
char buff_3d[BUFF_SIZE_3D];
struct hdmi_edid_ctrl *edid_ctrl =
hdmi_get_featuredata_from_sysfs_dev(dev, HDMI_TX_FEAT_EDID);
struct hdmi_edid_ctrl *edid_ctrl = hdmi_edid_get_ctrl(dev);
if (!edid_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
@ -478,16 +553,17 @@ static DEVICE_ATTR(edid_3d_modes, S_IRUGO, hdmi_edid_sysfs_rda_3d_modes, NULL);
static ssize_t hdmi_common_rda_edid_raw_data(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hdmi_edid_ctrl *edid_ctrl =
hdmi_get_featuredata_from_sysfs_dev(dev, HDMI_TX_FEAT_EDID);
u32 size = sizeof(edid_ctrl->edid_buf) < PAGE_SIZE ?
sizeof(edid_ctrl->edid_buf) : PAGE_SIZE;
struct hdmi_edid_ctrl *edid_ctrl = hdmi_edid_get_ctrl(dev);
u32 size;
if (!edid_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
return -EINVAL;
}
size = sizeof(edid_ctrl->edid_buf) < PAGE_SIZE ?
sizeof(edid_ctrl->edid_buf) : PAGE_SIZE;
/* buf can have max size of PAGE_SIZE */
memcpy(buf, edid_ctrl->edid_buf, size);
@ -516,97 +592,13 @@ static struct attribute_group hdmi_edid_fs_attrs_group = {
static bool hdmi_edid_is_mode_supported(struct hdmi_edid_ctrl *edid_ctrl,
struct msm_hdmi_mode_timing_info *timing)
{
if (!timing->supported || timing->pixel_freq > edid_ctrl->max_pclk_khz)
if (!timing->supported ||
timing->pixel_freq > edid_ctrl->init_data.max_pclk_khz)
return false;
return true;
}
static int hdmi_edid_read_block(struct hdmi_edid_ctrl *edid_ctrl, int block,
u8 *edid_buf)
{
const u8 *b = NULL;
u32 ndx, check_sum, print_len;
int block_size;
int i, status;
int retry_cnt = 0, checksum_retry = 0;
struct hdmi_tx_ddc_data ddc_data;
b = edid_buf;
if (!edid_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
return -EINVAL;
}
read_retry:
block_size = 0x80;
status = 0;
do {
DEV_DBG("EDID: reading block(%d) with block-size=%d\n",
block, block_size);
for (i = 0; i < 0x80; i += block_size) {
memset(&ddc_data, 0, sizeof(ddc_data));
ddc_data.dev_addr = 0xA0;
ddc_data.offset = block*0x80 + i;
ddc_data.data_buf = edid_buf+i;
ddc_data.data_len = block_size;
ddc_data.request_len = block_size;
ddc_data.retry = 1;
ddc_data.what = "EDID";
ddc_data.no_align = false;
/*Read EDID twice with 32bit alighnment too */
if (block < 2)
status = hdmi_ddc_read(
edid_ctrl->init_data.ddc_ctrl,
&ddc_data);
else
status = hdmi_ddc_read_seg(
edid_ctrl->init_data.ddc_ctrl,
&ddc_data);
if (status)
break;
}
if (retry_cnt++ >= MAX_EDID_READ_RETRY)
block_size /= 2;
} while (status && (block_size >= 16));
if (status)
goto error;
/* Calculate checksum */
check_sum = 0;
for (ndx = 0; ndx < 0x80; ++ndx)
check_sum += edid_buf[ndx];
if (check_sum & 0xFF) {
DEV_ERR("%s: failed CHECKSUM (read:%x, expected:%x)\n",
__func__, (u8)edid_buf[0x7F], (u8)check_sum);
for (ndx = 0; ndx < 0x100; ndx += 4)
DEV_DBG("EDID[%02x-%02x] %02x %02x %02x %02x\n",
ndx, ndx+3,
b[ndx+0], b[ndx+1], b[ndx+2], b[ndx+3]);
status = -EPROTO;
if (checksum_retry++ < 3) {
DEV_DBG("Retrying reading EDID %d time\n",
checksum_retry);
goto read_retry;
}
goto error;
}
print_len = 0x80;
for (ndx = 0; ndx < print_len; ndx += 4)
DEV_DBG("EDID[%02x-%02x] %02x %02x %02x %02x\n",
ndx, ndx+3,
b[ndx+0], b[ndx+1], b[ndx+2], b[ndx+3]);
error:
return status;
} /* hdmi_edid_read_block */
#define EDID_BLK_LEN 128
#define EDID_DTD_LEN 18
static const u8 *hdmi_edid_find_block(const u8 *in_buf, u32 start_offset,
u8 type, u8 *len)
{
@ -614,7 +606,7 @@ static const u8 *hdmi_edid_find_block(const u8 *in_buf, u32 start_offset,
u32 offset = start_offset;
u32 dbc_offset = in_buf[2];
if (dbc_offset >= EDID_BLK_LEN - EDID_DTD_LEN)
if (dbc_offset >= EDID_BLOCK_SIZE - EDID_DTD_LEN)
return NULL;
*len = 0;
@ -798,7 +790,7 @@ static void hdmi_edid_extract_extended_data_blocks(
struct hdmi_edid_ctrl *edid_ctrl, const u8 *in_buf)
{
u8 len = 0;
u32 start_offset = DBC_START_OFFSET;
u32 start_offset = 0;
u8 const *etag = NULL;
if (!edid_ctrl) {
@ -806,72 +798,76 @@ static void hdmi_edid_extract_extended_data_blocks(
return;
}
/* A Tage code of 7 identifies extended data blocks */
etag = hdmi_edid_find_block(in_buf, start_offset, USE_EXTENDED_TAG,
&len);
do {
if (!start_offset && !etag)
start_offset = DBC_START_OFFSET;
else
start_offset = etag - in_buf + len + 1;
while (etag != NULL) {
/* The extended data block should at least be 2 bytes long */
if (len < 2) {
DEV_DBG("%s: data block of len < 2 bytes. Ignor...\n",
__func__);
} else {
/*
* The second byte of the extended data block has the
* extended tag code
*/
switch (etag[1]) {
case VIDEO_CAPABILITY_DATA_BLOCK:
/* Video Capability Data Block */
DEV_DBG("%s: EDID: VCDB=%02X %02X\n", __func__,
etag[1], etag[2]);
/*
* Check if the sink specifies underscan
* support for:
* BIT 5: preferred video format
* BIT 3: IT video format
* BIT 1: CE video format
*/
edid_ctrl->pt_scan_info =
(etag[2] & (BIT(4) | BIT(5))) >> 4;
edid_ctrl->it_scan_info =
(etag[2] & (BIT(3) | BIT(2))) >> 2;
edid_ctrl->ce_scan_info =
etag[2] & (BIT(1) | BIT(0));
DEV_DBG("%s: Scan Info (pt|it|ce): (%d|%d|%d)",
__func__,
edid_ctrl->pt_scan_info,
edid_ctrl->it_scan_info,
edid_ctrl->ce_scan_info);
break;
case HDMI_VIDEO_DATA_BLOCK:
/* HDMI Video data block defined in HDMI 2.0 */
DEV_DBG("%s: EDID: HVDB found\n", __func__);
hdmi_edid_parse_hvdb(edid_ctrl, etag);
break;
case Y420_CAPABILITY_MAP_DATA_BLOCK:
DEV_DBG("%s found Y420CMDB byte 3 = 0x%x",
__func__, etag[2]);
hdmi_edid_parse_Y420CMDB(edid_ctrl, etag);
break;
case Y420_VIDEO_DATA_BLOCK:
DEV_DBG("%s found Y420VDB byte 3 = 0x%x",
__func__, etag[2]);
hdmi_edid_parse_Y420VDB(edid_ctrl, etag);
break;
default:
DEV_DBG("%s: Tag Code %d not supported\n",
__func__, etag[1]);
break;
}
}
/* There could be more that one extended data block */
start_offset = etag - in_buf + len + 1;
etag = hdmi_edid_find_block(in_buf, start_offset,
USE_EXTENDED_TAG, &len);
}
if (!etag || !len) {
DEV_DBG("%s: No more extended block found\n", __func__);
break;
}
/* The extended data block should at least be 2 bytes long */
if (len < 2) {
DEV_DBG("%s: invalid block size\n", __func__);
continue;
}
/*
* The second byte of the extended data block has the
* extended tag code
*/
switch (etag[1]) {
case VIDEO_CAPABILITY_DATA_BLOCK:
/* Video Capability Data Block */
DEV_DBG("%s: EDID: VCDB=%02X %02X\n", __func__,
etag[1], etag[2]);
/*
* Check if the sink specifies underscan
* support for:
* BIT 5: preferred video format
* BIT 3: IT video format
* BIT 1: CE video format
*/
edid_ctrl->pt_scan_info =
(etag[2] & (BIT(4) | BIT(5))) >> 4;
edid_ctrl->it_scan_info =
(etag[2] & (BIT(3) | BIT(2))) >> 2;
edid_ctrl->ce_scan_info =
etag[2] & (BIT(1) | BIT(0));
DEV_DBG("%s: Scan Info (pt|it|ce): (%d|%d|%d)",
__func__,
edid_ctrl->pt_scan_info,
edid_ctrl->it_scan_info,
edid_ctrl->ce_scan_info);
break;
case HDMI_VIDEO_DATA_BLOCK:
/* HDMI Video data block defined in HDMI 2.0 */
DEV_DBG("%s: EDID: HVDB found\n", __func__);
hdmi_edid_parse_hvdb(edid_ctrl, etag);
break;
case Y420_CAPABILITY_MAP_DATA_BLOCK:
DEV_DBG("%s found Y420CMDB byte 3 = 0x%x",
__func__, etag[2]);
hdmi_edid_parse_Y420CMDB(edid_ctrl, etag);
break;
case Y420_VIDEO_DATA_BLOCK:
DEV_DBG("%s found Y420VDB byte 3 = 0x%x",
__func__, etag[2]);
hdmi_edid_parse_Y420VDB(edid_ctrl, etag);
break;
default:
DEV_DBG("%s: Tag Code %d not supported\n",
__func__, etag[1]);
break;
}
} while (1);
} /* hdmi_edid_extract_extended_data_blocks */
static void hdmi_edid_extract_3d_present(struct hdmi_edid_ctrl *edid_ctrl,
@ -1103,10 +1099,19 @@ static u32 hdmi_edid_extract_ieee_reg_id(struct hdmi_edid_ctrl *edid_ctrl,
return ((u32)vsd[3] << 16) + ((u32)vsd[2] << 8) + (u32)vsd[1];
} /* hdmi_edid_extract_ieee_reg_id */
static void hdmi_edid_extract_vendor_id(const u8 *in_buf,
char *vendor_id)
static void hdmi_edid_extract_vendor_id(struct hdmi_edid_ctrl *edid_ctrl)
{
u32 id_codes = ((u32)in_buf[8] << 8) + in_buf[9];
char *vendor_id;
u32 id_codes;
if (!edid_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
return;
}
vendor_id = edid_ctrl->vendor_id;
id_codes = ((u32)edid_ctrl->edid_buf[8] << 8) +
edid_ctrl->edid_buf[9];
vendor_id[0] = 'A' - 1 + ((id_codes >> 10) & 0x1F);
vendor_id[1] = 'A' - 1 + ((id_codes >> 5) & 0x1F);
@ -1651,13 +1656,14 @@ loop_end:
}
}
static void hdmi_edid_get_display_mode(struct hdmi_edid_ctrl *edid_ctrl,
const u8 *data_buf, u32 num_of_cea_blocks)
static void hdmi_edid_get_display_mode(struct hdmi_edid_ctrl *edid_ctrl)
{
u8 i = 0, offset = 0, std_blk = 0;
u32 video_format = HDMI_VFRMT_640x480p60_4_3;
u32 has480p = false;
u8 len = 0;
u8 num_of_cea_blocks;
u8 *data_buf;
int rc;
const u8 *edid_blk0 = NULL;
const u8 *edid_blk1 = NULL;
@ -1667,11 +1673,14 @@ static void hdmi_edid_get_display_mode(struct hdmi_edid_ctrl *edid_ctrl,
bool read_block0_res = false;
struct hdmi_edid_sink_data *sink_data = NULL;
if (!edid_ctrl || !data_buf) {
if (!edid_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
return;
}
data_buf = edid_ctrl->edid_buf;
num_of_cea_blocks = edid_ctrl->cea_blks;
edid_blk0 = &data_buf[0x0];
edid_blk1 = &data_buf[0x80];
svd = num_of_cea_blocks ?
@ -1910,142 +1919,116 @@ static void hdmi_edid_get_display_mode(struct hdmi_edid_ctrl *edid_ctrl,
HDMI_VFRMT_640x480p60_4_3);
} /* hdmi_edid_get_display_mode */
int hdmi_edid_read(void *input)
u32 hdmi_edid_get_raw_data(void *input, u8 *buf, u32 size)
{
struct hdmi_edid_ctrl *edid_ctrl = (struct hdmi_edid_ctrl *) input;
u32 ret = 0;
u32 buf_size;
if (!edid_ctrl || !buf) {
DEV_ERR("%s: invalid input\n", __func__);
ret = -EINVAL;
goto end;
}
buf_size = sizeof(edid_ctrl->edid_buf);
size = min(size, buf_size);
memcpy(buf, edid_ctrl->edid_buf, size);
end:
return ret;
}
int hdmi_edid_parser(void *input)
{
/* EDID_BLOCK_SIZE[0x80] Each page size in the EDID ROM */
u8 *edid_buf = NULL;
u32 cea_extension_ver = 0;
u32 num_of_cea_blocks = 0;
u32 ieee_reg_id = 0;
u32 i = 1;
u16 ieee_reg_id;
int status = 0;
char vendor_id[5];
u32 i = 0;
struct hdmi_edid_ctrl *edid_ctrl = (struct hdmi_edid_ctrl *)input;
if (!edid_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
return -EINVAL;
status = -EINVAL;
goto err_invalid_data;
}
edid_ctrl->page_id = MSM_HDMI_INIT_RES_PAGE;
/* reset edid data for new hdmi connection */
hdmi_edid_reset_parser(edid_ctrl);
edid_buf = edid_ctrl->edid_buf;
edid_ctrl->pt_scan_info = 0;
edid_ctrl->it_scan_info = 0;
edid_ctrl->ce_scan_info = 0;
edid_ctrl->present_3d = 0;
memset(&edid_ctrl->sink_data, 0, sizeof(edid_ctrl->sink_data));
memset(edid_buf, 0, sizeof(edid_ctrl->edid_buf));
memset(edid_ctrl->audio_data_block, 0,
sizeof(edid_ctrl->audio_data_block));
memset(edid_ctrl->spkr_alloc_data_block, 0,
sizeof(edid_ctrl->spkr_alloc_data_block));
memset(&edid_ctrl->sink_caps, 0, sizeof(edid_ctrl->sink_caps));
DEV_DBG("%s: === HDMI EDID BLOCK 0 ===\n", __func__);
print_hex_dump(KERN_DEBUG, "HDMI EDID: ", DUMP_PREFIX_NONE, 16, 1,
edid_buf, EDID_BLOCK_SIZE, false);
edid_ctrl->adb_size = 0;
edid_ctrl->sadb_size = 0;
hdmi_reset_resv_timing_info();
status = hdmi_edid_read_block(edid_ctrl, 0, edid_buf);
if (status || !hdmi_edid_check_header(edid_buf)) {
if (!status)
status = -EPROTO;
DEV_ERR("%s: blk0 fail:%d[%02x%02x%02x%02x%02x%02x%02x%02x]\n",
__func__, status,
edid_buf[0], edid_buf[1], edid_buf[2], edid_buf[3],
edid_buf[4], edid_buf[5], edid_buf[6], edid_buf[7]);
goto error;
if (!hdmi_edid_check_header(edid_buf)) {
status = -EPROTO;
goto err_invalid_header;
}
hdmi_edid_extract_vendor_id(edid_buf, vendor_id);
hdmi_edid_extract_vendor_id(edid_ctrl);
/* EDID_CEA_EXTENSION_FLAG[0x7E] - CEC extension byte */
num_of_cea_blocks = edid_buf[0x7E];
num_of_cea_blocks = edid_buf[EDID_BLOCK_SIZE - 2];
DEV_DBG("%s: No. of CEA blocks is [%u]\n", __func__,
num_of_cea_blocks);
/* Find out any CEA extension blocks following block 0 */
switch (num_of_cea_blocks) {
case 0: /* No CEA extension */
edid_ctrl->sink_mode = false;
if (num_of_cea_blocks == 0) {
/* No CEA extension */
edid_ctrl->sink_mode = SINK_MODE_DVI;
DEV_DBG("HDMI DVI mode: %s\n",
edid_ctrl->sink_mode ? "no" : "yes");
break;
case 1: /* Read block 1 */
status = hdmi_edid_read_block(edid_ctrl, 1, &edid_buf[0x80]);
if (status) {
DEV_ERR("%s: ddc read block(1) failed: %d\n", __func__,
status);
goto error;
}
if (edid_buf[0x80] != 2)
num_of_cea_blocks = 0;
break;
case 2:
case 3:
case 4:
for (i = 1; i <= num_of_cea_blocks; i++) {
status = hdmi_edid_read_block(
edid_ctrl, i, edid_buf + (0x80 * i));
if (status) {
DEV_ERR("%s: read blk(%d) failed:%d\n",
__func__, i, status);
goto error;
}
}
break;
default:
DEV_ERR("%s: ddc read failed, not supported multi-blocks: %d\n",
__func__, num_of_cea_blocks);
status = -EPROTO;
goto error;
goto bail;
}
if (num_of_cea_blocks) {
/* EDID_CEA_EXTENSION_VERSION[0x81]: Offset to CEA extension
* version number - v1,v2,v3 (v1 is seldom, v2 is obsolete,
* v3 most common) */
cea_extension_ver = edid_buf[0x81];
/* check for valid CEA block */
if (edid_buf[EDID_BLOCK_SIZE] != 2) {
DEV_ERR("%s: Invalid CEA block\n", __func__);
num_of_cea_blocks = 0;
goto bail;
}
/* EDID_VERSION[0x12] - EDID Version */
/* EDID_REVISION[0x13] - EDID Revision */
DEV_INFO("%s: V=%d.%d #CEABlks=%d[V%d] ID=%s IEEE=%04x Ext=0x%02x\n",
__func__, edid_buf[0x12], edid_buf[0x13],
num_of_cea_blocks, cea_extension_ver, vendor_id, ieee_reg_id,
edid_buf[0x80]);
/* goto to CEA extension edid block */
edid_buf += EDID_BLOCK_SIZE;
hdmi_edid_get_display_mode(edid_ctrl, edid_buf, num_of_cea_blocks);
ieee_reg_id = hdmi_edid_extract_ieee_reg_id(edid_ctrl, edid_buf);
if (ieee_reg_id == EDID_IEEE_REG_ID)
edid_ctrl->sink_mode = SINK_MODE_HDMI;
else
edid_ctrl->sink_mode = SINK_MODE_DVI;
if (num_of_cea_blocks) {
ieee_reg_id =
hdmi_edid_extract_ieee_reg_id(edid_ctrl,
edid_buf+0x80);
if (ieee_reg_id == 0x0c03)
edid_ctrl->sink_mode = true;
else
edid_ctrl->sink_mode = false;
hdmi_edid_extract_sink_caps(edid_ctrl, edid_buf+0x80);
hdmi_edid_extract_sink_caps(edid_ctrl, edid_buf);
hdmi_edid_extract_latency_fields(edid_ctrl, edid_buf);
hdmi_edid_extract_speaker_allocation_data(edid_ctrl, edid_buf);
hdmi_edid_extract_audio_data_blocks(edid_ctrl, edid_buf);
hdmi_edid_extract_3d_present(edid_ctrl, edid_buf);
hdmi_edid_extract_extended_data_blocks(edid_ctrl, edid_buf);
hdmi_edid_extract_latency_fields(edid_ctrl,
edid_buf+0x80);
hdmi_edid_extract_speaker_allocation_data(
edid_ctrl, edid_buf+0x80);
hdmi_edid_extract_audio_data_blocks(edid_ctrl,
edid_buf+0x80);
hdmi_edid_extract_3d_present(edid_ctrl,
edid_buf+0x80);
hdmi_edid_extract_extended_data_blocks(edid_ctrl,
edid_buf+0x80);
bail:
for (i = 1; i <= num_of_cea_blocks; i++) {
DEV_DBG("%s: === HDMI EDID BLOCK %d ===\n", __func__, i);
print_hex_dump(KERN_DEBUG, "HDMI EDID: ", DUMP_PREFIX_NONE,
16, 1, edid_ctrl->edid_buf + (i * EDID_BLOCK_SIZE),
EDID_BLOCK_SIZE, false);
}
edid_ctrl->cea_blks = num_of_cea_blocks;
hdmi_edid_get_display_mode(edid_ctrl);
return 0;
error:
err_invalid_header:
edid_ctrl->sink_data.num_of_elements = 1;
edid_ctrl->sink_data.disp_mode_list[0].video_format =
edid_ctrl->video_resolution;
edid_ctrl->sink_data.disp_mode_list[0].rgb_support = true;
err_invalid_data:
return status;
} /* hdmi_edid_read */
@ -2180,40 +2163,45 @@ void hdmi_edid_deinit(void *input)
struct hdmi_edid_ctrl *edid_ctrl = (struct hdmi_edid_ctrl *)input;
if (edid_ctrl) {
sysfs_remove_group(edid_ctrl->init_data.sysfs_kobj,
&hdmi_edid_fs_attrs_group);
if (edid_ctrl->init_data.kobj)
sysfs_remove_group(edid_ctrl->init_data.kobj,
&hdmi_edid_fs_attrs_group);
kfree(edid_ctrl);
}
} /* hdmi_edid_deinit */
}
void *hdmi_edid_init(struct hdmi_edid_init_data *init_data, u32 max_pclk_khz)
void *hdmi_edid_init(struct hdmi_edid_init_data *idata)
{
struct hdmi_edid_ctrl *edid_ctrl = NULL;
if (!init_data || !init_data->io ||
!init_data->mutex || !init_data->sysfs_kobj ||
!init_data->ddc_ctrl) {
if (!idata) {
DEV_ERR("%s: invalid input\n", __func__);
goto error;
}
edid_ctrl = kzalloc(sizeof(*edid_ctrl), GFP_KERNEL);
if (!edid_ctrl) {
DEV_ERR("%s: Out of memory\n", __func__);
if (!edid_ctrl)
goto error;
edid_ctrl->init_data = *idata;
if (idata->kobj) {
if (sysfs_create_group(idata->kobj,
&hdmi_edid_fs_attrs_group))
DEV_ERR("%s: EDID sysfs create failed\n",
__func__);
} else {
DEV_DBG("%s: kobj not provided\n", __func__);
}
edid_ctrl->init_data = *init_data;
edid_ctrl->sink_mode = false;
edid_ctrl->max_pclk_khz = max_pclk_khz;
/* provide edid buffer to the client */
idata->buf = edid_ctrl->edid_buf;
idata->buf_size = sizeof(edid_ctrl->edid_buf);
if (sysfs_create_group(init_data->sysfs_kobj,
&hdmi_edid_fs_attrs_group)) {
DEV_ERR("%s: EDID sysfs create failed\n", __func__);
kfree(edid_ctrl);
edid_ctrl = NULL;
}
return (void *)edid_ctrl;
error:
return (void *)edid_ctrl;
} /* hdmi_edid_deinit */
kfree(edid_ctrl);
return NULL;
}

View file

@ -16,16 +16,20 @@
#include <linux/msm_hdmi.h>
#include "mdss_hdmi_util.h"
struct hdmi_edid_init_data {
struct dss_io_data *io;
struct mutex *mutex;
struct kobject *sysfs_kobj;
#define EDID_BLOCK_SIZE 0x80
#define EDID_BLOCK_ADDR 0xA0
#define MAX_EDID_BLOCKS 5
struct hdmi_tx_ddc_ctrl *ddc_ctrl;
struct hdmi_edid_init_data {
struct kobject *kobj;
struct hdmi_util_ds_data *ds_data;
u32 max_pclk_khz;
u8 *buf;
u32 buf_size;
};
int hdmi_edid_read(void *edid_ctrl);
int hdmi_edid_parser(void *edid_ctrl);
u32 hdmi_edid_get_raw_data(void *edid_ctrl, u8 *buf, u32 size);
u8 hdmi_edid_get_sink_scaninfo(void *edid_ctrl, u32 resolution);
u32 hdmi_edid_get_sink_mode(void *edid_ctrl);
bool hdmi_edid_get_sink_scrambler_support(void *input);
@ -34,6 +38,6 @@ int hdmi_edid_get_audio_blk(void *edid_ctrl,
struct msm_hdmi_audio_edid_blk *blk);
void hdmi_edid_set_video_resolution(void *edid_ctrl, u32 resolution);
void hdmi_edid_deinit(void *edid_ctrl);
void *hdmi_edid_init(struct hdmi_edid_init_data *init_data, u32 max_pclk_khz);
void *hdmi_edid_init(struct hdmi_edid_init_data *init_data);
#endif /* __HDMI_EDID_H__ */

View file

@ -103,6 +103,7 @@ static bool hdcp_feature_on = true;
/* Line numbers at which AVI Infoframe and Vendor Infoframe will be sent */
#define AVI_IFRAME_LINE_NUMBER 1
#define VENDOR_IFRAME_LINE_NUMBER 3
#define MAX_EDID_READ_RETRY 5
enum {
DATA_BYTE_1,
@ -1233,32 +1234,187 @@ static void hdmi_tx_hdcp_cb(void *ptr, enum hdmi_hdcp_state status)
}
}
static u32 hdmi_tx_ddc_read(struct hdmi_tx_ddc_ctrl *ddc_ctrl,
u32 block, u8 *edid_buf)
{
u32 block_size = EDID_BLOCK_SIZE;
struct hdmi_tx_ddc_data ddc_data;
u32 status = 0, retry_cnt = 0, i;
if (!ddc_ctrl || !edid_buf) {
DEV_ERR("%s: invalid input\n", __func__);
return -EINVAL;
}
do {
DEV_DBG("EDID: reading block(%d) with block-size=%d\n",
block, block_size);
for (i = 0; i < EDID_BLOCK_SIZE; i += block_size) {
memset(&ddc_data, 0, sizeof(ddc_data));
ddc_data.dev_addr = EDID_BLOCK_ADDR;
ddc_data.offset = block * EDID_BLOCK_SIZE + i;
ddc_data.data_buf = edid_buf + i;
ddc_data.data_len = block_size;
ddc_data.request_len = block_size;
ddc_data.retry = 1;
ddc_data.what = "EDID";
ddc_data.no_align = false;
/* Read EDID twice with 32bit alighnment too */
if (block < 2)
status = hdmi_ddc_read(ddc_ctrl, &ddc_data);
else
status = hdmi_ddc_read_seg(ddc_ctrl, &ddc_data);
if (status)
break;
}
if (retry_cnt++ >= MAX_EDID_READ_RETRY)
block_size /= 2;
} while (status && (block_size >= 16));
return status;
}
static int hdmi_tx_read_edid_retry(struct hdmi_tx_ctrl *hdmi_ctrl, u8 block)
{
u32 checksum_retry = 0;
u8 *ebuf;
int ret = 0;
struct hdmi_tx_ddc_ctrl *ddc_ctrl;
if (!hdmi_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
ret = -EINVAL;
goto end;
}
ebuf = hdmi_ctrl->edid_buf;
if (!ebuf) {
DEV_ERR("%s: invalid edid buf\n", __func__);
ret = -EINVAL;
goto end;
}
ddc_ctrl = &hdmi_ctrl->ddc_ctrl;
while (checksum_retry++ < MAX_EDID_READ_RETRY) {
ret = hdmi_tx_ddc_read(ddc_ctrl, block,
ebuf + (block * EDID_BLOCK_SIZE));
if (ret)
continue;
else
break;
}
end:
return ret;
}
static int hdmi_tx_read_edid(struct hdmi_tx_ctrl *hdmi_ctrl)
{
int ndx, check_sum;
int cea_blks = 0, block = 0, total_blocks = 0;
int ret = 0;
u8 *ebuf;
struct hdmi_tx_ddc_ctrl *ddc_ctrl;
if (!hdmi_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
ret = -EINVAL;
goto end;
}
ebuf = hdmi_ctrl->edid_buf;
if (!ebuf) {
DEV_ERR("%s: invalid edid buf\n", __func__);
ret = -EINVAL;
goto end;
}
memset(ebuf, 0, hdmi_ctrl->edid_buf_size);
ddc_ctrl = &hdmi_ctrl->ddc_ctrl;
do {
if (block * EDID_BLOCK_SIZE > hdmi_ctrl->edid_buf_size) {
DEV_ERR("%s: no mem for block %d, max mem %d\n",
__func__, block, hdmi_ctrl->edid_buf_size);
ret = -ENOMEM;
goto end;
}
ret = hdmi_tx_read_edid_retry(hdmi_ctrl, block);
if (ret) {
DEV_ERR("%s: edid read failed\n", __func__);
goto end;
}
/* verify checksum to validate edid block */
check_sum = 0;
for (ndx = 0; ndx < EDID_BLOCK_SIZE; ++ndx)
check_sum += ebuf[ndx];
if (check_sum & 0xFF) {
DEV_ERR("%s: checksome mismatch\n", __func__);
ret = -EINVAL;
goto end;
}
/* get number of cea extension blocks as given in block 0*/
if (block == 0) {
cea_blks = ebuf[EDID_BLOCK_SIZE - 2];
if (cea_blks < 0 || cea_blks >= MAX_EDID_BLOCKS) {
cea_blks = 0;
DEV_ERR("%s: invalid cea blocks %d\n",
__func__, cea_blks);
ret = -EINVAL;
goto end;
}
total_blocks = cea_blks + 1;
}
} while ((cea_blks-- > 0) && (block++ < MAX_EDID_BLOCKS));
end:
return ret;
}
/* Enable HDMI features */
static int hdmi_tx_init_features(struct hdmi_tx_ctrl *hdmi_ctrl)
static int hdmi_tx_init_features(struct hdmi_tx_ctrl *hdmi_ctrl,
struct fb_info *fbi)
{
struct hdmi_edid_init_data edid_init_data;
struct hdmi_hdcp_init_data hdcp_init_data;
struct hdmi_cec_init_data cec_init_data;
struct resource *res = NULL;
if (!hdmi_ctrl) {
if (!hdmi_ctrl || !fbi) {
DEV_ERR("%s: invalid input\n", __func__);
return -EINVAL;
}
/* Initialize EDID feature */
edid_init_data.io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
edid_init_data.mutex = &hdmi_ctrl->mutex;
edid_init_data.sysfs_kobj = hdmi_ctrl->kobj;
edid_init_data.ddc_ctrl = &hdmi_ctrl->ddc_ctrl;
edid_init_data.kobj = hdmi_ctrl->kobj;
edid_init_data.ds_data = &hdmi_ctrl->ds_data;
edid_init_data.max_pclk_khz = hdmi_ctrl->max_pclk_khz;
hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID] =
hdmi_edid_init(&edid_init_data, hdmi_ctrl->max_pclk_khz);
hdmi_edid_init(&edid_init_data);
if (!hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID]) {
DEV_ERR("%s: hdmi_edid_init failed\n", __func__);
return -EPERM;
}
hdmi_ctrl->panel_data.panel_info.edid_data =
hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID];
/* get edid buffer from edid parser */
hdmi_ctrl->edid_buf = edid_init_data.buf;
hdmi_ctrl->edid_buf_size = edid_init_data.buf_size;
hdmi_edid_set_video_resolution(
hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID],
hdmi_ctrl->vid_cfg.vic);
@ -1401,11 +1557,15 @@ static int hdmi_tx_read_sink_info(struct hdmi_tx_ctrl *hdmi_ctrl)
hdmi_ddc_config(&hdmi_ctrl->ddc_ctrl);
status = hdmi_edid_read(hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID]);
if (!status)
DEV_DBG("%s: hdmi_edid_read success\n", __func__);
else
DEV_ERR("%s: hdmi_edid_read failed\n", __func__);
status = hdmi_tx_read_edid(hdmi_ctrl);
if (status) {
DEV_ERR("%s: error reading edid\n", __func__);
goto error;
}
status = hdmi_edid_parser(hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID]);
if (status)
DEV_ERR("%s: edid parse failed\n", __func__);
error:
return status;
@ -3789,7 +3949,7 @@ static int hdmi_tx_panel_event_handler(struct mdss_panel_data *panel_data,
__func__, rc);
return rc;
}
rc = hdmi_tx_init_features(hdmi_ctrl);
rc = hdmi_tx_init_features(hdmi_ctrl, arg);
if (rc) {
DEV_ERR("%s: init_features failed.rc=%d\n",
__func__, rc);

View file

@ -187,6 +187,9 @@ struct hdmi_tx_ctrl {
struct hdmi_hdcp_ops *hdcp_ops;
void *hdcp_feature_data;
bool hdcp22_present;
u8 *edid_buf;
u32 edid_buf_size;
};
#endif /* __MDSS_HDMI_TX_H__ */

View file

@ -546,6 +546,8 @@ struct mdss_panel_info {
/* refer sim_panel_modes enum for different modes */
u8 sim_panel_mode;
void *edid_data;
char panel_name[MDSS_MAX_PANEL_LEN];
struct mdss_mdp_pp_tear_check te;