/* 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;
}