diff options
Diffstat (limited to 'drivers/media/video/samsung/tv20/s5pv210/hdcp_s5pv210.c')
-rw-r--r-- | drivers/media/video/samsung/tv20/s5pv210/hdcp_s5pv210.c | 1790 |
1 files changed, 1790 insertions, 0 deletions
diff --git a/drivers/media/video/samsung/tv20/s5pv210/hdcp_s5pv210.c b/drivers/media/video/samsung/tv20/s5pv210/hdcp_s5pv210.c new file mode 100644 index 0000000..25ab4c5 --- /dev/null +++ b/drivers/media/video/samsung/tv20/s5pv210/hdcp_s5pv210.c @@ -0,0 +1,1790 @@ +/* linux/drivers/media/video/samsung/tv20/s5pv210/hdcp_s5pv210.c + * + * hdcp raw ftn file for Samsung TVOut driver + * + * Copyright (c) 2010 Samsung Electronics + * http://www.samsungsemi.com/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/wait.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/io.h> + +#include <plat/gpio-cfg.h> + +#include <mach/regs-gpio.h> +#include <mach/gpio.h> + +#include "../ddc.h" +#include "tv_out_s5pv210.h" +#include "regs/regs-hdmi.h" + +/* for Operation check */ +#ifdef COFIG_TVOUT_RAW_DBG +#define S5P_HDCP_DEBUG 1 +#define S5P_HDCP_I2C_DEBUG 1 +#define S5P_HDCP_AUTH_DEBUG 1 +#endif + +#ifdef S5P_HDCP_DEBUG +#define HDCPPRINTK(fmt, args...) \ + printk(KERN_INFO "\t\t[HDCP] %s: " fmt, __func__ , ## args) +#else +#define HDCPPRINTK(fmt, args...) +#endif + +/* for authentication key check */ +#ifdef S5P_HDCP_AUTH_DEBUG +#define AUTHPRINTK(fmt, args...) \ + printk("\t\t\t[AUTHKEY] %s: " fmt, __func__ , ## args) +#else +#define AUTHPRINTK(fmt, args...) +#endif + +enum hdmi_run_mode { + DVI_MODE, + HDMI_MODE +}; + +enum hdmi_resolution { + SD480P, + SD480I, + WWSD480P, + HD720P, + SD576P, + WWSD576P, + HD1080I +}; + +enum hdmi_color_bar_type { + HORIZONTAL, + VERTICAL +}; + +enum hdcp_event { + /* Stop HDCP */ + HDCP_EVENT_STOP, + /* Start HDCP*/ + HDCP_EVENT_START, + /* Start to read Bksv, Bcaps */ + HDCP_EVENT_READ_BKSV_START, + /* Start to write Aksv, An */ + HDCP_EVENT_WRITE_AKSV_START, + /* Start to check if Ri is equal to Rj */ + HDCP_EVENT_CHECK_RI_START, + /* Start 2nd authentication process */ + HDCP_EVENT_SECOND_AUTH_START +}; + +enum hdcp_state { + NOT_AUTHENTICATED, + RECEIVER_READ_READY, + BCAPS_READ_DONE, + BKSV_READ_DONE, + AN_WRITE_DONE, + AKSV_WRITE_DONE, + FIRST_AUTHENTICATION_DONE, + SECOND_AUTHENTICATION_RDY, + RECEIVER_FIFOLSIT_READY, + SECOND_AUTHENTICATION_DONE, +}; + +/* + * Below CSC_TYPE is temporary. CSC_TYPE enum. + * may be included in SetSD480pVars_60Hz etc. + * + * LR : Limited Range (16~235) + * FR : Full Range (0~255) + */ +enum hdmi_intr_src { + WAIT_FOR_ACTIVE_RX, + WDT_FOR_REPEATER, + EXCHANGE_KSV, + UPDATE_P_VAL, + UPDATE_R_VAL, + AUDIO_OVERFLOW, + AUTHEN_ACK, + UNKNOWN_INT +}; + +struct s5p_hdcp_info { + bool is_repeater; + bool hpd_status; + u32 time_out; + u32 hdcp_enable; + + spinlock_t lock; + spinlock_t reset_lock; + + struct i2c_client *client; + + wait_queue_head_t waitq; + enum hdcp_event event; + enum hdcp_state auth_status; + + struct work_struct work; +}; + +static struct s5p_hdcp_info hdcp_info = { + .is_repeater = false, + .time_out = 0, + .hdcp_enable = false, + .client = NULL, + .event = HDCP_EVENT_STOP, + .auth_status = NOT_AUTHENTICATED, + +}; + +#define HDCP_RI_OFFSET 0x08 +#define INFINITE 0xffffffff + +#define HDMI_SYS_ENABLE (1 << 0) +#define HDMI_ASP_ENABLE (1 << 2) +#define HDMI_ASP_DISABLE (~HDMI_ASP_ENABLE) + +#define MAX_DEVS_EXCEEDED (0x1 << 7) +#define MAX_CASCADE_EXCEEDED (0x1 << 3) + +#define MAX_CASCADE_EXCEEDED_ERROR (-1) +#define MAX_DEVS_EXCEEDED_ERROR (-2) +#define REPEATER_ILLEGAL_DEVICE_ERROR (-3) +#define REPEATER_TIMEOUT_ERROR (-4) + +#define AINFO_SIZE 1 +#define BCAPS_SIZE 1 +#define BSTATUS_SIZE 2 +#define SHA_1_HASH_SIZE 20 + +#define KSV_FIFO_READY (0x1 << 5) + +/* spmoon for test : it's not in manual */ +#define SET_HDCP_KSV_WRITE_DONE (0x1 << 3) +#define CLEAR_HDCP_KSV_WRITE_DONE (~SET_HDCP_KSV_WRITE_DONE) + +#define SET_HDCP_KSV_LIST_EMPTY (0x1 << 2) +#define CLEAR_HDCP_KSV_LIST_EMPTY (~SET_HDCP_KSV_LIST_EMPTY) +#define SET_HDCP_KSV_END (0x1 << 1) +#define CLEAR_HDCP_KSV_END (~SET_HDCP_KSV_END) +#define SET_HDCP_KSV_READ (0x1 << 0) +#define CLEAR_HDCP_KSV_READ (~SET_HDCP_KSV_READ) + +#define SET_HDCP_SHA_VALID_READY (0x1 << 1) +#define CLEAR_HDCP_SHA_VALID_READY (~SET_HDCP_SHA_VALID_READY) +#define SET_HDCP_SHA_VALID (0x1 << 0) +#define CLEAR_HDCP_SHA_VALID (~SET_HDCP_SHA_VALID) + +#define TRANSMIT_EVERY_VSYNC (0x1 << 1) + +/* must be checked */ +static bool sw_reset; +static bool is_dvi; +static bool av_mute; +static bool audio_en; + +void s5p_hdmi_set_audio(bool en) +{ + if (en) + audio_en = true; + else + audio_en = false; +} + +int s5p_hdcp_is_reset(void) +{ + int ret = 0; + + if (spin_is_locked(&hdcp_info.reset_lock)) + return 1; + + return ret; +} + +int s5p_hdmi_set_dvi(bool en) +{ + if (en) + is_dvi = true; + else + is_dvi = false; + + return 0; +} + +int s5p_hdmi_set_mute(bool en) +{ + if (en) + av_mute = true; + else + av_mute = false; + + return 0; +} + +int s5p_hdmi_get_mute(void) +{ + return av_mute ? true : false; +} + +int s5p_hdmi_audio_enable(bool en) +{ + u8 reg; + + if (!is_dvi) { + reg = readl(hdmi_base + S5P_HDMI_CON_0); + + if (en) { + reg |= ASP_EN; + writel(HDMI_TRANS_EVERY_SYNC , hdmi_base + S5P_AUI_CON); + } else { + reg &= ~ASP_EN; + writel(HDMI_DO_NOT_TANS , hdmi_base + S5P_AUI_CON); + } + + writel(reg, hdmi_base + S5P_HDMI_CON_0); + } + + return 0; +} + +void s5p_hdmi_mute_en(bool en) +{ + if (!av_mute) { + if (en) { + __s5p_hdmi_video_set_bluescreen(true, 0, 0, 0); + s5p_hdmi_audio_enable(false); + } else { + __s5p_hdmi_video_set_bluescreen(false, 0, 0, 0); + if (audio_en) + s5p_hdmi_audio_enable(true); + } + } +} + +/* + * 1st Authentication step func. + * Write the Ainfo data to Rx + */ +static bool write_ainfo(void) +{ + int ret = 0; + u8 ainfo[2]; + + ainfo[0] = HDCP_Ainfo; + ainfo[1] = 0; + + ret = ddc_write(ainfo, 2); + if (ret < 0) + HDCPPRINTK("Can't write ainfo data through i2c bus\n"); + + return (ret < 0) ? false : true; +} + +/* + * Write the An data to Rx + */ +static bool write_an(void) +{ + int ret = 0; + u8 an[AN_SIZE+1]; + + an[0] = HDCP_An; + + an[1] = readb(hdmi_base + S5P_HDCP_An_0_0); + an[2] = readb(hdmi_base + S5P_HDCP_An_0_1); + an[3] = readb(hdmi_base + S5P_HDCP_An_0_2); + an[4] = readb(hdmi_base + S5P_HDCP_An_0_3); + an[5] = readb(hdmi_base + S5P_HDCP_An_1_0); + an[6] = readb(hdmi_base + S5P_HDCP_An_1_1); + an[7] = readb(hdmi_base + S5P_HDCP_An_1_2); + an[8] = readb(hdmi_base + S5P_HDCP_An_1_3); + + ret = ddc_write(an, AN_SIZE + 1); + if (ret < 0) + HDCPPRINTK("Can't write an data through i2c bus\n"); + +#ifdef S5P_HDCP_AUTH_DEBUG + { + u16 i = 0; + for (i = 1; i < AN_SIZE + 1; i++) + AUTHPRINTK("HDCPAn[%d]: 0x%x \n", i, an[i]); + + } +#endif + + return (ret < 0) ? false : true; +} + +/* + * Write the Aksv data to Rx + */ +static bool write_aksv(void) +{ + int ret = 0; + u8 aksv[AKSV_SIZE+1]; + + aksv[0] = HDCP_Aksv; + + aksv[1] = readb(hdmi_base + S5P_HDCP_AKSV_0_0); + aksv[2] = readb(hdmi_base + S5P_HDCP_AKSV_0_1); + aksv[3] = readb(hdmi_base + S5P_HDCP_AKSV_0_2); + aksv[4] = readb(hdmi_base + S5P_HDCP_AKSV_0_3); + aksv[5] = readb(hdmi_base + S5P_HDCP_AKSV_1); + + if (aksv[1] == 0 && + aksv[2] == 0 && + aksv[3] == 0 && + aksv[4] == 0 && + aksv[5] == 0) + return false; + + ret = ddc_write(aksv, AKSV_SIZE + 1); + if (ret < 0) + HDCPPRINTK("Can't write aksv data through i2c bus\n"); + +#ifdef S5P_HDCP_AUTH_DEBUG + { + u16 i = 0; + for (i = 1; i < AKSV_SIZE + 1; i++) + AUTHPRINTK("HDCPAksv[%d]: 0x%x\n", i, aksv[i]); + + } +#endif + + return (ret < 0) ? false : true; +} + +static bool read_bcaps(void) +{ + int ret = 0; + u8 bcaps[BCAPS_SIZE] = {0}; + + ret = ddc_read(HDCP_Bcaps, bcaps, BCAPS_SIZE); + + if (ret < 0) { + HDCPPRINTK("Can't read bcaps data from i2c bus\n"); + return false; + } + + writel(bcaps[0], hdmi_base + S5P_HDCP_BCAPS); + + HDCPPRINTK("BCAPS(from i2c) : 0x%08x\n", bcaps[0]); + + if (bcaps[0] & REPEATER_SET) + hdcp_info.is_repeater = true; + else + hdcp_info.is_repeater = false; + + HDCPPRINTK("attached device type : %s !! \n\r", + hdcp_info.is_repeater ? "REPEATER" : "SINK"); + HDCPPRINTK("BCAPS(from sfr) = 0x%08x\n", + readl(hdmi_base + S5P_HDCP_BCAPS)); + + return true; +} + +static bool read_again_bksv(void) +{ + u8 bk_sv[BKSV_SIZE] = {0, 0, 0, 0, 0}; + u8 i = 0; + u8 j = 0; + u32 no_one = 0; + u32 no_zero = 0; + u32 result = 0; + int ret = 0; + + ret = ddc_read(HDCP_Bksv, bk_sv, BKSV_SIZE); + + if (ret < 0) { + HDCPPRINTK("Can't read bk_sv data from i2c bus\n"); + return false; + } + +#ifdef S5P_HDCP_AUTH_DEBUG + for (i = 0; i < BKSV_SIZE; i++) + AUTHPRINTK("i2c read : Bksv[%d]: 0x%x\n", i, bk_sv[i]); +#endif + + for (i = 0; i < BKSV_SIZE; i++) { + + for (j = 0; j < 8; j++) { + + result = bk_sv[i] & (0x1 << j); + + if (result == 0) + no_zero += 1; + else + no_one += 1; + } + } + + if ((no_zero == 20) && (no_one == 20)) { + HDCPPRINTK("Suucess: no_zero, and no_one is 20\n"); + + writel(bk_sv[0], hdmi_base + S5P_HDCP_BKSV_0_0); + writel(bk_sv[1], hdmi_base + S5P_HDCP_BKSV_0_1); + writel(bk_sv[2], hdmi_base + S5P_HDCP_BKSV_0_2); + writel(bk_sv[3], hdmi_base + S5P_HDCP_BKSV_0_3); + writel(bk_sv[4], hdmi_base + S5P_HDCP_BKSV_1); + +#ifdef S5P_HDCP_AUTH_DEBUG + for (i = 0; i < BKSV_SIZE; i++) + AUTHPRINTK("set reg : Bksv[%d]: 0x%x\n", i, bk_sv[i]); + + /* + writel(HDCP_ENC_ENABLE, hdmi_base + S5P_ENC_EN); + */ +#endif + return true; + } else { + HDCPPRINTK("Failed: no_zero or no_one is NOT 20\n"); + return false; + } +} + +static bool read_bksv(void) +{ + u8 bk_sv[BKSV_SIZE] = {0, 0, 0, 0, 0}; + + int i = 0; + int j = 0; + + u32 no_one = 0; + u32 no_zero = 0; + u32 result = 0; + u32 count = 0; + int ret = 0; + + ret = ddc_read(HDCP_Bksv, bk_sv, BKSV_SIZE); + + if (ret < 0) { + HDCPPRINTK("Can't read bk_sv data from i2c bus\n"); + return false; + } + +#ifdef S5P_HDCP_AUTH_DEBUG + for (i = 0; i < BKSV_SIZE; i++) + AUTHPRINTK("i2c read : Bksv[%d]: 0x%x\n", i, bk_sv[i]); +#endif + + for (i = 0; i < BKSV_SIZE; i++) { + + for (j = 0; j < 8; j++) { + + result = bk_sv[i] & (0x1 << j); + + if (result == 0) + no_zero++; + else + no_one++; + } + } + + if ((no_zero == 20) && (no_one == 20)) { + + writel(bk_sv[0], hdmi_base + S5P_HDCP_BKSV_0_0); + writel(bk_sv[1], hdmi_base + S5P_HDCP_BKSV_0_1); + writel(bk_sv[2], hdmi_base + S5P_HDCP_BKSV_0_2); + writel(bk_sv[3], hdmi_base + S5P_HDCP_BKSV_0_3); + writel(bk_sv[4], hdmi_base + S5P_HDCP_BKSV_1); + +#ifdef S5P_HDCP_AUTH_DEBUG + for (i = 0; i < BKSV_SIZE; i++) + AUTHPRINTK("set reg : Bksv[%d]: 0x%x\n", i, bk_sv[i]); +#endif + + HDCPPRINTK("Success: no_zero, and no_one is 20\n"); + + } else { + + HDCPPRINTK("Failed: no_zero or no_one is NOT 20\n"); + + while (!read_again_bksv()) { + + count++; + + mdelay(200); + + if (count == 14) + return false; + } + } + + return true; +} + +/* + * Compare the R value of Tx with that of Rx + */ +static bool compare_r_val(void) +{ + int ret = 0; + u8 ri[2] = {0, 0}; + u8 rj[2] = {0, 0}; + u16 i; + + for (i = 0; i < R_VAL_RETRY_CNT; i++) { + + if (hdcp_info.auth_status < AKSV_WRITE_DONE) { + ret = false; + break; + } + + /* Read R value from Tx */ + ri[0] = readl(hdmi_base + S5P_HDCP_Ri_0); + ri[1] = readl(hdmi_base + S5P_HDCP_Ri_1); + + /* Read R value from Rx */ + ret = ddc_read(HDCP_Ri, rj, 2); + if (ret < 0) { + HDCPPRINTK("Can't read r data from i2c bus\n"); + return false; + } + +#ifdef S5P_HDCP_AUTH_DEBUG + AUTHPRINTK("retries :: %d\n", i); + printk(KERN_INFO "\t\t\t Rx(ddc) ->"); + printk(KERN_INFO "rj[0]: 0x%02x, rj[1]: 0x%02x\n", + rj[0], rj[1]); + printk(KERN_INFO "\t\t\t Tx(register) ->"); + printk(KERN_INFO "ri[0]: 0x%02x, ri[1]: 0x%02x\n", + ri[0], ri[1]); +#endif + + /* Compare R value */ + if ((ri[0] == rj[0]) && (ri[1] == rj[1]) && (ri[0] | ri[1])) { + writel(Ri_MATCH_RESULT__YES, + hdmi_base + S5P_HDCP_CHECK_RESULT); + HDCPPRINTK("R0, R0' is matched!!\n"); + ret = true; + break; + } else { + writel(Ri_MATCH_RESULT__NO, + hdmi_base + S5P_HDCP_CHECK_RESULT); + HDCPPRINTK("R0, R0' is not matched!!\n"); + ret = false; + } + + ri[0] = 0; + ri[1] = 0; + rj[0] = 0; + rj[1] = 0; + + } + + if (!ret) { + hdcp_info.event = HDCP_EVENT_STOP; + hdcp_info.auth_status = NOT_AUTHENTICATED; + } + + return ret ? true : false; +} + + +/* + * Enable/Disable Software HPD control + */ +void sw_hpd_enable(bool enable) +{ + u8 reg; + + reg = readb(hdmi_base + S5P_HPD); + reg &= ~HPD_SW_ENABLE; + + if (enable) + writeb(reg | HPD_SW_ENABLE, hdmi_base + S5P_HPD); + else + writeb(reg, hdmi_base + S5P_HPD); +} + +/* + * Set Software HPD level + * + * @param level [in] if 0 - low;othewise, high + */ +void set_sw_hpd(bool level) +{ + u8 reg; + + reg = readb(hdmi_base + S5P_HPD); + reg &= ~HPD_ON; + + if (level) + writeb(reg | HPD_ON, hdmi_base + S5P_HPD); + else + writeb(reg, hdmi_base + S5P_HPD); +} + + +/* + * Reset Authentication + */ +void reset_authentication(void) +{ + u8 reg; + + spin_lock_irq(&hdcp_info.reset_lock); + + hdcp_info.time_out = INFINITE; + hdcp_info.event = HDCP_EVENT_STOP; + hdcp_info.auth_status = NOT_AUTHENTICATED; + + + /* Disable hdcp */ + writeb(0x0, hdmi_base + S5P_HDCP_CTRL1); + writeb(0x0, hdmi_base + S5P_HDCP_CTRL2); + + s5p_hdmi_mute_en(true); + + /* Disable encryption */ + HDCPPRINTK("Stop Encryption by reset!!\n"); + writeb(HDCP_ENC_DIS, hdmi_base + S5P_ENC_EN); + + HDCPPRINTK("Now reset authentication\n"); + + /* disable hdmi status enable reg. */ + reg = readb(hdmi_base + S5P_STATUS_EN); + reg &= HDCP_STATUS_DIS_ALL; + writeb(reg, hdmi_base + S5P_STATUS_EN); + + /* clear all result */ + + writeb(CLEAR_ALL_RESULTS, hdmi_base + S5P_HDCP_CHECK_RESULT); + + /* + * 1. Mask HPD plug and unplug interrupt + * disable HPD INT + */ + sw_reset = true; + reg = s5p_hdmi_get_enabled_interrupt(); + + s5p_hdmi_disable_interrupts(HDMI_IRQ_HPD_PLUG); + s5p_hdmi_disable_interrupts(HDMI_IRQ_HPD_UNPLUG); + + /* 2. Enable software HPD */ + sw_hpd_enable(true); + + /* 3. Make software HPD logical 0 */ + set_sw_hpd(false); + + /* 4. Make software HPD logical 1 */ + set_sw_hpd(true); + + /* 5. Disable software HPD */ + sw_hpd_enable(false); + + /* 6. Unmask HPD plug and unplug interrupt */ + if (reg & 1<<HDMI_IRQ_HPD_PLUG) + s5p_hdmi_enable_interrupts(HDMI_IRQ_HPD_PLUG); + if (reg & 1<<HDMI_IRQ_HPD_UNPLUG) + s5p_hdmi_enable_interrupts(HDMI_IRQ_HPD_UNPLUG); + + + sw_reset = false; + + /* clear result */ +#if 0 + writel(Ri_MATCH_RESULT__NO, hdmi_base + S5P_HDCP_CHECK_RESULT); + writel(readl(hdmi_base + S5P_HDMI_CON_0) & HDMI_DIS, + hdmi_base + S5P_HDMI_CON_0); + writel(readl(hdmi_base + S5P_HDMI_CON_0) | HDMI_EN, + hdmi_base + S5P_HDMI_CON_0); +#endif + writel(CLEAR_ALL_RESULTS, hdmi_base + S5P_HDCP_CHECK_RESULT); + + /* set hdcp_int enable */ + reg = readb(hdmi_base + S5P_STATUS_EN); + reg |= WTFORACTIVERX_INT_OCCURRED | + WATCHDOG_INT_OCCURRED | + EXCHANGEKSV_INT_OCCURRED | + UPDATE_RI_INT_OCCURRED; + writeb(reg, hdmi_base + S5P_STATUS_EN); + + /* HDCP Enable */ + writeb(CP_DESIRED_EN, hdmi_base + S5P_HDCP_CTRL1); + + spin_unlock_irq(&hdcp_info.reset_lock); +} + +/* + * Set the timing parameter for load e-fuse key. + */ + +/* TODO: must use clk_get for pclk rate */ +#define PCLK_D_RATE_FOR_HDCP 166000000 + +u32 efuse_ceil(u32 val, u32 time) +{ + u32 res; + + res = val / time; + + if (val % time) + res += 1; + + return res; +} + +#if 0 +static void hdcp_efuse_timing(void) +{ + u32 time, val; + + /* TODO: must use clk_get for pclk rate */ + time = 1000000000/PCLK_D_RATE_FOR_HDCP; + + val = efuse_ceil(EFUSE_ADDR_WIDTH, time); + writeb(val, hdmi_base + S5P_EFUSE_ADDR_WIDTH); + + val = efuse_ceil(EFUSE_SIGDEV_ASSERT, time); + writeb(val, hdmi_base + S5P_EFUSE_SIGDEV_ASSERT); + + val = efuse_ceil(EFUSE_SIGDEV_DEASSERT, time); + writeb(val, hdmi_base + S5P_EFUSE_SIGDEV_DEASSERT); + + val = efuse_ceil(EFUSE_PRCHG_ASSERT, time); + writeb(val, hdmi_base + S5P_EFUSE_PRCHG_ASSERT); + + val = efuse_ceil(EFUSE_PRCHG_DEASSERT, time); + writeb(val, hdmi_base + S5P_EFUSE_PRCHG_DEASSERT); + + val = efuse_ceil(EFUSE_FSET_ASSERT, time); + writeb(val, hdmi_base + S5P_EFUSE_FSET_ASSERT); + + val = efuse_ceil(EFUSE_FSET_DEASSERT, time); + writeb(val, hdmi_base + S5P_EFUSE_FSET_DEASSERT); + + val = efuse_ceil(EFUSE_SENSING, time); + writeb(val, hdmi_base + S5P_EFUSE_SENSING); + + val = efuse_ceil(EFUSE_SCK_ASSERT, time); + writeb(val, hdmi_base + S5P_EFUSE_SCK_ASSERT); + + val = efuse_ceil(EFUSE_SCK_DEASSERT, time); + writeb(val, hdmi_base + S5P_EFUSE_SCK_DEASSERT); + + val = efuse_ceil(EFUSE_SDOUT_OFFSET, time); + writeb(val, hdmi_base + S5P_EFUSE_SDOUT_OFFSET); + + val = efuse_ceil(EFUSE_READ_OFFSET, time); + writeb(val, hdmi_base + S5P_EFUSE_READ_OFFSET); + +} +#endif + +/* + * load hdcp key from e-fuse mem. + */ +static int hdcp_loadkey(void) +{ + u8 status; + +#if 0 + hdcp_efuse_timing(); +#endif + /* read HDCP key from E-Fuse */ + writeb(EFUSE_CTRL_ACTIVATE, hdmi_base + S5P_EFUSE_CTRL); + + do { + status = readb(hdmi_base + S5P_EFUSE_STATUS); + } while (!(status & EFUSE_ECC_DONE)); + + if (readb(hdmi_base + S5P_EFUSE_STATUS) & EFUSE_ECC_FAIL) { + HDCPPRINTK("Can't load key from fuse ctrl.\n"); + return -EINVAL; + } + + return 0; + +} + +/* + * Start encryption + */ +static void start_encryption(void) +{ + u32 time_out = 100; + + if (readl(hdmi_base + S5P_HDCP_CHECK_RESULT) == + Ri_MATCH_RESULT__YES) { + + while (time_out) { + + if (readl(hdmi_base + S5P_STATUS) & AUTHENTICATED) { + writel(HDCP_ENC_ENABLE, + hdmi_base + S5P_ENC_EN); + HDCPPRINTK("Encryption start!!\n"); + s5p_hdmi_mute_en(false); + break; + } else { + time_out--; + mdelay(1); + } + } + } else { + writel(HDCP_ENC_DISABLE, hdmi_base + S5P_ENC_EN); + s5p_hdmi_mute_en(true); + HDCPPRINTK("Encryption stop!!\n"); + } +} + +/* + * Check whether Rx is repeater or not + */ +static int check_repeater(void) +{ + int ret = 0; + + u8 i = 0; + u16 j = 0; + + u8 bcaps[BCAPS_SIZE] = {0}; + u8 status[BSTATUS_SIZE] = {0, 0}; + u8 rx_v[SHA_1_HASH_SIZE] = {0}; + u8 ksv_list[HDCP_MAX_DEVS*HDCP_KSV_SIZE] = {0}; + + u32 dev_cnt; + u32 stat; + + bool ksv_fifo_ready = false; + + memset(rx_v, 0x0, SHA_1_HASH_SIZE); + memset(ksv_list, 0x0, HDCP_MAX_DEVS*HDCP_KSV_SIZE); + + while (j <= 50) { + ret = ddc_read(HDCP_Bcaps, + bcaps, BCAPS_SIZE); + + if (ret < 0) { + HDCPPRINTK("Can't read bcaps data from i2c bus\n"); + return false; + } + + if (bcaps[0] & KSV_FIFO_READY) { + HDCPPRINTK("ksv fifo is ready\n"); + ksv_fifo_ready = true; + writel(bcaps[0], hdmi_base + S5P_HDCP_BCAPS); + break; + } else { + HDCPPRINTK("ksv fifo is not ready\n"); + ksv_fifo_ready = false; + mdelay(100); + j++; + } + + bcaps[0] = 0; + } + + if (!ksv_fifo_ready) + return REPEATER_TIMEOUT_ERROR; + + /* + * Check MAX_CASCADE_EXCEEDED + * or MAX_DEVS_EXCEEDED indicator + */ + ret = ddc_read(HDCP_BStatus, + status, BSTATUS_SIZE); + + if (ret < 0) { + HDCPPRINTK("Can't read status data from i2c bus\n"); + return false; + } + + /* MAX_CASCADE_EXCEEDED || MAX_DEVS_EXCEEDED */ + if (status[1] & MAX_CASCADE_EXCEEDED) { + HDCPPRINTK("MAX_CASCADE_EXCEEDED\n"); + return MAX_CASCADE_EXCEEDED_ERROR; + } else if (status[0] & MAX_DEVS_EXCEEDED) { + HDCPPRINTK("MAX_CASCADE_EXCEEDED\n"); + return MAX_DEVS_EXCEEDED_ERROR; + } + + writel(status[0], hdmi_base + S5P_HDCP_BSTATUS_0); + writel(status[1], hdmi_base + S5P_HDCP_BSTATUS_1); + + /* Read KSV list */ + dev_cnt = status[0] & 0x7f; + + HDCPPRINTK("status[0] :0x%08x, status[1] :0x%08x!!\n", + status[0], status[1]); + + if (dev_cnt) { + + u32 val = 0; + + /* read ksv */ + ret = ddc_read(HDCP_KSVFIFO, ksv_list, + dev_cnt * HDCP_KSV_SIZE); + if (ret < 0) { + HDCPPRINTK("Can't read ksv fifo!!\n"); + return false; + } + + /* write ksv */ + for (i = 0; i < dev_cnt - 1; i++) { + + writel(ksv_list[(i*5) + 0], + hdmi_base + S5P_HDCP_RX_KSV_0_0); + writel(ksv_list[(i*5) + 1], + hdmi_base + S5P_HDCP_RX_KSV_0_1); + writel(ksv_list[(i*5) + 2], + hdmi_base + S5P_HDCP_RX_KSV_0_2); + writel(ksv_list[(i*5) + 3], + hdmi_base + S5P_HDCP_RX_KSV_0_3); + writel(ksv_list[(i*5) + 4], + hdmi_base + S5P_HDCP_RX_KSV_0_4); + + mdelay(1); + writel(SET_HDCP_KSV_WRITE_DONE, + hdmi_base + S5P_HDCP_RX_KSV_LIST_CTRL); + mdelay(1); + + stat = readl(hdmi_base + S5P_HDCP_RX_KSV_LIST_CTRL); + + if (!(stat & SET_HDCP_KSV_READ)) + return false; + + HDCPPRINTK("HDCP_RX_KSV_1 = 0x%x\n\r", + readl(hdmi_base + S5P_HDCP_RX_KSV_LIST_CTRL)); + HDCPPRINTK("i : %d, dev_cnt : %d, val = 0x%08x\n", + i, dev_cnt, val); + } + + writel(ksv_list[(i*5) + 0], hdmi_base + S5P_HDCP_RX_KSV_0_0); + writel(ksv_list[(i*5) + 1], hdmi_base + S5P_HDCP_RX_KSV_0_1); + writel(ksv_list[(i*5) + 2], hdmi_base + S5P_HDCP_RX_KSV_0_2); + writel(ksv_list[(i*5) + 3], hdmi_base + S5P_HDCP_RX_KSV_0_3); + writel(ksv_list[(i*5) + 4], hdmi_base + S5P_HDCP_RX_KSV_0_4); + + mdelay(1); + + /* end of ksv */ + val = SET_HDCP_KSV_END|SET_HDCP_KSV_WRITE_DONE; + writel(val, hdmi_base + S5P_HDCP_RX_KSV_LIST_CTRL); + + HDCPPRINTK("HDCP_RX_KSV_1 = 0x%x\n\r", + readl(hdmi_base + S5P_HDCP_RX_KSV_LIST_CTRL)); + HDCPPRINTK("i : %d, dev_cnt : %d, val = 0x%08x\n", + i, dev_cnt, val); + + } else { + + /* + mdelay(200); + */ + + writel(SET_HDCP_KSV_LIST_EMPTY, + hdmi_base + S5P_HDCP_RX_KSV_LIST_CTRL); + } + + + /* Read SHA-1 from receiver */ + ret = ddc_read(HDCP_SHA1, + rx_v, SHA_1_HASH_SIZE); + + if (ret < 0) { + HDCPPRINTK("Can't read sha_1_hash data from i2c bus\n"); + return false; + } + +#ifdef S5P_HDCP_DEBUG + for (i = 0; i < SHA_1_HASH_SIZE; i++) + HDCPPRINTK("SHA_1 rx :: %x\n", rx_v[i]); +#endif + + /* write SHA-1 to register */ + writeb(rx_v[0], hdmi_base + S5P_HDCP_RX_SHA1_0_0); + writeb(rx_v[1], hdmi_base + S5P_HDCP_RX_SHA1_0_1); + writeb(rx_v[2], hdmi_base + S5P_HDCP_RX_SHA1_0_2); + writeb(rx_v[3], hdmi_base + S5P_HDCP_RX_SHA1_0_3); + writeb(rx_v[4], hdmi_base + S5P_HDCP_RX_SHA1_1_0); + writeb(rx_v[5], hdmi_base + S5P_HDCP_RX_SHA1_1_1); + writeb(rx_v[6], hdmi_base + S5P_HDCP_RX_SHA1_1_2); + writeb(rx_v[7], hdmi_base + S5P_HDCP_RX_SHA1_1_3); + writeb(rx_v[8], hdmi_base + S5P_HDCP_RX_SHA1_2_0); + writeb(rx_v[9], hdmi_base + S5P_HDCP_RX_SHA1_2_1); + writeb(rx_v[10], hdmi_base + S5P_HDCP_RX_SHA1_2_2); + writeb(rx_v[11], hdmi_base + S5P_HDCP_RX_SHA1_2_3); + writeb(rx_v[12], hdmi_base + S5P_HDCP_RX_SHA1_3_0); + writeb(rx_v[13], hdmi_base + S5P_HDCP_RX_SHA1_3_1); + writeb(rx_v[14], hdmi_base + S5P_HDCP_RX_SHA1_3_2); + writeb(rx_v[15], hdmi_base + S5P_HDCP_RX_SHA1_3_3); + writeb(rx_v[16], hdmi_base + S5P_HDCP_RX_SHA1_4_0); + writeb(rx_v[17], hdmi_base + S5P_HDCP_RX_SHA1_4_1); + writeb(rx_v[18], hdmi_base + S5P_HDCP_RX_SHA1_4_2); + writeb(rx_v[19], hdmi_base + S5P_HDCP_RX_SHA1_4_3); + + /* SHA write done, and wait for SHA computation being done */ + mdelay(1); + + /* check authentication success or not */ + stat = readb(hdmi_base + S5P_HDCP_AUTH_STATUS); + + HDCPPRINTK("auth status %d\n", stat); + + if (stat & SET_HDCP_SHA_VALID_READY) { + + stat = readb(hdmi_base + S5P_HDCP_AUTH_STATUS); + + if (stat & SET_HDCP_SHA_VALID) + ret = true; + else + ret = false; + } else { + HDCPPRINTK("SHA not ready 0x%x \n\r", stat); + ret = false; + } + + /* clear all validate bit */ + writeb(0x0, hdmi_base + S5P_HDCP_AUTH_STATUS); + + return ret; + +} + +static bool try_read_receiver(void) +{ + u16 i = 0; + bool ret = false; + + s5p_hdmi_mute_en(true); + + for (i = 0; i < 400; i++) { + + msleep(250); + + if (hdcp_info.auth_status != RECEIVER_READ_READY) { + + HDCPPRINTK("hdcp stat. changed!!" + "failed attempt no = %d\n\r", i); + + return false; + } + + ret = read_bcaps(); + + if (ret) { + HDCPPRINTK("succeeded at attempt no= %d \n\r", i); + + return true; + } else + HDCPPRINTK("can't read bcaps!!" + "failed attempt no=%d\n\r", i); + } + + return false; +} + +/* + * stop - stop functions are only called under running HDCP + */ +bool __s5p_stop_hdcp(void) +{ + u32 sfr_val = 0; + + HDCPPRINTK("HDCP ftn. Stop!!\n"); +#if 0 + s5p_hdmi_disable_interrupts(HDMI_IRQ_HPD_PLUG); + s5p_hdmi_disable_interrupts(HDMI_IRQ_HPD_UNPLUG); +#endif + s5p_hdmi_disable_interrupts(HDMI_IRQ_HDCP); + + hdcp_protocol_status = 0; + + hdcp_info.time_out = INFINITE; + hdcp_info.event = HDCP_EVENT_STOP; + hdcp_info.auth_status = NOT_AUTHENTICATED; + hdcp_info.hdcp_enable = false; + + /* + hdcp_info.client = NULL; + */ + + /* 3. disable hdcp control reg. */ + sfr_val = readl(hdmi_base + S5P_HDCP_CTRL1); + sfr_val &= (ENABLE_1_DOT_1_FEATURE_DIS + & CLEAR_REPEATER_TIMEOUT + & EN_PJ_DIS + & CP_DESIRED_DIS); + writel(sfr_val, hdmi_base + S5P_HDCP_CTRL1); + + /* 1-3. disable hdmi hpd reg. */ + sw_hpd_enable(false); + + /* 1-2. disable hdmi status enable reg. */ + sfr_val = readl(hdmi_base + S5P_STATUS_EN); + sfr_val &= HDCP_STATUS_DIS_ALL; + writel(sfr_val, hdmi_base + S5P_STATUS_EN); + + /* 1-1. clear all status pending */ + sfr_val = readl(hdmi_base + S5P_STATUS); + sfr_val |= HDCP_STATUS_EN_ALL; + writel(sfr_val, hdmi_base + S5P_STATUS); + + /* disable encryption */ + HDCPPRINTK("Stop Encryption by Stop!!\n"); + writel(HDCP_ENC_DISABLE, hdmi_base + S5P_ENC_EN); + s5p_hdmi_mute_en(true); + + /* clear result */ + writel(Ri_MATCH_RESULT__NO, hdmi_base + S5P_HDCP_CHECK_RESULT); + writel(CLEAR_ALL_RESULTS, hdmi_base + S5P_HDCP_CHECK_RESULT); + + /* hdmi disable */ +#if 0 + sfr_val = readl(hdmi_base + S5P_HDMI_CON_0); + sfr_val &= ~(PWDN_ENB_NORMAL | HDMI_EN | ASP_EN); + writel(sfr_val, hdmi_base + S5P_HDMI_CON_0); + */ + HDCPPRINTK("\tSTATUS \t0x%08x\n", readl(hdmi_base + S5P_STATUS)); + HDCPPRINTK("\tSTATUS_EN \t0x%08x\n", + readl(hdmi_base + S5P_STATUS_EN)); + HDCPPRINTK("\tHPD \t0x%08x\n", readl(hdmi_base + S5P_HPD)); + HDCPPRINTK("\tHDCP_CTRL \t0x%08x\n", + readl(hdmi_base + S5P_HDCP_CTRL1)); + HDCPPRINTK("\tMODE_SEL \t0x%08x\n", + readl(hdmi_base + S5P_MODE_SEL)); + HDCPPRINTK("\tENC_EN \t0x%08x\n", readl(hdmi_base + S5P_ENC_EN)); + HDCPPRINTK("\tHDMI_CON_0 \t0x%08x\n", + readl(hdmi_base + S5P_HDMI_CON_0)); + + writel(sfr_val, hdmi_base + S5P_HDMI_CON_0); +#endif + return true; +} + + +void __s5p_hdcp_reset(void) +{ + + __s5p_stop_hdcp(); + + hdcp_protocol_status = 2; + + HDCPPRINTK("HDCP ftn. reset!!\n"); +} + +/* + * start - start functions are only called under stopping HDCP + */ +bool __s5p_start_hdcp(void) +{ + u8 reg; + u32 sfr_val; + + hdcp_info.event = HDCP_EVENT_STOP; + hdcp_info.time_out = INFINITE; + hdcp_info.auth_status = NOT_AUTHENTICATED; + + HDCPPRINTK("HDCP ftn. Start!!\n"); + + sw_reset = true; + reg = s5p_hdmi_get_enabled_interrupt(); + + s5p_hdmi_disable_interrupts(HDMI_IRQ_HPD_PLUG); + s5p_hdmi_disable_interrupts(HDMI_IRQ_HPD_UNPLUG); + + /* 2. Enable software HPD */ + sw_hpd_enable(true); + + /* 3. Make software HPD logical */ + set_sw_hpd(false); + + /* 4. Make software HPD logical */ + set_sw_hpd(true); + + /* 5. Disable software HPD */ + sw_hpd_enable(false); + set_sw_hpd(false); + + /* 6. Unmask HPD plug and unplug interrupt */ + + if (reg & 1<<HDMI_IRQ_HPD_PLUG) + s5p_hdmi_enable_interrupts(HDMI_IRQ_HPD_PLUG); + if (reg & 1<<HDMI_IRQ_HPD_UNPLUG) + s5p_hdmi_enable_interrupts(HDMI_IRQ_HPD_UNPLUG); + + sw_reset = false; + HDCPPRINTK("Stop Encryption by Start!!\n"); + + writel(HDCP_ENC_DISABLE, hdmi_base + S5P_ENC_EN); + s5p_hdmi_mute_en(true); + + hdcp_protocol_status = 1; + + if (hdcp_loadkey() < 0) + return false; + + /* for av mute */ + writel(DO_NOT_TRANSMIT, hdmi_base + S5P_GCP_CON); + + /* + * 1-1. set hdmi status enable reg. + * Update_Ri_int_en should be enabled after + * s/w gets ExchangeKSV_int. + */ + writel(HDCP_STATUS_EN_ALL, hdmi_base + S5P_STATUS_EN); + + /* + * 3. set hdcp control reg. + * Disable advance cipher option, Enable CP(Content Protection), + * Disable time-out (This bit is only available in a REPEATER) + * Disable XOR shift, Disable Pj port update, Use external key + */ + sfr_val = 0; + sfr_val |= CP_DESIRED_EN; + writel(sfr_val, hdmi_base + S5P_HDCP_CTRL1); + + s5p_hdmi_enable_interrupts(HDMI_IRQ_HDCP); + + if (!read_bcaps()) { + HDCPPRINTK("can't read ddc port!\n"); + reset_authentication(); + } + + hdcp_info.hdcp_enable = true; + + HDCPPRINTK("\tSTATUS \t0x%08x\n", + readl(hdmi_base + S5P_STATUS)); + HDCPPRINTK("\tSTATUS_EN \t0x%08x\n", + readl(hdmi_base + S5P_STATUS_EN)); + HDCPPRINTK("\tHPD \t0x%08x\n", readl(hdmi_base + S5P_HPD)); + HDCPPRINTK("\tHDCP_CTRL \t0x%08x\n", + readl(hdmi_base + S5P_HDCP_CTRL1)); + HDCPPRINTK("\tMODE_SEL \t0x%08x\n", + readl(hdmi_base + S5P_MODE_SEL)); + HDCPPRINTK("\tENC_EN \t0x%08x\n", + readl(hdmi_base + S5P_ENC_EN)); + HDCPPRINTK("\tHDMI_CON_0 \t0x%08x\n", + readl(hdmi_base + S5P_HDMI_CON_0)); + + return true; +} + + +static void bksv_start_bh(void) +{ + bool ret = false; + + HDCPPRINTK("HDCP_EVENT_READ_BKSV_START bh\n"); + + hdcp_info.auth_status = RECEIVER_READ_READY; + + ret = read_bcaps(); + + if (!ret) { + + ret = try_read_receiver(); + + if (!ret) { + HDCPPRINTK("Can't read bcaps!! retry failed!!\n" + "\t\t\t\thdcp ftn. will be stopped\n"); + + reset_authentication(); + return; + } + } + + hdcp_info.auth_status = BCAPS_READ_DONE; + + ret = read_bksv(); + + if (!ret) { + HDCPPRINTK("Can't read bksv!!" + "hdcp ftn. will be reset\n"); + + reset_authentication(); + return; + } + + hdcp_info.auth_status = BKSV_READ_DONE; + + HDCPPRINTK("authentication status : bksv is done (0x%08x)\n", + hdcp_info.auth_status); +} + +static void second_auth_start_bh(void) +{ + u8 count = 0; + int reg; + bool ret = false; + + int ret_err; + + u32 bcaps; + + HDCPPRINTK("HDCP_EVENT_SECOND_AUTH_START bh\n"); + + ret = read_bcaps(); + + if (!ret) { + + ret = try_read_receiver(); + + if (!ret) { + + HDCPPRINTK("Can't read bcaps!! retry failed!!\n" + "\t\t\t\thdcp ftn. will be stopped\n"); + + reset_authentication(); + return; + } + + } + + bcaps = readl(hdmi_base + S5P_HDCP_BCAPS); + bcaps &= (KSV_FIFO_READY); + + if (!bcaps) { + + HDCPPRINTK("ksv fifo is not ready\n"); + + do { + count++; + + ret = read_bcaps(); + + if (!ret) { + + ret = try_read_receiver(); + if (!ret) + reset_authentication(); + return; + + } + + bcaps = readl(hdmi_base + S5P_HDCP_BCAPS); + bcaps &= (KSV_FIFO_READY); + + if (bcaps) { + HDCPPRINTK("bcaps retries : %d\n", count); + break; + } + + mdelay(100); + + if (!hdcp_info.hdcp_enable) { + + reset_authentication(); + return; + + } + + } while (count <= 50); + + /* wait times exceeded 5 seconds */ + if (count > 50) { + + hdcp_info.time_out = INFINITE; + + /* + * time-out (This bit is only available in a REPEATER) + */ + writel(readl(hdmi_base + S5P_HDCP_CTRL1) | 0x1 << 2, + hdmi_base + S5P_HDCP_CTRL1); + + reset_authentication(); + + return; + } + } + + HDCPPRINTK("ksv fifo ready\n"); + + ret_err = check_repeater(); + + if (ret_err == true) { + u32 flag; + + hdcp_info.auth_status = SECOND_AUTHENTICATION_DONE; + HDCPPRINTK("second authentication done!!\n"); + + flag = readb(hdmi_base + S5P_STATUS); + HDCPPRINTK("hdcp state : %s authenticated!!\n", + flag & AUTHENTICATED ? "" : "not not"); + + start_encryption(); + } else if (ret_err == false) { + /* i2c error */ + HDCPPRINTK("repeater check error!!\n"); + reset_authentication(); + } else { + if (ret_err == REPEATER_ILLEGAL_DEVICE_ERROR) { + /* + * No need to start the HDCP + * in case of invalid KSV (revocation case) + */ + HDCPPRINTK("illegal dev. error!!\n"); + reg = readl(hdmi_base + S5P_HDCP_CTRL2); + reg = 0x1; + writel(reg, hdmi_base + S5P_HDCP_CTRL2); + reg = 0x0; + writel(reg, hdmi_base + S5P_HDCP_CTRL2); + + hdcp_info.auth_status = NOT_AUTHENTICATED; + + } else if (ret_err == REPEATER_TIMEOUT_ERROR) { + reg = readl(hdmi_base + S5P_HDCP_CTRL1); + reg |= SET_REPEATER_TIMEOUT; + writel(reg, hdmi_base + S5P_HDCP_CTRL1); + reg &= ~SET_REPEATER_TIMEOUT; + writel(reg, hdmi_base + S5P_HDCP_CTRL1); + + hdcp_info.auth_status = NOT_AUTHENTICATED; + } else { + /* + * MAX_CASCADE_EXCEEDED_ERROR + * MAX_DEVS_EXCEEDED_ERROR + */ + HDCPPRINTK("repeater check error(MAX_EXCEEDED)!!\n"); + reset_authentication(); + } + } +} + +static bool write_aksv_start_bh(void) +{ + bool ret = false; + + HDCPPRINTK("HDCP_EVENT_WRITE_AKSV_START bh\n"); + + if (hdcp_info.auth_status != BKSV_READ_DONE) { + HDCPPRINTK("bksv is not ready!!\n"); + return false; + } + + ret = write_an(); + if (!ret) + return false; + + hdcp_info.auth_status = AN_WRITE_DONE; + + HDCPPRINTK("an write done!!\n"); + + ret = write_aksv(); + if (!ret) + return false; + + /* + * Wait for 100ms. Transmitter must not read + * Ro' value sooner than 100ms after writing + * Aksv + */ + mdelay(100); + + hdcp_info.auth_status = AKSV_WRITE_DONE; + + HDCPPRINTK("aksv write done!!\n"); + + return ret; +} + +static bool check_ri_start_bh(void) +{ + bool ret = false; + + + HDCPPRINTK("HDCP_EVENT_CHECK_RI_START bh\n"); + + if (hdcp_info.auth_status == AKSV_WRITE_DONE || + hdcp_info.auth_status == FIRST_AUTHENTICATION_DONE || + hdcp_info.auth_status == SECOND_AUTHENTICATION_DONE) { + + ret = compare_r_val(); + + if (ret) { + + if (hdcp_info.auth_status == AKSV_WRITE_DONE) { + /* + * Check whether HDMI receiver is + * repeater or not + */ + if (hdcp_info.is_repeater) + hdcp_info.auth_status + = SECOND_AUTHENTICATION_RDY; + else { + hdcp_info.auth_status + = FIRST_AUTHENTICATION_DONE; + start_encryption(); + } + } + + } else { + + HDCPPRINTK("authentication reset\n"); + reset_authentication(); + + } + + HDCPPRINTK("auth_status = 0x%08x\n", + hdcp_info.auth_status); + + + return true; + } else + reset_authentication(); + + HDCPPRINTK("aksv_write or first/second" + " authentication is not done\n"); + + return false; +} + +/* + * bottom half for hdmi interrupt + * + */ +static void hdcp_work(void *arg) +{ + /* + * I2C int. was occurred + * for reading Bksv and Bcaps + */ + if (hdcp_info.event & (1 << HDCP_EVENT_READ_BKSV_START)) { + + bksv_start_bh(); + + /* clear event */ + /* + spin_lock_bh(&hdcp_info.lock); + */ + hdcp_info.event &= ~(1 << HDCP_EVENT_READ_BKSV_START); + /* + spin_unlock_bh(&hdcp_info.lock); + */ + } + /* + * Watchdog timer int. was occurred + * for checking repeater + */ + if (hdcp_info.event & (1 << HDCP_EVENT_SECOND_AUTH_START)) { + + second_auth_start_bh(); + + /* clear event */ + /* + spin_lock_bh(&hdcp_info.lock); + */ + hdcp_info.event &= ~(1 << HDCP_EVENT_SECOND_AUTH_START); + /* + spin_unlock_bh(&hdcp_info.lock); + */ + } + + /* + * An_Write int. was occurred + * for writing Ainfo, An and Aksv + */ + if (hdcp_info.event & (1 << HDCP_EVENT_WRITE_AKSV_START)) { + + write_aksv_start_bh(); + + /* clear event */ + /* + spin_lock_bh(&hdcp_info.lock); + */ + hdcp_info.event &= ~(1 << HDCP_EVENT_WRITE_AKSV_START); + /* + spin_unlock_bh(&hdcp_info.lock); + */ + } + + /* + * Ri int. was occurred + * for comparing Ri and Ri'(from HDMI sink) + */ + if (hdcp_info.event & (1 << HDCP_EVENT_CHECK_RI_START)) { + + + check_ri_start_bh(); + + /* clear event */ + /* + spin_lock_bh(&hdcp_info.lock); + */ + hdcp_info.event &= ~(1 << HDCP_EVENT_CHECK_RI_START); + /* + spin_unlock_bh(&hdcp_info.lock); + */ + } + +} + +void __s5p_init_hdcp(bool hpd_status, struct i2c_client *ddc_port) +{ + + HDCPPRINTK("HDCP ftn. Init!!\n"); + + is_dvi = false; + av_mute = false; + audio_en = true; + + /* for bh */ + INIT_WORK(&hdcp_info.work, (work_func_t)hdcp_work); + + init_waitqueue_head(&hdcp_info.waitq); + + /* for dev_dbg err. */ + spin_lock_init(&hdcp_info.lock); + +} + + +irqreturn_t __s5p_hdcp_irq_handler(int irq) + +{ + u32 event = 0; + u8 flag; + + event = 0; + /* check HDCP Status */ + flag = readb(hdmi_base + S5P_STATUS); + + HDCPPRINTK("irq_status : 0x%08x\n", readb(hdmi_base + S5P_STATUS)); + + HDCPPRINTK("hdcp state : %s authenticated!!\n", + flag & AUTHENTICATED ? "" : "not"); + + spin_lock_irq(&hdcp_info.lock); + + /* + * processing interrupt + * interrupt processing seq. is firstly set event for workqueue, + * and interrupt pending clear. 'flag|' was used for preventing + * to clear AUTHEN_ACK.- it caused many problem. be careful. + */ + /* I2C INT */ + if (flag & WTFORACTIVERX_INT_OCCURRED) { + event |= (1 << HDCP_EVENT_READ_BKSV_START); + writeb(flag | WTFORACTIVERX_INT_OCCURRED, + hdmi_base + S5P_STATUS); + writeb(0x0, hdmi_base + S5P_HDCP_I2C_INT); + } + + /* AN INT */ + if (flag & EXCHANGEKSV_INT_OCCURRED) { + event |= (1 << HDCP_EVENT_WRITE_AKSV_START); + writeb(flag | EXCHANGEKSV_INT_OCCURRED, + hdmi_base + S5P_STATUS); + writeb(0x0, hdmi_base + S5P_HDCP_AN_INT); + } + + /* RI INT */ + if (flag & UPDATE_RI_INT_OCCURRED) { + event |= (1 << HDCP_EVENT_CHECK_RI_START); + writeb(flag | UPDATE_RI_INT_OCCURRED, + hdmi_base + S5P_STATUS); + writeb(0x0, hdmi_base + S5P_HDCP_RI_INT); + } + + /* WATCHDOG INT */ + if (flag & WATCHDOG_INT_OCCURRED) { + event |= (1 << HDCP_EVENT_SECOND_AUTH_START); + writeb(flag | WATCHDOG_INT_OCCURRED, + hdmi_base + S5P_STATUS); + writeb(0x0, hdmi_base + S5P_HDCP_WDT_INT); + } + + if (!event) { + HDCPPRINTK("unknown irq.\n"); + return IRQ_HANDLED; + } + + hdcp_info.event |= event; + + schedule_work(&hdcp_info.work); + + spin_unlock_irq(&hdcp_info.lock); + + return IRQ_HANDLED; +} + +bool __s5p_set_hpd_detection(bool detection, bool hdcp_enabled, + struct i2c_client *client) +{ + u32 hpd_reg_val = 0; + + if (detection) + hpd_reg_val = CABLE_PLUGGED; + else + hpd_reg_val = CABLE_UNPLUGGED; + + + writel(hpd_reg_val, hdmi_base + S5P_HPD); + + HDCPPRINTK("HPD status :: 0x%08x\n\r", + readl(hdmi_base + S5P_HPD)); + + return true; +} + +int __s5p_hdcp_init(void) +{ + /* for bh */ + INIT_WORK(&hdcp_info.work, (work_func_t)hdcp_work); + is_dvi = false; + av_mute = false; + audio_en = true; + + init_waitqueue_head(&hdcp_info.waitq); + + /* for dev_dbg err. */ + spin_lock_init(&hdcp_info.lock); + spin_lock_init(&hdcp_info.reset_lock); + + s5p_hdmi_register_isr((hdmi_isr)__s5p_hdcp_irq_handler, + (u8)HDMI_IRQ_HDCP); + + return 0; +} + +/* called by hpd */ +int s5p_hdcp_encrypt_stop(bool on) +{ + u32 reg; + + if (hdcp_info.hdcp_enable) { + /* clear interrupt pending all */ + writeb(0x0, hdmi_base + S5P_HDCP_I2C_INT); + writeb(0x0, hdmi_base + S5P_HDCP_AN_INT); + writeb(0x0, hdmi_base + S5P_HDCP_RI_INT); + writeb(0x0, hdmi_base + S5P_HDCP_WDT_INT); + + writel(HDCP_ENC_DISABLE, hdmi_base + S5P_ENC_EN); + s5p_hdmi_mute_en(true); + + if (!sw_reset) { + reg = readl(hdmi_base + S5P_HDCP_CTRL1); + + if (on) { + writel(reg | CP_DESIRED_EN, + hdmi_base + S5P_HDCP_CTRL1); + s5p_hdmi_enable_interrupts(HDMI_IRQ_HDCP); + } else { + hdcp_info.event + = HDCP_EVENT_STOP; + hdcp_info.auth_status + = NOT_AUTHENTICATED; + writel(reg & ~CP_DESIRED_EN, + hdmi_base + S5P_HDCP_CTRL1); + s5p_hdmi_disable_interrupts(HDMI_IRQ_HDCP); + } + } + + HDCPPRINTK("Stop Encryption by HPD Event!!\n"); + } + + return 0; +} +EXPORT_SYMBOL(s5p_hdcp_encrypt_stop); + |