Initial Contribution
msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142
Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/drivers/video/msm/adv7520.c b/drivers/video/msm/adv7520.c
new file mode 100644
index 0000000..0900f23
--- /dev/null
+++ b/drivers/video/msm/adv7520.c
@@ -0,0 +1,1005 @@
+/* Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/i2c.h>
+#include <linux/types.h>
+#include <linux/bitops.h>
+#include <linux/adv7520.h>
+#include <linux/time.h>
+#include <linux/completion.h>
+#include <linux/wakelock.h>
+#include <asm/atomic.h>
+#include "msm_fb.h"
+
+#define DEBUG
+#define DEV_DBG_PREFIX "HDMI: "
+
+#include "external_common.h"
+
+/* #define PORT_DEBUG */
+/* #define TESTING_FORCE_480p */
+
+#define HPD_DUTY_CYCLE 4 /*secs*/
+
+static struct external_common_state_type hdmi_common;
+
+static struct i2c_client *hclient;
+
+static bool chip_power_on = FALSE; /* For chip power on/off */
+static bool enable_5v_on = FALSE;
+static bool hpd_power_on = FALSE;
+static atomic_t comm_power_on; /* For dtv power on/off (I2C) */
+static int suspend_count;
+
+static u8 reg[256]; /* HDMI panel registers */
+
+struct hdmi_data {
+ struct msm_hdmi_platform_data *pd;
+ struct work_struct isr_work;
+};
+static struct hdmi_data *dd;
+static struct work_struct hpd_timer_work;
+
+#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT
+static struct work_struct hdcp_handle_work;
+static int hdcp_activating;
+static DEFINE_MUTEX(hdcp_state_mutex);
+static int has_hdcp_hw_support = true;
+#endif
+
+static struct timer_list hpd_timer;
+static struct timer_list hpd_duty_timer;
+static struct work_struct hpd_duty_work;
+static unsigned int monitor_sense;
+static boolean hpd_cable_chg_detected;
+
+struct wake_lock wlock;
+
+/* Change HDMI state */
+static void change_hdmi_state(int online)
+{
+ if (!external_common_state)
+ return;
+
+ mutex_lock(&external_common_state_hpd_mutex);
+ external_common_state->hpd_state = online;
+ mutex_unlock(&external_common_state_hpd_mutex);
+
+ if (!external_common_state->uevent_kobj)
+ return;
+
+ if (online)
+ kobject_uevent(external_common_state->uevent_kobj,
+ KOBJ_ONLINE);
+ else
+ kobject_uevent(external_common_state->uevent_kobj,
+ KOBJ_OFFLINE);
+ DEV_INFO("adv7520_uevent: %d [suspend# %d]\n", online, suspend_count);
+}
+
+
+/*
+ * Read a value from a register on ADV7520 device
+ * If sucessfull returns value read , otherwise error.
+ */
+static u8 adv7520_read_reg(struct i2c_client *client, u8 reg)
+{
+ int err;
+ struct i2c_msg msg[2];
+ u8 reg_buf[] = { reg };
+ u8 data_buf[] = { 0 };
+
+ if (!client->adapter)
+ return -ENODEV;
+ if (!atomic_read(&comm_power_on)) {
+ DEV_WARN("%s: WARN: missing GPIO power\n", __func__);
+ return -ENODEV;
+ }
+
+ msg[0].addr = client->addr;
+ msg[0].flags = 0;
+ msg[0].len = 1;
+ msg[0].buf = reg_buf;
+
+ msg[1].addr = client->addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].len = 1;
+ msg[1].buf = data_buf;
+
+ err = i2c_transfer(client->adapter, msg, 2);
+
+ if (err < 0) {
+ DEV_INFO("%s: I2C err: %d\n", __func__, err);
+ return err;
+ }
+
+#ifdef PORT_DEBUG
+ DEV_INFO("HDMI[%02x] [R] %02x\n", reg, data);
+#endif
+ return *data_buf;
+}
+
+/*
+ * Write a value to a register on adv7520 device.
+ * Returns zero if successful, or non-zero otherwise.
+ */
+static int adv7520_write_reg(struct i2c_client *client, u8 reg, u8 val)
+{
+ int err;
+ struct i2c_msg msg[1];
+ unsigned char data[2];
+
+ if (!client->adapter)
+ return -ENODEV;
+ if (!atomic_read(&comm_power_on)) {
+ DEV_WARN("%s: WARN: missing GPIO power\n", __func__);
+ return -ENODEV;
+ }
+
+ msg->addr = client->addr;
+ msg->flags = 0;
+ msg->len = 2;
+ msg->buf = data;
+ data[0] = reg;
+ data[1] = val;
+
+ err = i2c_transfer(client->adapter, msg, 1);
+ if (err >= 0)
+ return 0;
+#ifdef PORT_DEBUG
+ DEV_INFO("HDMI[%02x] [W] %02x [%d]\n", reg, val, err);
+#endif
+ return err;
+}
+
+#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT
+static void adv7520_close_hdcp_link(void)
+{
+ if (!external_common_state->hdcp_active && !hdcp_activating)
+ return;
+
+ DEV_INFO("HDCP: Close link\n");
+
+ reg[0xD5] = adv7520_read_reg(hclient, 0xD5);
+ reg[0xD5] &= 0xFE;
+ adv7520_write_reg(hclient, 0xD5, (u8)reg[0xD5]);
+
+ reg[0x16] = adv7520_read_reg(hclient, 0x16);
+ reg[0x16] &= 0xFE;
+ adv7520_write_reg(hclient, 0x16, (u8)reg[0x16]);
+
+ /* UnMute Audio */
+ adv7520_write_reg(hclient, 0x0C, (u8)0x84);
+
+ external_common_state->hdcp_active = FALSE;
+ mutex_lock(&hdcp_state_mutex);
+ hdcp_activating = FALSE;
+ mutex_unlock(&hdcp_state_mutex);
+}
+
+static void adv7520_comm_power(int on, int show);
+static void adv7520_hdcp_enable(struct work_struct *work)
+{
+ DEV_INFO("HDCP: Start reg[0xaf]=%02x (mute audio)\n", reg[0xaf]);
+
+ adv7520_comm_power(1, 1);
+
+ /* Mute Audio */
+ adv7520_write_reg(hclient, 0x0C, (u8)0xC3);
+
+ msleep(200);
+ /* Wait for BKSV ready interrupt */
+ /* Read BKSV's keys from HDTV */
+ reg[0xBF] = adv7520_read_reg(hclient, 0xBF);
+ reg[0xC0] = adv7520_read_reg(hclient, 0xC0);
+ reg[0xC1] = adv7520_read_reg(hclient, 0xC1);
+ reg[0xC2] = adv7520_read_reg(hclient, 0xC2);
+ reg[0xc3] = adv7520_read_reg(hclient, 0xC3);
+
+ DEV_DBG("HDCP: BKSV={%02x,%02x,%02x,%02x,%02x}\n", reg[0xbf], reg[0xc0],
+ reg[0xc1], reg[0xc2], reg[0xc3]);
+
+ /* Is SINK repeater */
+ reg[0xBE] = adv7520_read_reg(hclient, 0xBE);
+ if (~(reg[0xBE] & 0x40)) {
+ ; /* compare with revocation list */
+ /* Check 20 1's and 20 zero's */
+ } else {
+ /* Don't implement HDCP if sink as a repeater */
+ adv7520_write_reg(hclient, 0x0C, (u8)0x84);
+ mutex_lock(&hdcp_state_mutex);
+ hdcp_activating = FALSE;
+ mutex_unlock(&hdcp_state_mutex);
+ DEV_WARN("HDCP: Sink Repeater (%02x), (unmute audio)\n",
+ reg[0xbe]);
+
+ adv7520_comm_power(0, 1);
+ return;
+ }
+
+ msleep(200);
+ reg[0xB8] = adv7520_read_reg(hclient, 0xB8);
+ DEV_INFO("HDCP: Status reg[0xB8] is %02x\n", reg[0xb8]);
+ if (reg[0xb8] & 0x40) {
+ /* UnMute Audio */
+ adv7520_write_reg(hclient, 0x0C, (u8)0x84);
+ DEV_INFO("HDCP: A/V content Encrypted (unmute audio)\n");
+ external_common_state->hdcp_active = TRUE;
+ }
+ adv7520_comm_power(0, 1);
+
+ mutex_lock(&hdcp_state_mutex);
+ hdcp_activating = FALSE;
+ mutex_unlock(&hdcp_state_mutex);
+}
+#endif
+
+static int adv7520_read_edid_block(int block, uint8 *edid_buf)
+{
+ u8 r = 0;
+ int ret;
+ struct i2c_msg msg[] = {
+ { .addr = reg[0x43] >> 1,
+ .flags = 0,
+ .len = 1,
+ .buf = &r },
+ { .addr = reg[0x43] >> 1,
+ .flags = I2C_M_RD,
+ .len = 0x100,
+ .buf = edid_buf } };
+
+ if (block > 0)
+ return 0;
+ ret = i2c_transfer(hclient->adapter, msg, 2);
+ DEV_DBG("EDID block: addr=%02x, ret=%d\n", reg[0x43] >> 1, ret);
+ return (ret < 2) ? -ENODEV : 0;
+}
+
+static void adv7520_read_edid(void)
+{
+ external_common_state->read_edid_block = adv7520_read_edid_block;
+ if (hdmi_common_read_edid()) {
+ u8 timeout;
+ DEV_INFO("%s: retry\n", __func__);
+ adv7520_write_reg(hclient, 0xc9, 0x13);
+ msleep(500);
+ timeout = (adv7520_read_reg(hclient, 0x96) & (1 << 2));
+ if (timeout) {
+ hdmi_common_read_edid();
+ }
+ }
+}
+
+static void adv7520_chip_on(void)
+{
+ if (!chip_power_on) {
+ /* Get the current register holding the power bit. */
+ unsigned long reg0xaf = adv7520_read_reg(hclient, 0xaf);
+
+ dd->pd->core_power(1, 1);
+
+ /* Set the HDMI select bit. */
+ set_bit(1, ®0xaf);
+ DEV_INFO("%s: turn on chip power\n", __func__);
+ adv7520_write_reg(hclient, 0x41, 0x10);
+ adv7520_write_reg(hclient, 0xaf, (u8)reg0xaf);
+ chip_power_on = TRUE;
+ } else
+ DEV_INFO("%s: chip already has power\n", __func__);
+}
+
+static void adv7520_chip_off(void)
+{
+ if (chip_power_on) {
+#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT
+ if (has_hdcp_hw_support)
+ adv7520_close_hdcp_link();
+#endif
+
+ DEV_INFO("%s: turn off chip power\n", __func__);
+ adv7520_write_reg(hclient, 0x41, 0x50);
+ dd->pd->core_power(0, 1);
+ chip_power_on = FALSE;
+ } else
+ DEV_INFO("%s: chip is already off\n", __func__);
+
+ monitor_sense = 0;
+ hpd_cable_chg_detected = FALSE;
+
+ if (enable_5v_on) {
+ dd->pd->enable_5v(0);
+ enable_5v_on = FALSE;
+ }
+}
+
+/* Power ON/OFF ADV7520 chip */
+static void adv7520_isr_w(struct work_struct *work);
+static void adv7520_comm_power(int on, int show)
+{
+ if (!on)
+ atomic_dec(&comm_power_on);
+ dd->pd->comm_power(on, 0/*show*/);
+ if (on)
+ atomic_inc(&comm_power_on);
+}
+
+#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT
+static void adv7520_start_hdcp(void);
+#endif
+static int adv7520_power_on(struct platform_device *pdev)
+{
+ struct msm_fb_data_type *mfd = platform_get_drvdata(pdev);
+
+ external_common_state->dev = &pdev->dev;
+ if (mfd != NULL) {
+ DEV_INFO("adv7520_power: ON (%dx%d %d)\n",
+ mfd->var_xres, mfd->var_yres, mfd->var_pixclock);
+ hdmi_common_get_video_format_from_drv_data(mfd);
+ }
+
+ adv7520_comm_power(1, 1);
+ /* Check if HPD is signaled */
+ if (adv7520_read_reg(hclient, 0x42) & (1 << 6)) {
+ DEV_INFO("power_on: cable detected\n");
+ monitor_sense = adv7520_read_reg(hclient, 0xC6);
+#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT
+ if (has_hdcp_hw_support) {
+ if (!hdcp_activating)
+ adv7520_start_hdcp();
+ }
+#endif
+ } else
+ DEV_INFO("power_on: cable NOT detected\n");
+ adv7520_comm_power(0, 1);
+ wake_lock(&wlock);
+
+ return 0;
+}
+
+static int adv7520_power_off(struct platform_device *pdev)
+{
+ DEV_INFO("power_off\n");
+ adv7520_comm_power(1, 1);
+ adv7520_chip_off();
+ wake_unlock(&wlock);
+ adv7520_comm_power(0, 1);
+
+ return 0;
+}
+
+
+/* AV7520 chip specific initialization */
+static void adv7520_chip_init(void)
+{
+ /* Initialize the variables used to read/write the ADV7520 chip. */
+ memset(®, 0xff, sizeof(reg));
+
+ /* Get the values from the "Fixed Registers That Must Be Set". */
+ reg[0x98] = adv7520_read_reg(hclient, 0x98);
+ reg[0x9c] = adv7520_read_reg(hclient, 0x9c);
+ reg[0x9d] = adv7520_read_reg(hclient, 0x9d);
+ reg[0xa2] = adv7520_read_reg(hclient, 0xa2);
+ reg[0xa3] = adv7520_read_reg(hclient, 0xa3);
+ reg[0xde] = adv7520_read_reg(hclient, 0xde);
+
+ /* Get the "HDMI/DVI Selection" register. */
+ reg[0xaf] = adv7520_read_reg(hclient, 0xaf);
+
+ /* Read Packet Memory I2C Address */
+ reg[0x45] = adv7520_read_reg(hclient, 0x45);
+
+ /* Hard coded values provided by ADV7520 data sheet. */
+ reg[0x98] = 0x03;
+ reg[0x9c] = 0x38;
+ reg[0x9d] = 0x61;
+ reg[0xa2] = 0x94;
+ reg[0xa3] = 0x94;
+ reg[0xde] = 0x88;
+
+ /* Set the HDMI select bit. */
+ reg[0xaf] |= 0x16;
+
+ /* Set the audio related registers. */
+ reg[0x01] = 0x00;
+ reg[0x02] = 0x2d;
+ reg[0x03] = 0x80;
+ reg[0x0a] = 0x4d;
+ reg[0x0b] = 0x0e;
+ reg[0x0c] = 0x84;
+ reg[0x0d] = 0x10;
+ reg[0x12] = 0x00;
+ reg[0x14] = 0x00;
+ reg[0x15] = 0x20;
+ reg[0x44] = 0x79;
+ reg[0x73] = 0x01;
+ reg[0x76] = 0x00;
+
+ /* Set 720p display related registers */
+ reg[0x16] = 0x00;
+
+ reg[0x18] = 0x46;
+ reg[0x55] = 0x00;
+ reg[0x3c] = 0x04;
+
+ /* Set Interrupt Mask register for HPD/HDCP */
+ reg[0x94] = 0xC0;
+#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT
+ if (has_hdcp_hw_support)
+ reg[0x95] = 0xC0;
+ else
+ reg[0x95] = 0x00;
+#else
+ reg[0x95] = 0x00;
+#endif
+ adv7520_write_reg(hclient, 0x94, reg[0x94]);
+ adv7520_write_reg(hclient, 0x95, reg[0x95]);
+
+ /* Set Packet Memory I2C Address */
+ reg[0x45] = 0x74;
+
+ /* Set the values from the "Fixed Registers That Must Be Set". */
+ adv7520_write_reg(hclient, 0x98, reg[0x98]);
+ adv7520_write_reg(hclient, 0x9c, reg[0x9c]);
+ adv7520_write_reg(hclient, 0x9d, reg[0x9d]);
+ adv7520_write_reg(hclient, 0xa2, reg[0xa2]);
+ adv7520_write_reg(hclient, 0xa3, reg[0xa3]);
+ adv7520_write_reg(hclient, 0xde, reg[0xde]);
+
+ /* Set the "HDMI/DVI Selection" register. */
+ adv7520_write_reg(hclient, 0xaf, reg[0xaf]);
+
+ /* Set EDID Monitor address */
+ reg[0x43] = 0x7E;
+ adv7520_write_reg(hclient, 0x43, reg[0x43]);
+
+ /* Enable the i2s audio input. */
+ adv7520_write_reg(hclient, 0x01, reg[0x01]);
+ adv7520_write_reg(hclient, 0x02, reg[0x02]);
+ adv7520_write_reg(hclient, 0x03, reg[0x03]);
+ adv7520_write_reg(hclient, 0x0a, reg[0x0a]);
+ adv7520_write_reg(hclient, 0x0b, reg[0x0b]);
+ adv7520_write_reg(hclient, 0x0c, reg[0x0c]);
+ adv7520_write_reg(hclient, 0x0d, reg[0x0d]);
+ adv7520_write_reg(hclient, 0x12, reg[0x12]);
+ adv7520_write_reg(hclient, 0x14, reg[0x14]);
+ adv7520_write_reg(hclient, 0x15, reg[0x15]);
+ adv7520_write_reg(hclient, 0x44, reg[0x44]);
+ adv7520_write_reg(hclient, 0x73, reg[0x73]);
+ adv7520_write_reg(hclient, 0x76, reg[0x76]);
+
+ /* Enable 720p display */
+ adv7520_write_reg(hclient, 0x16, reg[0x16]);
+ adv7520_write_reg(hclient, 0x18, reg[0x18]);
+ adv7520_write_reg(hclient, 0x55, reg[0x55]);
+ adv7520_write_reg(hclient, 0x3c, reg[0x3c]);
+
+ /* Set Packet Memory address to avoid conflict
+ with Bosch Accelerometer */
+ adv7520_write_reg(hclient, 0x45, reg[0x45]);
+
+ /* Ensure chip is in low-power state */
+ adv7520_write_reg(hclient, 0x41, 0x50);
+}
+
+#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT
+static void adv7520_start_hdcp(void)
+{
+ mutex_lock(&hdcp_state_mutex);
+ if (hdcp_activating) {
+ DEV_WARN("adv7520_timer: HDCP already"
+ " activating, skipping\n");
+ mutex_unlock(&hdcp_state_mutex);
+ return;
+ }
+ hdcp_activating = TRUE;
+ mutex_unlock(&hdcp_state_mutex);
+
+ del_timer(&hpd_duty_timer);
+
+ adv7520_comm_power(1, 1);
+
+ if (!enable_5v_on) {
+ dd->pd->enable_5v(1);
+ enable_5v_on = TRUE;
+ adv7520_chip_on();
+ }
+
+ /* request for HDCP */
+ reg[0xaf] = adv7520_read_reg(hclient, 0xaf);
+ reg[0xaf] |= 0x90;
+ adv7520_write_reg(hclient, 0xaf, reg[0xaf]);
+ reg[0xaf] = adv7520_read_reg(hclient, 0xaf);
+
+ reg[0xba] = adv7520_read_reg(hclient, 0xba);
+ reg[0xba] |= 0x10;
+ adv7520_write_reg(hclient, 0xba, reg[0xba]);
+ reg[0xba] = adv7520_read_reg(hclient, 0xba);
+ adv7520_comm_power(0, 1);
+
+ DEV_INFO("HDCP: reg[0xaf]=0x%02x, reg[0xba]=0x%02x, waiting for BKSV\n",
+ reg[0xaf], reg[0xba]);
+
+ /* will check for HDCP Error or BKSV ready */
+ mod_timer(&hpd_duty_timer, jiffies + HZ/2);
+}
+#endif
+
+static void adv7520_hpd_timer_w(struct work_struct *work)
+{
+ if (!external_common_state->hpd_feature_on) {
+ DEV_INFO("adv7520_timer: skipping, feature off\n");
+ return;
+ }
+
+ if ((monitor_sense & 0x4) && !external_common_state->hpd_state) {
+ int timeout;
+ DEV_DBG("adv7520_timer: Cable Detected\n");
+ adv7520_comm_power(1, 1);
+ adv7520_chip_on();
+
+ if (hpd_cable_chg_detected) {
+ hpd_cable_chg_detected = FALSE;
+ /* Ensure 5V to read EDID */
+ if (!enable_5v_on) {
+ dd->pd->enable_5v(1);
+ enable_5v_on = TRUE;
+ }
+ msleep(500);
+ timeout = (adv7520_read_reg(hclient, 0x96) & (1 << 2));
+ if (timeout) {
+ DEV_DBG("adv7520_timer: EDID-Ready..\n");
+ adv7520_read_edid();
+ } else
+ DEV_DBG("adv7520_timer: EDID TIMEOUT (C9=%02x)"
+ "\n", adv7520_read_reg(hclient, 0xC9));
+ }
+#ifdef TESTING_FORCE_480p
+ external_common_state->disp_mode_list.num_of_elements = 1;
+ external_common_state->disp_mode_list.disp_mode_list[0] =
+ HDMI_VFRMT_720x480p60_16_9;
+#endif
+ adv7520_comm_power(0, 1);
+#ifndef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT
+ /* HDMI_5V_EN not needed anymore */
+ if (enable_5v_on) {
+ DEV_DBG("adv7520_timer: EDID done, no HDCP, 5V not "
+ "needed anymore\n");
+ dd->pd->enable_5v(0);
+ enable_5v_on = FALSE;
+ }
+#endif
+ change_hdmi_state(1);
+ } else if (external_common_state->hpd_state) {
+ adv7520_comm_power(1, 1);
+ adv7520_chip_off();
+ adv7520_comm_power(0, 1);
+ DEV_DBG("adv7520_timer: Cable Removed\n");
+ change_hdmi_state(0);
+ }
+}
+
+static void adv7520_hpd_timer_f(unsigned long data)
+{
+ schedule_work(&hpd_timer_work);
+}
+
+static void adv7520_isr_w(struct work_struct *work)
+{
+ static int state_count;
+ static u8 last_reg0x96;
+ u8 reg0xc8;
+ u8 reg0x96;
+#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT
+ static u8 last_reg0x97;
+ u8 reg0x97 = 0;
+#endif
+ if (!external_common_state->hpd_feature_on) {
+ DEV_DBG("adv7520_irq: skipping, hpd off\n");
+ return;
+ }
+
+ adv7520_comm_power(1, 1);
+ reg0x96 = adv7520_read_reg(hclient, 0x96);
+#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT
+ if (has_hdcp_hw_support) {
+ reg0x97 = adv7520_read_reg(hclient, 0x97);
+ /* Clearing the Interrupts */
+ adv7520_write_reg(hclient, 0x97, reg0x97);
+ }
+#endif
+ /* Clearing the Interrupts */
+ adv7520_write_reg(hclient, 0x96, reg0x96);
+
+ if ((reg0x96 == 0xC0) || (reg0x96 & 0x40)) {
+#ifdef DEBUG
+ unsigned int hpd_state = adv7520_read_reg(hclient, 0x42);
+#endif
+ monitor_sense = adv7520_read_reg(hclient, 0xC6);
+ DEV_DBG("adv7520_irq: reg[0x42]=%02x && reg[0xC6]=%02x\n",
+ hpd_state, monitor_sense);
+
+ if (!enable_5v_on) {
+ dd->pd->enable_5v(1);
+ enable_5v_on = TRUE;
+ }
+ if (!hpd_power_on) {
+ dd->pd->core_power(1, 1);
+ hpd_power_on = TRUE;
+ }
+
+ /* Timer for catching interrupt debouning */
+ DEV_DBG("adv7520_irq: Timer in .5sec\n");
+ hpd_cable_chg_detected = TRUE;
+ mod_timer(&hpd_timer, jiffies + HZ/2);
+ }
+#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT
+ if (has_hdcp_hw_support) {
+ if (hdcp_activating) {
+ /* HDCP controller error Interrupt */
+ if (reg0x97 & 0x80) {
+ DEV_ERR("adv7520_irq: HDCP_ERROR\n");
+ state_count = 0;
+ adv7520_close_hdcp_link();
+ /* BKSV Ready interrupts */
+ } else if (reg0x97 & 0x40) {
+ DEV_INFO("adv7520_irq: BKSV keys ready, Begin"
+ " HDCP encryption\n");
+ state_count = 0;
+ schedule_work(&hdcp_handle_work);
+ } else if (++state_count > 2 && (monitor_sense & 0x4)) {
+ DEV_INFO("adv7520_irq: Still waiting for BKSV,"
+ "restart HDCP\n");
+ hdcp_activating = FALSE;
+ state_count = 0;
+ adv7520_chip_off();
+ adv7520_start_hdcp();
+ }
+ reg0xc8 = adv7520_read_reg(hclient, 0xc8);
+ DEV_INFO("adv7520_irq: DDC controller reg[0xC8]=0x%02x,"
+ "state_count=%d, monitor_sense=%x\n",
+ reg0xc8, state_count, monitor_sense);
+ } else if (!external_common_state->hdcp_active
+ && (monitor_sense & 0x4)) {
+ DEV_INFO("adv7520_irq: start HDCP with"
+ " monitor sense\n");
+ state_count = 0;
+ adv7520_start_hdcp();
+ } else
+ state_count = 0;
+ if (last_reg0x97 != reg0x97 || last_reg0x96 != reg0x96)
+ DEV_DBG("adv7520_irq: reg[0x96]=%02x "
+ "reg[0x97]=%02x: HDCP: %d\n", reg0x96, reg0x97,
+ external_common_state->hdcp_active);
+ last_reg0x97 = reg0x97;
+ } else {
+ if (last_reg0x96 != reg0x96)
+ DEV_DBG("adv7520_irq: reg[0x96]=%02x\n", reg0x96);
+ }
+#else
+ if (last_reg0x96 != reg0x96)
+ DEV_DBG("adv7520_irq: reg[0x96]=%02x\n", reg0x96);
+#endif
+ last_reg0x96 = reg0x96;
+ adv7520_comm_power(0, 1);
+}
+
+static void adv7520_hpd_duty_work(struct work_struct *work)
+{
+ if (!external_common_state->hpd_feature_on) {
+ DEV_WARN("%s: hpd feature is off, skipping\n", __func__);
+ return;
+ }
+
+ dd->pd->core_power(1, 0);
+ msleep(10);
+ adv7520_isr_w(NULL);
+ dd->pd->core_power(0, 0);
+}
+
+static void adv7520_hpd_duty_timer_f(unsigned long data)
+{
+ if (!external_common_state->hpd_feature_on) {
+ DEV_WARN("%s: hpd feature is off, skipping\n", __func__);
+ return;
+ }
+
+ mod_timer(&hpd_duty_timer, jiffies + HPD_DUTY_CYCLE*HZ);
+ schedule_work(&hpd_duty_work);
+}
+
+static const struct i2c_device_id adv7520_id[] = {
+ { ADV7520_DRV_NAME , 0},
+ {}
+};
+
+static struct msm_fb_panel_data hdmi_panel_data = {
+ .on = adv7520_power_on,
+ .off = adv7520_power_off,
+};
+
+static struct platform_device hdmi_device = {
+ .name = ADV7520_DRV_NAME ,
+ .id = 2,
+ .dev = {
+ .platform_data = &hdmi_panel_data,
+ }
+};
+
+static void adv7520_ensure_init(void)
+{
+ static boolean init_done;
+ if (!init_done) {
+ int rc = dd->pd->init_irq();
+ if (rc) {
+ DEV_ERR("adv7520_init: init_irq: %d\n", rc);
+ return;
+ }
+
+ init_done = TRUE;
+ }
+ DEV_INFO("adv7520_init: chip init\n");
+ adv7520_comm_power(1, 1);
+ adv7520_chip_init();
+ adv7520_comm_power(0, 1);
+}
+
+static int adv7520_hpd_feature(int on)
+{
+ int rc = 0;
+
+ if (!on) {
+ if (enable_5v_on) {
+ dd->pd->enable_5v(0);
+ enable_5v_on = FALSE;
+ }
+ if (hpd_power_on) {
+ dd->pd->core_power(0, 1);
+ hpd_power_on = FALSE;
+ }
+
+ DEV_DBG("adv7520_hpd: %d: stop duty timer\n", on);
+ del_timer(&hpd_timer);
+ del_timer(&hpd_duty_timer);
+ external_common_state->hpd_state = 0;
+ }
+
+ if (on) {
+ dd->pd->core_power(1, 0);
+ adv7520_ensure_init();
+
+ adv7520_comm_power(1, 1);
+ monitor_sense = adv7520_read_reg(hclient, 0xC6);
+ DEV_DBG("adv7520_irq: reg[0xC6]=%02x\n", monitor_sense);
+ adv7520_comm_power(0, 1);
+ dd->pd->core_power(0, 0);
+
+ if (monitor_sense & 0x4) {
+ if (!enable_5v_on) {
+ dd->pd->enable_5v(1);
+ enable_5v_on = TRUE;
+ }
+ if (!hpd_power_on) {
+ dd->pd->core_power(1, 1);
+ hpd_power_on = TRUE;
+ }
+
+ hpd_cable_chg_detected = TRUE;
+ mod_timer(&hpd_timer, jiffies + HZ/2);
+ }
+
+ DEV_DBG("adv7520_hpd: %d start duty timer\n", on);
+ mod_timer(&hpd_duty_timer, jiffies + HZ/100);
+ }
+
+ DEV_INFO("adv7520_hpd: %d\n", on);
+ return rc;
+}
+
+static int __devinit
+ adv7520_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ int rc;
+ struct platform_device *fb_dev;
+
+ dd = kzalloc(sizeof *dd, GFP_KERNEL);
+ if (!dd) {
+ rc = -ENOMEM;
+ goto probe_exit;
+ }
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return -ENODEV;
+
+ external_common_state->dev = &client->dev;
+
+ /* Init real i2c_client */
+ hclient = client;
+
+ i2c_set_clientdata(client, dd);
+ dd->pd = client->dev.platform_data;
+ if (!dd->pd) {
+ rc = -ENODEV;
+ goto probe_free;
+ }
+
+ INIT_WORK(&dd->isr_work, adv7520_isr_w);
+ INIT_WORK(&hpd_timer_work, adv7520_hpd_timer_w);
+#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT
+ if (dd->pd->check_hdcp_hw_support)
+ has_hdcp_hw_support = dd->pd->check_hdcp_hw_support();
+
+ if (has_hdcp_hw_support)
+ INIT_WORK(&hdcp_handle_work, adv7520_hdcp_enable);
+ else
+ DEV_INFO("%s: no hdcp hw support.\n", __func__);
+#endif
+
+ init_timer(&hpd_timer);
+ hpd_timer.function = adv7520_hpd_timer_f;
+ hpd_timer.data = (unsigned long)NULL;
+ hpd_timer.expires = 0xffffffff;
+ add_timer(&hpd_timer);
+
+ external_common_state->hpd_feature = adv7520_hpd_feature;
+ DEV_INFO("adv7520_probe: HPD detection on request\n");
+ init_timer(&hpd_duty_timer);
+ hpd_duty_timer.function = adv7520_hpd_duty_timer_f;
+ hpd_duty_timer.data = (unsigned long)NULL;
+ hpd_duty_timer.expires = 0xffffffff;
+ add_timer(&hpd_duty_timer);
+ INIT_WORK(&hpd_duty_work, adv7520_hpd_duty_work);
+ DEV_INFO("adv7520_probe: HPD detection ON (duty)\n");
+
+ fb_dev = msm_fb_add_device(&hdmi_device);
+
+ if (fb_dev) {
+ rc = external_common_state_create(fb_dev);
+ if (rc)
+ goto probe_free;
+ } else
+ DEV_ERR("adv7520_probe: failed to add fb device\n");
+
+ return 0;
+
+probe_free:
+ kfree(dd);
+ dd = NULL;
+probe_exit:
+ return rc;
+
+}
+
+static int __devexit adv7520_remove(struct i2c_client *client)
+{
+ if (!client->adapter) {
+ DEV_ERR("%s: No HDMI Device\n", __func__);
+ return -ENODEV;
+ }
+ wake_lock_destroy(&wlock);
+ kfree(dd);
+ dd = NULL;
+ return 0;
+}
+
+#ifdef CONFIG_SUSPEND
+static int adv7520_i2c_suspend(struct device *dev)
+{
+ DEV_INFO("%s\n", __func__);
+
+ ++suspend_count;
+
+ if (external_common_state->hpd_feature_on) {
+ DEV_DBG("%s: stop duty timer\n", __func__);
+ del_timer(&hpd_duty_timer);
+ del_timer(&hpd_timer);
+ }
+
+ /* Turn off LDO8 and go into low-power state */
+ if (chip_power_on) {
+ DEV_DBG("%s: turn off power\n", __func__);
+ adv7520_comm_power(1, 1);
+ adv7520_write_reg(hclient, 0x41, 0x50);
+ adv7520_comm_power(0, 1);
+ dd->pd->core_power(0, 1);
+ }
+
+ return 0;
+}
+
+static int adv7520_i2c_resume(struct device *dev)
+{
+ DEV_INFO("%s\n", __func__);
+
+ /* Turn on LDO8 and go into normal-power state */
+ if (chip_power_on) {
+ DEV_DBG("%s: turn on power\n", __func__);
+ dd->pd->core_power(1, 1);
+ adv7520_comm_power(1, 1);
+ adv7520_write_reg(hclient, 0x41, 0x10);
+ adv7520_comm_power(0, 1);
+ }
+
+ if (external_common_state->hpd_feature_on) {
+ DEV_DBG("%s: start duty timer\n", __func__);
+ mod_timer(&hpd_duty_timer, jiffies + HPD_DUTY_CYCLE*HZ);
+ }
+
+ return 0;
+}
+#else
+#define adv7520_i2c_suspend NULL
+#define adv7520_i2c_resume NULL
+#endif
+
+static const struct dev_pm_ops adv7520_device_pm_ops = {
+ .suspend = adv7520_i2c_suspend,
+ .resume = adv7520_i2c_resume,
+};
+
+static struct i2c_driver hdmi_i2c_driver = {
+ .driver = {
+ .name = ADV7520_DRV_NAME,
+ .owner = THIS_MODULE,
+ .pm = &adv7520_device_pm_ops,
+ },
+ .probe = adv7520_probe,
+ .id_table = adv7520_id,
+ .remove = __devexit_p(adv7520_remove),
+};
+
+static int __init adv7520_init(void)
+{
+ int rc;
+
+ pr_info("%s\n", __func__);
+ external_common_state = &hdmi_common;
+ external_common_state->video_resolution = HDMI_VFRMT_1280x720p60_16_9;
+ HDMI_SETUP_LUT(640x480p60_4_3); /* 25.20MHz */
+ HDMI_SETUP_LUT(720x480p60_16_9); /* 27.03MHz */
+ HDMI_SETUP_LUT(1280x720p60_16_9); /* 74.25MHz */
+
+ HDMI_SETUP_LUT(720x576p50_16_9); /* 27.00MHz */
+ HDMI_SETUP_LUT(1280x720p50_16_9); /* 74.25MHz */
+
+ hdmi_common_init_panel_info(&hdmi_panel_data.panel_info);
+
+ rc = i2c_add_driver(&hdmi_i2c_driver);
+ if (rc) {
+ pr_err("hdmi_init FAILED: i2c_add_driver rc=%d\n", rc);
+ goto init_exit;
+ }
+
+ if (machine_is_msm7x30_surf() || machine_is_msm8x55_surf()) {
+ short *hdtv_mux = (short *)ioremap(0x8e000170 , 0x100);
+ *hdtv_mux++ = 0x020b;
+ *hdtv_mux = 0x8000;
+ iounmap(hdtv_mux);
+ }
+ wake_lock_init(&wlock, WAKE_LOCK_IDLE, "hdmi_active");
+
+ return 0;
+
+init_exit:
+ return rc;
+}
+
+static void __exit adv7520_exit(void)
+{
+ i2c_del_driver(&hdmi_i2c_driver);
+}
+
+module_init(adv7520_init);
+module_exit(adv7520_exit);
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("0.1");
+MODULE_AUTHOR("Qualcomm Innovation Center, Inc.");
+MODULE_DESCRIPTION("ADV7520 HDMI driver");