Index: drivers/media/video/samsung/tv20/hdcp_s5pv210.c |
diff --git a/drivers/media/video/samsung/tv20/hdcp_s5pv210.c b/drivers/media/video/samsung/tv20/hdcp_s5pv210.c |
new file mode 100644 |
index 0000000000000000000000000000000000000000..6b71e0f17526df8708ba81c4c68a6da2e6d03245 |
--- /dev/null |
+++ b/drivers/media/video/samsung/tv20/hdcp_s5pv210.c |
@@ -0,0 +1,1569 @@ |
+/* linux/drivers/media/video/samsung/tv20/hdcp_s5pv210.c |
+* |
+* Copyright (c) 2010 Samsung Electronics Co., Ltd. |
+* http://www.samsung.com/ |
+* |
+* S5PV210 - hdcp raw ftn file for Samsung TVOut driver |
+* |
+* 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 <mach/gpio.h> |
+ |
+#include "ddc.h" |
+#include "tv_out_s5pv210.h" |
+#include <mach/regs-hdmi.h> |
+ |
+#include <plat/gpio-cfg.h> |
+ |
+/* for Operation check */ |
+#ifdef CONFIG_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(KERN_INFO "\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; |
+ |
+ 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 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) |
+ |
+/* |
+ * 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; |
+ |
+ /* Read An from HDMI */ |
+ 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; |
+ |
+ /* Read Aksv from HDMI */ |
+ 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); |
+ |
+ 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"); |
+ |
+ /* writel(HDCP_ENC_DISABLE, hdmi_base + S5P_ENC_EN); */ |
+ |
+ while (!read_again_bksv()) { |
+ |
+ count++; |
+ |
+ mdelay(20); |
+ |
+ if (count == 140) |
+ 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++) { |
+ /* 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("\t\t\t Rx(ddc) ->"); |
+ printk("rj[0]: 0x%02x, rj[1]: 0x%02x\n", rj[0], rj[1]); |
+ printk(KERN_INFO "\t\t\t Tx(register) ->"); |
+ printk("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; |
+ } |
+ |
+ } |
+ |
+ 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; |
+ |
+ hdcp_info.time_out = INFINITE; |
+ hdcp_info.event = HDCP_EVENT_STOP; |
+ hdcp_info.auth_status = NOT_AUTHENTICATED; |
+ |
+ HDCPPRINTK("Now reset authentication\n"); |
+ |
+ /* set hdcp_int disable */ |
+ 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); |
+ |
+ /* clear all result */ |
+ writeb(CLEAR_ALL_RESULTS, hdmi_base + S5P_HDCP_CHECK_RESULT); |
+ |
+ /* 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 status pending */ |
+ reg = readb(hdmi_base + S5P_STATUS); |
+ reg |= HDCP_STATUS_EN_ALL; |
+ writeb(reg, hdmi_base + S5P_STATUS); |
+ |
+ /* Disable encryption */ |
+ writeb(HDCP_ENC_DIS, hdmi_base + S5P_ENC_EN); |
+ |
+ /* Disable hdcp */ |
+ writeb(0x0, hdmi_base + S5P_HDCP_CTRL1); |
+ writeb(0x0, hdmi_base + S5P_HDCP_CTRL2); |
+ |
+ /* |
+ * 1. Mask HPD plug and unplug interrupt |
+ * disable HPD INT |
+ */ |
+ 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 */ |
+ s5p_hdmi_enable_interrupts(HDMI_IRQ_HPD_PLUG); |
+ s5p_hdmi_enable_interrupts(HDMI_IRQ_HPD_UNPLUG); |
+ |
+ /* 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); |
+ |
+} |
+ |
+/* |
+ * 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; |
+} |
+ |
+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); |
+ |
+} |
+ |
+/* |
+ * load hdcp key from e-fuse mem. |
+ */ |
+static int hdcp_loadkey(void) |
+{ |
+ u8 status; |
+ |
+ hdcp_efuse_timing(); |
+ |
+ /* 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 hdcp_status; |
+ |
+ /* Ri == Ri' |Ready the compared result of Ri */ |
+ writel(Ri_MATCH_RESULT__YES, hdmi_base + S5P_HDCP_CHECK_RESULT); |
+ |
+ do { |
+ hdcp_status = readl(hdmi_base + S5P_STATUS); |
+ /* Wait for STATUS[7] to '1'*/ |
+ } while ((hdcp_status & AUTHENTICATED) != AUTHENTICATED); |
+ |
+ /* Start encryption */ |
+ writel(HDCP_ENC_ENABLE, hdmi_base + S5P_ENC_EN); |
+ |
+} |
+ |
+/* |
+ * 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 hdcp_ctrl = 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 <= 500) { |
+ 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(10); |
+ j++; |
+ } |
+ |
+ } |
+ |
+ if (j == 500) { |
+ HDCPPRINTK("ksv fifo check timeout occurred!!\n"); |
+ return false; |
+ } |
+ |
+ if (ksv_fifo_ready) { |
+ hdcp_ctrl = readl(hdmi_base + S5P_HDCP_CTRL1); |
+ hdcp_ctrl &= CLEAR_REPEATER_TIMEOUT; |
+ writel(hdcp_ctrl, hdmi_base + S5P_HDCP_CTRL1); |
+ } else |
+ return false; |
+ |
+ /* |
+ * 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) & 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; 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); |
+ |
+ if (i != (dev_cnt - 1)) { /* if it's not end */ |
+ /* it's not in manual */ |
+ writel(SET_HDCP_KSV_WRITE_DONE, |
+ S5P_HDCP_RX_KSV_LIST_CTRL); |
+ |
+ mdelay(20); |
+ |
+ /* check ksv readed */ |
+ do { |
+ if (!hdcp_info.hdcp_enable) |
+ return false; |
+ |
+ stat = readl(hdmi_base + |
+ S5P_HDCP_RX_KSV_LIST_CTRL); |
+ |
+ } while (!(stat & SET_HDCP_KSV_READ)); |
+ |
+ |
+ HDCPPRINTK("read complete\n"); |
+ |
+ } |
+ |
+ 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); |
+ } |
+ |
+ /* end of ksv */ |
+ val = readl(hdmi_base + S5P_HDCP_RX_KSV_LIST_CTRL); |
+ 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; |
+ } |
+ |
+ for (i = 0; i < SHA_1_HASH_SIZE; i++) |
+ HDCPPRINTK("SHA_1 rx :: %x\n", rx_v[i]); |
+ |
+ /* write SHA-1 to register */ |
+ writel(rx_v[0], hdmi_base + S5P_HDCP_RX_SHA1_0_0); |
+ writel(rx_v[1], hdmi_base + S5P_HDCP_RX_SHA1_0_1); |
+ writel(rx_v[2], hdmi_base + S5P_HDCP_RX_SHA1_0_2); |
+ writel(rx_v[3], hdmi_base + S5P_HDCP_RX_SHA1_0_3); |
+ writel(rx_v[4], hdmi_base + S5P_HDCP_RX_SHA1_1_0); |
+ writel(rx_v[5], hdmi_base + S5P_HDCP_RX_SHA1_1_1); |
+ writel(rx_v[6], hdmi_base + S5P_HDCP_RX_SHA1_1_2); |
+ writel(rx_v[7], hdmi_base + S5P_HDCP_RX_SHA1_1_3); |
+ writel(rx_v[8], hdmi_base + S5P_HDCP_RX_SHA1_2_0); |
+ writel(rx_v[9], hdmi_base + S5P_HDCP_RX_SHA1_2_1); |
+ writel(rx_v[10], hdmi_base + S5P_HDCP_RX_SHA1_2_2); |
+ writel(rx_v[11], hdmi_base + S5P_HDCP_RX_SHA1_2_3); |
+ writel(rx_v[12], hdmi_base + S5P_HDCP_RX_SHA1_3_0); |
+ writel(rx_v[13], hdmi_base + S5P_HDCP_RX_SHA1_3_1); |
+ writel(rx_v[14], hdmi_base + S5P_HDCP_RX_SHA1_3_2); |
+ writel(rx_v[15], hdmi_base + S5P_HDCP_RX_SHA1_3_3); |
+ writel(rx_v[16], hdmi_base + S5P_HDCP_RX_SHA1_4_0); |
+ writel(rx_v[17], hdmi_base + S5P_HDCP_RX_SHA1_4_1); |
+ writel(rx_v[18], hdmi_base + S5P_HDCP_RX_SHA1_4_2); |
+ writel(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 = readl(hdmi_base + S5P_HDCP_AUTH_STATUS); |
+ |
+ HDCPPRINTK("auth status %d\n", stat); |
+ |
+ if (stat & SET_HDCP_SHA_VALID_READY) { |
+ |
+ HDCPPRINTK("SHA valid ready 0x%x \n\r", stat); |
+ |
+ stat = readl(hdmi_base + S5P_HDCP_AUTH_STATUS); |
+ |
+ if (stat & SET_HDCP_SHA_VALID) { |
+ |
+ HDCPPRINTK("SHA valid 0x%x \n\r", stat); |
+ |
+ ret = true; |
+ } else { |
+ HDCPPRINTK("SHA valid ready, but not valid 0x%x \n\r", |
+ stat); |
+ ret = false; |
+ } |
+ |
+ } else { |
+ |
+ HDCPPRINTK("SHA not ready 0x%x \n\r", stat); |
+ ret = false; |
+ } |
+ |
+ |
+ /* clear all validate bit */ |
+ writel(0x0, hdmi_base + S5P_HDCP_AUTH_STATUS); |
+ |
+ return ret; |
+ |
+} |
+ |
+static bool try_read_receiver(void) |
+{ |
+ u8 i = 0; |
+ bool ret = false; |
+ |
+ for (i = 0; i < 40; i++) { |
+ |
+ mdelay(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 tv_stop_hdcp(void) |
+{ |
+ u32 sfr_val = 0; |
+ |
+ HDCPPRINTK("HDCP ftn. Stop!!\n"); |
+ |
+ s5p_hdmi_disable_interrupts(HDMI_IRQ_HPD_PLUG); |
+ s5p_hdmi_disable_interrupts(HDMI_IRQ_HPD_UNPLUG); |
+ 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. */ |
+ writel(CABLE_UNPLUGGED, hdmi_base + S5P_HPD); |
+ |
+ /* 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 */ |
+ writel(HDCP_ENC_DISABLE, hdmi_base + S5P_ENC_EN); |
+ |
+ /* clear result */ |
+ 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); |
+ writel(CLEAR_ALL_RESULTS, hdmi_base + S5P_HDCP_CHECK_RESULT); |
+ |
+ /* hdmi disable */ |
+ /* |
+ 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)); |
+ |
+ return true; |
+} |
+ |
+void tv_hdcp_reset(void) |
+{ |
+ tv_stop_hdcp(); |
+ |
+ hdcp_protocol_status = 2; |
+ |
+ HDCPPRINTK("HDCP ftn. reset!!\n"); |
+} |
+ |
+/* |
+ * start - start functions are only called under stopping HDCP |
+ */ |
+bool tv_start_hdcp(void) |
+{ |
+ 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"); |
+ |
+ /* from hpd for test */ |
+ s5p_hdmi_hpd_gen(); |
+ |
+ s3c_gpio_cfgpin(S5PV210_GPH1(5), S5P_GPH1_5_HDMI_HPD); |
+ s3c_gpio_setpull(S5PV210_GPH1(5), S3C_GPIO_PULL_UP); |
+ |
+ s5p_hdmi_enable_interrupts(HDMI_IRQ_HDCP); |
+ |
+ 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 */ |
+ s5p_hdmi_enable_interrupts(HDMI_IRQ_HPD_PLUG); |
+ s5p_hdmi_enable_interrupts(HDMI_IRQ_HPD_UNPLUG); |
+ |
+ |
+ hdcp_protocol_status = 1; |
+ |
+ if (!read_bcaps()) { |
+ HDCPPRINTK("can't read ddc port!\n"); |
+ reset_authentication(); |
+ } |
+ |
+ /* 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); |
+ |
+ /* 1-2. set hdmi hpd reg. */ |
+ writel(CABLE_PLUGGED, hdmi_base+S5P_HPD); |
+ |
+ if (hdcp_loadkey() < 0) |
+ return false; |
+ |
+ /* |
+ * 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); |
+ |
+ 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"); |
+ |
+ tv_stop_hdcp(); |
+ return; |
+ } |
+ } |
+ |
+ hdcp_info.auth_status = BCAPS_READ_DONE; |
+ |
+ ret = read_bksv(); |
+ |
+ if (!ret) { |
+ HDCPPRINTK("Can't read bksv!!" |
+ "hdcp ftn. will be reset\n"); |
+ |
+ tv_stop_hdcp(); |
+ 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; |
+ 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"); |
+ |
+ tv_stop_hdcp(); |
+ 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) |
+ tv_stop_hdcp(); |
+ |
+ 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) { |
+ |
+ tv_stop_hdcp(); |
+ |
+ 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"); |
+ |
+ tv_stop_hdcp(); |
+ } 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_ainfo(); |
+ if (!ret) |
+ return false; |
+ |
+ HDCPPRINTK("ainfo write done!!\n"); |
+ |
+ 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; |
+ } |
+ |
+ HDCPPRINTK("aksv_write or first/second" |
+ " authentication is not done\n"); |
+ |
+ return false; |
+} |
+ |
+/* |
+ * bottom half for hdmi interrupt |
+ * |
+ */ |
+static void hdcp_work(void *arg) |
+{ |
+ /* HDCPPRINTK("event : 0x%08x \n\r", hdcp_info.event); */ |
+ |
+ /* |
+ * 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); */ |
+ } |
+ |
+} |
+ |
+irqreturn_t tv_hdcp_irq_handler(int irq, void *dev_id) |
+{ |
+ 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 tv_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 tv_hdcp_init(void) |
+{ |
+ /* 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); |
+ |
+ s5p_hdmi_register_isr((hdmi_isr)tv_hdcp_irq_handler, |
+ (u8)HDMI_IRQ_HDCP); |
+ |
+ return 0; |
+} |