961 lines
27 KiB
C
961 lines
27 KiB
C
#include <linux/clk.h>
|
|
#include <linux/err.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/uaccess.h>
|
|
#include <mach/msm_fb.h>
|
|
#include <mach/msm_hdmi.h>
|
|
#include <linux/interrupt.h>
|
|
|
|
// FIXME: remove this if unnecessary in the future
|
|
#ifdef CONFIG_HTC_HEADSET_MGR
|
|
#include <mach/htc_headset_mgr.h>
|
|
#endif
|
|
|
|
#if 1
|
|
#define HDMI_DBG(s...) printk("[hdmi/tpi]" s)
|
|
#else
|
|
#define HDMI_DBG(s...) do {} while (0)
|
|
#endif
|
|
|
|
#include "../include/fb-hdmi.h"
|
|
#include "../include/sil902x.h"
|
|
#include "../include/tpi.h"
|
|
|
|
#define NEW_INTEGRATE
|
|
#define DBG_POLLING 0x1
|
|
static int debug_mask;
|
|
module_param_named(debug_mask, debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP);
|
|
|
|
#define DLOG(mask, fmt, args...) \
|
|
do { \
|
|
if (debug_mask & mask) \
|
|
printk(KERN_INFO "[hdmi/sil]: "fmt, ##args); \
|
|
} while (0)
|
|
|
|
#define X1 0x01
|
|
#define AFTER_INIT 1
|
|
|
|
void HotPlugService (struct hdmi_info *hdmi);
|
|
// FIXME: should be decide by detection
|
|
static bool dsRxPoweredUp;
|
|
static bool edidDataValid;
|
|
static bool tmdsPoweredUp;
|
|
u8 pvid_mode, vid_mode = 16;
|
|
u8 systemInitialized;
|
|
|
|
void tpi_clear_interrupt(struct hdmi_info *hdmi, u8 pattern)
|
|
{
|
|
/* write "1" to clear interrupt bit, and 0 won't effect origin value. */
|
|
hdmi_write_byte(hdmi->client, TPI_INTERRUPT_STATUS_REG, pattern);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// FUNCTION : EnableInterrupts()
|
|
// PURPOSE : Enable the interrupts specified in the input parameter
|
|
// INPUT PARAMS : A bit pattern with "1" for each interrupt that needs to be
|
|
// set in the Interrupt Enable Register (TPI offset 0x3C)
|
|
// OUTPUT PARAMS : void
|
|
// GLOBALS USED : None
|
|
// RETURNS : TRUE
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
bool tpi_enable_interrupts(struct hdmi_info *hdmi, u8 Interrupt_Pattern)
|
|
{
|
|
HDMI_DBG("%s, reg=%02x, pat=%02x\n", __func__, TPI_INTERRUPT_EN, Interrupt_Pattern);
|
|
ReadSetWriteTPI(hdmi, TPI_INTERRUPT_EN, Interrupt_Pattern);
|
|
return true;
|
|
}
|
|
|
|
static void tpi_disable_interrupts(struct hdmi_info *hdmi, u8 pattern)
|
|
{
|
|
/*
|
|
HDMI_DBG("%s, reg=%02x, pat=%02x\n", __func__,
|
|
TPI_INTERRUPT_EN, pattern);
|
|
*/
|
|
ReadClearWriteTPI(hdmi, TPI_INTERRUPT_EN, pattern);
|
|
}
|
|
|
|
static void tpi_clear_pending_event(struct hdmi_info *hdmi)
|
|
{
|
|
int retry = 100;
|
|
|
|
if (hdmi->sleeping == SLEEP) return;
|
|
while (retry--) {
|
|
hdmi_write_byte(hdmi->client, 0x3c, 1);
|
|
hdmi_write_byte(hdmi->client, 0x3d, 1);
|
|
if (hdmi_read(hdmi->client, 0x3d) & 0x01)
|
|
msleep(1);
|
|
else
|
|
break;
|
|
}
|
|
if (retry < 19) HDMI_DBG("%s: retry=%d\n", __func__, 19 - retry);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// FUNCTION : ReadBackDoorRegister()
|
|
// PURPOSE : Read a 922x register value from a backdoor register
|
|
// Write:
|
|
// 1. 0xBC => Internal page num
|
|
// 2. 0xBD => Backdoor register offset
|
|
// Read:
|
|
// 3. 0xBE => Returns the backdoor register value
|
|
// INPUT PARAMS : Internal page number, backdoor register offset, pointer to
|
|
// buffer to store read value
|
|
// OUTPUT PARAMS: Buffer that stores the read value
|
|
// RETURNS : TRUE
|
|
// NOTE : This workaround is needed for the 9220/2 only.
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
int tpi_read_backdoor_register(struct hdmi_info *hdmi, u8 PageNum, u8 RegOffset)
|
|
{
|
|
// FIXME: error handling
|
|
struct i2c_client *client = hdmi->client;
|
|
|
|
/* Internal page */
|
|
hdmi_write_byte(client, TPI_INTERNAL_PAGE_REG, PageNum);
|
|
/* Indexed register */
|
|
hdmi_write_byte(client, TPI_REGISTER_OFFSET_REG, RegOffset);
|
|
/* Read value into buffer */
|
|
return hdmi_read(client, TPI_REGISTER_VALUE_REG);
|
|
}
|
|
|
|
void tpi_write_backdoor_register(struct hdmi_info *hdmi, u8 PageNum, u8 RegOffset, u8 RegValue) {
|
|
/* Internal page */
|
|
hdmi_write_byte(hdmi->client, TPI_INTERNAL_PAGE_REG, PageNum);
|
|
/* Indexed register */
|
|
hdmi_write_byte(hdmi->client, TPI_REGISTER_OFFSET_REG, RegOffset);
|
|
/* Read value into buffer */
|
|
hdmi_write_byte(hdmi->client, TPI_REGISTER_VALUE_REG, RegValue);
|
|
}
|
|
|
|
#define TPI_INTERNAL_PAGE_REG 0xBC
|
|
#define TPI_INDEXED_OFFSET_REG 0xBD
|
|
#define TPI_INDEXED_VALUE_REG 0xBE
|
|
#define INDEXED_PAGE_0 0x01
|
|
#define INDEXED_PAGE_1 0x02
|
|
#define INDEXED_PAGE_2 0x03
|
|
|
|
void ReadModifyWriteIndexedRegister(struct hdmi_info *hdmi, u8 PageNum, u8 RegOffset, u8 Mask, u8 Value)
|
|
{
|
|
u8 Tmp;
|
|
|
|
hdmi_write_byte(hdmi->client, TPI_INTERNAL_PAGE_REG, PageNum);
|
|
hdmi_write_byte(hdmi->client, TPI_INDEXED_OFFSET_REG, RegOffset);
|
|
Tmp = hdmi_read(hdmi->client, TPI_INDEXED_VALUE_REG);
|
|
|
|
Tmp &= ~Mask;
|
|
Tmp |= (Value & Mask);
|
|
|
|
hdmi_write_byte(hdmi->client, TPI_INDEXED_VALUE_REG, Tmp);
|
|
}
|
|
|
|
void ReadSetWriteTPI(struct hdmi_info *hdmi, u8 Offset, u8 Pattern)
|
|
{
|
|
u8 Tmp;
|
|
struct i2c_client *client = hdmi->client;
|
|
|
|
Tmp = hdmi_read(client, Offset);
|
|
Tmp |= Pattern;
|
|
hdmi_write_byte(client, Offset, Tmp);
|
|
}
|
|
|
|
int tpi_set_bit(struct hdmi_info *hdmi, u8 reg, u8 pattern)
|
|
{
|
|
return hdmi_write_byte(hdmi->client, reg,
|
|
hdmi_read(hdmi->client, reg) | pattern);
|
|
}
|
|
////
|
|
void ReadModifyWriteTPI(struct hdmi_info *hdmi, u8 Offset, u8 Mask, u8 Value)
|
|
{
|
|
u8 Tmp;
|
|
struct i2c_client *client = hdmi->client;
|
|
|
|
Tmp = hdmi_read(client, Offset);
|
|
Tmp &= ~Mask;
|
|
Tmp |= (Value & Mask);
|
|
hdmi_write_byte(client, Offset, Tmp);
|
|
}
|
|
////
|
|
void ReadClearWriteTPI(struct hdmi_info *hdmi, u8 Offset, u8 Pattern)
|
|
{
|
|
u8 Tmp;
|
|
|
|
Tmp = hdmi_read(hdmi->client, Offset);
|
|
Tmp &= ~Pattern;
|
|
hdmi_write_byte(hdmi->client, Offset, Tmp);
|
|
}
|
|
void tpi_clear_bit(struct hdmi_info *hdmi, u8 reg, u8 pattern)
|
|
{
|
|
hdmi_write_byte(hdmi->client, reg,
|
|
hdmi_read(hdmi->client, reg) & pattern);
|
|
}
|
|
////
|
|
|
|
/* Caller: ChangeVideoMode(), HDCP_Poll(), HotPlugServiceLoop(), RestartHDCP()
|
|
*/
|
|
|
|
void EnableTMDS(struct hdmi_info *hdmi)
|
|
{
|
|
u8 val;
|
|
#if 1
|
|
/* 0x1A[4] = 0 */
|
|
ReadClearWriteTPI(hdmi, TPI_SYSTEM_CONTROL, BIT_TMDS_OUTPUT);
|
|
|
|
if (edid_check_sink_type(hdmi))
|
|
hdmi_write_byte(hdmi->client, 0x26,
|
|
hdmi_read(hdmi->client, 0x26) & ~0x10);
|
|
|
|
#else
|
|
struct i2c_client *client = hdmi->i2c_client;
|
|
|
|
val = hdmi_read(client, TPI_SYSTEM_CONTROL);
|
|
hdmi_write_byte(client, TPI_SYSTEM_CONTROL, val & ~BIT_TMDS_OUTPUT);
|
|
HDMI_DBG("%s, reg 0x1a: %02x->%02x\n", __func__,
|
|
val, val & ~BIT_TMDS_OUTPUT);
|
|
#endif
|
|
|
|
}
|
|
|
|
/* Caller: ChangeVideoMode(), HDCP_Poll(), TPI_Poll(), RestartHDCP(),
|
|
* OnHdmiCableDisconnected()
|
|
*/
|
|
|
|
void DisableTMDS(struct hdmi_info *hdmi)
|
|
{
|
|
/* 0x1A[4] = 1 */
|
|
//ReadClearWriteTPI(hdmi, TPI_SYSTEM_CONTROL, BIT_TMDS_OUTPUT);
|
|
ReadSetWriteTPI(hdmi, TPI_SYSTEM_CONTROL, BIT_TMDS_OUTPUT);
|
|
}
|
|
static void OnDownstreamRxPoweredDown(struct hdmi_info *hdmi)
|
|
{
|
|
HDMI_DBG("%s\n", __func__);
|
|
dsRxPoweredUp = false;
|
|
hdcp_off(hdmi);
|
|
}
|
|
|
|
static void OnDownstreamRxPoweredUp(struct hdmi_info *hdmi)
|
|
{
|
|
HDMI_DBG("%s\n", __func__);
|
|
dsRxPoweredUp = true;
|
|
HotPlugService(hdmi);
|
|
#ifdef CONFIG_HTC_HEADSET_MGR
|
|
/* send cable in event */
|
|
switch_send_event(BIT_HDMI_CABLE, 1);
|
|
HDMI_DBG("Cable inserted.\n");
|
|
#endif
|
|
pvid_mode = vid_mode;
|
|
hdmi_active9022_dup(hdmi->client);
|
|
}
|
|
|
|
bool GetDDC_Access(struct hdmi_info *hdmi, u8* SysCtrlRegVal)
|
|
{
|
|
u8 sysCtrl, TPI_ControlImage, DDCReqTimeout = T_DDC_ACCESS;
|
|
|
|
HDMI_DBG("%s\n", __func__);
|
|
/* Read and store original value. Will be passed into ReleaseDDC() */
|
|
sysCtrl = hdmi_read(hdmi->client, TPI_SYSTEM_CONTROL);
|
|
*SysCtrlRegVal = sysCtrl;
|
|
|
|
sysCtrl |= BIT_DDC_BUS_REQ;
|
|
hdmi_write_byte(hdmi->client, TPI_SYSTEM_CONTROL, sysCtrl);
|
|
|
|
/* Loop till 0x1A[1] reads "1" */
|
|
while (DDCReqTimeout--) {
|
|
TPI_ControlImage = hdmi_read(hdmi->client, TPI_SYSTEM_CONTROL);
|
|
|
|
/* When 0x1A[1] reads "1" */
|
|
if (TPI_ControlImage & BIT_DDC_BUS_GRANT) {
|
|
sysCtrl |= BIT_DDC_BUS_GRANT;
|
|
/* lock host DDC bus access (0x1A[2:1] = 11) */
|
|
hdmi_write_byte(hdmi->client, TPI_SYSTEM_CONTROL, sysCtrl);
|
|
return true;
|
|
}
|
|
/* 0x1A[2] = "1" - Requst the DDC bus */
|
|
hdmi_write_byte(hdmi->client, TPI_SYSTEM_CONTROL, sysCtrl);
|
|
mdelay(200);
|
|
}
|
|
|
|
/* Failure... restore original value. */
|
|
hdmi_write_byte(hdmi->client, TPI_SYSTEM_CONTROL, sysCtrl);
|
|
return false;
|
|
}
|
|
|
|
bool ReleaseDDC(struct hdmi_info *hdmi, u8 SysCtrlRegVal)
|
|
{
|
|
u8 DDCReqTimeout = T_DDC_ACCESS, TPI_ControlImage;
|
|
|
|
HDMI_DBG("%s\n", __func__);
|
|
/* Just to be sure bits [2:1] are 0 before it is written */
|
|
SysCtrlRegVal &= ~(0x6);
|
|
/* Loop till 0x1A[1] reads "0" */
|
|
while (DDCReqTimeout--) {
|
|
/* Cannot use ReadClearWriteTPI() here. A read of
|
|
* TPI_SYSTEM_CONTROL is invalid while DDC is granted.
|
|
* Doing so will return 0xFF, and cause an invalid value to be
|
|
* written back.
|
|
*/
|
|
/* 0x1A[2:1] = "0" - release the DDC bus */
|
|
//ReadClearWriteTPI(TPI_SYSTEM_CONTROL,BITS_2_1);
|
|
|
|
hdmi_write_byte(hdmi->client, TPI_SYSTEM_CONTROL, SysCtrlRegVal);
|
|
TPI_ControlImage = hdmi_read(hdmi->client, TPI_SYSTEM_CONTROL);
|
|
/* When 0x1A[2:1] read "0" */
|
|
if (!(TPI_ControlImage & 0x6))
|
|
return true;
|
|
}
|
|
|
|
/* Failed to release DDC bus control */
|
|
return false;
|
|
}
|
|
|
|
int tpi_read_edid(struct hdmi_info *hdmi)
|
|
{
|
|
u8 SysCtrlReg;
|
|
int ret, edid_blocks = 0;
|
|
struct i2c_msg msg;
|
|
u8 i2c_buff[2];
|
|
u8 pbuf[] = {1, 0, 1, 128} ;
|
|
|
|
struct i2c_msg paging_msg[] = {
|
|
{
|
|
.addr = 0x30, .flags = 0, .len = 1, .buf = &pbuf[0],
|
|
},
|
|
{
|
|
.addr = 0x50, .flags = 0, .len = 1, .buf = &pbuf[1],
|
|
},
|
|
{ //Block-2
|
|
.addr = 0x50, .flags = I2C_M_RD, .len = 128, .buf = &hdmi->edid_buf[256],
|
|
},
|
|
{
|
|
.addr = 0x30, .flags = 0, .len = 1, .buf = &pbuf[2],
|
|
},
|
|
{
|
|
.addr = 0x50, .flags = 0, .len = 1, .buf = &pbuf[3],
|
|
},
|
|
{ //Block-3
|
|
.addr = 0x50, .flags = I2C_M_RD, .len = 128, .buf = &hdmi->edid_buf[384],
|
|
},
|
|
};
|
|
|
|
HDMI_DBG("%s\n", __func__);
|
|
#if 0
|
|
DisableTMDS(hdmi);
|
|
#else
|
|
u8 val;
|
|
val = hdmi_read(hdmi->client, TPI_SYSTEM_CONTROL);
|
|
//hdmi_write_byte(hdmi->client, TPI_SYSTEM_CONTROL, val|BIT_4|BIT_6);
|
|
hdmi_write_byte(hdmi->client, TPI_SYSTEM_CONTROL, val|BIT_4);
|
|
#endif
|
|
|
|
if (!GetDDC_Access(hdmi, &SysCtrlReg)) {
|
|
pr_err("%s: DDC bus request failed\n", __func__);
|
|
return DDC_BUS_REQ_FAILURE;
|
|
}
|
|
|
|
// Block-0
|
|
memset(hdmi->edid_buf, 0, 512);
|
|
|
|
msg.addr = 0x50;
|
|
msg.flags = 0;
|
|
msg.len = 1;
|
|
msg.buf = hdmi->edid_buf;
|
|
ret = i2c_transfer(hdmi->client->adapter, &msg, 1);
|
|
if (ret < 0)
|
|
dev_err(&hdmi->client->dev, "%s: i2c transfer error\n", __func__);
|
|
|
|
msg.addr = 0x50;
|
|
msg.flags = I2C_M_RD;
|
|
msg.len = 128;
|
|
msg.buf = hdmi->edid_buf;
|
|
ret = i2c_transfer(hdmi->client->adapter, &msg, 1);
|
|
if (ret < 0) {
|
|
dev_err(&hdmi->client->dev, "%s: i2c transfer error\n", __func__);
|
|
goto end_read_edid;
|
|
} else {
|
|
if (hdmi->edid_buf[0x7e] <= 3)
|
|
edid_blocks = hdmi->edid_buf[0x7e] ;
|
|
|
|
dev_info(&hdmi->client->dev, "EDID blocks = %d\n", edid_blocks);
|
|
|
|
if (edid_blocks == 0 ) {
|
|
goto end_read_edid;
|
|
}
|
|
// Block-1
|
|
msg.addr = 0x50;
|
|
msg.flags = 0;
|
|
msg.len = 1;
|
|
i2c_buff[0] = 128;
|
|
msg.buf = i2c_buff;
|
|
ret = i2c_transfer(hdmi->client->adapter, &msg, 1);
|
|
|
|
msg.addr = 0x50;
|
|
msg.flags = I2C_M_RD;
|
|
msg.len = 128;
|
|
msg.buf = &hdmi->edid_buf[128];
|
|
ret = i2c_transfer(hdmi->client->adapter, &msg, 1);
|
|
}
|
|
|
|
if (edid_blocks > 1) {
|
|
// block 2/3
|
|
i2c_transfer(hdmi->client->adapter, paging_msg, 3);
|
|
i2c_transfer(hdmi->client->adapter, &paging_msg[3], 3);
|
|
}
|
|
|
|
end_read_edid:
|
|
if (!ReleaseDDC(hdmi, SysCtrlReg)) {
|
|
pr_err("%s: DDC bus release failed\n", __func__);
|
|
return DDC_BUS_REQ_FAILURE;
|
|
}
|
|
|
|
edid_simple_parsing(hdmi);
|
|
|
|
return 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// FUNCTION : HotPlugService()
|
|
// PURPOSE : Implement Hot Plug Service Loop activities
|
|
// INPUT PARAMS : None
|
|
// OUTPUT PARAMS: void
|
|
// GLOBALS USED : LinkProtectionLevel
|
|
// RETURNS : An error code that indicates success or cause of failure
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
extern bool HDCP_TxSupports;
|
|
static bool tmdsPoweredUp;
|
|
void HotPlugService (struct hdmi_info *hdmi)
|
|
{
|
|
HDMI_DBG("%s\n", __func__);
|
|
|
|
mutex_lock(&hdmi->lock);
|
|
tpi_disable_interrupts(hdmi, 0xFF);
|
|
|
|
/*
|
|
// use 1st mode supported by sink
|
|
//vid_mode = EDID_Data.VideoDescriptor[0];
|
|
vid_mode = 0;
|
|
*/
|
|
avc_init_video(hdmi, vid_mode, X1, AFTER_INIT);
|
|
|
|
hdmi_write_byte(hdmi->client, HDMI_POWER, 0);
|
|
if (edid_check_sink_type(hdmi))
|
|
avc_send_avi_info_frames(hdmi);
|
|
|
|
/* This check needs to be changed to if HDCP is required by the content
|
|
once support has been added by RX-side library. */
|
|
if (HDCP_TxSupports == true) {
|
|
HDMI_DBG("TMDS -> Enabled\n");
|
|
/* turn on black mode will lost around 3 secs frames thus remove it */
|
|
//SetInputColorSpace(hdmi, INPUT_COLOR_SPACE_BLACK_MODE);
|
|
#if 1
|
|
ReadModifyWriteTPI(hdmi, TPI_SYSTEM_CONTROL,
|
|
LINK_INTEGRITY_MODE_MASK | TMDS_OUTPUT_CONTROL_MASK,
|
|
LINK_INTEGRITY_DYNAMIC | TMDS_OUTPUT_CONTROL_ACTIVE);
|
|
#else
|
|
ReadModifyWriteTPI(hdmi, TPI_SYSTEM_CONTROL,
|
|
LINK_INTEGRITY_MODE_MASK | TMDS_OUTPUT_CONTROL_MASK,
|
|
LINK_INTEGRITY_DYNAMIC);
|
|
#endif
|
|
tmdsPoweredUp = true;
|
|
} else {
|
|
EnableTMDS(hdmi);
|
|
}
|
|
|
|
if (edid_check_sink_type(hdmi))
|
|
avc_set_basic_audio(hdmi);
|
|
else
|
|
SetAudioMute(hdmi, AUDIO_MUTE_MUTED);
|
|
|
|
tpi_enable_interrupts(hdmi, HOT_PLUG_EVENT | RX_SENSE_EVENT |
|
|
AUDIO_ERROR_EVENT | SECURITY_CHANGE_EVENT |
|
|
V_READY_EVENT | HDCP_CHANGE_EVENT);
|
|
|
|
//complete(&hdmi->hotplug_completion);
|
|
mutex_unlock(&hdmi->lock);
|
|
}
|
|
|
|
static bool tpi_start(struct hdmi_info *hdmi)
|
|
{
|
|
u8 devID = 0x00;
|
|
u16 wID = 0x0000;
|
|
|
|
hdmi_write_byte(hdmi->client, TPI_ENABLE, 0x00); // Write "0" to 72:C7 to start HW TPI mode
|
|
mdelay(100);
|
|
|
|
devID = tpi_read_backdoor_register(hdmi, 0x00, 0x03);
|
|
wID = devID;
|
|
wID <<= 8;
|
|
devID = tpi_read_backdoor_register(hdmi, 0x00, 0x02);
|
|
wID |= devID;
|
|
devID = hdmi_read(hdmi->client, TPI_DEVICE_ID);
|
|
HDMI_DBG("%s, ID=%04X\n", __func__, (u32)wID);
|
|
|
|
if (devID == SiI_DEVICE_ID) {
|
|
return true;
|
|
}
|
|
|
|
pr_err("%s: Unsupported TX\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
bool tpi_init(struct hdmi_info *hdmi)
|
|
{
|
|
tmdsPoweredUp = false;
|
|
hdmi->cable_connected = false;
|
|
dsRxPoweredUp = false;
|
|
edidDataValid = false;
|
|
|
|
/* Enable HW TPI mode, check device ID */
|
|
if (tpi_start(hdmi)) {
|
|
hdcp_init(hdmi);
|
|
return true;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void SetAudioMute(struct hdmi_info *hdmi, u8 audioMute)
|
|
{
|
|
ReadModifyWriteTPI(hdmi, TPI_AUDIO_INTERFACE_REG, AUDIO_MUTE_MASK, audioMute);
|
|
}
|
|
|
|
void SetInputColorSpace(struct hdmi_info *hdmi, u8 inputColorSpace)
|
|
{
|
|
ReadModifyWriteTPI(hdmi, TPI_INPUT_FORMAT_REG, INPUT_COLOR_SPACE_MASK, inputColorSpace);
|
|
/* Must be written for previous write to take effect. Just write read value unmodified. */
|
|
ReadModifyWriteTPI(hdmi, TPI_END_RIGHT_BAR_MSB, 0x00, 0x00);
|
|
}
|
|
|
|
static char edid_hex_buff[2048];
|
|
int lcdc_enable_video(void);
|
|
int lcdc_disable_video(void);
|
|
void tpi_cable_conn(struct hdmi_info *hdmi)
|
|
{
|
|
HDMI_DBG("%s\n", __func__);
|
|
|
|
hdmi->cable_connected = true;
|
|
tpi_write_backdoor_register(hdmi, INTERNAL_PAGE_0, 0xCE, 0x00); // Clear BStatus
|
|
tpi_write_backdoor_register(hdmi, INTERNAL_PAGE_0, 0xCF, 0x00);
|
|
|
|
//-----------------------------------------------
|
|
hdmi_write_byte(hdmi->client, 0x09, 0x03);
|
|
hdmi_write_byte(hdmi->client, 0x19, 0x00); // go to blank mode, avoid screen noise
|
|
|
|
/*
|
|
HDMI_DBG("solomon: H/V total=%02x, %02x, %02x, %02x\n",
|
|
hdmi_read(hdmi->client, 0x6a),
|
|
hdmi_read(hdmi->client, 0x6b),
|
|
hdmi_read(hdmi->client, 0x6c),
|
|
hdmi_read(hdmi->client, 0x6d)
|
|
);
|
|
*/
|
|
|
|
lcdc_enable_video();
|
|
msleep(160);
|
|
/*
|
|
//clk_set_rate(hdmi->ebi1_clk, 120000000);
|
|
HDMI_DBG("solomon: H/V total=%02x, %02x, %02x, %02x\n",
|
|
hdmi_read(hdmi->client, 0x6a),
|
|
hdmi_read(hdmi->client, 0x6b),
|
|
hdmi_read(hdmi->client, 0x6c),
|
|
hdmi_read(hdmi->client, 0x6d)
|
|
);
|
|
*/
|
|
EnableTMDS(hdmi);
|
|
|
|
//-----------------------------------------------
|
|
|
|
tpi_read_edid(hdmi);
|
|
memset(edid_hex_buff, 0, 2048);
|
|
edid_dump_hex(hdmi->edid_buf, 256, edid_hex_buff, 2048);
|
|
printk("EDID data:\n%s\n=====", edid_hex_buff);
|
|
/* select output mode (HDMI/DVI) according to sink capabilty */
|
|
if (edid_check_sink_type(hdmi))
|
|
ReadModifyWriteTPI(hdmi, TPI_SYSTEM_CONTROL, OUTPUT_MODE_MASK, OUTPUT_MODE_HDMI);
|
|
else
|
|
ReadModifyWriteTPI(hdmi, TPI_SYSTEM_CONTROL, OUTPUT_MODE_MASK, OUTPUT_MODE_DVI);
|
|
|
|
hdmi->first = false;
|
|
#if 0
|
|
#ifdef CONFIG_HTC_HEADSET_MGR
|
|
/* send cable in event */
|
|
switch_send_event(BIT_HDMI_CABLE, 1);
|
|
HDMI_DBG("Cable inserted.\n");
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
void tpi_cable_disconn(struct hdmi_info *hdmi, bool into_d3)
|
|
{
|
|
HDMI_DBG("%s, into_d3=%d\n", __func__, into_d3);
|
|
|
|
hdmi->cable_connected = false;
|
|
dsRxPoweredUp = false;
|
|
edidDataValid = false;
|
|
hdcp_off(hdmi);
|
|
DisableTMDS(hdmi);
|
|
#if 1
|
|
/* wait for debounce */
|
|
msleep(20);
|
|
tpi_clear_pending_event(hdmi);
|
|
#else
|
|
reg = hdmi_read(hdmi->client, 0x3d);
|
|
if (!(reg & 0x0c))
|
|
tpi_clear_pending_event(hdmi);
|
|
#endif
|
|
if (into_d3) {
|
|
mutex_lock(&hdmi->lock);
|
|
HDMI_DBG("%s, playing=%d\n", __func__, hdmi->user_playing);
|
|
if (false == hdmi->user_playing)
|
|
lcdc_disable_video();
|
|
clk_set_rate(hdmi->ebi1_clk, 0);
|
|
hdmi_standby(hdmi);
|
|
hdmi->power(2);
|
|
memset(hdmi->edid_buf, 0, 512);
|
|
mutex_unlock(&hdmi->lock);
|
|
}
|
|
#ifdef CONFIG_HTC_HEADSET_MGR
|
|
HDMI_DBG("Cable unplugged.\n");
|
|
switch_send_event(BIT_HDMI_CABLE, 0);
|
|
#endif
|
|
}
|
|
|
|
static char *str_debug_interrupt[] = {
|
|
"HOT_PLUG_EVENT\t\t\t",
|
|
"RECEIVER_SENSE_EVENT\t\t",
|
|
"HOT_PLUG_PIN_STATE\t\t",
|
|
"RX_SENSE_MASK\t\t\t",
|
|
"AUDIO_ERROR_EVENT\t\t",
|
|
"HDCP_SECURITY_CHANGE_EVENT\t",
|
|
"HDCP_VPRIME_VALUE_READY_EVENT\t",
|
|
"HDCP_AUTH_STATUS_CHANGE_EVENT\t",
|
|
};
|
|
|
|
void tpi_debug_interrupt(struct hdmi_info *hdmi, u8 old_status, u8 new_status)
|
|
{
|
|
int i, diff, on_off;
|
|
HDMI_DBG("%s: status changed, %02x to %02x\n", __func__,
|
|
old_status, new_status);
|
|
for (i = 7; i >= 0; i--) {
|
|
diff = (old_status ^ new_status) & (1 << i);
|
|
if (!diff)
|
|
continue;
|
|
on_off = new_status & (1 << i);
|
|
HDMI_DBG("%d-%s->%s\n", i, str_debug_interrupt[i],
|
|
on_off ? "on" : "off");
|
|
}
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// FUNCTION : TPI_Poll ()
|
|
// PURPOSE : Poll Interrupt Status register for new interrupts
|
|
// INPUT PARAMS : None
|
|
// OUTPUT PARAMS: None
|
|
// GLOBALS USED : LinkProtectionLevel
|
|
// RETURNS : None
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
static u8 last_status = 0;
|
|
static void tpi_poll(struct hdmi_info *hdmi)
|
|
{
|
|
u8 status, orig_status;
|
|
int retry = 20;
|
|
|
|
mutex_lock(&hdmi->polling_lock);
|
|
orig_status = status = hdmi_read(hdmi->client, TPI_INTERRUPT_STATUS_REG);
|
|
if (last_status != status) {
|
|
tpi_debug_interrupt(hdmi, last_status, status);
|
|
}
|
|
last_status = status;
|
|
DLOG(DBG_POLLING, "%s, INT_STAT=%02x\n", __func__, status);
|
|
#if 0
|
|
if (status & HOT_PLUG_EVENT) {
|
|
#else
|
|
if (hdmi->first || status & HOT_PLUG_EVENT) {
|
|
if (hdmi->first) hdmi->first = false;
|
|
#endif
|
|
// Enable HPD interrupt bit
|
|
ReadSetWriteTPI(hdmi, TPI_INTERRUPT_ENABLE_REG, HOT_PLUG_EVENT);
|
|
// Repeat this loop while cable is bouncing:
|
|
do {
|
|
//DLOG(DBG_POLLING, "TPI: Interrupt status image - 2= %02x\n", status);
|
|
hdmi_write_byte(hdmi->client, TPI_INTERRUPT_STATUS_REG, HOT_PLUG_EVENT);
|
|
// Delay for metastability protection and to help filter out connection bouncing
|
|
mdelay(T_HPD_DELAY);
|
|
// Read Interrupt status register
|
|
status = hdmi_read(hdmi->client, TPI_INTERRUPT_STATUS_REG);
|
|
//DLOG(DBG_POLLING, "TPI: Interrupt status image - 3= %02x\n", status);
|
|
if (!retry--) {
|
|
HDMI_DBG("%s: retry failed\n", __func__);
|
|
break;
|
|
}
|
|
|
|
} while (status & HOT_PLUG_EVENT);// loop as long as HP interrupts recur
|
|
DLOG(DBG_POLLING, "int status: %02x, after debouncing: %02x\n",
|
|
orig_status, status);
|
|
|
|
//DLOG(DBG_POLLING, "TPI->hdmiCableConnected = %d\n", hdmi->cable_connected);
|
|
if (((status & HOT_PLUG_STATE) >> 2) != hdmi->cable_connected) {
|
|
DLOG(DBG_POLLING, "cable status changed: from %d to %d\n",
|
|
hdmi->cable_connected, !!(status & HOT_PLUG_STATE));
|
|
//DLOG(DBG_POLLING, "TPI-> CONDITION\n");
|
|
if (hdmi->cable_connected == true)
|
|
tpi_cable_disconn(hdmi, status & 0x8 ? false : true);
|
|
else {
|
|
tpi_cable_conn(hdmi);
|
|
ReadModifyWriteIndexedRegister(hdmi, INDEXED_PAGE_0, 0x0A, 0x08, 0x08);
|
|
}
|
|
if (hdmi->cable_connected == false) {
|
|
mutex_unlock(&hdmi->polling_lock);
|
|
return;
|
|
}
|
|
} else if ( false == hdmi->cable_connected)
|
|
/* only occur while booting without cable attached. */
|
|
tpi_cable_disconn(hdmi, true);
|
|
}
|
|
|
|
// Check rx power
|
|
if (((status & RX_SENSE_STATE) >> 3) != dsRxPoweredUp)
|
|
{
|
|
if (hdmi->cable_connected == true) {
|
|
if (dsRxPoweredUp == true)
|
|
OnDownstreamRxPoweredDown(hdmi);
|
|
else
|
|
OnDownstreamRxPoweredUp(hdmi);
|
|
}
|
|
tpi_clear_interrupt(hdmi, RX_SENSE_EVENT);
|
|
}
|
|
|
|
// Check if Audio Error event has occurred:
|
|
if (status & AUDIO_ERROR_EVENT)
|
|
// The hardware handles the event without need for host intervention (PR, p. 31)
|
|
tpi_clear_interrupt(hdmi, AUDIO_ERROR_EVENT);
|
|
|
|
if (hdmi->video_streaming) {
|
|
if ((hdmi->cable_connected == true) && (dsRxPoweredUp == true))
|
|
hdcp_check_status(hdmi, status);
|
|
}
|
|
mutex_unlock(&hdmi->polling_lock);
|
|
}
|
|
|
|
static void tpi_work_func(struct work_struct *work)
|
|
{
|
|
u8 reg = 0;
|
|
struct hdmi_info *hdmi =
|
|
container_of(work, struct hdmi_info, polling_work);
|
|
|
|
if (hdmi->sleeping == SLEEP) {
|
|
mutex_lock(&hdmi->lock);
|
|
hdmi->power(3);
|
|
hdmi_wakeup(hdmi);
|
|
tpi_init(hdmi);
|
|
hdcp_off(hdmi);
|
|
mutex_unlock(&hdmi->lock);
|
|
}
|
|
|
|
tpi_poll(hdmi);
|
|
#if 1
|
|
mutex_lock(&hdmi->lock);
|
|
if (hdmi->sleeping == AWAKE)
|
|
reg = hdmi_read(hdmi->client, 0x3d) & 0x0c;
|
|
if (hdmi->cable_connected || reg) {
|
|
hdmi->polling = true;
|
|
mod_timer(&hdmi->timer, jiffies + INTERVAL_HDCP_POLLING);
|
|
} else {
|
|
enable_irq(hdmi->client->irq);
|
|
hdmi->isr_enabled = true;
|
|
hdmi->polling = false;
|
|
}
|
|
mutex_unlock(&hdmi->lock);
|
|
#else
|
|
if (hdmi->sleeping == AWAKE) {
|
|
reg = hdmi_read(hdmi->client, 0x3d);
|
|
if (reg & 0x0c) {
|
|
hdmi->polling = true;
|
|
mod_timer(&hdmi->timer, jiffies + INTERVAL_HDCP_POLLING);
|
|
} else {
|
|
tpi_clear_pending_event(hdmi);
|
|
}
|
|
}
|
|
|
|
if (hdmi->cable_connected ) {
|
|
hdmi->polling = true;
|
|
mod_timer(&hdmi->timer, jiffies + INTERVAL_HDCP_POLLING);
|
|
} else {
|
|
enable_irq(hdmi->client->irq);
|
|
hdmi->isr_enabled = true;
|
|
hdmi->polling = false;
|
|
}
|
|
#endif
|
|
/*
|
|
HDMI_DBG("after polling: reg=%02x, conn=%d, isr=%d, polling=%d\n",
|
|
reg, hdmi->cable_connected, hdmi->isr_enabled, hdmi->polling);
|
|
*/
|
|
}
|
|
|
|
static void tpi_timer_func(unsigned long arg)
|
|
{
|
|
struct hdmi_info *hdmi = (struct hdmi_info *) arg;
|
|
|
|
schedule_work(&hdmi->polling_work);
|
|
}
|
|
|
|
int tpi_prepare(struct hdmi_info *hdmi)
|
|
{
|
|
HDMI_DBG("%s\n", __func__);
|
|
init_timer(&hdmi->timer);
|
|
hdmi->timer.data = (unsigned long)hdmi;
|
|
hdmi->timer.function = tpi_timer_func;
|
|
hdmi->cable_connected = false;
|
|
|
|
init_completion(&hdmi->hotplug_completion);
|
|
INIT_WORK(&hdmi->polling_work, tpi_work_func);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*============================================================================*/
|
|
#if defined(HDMI_DEBUGFS)
|
|
static ssize_t tpi_dbg_open(struct inode *inode, struct file *file)
|
|
{
|
|
file->private_data = inode->i_private;
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t tpi_ddc_request_read(struct file *filp, char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
//struct hdmi_info *hdmi = (struct hdmi_info*)filp->private_data;
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t tpi_ddc_request_write(struct file *filp, const char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t tpi_isr_read(struct file *filp, char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
int n = 0;
|
|
char buffer[4];
|
|
struct hdmi_info *hdmi = (struct hdmi_info*)filp->private_data;
|
|
|
|
HDMI_DBG("%s\n", __func__);
|
|
n = scnprintf(buffer, 4, "%d\n", hdmi->isr_enabled);
|
|
n++;
|
|
buffer[n] = 0;
|
|
return simple_read_from_buffer(buf, count, ppos, buffer, n);
|
|
}
|
|
|
|
static ssize_t tpi_polling_read(struct file *filp, char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
int n = 0;
|
|
char buffer[4];
|
|
struct hdmi_info *hdmi = (struct hdmi_info*)filp->private_data;
|
|
|
|
HDMI_DBG("%s\n", __func__);
|
|
n = scnprintf(buffer, 4, "%d\n", hdmi->polling);
|
|
n++;
|
|
buffer[n] = 0;
|
|
return simple_read_from_buffer(buf, count, ppos, buffer, n);
|
|
}
|
|
|
|
static ssize_t tpi_int_status_read(struct file *filp, char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
int n = 0;
|
|
char buffer[8];
|
|
struct hdmi_info *hdmi = (struct hdmi_info*)filp->private_data;
|
|
|
|
HDMI_DBG("%s\n", __func__);
|
|
n = scnprintf(buffer, 8, "%02x\n", hdmi_read(hdmi->client, 0x3d));
|
|
n++;
|
|
buffer[n] = 0;
|
|
return simple_read_from_buffer(buf, count, ppos, buffer, n);
|
|
}
|
|
|
|
static ssize_t tpi_int_enable_read(struct file *filp, char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
int n = 0;
|
|
char buffer[8];
|
|
struct hdmi_info *hdmi = (struct hdmi_info*)filp->private_data;
|
|
|
|
HDMI_DBG("%s\n", __func__);
|
|
n = scnprintf(buffer, 8, "%02x\n", hdmi_read(hdmi->client, 0x3c));
|
|
n++;
|
|
buffer[n] = 0;
|
|
return simple_read_from_buffer(buf, count, ppos, buffer, n);
|
|
}
|
|
|
|
static ssize_t tpi_avc_read(struct file *filp, char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
int n = 0;
|
|
char buffer[8];
|
|
struct hdmi_info *hdmi = (struct hdmi_info*)filp->private_data;
|
|
|
|
HDMI_DBG("%s\n", __func__);
|
|
/*
|
|
n = scnprintf(buffer, 8, "%02x\n", hdmi_read(hdmi->client, 0x3c));
|
|
n++;
|
|
buffer[n] = 0;
|
|
return simple_read_from_buffer(buf, count, ppos, buffer, n);
|
|
*/
|
|
hdmi_active9022(hdmi->client);
|
|
return 0;
|
|
}
|
|
|
|
static struct file_operations tpi_debugfs_fops[] = {
|
|
{
|
|
.open = tpi_dbg_open,
|
|
.read = tpi_ddc_request_read,
|
|
.write = tpi_ddc_request_write,
|
|
},
|
|
{
|
|
.open = tpi_dbg_open,
|
|
.read = tpi_isr_read,
|
|
},
|
|
{
|
|
.open = tpi_dbg_open,
|
|
.read = tpi_polling_read,
|
|
},
|
|
{
|
|
.open = tpi_dbg_open,
|
|
.read = tpi_int_status_read,
|
|
},
|
|
{
|
|
.open = tpi_dbg_open,
|
|
.read = tpi_int_enable_read,
|
|
},
|
|
{
|
|
.open = tpi_dbg_open,
|
|
.read = tpi_avc_read,
|
|
},
|
|
};
|
|
|
|
int tpi_debugfs_init(struct hdmi_info *hdmi)
|
|
{
|
|
struct dentry *tpi_dent;
|
|
|
|
tpi_dent = debugfs_create_dir("tpi", hdmi->debug_dir);
|
|
if (IS_ERR(tpi_dent))
|
|
return PTR_ERR(tpi_dent);
|
|
|
|
//FIXME: error handling
|
|
debugfs_create_file("ddc_request", 0644, tpi_dent, hdmi,
|
|
&tpi_debugfs_fops[0]);
|
|
debugfs_create_file("isr_enabled", 0444, tpi_dent, hdmi,
|
|
&tpi_debugfs_fops[1]);
|
|
debugfs_create_file("polling", 0444, tpi_dent, hdmi,
|
|
&tpi_debugfs_fops[2]);
|
|
debugfs_create_file("int_stat", 0444, tpi_dent, hdmi,
|
|
&tpi_debugfs_fops[3]);
|
|
debugfs_create_file("int_ena", 0444, tpi_dent, hdmi,
|
|
&tpi_debugfs_fops[4]);
|
|
debugfs_create_file("avc", 0444, tpi_dent, hdmi,
|
|
&tpi_debugfs_fops[5]);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|