hdmitx: add SCDC/CED function [1/1]

PD#SWPL-6361

Problem:
Lack SCDC/CED function to check Rx TMDS status

Solution:
Add SCDC/CED function
By default, this function is not open.
Enable it in board dts file like below:
  &amhdmitx {
	cedst_en = <1>;
  };
Also, you can manually enable it by
  'echo 1 > /sys/class/amhdmitx/amhdmitx0/cedst_policy'
Then listen '/sys/class/extcon/hdmi_cedst/uevent'
and check 'cat /sys/class/amhdmitx/amhdmitx0/cedst_count'

Verify:
G12/U212

Change-Id: Ic9c90936bad643ea95d418d7b019eb37210d7123
Signed-off-by: Zongdong Jiao <zongdong.jiao@amlogic.com>
This commit is contained in:
Zongdong Jiao
2019-06-14 16:27:19 +08:00
committed by Luan Yuan
parent 7684ca16ab
commit 35f2ddb9c6
6 changed files with 258 additions and 9 deletions

View File

@@ -1076,6 +1076,7 @@
*/
ic_type = <10>;
dongle_mode = <0>;
cedst_en = <0>;
vend_data: vend_data{ /* Should modified by Customer */
vendor_name = "Amlogic"; /* Max Chars: 8 */
/* standards.ieee.org/develop/regauth/oui/oui.txt */

View File

@@ -109,6 +109,7 @@ struct extcon_dev *hdmitx_extcon_power;
struct extcon_dev *hdmitx_extcon_hdr;
struct extcon_dev *hdmitx_extcon_rxsense;
struct extcon_dev *hdmitx_extcon_hdcp;
struct extcon_dev *hdmitx_extcon_cedst;
static inline void hdmitx_notify_hpd(int hpd)
{
@@ -477,6 +478,8 @@ static int set_disp_mode_auto(void)
hdev->HWOp.CntlConfig(hdev, CONF_CLR_VSDB_PACKET, 0);
hdev->HWOp.CntlMisc(hdev, MISC_TMDS_PHY_OP, TMDS_PHY_DISABLE);
hdev->para = hdmi_get_fmt_name("invalid", hdev->fmt_attr);
if (hdev->cedst_policy)
cancel_delayed_work(&hdev->work_cedst);
return -1;
}
strncpy(mode, info->name, sizeof(mode));
@@ -573,6 +576,10 @@ static int set_disp_mode_auto(void)
}
}
hdmitx_set_audio(hdev, &(hdev->cur_audio_param));
if (hdev->cedst_policy) {
cancel_delayed_work(&hdev->work_cedst);
queue_delayed_work(hdev->cedst_wq, &hdev->work_cedst, 0);
}
hdev->output_blank_flag = 1;
hdev->ready = 1;
return ret;
@@ -2996,6 +3003,82 @@ static ssize_t show_rxsense_policy(struct device *dev,
return pos;
}
/* cedst_policy: 0, no CED feature
* 1, auto mode, depends on RX scdc_present
* 2, forced CED feature
*/
static ssize_t store_cedst_policy(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int val = 0;
struct hdmitx_dev *hdev = &hdmitx_device;
if (isdigit(buf[0])) {
val = buf[0] - '0';
pr_info("hdmitx: set cedst_policy as %d\n", val);
if ((val == 0) || (val == 1) || (val == 2)) {
hdev->cedst_policy = val;
if (val == 1) { /* Auto mode, depends on Rx */
/* check RX scdc_present */
if (hdev->RXCap.scdc_present)
hdev->cedst_policy = 1;
else
hdev->cedst_policy = 0;
}
if (val == 2) /* Force mode */
hdev->cedst_policy = 1;
} else
pr_info("only accept as 0, 1(auto), or 2(force)\n");
}
if (hdev->cedst_policy)
queue_delayed_work(hdev->cedst_wq, &hdev->work_cedst, 0);
else
cancel_delayed_work(&hdev->work_cedst);
return count;
}
static ssize_t show_cedst_policy(struct device *dev,
struct device_attribute *attr, char *buf)
{
int pos = 0;
pos += snprintf(buf + pos, PAGE_SIZE, "%d\n",
hdmitx_device.cedst_policy);
return pos;
}
static ssize_t show_cedst_count(struct device *dev,
struct device_attribute *attr, char *buf)
{
int pos = 0;
struct ced_cnt *ced = &hdmitx_device.ced_cnt;
struct scdc_locked_st *ch_st = &hdmitx_device.chlocked_st;
if (!ch_st->clock_detected)
pos += snprintf(buf + pos, PAGE_SIZE, "clock undetected\n");
if (!ch_st->ch0_locked)
pos += snprintf(buf + pos, PAGE_SIZE, "CH0 unlocked\n");
if (!ch_st->ch1_locked)
pos += snprintf(buf + pos, PAGE_SIZE, "CH1 unlocked\n");
if (!ch_st->ch2_locked)
pos += snprintf(buf + pos, PAGE_SIZE, "CH2 unlocked\n");
if (ced->ch0_valid && ced->ch0_cnt)
pos += snprintf(buf + pos, PAGE_SIZE, "CH0 ErrCnt 0x%x\n",
ced->ch0_cnt);
if (ced->ch1_valid && ced->ch1_cnt)
pos += snprintf(buf + pos, PAGE_SIZE, "CH1 ErrCnt 0x%x\n",
ced->ch1_cnt);
if (ced->ch2_valid && ced->ch2_cnt)
pos += snprintf(buf + pos, PAGE_SIZE, "CH2 ErrCnt 0x%x\n",
ced->ch2_cnt);
memset(ced, 0, sizeof(*ced));
return pos;
}
static ssize_t store_sspll(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
@@ -3558,6 +3641,8 @@ static DEVICE_ATTR(sspll, 0664, show_sspll, store_sspll);
static DEVICE_ATTR(frac_rate_policy, 0664, show_frac_rate, store_frac_rate);
static DEVICE_ATTR(rxsense_policy, 0644, show_rxsense_policy,
store_rxsense_policy);
static DEVICE_ATTR(cedst_policy, 0664, show_cedst_policy, store_cedst_policy);
static DEVICE_ATTR(cedst_count, 0444, show_cedst_count, NULL);
static DEVICE_ATTR(hdcp_clkdis, 0664, show_hdcp_clkdis, store_hdcp_clkdis);
static DEVICE_ATTR(hdcp_pwr, 0664, show_hdcp_pwr, store_hdcp_pwr);
static DEVICE_ATTR(hdcp_byp, 0200, NULL, store_hdcp_byp);
@@ -3644,6 +3729,11 @@ static int hdmitx_module_disable(enum vmode_e cur_vmod)
hdmitx_disable_vclk2_enci(hdev);
hdev->para = hdmi_get_fmt_name("invalid", hdev->fmt_attr);
hdmitx_validate_vmode("null");
if (hdev->cedst_policy)
cancel_delayed_work(&hdev->work_cedst);
if (hdev->rxsense_policy)
queue_delayed_work(hdmitx_device.rxsense_wq,
&hdmitx_device.work_rxsense, 0);
return 0;
}
@@ -3893,6 +3983,19 @@ static void hdmitx_rxsense_process(struct work_struct *work)
queue_delayed_work(hdev->rxsense_wq, &hdev->work_rxsense, HZ);
}
static void hdmitx_cedst_process(struct work_struct *work)
{
int ced;
struct hdmitx_dev *hdev = container_of((struct delayed_work *)work,
struct hdmitx_dev, work_cedst);
ced = hdev->HWOp.CntlMisc(hdev, MISC_TMDS_CEDST, 0);
/* firstly send as 0, then real ced, A trigger signal */
extcon_set_state_sync(hdmitx_extcon_cedst, EXTCON_DISP_HDMI, 0);
extcon_set_state_sync(hdmitx_extcon_cedst, EXTCON_DISP_HDMI, ced);
queue_delayed_work(hdev->cedst_wq, &hdev->work_cedst, HZ);
}
static void hdmitx_hpd_plugin_handler(struct work_struct *work)
{
char bksv_buf[5];
@@ -3916,6 +4019,7 @@ static void hdmitx_hpd_plugin_handler(struct work_struct *work)
if (hdev->repeater_tx)
rx_repeat_hpd_state(1);
hdmitx_get_edid(hdev);
hdev->cedst_policy = hdev->cedst_en & hdev->RXCap.scdc_present;
hdmi_physcial_size_update(hdev);
if (hdev->RXCap.ieeeoui != HDMI_IEEEOUI)
hdev->HWOp.CntlConfig(hdev,
@@ -3945,6 +4049,10 @@ static void hdmitx_hpd_plugin_handler(struct work_struct *work)
extcon_set_state_sync(hdmitx_extcon_hdmi, EXTCON_DISP_HDMI, 1);
extcon_set_state_sync(hdmitx_extcon_audio, EXTCON_DISP_HDMI, 1);
mutex_unlock(&setclk_mutex);
/* Should be started at end of output */
cancel_delayed_work(&hdev->work_cedst);
if (hdev->cedst_policy)
queue_delayed_work(hdev->cedst_wq, &hdev->work_cedst, 0);
}
static void clear_rx_vinfo(struct hdmitx_dev *hdev)
@@ -3969,6 +4077,8 @@ static void hdmitx_hpd_plugout_handler(struct work_struct *work)
hdev->HWOp.CntlDDC(hdev, DDC_HDCP_MUX_INIT, 1);
hdev->HWOp.CntlDDC(hdev, DDC_HDCP_OP, HDCP14_OFF);
mutex_lock(&setclk_mutex);
if (hdev->cedst_policy)
cancel_delayed_work(&hdev->work_cedst);
pr_info(SYS "plugout\n");
if (!!(hdev->HWOp.CntlMisc(hdev, MISC_HPD_GPI_ST, 0))) {
pr_info(SYS "hpd gpio high\n");
@@ -4063,6 +4173,10 @@ static int hdmi_task_handle(void *data)
hdmitx_device->rxsense_wq = alloc_workqueue(hdmitx_extcon_rxsense->name,
WQ_SYSFS | WQ_FREEZABLE, 0);
INIT_DELAYED_WORK(&hdmitx_device->work_rxsense, hdmitx_rxsense_process);
/* for cedst feature */
hdmitx_device->cedst_wq = alloc_workqueue(hdmitx_extcon_cedst->name,
WQ_SYSFS | WQ_FREEZABLE, 0);
INIT_DELAYED_WORK(&hdmitx_device->work_cedst, hdmitx_cedst_process);
hdmitx_device->tx_aud_cfg = 1; /* default audio configure is on */
@@ -4241,6 +4355,7 @@ void hdmitx_extcon_register(struct platform_device *pdev, struct device *dev)
ret = extcon_dev_register(edev);
if (ret < 0) {
pr_info(SYS "failed to register hdmitx extcon hdmi\n");
extcon_dev_free(edev);
return;
}
hdmitx_extcon_hdmi = edev;
@@ -4251,13 +4366,13 @@ void hdmitx_extcon_register(struct platform_device *pdev, struct device *dev)
pr_info(SYS "failed to allocate hdmitx extcon audio\n");
return;
}
edev->dev.parent = dev;
edev->name = "hdmitx_extcon_audio";
dev_set_name(&edev->dev, "hdmi_audio");
ret = extcon_dev_register(edev);
if (ret < 0) {
pr_info(SYS "failed to register hdmitx extcon audio\n");
extcon_dev_free(edev);
return;
}
hdmitx_extcon_audio = edev;
@@ -4268,13 +4383,13 @@ void hdmitx_extcon_register(struct platform_device *pdev, struct device *dev)
pr_info(SYS "failed to allocate hdmitx extcon power\n");
return;
}
edev->dev.parent = dev;
edev->name = "hdmitx_extcon_power";
dev_set_name(&edev->dev, "hdmi_power");
ret = extcon_dev_register(edev);
if (ret < 0) {
pr_info(SYS "failed to register extcon power\n");
extcon_dev_free(edev);
return;
}
hdmitx_extcon_power = edev;
@@ -4285,30 +4400,47 @@ void hdmitx_extcon_register(struct platform_device *pdev, struct device *dev)
pr_info(SYS "failed to allocate hdmitx extcon hdr\n");
return;
}
edev->dev.parent = dev;
edev->name = "hdmitx_extcon_hdr";
dev_set_name(&edev->dev, "hdmi_hdr");
ret = extcon_dev_register(edev);
if (ret < 0) {
pr_info(SYS "failed to register hdmitx extcon hdr\n");
extcon_dev_free(edev);
return;
}
hdmitx_extcon_hdr = edev;
/*hdmitx extcon CED */
edev = extcon_dev_allocate(hdmi_cable);
if (IS_ERR(edev)) {
pr_info(SYS "failed to allocate extcon rxsense\n");
return;
}
edev->dev.parent = dev;
edev->name = "hdmitx_extcon_cedst";
dev_set_name(&edev->dev, "hdmi_cedst");
ret = extcon_dev_register(edev);
if (ret < 0) {
pr_info(SYS "failed to register extcon cedst\n");
extcon_dev_free(edev);
return;
}
hdmitx_extcon_cedst = edev;
/*hdmitx extcon rxsense*/
edev = extcon_dev_allocate(hdmi_cable);
if (IS_ERR(edev)) {
pr_info(SYS "failed to allocate extcon rxsense\n");
return;
}
edev->dev.parent = dev;
edev->name = "hdmitx_extcon_rxsense";
dev_set_name(&edev->dev, "hdmi_rxsense");
ret = extcon_dev_register(edev);
if (ret < 0) {
pr_info(SYS "failed to register extcon rxsense\n");
extcon_dev_free(edev);
return;
}
hdmitx_extcon_rxsense = edev;
@@ -4319,17 +4451,16 @@ void hdmitx_extcon_register(struct platform_device *pdev, struct device *dev)
pr_info(SYS "failed to allocate extcon hdcp\n");
return;
}
edev->dev.parent = dev;
edev->name = "hdmitx_extcon_hdcp";
dev_set_name(&edev->dev, "hdcp");
ret = extcon_dev_register(edev);
if (ret < 0) {
pr_info(SYS "failed to register extcon hdcp\n");
extcon_dev_free(edev);
return;
}
hdmitx_extcon_hdcp = edev;
}
static void hdmitx_init_parameters(struct hdmitx_info *info)
@@ -4458,6 +4589,11 @@ static int amhdmitx_get_dt_info(struct platform_device *pdev)
hdmitx_device.topo_info = kzalloc(
sizeof(*hdmitx_device.topo_info), GFP_KERNEL);
ret = of_property_read_u32(pdev->dev.of_node,
"cedst_en", &val);
if (!ret)
hdmitx_device.cedst_en = !!val;
/* Get vendor information */
ret = of_property_read_u32(pdev->dev.of_node,
"vend-data", &val);
@@ -4657,6 +4793,8 @@ static int amhdmitx_probe(struct platform_device *pdev)
ret = device_create_file(dev, &dev_attr_frac_rate_policy);
ret = device_create_file(dev, &dev_attr_sspll);
ret = device_create_file(dev, &dev_attr_rxsense_policy);
ret = device_create_file(dev, &dev_attr_cedst_policy);
ret = device_create_file(dev, &dev_attr_cedst_count);
ret = device_create_file(dev, &dev_attr_hdcp_clkdis);
ret = device_create_file(dev, &dev_attr_hdcp_pwr);
ret = device_create_file(dev, &dev_attr_hdcp_ksv_info);
@@ -4766,6 +4904,8 @@ static int amhdmitx_remove(struct platform_device *pdev)
device_remove_file(dev, &dev_attr_frac_rate_policy);
device_remove_file(dev, &dev_attr_sspll);
device_remove_file(dev, &dev_attr_rxsense_policy);
device_remove_file(dev, &dev_attr_cedst_policy);
device_remove_file(dev, &dev_attr_cedst_count);
device_remove_file(dev, &dev_attr_hdcp_pwr);
device_remove_file(dev, &dev_attr_div40);
device_remove_file(dev, &dev_attr_hdcp_repeater);

View File

@@ -25,3 +25,74 @@ void scdc_config(struct hdmitx_dev *hdev)
/* TMDS 1/40 & Scramble */
scdc_wr_sink(TMDS_CFG, hdev->para->tmds_clk_div40 ? 0x3 : 0);
}
/* update CED, 10.4.1.8 */
static int scdc_ced_cnt(struct hdmitx_dev *hdev)
{
struct ced_cnt *ced = &hdev->ced_cnt;
u8 raw[7];
u8 chksum;
int i;
memset(raw, 0, sizeof(raw));
memset(ced, 0, sizeof(struct ced_cnt));
chksum = 0;
for (i = 0; i < 7; i++) {
scdc_rd_sink(ERR_DET_0_L + i, &raw[i]);
chksum += raw[i];
}
ced->ch0_cnt = raw[0] + ((raw[1] & 0x7f) << 8);
ced->ch0_valid = (raw[1] >> 7) & 0x1;
ced->ch1_cnt = raw[2] + ((raw[3] & 0x7f) << 8);
ced->ch1_valid = (raw[3] >> 7) & 0x1;
ced->ch2_cnt = raw[4] + ((raw[5] & 0x7f) << 8);
ced->ch2_valid = (raw[5] >> 7) & 0x1;
/* Do checksum */
if (chksum != 0)
pr_info("ced check sum error\n");
if (ced->ch0_cnt)
pr_info("ced: ch0_cnt = %d %s\n", ced->ch0_cnt,
ced->ch0_valid ? "" : "unvalid");
if (ced->ch1_cnt)
pr_info("ced: ch1_cnt = %d %s\n", ced->ch1_cnt,
ced->ch1_valid ? "" : "unvalid");
if (ced->ch2_cnt)
pr_info("ced: ch2_cnt = %d %s\n", ced->ch2_cnt,
ced->ch2_valid ? "" : "unvalid");
return chksum != 0;
}
/* update scdc status flags, 10.4.1.7 */
/* ignore STATUS_FLAGS_1, all bits are RSVD */
int scdc_status_flags(struct hdmitx_dev *hdev)
{
u8 st = 0;
u8 locked_st = 0;
scdc_rd_sink(UPDATE_0, &st);
if (st & STATUS_UPDATE) {
scdc_rd_sink(STATUS_FLAGS_0, &locked_st);
hdev->chlocked_st.clock_detected = locked_st & (1 << 0);
hdev->chlocked_st.ch0_locked = !!(locked_st & (1 << 1));
hdev->chlocked_st.ch1_locked = !!(locked_st & (2 << 1));
hdev->chlocked_st.ch2_locked = !!(locked_st & (3 << 1));
}
if (st & CED_UPDATE)
scdc_ced_cnt(hdev);
if (st & (STATUS_UPDATE | CED_UPDATE))
scdc_wr_sink(UPDATE_0, st & (STATUS_UPDATE | CED_UPDATE));
if (!hdev->chlocked_st.clock_detected)
pr_info("ced: clock undetected\n");
if (!hdev->chlocked_st.ch0_locked)
pr_info("ced: ch0 unlocked\n");
if (!hdev->chlocked_st.ch1_locked)
pr_info("ced: ch1 unlocked\n");
if (!hdev->chlocked_st.ch2_locked)
pr_info("ced: ch2 unlocked\n");
return st & (STATUS_UPDATE | CED_UPDATE);
}

View File

@@ -5009,6 +5009,13 @@ static int hdmitx_tmds_rxsense(void)
return ret;
}
/*Check from SCDC Status_Flags_0/1 */
/* 0 means TMDS ok */
static int hdmitx_tmds_cedst(struct hdmitx_dev *hdev)
{
return scdc_status_flags(hdev);
}
static int hdmitx_cntl_misc(struct hdmitx_dev *hdev, unsigned int cmd,
unsigned int argv)
{
@@ -5039,6 +5046,8 @@ static int hdmitx_cntl_misc(struct hdmitx_dev *hdev, unsigned int cmd,
break;
case MISC_TMDS_RXSENSE:
return hdmitx_tmds_rxsense();
case MISC_TMDS_CEDST:
return hdmitx_tmds_cedst(hdev);
case MISC_ESM_RESET:
if (hdev->hdcp_hpd_stick == 1) {
pr_info(HW "hdcp: stick mode\n");

View File

@@ -19,7 +19,7 @@
#define __HDMI_TX_DDC_H__
#include <linux/types.h>
#include <linux/amlogic/media/vout/hdmi_tx/hdmi_tx_module.h>
#include "hdmi_tx_module.h"
#define EDID_SLAVE 0x50
#define EDIDSEG_ADR 0x30
@@ -31,6 +31,9 @@ enum scdc_addr {
SINK_VER = 0x01,
SOURCE_VER, /* RW */
UPDATE_0 = 0x10, /* RW */
#define STATUS_UPDATE BIT(0)
#define CED_UPDATE BIT(1)
#define RR_TEST BIT(2)
UPDATE_1, /* RW */
TMDS_CFG = 0x20, /* RW */
SCRAMBLER_ST,
@@ -83,5 +86,6 @@ uint32_t hdcp_rd_hdcp14_ver(void);
uint32_t hdcp_rd_hdcp22_ver(void);
void scdc_config(struct hdmitx_dev *hdev);
void edid_read_head_8bytes(void);
int scdc_status_flags(struct hdmitx_dev *hdev);
#endif /* __HDMI_TX_SCDC_H__ */

View File

@@ -202,6 +202,23 @@ struct frac_rate_table {
u32 sync_den_dec;
};
struct ced_cnt {
bool ch0_valid;
u16 ch0_cnt:15;
bool ch1_valid;
u16 ch1_cnt:15;
bool ch2_valid;
u16 ch2_cnt:15;
u8 chksum;
};
struct scdc_locked_st {
u8 clock_detected:1;
u8 ch0_locked:1;
u8 ch1_locked:1;
u8 ch2_locked:1;
};
enum hdmi_hdr_transfer {
T_UNKNOWN = 0,
T_BT709,
@@ -302,6 +319,7 @@ struct hdmitx_dev {
struct notifier_block nb;
struct workqueue_struct *hdmi_wq;
struct workqueue_struct *rxsense_wq;
struct workqueue_struct *cedst_wq;
struct device *hdtx_dev;
struct device *pdev; /* for pinctrl*/
struct pinctrl_state *pinctrl_i2c;
@@ -310,6 +328,7 @@ struct hdmitx_dev {
struct delayed_work work_hpd_plugout;
struct delayed_work work_rxsense;
struct delayed_work work_internal_intr;
struct delayed_work work_cedst;
struct work_struct work_hdr;
struct delayed_work work_do_hdcp;
#ifdef CONFIG_AML_HDMI_TX_14
@@ -422,6 +441,9 @@ struct hdmitx_dev {
/* 0.1% clock shift, 1080p60hz->59.94hz */
unsigned int frac_rate_policy;
unsigned int rxsense_policy;
unsigned int cedst_policy;
struct ced_cnt ced_cnt;
struct scdc_locked_st chlocked_st;
/* allm_mode: 1/game, 2/graphcis, 3/photo, 4/cinema */
unsigned int allm_mode;
unsigned int sspll;
@@ -449,6 +471,7 @@ struct hdmitx_dev {
unsigned int flag_3dtb:1;
unsigned int flag_3dss:1;
unsigned int dongle_mode:1;
unsigned int cedst_en:1; /* configure in DTS */
unsigned int drm_feature;/*Direct Rander Management*/
};
@@ -564,9 +587,10 @@ struct hdmitx_dev {
#define MISC_ESM_RESET (CMD_MISC_OFFSET + 0x0d)
#define MISC_HDCP_CLKDIS (CMD_MISC_OFFSET + 0x0e)
#define MISC_TMDS_RXSENSE (CMD_MISC_OFFSET + 0x0f)
#define MISC_I2C_REACTIVE (CMD_MISC_OFFSET + 0x10)
#define MISC_I2C_RESET (CMD_MISC_OFFSET + 0x11)
#define MISC_I2C_REACTIVE (CMD_MISC_OFFSET + 0x10) /* For gxl */
#define MISC_I2C_RESET (CMD_MISC_OFFSET + 0x11) /* For g12 */
#define MISC_READ_AVMUTE_OP (CMD_MISC_OFFSET + 0x12)
#define MISC_TMDS_CEDST (CMD_MISC_OFFSET + 0x13)
/***********************************************************************
* Get State //GetState