/* Copyright (c) 2010,2012, The Linux Foundation. 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 #include #include #include #include #include #include #include #include #include #include #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 struct clk *tv_enc_clk; 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; static struct pm_qos_request pm_qos_req; /* 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); switch_set_state(&external_common_state->sdev, 1); } else { kobject_uevent(external_common_state->uevent_kobj, KOBJ_OFFLINE); switch_set_state(&external_common_state->sdev, 0); } 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); clk_prepare_enable(tv_enc_clk); 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); pm_qos_update_request(&pm_qos_req, msm_cpuidle_get_deep_idle_latency()); return 0; } static int adv7520_power_off(struct platform_device *pdev) { DEV_INFO("power_off\n"); adv7520_comm_power(1, 1); adv7520_chip_off(); pm_qos_update_request(&pm_qos_req, PM_QOS_DEFAULT_VALUE); adv7520_comm_power(0, 1); clk_disable_unprepare(tv_enc_clk); 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"); if (hdmi_prim_display) external_common_state->sdev.name = "hdmi_as_primary"; else external_common_state->sdev.name = "hdmi"; if (switch_dev_register(&external_common_state->sdev) < 0) DEV_ERR("Hdmi switch registration failed\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; } switch_dev_unregister(&external_common_state->sdev); pm_qos_remove_request(&pm_qos_req); 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; tv_enc_clk = clk_get(NULL, "tv_enc_clk"); if (IS_ERR(tv_enc_clk)) { printk(KERN_ERR "error: can't get tv_enc_clk!\n"); return IS_ERR(tv_enc_clk); } 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); } pm_qos_add_request(&pm_qos_req, PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE); return 0; init_exit: if (tv_enc_clk) clk_put(tv_enc_clk); 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");