diff options
Diffstat (limited to 'drivers/media/video/exynos/tv/hdcp_drv.c')
-rw-r--r-- | drivers/media/video/exynos/tv/hdcp_drv.c | 987 |
1 files changed, 987 insertions, 0 deletions
diff --git a/drivers/media/video/exynos/tv/hdcp_drv.c b/drivers/media/video/exynos/tv/hdcp_drv.c new file mode 100644 index 0000000..d223fdd --- /dev/null +++ b/drivers/media/video/exynos/tv/hdcp_drv.c @@ -0,0 +1,987 @@ +/* linux/drivers/media/video/exynos/tv/hdcp_drv.c + * + * Copyright (c) 2011 Samsung Electronics + * http://www.samsung.com/ + * + * HDCP function for Samsung TV 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/i2c.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/workqueue.h> + +#include "hdmi.h" +#include "regs-hdmi-5250.h" + +#define AN_SIZE 8 +#define AKSV_SIZE 5 +#define BKSV_SIZE 5 +#define MAX_KEY_SIZE 16 + +#define BKSV_RETRY_CNT 14 +#define BKSV_DELAY 100 + +#define DDC_RETRY_CNT 400000 +#define DDC_DELAY 25 + +#define KEY_LOAD_RETRY_CNT 1000 +#define ENCRYPT_CHECK_CNT 10 + +#define KSV_FIFO_RETRY_CNT 50 +#define KSV_FIFO_CHK_DELAY 100 /* ms */ +#define KSV_LIST_RETRY_CNT 10000 + +#define BCAPS_SIZE 1 +#define BSTATUS_SIZE 2 +#define SHA_1_HASH_SIZE 20 +#define HDCP_MAX_DEVS 128 +#define HDCP_KSV_SIZE 5 + +/* offset of HDCP port */ +#define HDCP_BKSV 0x00 +#define HDCP_RI 0x08 +#define HDCP_AKSV 0x10 +#define HDCP_AN 0x18 +#define HDCP_SHA1 0x20 +#define HDCP_BCAPS 0x40 +#define HDCP_BSTATUS 0x41 +#define HDCP_KSVFIFO 0x43 + +#define KSV_FIFO_READY (0x1 << 5) + +#define MAX_CASCADE_EXCEEDED_ERROR (-2) +#define MAX_DEVS_EXCEEDED_ERROR (-3) +#define REPEATER_ILLEGAL_DEVICE_ERROR (-4) +#define REPEATER_TIMEOUT_ERROR (-5) + +#define MAX_CASCADE_EXCEEDED (0x1 << 3) +#define MAX_DEVS_EXCEEDED (0x1 << 7) + +struct i2c_client *hdcp_client; + +int hdcp_i2c_read(struct hdmi_device *hdev, u8 offset, int bytes, u8 *buf) +{ + struct device *dev = hdev->dev; + struct i2c_client *i2c = hdcp_client; + int ret, cnt = 0; + + struct i2c_msg msg[] = { + [0] = { + .addr = i2c->addr, + .flags = 0, + .len = 1, + .buf = &offset + }, + [1] = { + .addr = i2c->addr, + .flags = I2C_M_RD, + .len = bytes, + .buf = buf + } + }; + + do { + if (!is_hdmi_streaming(hdev)) + goto ddc_read_err; + + ret = i2c_transfer(i2c->adapter, msg, 2); + + if (ret < 0 || ret != 2) + dev_dbg(dev, "%s: can't read data, retry %d\n", + __func__, cnt); + else + break; + + if (hdev->hdcp_info.auth_status == FIRST_AUTHENTICATION_DONE + || hdev->hdcp_info.auth_status == SECOND_AUTHENTICATION_DONE) + goto ddc_read_err; + + msleep(DDC_DELAY); + cnt++; + } while (cnt < DDC_RETRY_CNT); + + if (cnt == DDC_RETRY_CNT) + goto ddc_read_err; + + dev_dbg(dev, "%s: read data ok\n", __func__); + + return 0; + +ddc_read_err: + dev_err(dev, "%s: can't read data, timeout\n", __func__); + return -ETIME; +} + +int hdcp_i2c_write(struct hdmi_device *hdev, u8 offset, int bytes, u8 *buf) +{ + struct device *dev = hdev->dev; + struct i2c_client *i2c = hdcp_client; + u8 msg[bytes + 1]; + int ret, cnt = 0; + + msg[0] = offset; + memcpy(&msg[1], buf, bytes); + + do { + if (!is_hdmi_streaming(hdev)) + goto ddc_write_err; + + ret = i2c_master_send(i2c, msg, bytes + 1); + + if (ret < 0 || ret < bytes + 1) + dev_dbg(dev, "%s: can't write data, retry %d\n", + __func__, cnt); + else + break; + + msleep(DDC_DELAY); + cnt++; + } while (cnt < DDC_RETRY_CNT); + + if (cnt == DDC_RETRY_CNT) + goto ddc_write_err; + + dev_dbg(dev, "%s: write data ok\n", __func__); + return 0; + +ddc_write_err: + dev_err(dev, "%s: can't write data, timeout\n", __func__); + return -ETIME; +} + +static int __devinit hdcp_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + int ret = 0; + + hdcp_client = client; + + dev_info(&client->adapter->dev, "attached exynos hdcp " + "into i2c adapter successfully\n"); + + return ret; +} + +static int hdcp_remove(struct i2c_client *client) +{ + dev_info(&client->adapter->dev, "detached exynos hdcp " + "from i2c adapter successfully\n"); + + return 0; +} + +static int hdcp_suspend(struct i2c_client *cl, pm_message_t mesg) +{ + return 0; +}; + +static int hdcp_resume(struct i2c_client *cl) +{ + return 0; +}; + +static struct i2c_device_id hdcp_idtable[] = { + {"exynos_hdcp", 0}, +}; +MODULE_DEVICE_TABLE(i2c, hdcp_idtable); + +static struct i2c_driver hdcp_driver = { + .driver = { + .name = "exynos_hdcp", + .owner = THIS_MODULE, + }, + .id_table = hdcp_idtable, + .probe = hdcp_probe, + .remove = __devexit_p(hdcp_remove), + .suspend = hdcp_suspend, + .resume = hdcp_resume, +}; + +static int __init hdcp_init(void) +{ + return i2c_add_driver(&hdcp_driver); +} + +static void __exit hdcp_exit(void) +{ + i2c_del_driver(&hdcp_driver); +} + +module_init(hdcp_init); +module_exit(hdcp_exit); + +/* internal functions of HDCP */ +static void hdcp_encryption(struct hdmi_device *hdev, bool on) +{ + if (on) + hdmi_write_mask(hdev, HDMI_ENC_EN, ~0, HDMI_HDCP_ENC_ENABLE); + else + hdmi_write_mask(hdev, HDMI_ENC_EN, 0, HDMI_HDCP_ENC_ENABLE); + + hdmi_reg_mute(hdev, !on); +} + +static int hdcp_write_key(struct hdmi_device *hdev, int size, int reg, int offset) +{ + struct device *dev = hdev->dev; + u8 buf[MAX_KEY_SIZE]; + int cnt, zero = 0; + int i; + + memset(buf, 0, sizeof(buf)); + hdmi_read_bytes(hdev, reg, buf, size); + + for (cnt = 0; cnt < size; cnt++) + if (buf[cnt] == 0) + zero++; + + if (zero == size) { + dev_dbg(dev, "%s: %s is null\n", __func__, + offset == HDCP_AN ? "An" : "Aksv"); + goto write_key_err; + } + + if (hdcp_i2c_write(hdev, offset, size, buf) < 0) + goto write_key_err; + + for (i = 1; i < size + 1; i++) + dev_dbg(dev, "%s: %s[%d] : 0x%02x\n", __func__, + offset == HDCP_AN ? "An" : "Aksv", i, buf[i]); + + return 0; + +write_key_err: + dev_dbg(dev, "%s: write %s is failed\n", __func__, + offset == HDCP_AN ? "An" : "Aksv"); + return -1; +} + +static int hdcp_read_bcaps(struct hdmi_device *hdev) +{ + struct device *dev = hdev->dev; + u8 bcaps = 0; + + if (hdcp_i2c_read(hdev, HDCP_BCAPS, BCAPS_SIZE, &bcaps) < 0) + goto bcaps_read_err; + + if (!is_hdmi_streaming(hdev)) + goto bcaps_read_err; + + hdmi_writeb(hdev, HDMI_HDCP_BCAPS, bcaps); + + if (bcaps & HDMI_HDCP_BCAPS_REPEATER) + hdev->hdcp_info.is_repeater = 1; + else + hdev->hdcp_info.is_repeater = 0; + + dev_dbg(dev, "%s: device is %s\n", __func__, + hdev->hdcp_info.is_repeater ? "REPEAT" : "SINK"); + dev_dbg(dev, "%s: [i2c] bcaps : 0x%02x\n", __func__, bcaps); + + return 0; + +bcaps_read_err: + dev_err(dev, "can't read bcaps : timeout\n"); + return -ETIME; +} + +static int hdcp_read_bksv(struct hdmi_device *hdev) +{ + struct device *dev = hdev->dev; + u8 bksv[BKSV_SIZE]; + int i, j; + u32 one = 0, zero = 0, result = 0; + u32 cnt = 0; + + memset(bksv, 0, sizeof(bksv)); + + do { + if (hdcp_i2c_read(hdev, HDCP_BKSV, BKSV_SIZE, bksv) < 0) + goto bksv_read_err; + + for (i = 0; i < BKSV_SIZE; i++) + dev_dbg(dev, "%s: i2c read : bksv[%d]: 0x%x\n", + __func__, i, bksv[i]); + + for (i = 0; i < BKSV_SIZE; i++) { + + for (j = 0; j < 8; j++) { + result = bksv[i] & (0x1 << j); + + if (result == 0) + zero++; + else + one++; + } + + } + + if (!is_hdmi_streaming(hdev)) + goto bksv_read_err; + + if ((zero == 20) && (one == 20)) { + hdmi_write_bytes(hdev, HDMI_HDCP_BKSV_(0), bksv, BKSV_SIZE); + break; + } + dev_dbg(dev, "%s: invalid bksv, retry : %d\n", __func__, cnt); + + msleep(BKSV_DELAY); + cnt++; + } while (cnt < BKSV_RETRY_CNT); + + if (cnt == BKSV_RETRY_CNT) + goto bksv_read_err; + + dev_dbg(dev, "%s: bksv read OK, retry : %d\n", __func__, cnt); + return 0; + +bksv_read_err: + dev_err(dev, "%s: can't read bksv : timeout\n", __func__); + return -ETIME; +} + +static int hdcp_read_ri(struct hdmi_device *hdev) +{ + struct device *dev = hdev->dev; + u8 ri[2] = {0, 0}; + u8 rj[2] = {0, 0}; + + + ri[0] = hdmi_readb(hdev, HDMI_HDCP_RI_0); + ri[1] = hdmi_readb(hdev, HDMI_HDCP_RI_1); + + if (hdcp_i2c_read(hdev, HDCP_RI, 2, rj) < 0) + goto compare_err; + + dev_dbg(dev, "%s: Rx -> rj[0]: 0x%02x, rj[1]: 0x%02x\n", __func__, + rj[0], rj[1]); + dev_dbg(dev, "%s: Tx -> ri[0]: 0x%02x, ri[1]: 0x%02x\n", __func__, + ri[0], ri[1]); + + if ((ri[0] == rj[0]) && (ri[1] == rj[1]) && (ri[0] | ri[1])) + hdmi_writeb(hdev, HDMI_HDCP_CHECK_RESULT, + HDMI_HDCP_RI_MATCH_RESULT_Y); + else { + hdmi_writeb(hdev, HDMI_HDCP_CHECK_RESULT, + HDMI_HDCP_RI_MATCH_RESULT_N); + goto compare_err; + } + + memset(ri, 0, sizeof(ri)); + memset(rj, 0, sizeof(rj)); + + dev_dbg(dev, "%s: ri and ri' are matched\n", __func__); + + return 0; + +compare_err: + hdev->hdcp_info.event = HDCP_EVENT_STOP; + hdev->hdcp_info.auth_status = NOT_AUTHENTICATED; + dev_err(dev, "%s: ri and ri' are mismatched\n", __func__); + msleep(10); + return -1; +} + +static void hdcp_sw_reset(struct hdmi_device *hdev) +{ + u8 val; + + val = hdmi_get_int_mask(hdev); + + hdmi_set_int_mask(hdev, HDMI_INTC_EN_HPD_PLUG, 0); + hdmi_set_int_mask(hdev, HDMI_INTC_EN_HPD_UNPLUG, 0); + + hdmi_sw_hpd_enable(hdev, 1); + hdmi_sw_hpd_plug(hdev, 0); + hdmi_sw_hpd_plug(hdev, 1); + hdmi_sw_hpd_enable(hdev, 0); + + if (val & HDMI_INTC_EN_HPD_PLUG) + hdmi_set_int_mask(hdev, HDMI_INTC_EN_HPD_PLUG, 1); + if (val & HDMI_INTC_EN_HPD_UNPLUG) + hdmi_set_int_mask(hdev, HDMI_INTC_EN_HPD_UNPLUG, 1); +} + +static int hdcp_reset_auth(struct hdmi_device *hdev) +{ + struct device *dev = hdev->dev; + u8 val; + unsigned long spin_flags; + + if (!is_hdmi_streaming(hdev)) + return -ENODEV; + + spin_lock_irqsave(&hdev->hdcp_info.reset_lock, spin_flags); + + hdev->hdcp_info.event = HDCP_EVENT_STOP; + hdev->hdcp_info.auth_status = NOT_AUTHENTICATED; + + hdmi_write(hdev, HDMI_HDCP_CTRL1, 0x0); + hdmi_write(hdev, HDMI_HDCP_CTRL2, 0x0); + hdmi_reg_mute(hdev, 1); + + hdcp_encryption(hdev, 0); + + dev_dbg(dev, "%s: reset authentication\n", __func__); + + val = HDMI_UPDATE_RI_INT_EN | HDMI_WRITE_INT_EN | + HDMI_WATCHDOG_INT_EN | HDMI_WTFORACTIVERX_INT_EN; + hdmi_write_mask(hdev, HDMI_STATUS_EN, 0, val); + + hdmi_writeb(hdev, HDMI_HDCP_CHECK_RESULT, HDMI_HDCP_CLR_ALL_RESULTS); + + /* need some delay (at least 1 frame) */ + mdelay(16); + + hdcp_sw_reset(hdev); + + val = HDMI_UPDATE_RI_INT_EN | HDMI_WRITE_INT_EN | + HDMI_WATCHDOG_INT_EN | HDMI_WTFORACTIVERX_INT_EN; + hdmi_write_mask(hdev, HDMI_STATUS_EN, ~0, val); + hdmi_write_mask(hdev, HDMI_HDCP_CTRL1, ~0, HDMI_HDCP_CP_DESIRED_EN); + spin_unlock_irqrestore(&hdev->hdcp_info.reset_lock, spin_flags); + + return 0; +} + +static int hdcp_loadkey(struct hdmi_device *hdev) +{ + struct device *dev = hdev->dev; + u8 val; + int cnt = 0; + + hdmi_write_mask(hdev, HDMI_EFUSE_CTRL, ~0, HDMI_EFUSE_CTRL_HDCP_KEY_READ); + + do { + val = hdmi_readb(hdev, HDMI_EFUSE_STATUS); + if (val & HDMI_EFUSE_ECC_DONE) + break; + cnt++; + mdelay(1); + } while (cnt < KEY_LOAD_RETRY_CNT); + + if (cnt == KEY_LOAD_RETRY_CNT) + goto key_load_err; + + val = hdmi_readb(hdev, HDMI_EFUSE_STATUS); + + if (val & HDMI_EFUSE_ECC_FAIL) + goto key_load_err; + + dev_dbg(dev, "%s: load key is ok\n", __func__); + return 0; + +key_load_err: + dev_err(dev, "%s: can't load key\n", __func__); + return -1; +} + +static int hdmi_start_encryption(struct hdmi_device *hdev) +{ + struct device *dev = hdev->dev; + u8 val; + u32 cnt = 0; + + do { + val = hdmi_readb(hdev, HDMI_STATUS); + + if (val & HDMI_AUTHEN_ACK_AUTH) { + hdcp_encryption(hdev, 1); + break; + } + + mdelay(1); + + cnt++; + } while (cnt < ENCRYPT_CHECK_CNT); + + if (cnt == ENCRYPT_CHECK_CNT) + goto encrypt_err; + + + dev_dbg(dev, "%s: encryption is start\n", __func__); + return 0; + +encrypt_err: + hdcp_encryption(hdev, 0); + dev_err(dev, "%s: encryption is failed\n", __func__); + return -1; +} + +static int hdmi_check_repeater(struct hdmi_device *hdev) +{ + struct device *dev = hdev->dev; + int val, i; + int cnt = 0, cnt2 = 0; + + u8 bcaps = 0; + u8 status[BSTATUS_SIZE]; + u8 rx_v[SHA_1_HASH_SIZE]; + u8 ksv_list[HDCP_MAX_DEVS * HDCP_KSV_SIZE]; + + u32 dev_cnt; + + memset(status, 0, sizeof(status)); + memset(rx_v, 0, sizeof(rx_v)); + memset(ksv_list, 0, sizeof(ksv_list)); + + do { + if (hdcp_read_bcaps(hdev) < 0) + goto check_repeater_err; + + bcaps = hdmi_readb(hdev, HDMI_HDCP_BCAPS); + + if (bcaps & KSV_FIFO_READY) { + dev_dbg(dev, "%s: repeater : ksv fifo not ready\n", + __func__); + dev_dbg(dev, "%s: retries = %d\n", __func__, cnt); + break; + } + + msleep(KSV_FIFO_CHK_DELAY); + + cnt++; + } while (cnt < KSV_FIFO_RETRY_CNT); + + if (cnt == KSV_FIFO_RETRY_CNT) + return REPEATER_TIMEOUT_ERROR; + + dev_dbg(dev, "%s: repeater : ksv fifo ready\n", __func__); + + if (hdcp_i2c_read(hdev, HDCP_BSTATUS, BSTATUS_SIZE, status) < 0) + goto check_repeater_err; + + if (status[1] & MAX_CASCADE_EXCEEDED) + return MAX_CASCADE_EXCEEDED_ERROR; + else if (status[0] & MAX_DEVS_EXCEEDED) + return MAX_DEVS_EXCEEDED_ERROR; + + hdmi_writeb(hdev, HDMI_HDCP_BSTATUS_0, status[0]); + hdmi_writeb(hdev, HDMI_HDCP_BSTATUS_1, status[1]); + + dev_dbg(dev, "%s: status[0] :0x%02x\n", __func__, status[0]); + dev_dbg(dev, "%s: status[1] :0x%02x\n", __func__, status[1]); + + dev_cnt = status[0] & 0x7f; + + dev_dbg(dev, "%s: repeater : dev cnt = %d\n", __func__, dev_cnt); + + if (dev_cnt) { + + if (hdcp_i2c_read(hdev, HDCP_KSVFIFO, dev_cnt * HDCP_KSV_SIZE, + ksv_list) < 0) + goto check_repeater_err; + + cnt = 0; + + do { + hdmi_write_bytes(hdev, HDMI_HDCP_KSV_LIST_(0), + &ksv_list[cnt * 5], HDCP_KSV_SIZE); + + val = HDMI_HDCP_KSV_WRITE_DONE; + + if (cnt == dev_cnt - 1) + val |= HDMI_HDCP_KSV_END; + + hdmi_write(hdev, HDMI_HDCP_KSV_LIST_CON, val); + + if (cnt < dev_cnt - 1) { + cnt2 = 0; + do { + val = hdmi_readb(hdev, + HDMI_HDCP_KSV_LIST_CON); + if (val & HDMI_HDCP_KSV_READ) + break; + cnt2++; + } while (cnt2 < KSV_LIST_RETRY_CNT); + + if (cnt2 == KSV_LIST_RETRY_CNT) + dev_dbg(dev, "%s: ksv list not readed\n", + __func__); + } + cnt++; + } while (cnt < dev_cnt); + } else + hdmi_writeb(hdev, HDMI_HDCP_KSV_LIST_CON, HDMI_HDCP_KSV_LIST_EMPTY); + + if (hdcp_i2c_read(hdev, HDCP_SHA1, SHA_1_HASH_SIZE, rx_v) < 0) + goto check_repeater_err; + + for (i = 0; i < SHA_1_HASH_SIZE; i++) + dev_dbg(dev, "%s: [i2c] SHA-1 rx :: %02x\n", __func__, rx_v[i]); + + hdmi_write_bytes(hdev, HDMI_HDCP_SHA1_(0), rx_v, SHA_1_HASH_SIZE); + + val = hdmi_readb(hdev, HDMI_HDCP_SHA_RESULT); + if (val & HDMI_HDCP_SHA_VALID_RD) { + if (val & HDMI_HDCP_SHA_VALID) { + dev_dbg(dev, "%s: SHA-1 result is ok\n", __func__); + hdmi_writeb(hdev, HDMI_HDCP_SHA_RESULT, 0x0); + } else { + dev_dbg(dev, "%s: SHA-1 result is not vaild\n", __func__); + hdmi_writeb(hdev, HDMI_HDCP_SHA_RESULT, 0x0); + goto check_repeater_err; + } + } else { + dev_dbg(dev, "%s: SHA-1 result is not ready\n", __func__); + hdmi_writeb(hdev, HDMI_HDCP_SHA_RESULT, 0x0); + goto check_repeater_err; + } + + dev_dbg(dev, "%s: check repeater is ok\n", __func__); + return 0; + +check_repeater_err: + dev_err(dev, "%s: check repeater is failed\n", __func__); + return -1; +} + +static int hdcp_bksv(struct hdmi_device *hdev) +{ + struct device *dev = hdev->dev; + + dev_dbg(dev, "%s\n", __func__); + + hdev->hdcp_info.auth_status = RECEIVER_READ_READY; + + if (hdcp_read_bcaps(hdev) < 0) + goto bksv_start_err; + + hdev->hdcp_info.auth_status = BCAPS_READ_DONE; + + if (hdcp_read_bksv(hdev) < 0) + goto bksv_start_err; + + hdev->hdcp_info.auth_status = BKSV_READ_DONE; + + dev_dbg(dev, "%s: bksv start is ok\n", __func__); + + return 0; + +bksv_start_err: + dev_err(dev, "%s: failed to start bksv\n", __func__); + msleep(100); + return -1; +} + +static int hdcp_second_auth(struct hdmi_device *hdev) +{ + struct device *dev = hdev->dev; + int ret = 0; + + dev_dbg(dev, "%s\n", __func__); + + if (!hdev->hdcp_info.hdcp_start) + goto second_auth_err; + + if (!is_hdmi_streaming(hdev)) + goto second_auth_err; + + ret = hdmi_check_repeater(hdev); + + if (!ret) { + hdev->hdcp_info.auth_status = SECOND_AUTHENTICATION_DONE; + hdmi_start_encryption(hdev); + } else { + switch (ret) { + + case REPEATER_ILLEGAL_DEVICE_ERROR: + hdmi_writeb(hdev, HDMI_HDCP_CTRL2, 0x1); + mdelay(1); + hdmi_writeb(hdev, HDMI_HDCP_CTRL2, 0x0); + + dev_dbg(dev, "%s: repeater : illegal device\n", + __func__); + break; + case REPEATER_TIMEOUT_ERROR: + hdmi_write_mask(hdev, HDMI_HDCP_CTRL1, ~0, + HDMI_HDCP_SET_REPEATER_TIMEOUT); + hdmi_write_mask(hdev, HDMI_HDCP_CTRL1, 0, + HDMI_HDCP_SET_REPEATER_TIMEOUT); + + dev_dbg(dev, "%s: repeater : timeout\n", __func__); + break; + case MAX_CASCADE_EXCEEDED_ERROR: + + dev_dbg(dev, "%s: repeater : exceeded MAX_CASCADE\n", + __func__); + break; + case MAX_DEVS_EXCEEDED_ERROR: + + dev_dbg(dev, "%s: repeater : exceeded MAX_DEVS\n", + __func__); + break; + default: + break; + } + + hdev->hdcp_info.auth_status = NOT_AUTHENTICATED; + + goto second_auth_err; + } + + dev_dbg(dev, "%s: second authentication is OK\n", __func__); + return 0; + +second_auth_err: + dev_dbg(dev, "%s: second authentication is failed\n", __func__); + return -1; +} + +static int hdcp_write_aksv(struct hdmi_device *hdev) +{ + struct device *dev = hdev->dev; + dev_dbg(dev, "%s\n", __func__); + + if (hdev->hdcp_info.auth_status != BKSV_READ_DONE) { + dev_err(dev, "%s: bksv is not ready\n", __func__); + goto aksv_write_err; + } + if (!is_hdmi_streaming(hdev)) + goto aksv_write_err; + + if (hdcp_write_key(hdev, AN_SIZE, HDMI_HDCP_AN_(0), HDCP_AN) < 0) + goto aksv_write_err; + + hdev->hdcp_info.auth_status = AN_WRITE_DONE; + + dev_dbg(dev, "%s: write An is done\n", __func__); + + if (hdcp_write_key(hdev, AKSV_SIZE, HDMI_HDCP_AKSV_(0), HDCP_AKSV) < 0) + goto aksv_write_err; + + msleep(100); + + hdev->hdcp_info.auth_status = AKSV_WRITE_DONE; + + dev_dbg(dev, "%s: write aksv is done\n", __func__); + dev_dbg(dev, "%s: aksv start is OK\n", __func__); + return 0; + +aksv_write_err: + dev_err(dev, "%s: aksv start is failed\n", __func__); + return -1; +} + +static int hdcp_check_ri(struct hdmi_device *hdev) +{ + struct device *dev = hdev->dev; + + dev_dbg(dev, "%s\n", __func__); + + if (hdev->hdcp_info.auth_status < AKSV_WRITE_DONE) { + dev_dbg(dev, "%s: ri check is not ready\n", __func__); + goto check_ri_err; + } + + if (!is_hdmi_streaming(hdev)) + goto check_ri_err; + + if (hdcp_read_ri(hdev) < 0) + goto check_ri_err; + + if (hdev->hdcp_info.is_repeater) + hdev->hdcp_info.auth_status + = SECOND_AUTHENTICATION_RDY; + else { + hdev->hdcp_info.auth_status + = FIRST_AUTHENTICATION_DONE; + hdmi_start_encryption(hdev); + } + + dev_dbg(dev, "%s: ri check is OK\n", __func__); + return 0; + +check_ri_err: + dev_err(dev, "%s: ri check is failed\n", __func__); + return -1; +} + +static void hdcp_work(struct work_struct *work) +{ + struct hdmi_device *hdev = container_of(work, struct hdmi_device, work); + + if (!hdev->hdcp_info.hdcp_start) + return; + + if (!is_hdmi_streaming(hdev)) + return; + + if (hdev->hdcp_info.event & HDCP_EVENT_READ_BKSV_START) { + if (hdcp_bksv(hdev) < 0) + goto work_err; + else + hdev->hdcp_info.event &= ~HDCP_EVENT_READ_BKSV_START; + } + + if (hdev->hdcp_info.event & HDCP_EVENT_SECOND_AUTH_START) { + if (hdcp_second_auth(hdev) < 0) + goto work_err; + else + hdev->hdcp_info.event &= ~HDCP_EVENT_SECOND_AUTH_START; + } + + if (hdev->hdcp_info.event & HDCP_EVENT_WRITE_AKSV_START) { + if (hdcp_write_aksv(hdev) < 0) + goto work_err; + else + hdev->hdcp_info.event &= ~HDCP_EVENT_WRITE_AKSV_START; + } + + if (hdev->hdcp_info.event & HDCP_EVENT_CHECK_RI_START) { + if (hdcp_check_ri(hdev) < 0) + goto work_err; + else + hdev->hdcp_info.event &= ~HDCP_EVENT_CHECK_RI_START; + } + return; +work_err: + if (!hdev->hdcp_info.hdcp_start) + return; + if (!is_hdmi_streaming(hdev)) + return; + + hdcp_reset_auth(hdev); +} + +/* HDCP APIs for hdmi driver */ +irqreturn_t hdcp_irq_handler(struct hdmi_device *hdev) +{ + struct device *dev = hdev->dev; + u32 event = 0; + u8 flag; + event = 0; + + if (!hdev->streaming) { + hdev->hdcp_info.event = HDCP_EVENT_STOP; + hdev->hdcp_info.auth_status = NOT_AUTHENTICATED; + return IRQ_HANDLED; + } + + flag = hdmi_readb(hdev, HDMI_STATUS); + + if (flag & HDMI_WTFORACTIVERX_INT_OCC) { + event |= HDCP_EVENT_READ_BKSV_START; + hdmi_write_mask(hdev, HDMI_STATUS, ~0, HDMI_WTFORACTIVERX_INT_OCC); + hdmi_write(hdev, HDMI_HDCP_I2C_INT, 0x0); + } + + if (flag & HDMI_WRITE_INT_OCC) { + event |= HDCP_EVENT_WRITE_AKSV_START; + hdmi_write_mask(hdev, HDMI_STATUS, ~0, HDMI_WRITE_INT_OCC); + hdmi_write(hdev, HDMI_HDCP_AN_INT, 0x0); + } + + if (flag & HDMI_UPDATE_RI_INT_OCC) { + event |= HDCP_EVENT_CHECK_RI_START; + hdmi_write_mask(hdev, HDMI_STATUS, ~0, HDMI_UPDATE_RI_INT_OCC); + hdmi_write(hdev, HDMI_HDCP_RI_INT, 0x0); + } + + if (flag & HDMI_WATCHDOG_INT_OCC) { + event |= HDCP_EVENT_SECOND_AUTH_START; + hdmi_write_mask(hdev, HDMI_STATUS, ~0, HDMI_WATCHDOG_INT_OCC); + hdmi_write(hdev, HDMI_HDCP_WDT_INT, 0x0); + } + + if (!event) { + dev_dbg(dev, "%s: unknown irq\n", __func__); + return IRQ_HANDLED; + } + + if (is_hdmi_streaming(hdev)) { + hdev->hdcp_info.event |= event; + queue_work(hdev->hdcp_wq, &hdev->work); + } else { + hdev->hdcp_info.event = HDCP_EVENT_STOP; + hdev->hdcp_info.auth_status = NOT_AUTHENTICATED; + } + + return IRQ_HANDLED; +} + +int hdcp_prepare(struct hdmi_device *hdev) +{ + hdev->hdcp_wq = create_workqueue("khdcpd"); + if (hdev->hdcp_wq == NULL) + return -ENOMEM; + + INIT_WORK(&hdev->work, hdcp_work); + + spin_lock_init(&hdev->hdcp_info.reset_lock); + +#if defined(CONFIG_VIDEO_EXYNOS_HDCP) + hdev->hdcp_info.hdcp_enable = 1; +#else + hdev->hdcp_info.hdcp_enable = 0; +#endif + return 0; +} + +int hdcp_start(struct hdmi_device *hdev) +{ + struct device *dev = hdev->dev; + + hdev->hdcp_info.event = HDCP_EVENT_STOP; + hdev->hdcp_info.auth_status = NOT_AUTHENTICATED; + + dev_dbg(dev, "%s\n", __func__); + + hdcp_sw_reset(hdev); + + dev_dbg(dev, "%s: stop encryption\n", __func__); + + hdcp_encryption(hdev, 0); + + msleep(120); + if (hdcp_loadkey(hdev) < 0) + return -1; + + hdmi_write(hdev, HDMI_GCP_CON, HDMI_GCP_CON_NO_TRAN); + hdmi_write(hdev, HDMI_STATUS_EN, HDMI_INT_EN_ALL); + + hdmi_write(hdev, HDMI_HDCP_CTRL1, HDMI_HDCP_CP_DESIRED_EN); + + hdmi_set_int_mask(hdev, HDMI_INTC_EN_HDCP, 1); + + hdev->hdcp_info.hdcp_start = 1; + + return 0; +} + +int hdcp_stop(struct hdmi_device *hdev) +{ + struct device *dev = hdev->dev; + u8 val; + + dev_dbg(dev, "%s\n", __func__); + + hdmi_set_int_mask(hdev, HDMI_INTC_EN_HDCP, 0); + + hdev->hdcp_info.event = HDCP_EVENT_STOP; + hdev->hdcp_info.auth_status = NOT_AUTHENTICATED; + hdev->hdcp_info.hdcp_start = false; + + hdmi_writeb(hdev, HDMI_HDCP_CTRL1, 0x0); + + hdmi_sw_hpd_enable(hdev, 0); + + val = HDMI_UPDATE_RI_INT_EN | HDMI_WRITE_INT_EN | + HDMI_WATCHDOG_INT_EN | HDMI_WTFORACTIVERX_INT_EN; + hdmi_write_mask(hdev, HDMI_STATUS_EN, 0, val); + hdmi_write_mask(hdev, HDMI_STATUS_EN, ~0, val); + + hdmi_write_mask(hdev, HDMI_STATUS, ~0, HDMI_INT_EN_ALL); + + dev_dbg(dev, "%s: stop encryption\n", __func__); + hdcp_encryption(hdev, 0); + + hdmi_writeb(hdev, HDMI_HDCP_CHECK_RESULT, HDMI_HDCP_CLR_ALL_RESULTS); + + return 0; +} |