blob: 044a0110c517bf366179b4afdc7715fd41e09576 [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <drm/drm_modeset_helper.h>
#include <drm/drmP.h>
#include <drm/drm_edid.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_hdcp.h>
#include <linux/component.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/jiffies.h>
#include <linux/kthread.h>
#include <linux/workqueue.h>
#include <linux/amlogic/media/vout/vout_notify.h>
#include <linux/amlogic/media/vout/hdmi_tx/hdmi_tx_module.h>
#include <linux/arm-smccc.h>
#include <linux/workqueue.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include "meson_drv.h"
#include "meson_hdmi.h"
#include "meson_hdcp.h"
/* ioctl numbers */
enum {
TEE_HDCP_START,
TEE_HDCP_END,
HDCP_DAEMON_LOAD_END,
HDCP_DAEMON_REPORT,
HDCP_EXE_VER_SET,
HDCP_TX_VER_REPORT,
HDCP_DOWNSTR_VER_REPORT,
HDCP_EXE_VER_REPORT
};
#define TEE_HDCP_IOC_START _IOW('P', TEE_HDCP_START, int)
#define TEE_HDCP_IOC_END _IOW('P', TEE_HDCP_END, int)
#define HDCP_DAEMON_IOC_LOAD_END _IOW('P', HDCP_DAEMON_LOAD_END, int)
#define HDCP_DAEMON_IOC_REPORT _IOR('P', HDCP_DAEMON_REPORT, int)
#define HDCP_EXE_VER_IOC_SET _IOW('P', HDCP_EXE_VER_SET, int)
#define HDCP_TX_VER_IOC_REPORT _IOR('P', HDCP_TX_VER_REPORT, int)
#define HDCP_DOWNSTR_VER_IOC_REPORT _IOR('P', HDCP_DOWNSTR_VER_REPORT, int)
#define HDCP_EXE_VER_IOC_REPORT _IOR('P', HDCP_EXE_VER_REPORT, int)
enum {
HDCP_TX22_DISCONNECT = 0,
HDCP_TX22_START,
HDCP_TX22_STOP
};
enum {
HDCP22_DAEMON_LOADING = 0,
HDCP22_DAEMON_DONE,
HDCP22_DAEMON_TIMEOUT
};
#define HDCP_AUTH_TIMEOUT (40) /*40*200ms = 8s*/
#define HDCP22_LOAD_TIMEOUT (160)
#define TIMER_CHECK (1 * HZ / 2)
#define TIMER_CHK_CNT 40
struct meson_hdmitx_hdcp {
struct miscdevice hdcp_comm_device;
wait_queue_head_t hdcp_comm_queue;
unsigned int hdcp_tx_type;/*bit0:hdcp14 bit 1:hdcp22*/
unsigned int hdcp_downstream_type;/*bit0:hdcp14 bit 1:hdcp22*/
unsigned int hdcp_execute_type;/*0: null hdcp 1: hdcp14 2: hdcp22*/
unsigned int hdcp_debug_type;/*0: null hdcp 1: hdcp14 2: hdcp22*/
unsigned int hdcp_en;
int hdcp_poll_report;
int hdcp_auth_result;
int hdcp_fail_cnt;
int hdcp_report;
int hdcp22_daemon_state;
hdcp_notify hdcp_cb;
struct timer_list daemon_load_timer;
unsigned int key_chk_cnt;
struct delayed_work notify_work;
};
static struct meson_hdmitx_hdcp meson_hdcp;
static unsigned int get_hdcp_downstream_ver(void)
{
unsigned int hdcp_downstream_type = drm_get_rx_hdcp_cap();
DRM_INFO("downstream support hdcp14: %d\n",
hdcp_downstream_type & 0x1);
DRM_INFO("downstream support hdcp22: %d\n",
(hdcp_downstream_type & 0x2) >> 1);
return hdcp_downstream_type;
}
unsigned int meson_hdcp_get_key_version(void)
{
unsigned int hdcp_tx_type = drm_hdmitx_get_hdcp_cap();
DRM_INFO("hdmitx support hdcp14: %d\n",
hdcp_tx_type & 0x1);
DRM_INFO("hdmitx support hdcp22: %d\n",
(hdcp_tx_type & 0x2) >> 1);
return hdcp_tx_type;
}
int meson_hdcp_get_valid_type(int request_type_mask)
{
int type;
unsigned int hdcp_tx_type = meson_hdcp_get_key_version();
DRM_INFO("%s usr_type: %d, key_store: %d\n",
__func__, request_type_mask, hdcp_tx_type);
if (/* !meson_hdcp.hdcp_downstream_type && */
hdcp_tx_type) {
meson_hdcp.hdcp_downstream_type =
get_hdcp_downstream_ver();
if (meson_hdcp.hdcp22_daemon_state != HDCP22_DAEMON_DONE)
hdcp_tx_type &= ~(1 << 1);
}
switch (hdcp_tx_type & 0x3) {
case 0x3:
if ((meson_hdcp.hdcp_downstream_type & 0x2) &&
(request_type_mask & 0x2))
type = HDCP_MODE22;
else if ((meson_hdcp.hdcp_downstream_type & 0x1) &&
(request_type_mask & 0x1))
type = HDCP_MODE14;
else
type = HDCP_NULL;
break;
case 0x2:
if ((meson_hdcp.hdcp_downstream_type & 0x2) &&
(request_type_mask & 0x2))
type = HDCP_MODE22;
else
type = HDCP_NULL;
break;
case 0x1:
if ((meson_hdcp.hdcp_downstream_type & 0x1) &&
(request_type_mask & 0x1))
type = HDCP_MODE14;
else
type = HDCP_NULL;
break;
default:
type = HDCP_NULL;
DRM_INFO("[%s]: TX no hdcp key\n", __func__);
break;
}
return type;
}
static int am_get_hdcp_exe_type(void)
{
return meson_hdcp_get_valid_type(meson_hdcp.hdcp_debug_type);
}
void meson_hdcp_disable(void)
{
if (!meson_hdcp.hdcp_en)
return;
DRM_INFO("[%s]: %d\n", __func__, meson_hdcp.hdcp_execute_type);
/* TODO: whether to keep exe type */
/* if (meson_hdcp.hdcp_execute_type == HDCP_MODE22) { */
meson_hdcp.hdcp_report = HDCP_TX22_DISCONNECT;
/* wait for tx22 to enter unconnected state */
msleep(200);
meson_hdcp.hdcp_report = HDCP_TX22_STOP;
/* wakeup hdcp_tx22 to stop hdcp22 */
wake_up(&meson_hdcp.hdcp_comm_queue);
/* wait for hdcp_tx22 stop hdcp22 done */
msleep(200);
drm_hdmitx_hdcp_disable(HDCP_MODE22);
/* } else if (meson_hdcp.hdcp_execute_type == HDCP_MODE14) { */
drm_hdmitx_hdcp_disable(HDCP_MODE14);
/* } */
meson_hdcp.hdcp_execute_type = HDCP_NULL;
meson_hdcp.hdcp_auth_result = HDCP_AUTH_UNKNOWN;
meson_hdcp.hdcp_en = 0;
meson_hdcp.hdcp_fail_cnt = 0;
}
void meson_hdcp_disconnect(void)
{
/* if (meson_hdcp.hdcp_execute_type == HDCP_MODE22) */
/* drm_hdmitx_hdcp_disable(HDCP_MODE22); */
/* else if (meson_hdcp.hdcp_execute_type == HDCP_MODE14) */
/* drm_hdmitx_hdcp_disable(HDCP_MODE14); */
meson_hdcp.hdcp_report = HDCP_TX22_DISCONNECT;
meson_hdcp.hdcp_downstream_type = 0;
/* TODO: for suspend/resume, need to stop/start hdcp22
* need to keep exe type
*/
meson_hdcp.hdcp_execute_type = HDCP_NULL;
meson_hdcp.hdcp_auth_result = HDCP_AUTH_UNKNOWN;
meson_hdcp.hdcp_en = 0;
meson_hdcp.hdcp_fail_cnt = 0;
DRM_INFO("[%s]: HDCP_TX22_DISCONNECT\n", __func__);
wake_up(&meson_hdcp.hdcp_comm_queue);
}
static void meson_hdmitx_hdcp_cb(void *data, int auth)
{
struct meson_hdmitx_hdcp *hdcp_data = (struct meson_hdmitx_hdcp *)data;
int hdcp_auth_result = HDCP_AUTH_UNKNOWN;
if (hdcp_data->hdcp_en &&
hdcp_data->hdcp_auth_result == HDCP_AUTH_UNKNOWN) {
if (auth == 1) {
hdcp_auth_result = HDCP_AUTH_OK;
} else if (auth == 0) {
hdcp_data->hdcp_fail_cnt++;
if (hdcp_data->hdcp_fail_cnt > HDCP_AUTH_TIMEOUT)
hdcp_auth_result = HDCP_AUTH_FAIL;
}
DRM_DEBUG("HDCP cb %d vs %d\n", hdcp_data->hdcp_auth_result, auth);
if (hdcp_auth_result != hdcp_data->hdcp_auth_result) {
hdcp_data->hdcp_auth_result = hdcp_auth_result;
if (meson_hdcp.hdcp_cb)
meson_hdcp.hdcp_cb(hdcp_data->hdcp_execute_type,
hdcp_data->hdcp_auth_result);
}
}
}
void meson_hdcp_reg_result_notify(hdcp_notify cb)
{
if (meson_hdcp.hdcp_cb)
DRM_ERROR("Register hdcp notify again!?\n");
meson_hdcp.hdcp_cb = cb;
}
void meson_hdcp_enable(int hdcp_type)
{
if (hdcp_type == HDCP_NULL)
return;
/* hdcp enabled, but may not start auth as key not ready */
meson_hdcp.hdcp_en = 1;
meson_hdcp.hdcp_fail_cnt = 0;
meson_hdcp.hdcp_auth_result = HDCP_AUTH_UNKNOWN;
meson_hdcp.hdcp_execute_type = hdcp_type;
if (meson_hdcp.hdcp_execute_type == HDCP_MODE22) {
if (meson_hdcp.hdcp22_daemon_state != HDCP22_DAEMON_DONE) {
DRM_INFO("[%s]: hdcp_tx22 not ready, delay hdcp auth\n", __func__);
return;
}
drm_hdmitx_hdcp_enable(2);
meson_hdcp.hdcp_report = HDCP_TX22_START;
msleep(50);
wake_up(&meson_hdcp.hdcp_comm_queue);
} else if (meson_hdcp.hdcp_execute_type == HDCP_MODE14) {
drm_hdmitx_hdcp_enable(1);
}
DRM_INFO("[%s]: report=%d, use_type=%u, execute=%u\n",
__func__, meson_hdcp.hdcp_report,
meson_hdcp.hdcp_debug_type, meson_hdcp.hdcp_execute_type);
}
static long hdcp_comm_ioctl(struct file *file,
unsigned int cmd,
unsigned long arg)
{
int rtn_val;
unsigned int out_size;
int hdcp_type = HDCP_NULL;
switch (cmd) {
case TEE_HDCP_IOC_START:
/* notify by TEE, hdcp key ready, echo 2 > hdcp_mode */
rtn_val = 0;
meson_hdcp.hdcp_tx_type = meson_hdcp_get_key_version();
if (meson_hdcp.hdcp_tx_type & 0x2) {
/* when bootup, if hdcp22 init after hdcp14 auth,
* hdcp path will switch to hdcp22. need to delay
* hdcp auth to covery this issue.
*/
drm_hdmitx_hdcp22_init();
}
DRM_INFO("hdcp key load ready\n");
break;
case TEE_HDCP_IOC_END:
rtn_val = 0;
break;
case HDCP_DAEMON_IOC_LOAD_END:
/* hdcp_tx22 load ready (after TEE key ready) */
DRM_ERROR("IOC_LOAD_END %d, %d\n",
meson_hdcp.hdcp_report, meson_hdcp.hdcp_poll_report);
if (meson_hdcp.hdcp22_daemon_state == HDCP22_DAEMON_TIMEOUT)
DRM_ERROR("hdcp22 key load late than TIMEOUT.\n");
meson_hdcp.hdcp22_daemon_state = HDCP22_DAEMON_DONE;
meson_hdcp.hdcp_poll_report = meson_hdcp.hdcp_report;
rtn_val = 0;
break;
case HDCP_DAEMON_IOC_REPORT:
rtn_val = copy_to_user((void __user *)arg,
(void *)&meson_hdcp.hdcp_report,
sizeof(meson_hdcp.hdcp_report));
if (rtn_val != 0) {
out_size = sizeof(meson_hdcp.hdcp_report);
DRM_INFO("out_size: %u, leftsize: %u\n",
out_size, rtn_val);
rtn_val = -1;
}
break;
case HDCP_EXE_VER_IOC_SET:
if (arg > 2) {
rtn_val = -1;
} else {
meson_hdcp.hdcp_debug_type = arg;
rtn_val = 0;
if (hdmitx_hpd_hw_op(HPD_READ_HPD_GPIO)) {
meson_hdcp_disable();
hdcp_type = am_get_hdcp_exe_type();
meson_hdcp_enable(hdcp_type);
}
}
break;
case HDCP_TX_VER_IOC_REPORT:
rtn_val = copy_to_user((void __user *)arg,
(void *)&meson_hdcp.hdcp_tx_type,
sizeof(meson_hdcp.hdcp_tx_type));
if (rtn_val != 0) {
out_size = sizeof(meson_hdcp.hdcp_tx_type);
DRM_INFO("out_size: %u, leftsize: %u\n",
out_size, rtn_val);
rtn_val = -1;
}
break;
case HDCP_DOWNSTR_VER_IOC_REPORT:
rtn_val = copy_to_user((void __user *)arg,
(void *)&meson_hdcp.hdcp_downstream_type,
sizeof(meson_hdcp.hdcp_downstream_type));
if (rtn_val != 0) {
out_size = sizeof(meson_hdcp.hdcp_downstream_type);
DRM_INFO("out_size: %u, leftsize: %u\n",
out_size, rtn_val);
rtn_val = -1;
}
break;
case HDCP_EXE_VER_IOC_REPORT:
rtn_val = copy_to_user((void __user *)arg,
(void *)&meson_hdcp.hdcp_execute_type,
sizeof(meson_hdcp.hdcp_execute_type));
if (rtn_val != 0) {
out_size = sizeof(meson_hdcp.hdcp_execute_type);
DRM_INFO("out_size: %u, leftsize: %u\n",
out_size, rtn_val);
rtn_val = -1;
}
break;
default:
rtn_val = -EPERM;
}
return rtn_val;
}
static unsigned int hdcp_comm_poll(struct file *file, poll_table *wait)
{
unsigned int mask = 0;
DRM_INFO("hdcp_poll %d, %d\n", meson_hdcp.hdcp_report, meson_hdcp.hdcp_poll_report);
poll_wait(file, &meson_hdcp.hdcp_comm_queue, wait);
if (meson_hdcp.hdcp_report != meson_hdcp.hdcp_poll_report) {
mask = POLLIN | POLLRDNORM;
meson_hdcp.hdcp_poll_report = meson_hdcp.hdcp_report;
}
return mask;
}
static const struct file_operations hdcp_comm_file_operations = {
.owner = THIS_MODULE,
.unlocked_ioctl = hdcp_comm_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = hdcp_comm_ioctl,
#endif
.poll = hdcp_comm_poll,
};
/***** debug interface begin *****/
static void am_hdmitx_set_hdcp_mode(unsigned int user_type)
{
meson_hdcp.hdcp_debug_type = user_type;
DRM_INFO("set_hdcp_mode: %d manually\n", user_type);
}
static void am_hdmitx_set_hdmi_mode(void)
{
enum vmode_e vmode = get_current_vmode();
if (vmode == VMODE_HDMI) {
DRM_INFO("set_hdmi_mode manually\n");
} else {
DRM_INFO("set_hdmi_mode manually fail! vmode:%d\n", vmode);
return;
}
set_vout_mode_pre_process(vmode);
set_vout_vmode(vmode);
set_vout_mode_post_process(vmode);
}
/* set hdmi+hdcp mode */
static void am_hdmitx_set_out_mode(void)
{
enum vmode_e vmode = get_current_vmode();
struct hdmitx_dev *hdmitx_dev = get_hdmitx_device();
int last_hdcp_mode = HDCP_NULL;
if (vmode == VMODE_HDMI) {
DRM_INFO("set_out_mode\n");
} else {
DRM_INFO("set_out_mode fail! vmode:%d\n", vmode);
return;
}
if (hdmitx_dev->hdcp_ctl_lvl > 0) {
drm_hdmitx_avmute(1);
/* may reduce */
msleep(100);
last_hdcp_mode = meson_hdcp.hdcp_execute_type;
meson_hdcp_disable();
}
set_vout_mode_pre_process(vmode);
set_vout_vmode(vmode);
set_vout_mode_post_process(vmode);
/* msleep(1000); */
if (hdmitx_dev->hdcp_ctl_lvl > 0) {
drm_hdmitx_avmute(0);
meson_hdcp_enable(last_hdcp_mode);
}
}
static void am_hdmitx_hdcp_disable(void)
{
struct hdmitx_dev *hdmitx_dev = get_hdmitx_device();
if (hdmitx_dev->hdcp_ctl_lvl >= 1)
meson_hdcp_disable();
DRM_INFO("hdcp disable manually\n");
}
static void am_hdmitx_hdcp_enable(void)
{
struct hdmitx_dev *hdmitx_dev = get_hdmitx_device();
int hdcp_type = HDCP_NULL;
if (hdmitx_dev->hdcp_ctl_lvl >= 1) {
hdcp_type = am_get_hdcp_exe_type();
meson_hdcp_enable(hdcp_type);
}
DRM_INFO("hdcp enable manually\n");
}
static void am_hdmitx_hdcp_disconnect(void)
{
struct hdmitx_dev *hdmitx_dev = get_hdmitx_device();
if (hdmitx_dev->hdcp_ctl_lvl >= 1)
meson_hdcp_disconnect();
DRM_INFO("hdcp disconnect manually\n");
}
/***** debug interface end *****/
static void meson_hdcp_key_notify(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
struct meson_hdmitx_hdcp *meson_hdcp =
container_of(dwork, struct meson_hdmitx_hdcp,
notify_work);
meson_hdcp->hdcp_cb(HDCP_KEY_UPDATE, HDCP_AUTH_UNKNOWN);
}
void hdcp_key_check(struct timer_list *timer)
{
struct meson_hdmitx_hdcp *meson_hdcp =
container_of(timer, struct meson_hdmitx_hdcp,
daemon_load_timer);
/*send notify when key load finished or timeout.*/
if (meson_hdcp->hdcp22_daemon_state == HDCP22_DAEMON_DONE) {
DRM_INFO("hdcp_tx22 load ready, stop timer\n");
/*meson_hdcp->key_chk_cnt = 0;*/
schedule_delayed_work(&meson_hdcp->notify_work, HZ / 100);
} else if (meson_hdcp->key_chk_cnt++ < TIMER_CHK_CNT) {
meson_hdcp->daemon_load_timer.expires = jiffies + TIMER_CHECK;
add_timer(&meson_hdcp->daemon_load_timer);
} else {
DRM_INFO("hdcp_tx22 load timeout\n");
meson_hdcp->hdcp22_daemon_state = HDCP22_DAEMON_TIMEOUT;
/*meson_hdcp->key_chk_cnt = 0;*/
schedule_delayed_work(&meson_hdcp->notify_work, 0);
}
}
void meson_hdcp_init(void)
{
int ret;
struct drm_hdmitx_hdcp_cb hdcp_cb;
struct hdmitx_dev *hdmitx_dev;
meson_hdcp.hdcp_debug_type = 0x3;
meson_hdcp.hdcp_report = HDCP_TX22_DISCONNECT;
meson_hdcp.hdcp_en = 0;
meson_hdcp.hdcp22_daemon_state = HDCP22_DAEMON_LOADING;
init_waitqueue_head(&meson_hdcp.hdcp_comm_queue);
meson_hdcp.hdcp_comm_device.minor = MISC_DYNAMIC_MINOR;
meson_hdcp.hdcp_comm_device.name = "tee_comm_hdcp";
meson_hdcp.hdcp_comm_device.fops = &hdcp_comm_file_operations;
ret = misc_register(&meson_hdcp.hdcp_comm_device);
if (ret < 0)
DRM_ERROR("%s [ERROR] misc_register fail\n", __func__);
hdcp_cb.callback = meson_hdmitx_hdcp_cb;
hdcp_cb.data = &meson_hdcp;
drm_hdmitx_register_hdcp_cb(&hdcp_cb);
hdmitx_dev = get_hdmitx_device();
hdmitx_dev->hwop.am_hdmitx_set_hdcp_mode = am_hdmitx_set_hdcp_mode;
hdmitx_dev->hwop.am_hdmitx_set_hdmi_mode = am_hdmitx_set_hdmi_mode;
hdmitx_dev->hwop.am_hdmitx_set_out_mode = am_hdmitx_set_out_mode;
hdmitx_dev->hwop.am_hdmitx_hdcp_disable = am_hdmitx_hdcp_disable;
hdmitx_dev->hwop.am_hdmitx_hdcp_enable = am_hdmitx_hdcp_enable;
hdmitx_dev->hwop.am_hdmitx_hdcp_disconnect = am_hdmitx_hdcp_disconnect;
INIT_DELAYED_WORK(&meson_hdcp.notify_work, meson_hdcp_key_notify);
meson_hdcp.key_chk_cnt = 0;
timer_setup(&meson_hdcp.daemon_load_timer, hdcp_key_check, 0);
meson_hdcp.daemon_load_timer.expires = jiffies + TIMER_CHECK;
add_timer(&meson_hdcp.daemon_load_timer);
}
void meson_hdcp_exit(void)
{
misc_deregister(&meson_hdcp.hdcp_comm_device);
del_timer_sync(&meson_hdcp.daemon_load_timer);
cancel_delayed_work(&meson_hdcp.notify_work);
}