summaryrefslogtreecommitdiff
path: root/drivers/video/tegra/dc
diff options
context:
space:
mode:
authorJon Mayo <jmayo@nvidia.com>2011-01-03 17:26:25 -0800
committerYu-Huan Hsu <yhsu@nvidia.com>2011-01-05 13:21:32 -0800
commit4812e391e1e0fdae30b4140e77471ccf4ef6860c (patch)
tree5ab37ad1384b5387c7e50a91334fcf3f1506a7fb /drivers/video/tegra/dc
parent8e285a362156e2804931340c0c2a9f6d35f598cd (diff)
[ARM] tegra: hdmi nvhdcp driver
Device /dev/nvhdcpX is used to manage NVHDCP on framebuffer /dev/fbX. These devices are created on hdmi driver initialition when it is attached to dc. An ioctl interface is in video/nvhdcp.h Hooks into edid.c and hdmi.c were added to take control of I2C bus. use TEGRA_DC_OUT_NVHDCP_POLICY_ON_DEMAND in tegra_dc_out.flags in board panel configuration to select a different default policy at probe. Change-Id: I0db66fc86096b98d2604544061721d291523de75 Reviewed-on: http://git-master/r/14466 Reviewed-by: Jonathan Mayo <jmayo@nvidia.com> Tested-by: Jonathan Mayo <jmayo@nvidia.com> Reviewed-by: Phillip Smith <psmith@nvidia.com> Reviewed-by: Yu-Huan Hsu <yhsu@nvidia.com>
Diffstat (limited to 'drivers/video/tegra/dc')
-rw-r--r--drivers/video/tegra/dc/Makefile3
-rw-r--r--drivers/video/tegra/dc/edid.c7
-rw-r--r--drivers/video/tegra/dc/edid.h1
-rw-r--r--drivers/video/tegra/dc/hdmi.c138
-rw-r--r--drivers/video/tegra/dc/hdmi.h9
-rw-r--r--drivers/video/tegra/dc/hdmi_reg.h16
-rw-r--r--drivers/video/tegra/dc/nvhdcp.c1057
-rw-r--r--drivers/video/tegra/dc/nvhdcp.h28
8 files changed, 1211 insertions, 48 deletions
diff --git a/drivers/video/tegra/dc/Makefile b/drivers/video/tegra/dc/Makefile
index eb39d5d28e92..7ecf59a74db5 100644
--- a/drivers/video/tegra/dc/Makefile
+++ b/drivers/video/tegra/dc/Makefile
@@ -1,4 +1,5 @@
obj-y += dc.o
obj-y += rgb.o
obj-y += hdmi.o
-obj-y += edid.o \ No newline at end of file
+obj-y += edid.o
+obj-y += nvhdcp.o
diff --git a/drivers/video/tegra/dc/edid.c b/drivers/video/tegra/dc/edid.c
index 792ea54c98ff..e763caef6e63 100644
--- a/drivers/video/tegra/dc/edid.c
+++ b/drivers/video/tegra/dc/edid.c
@@ -81,6 +81,11 @@ void tegra_edid_debug_add(struct tegra_edid *edid)
}
#endif
+int tegra_edid_i2c(struct tegra_edid *edid, struct i2c_msg *msg, int msg_len)
+{
+ return i2c_transfer(edid->client->adapter, msg, msg_len);
+}
+
#ifdef DEBUG
static char tegra_edid_dump_buff[16 * 1024];
@@ -117,7 +122,7 @@ static void tegra_edid_dump(struct tegra_edid *edid)
}
#endif
-int tegra_edid_read_block(struct tegra_edid *edid, int block, u8 *data)
+static int tegra_edid_read_block(struct tegra_edid *edid, int block, u8 *data)
{
u8 block_buf[] = {block >> 1};
u8 cmd_buf[] = {(block & 0x1) * 128};
diff --git a/drivers/video/tegra/dc/edid.h b/drivers/video/tegra/dc/edid.h
index 821da90a8b4f..4d417f2ef98f 100644
--- a/drivers/video/tegra/dc/edid.h
+++ b/drivers/video/tegra/dc/edid.h
@@ -26,6 +26,7 @@ struct tegra_edid;
struct tegra_edid *tegra_edid_create(int bus);
void tegra_edid_destroy(struct tegra_edid *edid);
+int tegra_edid_i2c(struct tegra_edid *edid, struct i2c_msg *msg, int msg_len);
int tegra_edid_get_monspecs(struct tegra_edid *edid, struct fb_monspecs *specs);
#endif
diff --git a/drivers/video/tegra/dc/hdmi.c b/drivers/video/tegra/dc/hdmi.c
index 3d9d48733265..4cd36dc8a858 100644
--- a/drivers/video/tegra/dc/hdmi.c
+++ b/drivers/video/tegra/dc/hdmi.c
@@ -33,11 +33,14 @@
#include <mach/fb.h>
#include <mach/nvhost.h>
+#include <video/tegrafb.h>
+
#include "dc_reg.h"
#include "dc_priv.h"
#include "hdmi_reg.h"
#include "hdmi.h"
#include "edid.h"
+#include "nvhdcp.h"
/* datasheet claims this will always be 216MHz */
#define HDMI_AUDIOCLK_FREQ 216000000
@@ -47,6 +50,7 @@
struct tegra_dc_hdmi_data {
struct tegra_dc *dc;
struct tegra_edid *edid;
+ struct tegra_nvhdcp *nvhdcp;
struct delayed_work work;
struct resource *base_res;
@@ -65,7 +69,7 @@ struct tegra_dc_hdmi_data {
bool hpd_pending;
};
-const struct fb_videomode tegra_dc_hdmi_supported_modes[] = {
+static const struct fb_videomode tegra_dc_hdmi_supported_modes[] = {
/* 1280x720p 60hz: EIA/CEA-861-B Format 4 */
{
.xres = 1280,
@@ -223,13 +227,13 @@ static const struct tegra_hdmi_audio_config
}
-static inline unsigned long tegra_hdmi_readl(struct tegra_dc_hdmi_data *hdmi,
+static inline unsigned long _tegra_hdmi_readl(struct tegra_dc_hdmi_data *hdmi,
unsigned long reg)
{
return readl(hdmi->base + reg * 4);
}
-static inline void tegra_hdmi_writel(struct tegra_dc_hdmi_data *hdmi,
+static inline void _tegra_hdmi_writel(struct tegra_dc_hdmi_data *hdmi,
unsigned long val, unsigned long reg)
{
writel(val, hdmi->base + reg * 4);
@@ -239,15 +243,15 @@ static inline void tegra_hdmi_clrsetbits(struct tegra_dc_hdmi_data *hdmi,
unsigned long reg, unsigned long clr,
unsigned long set)
{
- unsigned long val = tegra_hdmi_readl(hdmi, reg);
+ unsigned long val = _tegra_hdmi_readl(hdmi, reg);
val &= ~clr;
val |= set;
- tegra_hdmi_writel(hdmi, val, reg);
+ _tegra_hdmi_writel(hdmi, val, reg);
}
#define DUMP_REG(a) do { \
printk("HDMI %-32s\t%03x\t%08lx\n", \
- #a, a, tegra_hdmi_readl(hdmi, a)); \
+ #a, a, _tegra_hdmi_readl(hdmi, a)); \
} while (0)
#ifdef DEBUG
@@ -482,6 +486,7 @@ static bool tegra_dc_hdmi_detect(struct tegra_dc *dc)
fail:
switch_set_state(&hdmi->hpd_switch, 0);
+ tegra_nvhdcp_set_plug(hdmi->nvhdcp, 0);
return false;
}
@@ -508,7 +513,8 @@ static irqreturn_t tegra_dc_hdmi_irq(int irq, void *ptr)
if (hdmi->suspended) {
hdmi->hpd_pending = true;
} else {
- if (tegra_dc_hdmi_hpd(dc))
+ bool v = tegra_dc_hdmi_hpd(dc);
+ if (v)
schedule_delayed_work(&hdmi->work, msecs_to_jiffies(100));
else
schedule_delayed_work(&hdmi->work, msecs_to_jiffies(0));
@@ -524,6 +530,7 @@ static void tegra_dc_hdmi_suspend(struct tegra_dc *dc)
unsigned long flags;
spin_lock_irqsave(&hdmi->suspend_lock, flags);
+ tegra_nvhdcp_suspend(hdmi->nvhdcp);
hdmi->suspended = true;
spin_unlock_irqrestore(&hdmi->suspend_lock, flags);
}
@@ -619,6 +626,13 @@ static int tegra_dc_hdmi_init(struct tegra_dc *dc)
goto err_free_irq;
}
+ hdmi->nvhdcp = tegra_nvhdcp_create(hdmi, dc->ndev->id);
+ if (IS_ERR_OR_NULL(hdmi->nvhdcp)) {
+ dev_err(&dc->ndev->dev, "hdmi: can't create nvhdcp\n");
+ err = PTR_ERR(hdmi->nvhdcp);
+ goto err_edid_destroy;
+ }
+
INIT_DELAYED_WORK(&hdmi->work, tegra_dc_hdmi_detect_worker);
hdmi->dc = dc;
@@ -639,8 +653,19 @@ static int tegra_dc_hdmi_init(struct tegra_dc *dc)
tegra_dc_set_outdata(dc, hdmi);
wake_lock_init(&hdmi->wake_lock, WAKE_LOCK_SUSPEND, "HDMI");
+
+ /* boards can select default content protection policy */
+ if (dc->out->flags & TEGRA_DC_OUT_NVHDCP_POLICY_ON_DEMAND) {
+ tegra_nvhdcp_set_policy(hdmi->nvhdcp,
+ TEGRA_NVHDCP_POLICY_ON_DEMAND);
+ } else {
+ tegra_nvhdcp_set_policy(hdmi->nvhdcp,
+ TEGRA_NVHDCP_POLICY_ALWAYS_ON);
+ }
return 0;
+err_edid_destroy:
+ tegra_edid_destroy(hdmi->edid);
err_free_irq:
free_irq(gpio_to_irq(dc->out->hotplug_gpio), dc);
err_put_clock:
@@ -672,6 +697,7 @@ static void tegra_dc_hdmi_destroy(struct tegra_dc *dc)
clk_put(hdmi->disp1_clk);
clk_put(hdmi->disp2_clk);
tegra_edid_destroy(hdmi->edid);
+ tegra_nvhdcp_destroy(hdmi->nvhdcp);
wake_lock_destroy(&hdmi->wake_lock);
kfree(hdmi);
@@ -705,7 +731,7 @@ static void tegra_dc_hdmi_setup_audio_fs_tables(struct tegra_dc *dc)
delta = 9;
eight_half = (8 * HDMI_AUDIOCLK_FREQ) / (f * 128);
- tegra_hdmi_writel(hdmi, AUDIO_FS_LOW(eight_half - delta) |
+ _tegra_hdmi_writel(hdmi, AUDIO_FS_LOW(eight_half - delta) |
AUDIO_FS_HIGH(eight_half + delta),
HDMI_NV_PDISP_AUDIO_FS(i));
}
@@ -718,7 +744,7 @@ static int tegra_dc_hdmi_setup_audio(struct tegra_dc *dc)
unsigned long audio_n;
unsigned audio_freq = 44100; /* TODO: find some way of configuring this */
- tegra_hdmi_writel(hdmi,
+ _tegra_hdmi_writel(hdmi,
AUDIO_CNTRL0_ERROR_TOLERANCE(6) |
AUDIO_CNTRL0_FRAMES_PER_BLOCK(0xc0) |
AUDIO_CNTRL0_SOURCE_SELECT_AUTO,
@@ -732,24 +758,24 @@ static int tegra_dc_hdmi_setup_audio(struct tegra_dc *dc)
return -EINVAL;
}
- tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_HDMI_ACR_CTRL);
+ _tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_HDMI_ACR_CTRL);
audio_n = AUDIO_N_RESETF | AUDIO_N_GENERATE_ALTERNALTE |
AUDIO_N_VALUE(config->n - 1);
- tegra_hdmi_writel(hdmi, audio_n, HDMI_NV_PDISP_AUDIO_N);
+ _tegra_hdmi_writel(hdmi, audio_n, HDMI_NV_PDISP_AUDIO_N);
- tegra_hdmi_writel(hdmi, ACR_SUBPACK_N(config->n) | ACR_ENABLE,
+ _tegra_hdmi_writel(hdmi, ACR_SUBPACK_N(config->n) | ACR_ENABLE,
HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_HIGH);
- tegra_hdmi_writel(hdmi, ACR_SUBPACK_CTS(config->cts),
+ _tegra_hdmi_writel(hdmi, ACR_SUBPACK_CTS(config->cts),
HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_LOW);
- tegra_hdmi_writel(hdmi, SPARE_HW_CTS | SPARE_FORCE_SW_CTS |
+ _tegra_hdmi_writel(hdmi, SPARE_HW_CTS | SPARE_FORCE_SW_CTS |
SPARE_CTS_RESET_VAL(1),
HDMI_NV_PDISP_HDMI_SPARE);
audio_n &= ~AUDIO_N_RESETF;
- tegra_hdmi_writel(hdmi, audio_n, HDMI_NV_PDISP_AUDIO_N);
+ _tegra_hdmi_writel(hdmi, audio_n, HDMI_NV_PDISP_AUDIO_N);
tegra_dc_hdmi_setup_audio_fs_tables(dc);
@@ -770,7 +796,7 @@ static void tegra_dc_hdmi_write_infopack(struct tegra_dc *dc, int header_reg,
csum +=((u8 *)data)[i];
((u8 *)data)[0] = 0x100 - csum;
- tegra_hdmi_writel(hdmi, INFOFRAME_HEADER_TYPE(type) |
+ _tegra_hdmi_writel(hdmi, INFOFRAME_HEADER_TYPE(type) |
INFOFRAME_HEADER_VERSION(version) |
INFOFRAME_HEADER_LEN(len - 1),
header_reg);
@@ -797,8 +823,8 @@ static void tegra_dc_hdmi_write_infopack(struct tegra_dc *dc, int header_reg,
if (subpack_idx == 6 || (i + 1 == len)) {
int reg = header_reg + 1 + (i / 7) * 2;
- tegra_hdmi_writel(hdmi, subpack[0], reg);
- tegra_hdmi_writel(hdmi, subpack[1], reg + 1);
+ _tegra_hdmi_writel(hdmi, subpack[0], reg);
+ _tegra_hdmi_writel(hdmi, subpack[1], reg + 1);
}
}
}
@@ -809,7 +835,7 @@ static void tegra_dc_hdmi_setup_avi_infoframe(struct tegra_dc *dc, bool dvi)
struct hdmi_avi_infoframe avi;
if (dvi) {
- tegra_hdmi_writel(hdmi, 0x0,
+ _tegra_hdmi_writel(hdmi, 0x0,
HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL);
return;
}
@@ -863,7 +889,7 @@ static void tegra_dc_hdmi_setup_avi_infoframe(struct tegra_dc *dc, bool dvi)
HDMI_AVI_VERSION,
&avi, sizeof(avi));
- tegra_hdmi_writel(hdmi, INFOFRAME_CTRL_ENABLE,
+ _tegra_hdmi_writel(hdmi, INFOFRAME_CTRL_ENABLE,
HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL);
}
@@ -905,7 +931,7 @@ static void tegra_dc_hdmi_setup_audio_infoframe(struct tegra_dc *dc, bool dvi)
struct hdmi_audio_infoframe audio;
if (dvi) {
- tegra_hdmi_writel(hdmi, 0x0,
+ _tegra_hdmi_writel(hdmi, 0x0,
HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL);
return;
}
@@ -918,7 +944,7 @@ static void tegra_dc_hdmi_setup_audio_infoframe(struct tegra_dc *dc, bool dvi)
HDMI_AUDIO_VERSION,
&audio, sizeof(audio));
- tegra_hdmi_writel(hdmi, INFOFRAME_CTRL_ENABLE,
+ _tegra_hdmi_writel(hdmi, INFOFRAME_CTRL_ENABLE,
HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL);
}
@@ -975,13 +1001,13 @@ static void tegra_dc_hdmi_enable(struct tegra_dc *dc)
tegra_dc_writel(dc, PULSE_START(pulse_start) | PULSE_END(pulse_start + 8),
DC_DISP_H_PULSE2_POSITION_A);
- tegra_hdmi_writel(hdmi,
+ _tegra_hdmi_writel(hdmi,
VSYNC_WINDOW_END(0x210) |
VSYNC_WINDOW_START(0x200) |
VSYNC_WINDOW_ENABLE,
HDMI_NV_PDISP_HDMI_VSYNC_WINDOW);
- tegra_hdmi_writel(hdmi,
+ _tegra_hdmi_writel(hdmi,
(dc->ndev->id ? HDMI_SRC_DISPLAYB : HDMI_SRC_DISPLAYA) |
ARM_VIDEO_RANGE_LIMITED,
HDMI_NV_PDISP_INPUT_CONTROL);
@@ -990,7 +1016,7 @@ static void tegra_dc_hdmi_enable(struct tegra_dc *dc)
clk_disable(hdmi->disp2_clk);
dispclk_div_8_2 = clk_get_rate(hdmi->clk) / 1000000 * 4;
- tegra_hdmi_writel(hdmi,
+ _tegra_hdmi_writel(hdmi,
SOR_REFCLK_DIV_INT(dispclk_div_8_2 >> 2) |
SOR_REFCLK_DIV_FRAC(dispclk_div_8_2),
HDMI_NV_PDISP_SOR_REFCLK);
@@ -1007,13 +1033,13 @@ static void tegra_dc_hdmi_enable(struct tegra_dc *dc)
rekey - 18) / 32);
if (!dvi)
val |= HDMI_CTRL_ENABLE;
- tegra_hdmi_writel(hdmi, val, HDMI_NV_PDISP_HDMI_CTRL);
+ _tegra_hdmi_writel(hdmi, val, HDMI_NV_PDISP_HDMI_CTRL);
if (dvi)
- tegra_hdmi_writel(hdmi, 0x0,
+ _tegra_hdmi_writel(hdmi, 0x0,
HDMI_NV_PDISP_HDMI_GENERIC_CTRL);
else
- tegra_hdmi_writel(hdmi, GENERIC_CTRL_AUDIO,
+ _tegra_hdmi_writel(hdmi, GENERIC_CTRL_AUDIO,
HDMI_NV_PDISP_HDMI_GENERIC_CTRL);
@@ -1044,11 +1070,11 @@ static void tegra_dc_hdmi_enable(struct tegra_dc *dc)
pll0 |= SOR_PLL_ICHPMP(2);
}
- tegra_hdmi_writel(hdmi, pll0, HDMI_NV_PDISP_SOR_PLL0);
- tegra_hdmi_writel(hdmi, pll1, HDMI_NV_PDISP_SOR_PLL1);
+ _tegra_hdmi_writel(hdmi, pll0, HDMI_NV_PDISP_SOR_PLL0);
+ _tegra_hdmi_writel(hdmi, pll1, HDMI_NV_PDISP_SOR_PLL1);
if (pll1 & SOR_PLL_PE_EN) {
- tegra_hdmi_writel(hdmi,
+ _tegra_hdmi_writel(hdmi,
PE_CURRENT0(0xf) |
PE_CURRENT1(0xf) |
PE_CURRENT2(0xf) |
@@ -1062,7 +1088,7 @@ static void tegra_dc_hdmi_enable(struct tegra_dc *dc)
else
ds = DRIVE_CURRENT_5_250_mA;
- tegra_hdmi_writel(hdmi,
+ _tegra_hdmi_writel(hdmi,
DRIVE_CURRENT_LANE0(ds) |
DRIVE_CURRENT_LANE1(ds) |
DRIVE_CURRENT_LANE2(ds) |
@@ -1070,7 +1096,7 @@ static void tegra_dc_hdmi_enable(struct tegra_dc *dc)
DRIVE_CURRENT_FUSE_OVERRIDE,
HDMI_NV_PDISP_SOR_LANE_DRIVE_CURRENT);
- tegra_hdmi_writel(hdmi,
+ _tegra_hdmi_writel(hdmi,
SOR_SEQ_CTL_PU_PC(0) |
SOR_SEQ_PU_PC_ALT(0) |
SOR_SEQ_PD_PC(8) |
@@ -1084,13 +1110,13 @@ static void tegra_dc_hdmi_enable(struct tegra_dc *dc)
SOR_SEQ_INST_PIN_B_LOW |
SOR_SEQ_INST_DRIVE_PWM_OUT_LO;
- tegra_hdmi_writel(hdmi, val, HDMI_NV_PDISP_SOR_SEQ_INST0);
- tegra_hdmi_writel(hdmi, val, HDMI_NV_PDISP_SOR_SEQ_INST8);
+ _tegra_hdmi_writel(hdmi, val, HDMI_NV_PDISP_SOR_SEQ_INST0);
+ _tegra_hdmi_writel(hdmi, val, HDMI_NV_PDISP_SOR_SEQ_INST8);
val = 0x1c800;
val &= ~SOR_CSTM_ROTCLK(~0);
val |= SOR_CSTM_ROTCLK(2);
- tegra_hdmi_writel(hdmi, val, HDMI_NV_PDISP_SOR_CSTM);
+ _tegra_hdmi_writel(hdmi, val, HDMI_NV_PDISP_SOR_CSTM);
tegra_dc_writel(dc, DISP_CTRL_MODE_STOP, DC_CMD_DISPLAY_COMMAND);
@@ -1099,13 +1125,13 @@ static void tegra_dc_hdmi_enable(struct tegra_dc *dc)
/* start SOR */
- tegra_hdmi_writel(hdmi,
+ _tegra_hdmi_writel(hdmi,
SOR_PWR_NORMAL_STATE_PU |
SOR_PWR_NORMAL_START_NORMAL |
SOR_PWR_SAFE_STATE_PD |
SOR_PWR_SETTING_NEW_TRIGGER,
HDMI_NV_PDISP_SOR_PWR);
- tegra_hdmi_writel(hdmi,
+ _tegra_hdmi_writel(hdmi,
SOR_PWR_NORMAL_STATE_PU |
SOR_PWR_NORMAL_START_NORMAL |
SOR_PWR_SAFE_STATE_PD |
@@ -1115,10 +1141,10 @@ static void tegra_dc_hdmi_enable(struct tegra_dc *dc)
retries = 1000;
do {
BUG_ON(--retries < 0);
- val = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_SOR_PWR);
+ val = _tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_SOR_PWR);
} while (val & SOR_PWR_SETTING_NEW_PENDING);
- tegra_hdmi_writel(hdmi,
+ _tegra_hdmi_writel(hdmi,
SOR_STATE_ASY_CRCMODE_COMPLETE |
SOR_STATE_ASY_OWNER_HEAD0 |
SOR_STATE_ASY_SUBOWNER_BOTH |
@@ -1130,13 +1156,13 @@ static void tegra_dc_hdmi_enable(struct tegra_dc *dc)
HDMI_NV_PDISP_SOR_STATE2);
val = SOR_STATE_ASY_HEAD_OPMODE_AWAKE | SOR_STATE_ASY_ORMODE_NORMAL;
- tegra_hdmi_writel(hdmi, val, HDMI_NV_PDISP_SOR_STATE1);
+ _tegra_hdmi_writel(hdmi, val, HDMI_NV_PDISP_SOR_STATE1);
- tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_SOR_STATE0);
- tegra_hdmi_writel(hdmi, SOR_STATE_UPDATE, HDMI_NV_PDISP_SOR_STATE0);
- tegra_hdmi_writel(hdmi, val | SOR_STATE_ATTACHED,
+ _tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_SOR_STATE0);
+ _tegra_hdmi_writel(hdmi, SOR_STATE_UPDATE, HDMI_NV_PDISP_SOR_STATE0);
+ _tegra_hdmi_writel(hdmi, val | SOR_STATE_ATTACHED,
HDMI_NV_PDISP_SOR_STATE1);
- tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_SOR_STATE0);
+ _tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_SOR_STATE0);
tegra_dc_writel(dc, HDMI_ENABLE, DC_DISP_DISP_WIN_OPTIONS);
@@ -1148,6 +1174,8 @@ static void tegra_dc_hdmi_enable(struct tegra_dc *dc)
tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL);
tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL);
+ tegra_nvhdcp_set_plug(hdmi->nvhdcp, 1);
+
wake_lock(&hdmi->wake_lock);
}
@@ -1155,10 +1183,13 @@ static void tegra_dc_hdmi_disable(struct tegra_dc *dc)
{
struct tegra_dc_hdmi_data *hdmi = tegra_dc_get_outdata(dc);
+ tegra_nvhdcp_set_plug(hdmi->nvhdcp, 0);
+
tegra_periph_reset_assert(hdmi->clk);
clk_disable(hdmi->clk);
wake_unlock(&hdmi->wake_lock);
}
+
struct tegra_dc_out_ops tegra_dc_hdmi_ops = {
.init = tegra_dc_hdmi_init,
.destroy = tegra_dc_hdmi_destroy,
@@ -1169,3 +1200,20 @@ struct tegra_dc_out_ops tegra_dc_hdmi_ops = {
.resume = tegra_dc_hdmi_resume,
};
+unsigned long tegra_hdmi_readl(struct tegra_dc_hdmi_data *hdmi,
+ unsigned long reg)
+{
+ return _tegra_hdmi_readl(hdmi, reg);
+}
+
+void tegra_hdmi_writel(struct tegra_dc_hdmi_data *hdmi,
+ unsigned long val, unsigned long reg)
+{
+ _tegra_hdmi_writel(hdmi, val, reg);
+}
+
+int tegra_hdmi_i2c(struct tegra_dc_hdmi_data *hdmi,
+ struct i2c_msg *msg, int msg_len)
+{
+ return tegra_edid_i2c(hdmi->edid, msg, msg_len);
+}
diff --git a/drivers/video/tegra/dc/hdmi.h b/drivers/video/tegra/dc/hdmi.h
index 5f0fc2e3f73e..a280cbdcb720 100644
--- a/drivers/video/tegra/dc/hdmi.h
+++ b/drivers/video/tegra/dc/hdmi.h
@@ -180,7 +180,6 @@ struct hdmi_audio_infoframe {
#define HDMI_AUDIO_CXT_HE_AAC_V2 0x2
#define HDMI_AUDIO_CXT_MPEG_SURROUND 0x3
-
/* all fields little endian */
struct hdmi_stereo_infoframe {
/* PB0 */
@@ -211,4 +210,12 @@ struct hdmi_stereo_infoframe {
#define HDMI_VENDOR_VERSION 0x01
+struct tegra_dc_hdmi_data;
+
+unsigned long tegra_hdmi_readl(struct tegra_dc_hdmi_data *hdmi,
+ unsigned long reg);
+void tegra_hdmi_writel(struct tegra_dc_hdmi_data *hdmi,
+ unsigned long val, unsigned long reg);
+int tegra_hdmi_i2c(struct tegra_dc_hdmi_data *hdmi,
+ struct i2c_msg *msg, int msg_len);
#endif
diff --git a/drivers/video/tegra/dc/hdmi_reg.h b/drivers/video/tegra/dc/hdmi_reg.h
index 67d2b23a3d81..9080fd79ecaa 100644
--- a/drivers/video/tegra/dc/hdmi_reg.h
+++ b/drivers/video/tegra/dc/hdmi_reg.h
@@ -57,16 +57,29 @@
#define HDMI_NV_PDISP_RG_HDCP_AKSV_MSB 0x08
#define HDMI_NV_PDISP_RG_HDCP_AKSV_LSB 0x09
#define HDMI_NV_PDISP_RG_HDCP_BKSV_MSB 0x0a
+#define REPEATER (1 << 31)
#define HDMI_NV_PDISP_RG_HDCP_BKSV_LSB 0x0b
#define HDMI_NV_PDISP_RG_HDCP_CKSV_MSB 0x0c
#define HDMI_NV_PDISP_RG_HDCP_CKSV_LSB 0x0d
#define HDMI_NV_PDISP_RG_HDCP_DKSV_MSB 0x0e
#define HDMI_NV_PDISP_RG_HDCP_DKSV_LSB 0x0f
#define HDMI_NV_PDISP_RG_HDCP_CTRL 0x10
+#define HDCP_RUN_YES (1 << 0)
+#define CRYPT_ENABLED (1 << 1)
+#define ONEONE_ENABLED (1 << 3)
+#define AN_VALID (1 << 8)
+#define R0_VALID (1 << 9)
+#define SPRIME_VALID (1 << 10)
+#define MPRIME_VALID (1 << 11)
+#define SROM_ERR (1 << 13)
#define HDMI_NV_PDISP_RG_HDCP_CMODE 0x11
+#define TMDS0_LINK0 (1 << 4)
+#define READ_S (1 << 0)
+#define READ_M (2 << 0)
#define HDMI_NV_PDISP_RG_HDCP_MPRIME_MSB 0x12
#define HDMI_NV_PDISP_RG_HDCP_MPRIME_LSB 0x13
#define HDMI_NV_PDISP_RG_HDCP_SPRIME_MSB 0x14
+#define STATUS_CS (1 << 6)
#define HDMI_NV_PDISP_RG_HDCP_SPRIME_LSB2 0x15
#define HDMI_NV_PDISP_RG_HDCP_SPRIME_LSB1 0x16
#define HDMI_NV_PDISP_RG_HDCP_RI 0x17
@@ -417,6 +430,9 @@
#define PE_CURRENT3(x) (((x) & 0xf) << 24)
#define HDMI_NV_PDISP_KEY_CTRL 0x9a
+#define LOCAL_KEYS (1 << 1)
+#define PKEY_REQUEST_RELOAD_TRIGGER (1 << 5)
+#define PKEY_LOADED (1 << 6)
#define HDMI_NV_PDISP_KEY_DEBUG0 0x9b
#define HDMI_NV_PDISP_KEY_DEBUG1 0x9c
#define HDMI_NV_PDISP_KEY_DEBUG2 0x9d
diff --git a/drivers/video/tegra/dc/nvhdcp.c b/drivers/video/tegra/dc/nvhdcp.c
new file mode 100644
index 000000000000..f6ca0b2238c7
--- /dev/null
+++ b/drivers/video/tegra/dc/nvhdcp.c
@@ -0,0 +1,1057 @@
+/*
+ * drivers/video/tegra/dc/nvhdcp.c
+ *
+ * Copyright (c) 2010-2011, NVIDIA Corporation.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/miscdevice.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <asm/atomic.h>
+
+#include <mach/dc.h>
+#include <mach/nvhost.h>
+#include <mach/kfuse.h>
+
+#include <video/nvhdcp.h>
+
+#include "dc_reg.h"
+#include "dc_priv.h"
+#include "hdmi_reg.h"
+#include "hdmi.h"
+
+#define BCAPS_REPEATER (1 << 6)
+#define BCAPS_READY (1 << 5)
+#define BCAPS_11 (1 << 1) /* used for both Bcaps and Ainfo */
+
+// TODO remove this
+#define nvhdcp_debug(...) pr_err("nvhdcp: " __VA_ARGS__)
+#define nvhdcp_err(...) pr_err("nvhdcp: Error: " __VA_ARGS__)
+#define nvhdcp_info(...) pr_info("nvhdcp: " __VA_ARGS__)
+
+/* for nvhdcp.state */
+enum {
+ STATE_OFF,
+ STATE_UNAUTHENTICATED,
+ STATE_LINK_VERIFY,
+ STATE_RENEGOTIATE,
+};
+
+struct tegra_nvhdcp {
+ struct work_struct work;
+ struct tegra_dc_hdmi_data *hdmi;
+ struct workqueue_struct *downstream_wq;
+ struct mutex lock;
+ struct miscdevice miscdev;
+ char name[12];
+ unsigned id;
+ atomic_t plugged; /* true if hotplug detected */
+ atomic_t policy; /* set policy */
+ atomic_t state; /* STATE_xxx */
+
+ u64 a_n;
+ u64 c_n;
+ u64 a_ksv;
+ u64 b_ksv;
+ u64 c_ksv;
+ u64 d_ksv;
+ u64 m_prime;
+ u32 b_status;
+};
+
+#define TEGRA_NVHDCP_NUM_AP 1
+static struct tegra_nvhdcp *nvhdcp_dev[TEGRA_NVHDCP_NUM_AP];
+static int nvhdcp_ap;
+
+static int nvhdcp_i2c_read(struct tegra_dc_hdmi_data *hdmi, u8 reg, size_t len, u8 *data)
+{
+ int status;
+ struct i2c_msg msg[] = {
+ {
+ .addr = 0x74 >> 1, /* primary link */
+ .flags = 0,
+ .len = 1,
+ .buf = &reg,
+ },
+ {
+ .addr = 0x74 >> 1, /* primary link */
+ .flags = I2C_M_RD,
+ .len = len,
+ .buf = data,
+ },
+ };
+
+ status = tegra_hdmi_i2c(hdmi, msg, ARRAY_SIZE(msg));
+
+ if (status < 0) {
+ nvhdcp_err("i2c xfer error %d\n", status);
+ return status;
+ }
+
+ return 0;
+}
+
+static int nvhdcp_i2c_write(struct tegra_dc_hdmi_data *hdmi, u8 reg, size_t len, const u8 *data)
+{
+ int status;
+ u8 buf[len + 1];
+ struct i2c_msg msg[] = {
+ {
+ .addr = 0x74 >> 1, /* primary link */
+ .flags = 0,
+ .len = len + 1,
+ .buf = buf,
+ },
+ };
+
+ buf[0] = reg;
+ memcpy(buf + 1, data, len);
+
+ status = tegra_hdmi_i2c(hdmi, msg, ARRAY_SIZE(msg));
+
+ if (status < 0) {
+ nvhdcp_err("i2c xfer error %d\n", status);
+ return status;
+ }
+
+ return 0;
+}
+
+static int nvhdcp_i2c_read8(struct tegra_dc_hdmi_data *hdmi, u8 reg, u8 *val)
+{
+ return nvhdcp_i2c_read(hdmi, reg, 1, val);
+}
+
+static int nvhdcp_i2c_write8(struct tegra_dc_hdmi_data *hdmi, u8 reg, u8 val)
+{
+ return nvhdcp_i2c_write(hdmi, reg, 1, &val);
+}
+
+static int nvhdcp_i2c_read16(struct tegra_dc_hdmi_data *hdmi, u8 reg, u16 *val)
+{
+ u8 buf[2];
+ int e;
+
+ e = nvhdcp_i2c_read(hdmi, reg, sizeof buf, buf);
+ if (e)
+ return e;
+
+ if (val)
+ *val = buf[0] | (u16)buf[1] << 8;
+
+ return 0;
+}
+
+static int nvhdcp_i2c_read40(struct tegra_dc_hdmi_data *hdmi, u8 reg, u64 *val)
+{
+ u8 buf[5];
+ int e, i;
+ u64 n;
+
+ e = nvhdcp_i2c_read(hdmi, reg, sizeof buf, buf);
+ if (e)
+ return e;
+
+ for(i = 0, n = 0; i < 5; i++ ) {
+ n <<= 8;
+ n |= buf[4 - i];
+ }
+
+ if (val)
+ *val = n;
+
+ return 0;
+}
+
+static int nvhdcp_i2c_write40(struct tegra_dc_hdmi_data *hdmi, u8 reg, u64 val)
+{
+ char buf[5];
+ int i;
+ for(i = 0; i < 5; i++ ) {
+ buf[i] = val;
+ val >>= 8;
+ }
+ return nvhdcp_i2c_write(hdmi, reg, sizeof buf, buf);
+}
+
+static int nvhdcp_i2c_write64(struct tegra_dc_hdmi_data *hdmi, u8 reg, u64 val)
+{
+ char buf[8];
+ int i;
+ for(i = 0; i < 8; i++ ) {
+ buf[i] = val;
+ val >>= 8;
+ }
+ return nvhdcp_i2c_write(hdmi, reg, sizeof buf, buf);
+}
+
+
+/* 64-bit link encryption session random number */
+static u64 get_an(struct tegra_dc_hdmi_data *hdmi)
+{
+ u64 r;
+ r = (u64)tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_AN_MSB) << 32;
+ r |= tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_AN_LSB);
+ return r;
+}
+
+/* 64-bit upstream exchange random number */
+static void set_cn(struct tegra_dc_hdmi_data *hdmi, u64 c_n)
+{
+ tegra_hdmi_writel(hdmi, (u32)c_n, HDMI_NV_PDISP_RG_HDCP_CN_LSB);
+ tegra_hdmi_writel(hdmi, c_n >> 32, HDMI_NV_PDISP_RG_HDCP_CN_MSB);
+}
+
+
+/* 40-bit transmitter's key selection vector */
+static inline u64 get_aksv(struct tegra_dc_hdmi_data *hdmi)
+{
+ u64 r;
+ r = (u64)tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_AKSV_MSB) << 32;
+ r |= tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_AKSV_LSB);
+ return r;
+}
+
+/* 40-bit receiver's key selection vector */
+static inline u64 get_bksv(struct tegra_dc_hdmi_data *hdmi)
+{
+ u64 r;
+ r = (u64)tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_BKSV_MSB) << 32;
+ r |= tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_BKSV_LSB);
+ return r;
+}
+
+/* 40-bit software's key selection vector */
+static void set_cksv(struct tegra_dc_hdmi_data *hdmi, u64 c_ksv)
+{
+ tegra_hdmi_writel(hdmi, (u32)c_ksv, HDMI_NV_PDISP_RG_HDCP_CKSV_LSB);
+ tegra_hdmi_writel(hdmi, c_ksv >> 32, HDMI_NV_PDISP_RG_HDCP_CKSV_MSB);
+}
+
+/* 40-bit connection state */
+static inline u64 get_cs(struct tegra_dc_hdmi_data *hdmi)
+{
+ u64 r;
+ r = (u64)tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_CS_MSB) << 32;
+ r |= tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_CS_LSB);
+ return r;
+}
+
+/* 40-bit upstream key selection vector */
+static inline u64 get_dksv(struct tegra_dc_hdmi_data *hdmi)
+{
+ u64 r;
+ r = (u64)tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_DKSV_MSB) << 32;
+ r |= tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_DKSV_LSB);
+ return r;
+}
+
+/* 64-bit encrypted M0 value */
+static inline u64 get_mprime(struct tegra_dc_hdmi_data *hdmi)
+{
+ u64 r;
+ r = (u64)tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_MPRIME_MSB) << 32;
+ r |= tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_MPRIME_LSB);
+ return r;
+}
+
+/* 56-bit S' value + some status bits in the upper 8-bits
+ * STATUS_CMODE_IDX 31:28
+ * STATUS_UNPROTECTED 27
+ * STATUS_EXTPNL 26
+ * STATUS_RPTR 25
+ * STATUS_ENCRYPTING 24
+ */
+static inline u64 get_sprime(struct tegra_dc_hdmi_data *hdmi)
+{
+ u64 r;
+ r = (u64)tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_SPRIME_LSB2) << 32;
+ r |= tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_SPRIME_LSB1);
+ return r;
+}
+
+static inline u16 get_transmitter_ri(struct tegra_dc_hdmi_data *hdmi)
+{
+ return tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_RI);
+}
+
+static inline int get_receiver_ri(struct tegra_dc_hdmi_data *hdmi, u16 *r)
+{
+ // TODO: implement short Ri read format
+ return nvhdcp_i2c_read16(hdmi, 0x8, r); /* long read */
+}
+
+static int get_bcaps(struct tegra_dc_hdmi_data *hdmi, u8 *b_caps)
+{
+ int e, retries = 3;
+ do {
+ e = nvhdcp_i2c_read8(hdmi, 0x40, b_caps);
+ if (!e)
+ return 0;
+ msleep(100);
+ } while (--retries);
+
+ return -EIO;
+}
+
+/* set or clear RUN_YES */
+static void hdcp_ctrl_run(struct tegra_dc_hdmi_data *hdmi, bool v)
+{
+ u32 ctrl;
+ ctrl = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_CTRL);
+
+ if (v)
+ ctrl |= HDCP_RUN_YES;
+ else
+ ctrl = 0;
+
+ tegra_hdmi_writel(hdmi, ctrl, HDMI_NV_PDISP_RG_HDCP_CTRL);
+}
+
+/* wait for any bits in mask to be set in HDMI_NV_PDISP_RG_HDCP_CTRL
+ * sleeps up to 120mS */
+static int wait_hdcp_ctrl(struct tegra_dc_hdmi_data *hdmi, u32 mask, u32 *v)
+{
+ int retries = 12;
+ u32 ctrl;
+
+ do {
+ ctrl = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_CTRL);
+ if ((ctrl | (mask))) {
+ if (v)
+ *v = ctrl;
+ break;
+ }
+ msleep(10);
+ } while (--retries);
+ if (!retries) {
+ nvhdcp_err("ctrl read timeout (mask=0x%x)\n", mask);
+ return -EIO;
+ }
+ return 0;
+}
+
+/* wait for any bits in mask to be set in HDMI_NV_PDISP_KEY_CTRL
+ * waits up to 100mS */
+static int wait_key_ctrl(struct tegra_dc_hdmi_data *hdmi, u32 mask)
+{
+ int retries = 100;
+ u32 ctrl;
+
+ do {
+ ctrl = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_KEY_CTRL);
+ if ((ctrl | (mask)))
+ break;
+ msleep(1);
+ } while (--retries);
+ if (!retries) {
+ nvhdcp_err("key ctrl read timeout (mask=0x%x)\n", mask);
+ return -EIO;
+ }
+ return 0;
+}
+
+/* check that key selection vector is well formed.
+ * NOTE: this function assumes KSV has already been checked against
+ * revocation list.
+ */
+static int verify_ksv(u64 k)
+{
+ unsigned i;
+
+ /* count set bits, must be exactly 20 set to be valid */
+ for(i = 0; k; i++)
+ k ^= k & -k;
+
+ return (i != 20) ? -EINVAL : 0;
+}
+
+/* get Status and Kprime signature - READ_S on TMDS0_LINK0 only */
+static int get_s_prime(struct tegra_nvhdcp *nvhdcp, struct tegra_nvhdcp_packet *pkt)
+{
+ struct tegra_dc_hdmi_data *hdmi = nvhdcp->hdmi;
+ u32 sp_msb, sp_lsb1, sp_lsb2;
+ int e;
+
+ pkt->packet_results = TEGRA_NVHDCP_RESULT_UNSUCCESSFUL;
+
+ /* we will be taking c_n, c_ksv as input */
+ if (!(pkt->value_flags & TEGRA_NVHDCP_FLAG_CN)
+ || !(pkt->value_flags & TEGRA_NVHDCP_FLAG_CKSV)) {
+ nvhdcp_err("missing value_flags (0x%x)\n", pkt->value_flags);
+ return -EINVAL;
+ }
+
+ pkt->value_flags = 0;
+
+ pkt->a_ksv = nvhdcp->a_ksv;
+ pkt->a_n = nvhdcp->a_n;
+ pkt->value_flags = TEGRA_NVHDCP_FLAG_AKSV | TEGRA_NVHDCP_FLAG_AN;
+
+ nvhdcp_debug("%s():cn %llx cksv %llx\n", __func__, pkt->c_n, pkt->c_ksv);
+
+ set_cn(hdmi, pkt->c_n);
+
+ tegra_hdmi_writel(hdmi, TMDS0_LINK0 | READ_S,
+ HDMI_NV_PDISP_RG_HDCP_CMODE);
+
+ set_cksv(hdmi, pkt->c_ksv);
+
+ e = wait_hdcp_ctrl(hdmi, SPRIME_VALID, NULL);
+ if (e) {
+ nvhdcp_err("Sprime read timeout\n");
+ pkt->packet_results = TEGRA_NVHDCP_RESULT_UNSUCCESSFUL;
+ return -EIO;
+ }
+
+ msleep(100);
+
+ /* read 56-bit Sprime plus 16 status bits */
+ sp_msb = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_SPRIME_MSB);
+ sp_lsb1 = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_SPRIME_LSB1);
+ sp_lsb2 = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_SPRIME_LSB2);
+
+ /* top 8 bits of LSB2 and bottom 8 bits of MSB hold status bits. */
+ pkt->hdcp_status = ( sp_msb << 8 ) | ( sp_lsb2 >> 24);
+ pkt->value_flags |= TEGRA_NVHDCP_FLAG_S;
+
+ /* 56-bit Kprime */
+ pkt->k_prime = ((u64)(sp_lsb2 & 0xffffff) << 32) | sp_lsb1;
+ pkt->value_flags |= TEGRA_NVHDCP_FLAG_KP;
+
+ /* is connection state supported? */
+ if (sp_msb & STATUS_CS) {
+ pkt->cs = get_cs(hdmi);
+ pkt->value_flags |= TEGRA_NVHDCP_FLAG_CS;
+ }
+
+ /* load Dksv */
+ pkt->d_ksv = get_dksv(hdmi);
+ if (verify_ksv(pkt->d_ksv)) {
+ nvhdcp_err("Dksv invalid!\n");
+ pkt->packet_results = TEGRA_NVHDCP_RESULT_UNSUCCESSFUL;
+ return -EIO; /* treat bad Dksv as I/O error */
+ }
+ pkt->value_flags |= TEGRA_NVHDCP_FLAG_DKSV;
+
+ /* copy current Bksv */
+ pkt->b_ksv = nvhdcp->b_ksv;
+ pkt->value_flags |= TEGRA_NVHDCP_FLAG_BKSV;
+
+ pkt->packet_results = TEGRA_NVHDCP_RESULT_SUCCESS;
+ return 0;
+}
+
+/* get M prime - READ_M on TMDS0_LINK0 only */
+static inline int get_m_prime(struct tegra_nvhdcp *nvhdcp, struct tegra_nvhdcp_packet *pkt)
+{
+ struct tegra_dc_hdmi_data *hdmi = nvhdcp->hdmi;
+ int e;
+
+ pkt->packet_results = TEGRA_NVHDCP_RESULT_UNSUCCESSFUL;
+
+ /* if connection isn't authenticated ... */
+ if (atomic_read(&nvhdcp->state) == STATE_UNAUTHENTICATED) {
+ memset(pkt, 0, sizeof *pkt);
+ pkt->packet_results = TEGRA_NVHDCP_RESULT_LINK_FAILED;
+ return 0;
+ }
+
+ pkt->a_ksv = nvhdcp->a_ksv;
+ pkt->a_n = nvhdcp->a_n;
+ pkt->value_flags = TEGRA_NVHDCP_FLAG_AKSV | TEGRA_NVHDCP_FLAG_AN;
+
+ set_cn(hdmi, pkt->c_n);
+
+ // TODO: original code used TMDS0_LINK1, weird
+ tegra_hdmi_writel(hdmi, TMDS0_LINK0 | READ_M,
+ HDMI_NV_PDISP_RG_HDCP_CMODE);
+
+ /* Cksv write triggers Mprime update */
+ set_cksv(hdmi, pkt->c_ksv);
+
+ e = wait_hdcp_ctrl(hdmi, MPRIME_VALID, NULL);
+ if (e) {
+ nvhdcp_err("Mprime read timeout\n");
+ return -EIO;
+ }
+
+ /* load Mprime */
+ pkt->m_prime = (u64)tegra_hdmi_readl(hdmi,
+ HDMI_NV_PDISP_RG_HDCP_MPRIME_MSB) << 32;
+ pkt->m_prime |= tegra_hdmi_readl(hdmi,
+ HDMI_NV_PDISP_RG_HDCP_MPRIME_LSB);
+ pkt->value_flags |= TEGRA_NVHDCP_FLAG_MP;
+
+ /* load Dksv */
+ pkt->d_ksv = get_dksv(hdmi);
+ if (verify_ksv(pkt->d_ksv)) {
+ nvhdcp_err("Dksv invalid!\n");
+ return -EIO; /* treat bad Dksv as I/O error */
+ }
+ pkt->value_flags |= TEGRA_NVHDCP_FLAG_DKSV;
+
+ pkt->packet_results = TEGRA_NVHDCP_RESULT_SUCCESS;
+ return 0;
+}
+
+static int load_kfuse(struct tegra_dc_hdmi_data *hdmi, bool repeater)
+{
+ unsigned buf[KFUSE_DATA_SZ / 4];
+ int e, i;
+ u32 ctrl;
+ u32 tmp;
+ int retries;
+
+ /* copy load kfuse into buffer - only needed for early Tegra parts */
+ e = tegra_kfuse_read(buf, sizeof buf);
+ if (e) {
+ nvhdcp_err("Kfuse read failure\n");
+ return e;
+ }
+
+ /* write the kfuse to HDMI SRAM */
+ if (repeater) {
+ tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_RG_HDCP_BKSV_LSB);
+ tegra_hdmi_writel(hdmi, REPEATER,
+ HDMI_NV_PDISP_RG_HDCP_BKSV_MSB);
+ }
+
+ tegra_hdmi_writel(hdmi, 1, HDMI_NV_PDISP_KEY_CTRL); /* LOAD_KEYS */
+
+ /* issue a reload */
+ ctrl = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_KEY_CTRL);
+ tegra_hdmi_writel(hdmi, ctrl | PKEY_REQUEST_RELOAD_TRIGGER
+ | LOCAL_KEYS , HDMI_NV_PDISP_KEY_CTRL);
+
+ e = wait_key_ctrl(hdmi, PKEY_LOADED);
+ if (e) {
+ nvhdcp_err("key reload timeout\n");
+ return -EIO;
+ }
+
+ tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_KEY_SKEY_INDEX);
+
+ /* wait for SRAM to be cleared */
+ retries = 5;
+ do {
+ tmp = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_KEY_DEBUG0);
+ if ((tmp & 1) == 0) break;
+ mdelay(1);
+ } while (--retries);
+ if (!retries) {
+ nvhdcp_err("key SRAM clear timeout\n");
+ return -EIO;
+ }
+
+ for (i = 0; i < KFUSE_DATA_SZ / 4; i += 4) {
+
+ /* load 128-bits*/
+ tegra_hdmi_writel(hdmi, buf[i], HDMI_NV_PDISP_KEY_HDCP_KEY_0);
+ tegra_hdmi_writel(hdmi, buf[i+1], HDMI_NV_PDISP_KEY_HDCP_KEY_1);
+ tegra_hdmi_writel(hdmi, buf[i+2], HDMI_NV_PDISP_KEY_HDCP_KEY_2);
+ tegra_hdmi_writel(hdmi, buf[i+3], HDMI_NV_PDISP_KEY_HDCP_KEY_3);
+
+ /* trigger LOAD_HDCP_KEY */
+ tegra_hdmi_writel(hdmi, 0x100, HDMI_NV_PDISP_KEY_HDCP_KEY_TRIG);
+
+ tmp = 0x11; /* LOCAL_KEYS | WRITE16 */
+ if (i)
+ tmp |= 0x2; /* AUTOINC */
+ tegra_hdmi_writel(hdmi, tmp, HDMI_NV_PDISP_KEY_CTRL);
+
+ /* wait for WRITE16 to complete */
+ e = wait_key_ctrl(hdmi, 0x10); /* WRITE16 */
+ if (e) {
+ nvhdcp_err("key write timeout\n");
+ return -EIO;
+ }
+ }
+
+ return 0;
+}
+
+static int verify_link(struct tegra_nvhdcp *nvhdcp, bool wait_ri)
+{
+ struct tegra_dc_hdmi_data *hdmi = nvhdcp->hdmi;
+ int retries = 3;
+ u16 old, rx, tx;
+ int e;
+
+ old = 0;
+ rx = 0;
+ tx = 0;
+ /* retry 3 times to deal with I2C link issues */
+ do {
+ if (wait_ri)
+ old = get_transmitter_ri(hdmi);
+
+ e = get_receiver_ri(hdmi, &rx);
+ if (e)
+ continue;
+
+ if (!rx) {
+ nvhdcp_err("Ri is 0!\n");
+ return -EINVAL;
+ }
+
+ tx = get_transmitter_ri(hdmi);
+
+ } while (wait_ri && --retries && old != tx);
+
+ nvhdcp_debug("%s():rx(0x%04x) == tx(0x%04x)\n", __func__, rx, tx);
+
+ if (!atomic_read(&nvhdcp->plugged)) {
+ nvhdcp_err("aborting verify links - lost hdmi connection\n");
+ return -EIO;
+ }
+
+ if (rx != tx)
+ return -EINVAL;
+
+ return 0;
+}
+
+static void nvhdcp_downstream_worker(struct work_struct *work)
+{
+ struct tegra_nvhdcp *nvhdcp =
+ container_of(work, struct tegra_nvhdcp, work);
+ struct tegra_dc_hdmi_data *hdmi = nvhdcp->hdmi;
+ int e;
+ u8 b_caps;
+ u32 tmp;
+ u32 res;
+ int st;
+
+ nvhdcp_debug("%s():started thread %s\n", __func__, nvhdcp->name);
+
+ st = atomic_read(&nvhdcp->state);
+ if (st == STATE_OFF) {
+ nvhdcp_err("nvhdcp failure - giving up\n");
+ return;
+ }
+
+ atomic_set(&nvhdcp->state, STATE_UNAUTHENTICATED);
+
+ /* check plug state to terminate early in case flush_workqueue() */
+ if (!atomic_read(&nvhdcp->plugged)) {
+ nvhdcp_err("worker started while unplugged!\n");
+ goto lost_hdmi;
+ }
+
+ nvhdcp->a_ksv = 0;
+ nvhdcp->b_ksv = 0;
+ nvhdcp->a_n = 0;
+
+ nvhdcp_debug("%s():hpd=%d\n", __func__, atomic_read(&nvhdcp->plugged));
+
+ e = get_bcaps(hdmi, &b_caps);
+ if (e) {
+ nvhdcp_err("Bcaps read failure\n");
+ goto failure;
+ }
+
+ nvhdcp_debug("read Bcaps = 0x%02x\n", b_caps);
+
+ nvhdcp_debug("kfuse loading ...\n");
+
+ e = load_kfuse(hdmi, (b_caps & BCAPS_REPEATER) != 0);
+ if (e) {
+ nvhdcp_err("kfuse could not be loaded\n");
+ goto failure;
+ }
+
+ hdcp_ctrl_run(hdmi, 1);
+
+ nvhdcp_debug("wait AN_VALID ...\n");
+
+ /* wait for hardware to generate HDCP values */
+ e = wait_hdcp_ctrl(hdmi, AN_VALID | SROM_ERR, &res);
+ if (e) {
+ nvhdcp_err("An key generation timeout\n");
+ goto failure;
+ }
+ if (res & SROM_ERR) {
+ nvhdcp_err("SROM error\n");
+ goto failure;
+ }
+
+ nvhdcp->a_ksv = get_aksv(hdmi);
+ nvhdcp->a_n = get_an(hdmi);
+ nvhdcp_debug("Aksv is 0x%016llx\n", nvhdcp->a_ksv);
+ nvhdcp_debug("An is 0x%016llx\n", nvhdcp->a_n);
+ if (verify_ksv(nvhdcp->a_ksv)) {
+ nvhdcp_err("Aksv verify failure!\n");
+ goto failure;
+ }
+
+ /* write Ainfo to receiver - set 1.1 only if b_caps supports it */
+ e = nvhdcp_i2c_write8(hdmi, 0x15, b_caps & BCAPS_11);
+ if (e) {
+ nvhdcp_err("Ainfo write failure\n");
+ goto failure;
+ }
+
+ /* write An to receiver */
+ e = nvhdcp_i2c_write64(hdmi, 0x18, nvhdcp->a_n);
+ if (e) {
+ nvhdcp_err("An write failure\n");
+ goto failure;
+ }
+
+ nvhdcp_debug("wrote An = 0x%016llx\n", nvhdcp->a_n);
+
+ /* write Aksv to receiver - triggers auth sequence */
+ e = nvhdcp_i2c_write40(hdmi, 0x10, nvhdcp->a_ksv);
+ if (e) {
+ nvhdcp_err("Aksv write failure\n");
+ goto failure;
+ }
+
+ nvhdcp_debug("wrote Aksv = 0x%010llx\n", nvhdcp->a_ksv);
+
+ /* bail out if unplugged in the middle of negotiation */
+ if (!atomic_read(&nvhdcp->plugged))
+ goto lost_hdmi;
+
+ /* get Bksv from reciever */
+ e = nvhdcp_i2c_read40(hdmi, 0x00, &nvhdcp->b_ksv);
+ if (e) {
+ nvhdcp_err("Bksv read failure\n");
+ goto failure;
+ }
+ nvhdcp_debug("Bksv is 0x%016llx\n", nvhdcp->b_ksv);
+ if (verify_ksv(nvhdcp->b_ksv)) {
+ nvhdcp_err("Bksv verify failure!\n");
+ goto failure;
+ }
+
+ nvhdcp_debug("read Bksv = 0x%010llx from device\n", nvhdcp->b_ksv);
+
+ /* LSB must be written first */
+ tmp = (u32)nvhdcp->b_ksv;
+ tegra_hdmi_writel(hdmi, tmp, HDMI_NV_PDISP_RG_HDCP_BKSV_LSB);
+ // TODO: mix in repeater flags
+ tmp = (u32)(nvhdcp->b_ksv >> 32);
+ tegra_hdmi_writel(hdmi, tmp, HDMI_NV_PDISP_RG_HDCP_BKSV_MSB);
+
+ nvhdcp_debug("load Bksv into controller\n");
+
+ e = wait_hdcp_ctrl(hdmi, R0_VALID, NULL);
+ if (e) {
+ nvhdcp_err("R0 read failure!\n");
+ goto failure;
+ }
+
+ nvhdcp_debug("R0 valid\n");
+
+ /* can't read R0' within 100ms of writing Aksv */
+ msleep(100);
+
+ nvhdcp_debug("verifying links ...\n");
+
+ e = verify_link(nvhdcp, false);
+ if (e) {
+ nvhdcp_err("link verification failed err %d\n", e);
+ goto failure;
+ }
+
+ tmp = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_CTRL);
+ tmp |= CRYPT_ENABLED;
+ if (b_caps & BCAPS_11) /* HDCP 1.1 ? */
+ tmp |= ONEONE_ENABLED;
+ tegra_hdmi_writel(hdmi, tmp, HDMI_NV_PDISP_RG_HDCP_CTRL);
+
+ nvhdcp_debug("CRYPT enabled\n");
+
+ /* if repeater then get repeater info */
+ if (b_caps & BCAPS_REPEATER) {
+ u64 v_prime;
+ u16 b_status;
+
+ nvhdcp_debug("repeater stuff ... \n"); // TODO: remove comments
+
+ /* Vprime */
+ e = nvhdcp_i2c_read40(hdmi, 0x20, &v_prime);
+ if (e) {
+ nvhdcp_err("V' read failure!\n");
+ goto failure;
+ }
+ // TODO: do somthing with Vprime
+
+ // TODO: get Bstatus
+ e = nvhdcp_i2c_read16(hdmi, 0x41, &b_status);
+ if (e) {
+ nvhdcp_err("Bstatus read failure!\n");
+ goto failure;
+ }
+
+ nvhdcp_debug("Bstatus: device_count=%d\n", b_status & 0x7f);
+
+ // TODO: read KSV fifo(0x43) for DEVICE_COUNT devices
+
+ nvhdcp_err("not implemented - repeater info\n");
+ goto failure;
+ }
+
+ atomic_set(&nvhdcp->state, STATE_LINK_VERIFY);
+ nvhdcp_info("link verified!\n");
+
+ while (atomic_read(&nvhdcp->state) == STATE_LINK_VERIFY) {
+ if (!atomic_read(&nvhdcp->plugged))
+ goto lost_hdmi;
+ e = verify_link(nvhdcp, true);
+ if (e) {
+ nvhdcp_err("link verification failed err %d\n", e);
+ goto failure;
+ }
+ msleep(1500);
+ }
+
+ return;
+lost_hdmi:
+ nvhdcp_err("lost hdmi connection\n");
+failure:
+ nvhdcp_err("nvhdcp failure - giving up\n");
+ atomic_set(&nvhdcp->state, STATE_UNAUTHENTICATED);
+ /* TODO: disable hdcp
+ hdcp_ctrl_run(nvhdcp->hdmi, 0);
+ */
+ return;
+}
+
+void tegra_nvhdcp_set_plug(struct tegra_nvhdcp *nvhdcp, bool hpd)
+{
+ nvhdcp_info("hdmi hotplug detected (hpd = %d)\n", hpd);
+
+ atomic_set(&nvhdcp->plugged, hpd);
+
+ if (hpd) {
+ queue_work(nvhdcp->downstream_wq, &nvhdcp->work);
+ } else {
+ flush_workqueue(nvhdcp->downstream_wq);
+ }
+}
+
+static int tegra_nvhdcp_on(struct tegra_nvhdcp *nvhdcp)
+{
+ atomic_set(&nvhdcp->state, STATE_UNAUTHENTICATED);
+ if (atomic_read(&nvhdcp->plugged))
+ queue_work(nvhdcp->downstream_wq, &nvhdcp->work);
+ return 0;
+}
+
+static int tegra_nvhdcp_off(struct tegra_nvhdcp *nvhdcp)
+{
+ atomic_set(&nvhdcp->state, STATE_OFF);
+ atomic_set(&nvhdcp->plugged, 0); /* force early termination */
+ flush_workqueue(nvhdcp->downstream_wq);
+ return 0;
+}
+
+int tegra_nvhdcp_set_policy(struct tegra_nvhdcp *nvhdcp, int pol)
+{
+ pol = !!pol;
+ nvhdcp_info("using \"%s\" policy.\n", pol ? "always on" : "on demand");
+ if (atomic_xchg(&nvhdcp->policy, pol) == TEGRA_NVHDCP_POLICY_ON_DEMAND
+ && pol == TEGRA_NVHDCP_POLICY_ALWAYS_ON) {
+ /* policy was off but now it is on, start working */
+ tegra_nvhdcp_on(nvhdcp);
+ }
+ return 0;
+}
+
+static int tegra_nvhdcp_renegotiate(struct tegra_nvhdcp *nvhdcp)
+{
+ atomic_set(&nvhdcp->state, STATE_RENEGOTIATE);
+ tegra_nvhdcp_on(nvhdcp);
+ return 0;
+}
+
+void tegra_nvhdcp_suspend(struct tegra_nvhdcp *nvhdcp)
+{
+ if (!nvhdcp) return;
+ tegra_nvhdcp_off(nvhdcp);
+}
+
+
+static ssize_t
+nvhdcp_dev_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
+{
+ struct tegra_nvhdcp *nvhdcp = filp->private_data;
+ u16 data[2];
+ int e;
+ size_t len;
+
+ if (!count)
+ return 0;
+
+ /* read Ri pairs as fast as possible into buffer */
+ for (len = 0; (len + sizeof(data)) < count; len += sizeof(data)) {
+ data[0] = get_transmitter_ri(nvhdcp->hdmi);
+ data[1] = 0;
+ e = get_receiver_ri(nvhdcp->hdmi, &data[1]);
+ if (e)
+ return e;
+
+ memcpy(buf + len, data, 2);
+ }
+ return len;
+}
+
+static long nvhdcp_dev_ioctl(struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ struct tegra_nvhdcp *nvhdcp = filp->private_data;
+ struct tegra_nvhdcp_packet *pkt;
+ int e = -ENOTTY;
+
+ switch (cmd) {
+ case TEGRAIO_NVHDCP_ON:
+ return tegra_nvhdcp_on(nvhdcp);
+
+ case TEGRAIO_NVHDCP_OFF:
+ return tegra_nvhdcp_off(nvhdcp);
+
+ case TEGRAIO_NVHDCP_SET_POLICY:
+ return tegra_nvhdcp_set_policy(nvhdcp, arg);
+
+ case TEGRAIO_NVHDCP_READ_M:
+ pkt = kmalloc(sizeof(*pkt), GFP_KERNEL);
+ if (!pkt)
+ return -ENOMEM;
+ if (copy_from_user(pkt, (void __user *)arg, sizeof(*pkt))) {
+ e = -EFAULT;
+ goto kfree_pkt;
+ }
+ e = get_m_prime(nvhdcp, pkt);
+ if (copy_to_user((void __user *)arg, pkt, sizeof(*pkt))) {
+ e = -EFAULT;
+ goto kfree_pkt;
+ }
+ kfree(pkt);
+ return e;
+
+ case TEGRAIO_NVHDCP_READ_S:
+ pkt = kmalloc(sizeof(*pkt), GFP_KERNEL);
+ if (!pkt)
+ return -ENOMEM;
+ if (copy_from_user(pkt, (void __user *)arg, sizeof(*pkt))) {
+ e = -EFAULT;
+ goto kfree_pkt;
+ }
+ e = get_s_prime(nvhdcp, pkt);
+ if (copy_to_user((void __user *)arg, pkt, sizeof(*pkt))) {
+ e = -EFAULT;
+ goto kfree_pkt;
+ }
+ kfree(pkt);
+ return e;
+
+ case TEGRAIO_NVHDCP_RENEGOTIATE:
+ e = tegra_nvhdcp_renegotiate(nvhdcp);
+ break;
+ }
+
+ return e;
+kfree_pkt:
+ kfree(pkt);
+ return e;
+}
+
+/* every open indexes a new AP link */
+static int nvhdcp_dev_open(struct inode *inode, struct file *filp)
+{
+ if (nvhdcp_ap >= TEGRA_NVHDCP_NUM_AP)
+ return -EMFILE;
+
+ if (!nvhdcp_dev[nvhdcp_ap])
+ return -ENODEV;
+
+ filp->private_data = nvhdcp_dev[nvhdcp_ap++];
+
+ return 0;
+}
+
+static int nvhdcp_dev_release(struct inode *inode, struct file *filp)
+{
+ filp->private_data = NULL;
+
+ --nvhdcp_ap;
+
+ // TODO: deactivate something maybe?
+ return 0;
+}
+
+static const struct file_operations nvhdcp_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = nvhdcp_dev_read,
+ .unlocked_ioctl = nvhdcp_dev_ioctl,
+ .open = nvhdcp_dev_open,
+ .release = nvhdcp_dev_release,
+};
+
+struct tegra_nvhdcp *tegra_nvhdcp_create(struct tegra_dc_hdmi_data *hdmi, int id)
+{
+ struct tegra_nvhdcp *nvhdcp;
+ int e;
+
+ nvhdcp = kzalloc(sizeof(*nvhdcp), GFP_KERNEL);
+ if (!nvhdcp)
+ return ERR_PTR(-ENOMEM);
+
+ nvhdcp->id = id;
+ snprintf(nvhdcp->name, sizeof(nvhdcp->name), "nvhdcp%u", id);
+ nvhdcp->hdmi = hdmi;
+ mutex_init(&nvhdcp->lock);
+
+ atomic_set(&nvhdcp->state, STATE_UNAUTHENTICATED);
+
+ nvhdcp->downstream_wq = create_singlethread_workqueue(nvhdcp->name);
+ INIT_WORK(&nvhdcp->work, nvhdcp_downstream_worker);
+
+ nvhdcp->miscdev.minor = MISC_DYNAMIC_MINOR;
+ nvhdcp->miscdev.name = nvhdcp->name;
+ nvhdcp->miscdev.fops = &nvhdcp_fops;
+
+ e = misc_register(&nvhdcp->miscdev);
+ if (e)
+ goto out1;
+
+ nvhdcp_debug("%s(): created misc device %s\n", __func__, nvhdcp->name);
+
+ nvhdcp_dev[0] = nvhdcp; /* we only support on AP right now */
+
+ return nvhdcp;
+out1:
+ destroy_workqueue(nvhdcp->downstream_wq);
+ kfree(nvhdcp);
+ nvhdcp_err("unable to create device.\n");
+ return ERR_PTR(e);
+}
+
+void tegra_nvhdcp_destroy(struct tegra_nvhdcp *nvhdcp)
+{
+ misc_deregister(&nvhdcp->miscdev);
+ atomic_set(&nvhdcp->plugged, 0); /* force early termination */
+ flush_workqueue(nvhdcp->downstream_wq);
+ destroy_workqueue(nvhdcp->downstream_wq);
+ kfree(nvhdcp);
+}
diff --git a/drivers/video/tegra/dc/nvhdcp.h b/drivers/video/tegra/dc/nvhdcp.h
new file mode 100644
index 000000000000..9c8fc00250b1
--- /dev/null
+++ b/drivers/video/tegra/dc/nvhdcp.h
@@ -0,0 +1,28 @@
+/*
+ * drivers/video/tegra/dc/nvhdcp.h
+ *
+ * Copyright (c) 2010-2011, NVIDIA Corporation.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#ifndef __DRIVERS_VIDEO_TEGRA_DC_NVHDCP_H
+#define __DRIVERS_VIDEO_TEGRA_DC_NVHDCP_H
+#include <video/nvhdcp.h>
+
+struct tegra_nvhdcp;
+void tegra_nvhdcp_set_plug(struct tegra_nvhdcp *nvhdcp, bool hpd);
+int tegra_nvhdcp_set_policy(struct tegra_nvhdcp *nvhdcp, int pol);
+void tegra_nvhdcp_suspend(struct tegra_nvhdcp *nvhdcp);
+struct tegra_nvhdcp *tegra_nvhdcp_create(struct tegra_dc_hdmi_data *hdmi, int id);
+void tegra_nvhdcp_destroy(struct tegra_nvhdcp *nvhdcp);
+
+#endif