aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/video/omap2/hdcp/hdcp_ddc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/omap2/hdcp/hdcp_ddc.c')
-rw-r--r--drivers/video/omap2/hdcp/hdcp_ddc.c310
1 files changed, 310 insertions, 0 deletions
diff --git a/drivers/video/omap2/hdcp/hdcp_ddc.c b/drivers/video/omap2/hdcp/hdcp_ddc.c
new file mode 100644
index 0000000..e5104fa
--- /dev/null
+++ b/drivers/video/omap2/hdcp/hdcp_ddc.c
@@ -0,0 +1,310 @@
+/*
+ * hdcp_ddc.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/delay.h>
+#include "hdcp.h"
+#include "hdcp_ddc.h"
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_suspend_resume_auto_ri
+ *-----------------------------------------------------------------------------
+ */
+static int hdcp_suspend_resume_auto_ri(enum ri_suspend_resume state)
+{
+ static u8 OldRiStat, OldRiCommand;
+ u8 TimeOut = 10;
+
+ /* Suspend Auto Ri in order to allow FW access MDDC bus.
+ * Poll 0x72:0x26[0] for MDDC bus availability or timeout
+ */
+
+ DBG("hdcp_suspend_resume_auto_ri() state=%s",
+ state == AUTO_RI_SUSPEND ? "SUSPEND" : "RESUME");
+
+ if (state == AUTO_RI_SUSPEND) {
+ /* Save original Auto Ri state */
+ OldRiCommand = RD_FIELD_32(hdcp.hdmi_wp_base_addr +
+ HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__RI_CMD, 0, 0);
+
+ /* Disable Auto Ri */
+ hdcp_lib_auto_ri_check(false);
+
+ /* Wait for HW to release MDDC bus */
+ /* TODO: while loop / timeout to be enhanced */
+ while (--TimeOut) {
+ if (!RD_FIELD_32(hdcp.hdmi_wp_base_addr +
+ HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__RI_STAT, 0, 0))
+ break;
+ }
+
+ /* MDDC bus not relinquished */
+ if (!TimeOut) {
+ printk(KERN_ERR "HDCP: Suspending Auto Ri failed !\n");
+ return -HDCP_DDC_ERROR;
+ }
+
+ OldRiStat = RD_FIELD_32(hdcp.hdmi_wp_base_addr +
+ HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__RI_STAT, 0, 0);
+ } else {
+ /* If Auto Ri was enabled before it was suspended */
+ if ((OldRiStat) && (OldRiCommand))
+ /* Re-enable Auto Ri */
+ hdcp_lib_auto_ri_check(false);
+ }
+
+ return HDCP_OK;
+}
+
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_start_ddc_transfer
+ *-----------------------------------------------------------------------------
+ */
+static int hdcp_start_ddc_transfer(mddc_type *mddc_cmd, u8 operation)
+{
+ u8 *cmd = (u8 *)mddc_cmd;
+ struct timeval t0, t1, t2;
+ u32 time_elapsed_ms = 0;
+ u32 i, size;
+ unsigned long flags;
+
+#ifdef _9032_AUTO_RI_
+ if (hdcp_suspend_resume_auto_ri(AUTO_RI_SUSPEND))
+ return -HDCP_DDC_ERROR;
+#endif
+
+ spin_lock_irqsave(&hdcp.spinlock, flags);
+
+ /* Abort Master DDC operation and Clear FIFO pointer */
+ WR_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__DDC_CMD, MASTER_CMD_CLEAR_FIFO);
+
+ /* Read to flush */
+ RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__DDC_CMD);
+
+ /* Sending DDC header, it'll clear DDC Status register too */
+ for (i = 0; i < 7; i++) {
+ WR_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__DDC_ADDR + i * sizeof(uint32_t),
+ cmd[i]);
+
+ /* Read to flush */
+ RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__DDC_ADDR +
+ i * sizeof(uint32_t));
+ }
+
+ spin_unlock_irqrestore(&hdcp.spinlock, flags);
+
+ do_gettimeofday(&t1);
+ memcpy(&t0, &t1, sizeof(t0));
+
+ i = 0;
+ size = mddc_cmd->nbytes_lsb + (mddc_cmd->nbytes_msb << 8);
+
+ while ((i < size) && (hdcp.pending_disable == 0)) {
+ if (operation == DDC_WRITE) {
+ /* Write data to DDC FIFO as long as it is NOT full */
+ if (RD_FIELD_32(hdcp.hdmi_wp_base_addr +
+ HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__DDC_STATUS, 3, 3)
+ == 0) {
+ WR_REG_32(hdcp.hdmi_wp_base_addr +
+ HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__DDC_DATA,
+ mddc_cmd->pdata[i++]);
+ do_gettimeofday(&t1);
+ }
+ } else if (operation == DDC_READ) {
+ /* Read from DDC FIFO as long as it is NOT empty */
+ if (RD_FIELD_32(hdcp.hdmi_wp_base_addr +
+ HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__DDC_STATUS, 2, 2)
+ == 0) {
+ mddc_cmd->pdata[i++] =
+ RD_REG_32(hdcp.hdmi_wp_base_addr +
+ HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__DDC_DATA);
+ do_gettimeofday(&t1);
+ }
+ }
+
+ do_gettimeofday(&t2);
+ time_elapsed_ms = (t2.tv_sec - t1.tv_sec) * 1000 +
+ (t2.tv_usec - t1.tv_usec) / 1000;
+
+ if (time_elapsed_ms > HDCP_DDC_TIMEOUT) {
+ DBG("DDC timeout - no data during %d ms - "
+ "status=%02x %u",
+ HDCP_DDC_TIMEOUT,
+ RD_REG_32(hdcp.hdmi_wp_base_addr +
+ HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__DDC_STATUS),
+ jiffies_to_msecs(jiffies));
+ goto ddc_error;
+ }
+ }
+
+ if (hdcp.pending_disable)
+ goto ddc_abort;
+
+ /* Wait for the FIFO to be empty (end of transfer) */
+ while ((RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__DDC_STATUS) != 0x4) &&
+ (hdcp.pending_disable == 0)) {
+ do_gettimeofday(&t2);
+ time_elapsed_ms = (t2.tv_sec - t1.tv_sec) * 1000 +
+ (t2.tv_usec - t1.tv_usec) / 1000;
+
+ if (time_elapsed_ms > HDCP_DDC_TIMEOUT) {
+ DBG("DDC timeout - FIFO not getting empty - "
+ "status=%02x",
+ RD_REG_32(hdcp.hdmi_wp_base_addr +
+ HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__DDC_STATUS));
+ goto ddc_error;
+ }
+ }
+
+ if (hdcp.pending_disable)
+ goto ddc_abort;
+
+ DBG("DDC transfer: bytes: %d time_us: %lu status: %x",
+ i,
+ (t2.tv_sec - t0.tv_sec) * 1000000 + (t2.tv_usec - t0.tv_usec),
+ RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__DDC_STATUS));
+
+#ifdef DDC_DBG
+ {
+ int k;
+ for (k = 0; k < i; k++)
+ printk(KERN_DEBUG "%02x ", mddc_cmd->pdata[k]);
+ printk(KERN_DEBUG "\n");
+ }
+#endif
+
+#ifdef _9032_AUTO_RI_
+ /* Re-enable Auto Ri */
+ if (hdcp_suspend_resume_auto_ri(AUTO_RI_RESUME))
+ return -HDCP_DDC_ERROR;
+#endif
+
+ return HDCP_OK;
+
+ddc_error:
+ hdcp_ddc_abort();
+ return -HDCP_DDC_ERROR;
+
+ddc_abort:
+ DBG("DDC transfer aborted - status=%02x",
+ RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__DDC_STATUS));
+
+ return HDCP_OK;
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_ddc_operation
+ *-----------------------------------------------------------------------------
+ */
+static int hdcp_ddc_operation(u16 no_bytes, u8 addr, u8 *pdata,
+ enum ddc_operation operation)
+{
+ mddc_type mddc;
+
+ mddc.slaveAddr = HDCPRX_SLV;
+ mddc.offset = 0;
+ mddc.regAddr = addr;
+ mddc.nbytes_lsb = no_bytes & 0xFF;
+ mddc.nbytes_msb = (no_bytes & 0x300) >> 8;
+ mddc.dummy = 0;
+ mddc.pdata = pdata;
+
+ if (operation == DDC_READ)
+ mddc.cmd = MASTER_CMD_SEQ_RD;
+ else
+ mddc.cmd = MASTER_CMD_SEQ_WR;
+
+ DBG("DDC %s: offset=%02x len=%d %u", operation == DDC_READ ?
+ "READ" : "WRITE",
+ addr, no_bytes,
+ jiffies_to_msecs(jiffies));
+
+ return hdcp_start_ddc_transfer(&mddc, operation);
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_ddc_read
+ *-----------------------------------------------------------------------------
+ */
+int hdcp_ddc_read(u16 no_bytes, u8 addr, u8 *pdata)
+{
+ return hdcp_ddc_operation(no_bytes, addr, pdata, DDC_READ);
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_ddc_write
+ *-----------------------------------------------------------------------------
+ */
+int hdcp_ddc_write(u16 no_bytes, u8 addr, u8 *pdata)
+{
+ return hdcp_ddc_operation(no_bytes, addr, pdata, DDC_WRITE);
+}
+
+/*-----------------------------------------------------------------------------
+ * Function: hdcp_ddc_abort
+ *-----------------------------------------------------------------------------
+ */
+void hdcp_ddc_abort(void)
+{
+ unsigned long flags;
+
+ /* In case of I2C_NO_ACK error, do not abort DDC to avoid
+ * DDC lockup
+ */
+ if (RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__DDC_STATUS) & 0x20)
+ return;
+
+ spin_lock_irqsave(&hdcp.spinlock, flags);
+
+ /* Abort Master DDC operation and Clear FIFO pointer */
+ WR_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__DDC_CMD, MASTER_CMD_ABORT);
+
+ /* Read to flush */
+ RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__DDC_CMD);
+
+ WR_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__DDC_CMD, MASTER_CMD_CLEAR_FIFO);
+
+ /* Read to flush */
+ RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM,
+ HDMI_IP_CORE_SYSTEM__DDC_CMD);
+
+ spin_unlock_irqrestore(&hdcp.spinlock, flags);
+}