diff options
author | Jon Mayo <jmayo@nvidia.com> | 2011-01-03 17:26:25 -0800 |
---|---|---|
committer | Yu-Huan Hsu <yhsu@nvidia.com> | 2011-01-05 13:21:32 -0800 |
commit | 4812e391e1e0fdae30b4140e77471ccf4ef6860c (patch) | |
tree | 5ab37ad1384b5387c7e50a91334fcf3f1506a7fb /drivers/video/tegra/dc | |
parent | 8e285a362156e2804931340c0c2a9f6d35f598cd (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/Makefile | 3 | ||||
-rw-r--r-- | drivers/video/tegra/dc/edid.c | 7 | ||||
-rw-r--r-- | drivers/video/tegra/dc/edid.h | 1 | ||||
-rw-r--r-- | drivers/video/tegra/dc/hdmi.c | 138 | ||||
-rw-r--r-- | drivers/video/tegra/dc/hdmi.h | 9 | ||||
-rw-r--r-- | drivers/video/tegra/dc/hdmi_reg.h | 16 | ||||
-rw-r--r-- | drivers/video/tegra/dc/nvhdcp.c | 1057 | ||||
-rw-r--r-- | drivers/video/tegra/dc/nvhdcp.h | 28 |
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 = ®, + }, + { + .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 |