diff options
author | Erik Gilling <konkers@android.com> | 2011-02-24 17:13:10 -0800 |
---|---|---|
committer | Erik Gilling <konkers@android.com> | 2011-02-24 17:13:10 -0800 |
commit | 56edb8f957edbfe32739a5c709bf8f587be52724 (patch) | |
tree | dde05e7b8c912cbc1f7d6e1d665c91a376eb3474 | |
parent | a5856ce00674aa34a38a55dd6960638182604d91 (diff) | |
parent | 1249163215ede23cb139a6da9f69e19a393e70f4 (diff) |
Merge branch linux-tegra-2.6.36 into android-tegra-2.6.36
Conflicts:
drivers/video/tegra/dc/hdmi.c
Change-Id: I10fd2dbcc07d7961dd75e10a2c4de926457c2912
-rw-r--r-- | arch/arm/include/asm/hardware/cache-l2x0.h | 1 | ||||
-rw-r--r-- | arch/arm/mach-tegra/Makefile | 2 | ||||
-rw-r--r-- | arch/arm/mach-tegra/apbio.c | 151 | ||||
-rw-r--r-- | arch/arm/mach-tegra/apbio.h | 20 | ||||
-rw-r--r-- | arch/arm/mach-tegra/common.c | 5 | ||||
-rw-r--r-- | arch/arm/mach-tegra/cortex-a9.S | 2 | ||||
-rw-r--r-- | arch/arm/mach-tegra/fuse.c | 135 | ||||
-rw-r--r-- | arch/arm/mach-tegra/fuse.h | 1 | ||||
-rw-r--r-- | arch/arm/mach-tegra/include/mach/dc.h | 10 | ||||
-rw-r--r-- | arch/arm/mach-tegra/include/mach/kfuse.h | 20 | ||||
-rw-r--r-- | arch/arm/mach-tegra/kfuse.c | 88 | ||||
-rw-r--r-- | arch/arm/mach-tegra/tegra2_clocks.c | 1 | ||||
-rw-r--r-- | drivers/video/tegra/dc/Makefile | 3 | ||||
-rw-r--r-- | drivers/video/tegra/dc/edid.c | 2 | ||||
-rw-r--r-- | drivers/video/tegra/dc/hdmi.c | 35 | ||||
-rw-r--r-- | drivers/video/tegra/dc/hdmi.h | 6 | ||||
-rw-r--r-- | drivers/video/tegra/dc/hdmi_reg.h | 18 | ||||
-rw-r--r-- | drivers/video/tegra/dc/nvhdcp.c | 1230 | ||||
-rw-r--r-- | drivers/video/tegra/dc/nvhdcp.h | 29 | ||||
-rw-r--r-- | include/video/nvhdcp.h | 91 |
20 files changed, 1716 insertions, 134 deletions
diff --git a/arch/arm/include/asm/hardware/cache-l2x0.h b/arch/arm/include/asm/hardware/cache-l2x0.h index 787c06ada555..d62847df3df5 100644 --- a/arch/arm/include/asm/hardware/cache-l2x0.h +++ b/arch/arm/include/asm/hardware/cache-l2x0.h @@ -54,6 +54,7 @@ #define L2X0_LINE_TAG 0xF30 #define L2X0_DEBUG_CTRL 0xF40 #define L2X0_PREFETCH_OFFSET 0xF60 +#define L2X0_PWR_CTRL 0xF80 #ifndef __ASSEMBLY__ extern void __init l2x0_init(void __iomem *base, __u32 aux_val, __u32 aux_mask); diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile index b2374b94d9ea..70ad2462cb17 100644 --- a/arch/arm/mach-tegra/Makefile +++ b/arch/arm/mach-tegra/Makefile @@ -1,4 +1,5 @@ obj-y += common.o +obj-y += apbio.o obj-y += io.o obj-y += irq.o legacy_irq.o obj-y += clock.o @@ -11,6 +12,7 @@ obj-y += delay.o obj-y += powergate.o obj-y += suspend.o obj-y += fuse.o +obj-y += kfuse.o obj-y += tegra_i2s_audio.o obj-y += tegra_spdif_audio.o obj-y += mc.o diff --git a/arch/arm/mach-tegra/apbio.c b/arch/arm/mach-tegra/apbio.c new file mode 100644 index 000000000000..d6e08c966e72 --- /dev/null +++ b/arch/arm/mach-tegra/apbio.c @@ -0,0 +1,151 @@ +/* + * arch/arm/mach-tegra/apbio.c + * + * Copyright (C) 2010 NVIDIA Corporation. + * Copyright (C) 2010 Google, Inc. + * + * 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/io.h> +#include <linux/dma-mapping.h> +#include <linux/spinlock.h> +#include <linux/completion.h> +#include <linux/sched.h> +#include <linux/mutex.h> + +#include <mach/dma.h> +#include <mach/iomap.h> + +#include "apbio.h" + +static DEFINE_MUTEX(tegra_apb_dma_lock); + +#ifdef CONFIG_TEGRA_SYSTEM_DMA +static struct tegra_dma_channel *tegra_apb_dma; +static u32 *tegra_apb_bb; +static dma_addr_t tegra_apb_bb_phys; +static DECLARE_COMPLETION(tegra_apb_wait); + +static void apb_dma_complete(struct tegra_dma_req *req) +{ + complete(&tegra_apb_wait); +} + +static inline u32 apb_readl(unsigned long offset) +{ + struct tegra_dma_req req; + int ret; + + if (!tegra_apb_dma) + return readl(IO_TO_VIRT(offset)); + + mutex_lock(&tegra_apb_dma_lock); + req.complete = apb_dma_complete; + req.to_memory = 1; + req.dest_addr = tegra_apb_bb_phys; + req.dest_bus_width = 32; + req.dest_wrap = 1; + req.source_addr = offset; + req.source_bus_width = 32; + req.source_wrap = 4; + req.req_sel = 0; + req.size = 4; + + INIT_COMPLETION(tegra_apb_wait); + + tegra_dma_enqueue_req(tegra_apb_dma, &req); + + ret = wait_for_completion_timeout(&tegra_apb_wait, + msecs_to_jiffies(50)); + + if (WARN(ret == 0, "apb read dma timed out")) + *(u32 *)tegra_apb_bb = 0; + + mutex_unlock(&tegra_apb_dma_lock); + return *((u32 *)tegra_apb_bb); +} + +static inline void apb_writel(u32 value, unsigned long offset) +{ + struct tegra_dma_req req; + int ret; + + if (!tegra_apb_dma) { + writel(value, IO_TO_VIRT(offset)); + return; + } + + mutex_lock(&tegra_apb_dma_lock); + *((u32 *)tegra_apb_bb) = value; + req.complete = apb_dma_complete; + req.to_memory = 0; + req.dest_addr = offset; + req.dest_wrap = 4; + req.dest_bus_width = 32; + req.source_addr = tegra_apb_bb_phys; + req.source_bus_width = 32; + req.source_wrap = 1; + req.req_sel = 0; + req.size = 4; + + INIT_COMPLETION(tegra_apb_wait); + + tegra_dma_enqueue_req(tegra_apb_dma, &req); + + ret = wait_for_completion_timeout(&tegra_apb_wait, + msecs_to_jiffies(50)); + + mutex_unlock(&tegra_apb_dma_lock); +} +#else +static inline u32 apb_readl(unsigned long offset) +{ + return readl(IO_TO_VIRT(offset)); +} + +static inline void apb_writel(u32 value, unsigned long offset) +{ + writel(value, IO_TO_VIRT(offset)); +} +#endif + +u32 tegra_apb_readl(unsigned long offset) +{ + return apb_readl(offset); +} + +void tegra_apb_writel(u32 value, unsigned long offset) +{ + apb_writel(value, offset); +} + +void tegra_init_apb_dma(void) +{ +#ifdef CONFIG_TEGRA_SYSTEM_DMA + tegra_apb_dma = tegra_dma_allocate_channel(TEGRA_DMA_MODE_ONESHOT | + TEGRA_DMA_SHARED); + if (!tegra_apb_dma) { + pr_err("%s: can not allocate dma channel\n", __func__); + return; + } + + tegra_apb_bb = dma_alloc_coherent(NULL, sizeof(u32), + &tegra_apb_bb_phys, GFP_KERNEL); + if (!tegra_apb_bb) { + pr_err("%s: can not allocate bounce buffer\n", __func__); + tegra_dma_free_channel(tegra_apb_dma); + tegra_apb_dma = NULL; + return; + } +#endif +} diff --git a/arch/arm/mach-tegra/apbio.h b/arch/arm/mach-tegra/apbio.h new file mode 100644 index 000000000000..a8b8250c891a --- /dev/null +++ b/arch/arm/mach-tegra/apbio.h @@ -0,0 +1,20 @@ +/* + * arch/arm/mach-tegra/apbio.h + * + * Copyright (C) 2010 NVIDIA Corporation. + * Copyright (C) 2010 Google, Inc. + * + * 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. + * + */ + +u32 tegra_apb_readl(unsigned long offset); +void tegra_apb_writel(u32 value, unsigned long offset); +void tegra_init_apb_dma(void); diff --git a/arch/arm/mach-tegra/common.c b/arch/arm/mach-tegra/common.c index 5283a17f3d2b..c1ecbdf3f0a3 100644 --- a/arch/arm/mach-tegra/common.c +++ b/arch/arm/mach-tegra/common.c @@ -33,6 +33,7 @@ #include <mach/powergate.h> #include <mach/system.h> +#include "apbio.h" #include "board.h" #include "clock.h" #include "fuse.h" @@ -70,6 +71,7 @@ static __initdata struct tegra_clk_init_table common_clk_init_table[] = { { "emc", NULL, 0, true }, { "csite", NULL, 0, true }, { "timer", NULL, 0, true }, + { "kfuse", NULL, 0, true }, { "rtc", NULL, 0, true }, /* set frequencies of some device clocks */ @@ -89,6 +91,7 @@ void __init tegra_init_cache(void) writel(0x331, p + L2X0_TAG_LATENCY_CTRL); writel(0x441, p + L2X0_DATA_LATENCY_CTRL); writel(7, p + L2X0_PREFETCH_OFFSET); + writel(2, p + L2X0_PWR_CTRL); l2x0_init(p, 0x7C480001, 0x8200c3fe); #endif @@ -141,7 +144,7 @@ void __init tegra_common_init(void) tegra_init_power(); tegra_init_cache(); tegra_dma_init(); - tegra_init_fuse_dma(); + tegra_init_apb_dma(); } static int __init tegra_bootloader_fb_arg(char *options) diff --git a/arch/arm/mach-tegra/cortex-a9.S b/arch/arm/mach-tegra/cortex-a9.S index 91d787f2adcb..1ca815d0fab8 100644 --- a/arch/arm/mach-tegra/cortex-a9.S +++ b/arch/arm/mach-tegra/cortex-a9.S @@ -536,6 +536,8 @@ ENTRY(__cortex_a9_l2x0_restart) str r6, [r9, #L2X0_DATA_LATENCY_CTRL] str r7, [r9, #L2X0_PREFETCH_OFFSET] str r4, [r9, #L2X0_AUX_CTRL] + mov r4, #0x2 @ L2X0_DYNAMIC_CLK_GATING_EN + str r4, [r9, #L2X0_PWR_CTRL] cmp r0, #0 beq __reenable_l2x0 diff --git a/arch/arm/mach-tegra/fuse.c b/arch/arm/mach-tegra/fuse.c index fcb6d05495fb..869860c0d41d 100644 --- a/arch/arm/mach-tegra/fuse.c +++ b/arch/arm/mach-tegra/fuse.c @@ -19,30 +19,17 @@ #include <linux/kernel.h> #include <linux/io.h> -#include <linux/dma-mapping.h> -#include <linux/spinlock.h> -#include <linux/completion.h> -#include <linux/sched.h> -#include <linux/mutex.h> -#include <mach/dma.h> #include <mach/iomap.h> #include "fuse.h" +#include "apbio.h" #define FUSE_UID_LOW 0x108 #define FUSE_UID_HIGH 0x10c #define FUSE_SKU_INFO 0x110 #define FUSE_SPARE_BIT 0x200 -static DEFINE_MUTEX(tegra_fuse_dma_lock); - -#ifdef CONFIG_TEGRA_SYSTEM_DMA -static struct tegra_dma_channel *tegra_fuse_dma; -static u32 *tegra_fuse_bb; -static dma_addr_t tegra_fuse_bb_phys; -static DECLARE_COMPLETION(tegra_fuse_wait); - static const char *tegra_revision_name[TEGRA_REVISION_MAX] = { [TEGRA_REVISION_UNKNOWN] = "unknown", [TEGRA_REVISION_A02] = "A02", @@ -50,102 +37,19 @@ static const char *tegra_revision_name[TEGRA_REVISION_MAX] = { [TEGRA_REVISION_A03p] = "A03 prime", }; -static void fuse_dma_complete(struct tegra_dma_req *req) -{ - complete(&tegra_fuse_wait); -} - -static inline u32 fuse_readl(unsigned long offset) -{ - struct tegra_dma_req req; - int ret; - - if (!tegra_fuse_dma) - return readl(IO_TO_VIRT(TEGRA_FUSE_BASE + offset)); - - mutex_lock(&tegra_fuse_dma_lock); - req.complete = fuse_dma_complete; - req.to_memory = 1; - req.dest_addr = tegra_fuse_bb_phys; - req.dest_bus_width = 32; - req.dest_wrap = 1; - req.source_addr = TEGRA_FUSE_BASE + offset; - req.source_bus_width = 32; - req.source_wrap = 4; - req.req_sel = 0; - req.size = 4; - - INIT_COMPLETION(tegra_fuse_wait); - - tegra_dma_enqueue_req(tegra_fuse_dma, &req); - - ret = wait_for_completion_timeout(&tegra_fuse_wait, - msecs_to_jiffies(50)); - - if (WARN(ret == 0, "fuse read dma timed out")) - *(u32 *)tegra_fuse_bb = 0; - - mutex_unlock(&tegra_fuse_dma_lock); - return *((u32 *)tegra_fuse_bb); -} - -static inline void fuse_writel(u32 value, unsigned long offset) -{ - struct tegra_dma_req req; - int ret; - - if (!tegra_fuse_dma) { - writel(value, IO_TO_VIRT(TEGRA_FUSE_BASE + offset)); - return; - } - - mutex_lock(&tegra_fuse_dma_lock); - *((u32 *)tegra_fuse_bb) = value; - req.complete = fuse_dma_complete; - req.to_memory = 0; - req.dest_addr = TEGRA_FUSE_BASE + offset; - req.dest_wrap = 4; - req.dest_bus_width = 32; - req.source_addr = tegra_fuse_bb_phys; - req.source_bus_width = 32; - req.source_wrap = 1; - req.req_sel = 0; - req.size = 4; - - INIT_COMPLETION(tegra_fuse_wait); - - tegra_dma_enqueue_req(tegra_fuse_dma, &req); - - ret = wait_for_completion_timeout(&tegra_fuse_wait, - msecs_to_jiffies(50)); - - mutex_unlock(&tegra_fuse_dma_lock); -} -#else -static inline u32 fuse_readl(unsigned long offset) -{ - return readl(IO_TO_VIRT(TEGRA_FUSE_BASE + offset)); -} - -static inline void fuse_writel(u32 value, unsigned long offset) -{ - writel(value, IO_TO_VIRT(TEGRA_FUSE_BASE + offset)); -} -#endif - u32 tegra_fuse_readl(unsigned long offset) { - return fuse_readl(offset); + return tegra_apb_readl(TEGRA_FUSE_BASE + offset); } void tegra_fuse_writel(u32 value, unsigned long offset) { - fuse_writel(value, offset); + tegra_apb_writel(value, TEGRA_FUSE_BASE + offset); } static inline bool get_spare_fuse(int bit) { - return fuse_readl(FUSE_SPARE_BIT + bit * 4); + return tegra_apb_readl(FUSE_SPARE_BIT + bit * 4); } void tegra_init_fuse(void) @@ -160,40 +64,19 @@ void tegra_init_fuse(void) tegra_core_process_id()); } -void tegra_init_fuse_dma(void) -{ -#ifdef CONFIG_TEGRA_SYSTEM_DMA - tegra_fuse_dma = tegra_dma_allocate_channel(TEGRA_DMA_MODE_ONESHOT | - TEGRA_DMA_SHARED); - if (!tegra_fuse_dma) { - pr_err("%s: can not allocate dma channel\n", __func__); - return; - } - - tegra_fuse_bb = dma_alloc_coherent(NULL, sizeof(u32), - &tegra_fuse_bb_phys, GFP_KERNEL); - if (!tegra_fuse_bb) { - pr_err("%s: can not allocate bounce buffer\n", __func__); - tegra_dma_free_channel(tegra_fuse_dma); - tegra_fuse_dma = NULL; - return; - } -#endif -} - unsigned long long tegra_chip_uid(void) { unsigned long long lo, hi; - lo = fuse_readl(FUSE_UID_LOW); - hi = fuse_readl(FUSE_UID_HIGH); + lo = tegra_fuse_readl(FUSE_UID_LOW); + hi = tegra_fuse_readl(FUSE_UID_HIGH); return (hi << 32ull) | lo; } int tegra_sku_id(void) { int sku_id; - u32 reg = fuse_readl(FUSE_SKU_INFO); + u32 reg = tegra_fuse_readl(FUSE_SKU_INFO); sku_id = reg & 0xFF; return sku_id; } @@ -201,7 +84,7 @@ int tegra_sku_id(void) int tegra_cpu_process_id(void) { int cpu_process_id; - u32 reg = fuse_readl(FUSE_SPARE_BIT); + u32 reg = tegra_fuse_readl(FUSE_SPARE_BIT); cpu_process_id = (reg >> 6) & 3; return cpu_process_id; } @@ -209,7 +92,7 @@ int tegra_cpu_process_id(void) int tegra_core_process_id(void) { int core_process_id; - u32 reg = fuse_readl(FUSE_SPARE_BIT); + u32 reg = tegra_fuse_readl(FUSE_SPARE_BIT); core_process_id = (reg >> 12) & 3; return core_process_id; } diff --git a/arch/arm/mach-tegra/fuse.h b/arch/arm/mach-tegra/fuse.h index 8a9042635f2b..e48838e69e95 100644 --- a/arch/arm/mach-tegra/fuse.h +++ b/arch/arm/mach-tegra/fuse.h @@ -30,7 +30,6 @@ int tegra_sku_id(void); int tegra_cpu_process_id(void); int tegra_core_process_id(void); void tegra_init_fuse(void); -void tegra_init_fuse_dma(void); u32 tegra_fuse_readl(unsigned long offset); void tegra_fuse_writel(u32 value, unsigned long offset); enum tegra_revision tegra_get_revision(void); diff --git a/arch/arm/mach-tegra/include/mach/dc.h b/arch/arm/mach-tegra/include/mach/dc.h index f5a64cba61ae..9bd26194f0c9 100644 --- a/arch/arm/mach-tegra/include/mach/dc.h +++ b/arch/arm/mach-tegra/include/mach/dc.h @@ -73,9 +73,13 @@ struct tegra_dc_out { int (*disable)(void); }; -#define TEGRA_DC_OUT_HOTPLUG_HIGH (0 << 1) -#define TEGRA_DC_OUT_HOTPLUG_LOW (1 << 1) -#define TEGRA_DC_OUT_HOTPLUG_MASK (1 << 1) +/* bits for tegra_dc_out.flags */ +#define TEGRA_DC_OUT_HOTPLUG_HIGH (0 << 1) +#define TEGRA_DC_OUT_HOTPLUG_LOW (1 << 1) +#define TEGRA_DC_OUT_HOTPLUG_MASK (1 << 1) +#define TEGRA_DC_OUT_NVHDCP_POLICY_ALWAYS_ON (0 << 2) +#define TEGRA_DC_OUT_NVHDCP_POLICY_ON_DEMAND (1 << 2) +#define TEGRA_DC_OUT_NVHDCP_POLICY_MASK (1 << 2) #define TEGRA_DC_ALIGN_MSB 0 #define TEGRA_DC_ALIGN_LSB 1 diff --git a/arch/arm/mach-tegra/include/mach/kfuse.h b/arch/arm/mach-tegra/include/mach/kfuse.h new file mode 100644 index 000000000000..cfe85cc86ff2 --- /dev/null +++ b/arch/arm/mach-tegra/include/mach/kfuse.h @@ -0,0 +1,20 @@ +/* + * arch/arm/mach-tegra/kfuse.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. + * + */ + +/* there are 144 32-bit values in total */ +#define KFUSE_DATA_SZ (144 * 4) + +int tegra_kfuse_read(void *dest, size_t len); diff --git a/arch/arm/mach-tegra/kfuse.c b/arch/arm/mach-tegra/kfuse.c new file mode 100644 index 000000000000..f5de828bb508 --- /dev/null +++ b/arch/arm/mach-tegra/kfuse.c @@ -0,0 +1,88 @@ +/* + * arch/arm/mach-tegra/kfuse.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. + * + */ + +/* The kfuse block stores downstream and upstream HDCP keys for use by HDMI + * module. + */ + +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/string.h> +#include <linux/delay.h> + +#include <mach/iomap.h> +#include <mach/kfuse.h> + +#include "apbio.h" + +/* register definition */ +#define KFUSE_STATE 0x80 +#define KFUSE_STATE_DONE (1u << 16) +#define KFUSE_STATE_CRCPASS (1u << 17) +#define KFUSE_KEYADDR 0x88 +#define KFUSE_KEYADDR_AUTOINC (1u << 16) +#define KFUSE_KEYS 0x8c + +static inline u32 tegra_kfuse_readl(unsigned long offset) +{ + return tegra_apb_readl(TEGRA_KFUSE_BASE + offset); +} + +static inline void tegra_kfuse_writel(u32 value, unsigned long offset) +{ + tegra_apb_writel(value, TEGRA_KFUSE_BASE + offset); +} + +static int wait_for_done(void) +{ + u32 reg; + int retries = 50; + do { + reg = tegra_kfuse_readl(KFUSE_STATE); + if (reg & KFUSE_STATE_DONE); + return 0; + msleep(10); + } while(--retries); + return -ETIMEDOUT; +} + +/* read up to KFUSE_DATA_SZ bytes into dest. + * always starts at the first kfuse. + */ +int tegra_kfuse_read(void *dest, size_t len) +{ + u32 v; + unsigned cnt; + + if (len > KFUSE_DATA_SZ) + return -EINVAL; + + tegra_kfuse_writel(KFUSE_KEYADDR_AUTOINC, KFUSE_KEYADDR); + wait_for_done(); + + if ((tegra_kfuse_readl(KFUSE_STATE) & KFUSE_STATE_CRCPASS) == 0) { + pr_err("kfuse: crc failed\n"); + return -EIO; + } + + for (cnt = 0; cnt < len; cnt += 4) { + v = tegra_kfuse_readl(KFUSE_KEYS); + memcpy(dest + cnt, &v, sizeof v); + } + + return 0; +} diff --git a/arch/arm/mach-tegra/tegra2_clocks.c b/arch/arm/mach-tegra/tegra2_clocks.c index 2f809e0219b7..446d05bbd97e 100644 --- a/arch/arm/mach-tegra/tegra2_clocks.c +++ b/arch/arm/mach-tegra/tegra2_clocks.c @@ -1955,6 +1955,7 @@ static struct clk tegra_clk_emc = { struct clk tegra_list_clks[] = { PERIPH_CLK("rtc", "rtc-tegra", NULL, 4, 0, 32768, mux_clk_32k, PERIPH_NO_RESET), PERIPH_CLK("timer", "timer", NULL, 5, 0, 26000000, mux_clk_m, 0), + PERIPH_CLK("kfuse", "kfuse-tegra", NULL, 40, 0, 26000000, mux_clk_m, 0), PERIPH_CLK("i2s1", "i2s.0", NULL, 11, 0x100, 26000000, mux_pllaout0_audio2x_pllp_clkm, MUX | DIV_U71), PERIPH_CLK("i2s2", "i2s.1", NULL, 18, 0x104, 26000000, mux_pllaout0_audio2x_pllp_clkm, MUX | DIV_U71), PERIPH_CLK("spdif_out", "spdif_out", NULL, 10, 0x108, 100000000, mux_pllaout0_audio2x_pllp_clkm, MUX | DIV_U71), diff --git a/drivers/video/tegra/dc/Makefile b/drivers/video/tegra/dc/Makefile index eb39d5d28e92..4567eba8cb93 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 += nvhdcp.o +obj-y += edid.o diff --git a/drivers/video/tegra/dc/edid.c b/drivers/video/tegra/dc/edid.c index 812a0087a96d..47f05e6ac31c 100644 --- a/drivers/video/tegra/dc/edid.c +++ b/drivers/video/tegra/dc/edid.c @@ -170,6 +170,8 @@ int tegra_edid_get_monspecs(struct tegra_edid *edid, struct fb_monspecs *specs) int extension_blocks; ret = tegra_edid_read_block(edid, 0, edid->data); + if (ret) + return ret; memset(specs, 0x0, sizeof(struct fb_monspecs)); fb_edid_to_monspecs(edid->data, specs); diff --git a/drivers/video/tegra/dc/hdmi.c b/drivers/video/tegra/dc/hdmi.c index 3e274ecb73de..92a12ee448c4 100644 --- a/drivers/video/tegra/dc/hdmi.c +++ b/drivers/video/tegra/dc/hdmi.c @@ -32,11 +32,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 @@ -46,6 +49,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; @@ -206,13 +210,13 @@ static const struct tegra_hdmi_audio_config } -static inline unsigned long tegra_hdmi_readl(struct tegra_dc_hdmi_data *hdmi, +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, +void tegra_hdmi_writel(struct tegra_dc_hdmi_data *hdmi, unsigned long val, unsigned long reg) { writel(val, hdmi->base + reg * 4); @@ -468,6 +472,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; } @@ -510,6 +515,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); } @@ -605,6 +611,14 @@ static int tegra_dc_hdmi_init(struct tegra_dc *dc) goto err_free_irq; } + hdmi->nvhdcp = tegra_nvhdcp_create(hdmi, dc->ndev->id, + dc->out->dcc_bus); + 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; @@ -624,8 +638,18 @@ static int tegra_dc_hdmi_init(struct tegra_dc *dc) tegra_dc_set_outdata(dc, 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: @@ -657,6 +681,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); kfree(hdmi); @@ -1108,15 +1133,21 @@ static void tegra_dc_hdmi_enable(struct tegra_dc *dc) tegra_dc_writel(dc, DISP_CTRL_MODE_C_DISPLAY, DC_CMD_DISPLAY_COMMAND); tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); + + if (!hdmi->dvi) + tegra_nvhdcp_set_plug(hdmi->nvhdcp, 1); } 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); } + struct tegra_dc_out_ops tegra_dc_hdmi_ops = { .init = tegra_dc_hdmi_init, .destroy = tegra_dc_hdmi_destroy, diff --git a/drivers/video/tegra/dc/hdmi.h b/drivers/video/tegra/dc/hdmi.h index 0189f08719fe..a2fa3482f2b9 100644 --- a/drivers/video/tegra/dc/hdmi.h +++ b/drivers/video/tegra/dc/hdmi.h @@ -180,4 +180,10 @@ struct hdmi_audio_infoframe { #define HDMI_AUDIO_CXT_HE_AAC_V2 0x2 #define HDMI_AUDIO_CXT_MPEG_SURROUND 0x3 +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); #endif diff --git a/drivers/video/tegra/dc/hdmi_reg.h b/drivers/video/tegra/dc/hdmi_reg.h index 67d2b23a3d81..db9c1b76e09f 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,11 @@ #define PE_CURRENT3(x) (((x) & 0xf) << 24) #define HDMI_NV_PDISP_KEY_CTRL 0x9a +#define LOCAL_KEYS (1 << 0) +#define AUTOINC (1 << 1) +#define WRITE16 (1 << 4) +#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..7bbe6741be45 --- /dev/null +++ b/drivers/video/tegra/dc/nvhdcp.c @@ -0,0 +1,1230 @@ +/* + * 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" + +/* for 0x40 Bcaps */ +#define BCAPS_REPEATER (1 << 6) +#define BCAPS_READY (1 << 5) +#define BCAPS_11 (1 << 1) /* used for both Bcaps and Ainfo */ + +/* for 0x41 Bstatus */ +#define BSTATUS_MAX_DEVS_EXCEEDED (1 << 7) +#define BSTATUS_MAX_CASCADE_EXCEEDED (1 << 11) + +#ifdef VERBOSE_DEBUG +#define nvhdcp_vdbg(...) \ + printk("nvhdcp: " __VA_ARGS__) +#else +#define nvhdcp_vdbg(...) \ +({ \ + if(0) \ + printk("nvhdcp: " __VA_ARGS__); \ + 0; \ +}) +#endif +#define nvhdcp_debug(...) \ + pr_debug("nvhdcp: " __VA_ARGS__) +#define nvhdcp_err(...) \ + pr_err("nvhdcp: Error: " __VA_ARGS__) +#define nvhdcp_info(...) \ + pr_info("nvhdcp: " __VA_ARGS__) + + +/* for nvhdcp.state */ +enum tegra_nvhdcp_state { + 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; + bool plugged; /* true if hotplug detected */ + atomic_t policy; /* set policy */ + enum tegra_nvhdcp_state state; /* STATE_xxx */ + struct i2c_client *client; + struct i2c_board_info info; + int bus; + u32 b_status; + u64 a_n; + u64 c_n; + u64 a_ksv; + u64 b_ksv; + u64 c_ksv; + u64 d_ksv; + u8 v_prime[20]; + u64 m_prime; + u32 num_bksv_list; + u64 bksv_list[TEGRA_NVHDCP_MAX_DEVS]; + int fail_count; +}; + +static inline bool nvhdcp_is_plugged(struct tegra_nvhdcp *nvhdcp) +{ + rmb(); + return nvhdcp->plugged; +} + +static inline bool nvhdcp_set_plugged(struct tegra_nvhdcp *nvhdcp, bool plugged) +{ + return nvhdcp->plugged = plugged; + wmb(); +} + +static int nvhdcp_i2c_read(struct tegra_nvhdcp *nvhdcp, u8 reg, + size_t len, void *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 = i2c_transfer(nvhdcp->client->adapter, 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_nvhdcp *nvhdcp, u8 reg, + size_t len, const void *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 = i2c_transfer(nvhdcp->client->adapter, msg, ARRAY_SIZE(msg)); + + if (status < 0) { + nvhdcp_err("i2c xfer error %d\n", status); + return status; + } + + return 0; +} + +static inline int nvhdcp_i2c_read8(struct tegra_nvhdcp *nvhdcp, u8 reg, u8 *val) +{ + return nvhdcp_i2c_read(nvhdcp, reg, 1, val); +} + +static inline int nvhdcp_i2c_write8(struct tegra_nvhdcp *nvhdcp, u8 reg, u8 val) +{ + return nvhdcp_i2c_write(nvhdcp, reg, 1, &val); +} + +static inline int nvhdcp_i2c_read16(struct tegra_nvhdcp *nvhdcp, + u8 reg, u16 *val) +{ + u8 buf[2]; + int e; + + e = nvhdcp_i2c_read(nvhdcp, 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_nvhdcp *nvhdcp, u8 reg, u64 *val) +{ + u8 buf[5]; + int e, i; + u64 n; + + e = nvhdcp_i2c_read(nvhdcp, 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_nvhdcp *nvhdcp, u8 reg, u64 val) +{ + char buf[5]; + int i; + for(i = 0; i < 5; i++ ) { + buf[i] = val; + val >>= 8; + } + return nvhdcp_i2c_write(nvhdcp, reg, sizeof buf, buf); +} + +static int nvhdcp_i2c_write64(struct tegra_nvhdcp *nvhdcp, u8 reg, u64 val) +{ + char buf[8]; + int i; + for(i = 0; i < 8; i++ ) { + buf[i] = val; + val >>= 8; + } + return nvhdcp_i2c_write(nvhdcp, reg, sizeof buf, buf); +} + + +/* 64-bit link encryption session random number */ +static inline 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 inline 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 void set_bksv(struct tegra_dc_hdmi_data *hdmi, u64 b_ksv, bool repeater) +{ + if (repeater) + b_ksv |= (u64)REPEATER << 32; + tegra_hdmi_writel(hdmi, (u32)b_ksv, HDMI_NV_PDISP_RG_HDCP_BKSV_LSB); + tegra_hdmi_writel(hdmi, b_ksv >> 32, HDMI_NV_PDISP_RG_HDCP_BKSV_MSB); +} + + +/* 40-bit software's key selection vector */ +static inline 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; +} + +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_nvhdcp *nvhdcp, u16 *r) +{ + return nvhdcp_i2c_read16(nvhdcp, 0x8, r); /* long read */ +} + +static int get_bcaps(struct tegra_nvhdcp *nvhdcp, u8 *b_caps) +{ + int e, retries = 4; + do { + e = nvhdcp_i2c_read8(nvhdcp, 0x40, b_caps); + if (!e) + return 0; + if (retries > 1) + msleep(100); + } while (--retries); + + return -EIO; +} + +static int get_ksvfifo(struct tegra_nvhdcp *nvhdcp, + unsigned num_bksv_list, u64 *ksv_list) +{ + u8 *buf, *p; + int e; + unsigned i; + size_t buf_len = num_bksv_list * 5; + + if (!ksv_list || num_bksv_list > TEGRA_NVHDCP_MAX_DEVS) + return -EINVAL; + + buf = kmalloc(buf_len, GFP_KERNEL); + if (IS_ERR_OR_NULL(buf)) + return -ENOMEM; + + e = nvhdcp_i2c_read(nvhdcp, 0x43, buf_len, buf); + if (e) { + kfree(buf); + return e; + } + + /* load 40-bit keys from repeater into array of u64 */ + p = buf; + for (i = 0; i < num_bksv_list; i++) { + ksv_list[i] = p[0] | ((u64)p[1] << 8) | ((u64)p[2] << 16) + | ((u64)p[3] << 24) | ((u64)p[4] << 32); + p += 5; + } + + kfree(buf); + return 0; +} + +/* get V' 160-bit SHA-1 hash from repeater */ +static int get_vprime(struct tegra_nvhdcp *nvhdcp, u8 *v_prime) +{ + int e, i; + + for (i = 0; i < 20; i += 4) { + e = nvhdcp_i2c_read(nvhdcp, 0x20 + i, 4, v_prime + i); + if (e) + return e; + } + return 0; +} + + +/* set or clear RUN_YES */ +static void hdcp_ctrl_run(struct tegra_dc_hdmi_data *hdmi, bool v) +{ + u32 ctrl; + + if (v) { + ctrl = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_CTRL); + 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 = 13; + u32 ctrl; + + do { + ctrl = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_RG_HDCP_CTRL); + if ((ctrl | (mask))) { + if (v) + *v = ctrl; + break; + } + if (retries > 1) + 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 = 101; + u32 ctrl; + + do { + ctrl = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_KEY_CTRL); + if ((ctrl | (mask))) + break; + if (retries > 1) + 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; + + /* if connection isn't authenticated ... */ + mutex_lock(&nvhdcp->lock); + if (nvhdcp->state != STATE_LINK_VERIFY) { + memset(pkt, 0, sizeof *pkt); + pkt->packet_results = TEGRA_NVHDCP_RESULT_LINK_FAILED; + e = 0; + goto err; + } + + 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); + e = -EINVAL; + goto err; + } + + 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_vdbg("%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; + e = -EIO; + goto err; + } + + msleep(50); + + /* 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; + e = -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; + mutex_unlock(&nvhdcp->lock); + return 0; + +err: + mutex_unlock(&nvhdcp->lock); + return e; +} + +/* 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 ... */ + mutex_lock(&nvhdcp->lock); + if (nvhdcp->state != STATE_LINK_VERIFY) { + memset(pkt, 0, sizeof *pkt); + pkt->packet_results = TEGRA_NVHDCP_RESULT_LINK_FAILED; + e = 0; + goto err; + } + + 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); + + 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"); + e = -EIO; + goto err; + } + msleep(50); + + /* load Mprime */ + pkt->m_prime = get_mprime(hdmi); + pkt->value_flags |= TEGRA_NVHDCP_FLAG_MP; + + pkt->b_status = nvhdcp->b_status; + pkt->value_flags |= TEGRA_NVHDCP_FLAG_BSTATUS; + + /* copy most recent KSVFIFO, if it is non-zero */ + pkt->num_bksv_list = nvhdcp->num_bksv_list; + if( nvhdcp->num_bksv_list ) { + BUILD_BUG_ON(sizeof(pkt->bksv_list) != sizeof(nvhdcp->bksv_list)); + memcpy(pkt->bksv_list, nvhdcp->bksv_list, + nvhdcp->num_bksv_list * sizeof(*pkt->bksv_list)); + pkt->value_flags |= TEGRA_NVHDCP_FLAG_BKSVLIST; + } + + /* copy v_prime */ + BUILD_BUG_ON(sizeof(pkt->v_prime) != sizeof(nvhdcp->v_prime)); + memcpy(pkt->v_prime, nvhdcp->v_prime, sizeof(nvhdcp->v_prime)); + pkt->value_flags |= TEGRA_NVHDCP_FLAG_V; + + /* load Dksv */ + pkt->d_ksv = get_dksv(hdmi); + if (verify_ksv(pkt->d_ksv)) { + nvhdcp_err("Dksv invalid!\n"); + e = -EIO; + goto err; + } + 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; + mutex_unlock(&nvhdcp->lock); + return 0; + +err: + mutex_unlock(&nvhdcp->lock); + return e; +} + +static int load_kfuse(struct tegra_dc_hdmi_data *hdmi) +{ + 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 */ + + 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 = 6; + do { + tmp = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_KEY_DEBUG0); + if ((tmp & 1) == 0) break; + if (retries > 1) + 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 = LOCAL_KEYS | WRITE16; + if (i) + tmp |= 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(nvhdcp, &rx); + if (!e) { + if (!rx) { + nvhdcp_err("Ri is 0!\n"); + return -EINVAL; + } + + tx = get_transmitter_ri(hdmi); + } else { + rx = ~tx; + msleep(50); + } + + } while (wait_ri && --retries && old != tx); + + nvhdcp_debug("R0 Ri poll:rx=0x%04x tx=0x%04x\n", rx, tx); + + if (!nvhdcp_is_plugged(nvhdcp)) { + nvhdcp_err("aborting verify links - lost hdmi connection\n"); + return -EIO; + } + + if (rx != tx) + return -EINVAL; + + return 0; +} + +static int get_repeater_info(struct tegra_nvhdcp *nvhdcp) +{ + int e, retries; + u8 b_caps; + u16 b_status; + + nvhdcp_vdbg("repeater found:fetching repeater info\n"); + + /* wait up to 5 seconds for READY on repeater */ + retries = 51; + do { + if (!nvhdcp_is_plugged(nvhdcp)) { + nvhdcp_err("disconnect while waiting for repeater\n"); + return -EIO; + } + + e = get_bcaps(nvhdcp, &b_caps); + if (!e && (b_caps & BCAPS_READY)) { + nvhdcp_debug("Bcaps READY from repeater\n"); + break; + } + if (retries > 1) + msleep(100); + } while (--retries); + if (!retries) { + nvhdcp_err("repeater Bcaps read timeout\n"); + return -ETIMEDOUT; + } + + memset(nvhdcp->v_prime, 0, sizeof nvhdcp->v_prime); + e = get_vprime(nvhdcp, nvhdcp->v_prime); + if (e) { + nvhdcp_err("repeater Vprime read failure!\n"); + return e; + } + + e = nvhdcp_i2c_read16(nvhdcp, 0x41, &b_status); + if (e) { + nvhdcp_err("Bstatus read failure!\n"); + return e; + } + + if (b_status & BSTATUS_MAX_DEVS_EXCEEDED) { + nvhdcp_err("repeater:max devices (0x%04x)\n", b_status); + return -EINVAL; + } + + if (b_status & BSTATUS_MAX_CASCADE_EXCEEDED) { + nvhdcp_err("repeater:max cascade (0x%04x)\n", b_status); + return -EINVAL; + } + + nvhdcp->b_status = b_status; + nvhdcp->num_bksv_list = b_status & 0x7f; + nvhdcp_vdbg("Bstatus 0x%x (devices: %d)\n", + b_status, nvhdcp->num_bksv_list); + + memset(nvhdcp->bksv_list, 0, sizeof nvhdcp->bksv_list); + e = get_ksvfifo(nvhdcp, nvhdcp->num_bksv_list, nvhdcp->bksv_list); + if (e) { + nvhdcp_err("repeater:could not read KSVFIFO (err %d)\n", e); + return e; + } + + 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; + + nvhdcp_vdbg("%s():started thread %s\n", __func__, nvhdcp->name); + + mutex_lock(&nvhdcp->lock); + if (nvhdcp->state == STATE_OFF) { + nvhdcp_err("nvhdcp failure - giving up\n"); + goto err; + } + nvhdcp->state = STATE_UNAUTHENTICATED; + + /* check plug state to terminate early in case flush_workqueue() */ + if (!nvhdcp_is_plugged(nvhdcp)) { + nvhdcp_err("worker started while unplugged!\n"); + goto lost_hdmi; + } + nvhdcp_vdbg("%s():hpd=%d\n", __func__, nvhdcp->plugged); + + nvhdcp->a_ksv = 0; + nvhdcp->b_ksv = 0; + nvhdcp->a_n = 0; + + e = get_bcaps(nvhdcp, &b_caps); + if (e) { + nvhdcp_err("Bcaps read failure\n"); + goto failure; + } + + nvhdcp_vdbg("read Bcaps = 0x%02x\n", b_caps); + + nvhdcp_vdbg("kfuse loading ...\n"); + + /* repeater flag in Bskv must be configured before loading fuses */ + set_bksv(hdmi, 0, (b_caps & BCAPS_REPEATER)); + + e = load_kfuse(hdmi); + if (e) { + nvhdcp_err("kfuse could not be loaded\n"); + goto failure; + } + + hdcp_ctrl_run(hdmi, 1); + + nvhdcp_vdbg("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; + } + + msleep(25); + + nvhdcp->a_ksv = get_aksv(hdmi); + nvhdcp->a_n = get_an(hdmi); + nvhdcp_vdbg("Aksv is 0x%016llx\n", nvhdcp->a_ksv); + nvhdcp_vdbg("An is 0x%016llx\n", nvhdcp->a_n); + if (verify_ksv(nvhdcp->a_ksv)) { + nvhdcp_err("Aksv verify failure! (0x%016llx)\n", nvhdcp->a_ksv); + goto failure; + } + + /* write Ainfo to receiver - set 1.1 only if b_caps supports it */ + e = nvhdcp_i2c_write8(nvhdcp, 0x15, b_caps & BCAPS_11); + if (e) { + nvhdcp_err("Ainfo write failure\n"); + goto failure; + } + + /* write An to receiver */ + e = nvhdcp_i2c_write64(nvhdcp, 0x18, nvhdcp->a_n); + if (e) { + nvhdcp_err("An write failure\n"); + goto failure; + } + + nvhdcp_vdbg("wrote An = 0x%016llx\n", nvhdcp->a_n); + + /* write Aksv to receiver - triggers auth sequence */ + e = nvhdcp_i2c_write40(nvhdcp, 0x10, nvhdcp->a_ksv); + if (e) { + nvhdcp_err("Aksv write failure\n"); + goto failure; + } + + nvhdcp_vdbg("wrote Aksv = 0x%010llx\n", nvhdcp->a_ksv); + + /* bail out if unplugged in the middle of negotiation */ + if (!nvhdcp_is_plugged(nvhdcp)) + goto lost_hdmi; + + /* get Bksv from receiver */ + e = nvhdcp_i2c_read40(nvhdcp, 0x00, &nvhdcp->b_ksv); + if (e) { + nvhdcp_err("Bksv read failure\n"); + goto failure; + } + nvhdcp_vdbg("Bksv is 0x%016llx\n", nvhdcp->b_ksv); + if (verify_ksv(nvhdcp->b_ksv)) { + nvhdcp_err("Bksv verify failure!\n"); + goto failure; + } + + nvhdcp_vdbg("read Bksv = 0x%010llx from device\n", nvhdcp->b_ksv); + + set_bksv(hdmi, nvhdcp->b_ksv, (b_caps & BCAPS_REPEATER)); + + nvhdcp_vdbg("loaded Bksv into controller\n"); + + e = wait_hdcp_ctrl(hdmi, R0_VALID, NULL); + if (e) { + nvhdcp_err("R0 read failure!\n"); + goto failure; + } + + nvhdcp_vdbg("R0 valid\n"); + + msleep(100); /* can't read R0' within 100ms of writing Aksv */ + + nvhdcp_vdbg("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_vdbg("CRYPT enabled\n"); + + /* if repeater then get repeater info */ + if (b_caps & BCAPS_REPEATER) { + e = get_repeater_info(nvhdcp); + if (e) { + nvhdcp_err("get repeater info failed\n"); + goto failure; + } + } + + nvhdcp->state = STATE_LINK_VERIFY; + nvhdcp_info("link verified!\n"); + + while (1) { + if (nvhdcp->state != STATE_LINK_VERIFY) + goto failure; + + if (!nvhdcp_is_plugged(nvhdcp)) + goto lost_hdmi; + + e = verify_link(nvhdcp, true); + if (e) { + nvhdcp_err("link verification failed err %d\n", e); + goto failure; + } + mutex_unlock(&nvhdcp->lock); + msleep(1500); + mutex_lock(&nvhdcp->lock); + + } + +failure: + nvhdcp->fail_count++; + if(nvhdcp->fail_count > 5) { + nvhdcp_err("nvhdcp failure - too many failures, giving up!\n"); + } else { + nvhdcp_err("nvhdcp failure - renegotiating in 1.75 seconds\n"); + msleep(1750); + queue_work(nvhdcp->downstream_wq, &nvhdcp->work); + } + +lost_hdmi: + nvhdcp->state = STATE_UNAUTHENTICATED; + hdcp_ctrl_run(hdmi, 0); + +err: + mutex_unlock(&nvhdcp->lock); + return; +} + +void tegra_nvhdcp_set_plug(struct tegra_nvhdcp *nvhdcp, bool hpd) +{ + nvhdcp_debug("hdmi hotplug detected (hpd = %d)\n", hpd); + + nvhdcp_set_plugged(nvhdcp, 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) +{ + nvhdcp->state = STATE_UNAUTHENTICATED; + if (nvhdcp_is_plugged(nvhdcp)) { + nvhdcp->fail_count = 0; + queue_work(nvhdcp->downstream_wq, &nvhdcp->work); + } + return 0; +} + +static int tegra_nvhdcp_off(struct tegra_nvhdcp *nvhdcp) +{ + mutex_lock(&nvhdcp->lock); + nvhdcp->state = STATE_OFF; + nvhdcp_set_plugged(nvhdcp, false); + mutex_unlock(&nvhdcp->lock); + flush_workqueue(nvhdcp->downstream_wq); + return 0; +} + +int tegra_nvhdcp_set_policy(struct tegra_nvhdcp *nvhdcp, int pol) +{ + if (pol == TEGRA_NVHDCP_POLICY_ALWAYS_ON) { + nvhdcp_info("using \"always on\" policy.\n"); + if (atomic_xchg(&nvhdcp->policy, pol) != pol) { + /* policy changed, start working */ + tegra_nvhdcp_on(nvhdcp); + } + } else { + /* unsupported policy */ + return -EINVAL; + } + + return 0; +} + +static int tegra_nvhdcp_renegotiate(struct tegra_nvhdcp *nvhdcp) +{ + mutex_lock(&nvhdcp->lock); + nvhdcp->state = STATE_RENEGOTIATE; + mutex_unlock(&nvhdcp->lock); + tegra_nvhdcp_on(nvhdcp); + return 0; +} + +void tegra_nvhdcp_suspend(struct tegra_nvhdcp *nvhdcp) +{ + if (!nvhdcp) return; + tegra_nvhdcp_off(nvhdcp); +} + + +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; +} + +static int nvhdcp_dev_open(struct inode *inode, struct file *filp) +{ + struct miscdevice *miscdev = filp->private_data; + struct tegra_nvhdcp *nvhdcp = + container_of(miscdev, struct tegra_nvhdcp, miscdev); + filp->private_data = nvhdcp; + return 0; +} + +static int nvhdcp_dev_release(struct inode *inode, struct file *filp) +{ + filp->private_data = NULL; + return 0; +} + +static const struct file_operations nvhdcp_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = nvhdcp_dev_ioctl, + .open = nvhdcp_dev_open, + .release = nvhdcp_dev_release, +}; + +/* we only support one AP right now, so should only call this once. */ +struct tegra_nvhdcp *tegra_nvhdcp_create(struct tegra_dc_hdmi_data *hdmi, + int id, int bus) +{ + static struct tegra_nvhdcp *nvhdcp; /* prevent multiple calls */ + struct i2c_adapter *adapter; + int e; + + if (nvhdcp) + return ERR_PTR(-EMFILE); + + 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); + + strlcpy(nvhdcp->info.type, nvhdcp->name, sizeof(nvhdcp->info.type)); + nvhdcp->bus = bus; + nvhdcp->info.addr = 0x74 >> 1; + nvhdcp->info.platform_data = nvhdcp; + nvhdcp->fail_count = 0; + + adapter = i2c_get_adapter(bus); + if (!adapter) { + nvhdcp_err("can't get adapter for bus %d\n", bus); + e = -EBUSY; + goto free_nvhdcp; + } + + nvhdcp->client = i2c_new_device(adapter, &nvhdcp->info); + i2c_put_adapter(adapter); + + if (!nvhdcp->client) { + nvhdcp_err("can't create new device\n"); + e = -EBUSY; + goto free_nvhdcp; + } + + 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 free_workqueue; + + nvhdcp_vdbg("%s(): created misc device %s\n", __func__, nvhdcp->name); + + return nvhdcp; +free_workqueue: + destroy_workqueue(nvhdcp->downstream_wq); + i2c_release_client(nvhdcp->client); +free_nvhdcp: + 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); + tegra_nvhdcp_off(nvhdcp); + destroy_workqueue(nvhdcp->downstream_wq); + i2c_release_client(nvhdcp->client); + kfree(nvhdcp); +} diff --git a/drivers/video/tegra/dc/nvhdcp.h b/drivers/video/tegra/dc/nvhdcp.h new file mode 100644 index 000000000000..9b898252d534 --- /dev/null +++ b/drivers/video/tegra/dc/nvhdcp.h @@ -0,0 +1,29 @@ +/* + * 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, int bus); +void tegra_nvhdcp_destroy(struct tegra_nvhdcp *nvhdcp); + +#endif diff --git a/include/video/nvhdcp.h b/include/video/nvhdcp.h new file mode 100644 index 000000000000..f282ff8caa99 --- /dev/null +++ b/include/video/nvhdcp.h @@ -0,0 +1,91 @@ +/* + * include/video/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 _LINUX_NVHDCP_H_ +#define _LINUX_NVHDCP_H_ + +#include <linux/fb.h> +#include <linux/types.h> +#include <asm/ioctl.h> + +/* maximum receivers and repeaters connected at a time */ +#define TEGRA_NVHDCP_MAX_DEVS 127 + +/* values for value_flags */ +#define TEGRA_NVHDCP_FLAG_AN 0x0001 +#define TEGRA_NVHDCP_FLAG_AKSV 0x0002 +#define TEGRA_NVHDCP_FLAG_BKSV 0x0004 +#define TEGRA_NVHDCP_FLAG_BSTATUS 0x0008 /* repeater status */ +#define TEGRA_NVHDCP_FLAG_CN 0x0010 /* c_n */ +#define TEGRA_NVHDCP_FLAG_CKSV 0x0020 /* c_ksv */ +#define TEGRA_NVHDCP_FLAG_DKSV 0x0040 /* d_ksv */ +#define TEGRA_NVHDCP_FLAG_KP 0x0080 /* k_prime */ +#define TEGRA_NVHDCP_FLAG_S 0x0100 /* hdcp_status */ +#define TEGRA_NVHDCP_FLAG_CS 0x0200 /* connection state */ +#define TEGRA_NVHDCP_FLAG_V 0x0400 +#define TEGRA_NVHDCP_FLAG_MP 0x0800 +#define TEGRA_NVHDCP_FLAG_BKSVLIST 0x1000 + +/* values for packet_results */ +#define TEGRA_NVHDCP_RESULT_SUCCESS 0 +#define TEGRA_NVHDCP_RESULT_UNSUCCESSFUL 1 +#define TEGRA_NVHDCP_RESULT_PENDING 0x103 +#define TEGRA_NVHDCP_RESULT_LINK_FAILED 0xc0000013 +/* TODO: replace with -EINVAL */ +#define TEGRA_NVHDCP_RESULT_INVALID_PARAMETER 0xc000000d +#define TEGRA_NVHDCP_RESULT_INVALID_PARAMETER_MIX 0xc0000030 +/* TODO: replace with -ENOMEM */ +#define TEGRA_NVHDCP_RESULT_NO_MEMORY 0xc0000017 + +struct tegra_nvhdcp_packet { + __u32 value_flags; // (IN/OUT) + __u32 packet_results; // (OUT) + + __u64 c_n; // (IN) upstream exchange number + __u64 c_ksv; // (IN) + + __u32 b_status; // (OUT) link/repeater status + __u64 hdcp_status; // (OUT) READ_S + __u64 cs; // (OUT) Connection State + + __u64 k_prime; // (OUT) + __u64 a_n; // (OUT) + __u64 a_ksv; // (OUT) + __u64 b_ksv; // (OUT) + __u64 d_ksv; // (OUT) + __u8 v_prime[20]; // (OUT) 160-bit + __u64 m_prime; // (OUT) + + // (OUT) Valid KSVs in the bKsvList. Maximum is 127 devices + __u32 num_bksv_list; + + // (OUT) Up to 127 receivers & repeaters + __u64 bksv_list[TEGRA_NVHDCP_MAX_DEVS]; +}; + +/* parameters to TEGRAIO_NVHDCP_SET_POLICY */ +#define TEGRA_NVHDCP_POLICY_ON_DEMAND 0 +#define TEGRA_NVHDCP_POLICY_ALWAYS_ON 1 + +/* ioctls */ +#define TEGRAIO_NVHDCP_ON _IO('F', 0x70) +#define TEGRAIO_NVHDCP_OFF _IO('F', 0x71) +#define TEGRAIO_NVHDCP_SET_POLICY _IOW('F', 0x72, __u32) +#define TEGRAIO_NVHDCP_READ_M _IOWR('F', 0x73, struct tegra_nvhdcp_packet) +#define TEGRAIO_NVHDCP_READ_S _IOWR('F', 0x74, struct tegra_nvhdcp_packet) +#define TEGRAIO_NVHDCP_RENEGOTIATE _IO('F', 0x75) + +#endif |