aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/video/omap2/hdcp/hdcp_top.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/omap2/hdcp/hdcp_top.c')
-rw-r--r--drivers/video/omap2/hdcp/hdcp_top.c1050
1 files changed, 1050 insertions, 0 deletions
diff --git a/drivers/video/omap2/hdcp/hdcp_top.c b/drivers/video/omap2/hdcp/hdcp_top.c
new file mode 100644
index 0000000..1bb32f4
--- /dev/null
+++ b/drivers/video/omap2/hdcp/hdcp_top.c
@@ -0,0 +1,1050 @@
+/*
+ * hdcp_top.c
+ *
+ * HDCP interface DSS driver setting for TI's OMAP4 family of processor.
+ * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/
+ * Authors: Fabrice Olivero
+ * Fabrice Olivero <f-olivero@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/module.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/completion.h>
+#include <linux/miscdevice.h>
+#include <linux/firmware.h>
+#include "../../hdmi_ti_4xxx_ip.h"
+#include "../dss/dss.h"
+#include "hdcp.h"
+
+struct hdcp hdcp;
+struct hdcp_sha_in sha_input;
+
+/* State machine / workqueue */
+static void hdcp_wq_disable(void);
+static void hdcp_wq_start_authentication(void);
+static void hdcp_wq_check_r0(void);
+static void hdcp_wq_step2_authentication(void);
+static void hdcp_wq_authentication_failure(void);
+static void hdcp_work_queue(struct work_struct *work);
+static struct delayed_work *hdcp_submit_work(int event, int delay);
+static void hdcp_cancel_work(struct delayed_work **work);
+
+/* Callbacks */
+static void hdcp_start_frame_cb(void);
+static void hdcp_irq_cb(int hpd_low);
+
+/* Control */
+static long hdcp_enable_ctl(void __user *argp);
+static long hdcp_disable_ctl(void);
+static long hdcp_query_status_ctl(void __user *argp);
+static long hdcp_encrypt_key_ctl(void __user *argp);
+
+/* Driver */
+static int __init hdcp_init(void);
+static void __exit hdcp_exit(void);
+
+struct completion hdcp_comp;
+static DECLARE_WAIT_QUEUE_HEAD(hdcp_up_wait_queue);
+static DECLARE_WAIT_QUEUE_HEAD(hdcp_down_wait_queue);
+
+#define DSS_POWER
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_request_dss
+ *-----------------------------------------------------------------------------
+ */
+static void hdcp_request_dss(void)
+{
+#ifdef DSS_POWER
+ hdcp.dss_state = dss_runtime_get();
+#endif
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_user_space_task
+ *-----------------------------------------------------------------------------
+ */
+int hdcp_user_space_task(int flags)
+{
+ int ret;
+
+ DBG("Wait for user space task %x\n", flags);
+ hdcp.hdcp_up_event = flags & 0xFF;
+ hdcp.hdcp_down_event = flags & 0xFF;
+ wake_up_interruptible(&hdcp_up_wait_queue);
+ wait_event_interruptible(hdcp_down_wait_queue,
+ (hdcp.hdcp_down_event & 0xFF) == 0);
+ ret = (hdcp.hdcp_down_event & 0xFF00) >> 8;
+
+ DBG("User space task done %x\n", hdcp.hdcp_down_event);
+ hdcp.hdcp_down_event = 0;
+
+ return ret;
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_release_dss
+ *-----------------------------------------------------------------------------
+ */
+static void hdcp_release_dss(void)
+{
+#ifdef DSS_POWER
+ if (hdcp.dss_state == 0)
+ dss_runtime_put();
+#endif
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_wq_disable
+ *-----------------------------------------------------------------------------
+ */
+static void hdcp_wq_disable(void)
+{
+ printk(KERN_INFO "HDCP: disabled\n");
+
+ hdcp_cancel_work(&hdcp.pending_wq_event);
+ hdcp_lib_disable();
+ hdcp.pending_disable = 0;
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_wq_start_authentication
+ *-----------------------------------------------------------------------------
+ */
+static void hdcp_wq_start_authentication(void)
+{
+ int status = HDCP_OK;
+
+ hdcp.hdcp_state = HDCP_AUTHENTICATION_START;
+
+ printk(KERN_INFO "HDCP: authentication start\n");
+
+ /* Step 1 part 1 (until R0 calc delay) */
+ status = hdcp_lib_step1_start();
+
+ if (status == -HDCP_AKSV_ERROR) {
+ hdcp_wq_authentication_failure();
+ } else if (status == -HDCP_CANCELLED_AUTH) {
+ DBG("Authentication step 1 cancelled.");
+ return;
+ } else if (status != HDCP_OK) {
+ hdcp_wq_authentication_failure();
+ } else {
+ hdcp.hdcp_state = HDCP_WAIT_R0_DELAY;
+ hdcp.auth_state = HDCP_STATE_AUTH_1ST_STEP;
+ hdcp.pending_wq_event = hdcp_submit_work(HDCP_R0_EXP_EVENT,
+ HDCP_R0_DELAY);
+ }
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_wq_check_r0
+ *-----------------------------------------------------------------------------
+ */
+static void hdcp_wq_check_r0(void)
+{
+ int status = hdcp_lib_step1_r0_check();
+
+ if (status == -HDCP_CANCELLED_AUTH) {
+ DBG("Authentication step 1/R0 cancelled.");
+ return;
+ } else if (status < 0)
+ hdcp_wq_authentication_failure();
+ else {
+ if (hdcp_lib_check_repeater_bit_in_tx()) {
+ /* Repeater */
+ printk(KERN_INFO "HDCP: authentication step 1 "
+ "successful - Repeater\n");
+
+ hdcp.hdcp_state = HDCP_WAIT_KSV_LIST;
+ hdcp.auth_state = HDCP_STATE_AUTH_2ND_STEP;
+
+ hdcp.pending_wq_event =
+ hdcp_submit_work(HDCP_KSV_TIMEOUT_EVENT,
+ HDCP_KSV_TIMEOUT_DELAY);
+ } else {
+ /* Receiver */
+ printk(KERN_INFO "HDCP: authentication step 1 "
+ "successful - Receiver\n");
+
+ hdcp.hdcp_state = HDCP_LINK_INTEGRITY_CHECK;
+ hdcp.auth_state = HDCP_STATE_AUTH_3RD_STEP;
+
+ /* Restore retry counter */
+ if (hdcp.en_ctrl->nb_retry == 0)
+ hdcp.retry_cnt = HDCP_INFINITE_REAUTH;
+ else
+ hdcp.retry_cnt = hdcp.en_ctrl->nb_retry;
+ }
+ }
+}
+
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_wq_step2_authentication
+ *-----------------------------------------------------------------------------
+ */
+static void hdcp_wq_step2_authentication(void)
+{
+ int status = HDCP_OK;
+
+ /* KSV list timeout is running and should be canceled */
+ hdcp_cancel_work(&hdcp.pending_wq_event);
+
+ status = hdcp_lib_step2();
+
+ if (status == -HDCP_CANCELLED_AUTH) {
+ DBG("Authentication step 2 cancelled.");
+ return;
+ } else if (status < 0)
+ hdcp_wq_authentication_failure();
+ else {
+ printk(KERN_INFO "HDCP: (Repeater) authentication step 2 "
+ "successful\n");
+
+ hdcp.hdcp_state = HDCP_LINK_INTEGRITY_CHECK;
+ hdcp.auth_state = HDCP_STATE_AUTH_3RD_STEP;
+
+ /* Restore retry counter */
+ if (hdcp.en_ctrl->nb_retry == 0)
+ hdcp.retry_cnt = HDCP_INFINITE_REAUTH;
+ else
+ hdcp.retry_cnt = hdcp.en_ctrl->nb_retry;
+ }
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_wq_authentication_failure
+ *-----------------------------------------------------------------------------
+ */
+static void hdcp_wq_authentication_failure(void)
+{
+ if (hdcp.hdmi_state == HDMI_STOPPED) {
+ hdcp.auth_state = HDCP_STATE_AUTH_FAILURE;
+ return;
+ }
+
+ hdcp_lib_auto_ri_check(false);
+ hdcp_lib_auto_bcaps_rdy_check(false);
+ hdcp_lib_set_av_mute(AV_MUTE_SET);
+ hdcp_lib_set_encryption(HDCP_ENC_OFF);
+
+ hdcp_cancel_work(&hdcp.pending_wq_event);
+
+ hdcp_lib_disable();
+ hdcp.pending_disable = 0;
+
+ if (hdcp.retry_cnt && (hdcp.hdmi_state != HDMI_STOPPED)) {
+ if (hdcp.retry_cnt < HDCP_INFINITE_REAUTH) {
+ hdcp.retry_cnt--;
+ printk(KERN_INFO "HDCP: authentication failed - "
+ "retrying, attempts=%d\n",
+ hdcp.retry_cnt);
+ } else
+ printk(KERN_INFO "HDCP: authentication failed - "
+ "retrying\n");
+
+ hdcp.hdcp_state = HDCP_AUTHENTICATION_START;
+ hdcp.auth_state = HDCP_STATE_AUTH_FAIL_RESTARTING;
+
+ hdcp.pending_wq_event = hdcp_submit_work(HDCP_AUTH_REATT_EVENT,
+ HDCP_REAUTH_DELAY);
+ } else {
+ printk(KERN_INFO "HDCP: authentication failed - "
+ "HDCP disabled\n");
+ hdcp.hdcp_state = HDCP_ENABLE_PENDING;
+ hdcp.auth_state = HDCP_STATE_AUTH_FAILURE;
+ }
+
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_work_queue
+ *-----------------------------------------------------------------------------
+ */
+static void hdcp_work_queue(struct work_struct *work)
+{
+ struct hdcp_delayed_work *hdcp_w =
+ container_of(work, struct hdcp_delayed_work, work.work);
+ int event = hdcp_w->event;
+
+ mutex_lock(&hdcp.lock);
+
+ DBG("hdcp_work_queue() - START - %u hdmi=%d hdcp=%d auth=%d evt= %x %d"
+ " hdcp_ctrl=%02x",
+ jiffies_to_msecs(jiffies),
+ hdcp.hdmi_state,
+ hdcp.hdcp_state,
+ hdcp.auth_state,
+ (event & 0xFF00) >> 8,
+ event & 0xFF,
+ RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__HDCP_CTRL));
+
+ /* Clear pending_wq_event
+ * In case a delayed work is scheduled from the state machine
+ * "pending_wq_event" is used to memorize pointer on the event to be
+ * able to cancel any pending work in case HDCP is disabled
+ */
+ if (event & HDCP_WORKQUEUE_SRC)
+ hdcp.pending_wq_event = 0;
+
+ /* First handle HDMI state */
+ if (event == HDCP_START_FRAME_EVENT) {
+ hdcp.pending_start = 0;
+ hdcp.hdmi_state = HDMI_STARTED;
+ }
+ /**********************/
+ /* HDCP state machine */
+ /**********************/
+ switch (hdcp.hdcp_state) {
+
+ /* State */
+ /*********/
+ case HDCP_DISABLED:
+ /* HDCP enable control or re-authentication event */
+ if (event == HDCP_ENABLE_CTL) {
+ if (hdcp.en_ctrl->nb_retry == 0)
+ hdcp.retry_cnt = HDCP_INFINITE_REAUTH;
+ else
+ hdcp.retry_cnt = hdcp.en_ctrl->nb_retry;
+
+ if (hdcp.hdmi_state == HDMI_STARTED)
+ hdcp_wq_start_authentication();
+ else
+ hdcp.hdcp_state = HDCP_ENABLE_PENDING;
+ }
+
+ break;
+
+ /* State */
+ /*********/
+ case HDCP_ENABLE_PENDING:
+ /* HDMI start frame event */
+ if (event == HDCP_START_FRAME_EVENT)
+ hdcp_wq_start_authentication();
+
+ break;
+
+ /* State */
+ /*********/
+ case HDCP_AUTHENTICATION_START:
+ /* Re-authentication */
+ if (event == HDCP_AUTH_REATT_EVENT)
+ hdcp_wq_start_authentication();
+
+ break;
+
+ /* State */
+ /*********/
+ case HDCP_WAIT_R0_DELAY:
+ /* R0 timer elapsed */
+ if (event == HDCP_R0_EXP_EVENT)
+ hdcp_wq_check_r0();
+
+ break;
+
+ /* State */
+ /*********/
+ case HDCP_WAIT_KSV_LIST:
+ /* Ri failure */
+ if (event == HDCP_RI_FAIL_EVENT) {
+ printk(KERN_INFO "HDCP: Ri check failure\n");
+
+ hdcp_wq_authentication_failure();
+ }
+ /* KSV list ready event */
+ else if (event == HDCP_KSV_LIST_RDY_EVENT)
+ hdcp_wq_step2_authentication();
+ /* Timeout */
+ else if (event == HDCP_KSV_TIMEOUT_EVENT) {
+ printk(KERN_INFO "HDCP: BCAPS polling timeout\n");
+ hdcp_wq_authentication_failure();
+ }
+ break;
+
+ /* State */
+ /*********/
+ case HDCP_LINK_INTEGRITY_CHECK:
+ /* Ri failure */
+ if (event == HDCP_RI_FAIL_EVENT) {
+ printk(KERN_INFO "HDCP: Ri check failure\n");
+ hdcp_wq_authentication_failure();
+ }
+ break;
+
+ default:
+ printk(KERN_WARNING "HDCP: error - unknow HDCP state\n");
+ break;
+ }
+
+ kfree(hdcp_w);
+ hdcp_w = 0;
+ if (event == HDCP_START_FRAME_EVENT)
+ hdcp.pending_start = 0;
+ if (event == HDCP_KSV_LIST_RDY_EVENT ||
+ event == HDCP_R0_EXP_EVENT) {
+ hdcp.pending_wq_event = 0;
+ }
+
+ DBG("hdcp_work_queue() - END - %u hdmi=%d hdcp=%d auth=%d evt=%x %d ",
+ jiffies_to_msecs(jiffies),
+ hdcp.hdmi_state,
+ hdcp.hdcp_state,
+ hdcp.auth_state,
+ (event & 0xFF00) >> 8,
+ event & 0xFF);
+
+ mutex_unlock(&hdcp.lock);
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_submit_work
+ *-----------------------------------------------------------------------------
+ */
+static struct delayed_work *hdcp_submit_work(int event, int delay)
+{
+ struct hdcp_delayed_work *work;
+
+ work = kmalloc(sizeof(struct hdcp_delayed_work), GFP_ATOMIC);
+
+ if (work) {
+ INIT_DELAYED_WORK(&work->work, hdcp_work_queue);
+ work->event = event;
+ queue_delayed_work(hdcp.workqueue,
+ &work->work,
+ msecs_to_jiffies(delay));
+ } else {
+ printk(KERN_WARNING "HDCP: Cannot allocate memory to "
+ "create work\n");
+ return 0;
+ }
+
+ return &work->work;
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_cancel_work
+ *-----------------------------------------------------------------------------
+ */
+static void hdcp_cancel_work(struct delayed_work **work)
+{
+ int ret = 0;
+
+ if (*work) {
+ ret = cancel_delayed_work(*work);
+ if (ret != 1) {
+ ret = cancel_work_sync(&((*work)->work));
+ printk(KERN_INFO "Canceling work failed - "
+ "cancel_work_sync done %d\n", ret);
+ }
+ kfree(*work);
+ *work = 0;
+ }
+}
+
+
+/******************************************************************************
+ * HDCP callbacks
+ *****************************************************************************/
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_3des_cb
+ *-----------------------------------------------------------------------------
+ */
+static bool hdcp_3des_cb(void)
+{
+ DBG("hdcp_3des_cb() %u", jiffies_to_msecs(jiffies));
+
+ if (!hdcp.hdcp_keys_loaded) {
+ printk(KERN_ERR "%s: hdcp_keys not loaded = %d",
+ __func__, hdcp.hdcp_keys_loaded);
+ return false;
+ }
+
+ /* Load 3DES key */
+ if (hdcp_3des_load_key(hdcp.en_ctrl->key) != HDCP_OK) {
+ printk(KERN_ERR "Error Loading HDCP keys\n");
+ return false;
+ }
+ return true;
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_start_frame_cb
+ *-----------------------------------------------------------------------------
+ */
+static void hdcp_start_frame_cb(void)
+{
+ DBG("hdcp_start_frame_cb() %u", jiffies_to_msecs(jiffies));
+
+ if (!hdcp.hdcp_keys_loaded) {
+ DBG("%s: hdcp_keys not loaded = %d",
+ __func__, hdcp.hdcp_keys_loaded);
+ return;
+ }
+
+ /* Cancel any pending work */
+ if (hdcp.pending_start)
+ hdcp_cancel_work(&hdcp.pending_start);
+ if (hdcp.pending_wq_event)
+ hdcp_cancel_work(&hdcp.pending_wq_event);
+
+ hdcp.hpd_low = 0;
+ hdcp.pending_disable = 0;
+ hdcp.retry_cnt = hdcp.en_ctrl->nb_retry;
+ hdcp.pending_start = hdcp_submit_work(HDCP_START_FRAME_EVENT,
+ HDCP_ENABLE_DELAY);
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_irq_cb
+ *-----------------------------------------------------------------------------
+ */
+static void hdcp_irq_cb(int status)
+{
+ DBG("hdcp_irq_cb() status=%x", status);
+
+ if (!hdcp.hdcp_keys_loaded) {
+ DBG("%s: hdcp_keys not loaded = %d",
+ __func__, hdcp.hdcp_keys_loaded);
+ return;
+ }
+
+ /* Disable auto Ri/BCAPS immediately */
+ if (((status & HDMI_RI_ERR) ||
+ (status & HDMI_BCAP) ||
+ (status & HDMI_HPD_LOW)) &&
+ (hdcp.hdcp_state != HDCP_ENABLE_PENDING)) {
+ hdcp_lib_auto_ri_check(false);
+ hdcp_lib_auto_bcaps_rdy_check(false);
+ }
+
+ /* Work queue execution not required if HDCP is disabled */
+ /* TODO: ignore interrupts if they are masked (cannnot access UMASK
+ * here so should use global variable
+ */
+ if ((hdcp.hdcp_state != HDCP_DISABLED) &&
+ (hdcp.hdcp_state != HDCP_ENABLE_PENDING)) {
+ if (status & HDMI_HPD_LOW) {
+ hdcp_lib_set_encryption(HDCP_ENC_OFF);
+ hdcp_ddc_abort();
+ }
+
+ if (status & HDMI_RI_ERR) {
+ hdcp_lib_set_av_mute(AV_MUTE_SET);
+ hdcp_lib_set_encryption(HDCP_ENC_OFF);
+ hdcp_submit_work(HDCP_RI_FAIL_EVENT, 0);
+ }
+ /* RI error takes precedence over BCAP */
+ else if (status & HDMI_BCAP)
+ hdcp_submit_work(HDCP_KSV_LIST_RDY_EVENT, 0);
+ }
+
+ if (status & HDMI_HPD_LOW) {
+ hdcp.pending_disable = 1; /* Used to exit on-going HDCP
+ * work */
+ hdcp.hpd_low = 0; /* Used to cancel HDCP works */
+ hdcp_lib_disable();
+ /* In case of HDCP_STOP_FRAME_EVENT, HDCP stop
+ * frame callback is blocked and waiting for
+ * HDCP driver to finish accessing the HW
+ * before returning
+ * Reason is to avoid HDMI driver to shutdown
+ * DSS/HDMI power before HDCP work is finished
+ */
+ hdcp.hdmi_state = HDMI_STOPPED;
+ hdcp.hdcp_state = HDCP_ENABLE_PENDING;
+ hdcp.auth_state = HDCP_STATE_DISABLED;
+ }
+}
+
+/******************************************************************************
+ * HDCP control from ioctl
+ *****************************************************************************/
+
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_enable_ctl
+ *-----------------------------------------------------------------------------
+ */
+static long hdcp_enable_ctl(void __user *argp)
+{
+ DBG("hdcp_ioctl() - ENABLE %u", jiffies_to_msecs(jiffies));
+
+ if (hdcp.en_ctrl == 0) {
+ hdcp.en_ctrl =
+ kmalloc(sizeof(struct hdcp_enable_control),
+ GFP_KERNEL);
+
+ if (hdcp.en_ctrl == 0) {
+ printk(KERN_WARNING
+ "HDCP: Cannot allocate memory for HDCP"
+ " enable control struct\n");
+ return -EFAULT;
+ }
+ }
+
+ if (copy_from_user(hdcp.en_ctrl, argp,
+ sizeof(struct hdcp_enable_control))) {
+ printk(KERN_WARNING "HDCP: Error copying from user space "
+ "- enable ioctl\n");
+ return -EFAULT;
+ }
+
+ /* Post event to workqueue */
+ if (hdcp_submit_work(HDCP_ENABLE_CTL, 0) == 0)
+ return -EFAULT;
+
+ return 0;
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_disable_ctl
+ *-----------------------------------------------------------------------------
+ */
+static long hdcp_disable_ctl(void)
+{
+ DBG("hdcp_ioctl() - DISABLE %u", jiffies_to_msecs(jiffies));
+
+ hdcp_cancel_work(&hdcp.pending_start);
+ hdcp_cancel_work(&hdcp.pending_wq_event);
+
+ hdcp.pending_disable = 1;
+ /* Post event to workqueue */
+ if (hdcp_submit_work(HDCP_DISABLE_CTL, 0) == 0)
+ return -EFAULT;
+
+ return 0;
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_query_status_ctl
+ *-----------------------------------------------------------------------------
+ */
+static long hdcp_query_status_ctl(void __user *argp)
+{
+ uint32_t *status = (uint32_t *)argp;
+
+ DBG("hdcp_ioctl() - QUERY %u", jiffies_to_msecs(jiffies));
+
+ *status = hdcp.auth_state;
+
+ return 0;
+}
+
+static int hdcp_wait_re_entrance;
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_wait_event_ctl
+ *-----------------------------------------------------------------------------
+ */
+static long hdcp_wait_event_ctl(void __user *argp)
+{
+ struct hdcp_wait_control ctrl;
+
+ DBG("hdcp_ioctl() - WAIT %u %d", jiffies_to_msecs(jiffies),
+ hdcp.hdcp_up_event);
+
+ if (copy_from_user(&ctrl, argp,
+ sizeof(struct hdcp_wait_control))) {
+ printk(KERN_WARNING "HDCP: Error copying from user space"
+ " - wait ioctl");
+ return -EFAULT;
+ }
+
+ if (hdcp_wait_re_entrance == 0) {
+ hdcp_wait_re_entrance = 1;
+ wait_event_interruptible(hdcp_up_wait_queue,
+ (hdcp.hdcp_up_event & 0xFF) != 0);
+
+ ctrl.event = hdcp.hdcp_up_event;
+
+ if ((ctrl.event & 0xFF) == HDCP_EVENT_STEP2) {
+ if (copy_to_user(ctrl.data, &sha_input,
+ sizeof(struct hdcp_sha_in))) {
+ printk(KERN_WARNING "HDCP: Error copying to "
+ "user space - wait ioctl");
+ return -EFAULT;
+ }
+ }
+
+ hdcp.hdcp_up_event = 0;
+ hdcp_wait_re_entrance = 0;
+ } else
+ ctrl.event = HDCP_EVENT_EXIT;
+
+ /* Store output data to output pointer */
+ if (copy_to_user(argp, &ctrl,
+ sizeof(struct hdcp_wait_control))) {
+ printk(KERN_WARNING "HDCP: Error copying to user space -"
+ " wait ioctl");
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_done_ctl
+ *-----------------------------------------------------------------------------
+ */
+static long hdcp_done_ctl(void __user *argp)
+{
+ uint32_t *status = (uint32_t *)argp;
+
+ DBG("hdcp_ioctl() - DONE %u %d", jiffies_to_msecs(jiffies), *status);
+
+ hdcp.hdcp_down_event &= ~(*status & 0xFF);
+ hdcp.hdcp_down_event |= *status & 0xFF00;
+
+ wake_up_interruptible(&hdcp_down_wait_queue);
+
+ return 0;
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_encrypt_key_ctl
+ *-----------------------------------------------------------------------------
+ */
+static long hdcp_encrypt_key_ctl(void __user *argp)
+{
+ struct hdcp_encrypt_control *ctrl;
+ uint32_t *out_key;
+
+ DBG("hdcp_ioctl() - ENCRYPT KEY %u", jiffies_to_msecs(jiffies));
+
+ mutex_lock(&hdcp.lock);
+
+ if (hdcp.hdcp_state != HDCP_DISABLED) {
+ printk(KERN_INFO "HDCP: Cannot encrypt keys while HDCP "
+ "is enabled\n");
+ mutex_unlock(&hdcp.lock);
+ return -EFAULT;
+ }
+
+ hdcp.hdcp_state = HDCP_KEY_ENCRYPTION_ONGOING;
+
+ /* Encryption happens in ioctl / user context */
+ ctrl = kmalloc(sizeof(struct hdcp_encrypt_control),
+ GFP_KERNEL);
+
+ if (ctrl == 0) {
+ printk(KERN_WARNING "HDCP: Cannot allocate memory for HDCP"
+ " encryption control struct\n");
+ mutex_unlock(&hdcp.lock);
+ return -EFAULT;
+ }
+
+ out_key = kmalloc(sizeof(uint32_t) *
+ DESHDCP_KEY_SIZE, GFP_KERNEL);
+
+ if (out_key == 0) {
+ printk(KERN_WARNING "HDCP: Cannot allocate memory for HDCP "
+ "encryption output key\n");
+ kfree(ctrl);
+ mutex_unlock(&hdcp.lock);
+ return -EFAULT;
+ }
+
+ if (copy_from_user(ctrl, argp,
+ sizeof(struct hdcp_encrypt_control))) {
+ printk(KERN_WARNING "HDCP: Error copying from user space"
+ " - encrypt ioctl\n");
+ kfree(ctrl);
+ kfree(out_key);
+ mutex_unlock(&hdcp.lock);
+ return -EFAULT;
+ }
+
+ hdcp_request_dss();
+
+ /* Call encrypt function */
+ hdcp_3des_encrypt_key(ctrl, out_key);
+
+ hdcp_release_dss();
+
+ hdcp.hdcp_state = HDCP_DISABLED;
+ mutex_unlock(&hdcp.lock);
+
+ /* Store output data to output pointer */
+ if (copy_to_user(ctrl->out_key, out_key,
+ sizeof(uint32_t)*DESHDCP_KEY_SIZE)) {
+ printk(KERN_WARNING "HDCP: Error copying to user space -"
+ " encrypt ioctl\n");
+ kfree(ctrl);
+ kfree(out_key);
+ return -EFAULT;
+ }
+
+ kfree(ctrl);
+ kfree(out_key);
+ return 0;
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_ioctl
+ *-----------------------------------------------------------------------------
+ */
+long hdcp_ioctl(struct file *fd, unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+
+ switch (cmd) {
+ case HDCP_ENABLE:
+ return hdcp_enable_ctl(argp);
+
+ case HDCP_DISABLE:
+ return hdcp_disable_ctl();
+
+ case HDCP_ENCRYPT_KEY:
+ return hdcp_encrypt_key_ctl(argp);
+
+ case HDCP_QUERY_STATUS:
+ return hdcp_query_status_ctl(argp);
+
+ case HDCP_WAIT_EVENT:
+ return hdcp_wait_event_ctl(argp);
+
+ case HDCP_DONE:
+ return hdcp_done_ctl(argp);
+
+ default:
+ return -ENOTTY;
+ } /* End switch */
+}
+
+
+/******************************************************************************
+ * HDCP driver init/exit
+ *****************************************************************************/
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_mmap
+ *-----------------------------------------------------------------------------
+ */
+static int hdcp_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ int status;
+
+ DBG("hdcp_mmap() %lx %lx %lx\n", vma->vm_start, vma->vm_pgoff,
+ vma->vm_end - vma->vm_start);
+
+ vma->vm_flags |= VM_IO | VM_RESERVED;
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+
+ status = remap_pfn_range(vma, vma->vm_start,
+ HDMI_WP >> PAGE_SHIFT,
+ vma->vm_end - vma->vm_start,
+ vma->vm_page_prot);
+ if (status) {
+ DBG("mmap error %d\n", status);
+ return -EAGAIN;
+ }
+
+ DBG("mmap succesfull\n");
+ return 0;
+}
+
+static struct file_operations hdcp_fops = {
+ .owner = THIS_MODULE,
+ .mmap = hdcp_mmap,
+ .unlocked_ioctl = hdcp_ioctl,
+};
+
+struct miscdevice mdev;
+
+static void hdcp_load_keys_cb(const struct firmware *fw, void *context)
+{
+ struct hdcp_enable_control *en_ctrl;
+
+ if (!fw) {
+ pr_err("HDCP: failed to load keys\n");
+ return;
+ }
+
+ if (fw->size != sizeof(en_ctrl->key)) {
+ pr_err("HDCP: encrypted key file wrong size %d\n", fw->size);
+ return;
+ }
+
+ en_ctrl = kmalloc(sizeof(*en_ctrl), GFP_KERNEL);
+ if (!en_ctrl) {
+ pr_err("HDCP: can't allocated space for keys\n");
+ return;
+ }
+
+ memcpy(en_ctrl->key, fw->data, sizeof(en_ctrl->key));
+ en_ctrl->nb_retry = 20;
+
+ hdcp.en_ctrl = en_ctrl;
+ hdcp.retry_cnt = hdcp.en_ctrl->nb_retry;
+ hdcp.hdcp_state = HDCP_ENABLE_PENDING;
+ hdcp.hdcp_keys_loaded = true;
+ pr_info("HDCP: loaded keys\n");
+}
+
+static int hdcp_load_keys(void)
+{
+ int ret;
+
+ ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
+ "hdcp.keys", mdev.this_device, GFP_KERNEL,
+ &hdcp, hdcp_load_keys_cb);
+ if (ret < 0) {
+ pr_err("HDCP: request_firmware_nowait failed: %d\n", ret);
+ hdcp.hdcp_keys_loaded = false;
+ return ret;
+ }
+
+ return 0;
+}
+
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_init
+ *-----------------------------------------------------------------------------
+ */
+static int __init hdcp_init(void)
+{
+ DBG("hdcp_init() %u", jiffies_to_msecs(jiffies));
+
+ /* Map HDMI WP address */
+ hdcp.hdmi_wp_base_addr = ioremap(HDMI_WP, 0x1000);
+
+ if (!hdcp.hdmi_wp_base_addr) {
+ printk(KERN_ERR "HDCP: HDMI WP IOremap error\n");
+ return -EFAULT;
+ }
+
+ /* Map DESHDCP in kernel address space */
+ hdcp.deshdcp_base_addr = ioremap(DSS_SS_FROM_L3__DESHDCP, 0x34);
+
+ if (!hdcp.deshdcp_base_addr) {
+ printk(KERN_ERR "HDCP: DESHDCP IOremap error\n");
+ goto err_map_deshdcp;
+ }
+
+ mutex_init(&hdcp.lock);
+
+ mdev.minor = MISC_DYNAMIC_MINOR;
+ mdev.name = "hdcp";
+ mdev.mode = 0666;
+ mdev.fops = &hdcp_fops;
+
+ if (misc_register(&mdev)) {
+ printk(KERN_ERR "HDCP: Could not add character driver\n");
+ goto err_register;
+ }
+
+ mutex_lock(&hdcp.lock);
+
+ /* Variable init */
+ hdcp.en_ctrl = 0;
+ hdcp.hdcp_state = HDCP_DISABLED;
+ hdcp.pending_start = 0;
+ hdcp.pending_wq_event = 0;
+ hdcp.retry_cnt = 0;
+ hdcp.auth_state = HDCP_STATE_DISABLED;
+ hdcp.pending_disable = 0;
+ hdcp.hdcp_up_event = 0;
+ hdcp.hdcp_down_event = 0;
+ hdcp_wait_re_entrance = 0;
+ hdcp.hpd_low = 0;
+
+ spin_lock_init(&hdcp.spinlock);
+
+ init_completion(&hdcp_comp);
+
+ hdcp.workqueue = create_singlethread_workqueue("hdcp");
+ if (hdcp.workqueue == NULL)
+ goto err_add_driver;
+
+ hdcp_request_dss();
+
+ /* Register HDCP callbacks to HDMI library */
+ if (omapdss_hdmi_register_hdcp_callbacks(&hdcp_start_frame_cb,
+ &hdcp_irq_cb,
+ &hdcp_3des_cb))
+ hdcp.hdmi_state = HDMI_STARTED;
+ else
+ hdcp.hdmi_state = HDMI_STOPPED;
+
+ hdcp_release_dss();
+
+ mutex_unlock(&hdcp.lock);
+
+ hdcp_load_keys();
+
+ return 0;
+
+err_add_driver:
+ misc_deregister(&mdev);
+
+err_register:
+ mutex_destroy(&hdcp.lock);
+
+ iounmap(hdcp.deshdcp_base_addr);
+
+err_map_deshdcp:
+ iounmap(hdcp.hdmi_wp_base_addr);
+
+ return -EFAULT;
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_exit
+ *-----------------------------------------------------------------------------
+ */
+static void __exit hdcp_exit(void)
+{
+ DBG("hdcp_exit() %u", jiffies_to_msecs(jiffies));
+
+ mutex_lock(&hdcp.lock);
+
+ kfree(hdcp.en_ctrl);
+
+ hdcp_request_dss();
+
+ /* Un-register HDCP callbacks to HDMI library */
+ omapdss_hdmi_register_hdcp_callbacks(0, 0, 0);
+
+ hdcp_release_dss();
+
+ misc_deregister(&mdev);
+
+ /* Unmap HDMI WP / DESHDCP */
+ iounmap(hdcp.hdmi_wp_base_addr);
+ iounmap(hdcp.deshdcp_base_addr);
+
+ destroy_workqueue(hdcp.workqueue);
+
+ mutex_unlock(&hdcp.lock);
+
+ mutex_destroy(&hdcp.lock);
+}
+
+/*-----------------------------------------------------------------------------
+ *-----------------------------------------------------------------------------
+ */
+module_init(hdcp_init);
+module_exit(hdcp_exit);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("OMAP HDCP kernel module");
+MODULE_AUTHOR("Fabrice Olivero");