diff options
-rw-r--r-- | arch/arm/mach-omap2/vp.c | 65 | ||||
-rw-r--r-- | arch/arm/mach-omap2/vp.h | 2 | ||||
-rw-r--r-- | drivers/video/hdmi_ti_4xxx_ip.c | 123 | ||||
-rw-r--r-- | drivers/video/hdmi_ti_4xxx_ip.h | 5 | ||||
-rw-r--r-- | drivers/video/omap2/Kconfig | 1 | ||||
-rw-r--r-- | drivers/video/omap2/Makefile | 1 | ||||
-rw-r--r-- | drivers/video/omap2/dss/dss.h | 6 | ||||
-rw-r--r-- | drivers/video/omap2/dss/hdmi.c | 63 | ||||
-rw-r--r-- | drivers/video/omap2/dss/hdmi_panel.c | 1 | ||||
-rw-r--r-- | drivers/video/omap2/hdcp/Kconfig | 14 | ||||
-rw-r--r-- | drivers/video/omap2/hdcp/Makefile | 8 | ||||
-rw-r--r-- | drivers/video/omap2/hdcp/hdcp.h | 394 | ||||
-rw-r--r-- | drivers/video/omap2/hdcp/hdcp_ddc.c | 310 | ||||
-rw-r--r-- | drivers/video/omap2/hdcp/hdcp_ddc.h | 111 | ||||
-rw-r--r-- | drivers/video/omap2/hdcp/hdcp_lib.c | 837 | ||||
-rw-r--r-- | drivers/video/omap2/hdcp/hdcp_top.c | 1045 | ||||
-rw-r--r-- | include/video/hdmi_ti_4xxx_ip.h | 10 | ||||
-rw-r--r-- | sound/soc/codecs/omap-hdmi-codec.c | 141 |
18 files changed, 3076 insertions, 61 deletions
diff --git a/arch/arm/mach-omap2/vp.c b/arch/arm/mach-omap2/vp.c index 4677921..4202701 100644 --- a/arch/arm/mach-omap2/vp.c +++ b/arch/arm/mach-omap2/vp.c @@ -1,5 +1,6 @@ #include <linux/kernel.h> #include <linux/init.h> +#include <linux/ratelimit.h> #include <plat/common.h> @@ -117,6 +118,21 @@ int omap_vp_update_errorgain(struct voltagedomain *voltdm, return 0; } +#define _MAX_COUNT_ERR 10 +static u8 __vp_debug_error_message_count = _MAX_COUNT_ERR; +/* Dump with stack the first few messages, tone down severity for the rest */ +#define _vp_controlled_err(ARGS...) \ +{ \ + if (__vp_debug_error_message_count) { \ + pr_err(ARGS); \ + dump_stack(); \ + __vp_debug_error_message_count--; \ + } else { \ + pr_err_ratelimited(ARGS); \ + } \ +} + + /* VP force update method of voltage scaling */ int omap_vp_forceupdate_scale(struct voltagedomain *voltdm, unsigned long target_volt) @@ -126,6 +142,18 @@ int omap_vp_forceupdate_scale(struct voltagedomain *voltdm, u8 target_vsel, current_vsel; int ret, timeout = 0; + /* + * Wait for VP idle Typical latency is <2us. Maximum latency is ~100us + * This is an additional allowance to ensure we are in proper state + * to enter into forceupdate state transition. + */ + omap_test_timeout((voltdm->read(vp->vstatus)), VP_IDLE_TIMEOUT, + timeout); + + if (timeout >= VP_IDLE_TIMEOUT) + _vp_controlled_err("%s:vdd_%s idletimdout forceupdate(v=%ld)\n", + __func__, voltdm->name, target_volt); + ret = omap_vc_pre_scale(voltdm, target_volt, &target_vsel, ¤t_vsel); if (ret) return ret; @@ -141,8 +169,11 @@ int omap_vp_forceupdate_scale(struct voltagedomain *voltdm, udelay(1); } if (timeout >= VP_TRANXDONE_TIMEOUT) { - pr_warning("%s: vdd_%s TRANXDONE timeout exceeded." - "Voltage change aborted", __func__, voltdm->name); + _vp_controlled_err("%s: vdd_%s TRANXDONE timeout exceeded." + "Voltage change aborted target volt=%ld," + "target vsel=0x%02x, current_vsel=0x%02x\n", + __func__, voltdm->name, target_volt, + target_vsel, current_vsel); return -ETIMEDOUT; } @@ -171,9 +202,12 @@ int omap_vp_forceupdate_scale(struct voltagedomain *voltdm, omap_test_timeout(vp->common->ops->check_txdone(vp->id), VP_TRANXDONE_TIMEOUT, timeout); if (timeout >= VP_TRANXDONE_TIMEOUT) - pr_err("%s: vdd_%s TRANXDONE timeout exceeded." - "TRANXDONE never got set after the voltage update\n", - __func__, voltdm->name); + _vp_controlled_err("%s: vdd_%s TRANXDONE timeout exceeded. " + "TRANXDONE never got set after the voltage update. " + "target volt=%ld, target vsel=0x%02x, " + "current_vsel=0x%02x\n", + __func__, voltdm->name, target_volt, + target_vsel, current_vsel); omap_vc_post_scale(voltdm, target_volt, target_vsel, current_vsel); @@ -190,9 +224,11 @@ int omap_vp_forceupdate_scale(struct voltagedomain *voltdm, } if (timeout >= VP_TRANXDONE_TIMEOUT) - pr_warning("%s: vdd_%s TRANXDONE timeout exceeded while trying" - "to clear the TRANXDONE status\n", - __func__, voltdm->name); + _vp_controlled_err("%s: vdd_%s TRANXDONE timeout exceeded while" + "trying to clear the TRANXDONE status. target volt=%ld," + "target vsel=0x%02x, current_vsel=0x%02x\n", + __func__, voltdm->name, target_volt, + target_vsel, current_vsel); vpconfig = voltdm->read(vp->vpconfig); /* Clear initVDD copy trigger bit */ @@ -308,6 +344,17 @@ void omap_vp_disable(struct voltagedomain *voltdm) return; } + /* + * Wait for VP idle Typical latency is <2us. Maximum latency is ~100us + * Depending on if we catch VP in the middle of an SR operation. + */ + omap_test_timeout((voltdm->read(vp->vstatus)), + VP_IDLE_TIMEOUT, timeout); + + if (timeout >= VP_IDLE_TIMEOUT) + pr_warning("%s: vdd_%s idle timedout before disable\n", + __func__, voltdm->name); + /* Disable VP */ vpconfig = voltdm->read(vp->vpconfig); vpconfig &= ~vp->common->vpconfig_vpenable; @@ -320,7 +367,7 @@ void omap_vp_disable(struct voltagedomain *voltdm) VP_IDLE_TIMEOUT, timeout); if (timeout >= VP_IDLE_TIMEOUT) - pr_warning("%s: vdd_%s idle timedout\n", + pr_warning("%s: vdd_%s idle timedout after disable\n", __func__, voltdm->name); vp->enabled = false; diff --git a/arch/arm/mach-omap2/vp.h b/arch/arm/mach-omap2/vp.h index 57a4d44..ea3f9ee 100644 --- a/arch/arm/mach-omap2/vp.h +++ b/arch/arm/mach-omap2/vp.h @@ -22,7 +22,7 @@ struct voltagedomain; /* XXX document */ -#define VP_IDLE_TIMEOUT 200 +#define VP_IDLE_TIMEOUT 500 #define VP_TRANXDONE_TIMEOUT 300 /** diff --git a/drivers/video/hdmi_ti_4xxx_ip.c b/drivers/video/hdmi_ti_4xxx_ip.c index c105442..3cbcbe3 100644 --- a/drivers/video/hdmi_ti_4xxx_ip.c +++ b/drivers/video/hdmi_ti_4xxx_ip.c @@ -148,10 +148,30 @@ static int hdmi_pll_init(struct hdmi_ip_data *ip_data, return 0; } +static int hdmi_wait_for_audio_stop(struct hdmi_ip_data *ip_data) +{ + int count = 0; + /* wait for audio to stop before powering off the phy*/ + while (REG_GET(hdmi_wp_base(ip_data), + HDMI_WP_AUDIO_CTRL, 31, 31) != 0) { + msleep(100); + if (count++ > 100) { + pr_err("Audio is not turned off " + "even after 10 seconds\n"); + return -ETIMEDOUT; + } + } + return 0; +} /* PHY_PWR_CMD */ -static int hdmi_set_phy_pwr(struct hdmi_ip_data *ip_data, enum hdmi_phy_pwr val) +static int hdmi_set_phy_pwr(struct hdmi_ip_data *ip_data, + enum hdmi_phy_pwr val, bool set_mode) { + /* FIXME audio driver should have already stopped, but not yet */ + if (val == HDMI_PHYPWRCMD_OFF && !set_mode) + hdmi_wait_for_audio_stop(ip_data); + /* Command for power control of HDMI PHY */ REG_FLD_MOD(hdmi_wp_base(ip_data), HDMI_WP_PWR_CTRL, val, 7, 6); @@ -228,11 +248,11 @@ int hdmi_ti_4xxx_phy_init(struct hdmi_ip_data *ip_data) { u16 r = 0; - r = hdmi_set_phy_pwr(ip_data, HDMI_PHYPWRCMD_LDOON); + r = hdmi_set_phy_pwr(ip_data, HDMI_PHYPWRCMD_LDOON, false); if (r) return r; - r = hdmi_set_phy_pwr(ip_data, HDMI_PHYPWRCMD_TXON); + r = hdmi_set_phy_pwr(ip_data, HDMI_PHYPWRCMD_TXON, false); if (r) return r; @@ -259,9 +279,9 @@ int hdmi_ti_4xxx_phy_init(struct hdmi_ip_data *ip_data) return 0; } -void hdmi_ti_4xxx_phy_off(struct hdmi_ip_data *ip_data) +void hdmi_ti_4xxx_phy_off(struct hdmi_ip_data *ip_data, bool set_mode) { - hdmi_set_phy_pwr(ip_data, HDMI_PHYPWRCMD_OFF); + hdmi_set_phy_pwr(ip_data, HDMI_PHYPWRCMD_OFF, set_mode); } EXPORT_SYMBOL(hdmi_ti_4xxx_phy_init); EXPORT_SYMBOL(hdmi_ti_4xxx_phy_off); @@ -281,7 +301,7 @@ static int hdmi_core_ddc_edid(struct hdmi_ip_data *ip_data, * right shifted values( The behavior is not consistent and seen only * with some TV's) */ - usleep_range(800, 1000); + msleep(300); if (!ext) { /* Clk SCL Devices */ @@ -500,6 +520,8 @@ static void hdmi_core_video_config(struct hdmi_ip_data *ip_data, r = FLD_MOD(r, HDMI_CORE_CTRL1_HEN_FOLLOWHSYNC, 4, 4); r = FLD_MOD(r, HDMI_CORE_CTRL1_BSEL_24BITBUS, 2, 2); r = FLD_MOD(r, HDMI_CORE_CTRL1_EDGE_RISINGEDGE, 1, 1); + /* PD bit has to be written to recieve the interrupts */ + r = FLD_MOD(r, HDMI_CORE_CTRL1_POWER_DOWN, 0, 0); hdmi_write_reg(hdmi_core_sys_base(ip_data), HDMI_CORE_CTRL1, r); REG_FLD_MOD(hdmi_core_sys_base(ip_data), @@ -654,6 +676,36 @@ void hdmi_ti_4xxx_wp_video_start(struct hdmi_ip_data *ip_data, bool start) } EXPORT_SYMBOL(hdmi_ti_4xxx_wp_video_start); +int hdmi_ti_4xxx_wp_get_video_state(struct hdmi_ip_data *ip_data) +{ + u32 status = hdmi_read_reg(hdmi_wp_base(ip_data), HDMI_WP_VIDEO_CFG); + + return (status & 0x80000000) ? 1 : 0; +} + +int hdmi_ti_4xxx_set_wait_soft_reset(struct hdmi_ip_data *ip_data) +{ + u8 count = 0; + + /* reset W1 */ + REG_FLD_MOD(hdmi_wp_base(ip_data), HDMI_WP_SYSCONFIG, 0x1, 0, 0); + + /* wait till SOFTRESET == 0 */ + while (hdmi_wait_for_bit_change(hdmi_wp_base(ip_data), + HDMI_WP_SYSCONFIG, 0, 0, 0) != 0) { + if (count++ > 10) { + pr_err("SYSCONFIG[SOFTRESET] bit not set to 0\n"); + return -ETIMEDOUT; + } + } + + /* Make madule smart and wakeup capable*/ + REG_FLD_MOD(hdmi_wp_base(ip_data), HDMI_WP_SYSCONFIG, 0x3, 3, 2); + + return 0; +} + + static void hdmi_wp_video_init_format(struct hdmi_video_format *video_fmt, struct omap_video_timings *timings, struct hdmi_config *param) { @@ -711,6 +763,16 @@ static void hdmi_wp_video_config_timing(struct hdmi_ip_data *ip_data, hdmi_write_reg(hdmi_wp_base(ip_data), HDMI_WP_VIDEO_TIMING_V, timing_v); } +static void hdmi_wp_core_interrupt_set(struct hdmi_ip_data *ip_data, u32 val) +{ + u32 irqStatus; + irqStatus = hdmi_read_reg(hdmi_wp_base(ip_data), HDMI_WP_IRQENABLE_SET); + pr_debug("[HDMI] WP_IRQENABLE_SET..currently reads as:%x\n", irqStatus); + irqStatus = irqStatus | val; + hdmi_write_reg(hdmi_wp_base(ip_data), HDMI_WP_IRQENABLE_SET, irqStatus); + pr_debug("[HDMI]WP_IRQENABLE_SET..changed to :%x\n", irqStatus); +} + void hdmi_ti_4xxx_basic_configure(struct hdmi_ip_data *ip_data, struct hdmi_config *cfg) { @@ -730,6 +792,8 @@ void hdmi_ti_4xxx_basic_configure(struct hdmi_ip_data *ip_data, &avi_cfg, &repeat_cfg); + hdmi_wp_core_interrupt_set(ip_data, HDMI_WP_IRQENABLE_CORE); + hdmi_wp_video_init_format(&video_format, &video_timing, cfg); hdmi_wp_video_config_timing(ip_data, &video_timing); @@ -804,6 +868,53 @@ void hdmi_ti_4xxx_basic_configure(struct hdmi_ip_data *ip_data, } EXPORT_SYMBOL(hdmi_ti_4xxx_basic_configure); +u32 hdmi_ti_4xxx_irq_handler(struct hdmi_ip_data *ip_data) +{ + u32 val, sys_stat = 0, core_state = 0; + u32 intr2 = 0, intr3 = 0, r = 0; + void __iomem *wp_base = hdmi_wp_base(ip_data); + void __iomem *core_base = hdmi_core_sys_base(ip_data); + + pr_debug("Enter hdmi_ti_4xxx_irq_handler\n"); + + val = hdmi_read_reg(wp_base, HDMI_WP_IRQSTATUS); + if (val & HDMI_WP_IRQSTATUS_CORE) { + core_state = hdmi_read_reg(core_base, HDMI_CORE_SYS_INTR_STATE); + if (core_state & 0x1) { + sys_stat = hdmi_read_reg(core_base, + HDMI_CORE_SYS_SYS_STAT); + intr2 = hdmi_read_reg(core_base, HDMI_CORE_SYS_INTR2); + intr3 = hdmi_read_reg(core_base, HDMI_CORE_SYS_INTR3); + + pr_debug("HDMI_CORE_SYS_SYS_STAT = 0x%x\n", sys_stat); + pr_debug("HDMI_CORE_SYS_INTR2 = 0x%x\n", intr2); + pr_debug("HDMI_CORE_SYS_INTR3 = 0x%x\n", intr3); + + hdmi_write_reg(core_base, HDMI_CORE_SYS_INTR2, intr2); + hdmi_write_reg(core_base, HDMI_CORE_SYS_INTR3, intr3); + + hdmi_read_reg(core_base, HDMI_CORE_SYS_INTR2); + hdmi_read_reg(core_base, HDMI_CORE_SYS_INTR3); + } + } + + pr_debug("HDMI_WP_IRQSTATUS = 0x%x\n", val); + pr_debug("HDMI_CORE_SYS_INTR_STATE = 0x%x\n", core_state); + + if (intr2 & HDMI_CORE_SYSTEM_INTR2__BCAP) + r |= HDMI_BCAP; + + if (intr3 & HDMI_CORE_SYSTEM_INTR3__RI_ERR) + r |= HDMI_RI_ERR; + + /* Ack other interrupts if any */ + hdmi_write_reg(wp_base, HDMI_WP_IRQSTATUS, val); + /* flush posted write */ + hdmi_read_reg(wp_base, HDMI_WP_IRQSTATUS); + return r; +} +EXPORT_SYMBOL(hdmi_ti_4xxx_irq_handler); + void hdmi_ti_4xxx_dump_regs(struct hdmi_ip_data *ip_data, struct seq_file *s) { #define DUMPREG(g, r) seq_printf(s, "%-35s %08x\n", #r, hdmi_read_reg(g, r)) diff --git a/drivers/video/hdmi_ti_4xxx_ip.h b/drivers/video/hdmi_ti_4xxx_ip.h index 6c31e70..4572d66 100644 --- a/drivers/video/hdmi_ti_4xxx_ip.h +++ b/drivers/video/hdmi_ti_4xxx_ip.h @@ -47,6 +47,8 @@ struct hdmi_reg { u16 idx; }; #define HDMI_WP_AUDIO_CFG2 HDMI_WP_REG(0x84) #define HDMI_WP_AUDIO_CTRL HDMI_WP_REG(0x88) #define HDMI_WP_AUDIO_DATA HDMI_WP_REG(0x8C) +#define HDMI_WP_IRQSTATUS_CORE 0x1 +#define HDMI_WP_IRQENABLE_CORE 0x1 /* HDMI IP Core System */ #define HDMI_CORE_SYS_REG(idx) HDMI_REG(idx) @@ -78,6 +80,9 @@ struct hdmi_reg { u16 idx; }; #define HDMI_CORE_CTRL1_HEN_FOLLOWHSYNC 0x1 #define HDMI_CORE_CTRL1_BSEL_24BITBUS 0x1 #define HDMI_CORE_CTRL1_EDGE_RISINGEDGE 0x1 +#define HDMI_CORE_CTRL1_POWER_DOWN 0x1 +#define HDMI_CORE_SYSTEM_INTR2__BCAP 0x80 +#define HDMI_CORE_SYSTEM_INTR3__RI_ERR 0xF0 /* HDMI DDC E-DID */ #define HDMI_CORE_DDC_CMD HDMI_CORE_SYS_REG(0x3CC) diff --git a/drivers/video/omap2/Kconfig b/drivers/video/omap2/Kconfig index 97a5bc4..c4ec41a 100644 --- a/drivers/video/omap2/Kconfig +++ b/drivers/video/omap2/Kconfig @@ -8,3 +8,4 @@ source "drivers/video/omap2/dss/Kconfig" source "drivers/video/omap2/omapfb/Kconfig" source "drivers/video/omap2/displays/Kconfig" source "drivers/video/omap2/dsscomp/Kconfig" +source "drivers/video/omap2/hdcp/Kconfig" diff --git a/drivers/video/omap2/Makefile b/drivers/video/omap2/Makefile index 2d5a205..ceb1dd9 100644 --- a/drivers/video/omap2/Makefile +++ b/drivers/video/omap2/Makefile @@ -3,5 +3,6 @@ obj-$(CONFIG_OMAP2_VRFB) += vrfb.o obj-$(CONFIG_OMAP2_DSS) += dss/ obj-$(CONFIG_FB_OMAP2) += omapfb/ +obj-$(CONFIG_OMAP4_HDCP) += hdcp/ obj-y += displays/ obj-y += dsscomp/ diff --git a/drivers/video/omap2/dss/dss.h b/drivers/video/omap2/dss/dss.h index 0dc6b5c..e508e05 100644 --- a/drivers/video/omap2/dss/dss.h +++ b/drivers/video/omap2/dss/dss.h @@ -515,6 +515,7 @@ int omapdss_hdmi_display_check_timing(struct omap_dss_device *dssdev, struct omap_video_timings *timings); int omapdss_hdmi_display_set_mode(struct omap_dss_device *dssdev, struct fb_videomode *mode); +void omapdss_hdmi_restart(void); int hdmi_panel_hpd_handler(int hpd); int omapdss_hdmi_get_pixel_clock(void); int omapdss_hdmi_get_mode(void); @@ -523,11 +524,14 @@ void omapdss_hdmi_set_deepcolor(int val); int hdmi_get_current_hpd(void); void hdmi_get_monspecs(struct fb_monspecs *specs); u8 *hdmi_read_edid(struct omap_video_timings *); +void hdmi_load_hdcp_keys(struct omap_dss_device *dssdev); int hdmi_panel_init(void); void hdmi_panel_exit(void); void hdmi_dump_regs(struct seq_file *s); - +int omapdss_hdmi_register_hdcp_callbacks(void (*hdmi_start_frame_cb)(void), + void (*hdmi_irq_cb)(int status), + void (*hdmi_power_on_cb)(void)); int omap_dss_ovl_set_info(struct omap_overlay *ovl, struct omap_overlay_info *info); diff --git a/drivers/video/omap2/dss/hdmi.c b/drivers/video/omap2/dss/hdmi.c index 2ab0368..c244634 100644 --- a/drivers/video/omap2/dss/hdmi.c +++ b/drivers/video/omap2/dss/hdmi.c @@ -76,11 +76,17 @@ static struct { struct hdmi_config cfg; struct regulator *hdmi_reg; + int hdmi_irq; struct clk *sys_clk; struct clk *hdmi_clk; int runtime_count; int enabled; + bool set_mode; + + void (*hdmi_start_frame_cb)(void); + void (*hdmi_irq_cb)(int); + void (*hdmi_power_on_cb)(void); } hdmi; static const u8 edid_header[8] = {0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0}; @@ -415,6 +421,9 @@ static int hdmi_power_on(struct omap_dss_device *dssdev) hdmi_ti_4xxx_wp_video_start(&hdmi.hdmi_data, 1); + if (hdmi.hdmi_start_frame_cb && hdmi.custom_set) + (*hdmi.hdmi_start_frame_cb)(); + return 0; err: hdmi_runtime_put(); @@ -426,12 +435,21 @@ static void hdmi_power_off(struct omap_dss_device *dssdev) hdmi_ti_4xxx_wp_video_start(&hdmi.hdmi_data, 0); dispc_enable_channel(OMAP_DSS_CHANNEL_DIGIT, dssdev->type, 0); - hdmi_ti_4xxx_phy_off(&hdmi.hdmi_data); + hdmi_ti_4xxx_phy_off(&hdmi.hdmi_data, hdmi.set_mode); hdmi_ti_4xxx_set_pll_pwr(&hdmi.hdmi_data, HDMI_PLLPWRCMD_ALLOFF); hdmi_runtime_put(); hdmi.deep_color = HDMI_DEEP_COLOR_24BIT; } +void hdmi_load_hdcp_keys(struct omap_dss_device *dssdev) +{ + /* load the keys and reset the wrapper to populate the AKSV registers*/ + if (hdmi.hdmi_power_on_cb) { + hdmi.hdmi_power_on_cb(); + hdmi_ti_4xxx_set_wait_soft_reset(&hdmi.hdmi_data); + } +} + int omapdss_hdmi_get_pixel_clock(void) { return PICOS2KHZ(hdmi.cfg.timings.pixclock); @@ -442,6 +460,18 @@ int omapdss_hdmi_get_mode(void) return hdmi.mode; } +int omapdss_hdmi_register_hdcp_callbacks(void (*hdmi_start_frame_cb)(void), + void (*hdmi_irq_cb)(int status), + void (*hdmi_power_on_cb)(void)) +{ + hdmi.hdmi_start_frame_cb = hdmi_start_frame_cb; + hdmi.hdmi_irq_cb = hdmi_irq_cb; + hdmi.hdmi_power_on_cb = hdmi_power_on_cb; + + return hdmi_ti_4xxx_wp_get_video_state(&hdmi.hdmi_data); +} +EXPORT_SYMBOL(omapdss_hdmi_register_hdcp_callbacks); + void omapdss_hdmi_set_deepcolor(int val) { hdmi.deep_color = val; @@ -462,11 +492,27 @@ static irqreturn_t hpd_irq_handler(int irq, void *ptr) int hpd = hdmi_get_current_hpd(); pr_info("hpd %d\n", hpd); + if (!hpd && hdmi.hdmi_irq_cb) + hdmi.hdmi_irq_cb(HDMI_HPD_LOW); hdmi_panel_hpd_handler(hpd); return IRQ_HANDLED; } +static irqreturn_t hdmi_irq_handler(int irq, void *arg) +{ + int r = 0; + + r = hdmi_ti_4xxx_irq_handler(&hdmi.hdmi_data); + + DSSDBG("Received HDMI IRQ = %08x\n", r); + + if (hdmi.hdmi_irq_cb) + hdmi.hdmi_irq_cb(r); + + return IRQ_HANDLED; +} + int omapdss_hdmi_display_check_timing(struct omap_dss_device *dssdev, struct omap_video_timings *timings) { @@ -489,12 +535,14 @@ int omapdss_hdmi_display_set_mode(struct omap_dss_device *dssdev, { int r1, r2; /* turn the hdmi off and on to get new timings to use */ - omapdss_hdmi_display_disable(dssdev); + hdmi.set_mode = true; + dssdev->driver->disable(dssdev); + hdmi.set_mode = false; r1 = hdmi_set_timings(vm, false) ? 0 : -EINVAL; hdmi.custom_set = 1; hdmi.code = hdmi.cfg.cm.code; hdmi.mode = hdmi.cfg.cm.mode; - r2 = omapdss_hdmi_display_enable(dssdev); + r2 = dssdev->driver->enable(dssdev); return r1 ? : r2; } @@ -692,6 +740,15 @@ static int omapdss_hdmihw_probe(struct platform_device *pdev) return -EINVAL; } + hdmi.hdmi_irq = platform_get_irq(pdev, 0); + + r = request_irq(hdmi.hdmi_irq, hdmi_irq_handler, 0, "OMAP HDMI", NULL); + if (r < 0) { + pr_err("hdmi: request_irq %s failed\n", + pdev->name); + return -EINVAL; + } + hdmi.hdmi_data.hdmi_core_sys_offset = HDMI_CORE_SYS; hdmi.hdmi_data.hdmi_core_av_offset = HDMI_CORE_AV; hdmi.hdmi_data.hdmi_pll_offset = HDMI_PLLCTRL; diff --git a/drivers/video/omap2/dss/hdmi_panel.c b/drivers/video/omap2/dss/hdmi_panel.c index 0fa5dea..c17fad5 100644 --- a/drivers/video/omap2/dss/hdmi_panel.c +++ b/drivers/video/omap2/dss/hdmi_panel.c @@ -224,6 +224,7 @@ static void hdmi_hotplug_detect_worker(struct work_struct *work) dssdev->panel.monspecs.max_x * 10000; dssdev->panel.height_in_um = dssdev->panel.monspecs.max_y * 10000; + hdmi_load_hdcp_keys(dssdev); switch_set_state(&hdmi.hpd_switch, 1); goto done; } else if (state == HPD_STATE_EDID_TRYLAST){ diff --git a/drivers/video/omap2/hdcp/Kconfig b/drivers/video/omap2/hdcp/Kconfig new file mode 100644 index 0000000..a18f183 --- /dev/null +++ b/drivers/video/omap2/hdcp/Kconfig @@ -0,0 +1,14 @@ +menuconfig OMAP4_HDCP + bool "OMAP4 HDCP support" + depends on OMAP2_DSS && OMAP4_DSS_HDMI + default n + help + HDCP Interface. This adds the High Definition Content Protection Interface. + See http://www.digital-cp.com/ for HDCP specification. + +config OMAP4_HDCP_DEBUG + bool "OMAP4 HDCP Debugging" + depends on OMAP4_HDCP + default n + help + Enableds verbose debugging the the HDCP drivers diff --git a/drivers/video/omap2/hdcp/Makefile b/drivers/video/omap2/hdcp/Makefile new file mode 100644 index 0000000..80b0c0c --- /dev/null +++ b/drivers/video/omap2/hdcp/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for HDCP linux kernel module. +# + +ccflags-$(CONFIG_OMAP4_HDCP_DEBUG) = -DDEBUG -DHDCP_DEBUG + +obj-$(CONFIG_OMAP4_HDCP) += hdcp.o +hdcp-y := hdcp_top.o hdcp_lib.o hdcp_ddc.o diff --git a/drivers/video/omap2/hdcp/hdcp.h b/drivers/video/omap2/hdcp/hdcp.h new file mode 100644 index 0000000..e22a588 --- /dev/null +++ b/drivers/video/omap2/hdcp/hdcp.h @@ -0,0 +1,394 @@ +/* + * hdcp.h + * + * HDCP interface DSS driver setting for TI's OMAP4 family of processor. + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ + * Authors: Fabrice Olivero + * Fabrice Olivero <f-olivero@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _HDCP_H_ +#define _HDCP_H_ + + +/********************************/ +/* Structures related to ioctl */ +/********************************/ + +/* HDCP key size in 32-bit words */ +#define DESHDCP_KEY_SIZE 160 + +/* HDCP ioctl */ +#include <linux/ioctl.h> +#include <linux/types.h> + +struct hdcp_encrypt_control { + uint32_t in_key[DESHDCP_KEY_SIZE]; + uint32_t *out_key; +}; + +struct hdcp_enable_control { + uint32_t key[DESHDCP_KEY_SIZE]; + int nb_retry; +}; + +#define MAX_SHA_DATA_SIZE 645 +#define MAX_SHA_VPRIME_SIZE 20 + +struct hdcp_sha_in { + uint8_t data[MAX_SHA_DATA_SIZE]; + uint32_t byte_counter; + uint8_t vprime[MAX_SHA_VPRIME_SIZE]; +}; + +struct hdcp_wait_control { + uint32_t event; + struct hdcp_sha_in *data; +}; + +/* HDCP ioctl */ +#define HDCP_IOCTL_MAGIC 'h' +#define HDCP_ENABLE _IOW(HDCP_IOCTL_MAGIC, 0, \ + struct hdcp_enable_control) +#define HDCP_DISABLE _IO(HDCP_IOCTL_MAGIC, 1) +#define HDCP_ENCRYPT_KEY _IOWR(HDCP_IOCTL_MAGIC, 2, \ + struct hdcp_encrypt_control) +#define HDCP_QUERY_STATUS _IOWR(HDCP_IOCTL_MAGIC, 3, uint32_t) +#define HDCP_WAIT_EVENT _IOWR(HDCP_IOCTL_MAGIC, 4, \ + struct hdcp_wait_control) +#define HDCP_DONE _IOW(HDCP_IOCTL_MAGIC, 5, uint32_t) + +/* HDCP state */ +#define HDCP_STATE_DISABLED 0 +#define HDCP_STATE_INIT 1 +#define HDCP_STATE_AUTH_1ST_STEP 2 +#define HDCP_STATE_AUTH_2ND_STEP 3 +#define HDCP_STATE_AUTH_3RD_STEP 4 +#define HDCP_STATE_AUTH_FAIL_RESTARTING 5 +#define HDCP_STATE_AUTH_FAILURE 6 + +/* HDCP events */ +#define HDCP_EVENT_STEP1 (1 << 0x0) +#define HDCP_EVENT_STEP2 (1 << 0x1) +#define HDCP_EVENT_EXIT (1 << 0x2) + +/* HDCP user space status */ +#define HDCP_US_NO_ERR (0 << 8) +#define HDCP_US_FAILURE (1 << 8) + +#ifdef __KERNEL__ + +#include <linux/mutex.h> +#include <linux/workqueue.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/fs.h> + +#define _9032_AUTO_RI_ /* Auto Ri mode */ +#define _9032_BCAP_ /* BCAP polling */ +#undef _9032_AN_STOP_FIX_ + +#ifdef DEBUG +#define DDC_DBG /* Log DDC data */ +#undef POWER_TRANSITION_DBG /* Add wait loops to allow testing DSS power + * transition during HDCP */ +#endif + +/***************************/ +/* HW specific definitions */ +/***************************/ + +/* DESHDCP base address */ +/*----------------------*/ + +#define DSS_SS_FROM_L3__DESHDCP 0x58007000 + +/* DESHDCP registers */ +#define DESHDCP__DHDCP_CTRL 0x020 +#define DESHDCP__DHDCP_DATA_L 0x024 +#define DESHDCP__DHDCP_DATA_H 0x028 + +/* DESHDCP CTRL bits */ +#define DESHDCP__DHDCP_CTRL__DIRECTION_POS_F 2 +#define DESHDCP__DHDCP_CTRL__DIRECTION_POS_L 2 + +#define DESHDCP__DHDCP_CTRL__OUTPUT_READY_POS_F 0 +#define DESHDCP__DHDCP_CTRL__OUTPUT_READY_POS_L 0 + +/* HDMI WP base address */ +/*----------------------*/ +#define HDMI_WP 0x58006000 + +/* HDMI CORE SYSTEM base address */ +/*-------------------------------*/ + +#define HDMI_IP_CORE_SYSTEM 0x400 + +/* HDMI CORE registers */ +#define HDMI_IP_CORE_SYSTEM__DCTL 0x034 + +#define HDMI_IP_CORE_SYSTEM__HDCP_CTRL 0x03C + +#define HDMI_IP_CORE_SYSTEM__BKSV0 0x040 + +#define HDMI_IP_CORE_SYSTEM__AN0 0x054 + +#define HDMI_IP_CORE_SYSTEM__AKSV0 0x074 + +#define HDMI_IP_CORE_SYSTEM__R1 0x088 +#define HDMI_IP_CORE_SYSTEM__R2 0x08C + +#define HDMI_IP_CORE_SYSTEM__RI_CMD 0x09C +#define HDMI_IP_CORE_SYSTEM__RI_STAT 0x098 + +#define HDMI_IP_CORE_SYSTEM__INTR2 0x1C8 +#define HDMI_IP_CORE_SYSTEM__INTR3 0x1CC + +#define HDMI_IP_CORE_SYSTEM__INT_UNMASK2 0x1D8 +#define HDMI_IP_CORE_SYSTEM__INT_UNMASK3 0x1DC + +#define HDMI_IP_CORE_SYSTEM__SHA_CTRL 0x330 + +#define HDMI_IP_CORE_SYSTEM__INTR2__BCAP 0x80 +#define HDMI_IP_CORE_SYSTEM__INTR3__RI_ERR 0xF0 + +enum hdcp_repeater { + HDCP_RECEIVER = 0, + HDCP_REPEATER = 1 +}; + +enum encryption_state { + HDCP_ENC_OFF = 0x0, + HDCP_ENC_ON = 0x1 +}; + +/* HDMI CORE AV base address */ +/*---------------------------*/ + +#define HDMI_CORE_AV_BASE 0x900 +#ifndef HDMI_CORE_AV_HDMI_CTRL +#define HDMI_CORE_AV_HDMI_CTRL 0x0BC +#define HDMI_CORE_AV_PB_CTRL2 0x0FC +#define HDMI_CORE_AV_CP_BYTE1 0x37C +#endif + +#define HDMI_CORE_AV_HDMI_CTRL__HDMI_MODE 0x01 + +enum av_mute { + AV_MUTE_SET = 0x01, + AV_MUTE_CLEAR = 0x10 +}; +/***********************/ +/* HDCP DDC addresses */ +/***********************/ + +#define DDC_BKSV_ADDR 0x00 +#define DDC_Ri_ADDR 0x08 +#define DDC_AKSV_ADDR 0x10 +#define DDC_AN_ADDR 0x18 +#define DDC_V_ADDR 0x20 +#define DDC_BCAPS_ADDR 0x40 +#define DDC_BSTATUS_ADDR 0x41 +#define DDC_KSV_FIFO_ADDR 0x43 + +#define DDC_BKSV_LEN 5 +#define DDC_Ri_LEN 2 +#define DDC_AKSV_LEN 5 +#define DDC_AN_LEN 8 +#define DDC_V_LEN 20 +#define DDC_BCAPS_LEN 1 +#define DDC_BSTATUS_LEN 2 + +#define DDC_BIT_REPEATER 6 + +#define DDC_BSTATUS0_MAX_DEVS 0x80 +#define DDC_BSTATUS0_DEV_COUNT 0x7F +#define DDC_BSTATUS1_MAX_CASC 0x08 + +/***************************/ +/* Definitions */ +/***************************/ + +/* Status / error codes */ +#define HDCP_OK 0 +#define HDCP_DDC_ERROR 1 +#define HDCP_AUTH_FAILURE 2 +#define HDCP_AKSV_ERROR 3 +#define HDCP_3DES_ERROR 4 +#define HDCP_SHA1_ERROR 5 +#define HDCP_DRIVER_ERROR 6 +#define HDCP_CANCELLED_AUTH 7 + +#define HDCP_INFINITE_REAUTH 0x100 +#define HDCP_MAX_DDC_ERR 5 + +/* FIXME: should be 300ms delay between HDMI start frame event and HDCP enable + * (to respect 7 VSYNC delay in 24 Hz) + */ +#define HDCP_ENABLE_DELAY 300 +#define HDCP_R0_DELAY 110 +#define HDCP_KSV_TIMEOUT_DELAY 5000 +#define HDCP_REAUTH_DELAY 100 + +/* DDC access timeout in ms */ +#define HDCP_DDC_TIMEOUT 500 +#define HDCP_STOP_FRAME_BLOCKING_TIMEOUT (2*HDCP_DDC_TIMEOUT) + +/* Event source */ +#define HDCP_SRC_SHIFT 8 +#define HDCP_IOCTL_SRC (0x1 << HDCP_SRC_SHIFT) +#define HDCP_HDMI_SRC (0x2 << HDCP_SRC_SHIFT) +#define HDCP_IRQ_SRC (0x4 << HDCP_SRC_SHIFT) +#define HDCP_WORKQUEUE_SRC (0x8 << HDCP_SRC_SHIFT) + +/* Workqueue events */ +/* Note: HDCP_ENABLE_CTL, HDCP_R0_EXP_EVENT, HDCP_KSV_TIMEOUT_EVENT and + * HDCP_AUTH_REATT_EVENT can be cancelled by HDCP disabling + */ +#define HDCP_ENABLE_CTL (HDCP_IOCTL_SRC | 0) +#define HDCP_DISABLE_CTL (HDCP_IOCTL_SRC | 1) +#define HDCP_START_FRAME_EVENT (HDCP_HDMI_SRC | 2) +#define HDCP_STOP_FRAME_EVENT (HDCP_HDMI_SRC | 3) +#define HDCP_HPD_LOW_EVENT (HDCP_IRQ_SRC | 4) +#define HDCP_RI_FAIL_EVENT (HDCP_IRQ_SRC | 5) +#define HDCP_KSV_LIST_RDY_EVENT (HDCP_IRQ_SRC | 6) +#define HDCP_R0_EXP_EVENT (HDCP_WORKQUEUE_SRC | 7) +#define HDCP_KSV_TIMEOUT_EVENT (HDCP_WORKQUEUE_SRC | 8) +#define HDCP_AUTH_REATT_EVENT (HDCP_WORKQUEUE_SRC | 9) + +/* IRQ status */ +#define HDCP_IRQ_RI_FAIL 0x01 +#define HDCP_IRQ_KSV_RDY 0x02 + +enum hdcp_states { + HDCP_DISABLED, + HDCP_ENABLE_PENDING, + HDCP_AUTHENTICATION_START, + HDCP_WAIT_R0_DELAY, + HDCP_WAIT_KSV_LIST, + HDCP_LINK_INTEGRITY_CHECK, + HDCP_KEY_ENCRYPTION_ONGOING +}; + +enum hdmi_states { + HDMI_STOPPED, + HDMI_STARTED +}; + +struct hdcp_delayed_work { + struct delayed_work work; + int event; +}; + +struct hdcp { + void __iomem *hdmi_wp_base_addr; + void __iomem *deshdcp_base_addr; + struct mutex lock; + struct hdcp_enable_control *en_ctrl; + dev_t dev_id; + struct class *hdcp_class; + enum hdmi_states hdmi_state; + enum hdcp_states hdcp_state; + int auth_state; + struct delayed_work *pending_start; + /* Following variable should store works submitted from workqueue + * context + * WARNING: only ONE work at a time can be stored (no conflict + * should happen). It is used to allow cancelled pending works when + * disabling HDCP + */ + struct delayed_work *pending_wq_event; + int retry_cnt; + int dss_state; + int pending_disable; + int hdmi_restart; + int hpd_low; + spinlock_t spinlock; + struct workqueue_struct *workqueue; + int hdcp_up_event; + int hdcp_down_event; + bool hdcp_keys_loaded; +}; + +extern struct hdcp hdcp; +extern struct hdcp_sha_in sha_input; + + +/***************************/ +/* Macros for accessing HW */ +/***************************/ + +#define WR_REG_32(base, offset, val) __raw_writel(val, base + offset) +#define RD_REG_32(base, offset) __raw_readl(base + offset) + + +#undef FLD_MASK +#define FLD_MASK(start, end) (((1 << (start - end + 1)) - 1) << (end)) +#undef FLD_VAL +#define FLD_VAL(val, start, end) (((val) << end) & FLD_MASK(start, end)) +#define FLD_GET(val, start, end) (((val) & FLD_MASK(start, end)) >> (end)) +#define FLD_MOD(orig, val, start, end) \ + (((orig) & ~FLD_MASK(start, end)) | FLD_VAL(val, start, end)) + +#define WR_FIELD_32(base, offset, start, end, val) \ + WR_REG_32(base, offset, FLD_MOD(RD_REG_32(base, offset), val, \ + start, end)) + +#define RD_FIELD_32(base, offset, start, end) \ + ((RD_REG_32(base, offset) & FLD_MASK(start, end)) >> (end)) + + +#undef DBG + +#ifdef HDCP_DEBUG +#define DBG(format, ...) \ + printk(KERN_DEBUG "HDCP: " format "\n", ## __VA_ARGS__) +#else +#define DBG(format, ...) +#endif + +/***************************/ +/* Function prototypes */ +/***************************/ + +int hdcp_user_space_task(int flags); + +/* 3DES */ +int hdcp_3des_load_key(uint32_t *deshdcp_encrypted_key); +void hdcp_3des_encrypt_key(struct hdcp_encrypt_control *enc_ctrl, + uint32_t out_key[DESHDCP_KEY_SIZE]); + +/* IP control */ +int hdcp_lib_disable(void); +int hdcp_lib_step1_start(void); +int hdcp_lib_step1_r0_check(void); +int hdcp_lib_step2(void); +int hdcp_lib_irq(void); +void hdcp_lib_auto_ri_check(bool state); +void hdcp_lib_auto_bcaps_rdy_check(bool state); +void hdcp_lib_set_av_mute(enum av_mute av_mute_state); +void hdcp_lib_set_encryption(enum encryption_state enc_state); +u8 hdcp_lib_check_repeater_bit_in_tx(void); + +/* DDC */ +int hdcp_ddc_read(u16 no_bytes, u8 addr, u8 *pdata); +int hdcp_ddc_write(u16 no_bytes, u8 addr, u8 *pdata); +void hdcp_ddc_abort(void); + +#endif /* __KERNEL__ */ + +#endif /* _HDCP_H_ */ diff --git a/drivers/video/omap2/hdcp/hdcp_ddc.c b/drivers/video/omap2/hdcp/hdcp_ddc.c new file mode 100644 index 0000000..e5104fa --- /dev/null +++ b/drivers/video/omap2/hdcp/hdcp_ddc.c @@ -0,0 +1,310 @@ +/* + * hdcp_ddc.c + * + * HDCP interface DSS driver setting for TI's OMAP4 family of processor. + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ + * Authors: Fabrice Olivero + * Fabrice Olivero <f-olivero@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/delay.h> +#include "hdcp.h" +#include "hdcp_ddc.h" + +/*----------------------------------------------------------------------------- + * Function: hdcp_suspend_resume_auto_ri + *----------------------------------------------------------------------------- + */ +static int hdcp_suspend_resume_auto_ri(enum ri_suspend_resume state) +{ + static u8 OldRiStat, OldRiCommand; + u8 TimeOut = 10; + + /* Suspend Auto Ri in order to allow FW access MDDC bus. + * Poll 0x72:0x26[0] for MDDC bus availability or timeout + */ + + DBG("hdcp_suspend_resume_auto_ri() state=%s", + state == AUTO_RI_SUSPEND ? "SUSPEND" : "RESUME"); + + if (state == AUTO_RI_SUSPEND) { + /* Save original Auto Ri state */ + OldRiCommand = RD_FIELD_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__RI_CMD, 0, 0); + + /* Disable Auto Ri */ + hdcp_lib_auto_ri_check(false); + + /* Wait for HW to release MDDC bus */ + /* TODO: while loop / timeout to be enhanced */ + while (--TimeOut) { + if (!RD_FIELD_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__RI_STAT, 0, 0)) + break; + } + + /* MDDC bus not relinquished */ + if (!TimeOut) { + printk(KERN_ERR "HDCP: Suspending Auto Ri failed !\n"); + return -HDCP_DDC_ERROR; + } + + OldRiStat = RD_FIELD_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__RI_STAT, 0, 0); + } else { + /* If Auto Ri was enabled before it was suspended */ + if ((OldRiStat) && (OldRiCommand)) + /* Re-enable Auto Ri */ + hdcp_lib_auto_ri_check(false); + } + + return HDCP_OK; +} + + +/*----------------------------------------------------------------------------- + * Function: hdcp_start_ddc_transfer + *----------------------------------------------------------------------------- + */ +static int hdcp_start_ddc_transfer(mddc_type *mddc_cmd, u8 operation) +{ + u8 *cmd = (u8 *)mddc_cmd; + struct timeval t0, t1, t2; + u32 time_elapsed_ms = 0; + u32 i, size; + unsigned long flags; + +#ifdef _9032_AUTO_RI_ + if (hdcp_suspend_resume_auto_ri(AUTO_RI_SUSPEND)) + return -HDCP_DDC_ERROR; +#endif + + spin_lock_irqsave(&hdcp.spinlock, flags); + + /* Abort Master DDC operation and Clear FIFO pointer */ + WR_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_CMD, MASTER_CMD_CLEAR_FIFO); + + /* Read to flush */ + RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_CMD); + + /* Sending DDC header, it'll clear DDC Status register too */ + for (i = 0; i < 7; i++) { + WR_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_ADDR + i * sizeof(uint32_t), + cmd[i]); + + /* Read to flush */ + RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_ADDR + + i * sizeof(uint32_t)); + } + + spin_unlock_irqrestore(&hdcp.spinlock, flags); + + do_gettimeofday(&t1); + memcpy(&t0, &t1, sizeof(t0)); + + i = 0; + size = mddc_cmd->nbytes_lsb + (mddc_cmd->nbytes_msb << 8); + + while ((i < size) && (hdcp.pending_disable == 0)) { + if (operation == DDC_WRITE) { + /* Write data to DDC FIFO as long as it is NOT full */ + if (RD_FIELD_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_STATUS, 3, 3) + == 0) { + WR_REG_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_DATA, + mddc_cmd->pdata[i++]); + do_gettimeofday(&t1); + } + } else if (operation == DDC_READ) { + /* Read from DDC FIFO as long as it is NOT empty */ + if (RD_FIELD_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_STATUS, 2, 2) + == 0) { + mddc_cmd->pdata[i++] = + RD_REG_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_DATA); + do_gettimeofday(&t1); + } + } + + do_gettimeofday(&t2); + time_elapsed_ms = (t2.tv_sec - t1.tv_sec) * 1000 + + (t2.tv_usec - t1.tv_usec) / 1000; + + if (time_elapsed_ms > HDCP_DDC_TIMEOUT) { + DBG("DDC timeout - no data during %d ms - " + "status=%02x %u", + HDCP_DDC_TIMEOUT, + RD_REG_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_STATUS), + jiffies_to_msecs(jiffies)); + goto ddc_error; + } + } + + if (hdcp.pending_disable) + goto ddc_abort; + + /* Wait for the FIFO to be empty (end of transfer) */ + while ((RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_STATUS) != 0x4) && + (hdcp.pending_disable == 0)) { + do_gettimeofday(&t2); + time_elapsed_ms = (t2.tv_sec - t1.tv_sec) * 1000 + + (t2.tv_usec - t1.tv_usec) / 1000; + + if (time_elapsed_ms > HDCP_DDC_TIMEOUT) { + DBG("DDC timeout - FIFO not getting empty - " + "status=%02x", + RD_REG_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_STATUS)); + goto ddc_error; + } + } + + if (hdcp.pending_disable) + goto ddc_abort; + + DBG("DDC transfer: bytes: %d time_us: %lu status: %x", + i, + (t2.tv_sec - t0.tv_sec) * 1000000 + (t2.tv_usec - t0.tv_usec), + RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_STATUS)); + +#ifdef DDC_DBG + { + int k; + for (k = 0; k < i; k++) + printk(KERN_DEBUG "%02x ", mddc_cmd->pdata[k]); + printk(KERN_DEBUG "\n"); + } +#endif + +#ifdef _9032_AUTO_RI_ + /* Re-enable Auto Ri */ + if (hdcp_suspend_resume_auto_ri(AUTO_RI_RESUME)) + return -HDCP_DDC_ERROR; +#endif + + return HDCP_OK; + +ddc_error: + hdcp_ddc_abort(); + return -HDCP_DDC_ERROR; + +ddc_abort: + DBG("DDC transfer aborted - status=%02x", + RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_STATUS)); + + return HDCP_OK; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_ddc_operation + *----------------------------------------------------------------------------- + */ +static int hdcp_ddc_operation(u16 no_bytes, u8 addr, u8 *pdata, + enum ddc_operation operation) +{ + mddc_type mddc; + + mddc.slaveAddr = HDCPRX_SLV; + mddc.offset = 0; + mddc.regAddr = addr; + mddc.nbytes_lsb = no_bytes & 0xFF; + mddc.nbytes_msb = (no_bytes & 0x300) >> 8; + mddc.dummy = 0; + mddc.pdata = pdata; + + if (operation == DDC_READ) + mddc.cmd = MASTER_CMD_SEQ_RD; + else + mddc.cmd = MASTER_CMD_SEQ_WR; + + DBG("DDC %s: offset=%02x len=%d %u", operation == DDC_READ ? + "READ" : "WRITE", + addr, no_bytes, + jiffies_to_msecs(jiffies)); + + return hdcp_start_ddc_transfer(&mddc, operation); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_ddc_read + *----------------------------------------------------------------------------- + */ +int hdcp_ddc_read(u16 no_bytes, u8 addr, u8 *pdata) +{ + return hdcp_ddc_operation(no_bytes, addr, pdata, DDC_READ); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_ddc_write + *----------------------------------------------------------------------------- + */ +int hdcp_ddc_write(u16 no_bytes, u8 addr, u8 *pdata) +{ + return hdcp_ddc_operation(no_bytes, addr, pdata, DDC_WRITE); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_ddc_abort + *----------------------------------------------------------------------------- + */ +void hdcp_ddc_abort(void) +{ + unsigned long flags; + + /* In case of I2C_NO_ACK error, do not abort DDC to avoid + * DDC lockup + */ + if (RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_STATUS) & 0x20) + return; + + spin_lock_irqsave(&hdcp.spinlock, flags); + + /* Abort Master DDC operation and Clear FIFO pointer */ + WR_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_CMD, MASTER_CMD_ABORT); + + /* Read to flush */ + RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_CMD); + + WR_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_CMD, MASTER_CMD_CLEAR_FIFO); + + /* Read to flush */ + RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__DDC_CMD); + + spin_unlock_irqrestore(&hdcp.spinlock, flags); +} diff --git a/drivers/video/omap2/hdcp/hdcp_ddc.h b/drivers/video/omap2/hdcp/hdcp_ddc.h new file mode 100644 index 0000000..83bae23 --- /dev/null +++ b/drivers/video/omap2/hdcp/hdcp_ddc.h @@ -0,0 +1,111 @@ +/* + * hdcp_ddc.h + * + * HDCP interface DSS driver setting for TI's OMAP4 family of processor. + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ + * Authors: Fabrice Olivero + * Fabrice Olivero <f-olivero@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#define HDCPRX_SLV 0x74 + +#define MASTER_BASE 0xEC +#define MDDC_MANUAL_ADDR 0xEC +#define MDDC_SLAVE_ADDR 0xED +#define MDDC_SEGMENT_ADDR 0xEE +#define MDDC_OFFSET_ADDR 0xEF +#define MDDC_DIN_CNT_LSB_ADDR 0xF0 +#define MDDC_DIN_CNT_MSB_ADDR 0xF1 +#define MDDC_STATUS_ADDR 0xF2 +#define MDDC_COMMAND_ADDR 0xF3 +#define MDDC_FIFO_ADDR 0xF4 +#define MDDC_FIFO_CNT_ADDR 0xF5 + +#define BIT_MDDC_ST_IN_PROGR 0x10 +#define BIT_MDDC_ST_I2C_LOW 0x40 +#define BIT_MDDC_ST_NO_ACK 0x20 + +/* DDC Command[3:0]: + * + * 1111 - Abort transaction + * 1001 - Clear FIFO + * 1010 - Clock SCL + * 0000 - Current address read with no ACK on last byte + * 0001 - Current address read with ACK on last byte + * 0010 - Sequential read with no ACK on last byte + * 0011 - Sequential read with ACK on last byte + * 0100 - Enhanced DDC read with no ACK on last byte + * 0101 - Enhanced DDC read with ACK on last byte + * 0110 - Sequential write ignoring ACK on last byte + * 0111 - Sequential write requiring ACK on last byte + */ + +#define MASTER_CMD_ABORT 0x0f +#define MASTER_CMD_CLEAR_FIFO 0x09 +#define MASTER_CMD_CLOCK 0x0a +#define MASTER_CMD_CUR_RD 0x00 +#define MASTER_CMD_SEQ_RD 0x02 +#define MASTER_CMD_ENH_RD 0x04 +#define MASTER_CMD_SEQ_WR 0x06 + +#define MASTER_FIFO_WR_USE 0x01 +#define MASTER_FIFO_RD_USE 0x02 +#define MASTER_FIFO_EMPTY 0x04 +#define MASTER_FIFO_FULL 0x08 +#define MASTER_DDC_BUSY 0x10 +#define MASTER_DDC_NOACK 0x20 +#define MASTER_DDC_STUCK 0x40 +#define MASTER_DDC_RSVD 0x80 + +/* OMAP 4 HDMI TRM: */ +#define HDMI_IP_CORE_SYSTEM__DDC_MAN 0x3B0 +#define HDMI_IP_CORE_SYSTEM__DDC_ADDR 0x3B4 +#define HDMI_IP_CORE_SYSTEM__DDC_SEGM 0x3B8 +#define HDMI_IP_CORE_SYSTEM__DDC_OFFSET 0x3BC +#define HDMI_IP_CORE_SYSTEM__DDC_COUNT1 0x3C0 +#define HDMI_IP_CORE_SYSTEM__DDC_COUNT2 0x3C4 +#define HDMI_IP_CORE_SYSTEM__DDC_STATUS 0x3C8 +#define HDMI_IP_CORE_SYSTEM__DDC_CMD 0x3CC +#define HDMI_IP_CORE_SYSTEM__DDC_DATA 0x3D0 +#define HDMI_IP_CORE_SYSTEM__DDC_FIFOCNT 0x3D4 + +#define IIC_OK 0 +#define _IIC_CAPTURED 1 +#define _IIC_NOACK 2 +#define _MDDC_CAPTURED 3 +#define _MDDC_NOACK 4 +#define _MDDC_FIFO_FULL 5 + +typedef struct { + u8 slaveAddr; + u8 offset; /* "offset = DDC_SEGM register" */ + u8 regAddr; + u8 nbytes_lsb; + u8 nbytes_msb; + u8 dummy; + u8 cmd; + u8 *pdata; + u8 data[6]; +} mddc_type; + +enum ddc_operation { + DDC_READ, + DDC_WRITE +}; + +enum ri_suspend_resume { + AUTO_RI_SUSPEND, + AUTO_RI_RESUME +}; diff --git a/drivers/video/omap2/hdcp/hdcp_lib.c b/drivers/video/omap2/hdcp/hdcp_lib.c new file mode 100644 index 0000000..5b92aff --- /dev/null +++ b/drivers/video/omap2/hdcp/hdcp_lib.c @@ -0,0 +1,837 @@ +/* + * hdcp_lib.c + * + * HDCP interface DSS driver setting for TI's OMAP4 family of processor. + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ + * Authors: Fabrice Olivero + * Fabrice Olivero <f-olivero@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/delay.h> +#include <mach/omap4-common.h> +#include <linux/dma-mapping.h> +#include "hdcp.h" + +static void hdcp_lib_read_an(u8 *an); +static void hdcp_lib_read_aksv(u8 *ksv_data); +static void hdcp_lib_write_bksv(u8 *ksv_data); +static void hdcp_lib_generate_an(u8 *an); +static int hdcp_lib_r0_check(void); +static int hdcp_lib_sha_bstatus(struct hdcp_sha_in *sha); +static void hdcp_lib_set_repeater_bit_in_tx(enum hdcp_repeater rx_mode); +static void hdcp_lib_toggle_repeater_bit_in_tx(void); +static int hdcp_lib_initiate_step1(void); +static int hdcp_lib_check_ksv(uint8_t ksv[5]); + +#define PPA_SERVICE_HDCP_READ_M0 0x30 +#define PPA_SERVICE_HDCP_CHECK_V 0x31 +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_read_an + *----------------------------------------------------------------------------- + */ +static void hdcp_lib_read_an(u8 *an) +{ + u8 i; + + for (i = 0; i < 8; i++) { + an[i] = (RD_REG_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__AN0 + + i * sizeof(uint32_t))) & 0xFF; + } +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_read_aksv + *----------------------------------------------------------------------------- + */ +static void hdcp_lib_read_aksv(u8 *ksv_data) +{ + u8 i; + for (i = 0; i < 5; i++) { + ksv_data[i] = RD_REG_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__AKSV0 + + i * sizeof(uint32_t)); + + } +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_write_bksv + *----------------------------------------------------------------------------- + */ +static void hdcp_lib_write_bksv(u8 *ksv_data) +{ + u8 i; + for (i = 0; i < 5; i++) { + WR_REG_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, HDMI_IP_CORE_SYSTEM__BKSV0 + + i * sizeof(uint32_t), ksv_data[i]); + } +} +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_generate_an + *----------------------------------------------------------------------------- + */ +static void hdcp_lib_generate_an(u8 *an) +{ + /* Generate An using HDCP HW */ + DBG("hdcp_lib_generate_an()"); + + /* Start AN Gen */ + WR_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL, 3, 3, 0); + + /* Delay of 10 ms */ + mdelay(10); + + /* Stop AN Gen */ + WR_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL, 3, 3, 1); + + /* Must set 0x72:0x0F[3] twice to guarantee that takes effect */ + WR_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL, 3, 3, 1); + + hdcp_lib_read_an(an); + + DBG("AN: %x %x %x %x %x %x %x %x", an[0], an[1], an[2], an[3], + an[4], an[5], an[6], an[7]); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_r0_check + *----------------------------------------------------------------------------- + */ +static int hdcp_lib_r0_check(void) +{ + u8 ro_rx[2], ro_tx[2]; + + DBG("hdcp_lib_r0_check()"); + + /* DDC: Read Ri' from RX */ + if (hdcp_ddc_read(DDC_Ri_LEN, DDC_Ri_ADDR , (u8 *)&ro_rx)) + return -HDCP_DDC_ERROR; + + /* Read Ri in HDCP IP */ + ro_tx[0] = RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__R1) & 0xFF; + + ro_tx[1] = RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__R2) & 0xFF; + + /* Compare values */ + DBG("ROTX: %x%x RORX:%x%x", ro_tx[0], ro_tx[1], ro_rx[0], ro_rx[1]); + + if ((ro_rx[0] == ro_tx[0]) && (ro_rx[1] == ro_tx[1])) + return HDCP_OK; + else + return -HDCP_AUTH_FAILURE; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_sha_bstatus + *----------------------------------------------------------------------------- + */ +static int hdcp_lib_sha_bstatus(struct hdcp_sha_in *sha) +{ + u8 data[2]; + + if (hdcp_ddc_read(DDC_BSTATUS_LEN, DDC_BSTATUS_ADDR, data)) + return -HDCP_DDC_ERROR; + + sha->data[sha->byte_counter++] = data[0]; + sha->data[sha->byte_counter++] = data[1]; + + return HDCP_OK; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_set_repeater_bit_in_tx + *----------------------------------------------------------------------------- + */ +static void hdcp_lib_set_repeater_bit_in_tx(enum hdcp_repeater rx_mode) +{ + DBG("hdcp_lib_set_repeater_bit_in_tx() value=%d", rx_mode); + + WR_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL, 4, 4, rx_mode); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_toggle_repeater_bit_in_tx + *----------------------------------------------------------------------------- + */ +static void hdcp_lib_toggle_repeater_bit_in_tx(void) +{ + if (hdcp_lib_check_repeater_bit_in_tx()) + hdcp_lib_set_repeater_bit_in_tx(HDCP_RECEIVER); + else + hdcp_lib_set_repeater_bit_in_tx(HDCP_REPEATER); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_initiate_step1 + *----------------------------------------------------------------------------- + */ +static int hdcp_lib_initiate_step1(void) +{ + /* HDCP authentication steps: + * 1) Read Bksv - check validity (is HDMI Rx supporting HDCP ?) + * 2) Initializes HDCP (CP reset release) + * 3) Read Bcaps - is HDMI Rx a repeater ? + * *** First part authentication *** + * 4) Read Bksv - check validity (is HDMI Rx supporting HDCP ?) + * 5) Generates An + * 6) DDC: Writes An, Aksv + * 7) DDC: Write Bksv + */ + uint8_t an_ksv_data[8], an_bksv_data[8]; + uint8_t rx_type; + + DBG("hdcp_lib_initiate_step1()\n"); + + /* DDC: Read BKSV from RX */ + if (hdcp_ddc_read(DDC_BKSV_LEN, DDC_BKSV_ADDR , an_ksv_data)) + return -HDCP_DDC_ERROR; + + if (hdcp.pending_disable) + return -HDCP_CANCELLED_AUTH; + + DBG("BKSV: %02x %02x %02x %02x %02x", an_ksv_data[0], an_ksv_data[1], + an_ksv_data[2], an_ksv_data[3], + an_ksv_data[4]); + + if (hdcp_lib_check_ksv(an_ksv_data)) { + DBG("BKSV error (number of 0 and 1)"); + return -HDCP_AUTH_FAILURE; + } + + /* TODO: Need to confirm it is required */ +#ifndef _9032_AN_STOP_FIX_ + hdcp_lib_toggle_repeater_bit_in_tx(); +#endif + + /* Release CP reset bit */ + WR_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL, 2, 2, 1); + + /* Read BCAPS to determine if HDCP RX is a repeater */ + if (hdcp_ddc_read(DDC_BCAPS_LEN, DDC_BCAPS_ADDR, &rx_type)) + return -HDCP_DDC_ERROR; + + if (hdcp.pending_disable) + return -HDCP_CANCELLED_AUTH; + + rx_type = FLD_GET(rx_type, DDC_BIT_REPEATER, DDC_BIT_REPEATER); + + /* Set repeater bit in HDCP CTRL */ + if (rx_type == 1) { + hdcp_lib_set_repeater_bit_in_tx(HDCP_REPEATER); + DBG("HDCP RX is a repeater"); + } else { + hdcp_lib_set_repeater_bit_in_tx(HDCP_RECEIVER); + DBG("HDCP RX is a receiver"); + } + +/* Power debug code */ +#ifdef POWER_TRANSITION_DBG + printk(KERN_INFO "\n**************************\n" + "AUTHENTICATION: WAIT FOR DSS TRANSITION\n" + "*************************\n"); + mdelay(10000); + printk(KERN_INFO "\n**************************\n" + "DONE\n" + "*************************\n"); +#endif + /* DDC: Read BKSV from RX */ + if (hdcp_ddc_read(DDC_BKSV_LEN, DDC_BKSV_ADDR , an_bksv_data)) + return -HDCP_DDC_ERROR; + + /* Generate An */ + hdcp_lib_generate_an(an_ksv_data); + + /* Authentication 1st step initiated HERE */ + + /* DDC: Write An */ + if (hdcp_ddc_write(DDC_AN_LEN, DDC_AN_ADDR , an_ksv_data)) + return -HDCP_DDC_ERROR; + + if (hdcp.pending_disable) + return -HDCP_CANCELLED_AUTH; + + /* Read AKSV from IP: (HDCP AKSV register) */ + hdcp_lib_read_aksv(an_ksv_data); + + DBG("AKSV: %02x %02x %02x %02x %02x", an_ksv_data[0], an_ksv_data[1], + an_ksv_data[2], an_ksv_data[3], + an_ksv_data[4]); + + if (hdcp_lib_check_ksv(an_ksv_data)) { + printk(KERN_INFO "HDCP: AKSV error (number of 0 and 1)\n"); + return -HDCP_AKSV_ERROR; + } + + if (hdcp.pending_disable) + return -HDCP_CANCELLED_AUTH; + + /* DDC: Write AKSV */ + if (hdcp_ddc_write(DDC_AKSV_LEN, DDC_AKSV_ADDR, an_ksv_data)) + return -HDCP_DDC_ERROR; + + if (hdcp.pending_disable) + return -HDCP_CANCELLED_AUTH; + + /* Write Bksv to IP */ + hdcp_lib_write_bksv(an_bksv_data); + + /* Check IP BKSV error */ + if (RD_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL, 5, 5)) + return -HDCP_AUTH_FAILURE; + + /* Here BSKV should be checked against revokation list */ + + return HDCP_OK; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_check_ksv + *----------------------------------------------------------------------------- + */ +static int hdcp_lib_check_ksv(uint8_t ksv[5]) +{ + int i, j; + int zero = 0, one = 0; + + for (i = 0; i < 5; i++) { + /* Count number of zero / one */ + for (j = 0; j < 8; j++) { + if (ksv[i] & (0x01 << j)) + one++; + else + zero++; + } + } + + if (one == zero) + return 0; + else + return -1; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_3des_load_key + *----------------------------------------------------------------------------- + */ +int hdcp_3des_load_key(uint32_t *deshdcp_encrypted_key) +{ + int counter = 0, status = HDCP_OK; + + DBG("Loading HDCP keys..."); + + /* Set decryption mode in DES control register */ + WR_FIELD_32(hdcp.deshdcp_base_addr, + DESHDCP__DHDCP_CTRL, + DESHDCP__DHDCP_CTRL__DIRECTION_POS_F, + DESHDCP__DHDCP_CTRL__DIRECTION_POS_L, + 0x0); + + /* Write encrypted data */ + while (counter < DESHDCP_KEY_SIZE) { + /* Fill Data registers */ + WR_REG_32(hdcp.deshdcp_base_addr, DESHDCP__DHDCP_DATA_L, + deshdcp_encrypted_key[counter]); + WR_REG_32(hdcp.deshdcp_base_addr, DESHDCP__DHDCP_DATA_H, + deshdcp_encrypted_key[counter + 1]); + + /* Wait for output bit at '1' */ + while (RD_FIELD_32(hdcp.deshdcp_base_addr, + DESHDCP__DHDCP_CTRL, + DESHDCP__DHDCP_CTRL__OUTPUT_READY_POS_F, + DESHDCP__DHDCP_CTRL__OUTPUT_READY_POS_L + ) != 0x1) + ; + + /* Dummy read (indeed data are transfered directly into + * key memory) + */ + if (RD_REG_32(hdcp.deshdcp_base_addr, DESHDCP__DHDCP_DATA_L) != + 0x0) { + status = -HDCP_3DES_ERROR; + printk(KERN_ERR "HDCP: DESHDCP dummy read error\n"); + } + if (RD_REG_32(hdcp.deshdcp_base_addr, DESHDCP__DHDCP_DATA_H) != + 0x0) { + status = -HDCP_3DES_ERROR; + printk(KERN_ERR "HDCP: DESHDCP dummy read error\n"); + } + + counter += 2; + } + + return status; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_3des_encrypt_key + *----------------------------------------------------------------------------- + */ +void hdcp_3des_encrypt_key(struct hdcp_encrypt_control *enc_ctrl, + uint32_t out_key[DESHDCP_KEY_SIZE]) +{ + int counter = 0; + + DBG("Encrypting HDCP keys..."); + + /* Reset encrypted key array */ + for (counter = 0; counter < DESHDCP_KEY_SIZE; counter++) + out_key[counter] = 0; + + /* Set encryption mode in DES control register */ + WR_FIELD_32(hdcp.deshdcp_base_addr, + DESHDCP__DHDCP_CTRL, + DESHDCP__DHDCP_CTRL__DIRECTION_POS_F, + DESHDCP__DHDCP_CTRL__DIRECTION_POS_L, + 0x1); + + /* Write raw data and read encrypted data */ + counter = 0; + +#ifdef POWER_TRANSITION_DBG + printk(KERN_ERR "\n**************************\n" + "ENCRYPTION: WAIT FOR DSS TRANSITION\n" + "*************************\n"); + mdelay(10000); + printk(KER_INFO "\n**************************\n" + "DONE\n" + "*************************\n"); +#endif + + while (counter < DESHDCP_KEY_SIZE) { + /* Fill Data registers */ + WR_REG_32(hdcp.deshdcp_base_addr, DESHDCP__DHDCP_DATA_L, + enc_ctrl->in_key[counter]); + WR_REG_32(hdcp.deshdcp_base_addr, DESHDCP__DHDCP_DATA_H, + enc_ctrl->in_key[counter + 1]); + + /* Wait for output bit at '1' */ + while (RD_FIELD_32(hdcp.deshdcp_base_addr, + DESHDCP__DHDCP_CTRL, + DESHDCP__DHDCP_CTRL__OUTPUT_READY_POS_F, + DESHDCP__DHDCP_CTRL__OUTPUT_READY_POS_L + ) != 0x1) + ; + + /* Read enrypted data */ + out_key[counter] = RD_REG_32(hdcp.deshdcp_base_addr, + DESHDCP__DHDCP_DATA_L); + out_key[counter + 1] = RD_REG_32(hdcp.deshdcp_base_addr, + DESHDCP__DHDCP_DATA_H); + + counter += 2; + } +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_disable + *----------------------------------------------------------------------------- + */ +int hdcp_lib_disable() +{ + DBG("hdcp_lib_disable() %u", jiffies_to_msecs(jiffies)); + + /* CP reset */ + WR_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL, 2, 2, 0); + + /* Clear AV mute in case it was set */ + hdcp_lib_set_av_mute(AV_MUTE_CLEAR); + + return HDCP_OK; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_set_encryption + *----------------------------------------------------------------------------- + */ +void hdcp_lib_set_encryption(enum encryption_state enc_state) +{ + unsigned long flags; + + spin_lock_irqsave(&hdcp.spinlock, flags); + + /* HDCP_CTRL::ENC_EN set/clear */ + WR_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL, 0, 0, enc_state); + + /* Read to flush */ + RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL); + + spin_unlock_irqrestore(&hdcp.spinlock, flags); + + pr_info("HDCP: Encryption state changed: %s hdcp_ctrl: %02x", + enc_state == HDCP_ENC_OFF ? "OFF" : "ON", + RD_REG_32(hdcp.hdmi_wp_base_addr + + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL)); + +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_set_av_mute + *----------------------------------------------------------------------------- + */ +void hdcp_lib_set_av_mute(enum av_mute av_mute_state) +{ + unsigned long flags; + + DBG("hdcp_lib_set_av_mute() av_mute=%d", av_mute_state); + + + spin_lock_irqsave(&hdcp.spinlock, flags); + + { + u8 RegVal, TimeOutCount = 64; + + RegVal = RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_CORE_AV_BASE, + HDMI_CORE_AV_PB_CTRL2); + + /* PRguide-GPC: To change the content of the CP_BYTE1 register, + * CP_EN must be zero + * set PB_CTRL2 :: CP_RPT = 0 + */ + WR_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_CORE_AV_BASE, + HDMI_CORE_AV_PB_CTRL2, 2, 2, 0); + + /* Set/clear AV mute state */ + WR_REG_32(hdcp.hdmi_wp_base_addr + HDMI_CORE_AV_BASE, + HDMI_CORE_AV_CP_BYTE1, av_mute_state); + + /* FIXME: This loop should be removed */ + while (TimeOutCount--) { + /* Continue in this loop till CP_EN becomes 0, + * prior to TimeOutCount becoming 0 */ + if (!RD_FIELD_32(hdcp.hdmi_wp_base_addr + + HDMI_CORE_AV_BASE, + HDMI_CORE_AV_PB_CTRL2, 3, 3)) + break; + } + + DBG(" timeoutcount=%d", TimeOutCount); + + /* FIXME: why is this if condition required?, according to prg, + * this shall be unconditioanlly */ + if (TimeOutCount) { + /* set PB_CTRL2 :: CP_EN = 1 & CP_RPT = 1 */ + RegVal = FLD_MOD(RegVal, 0x3, 3, 2); + + WR_REG_32(hdcp.hdmi_wp_base_addr + HDMI_CORE_AV_BASE, + HDMI_CORE_AV_PB_CTRL2, RegVal); + } + } + + spin_unlock_irqrestore(&hdcp.spinlock, flags); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_check_repeater_bit_in_tx + *----------------------------------------------------------------------------- + */ +u8 hdcp_lib_check_repeater_bit_in_tx(void) +{ + return RD_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL, 4, 4); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_auto_ri_check + *----------------------------------------------------------------------------- + */ +void hdcp_lib_auto_ri_check(bool state) +{ + u8 reg_val; + unsigned long flags; + + DBG("hdcp_lib_auto_ri_check() state=%s", + state == true ? "ON" : "OFF"); + + spin_lock_irqsave(&hdcp.spinlock, flags); + + reg_val = RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__INT_UNMASK3); + + reg_val = (state == true) ? (reg_val | 0xB0) : (reg_val & ~0xB0); + + /* Turn on/off the following Auto Ri interrupts */ + WR_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__INT_UNMASK3, reg_val); + + /* Enable/Disable Ri */ + WR_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__RI_CMD, 0, 0, + ((state == true) ? 1 : 0)); + + /* Read to flush */ + RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__RI_CMD); + + spin_unlock_irqrestore(&hdcp.spinlock, flags); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_auto_bcaps_rdy_check + *----------------------------------------------------------------------------- + */ +void hdcp_lib_auto_bcaps_rdy_check(bool state) +{ + u8 reg_val; + unsigned long flags; + + DBG("hdcp_lib_auto_bcaps_rdy_check() state=%s", + state == true ? "ON" : "OFF"); + + spin_lock_irqsave(&hdcp.spinlock, flags); + + /* Enable KSV_READY / BACP_DONE interrupt */ + WR_FIELD_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__INT_UNMASK2, 7, 7, + ((state == true) ? 1 : 0)); + + /* Enable/Disable Ri & Bcap */ + reg_val = RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__RI_CMD); + + /* Enable RI_EN & BCAP_EN OR disable BCAP_EN */ + reg_val = (state == true) ? (reg_val | 0x3) : (reg_val & ~0x2); + + WR_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__RI_CMD, reg_val); + + /* Read to flush */ + RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__RI_CMD); + + spin_unlock_irqrestore(&hdcp.spinlock, flags); + + DBG("hdcp_lib_auto_bcaps_rdy_check() Done\n"); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_step1_start + *----------------------------------------------------------------------------- + */ +int hdcp_lib_step1_start(void) +{ + u8 hdmi_mode; + int status; + + DBG("hdcp_lib_step1_start() %u", jiffies_to_msecs(jiffies)); + + /* Check if mode is HDMI or DVI */ + hdmi_mode = RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_CORE_AV_BASE, + HDMI_CORE_AV_HDMI_CTRL) & + HDMI_CORE_AV_HDMI_CTRL__HDMI_MODE; + + DBG("RX mode: %s", hdmi_mode ? "HDMI" : "DVI"); + + /* Set AV Mute */ + hdcp_lib_set_av_mute(AV_MUTE_SET); + + /* Must turn encryption off when AVMUTE */ + hdcp_lib_set_encryption(HDCP_ENC_OFF); + + status = hdcp_lib_initiate_step1(); + + if (hdcp.pending_disable) + return -HDCP_CANCELLED_AUTH; + else + return status; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_step1_r0_check + *----------------------------------------------------------------------------- + */ +int hdcp_lib_step1_r0_check(void) +{ + int status = HDCP_OK; + + /* HDCP authentication steps: + * 1) DDC: Read M0' + * 2) Compare M0 and M0' + * if Rx is a receiver: switch to authentication step 3 + * 3) Enable encryption / auto Ri check / disable AV mute + * if Rx is a repeater: switch to authentication step 2 + * 3) Get M0 from HDMI IP and store it for further processing (V) + * 4) Enable encryption / auto Ri check / auto BCAPS RDY polling + * Disable AV mute + */ + + DBG("hdcp_lib_step1_r0_check() %u", jiffies_to_msecs(jiffies)); + + status = hdcp_lib_r0_check(); + if (status < 0) + return status; + + /* Authentication 1st step done */ + + /* Now prepare 2nd step authentication in case of RX repeater and + * enable encryption / Ri check + */ + + if (hdcp.pending_disable) + return -HDCP_CANCELLED_AUTH; + + if (hdcp_lib_check_repeater_bit_in_tx()) { + status = omap4_secure_dispatcher(PPA_SERVICE_HDCP_READ_M0, + 0x4, 0, 0, 0, 0, 0); + /* Wait for user space */ + if (status) { + printk(KERN_ERR "HDCP: omap4_secure_dispatcher M0 error " + "%d\n", status); + return -HDCP_AUTH_FAILURE; + } + + DBG("hdcp_lib_set_encryption() %u", jiffies_to_msecs(jiffies)); + + /* Enable encryption */ + hdcp_lib_set_encryption(HDCP_ENC_ON); + +#ifdef _9032_AUTO_RI_ + /* Enable Auto Ri */ + hdcp_lib_auto_ri_check(true); +#endif + +#ifdef _9032_BCAP_ + /* Enable automatic BCAPS polling */ + hdcp_lib_auto_bcaps_rdy_check(true); +#endif + + /* Now, IP waiting for BCAPS ready bit */ + } else { + /* Receiver: enable encryption and auto Ri check */ + hdcp_lib_set_encryption(HDCP_ENC_ON); + +#ifdef _9032_AUTO_RI_ + /* Enable Auto Ri */ + hdcp_lib_auto_ri_check(true); +#endif + + } + + /* Clear AV mute */ + hdcp_lib_set_av_mute(AV_MUTE_CLEAR); + + return HDCP_OK; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_lib_step2 + *----------------------------------------------------------------------------- + */ +int hdcp_lib_step2(void) +{ + /* HDCP authentication steps: + * 1) Disable auto Ri check + * 2) DDC: read BStatus (nb of devices, MAX_DEV + */ + + u8 bstatus[2]; + int status = HDCP_OK; + + DBG("hdcp_lib_step2() %u", jiffies_to_msecs(jiffies)); + +#ifdef _9032_AUTO_RI_ + /* Disable Auto Ri */ + hdcp_lib_auto_ri_check(false); +#endif + + /* DDC: Read Bstatus (1st byte) from Rx */ + if (hdcp_ddc_read(DDC_BSTATUS_LEN, DDC_BSTATUS_ADDR, bstatus)) + return -HDCP_DDC_ERROR; + + /* Get KSV list size */ + DBG("KSV list size: %d", bstatus[0] & DDC_BSTATUS0_DEV_COUNT); + sha_input.byte_counter = (bstatus[0] & DDC_BSTATUS0_DEV_COUNT) * 5; + + /* Check BStatus topology errors */ + if (bstatus[0] & DDC_BSTATUS0_MAX_DEVS) { + DBG("MAX_DEV_EXCEEDED set"); + return -HDCP_AUTH_FAILURE; + } + + if (bstatus[1] & DDC_BSTATUS1_MAX_CASC) { + DBG("MAX_CASCADE_EXCEEDED set"); + return -HDCP_AUTH_FAILURE; + } + + DBG("Retrieving KSV list..."); + + /* Clear all SHA input data */ + /* TODO: should be done earlier at HDCP init */ + memset(sha_input.data, 0, MAX_SHA_DATA_SIZE); + + if (hdcp.pending_disable) + return -HDCP_CANCELLED_AUTH; + + /* DDC: read KSV list */ + if (sha_input.byte_counter) { + if (hdcp_ddc_read(sha_input.byte_counter, DDC_KSV_FIFO_ADDR, + (u8 *)&sha_input.data)) + return -HDCP_DDC_ERROR; + } + + /* Read and add Bstatus */ + if (hdcp_lib_sha_bstatus(&sha_input)) + return -HDCP_DDC_ERROR; + + if (hdcp.pending_disable) + return -HDCP_CANCELLED_AUTH; + + /* Read V' */ + if (hdcp_ddc_read(DDC_V_LEN, DDC_V_ADDR, sha_input.vprime)) + return -HDCP_DDC_ERROR; + + if (hdcp.pending_disable) + return -HDCP_CANCELLED_AUTH; + + /* clear sha_input values in cache*/ + dma_sync_single_for_device(NULL, + __pa((u32)(&sha_input)), + sizeof(struct hdcp_sha_in), + DMA_TO_DEVICE); + + status = omap4_secure_dispatcher(PPA_SERVICE_HDCP_CHECK_V, + 0x4, 1, __pa((u32)&sha_input), + 0, 0, 0); + /* Wait for user space */ + if (status) { + printk(KERN_ERR "HDCP: omap4_secure_dispatcher CHECH_V error " + "%d\n", status); + return -HDCP_AUTH_FAILURE; + } + + if (status == HDCP_OK) { + /* Re-enable Ri check */ +#ifdef _9032_AUTO_RI_ + hdcp_lib_auto_ri_check(true); +#endif + } + + return status; +} diff --git a/drivers/video/omap2/hdcp/hdcp_top.c b/drivers/video/omap2/hdcp/hdcp_top.c new file mode 100644 index 0000000..f23605a --- /dev/null +++ b/drivers/video/omap2/hdcp/hdcp_top.c @@ -0,0 +1,1045 @@ +/* + * hdcp_top.c + * + * HDCP interface DSS driver setting for TI's OMAP4 family of processor. + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ + * Authors: Fabrice Olivero + * Fabrice Olivero <f-olivero@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/completion.h> +#include <linux/miscdevice.h> +#include <linux/firmware.h> +#include "../../hdmi_ti_4xxx_ip.h" +#include "../dss/dss.h" +#include "hdcp.h" + +struct hdcp hdcp; +struct hdcp_sha_in sha_input; + +/* State machine / workqueue */ +static void hdcp_wq_disable(void); +static void hdcp_wq_start_authentication(void); +static void hdcp_wq_check_r0(void); +static void hdcp_wq_step2_authentication(void); +static void hdcp_wq_authentication_failure(void); +static void hdcp_work_queue(struct work_struct *work); +static struct delayed_work *hdcp_submit_work(int event, int delay); +static void hdcp_cancel_work(struct delayed_work **work); + +/* Callbacks */ +static void hdcp_start_frame_cb(void); +static void hdcp_irq_cb(int hpd_low); + +/* Control */ +static long hdcp_enable_ctl(void __user *argp); +static long hdcp_disable_ctl(void); +static long hdcp_query_status_ctl(void __user *argp); +static long hdcp_encrypt_key_ctl(void __user *argp); + +/* Driver */ +static int __init hdcp_init(void); +static void __exit hdcp_exit(void); + +struct completion hdcp_comp; +static DECLARE_WAIT_QUEUE_HEAD(hdcp_up_wait_queue); +static DECLARE_WAIT_QUEUE_HEAD(hdcp_down_wait_queue); + +#define DSS_POWER + +/*----------------------------------------------------------------------------- + * Function: hdcp_request_dss + *----------------------------------------------------------------------------- + */ +static void hdcp_request_dss(void) +{ +#ifdef DSS_POWER + hdcp.dss_state = dss_runtime_get(); +#endif +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_user_space_task + *----------------------------------------------------------------------------- + */ +int hdcp_user_space_task(int flags) +{ + int ret; + + DBG("Wait for user space task %x\n", flags); + hdcp.hdcp_up_event = flags & 0xFF; + hdcp.hdcp_down_event = flags & 0xFF; + wake_up_interruptible(&hdcp_up_wait_queue); + wait_event_interruptible(hdcp_down_wait_queue, + (hdcp.hdcp_down_event & 0xFF) == 0); + ret = (hdcp.hdcp_down_event & 0xFF00) >> 8; + + DBG("User space task done %x\n", hdcp.hdcp_down_event); + hdcp.hdcp_down_event = 0; + + return ret; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_release_dss + *----------------------------------------------------------------------------- + */ +static void hdcp_release_dss(void) +{ +#ifdef DSS_POWER + if (hdcp.dss_state == 0) + dss_runtime_put(); +#endif +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_wq_disable + *----------------------------------------------------------------------------- + */ +static void hdcp_wq_disable(void) +{ + printk(KERN_INFO "HDCP: disabled\n"); + + hdcp_cancel_work(&hdcp.pending_wq_event); + hdcp_lib_disable(); + hdcp.pending_disable = 0; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_wq_start_authentication + *----------------------------------------------------------------------------- + */ +static void hdcp_wq_start_authentication(void) +{ + int status = HDCP_OK; + + hdcp.hdcp_state = HDCP_AUTHENTICATION_START; + + printk(KERN_INFO "HDCP: authentication start\n"); + + /* Step 1 part 1 (until R0 calc delay) */ + status = hdcp_lib_step1_start(); + + if (status == -HDCP_AKSV_ERROR) { + hdcp_wq_authentication_failure(); + } else if (status == -HDCP_CANCELLED_AUTH) { + DBG("Authentication step 1 cancelled."); + return; + } else if (status != HDCP_OK) { + hdcp_wq_authentication_failure(); + } else { + hdcp.hdcp_state = HDCP_WAIT_R0_DELAY; + hdcp.auth_state = HDCP_STATE_AUTH_1ST_STEP; + hdcp.pending_wq_event = hdcp_submit_work(HDCP_R0_EXP_EVENT, + HDCP_R0_DELAY); + } +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_wq_check_r0 + *----------------------------------------------------------------------------- + */ +static void hdcp_wq_check_r0(void) +{ + int status = hdcp_lib_step1_r0_check(); + + if (status == -HDCP_CANCELLED_AUTH) { + DBG("Authentication step 1/R0 cancelled."); + return; + } else if (status < 0) + hdcp_wq_authentication_failure(); + else { + if (hdcp_lib_check_repeater_bit_in_tx()) { + /* Repeater */ + printk(KERN_INFO "HDCP: authentication step 1 " + "successful - Repeater\n"); + + hdcp.hdcp_state = HDCP_WAIT_KSV_LIST; + hdcp.auth_state = HDCP_STATE_AUTH_2ND_STEP; + + hdcp.pending_wq_event = + hdcp_submit_work(HDCP_KSV_TIMEOUT_EVENT, + HDCP_KSV_TIMEOUT_DELAY); + } else { + /* Receiver */ + printk(KERN_INFO "HDCP: authentication step 1 " + "successful - Receiver\n"); + + hdcp.hdcp_state = HDCP_LINK_INTEGRITY_CHECK; + hdcp.auth_state = HDCP_STATE_AUTH_3RD_STEP; + + /* Restore retry counter */ + if (hdcp.en_ctrl->nb_retry == 0) + hdcp.retry_cnt = HDCP_INFINITE_REAUTH; + else + hdcp.retry_cnt = hdcp.en_ctrl->nb_retry; + } + } +} + + +/*----------------------------------------------------------------------------- + * Function: hdcp_wq_step2_authentication + *----------------------------------------------------------------------------- + */ +static void hdcp_wq_step2_authentication(void) +{ + int status = HDCP_OK; + + /* KSV list timeout is running and should be canceled */ + hdcp_cancel_work(&hdcp.pending_wq_event); + + status = hdcp_lib_step2(); + + if (status == -HDCP_CANCELLED_AUTH) { + DBG("Authentication step 2 cancelled."); + return; + } else if (status < 0) + hdcp_wq_authentication_failure(); + else { + printk(KERN_INFO "HDCP: (Repeater) authentication step 2 " + "successful\n"); + + hdcp.hdcp_state = HDCP_LINK_INTEGRITY_CHECK; + hdcp.auth_state = HDCP_STATE_AUTH_3RD_STEP; + + /* Restore retry counter */ + if (hdcp.en_ctrl->nb_retry == 0) + hdcp.retry_cnt = HDCP_INFINITE_REAUTH; + else + hdcp.retry_cnt = hdcp.en_ctrl->nb_retry; + } +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_wq_authentication_failure + *----------------------------------------------------------------------------- + */ +static void hdcp_wq_authentication_failure(void) +{ + if (hdcp.hdmi_state == HDMI_STOPPED) { + hdcp.auth_state = HDCP_STATE_AUTH_FAILURE; + return; + } + + hdcp_lib_auto_ri_check(false); + hdcp_lib_auto_bcaps_rdy_check(false); + hdcp_lib_set_av_mute(AV_MUTE_SET); + hdcp_lib_set_encryption(HDCP_ENC_OFF); + + hdcp_cancel_work(&hdcp.pending_wq_event); + + hdcp_lib_disable(); + hdcp.pending_disable = 0; + + if (hdcp.retry_cnt) { + if (hdcp.retry_cnt < HDCP_INFINITE_REAUTH) { + hdcp.retry_cnt--; + printk(KERN_INFO "HDCP: authentication failed - " + "retrying, attempts=%d\n", + hdcp.retry_cnt); + } else + printk(KERN_INFO "HDCP: authentication failed - " + "retrying\n"); + + hdcp.hdcp_state = HDCP_AUTHENTICATION_START; + hdcp.auth_state = HDCP_STATE_AUTH_FAIL_RESTARTING; + + hdcp.pending_wq_event = hdcp_submit_work(HDCP_AUTH_REATT_EVENT, + HDCP_REAUTH_DELAY); + } else { + printk(KERN_INFO "HDCP: authentication failed - " + "HDCP disabled\n"); + hdcp.hdcp_state = HDCP_ENABLE_PENDING; + hdcp.auth_state = HDCP_STATE_AUTH_FAILURE; + } + +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_work_queue + *----------------------------------------------------------------------------- + */ +static void hdcp_work_queue(struct work_struct *work) +{ + struct hdcp_delayed_work *hdcp_w = + container_of(work, struct hdcp_delayed_work, work.work); + int event = hdcp_w->event; + + mutex_lock(&hdcp.lock); + + DBG("hdcp_work_queue() - START - %u hdmi=%d hdcp=%d auth=%d evt= %x %d" + " hdcp_ctrl=%02x", + jiffies_to_msecs(jiffies), + hdcp.hdmi_state, + hdcp.hdcp_state, + hdcp.auth_state, + (event & 0xFF00) >> 8, + event & 0xFF, + RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, + HDMI_IP_CORE_SYSTEM__HDCP_CTRL)); + + /* Clear pending_wq_event + * In case a delayed work is scheduled from the state machine + * "pending_wq_event" is used to memorize pointer on the event to be + * able to cancel any pending work in case HDCP is disabled + */ + if (event & HDCP_WORKQUEUE_SRC) + hdcp.pending_wq_event = 0; + + /* First handle HDMI state */ + if (event == HDCP_START_FRAME_EVENT) { + hdcp.pending_start = 0; + hdcp.hdmi_state = HDMI_STARTED; + } + /**********************/ + /* HDCP state machine */ + /**********************/ + switch (hdcp.hdcp_state) { + + /* State */ + /*********/ + case HDCP_DISABLED: + /* HDCP enable control or re-authentication event */ + if (event == HDCP_ENABLE_CTL) { + if (hdcp.en_ctrl->nb_retry == 0) + hdcp.retry_cnt = HDCP_INFINITE_REAUTH; + else + hdcp.retry_cnt = hdcp.en_ctrl->nb_retry; + + if (hdcp.hdmi_state == HDMI_STARTED) + hdcp_wq_start_authentication(); + else + hdcp.hdcp_state = HDCP_ENABLE_PENDING; + } + + break; + + /* State */ + /*********/ + case HDCP_ENABLE_PENDING: + /* HDMI start frame event */ + if (event == HDCP_START_FRAME_EVENT) + hdcp_wq_start_authentication(); + + break; + + /* State */ + /*********/ + case HDCP_AUTHENTICATION_START: + /* Re-authentication */ + if (event == HDCP_AUTH_REATT_EVENT) + hdcp_wq_start_authentication(); + + break; + + /* State */ + /*********/ + case HDCP_WAIT_R0_DELAY: + /* R0 timer elapsed */ + if (event == HDCP_R0_EXP_EVENT) + hdcp_wq_check_r0(); + + break; + + /* State */ + /*********/ + case HDCP_WAIT_KSV_LIST: + /* Ri failure */ + if (event == HDCP_RI_FAIL_EVENT) { + printk(KERN_INFO "HDCP: Ri check failure\n"); + + hdcp_wq_authentication_failure(); + } + /* KSV list ready event */ + else if (event == HDCP_KSV_LIST_RDY_EVENT) + hdcp_wq_step2_authentication(); + /* Timeout */ + else if (event == HDCP_KSV_TIMEOUT_EVENT) { + printk(KERN_INFO "HDCP: BCAPS polling timeout\n"); + hdcp_wq_authentication_failure(); + } + break; + + /* State */ + /*********/ + case HDCP_LINK_INTEGRITY_CHECK: + /* Ri failure */ + if (event == HDCP_RI_FAIL_EVENT) { + printk(KERN_INFO "HDCP: Ri check failure\n"); + hdcp_wq_authentication_failure(); + } + break; + + default: + printk(KERN_WARNING "HDCP: error - unknow HDCP state\n"); + break; + } + + kfree(hdcp_w); + + DBG("hdcp_work_queue() - END - %u hdmi=%d hdcp=%d auth=%d evt=%x %d ", + jiffies_to_msecs(jiffies), + hdcp.hdmi_state, + hdcp.hdcp_state, + hdcp.auth_state, + (event & 0xFF00) >> 8, + event & 0xFF); + + mutex_unlock(&hdcp.lock); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_submit_work + *----------------------------------------------------------------------------- + */ +static struct delayed_work *hdcp_submit_work(int event, int delay) +{ + struct hdcp_delayed_work *work; + + work = kmalloc(sizeof(struct hdcp_delayed_work), GFP_ATOMIC); + + if (work) { + INIT_DELAYED_WORK(&work->work, hdcp_work_queue); + work->event = event; + queue_delayed_work(hdcp.workqueue, + &work->work, + msecs_to_jiffies(delay)); + } else { + printk(KERN_WARNING "HDCP: Cannot allocate memory to " + "create work\n"); + return 0; + } + + return &work->work; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_cancel_work + *----------------------------------------------------------------------------- + */ +static void hdcp_cancel_work(struct delayed_work **work) +{ + int ret = 0; + + if (*work) { + ret = cancel_delayed_work(*work); +#if 0 /* cancel_work_sync is only exported to GPL code + * as HDCP is not GPL, will not wait for work to finish + * but avoid freeing the work which is on-going + */ + if (ret != 1) { + ret = cancel_work_sync(&((*work)->work)); + printk(KERN_INFO "Canceling work failed - " + "cancel_work_sync done %d\n", ret); + } +#endif + kfree(*work); + *work = 0; + } +} + + +/****************************************************************************** + * HDCP callbacks + *****************************************************************************/ + +/*----------------------------------------------------------------------------- + * Function: hdcp_3des_cb + *----------------------------------------------------------------------------- + */ +static void hdcp_3des_cb(void) +{ + DBG("hdcp_3des_cb() %u", jiffies_to_msecs(jiffies)); + + if (!hdcp.hdcp_keys_loaded) { + DBG("%s: hdcp_keys not loaded = %d", + __func__, hdcp.hdcp_keys_loaded); + return; + } + + /* Load 3DES key */ + if (hdcp_3des_load_key(hdcp.en_ctrl->key) != HDCP_OK) { + printk(KERN_ERR "Error Loading HDCP keys\n"); + return; + } +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_start_frame_cb + *----------------------------------------------------------------------------- + */ +static void hdcp_start_frame_cb(void) +{ + DBG("hdcp_start_frame_cb() %u", jiffies_to_msecs(jiffies)); + + if (!hdcp.hdcp_keys_loaded) { + DBG("%s: hdcp_keys not loaded = %d", + __func__, hdcp.hdcp_keys_loaded); + return; + } + + /* Cancel any pending work */ + hdcp_cancel_work(&hdcp.pending_start); + hdcp_cancel_work(&hdcp.pending_wq_event); + + hdcp.hpd_low = 0; + hdcp.pending_disable = 0; + hdcp.retry_cnt = hdcp.en_ctrl->nb_retry; + hdcp.pending_start = hdcp_submit_work(HDCP_START_FRAME_EVENT, + HDCP_ENABLE_DELAY); +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_irq_cb + *----------------------------------------------------------------------------- + */ +static void hdcp_irq_cb(int status) +{ + DBG("hdcp_irq_cb() status=%x", status); + + if (!hdcp.hdcp_keys_loaded) { + DBG("%s: hdcp_keys not loaded = %d", + __func__, hdcp.hdcp_keys_loaded); + return; + } + + /* Disable auto Ri/BCAPS immediately */ + if (((status & HDMI_RI_ERR) || + (status & HDMI_BCAP) || + (status & HDMI_HPD_LOW)) && + (hdcp.hdcp_state != HDCP_ENABLE_PENDING)) { + hdcp_lib_auto_ri_check(false); + hdcp_lib_auto_bcaps_rdy_check(false); + } + + /* Work queue execution not required if HDCP is disabled */ + /* TODO: ignore interrupts if they are masked (cannnot access UMASK + * here so should use global variable + */ + if ((hdcp.hdcp_state != HDCP_DISABLED) && + (hdcp.hdcp_state != HDCP_ENABLE_PENDING)) { + if (status & HDMI_HPD_LOW) { + hdcp_lib_set_encryption(HDCP_ENC_OFF); + hdcp_ddc_abort(); + } + + if (status & HDMI_RI_ERR) { + hdcp_lib_set_av_mute(AV_MUTE_SET); + hdcp_lib_set_encryption(HDCP_ENC_OFF); + hdcp_submit_work(HDCP_RI_FAIL_EVENT, 0); + } + /* RI error takes precedence over BCAP */ + else if (status & HDMI_BCAP) + hdcp_submit_work(HDCP_KSV_LIST_RDY_EVENT, 0); + } + + if (status & HDMI_HPD_LOW) { + hdcp.pending_disable = 1; /* Used to exit on-going HDCP + * work */ + hdcp.hpd_low = 0; /* Used to cancel HDCP works */ + hdcp_lib_disable(); + /* In case of HDCP_STOP_FRAME_EVENT, HDCP stop + * frame callback is blocked and waiting for + * HDCP driver to finish accessing the HW + * before returning + * Reason is to avoid HDMI driver to shutdown + * DSS/HDMI power before HDCP work is finished + */ + hdcp.hdmi_state = HDMI_STOPPED; + hdcp.hdcp_state = HDCP_ENABLE_PENDING; + hdcp.auth_state = HDCP_STATE_DISABLED; + } +} + +/****************************************************************************** + * HDCP control from ioctl + *****************************************************************************/ + + +/*----------------------------------------------------------------------------- + * Function: hdcp_enable_ctl + *----------------------------------------------------------------------------- + */ +static long hdcp_enable_ctl(void __user *argp) +{ + DBG("hdcp_ioctl() - ENABLE %u", jiffies_to_msecs(jiffies)); + + if (hdcp.en_ctrl == 0) { + hdcp.en_ctrl = + kmalloc(sizeof(struct hdcp_enable_control), + GFP_KERNEL); + + if (hdcp.en_ctrl == 0) { + printk(KERN_WARNING + "HDCP: Cannot allocate memory for HDCP" + " enable control struct\n"); + return -EFAULT; + } + } + + if (copy_from_user(hdcp.en_ctrl, argp, + sizeof(struct hdcp_enable_control))) { + printk(KERN_WARNING "HDCP: Error copying from user space " + "- enable ioctl\n"); + return -EFAULT; + } + + /* Post event to workqueue */ + if (hdcp_submit_work(HDCP_ENABLE_CTL, 0) == 0) + return -EFAULT; + + return 0; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_disable_ctl + *----------------------------------------------------------------------------- + */ +static long hdcp_disable_ctl(void) +{ + DBG("hdcp_ioctl() - DISABLE %u", jiffies_to_msecs(jiffies)); + + hdcp_cancel_work(&hdcp.pending_start); + hdcp_cancel_work(&hdcp.pending_wq_event); + + hdcp.pending_disable = 1; + /* Post event to workqueue */ + if (hdcp_submit_work(HDCP_DISABLE_CTL, 0) == 0) + return -EFAULT; + + return 0; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_query_status_ctl + *----------------------------------------------------------------------------- + */ +static long hdcp_query_status_ctl(void __user *argp) +{ + uint32_t *status = (uint32_t *)argp; + + DBG("hdcp_ioctl() - QUERY %u", jiffies_to_msecs(jiffies)); + + *status = hdcp.auth_state; + + return 0; +} + +static int hdcp_wait_re_entrance; + +/*----------------------------------------------------------------------------- + * Function: hdcp_wait_event_ctl + *----------------------------------------------------------------------------- + */ +static long hdcp_wait_event_ctl(void __user *argp) +{ + struct hdcp_wait_control ctrl; + + DBG("hdcp_ioctl() - WAIT %u %d", jiffies_to_msecs(jiffies), + hdcp.hdcp_up_event); + + if (copy_from_user(&ctrl, argp, + sizeof(struct hdcp_wait_control))) { + printk(KERN_WARNING "HDCP: Error copying from user space" + " - wait ioctl"); + return -EFAULT; + } + + if (hdcp_wait_re_entrance == 0) { + hdcp_wait_re_entrance = 1; + wait_event_interruptible(hdcp_up_wait_queue, + (hdcp.hdcp_up_event & 0xFF) != 0); + + ctrl.event = hdcp.hdcp_up_event; + + if ((ctrl.event & 0xFF) == HDCP_EVENT_STEP2) { + if (copy_to_user(ctrl.data, &sha_input, + sizeof(struct hdcp_sha_in))) { + printk(KERN_WARNING "HDCP: Error copying to " + "user space - wait ioctl"); + return -EFAULT; + } + } + + hdcp.hdcp_up_event = 0; + hdcp_wait_re_entrance = 0; + } else + ctrl.event = HDCP_EVENT_EXIT; + + /* Store output data to output pointer */ + if (copy_to_user(argp, &ctrl, + sizeof(struct hdcp_wait_control))) { + printk(KERN_WARNING "HDCP: Error copying to user space -" + " wait ioctl"); + return -EFAULT; + } + + return 0; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_done_ctl + *----------------------------------------------------------------------------- + */ +static long hdcp_done_ctl(void __user *argp) +{ + uint32_t *status = (uint32_t *)argp; + + DBG("hdcp_ioctl() - DONE %u %d", jiffies_to_msecs(jiffies), *status); + + hdcp.hdcp_down_event &= ~(*status & 0xFF); + hdcp.hdcp_down_event |= *status & 0xFF00; + + wake_up_interruptible(&hdcp_down_wait_queue); + + return 0; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_encrypt_key_ctl + *----------------------------------------------------------------------------- + */ +static long hdcp_encrypt_key_ctl(void __user *argp) +{ + struct hdcp_encrypt_control *ctrl; + uint32_t *out_key; + + DBG("hdcp_ioctl() - ENCRYPT KEY %u", jiffies_to_msecs(jiffies)); + + mutex_lock(&hdcp.lock); + + if (hdcp.hdcp_state != HDCP_DISABLED) { + printk(KERN_INFO "HDCP: Cannot encrypt keys while HDCP " + "is enabled\n"); + mutex_unlock(&hdcp.lock); + return -EFAULT; + } + + hdcp.hdcp_state = HDCP_KEY_ENCRYPTION_ONGOING; + + /* Encryption happens in ioctl / user context */ + ctrl = kmalloc(sizeof(struct hdcp_encrypt_control), + GFP_KERNEL); + + if (ctrl == 0) { + printk(KERN_WARNING "HDCP: Cannot allocate memory for HDCP" + " encryption control struct\n"); + mutex_unlock(&hdcp.lock); + return -EFAULT; + } + + out_key = kmalloc(sizeof(uint32_t) * + DESHDCP_KEY_SIZE, GFP_KERNEL); + + if (out_key == 0) { + printk(KERN_WARNING "HDCP: Cannot allocate memory for HDCP " + "encryption output key\n"); + kfree(ctrl); + mutex_unlock(&hdcp.lock); + return -EFAULT; + } + + if (copy_from_user(ctrl, argp, + sizeof(struct hdcp_encrypt_control))) { + printk(KERN_WARNING "HDCP: Error copying from user space" + " - encrypt ioctl\n"); + kfree(ctrl); + kfree(out_key); + mutex_unlock(&hdcp.lock); + return -EFAULT; + } + + hdcp_request_dss(); + + /* Call encrypt function */ + hdcp_3des_encrypt_key(ctrl, out_key); + + hdcp_release_dss(); + + hdcp.hdcp_state = HDCP_DISABLED; + mutex_unlock(&hdcp.lock); + + /* Store output data to output pointer */ + if (copy_to_user(ctrl->out_key, out_key, + sizeof(uint32_t)*DESHDCP_KEY_SIZE)) { + printk(KERN_WARNING "HDCP: Error copying to user space -" + " encrypt ioctl\n"); + kfree(ctrl); + kfree(out_key); + return -EFAULT; + } + + kfree(ctrl); + kfree(out_key); + return 0; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_ioctl + *----------------------------------------------------------------------------- + */ +long hdcp_ioctl(struct file *fd, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + + switch (cmd) { + case HDCP_ENABLE: + return hdcp_enable_ctl(argp); + + case HDCP_DISABLE: + return hdcp_disable_ctl(); + + case HDCP_ENCRYPT_KEY: + return hdcp_encrypt_key_ctl(argp); + + case HDCP_QUERY_STATUS: + return hdcp_query_status_ctl(argp); + + case HDCP_WAIT_EVENT: + return hdcp_wait_event_ctl(argp); + + case HDCP_DONE: + return hdcp_done_ctl(argp); + + default: + return -ENOTTY; + } /* End switch */ +} + + +/****************************************************************************** + * HDCP driver init/exit + *****************************************************************************/ + +/*----------------------------------------------------------------------------- + * Function: hdcp_mmap + *----------------------------------------------------------------------------- + */ +static int hdcp_mmap(struct file *filp, struct vm_area_struct *vma) +{ + int status; + + DBG("hdcp_mmap() %lx %lx %lx\n", vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start); + + vma->vm_flags |= VM_IO | VM_RESERVED; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + status = remap_pfn_range(vma, vma->vm_start, + HDMI_WP >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, + vma->vm_page_prot); + if (status) { + DBG("mmap error %d\n", status); + return -EAGAIN; + } + + DBG("mmap succesfull\n"); + return 0; +} + +static struct file_operations hdcp_fops = { + .owner = THIS_MODULE, + .mmap = hdcp_mmap, + .unlocked_ioctl = hdcp_ioctl, +}; + +struct miscdevice mdev; + +static void hdcp_load_keys_cb(const struct firmware *fw, void *context) +{ + struct hdcp_enable_control *en_ctrl; + + if (!fw) { + pr_err("HDCP: failed to load keys\n"); + return; + } + + if (fw->size != sizeof(en_ctrl->key)) { + pr_err("HDCP: encrypted key file wrong size %d\n", fw->size); + return; + } + + en_ctrl = kmalloc(sizeof(*en_ctrl), GFP_KERNEL); + if (!en_ctrl) { + pr_err("HDCP: can't allocated space for keys\n"); + return; + } + + memcpy(en_ctrl->key, fw->data, sizeof(en_ctrl->key)); + en_ctrl->nb_retry = 20; + + hdcp.en_ctrl = en_ctrl; + hdcp.retry_cnt = hdcp.en_ctrl->nb_retry; + hdcp.hdcp_state = HDCP_ENABLE_PENDING; + hdcp.hdcp_keys_loaded = true; + pr_info("HDCP: loaded keys\n"); +} + +static int hdcp_load_keys(void) +{ + int ret; + + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, + "hdcp.keys", mdev.this_device, GFP_KERNEL, + &hdcp, hdcp_load_keys_cb); + if (ret < 0) { + pr_err("HDCP: request_firmware_nowait failed: %d\n", ret); + hdcp.hdcp_keys_loaded = false; + return ret; + } + + return 0; +} + + +/*----------------------------------------------------------------------------- + * Function: hdcp_init + *----------------------------------------------------------------------------- + */ +static int __init hdcp_init(void) +{ + DBG("hdcp_init() %u", jiffies_to_msecs(jiffies)); + + /* Map HDMI WP address */ + hdcp.hdmi_wp_base_addr = ioremap(HDMI_WP, 0x1000); + + if (!hdcp.hdmi_wp_base_addr) { + printk(KERN_ERR "HDCP: HDMI WP IOremap error\n"); + return -EFAULT; + } + + /* Map DESHDCP in kernel address space */ + hdcp.deshdcp_base_addr = ioremap(DSS_SS_FROM_L3__DESHDCP, 0x34); + + if (!hdcp.deshdcp_base_addr) { + printk(KERN_ERR "HDCP: DESHDCP IOremap error\n"); + goto err_map_deshdcp; + } + + mutex_init(&hdcp.lock); + + mdev.minor = MISC_DYNAMIC_MINOR; + mdev.name = "hdcp"; + mdev.mode = 0666; + mdev.fops = &hdcp_fops; + + if (misc_register(&mdev)) { + printk(KERN_ERR "HDCP: Could not add character driver\n"); + goto err_register; + } + + mutex_lock(&hdcp.lock); + + /* Variable init */ + hdcp.en_ctrl = 0; + hdcp.hdcp_state = HDCP_DISABLED; + hdcp.pending_start = 0; + hdcp.pending_wq_event = 0; + hdcp.retry_cnt = 0; + hdcp.auth_state = HDCP_STATE_DISABLED; + hdcp.pending_disable = 0; + hdcp.hdcp_up_event = 0; + hdcp.hdcp_down_event = 0; + hdcp_wait_re_entrance = 0; + hdcp.hpd_low = 0; + + spin_lock_init(&hdcp.spinlock); + + init_completion(&hdcp_comp); + + hdcp.workqueue = create_singlethread_workqueue("hdcp"); + if (hdcp.workqueue == NULL) + goto err_add_driver; + + hdcp_request_dss(); + + /* Register HDCP callbacks to HDMI library */ + if (omapdss_hdmi_register_hdcp_callbacks(&hdcp_start_frame_cb, + &hdcp_irq_cb, + &hdcp_3des_cb)) + hdcp.hdmi_state = HDMI_STARTED; + else + hdcp.hdmi_state = HDMI_STOPPED; + + hdcp_release_dss(); + + mutex_unlock(&hdcp.lock); + + hdcp_load_keys(); + + return 0; + +err_add_driver: + misc_deregister(&mdev); + +err_register: + mutex_destroy(&hdcp.lock); + + iounmap(hdcp.deshdcp_base_addr); + +err_map_deshdcp: + iounmap(hdcp.hdmi_wp_base_addr); + + return -EFAULT; +} + +/*----------------------------------------------------------------------------- + * Function: hdcp_exit + *----------------------------------------------------------------------------- + */ +static void __exit hdcp_exit(void) +{ + DBG("hdcp_exit() %u", jiffies_to_msecs(jiffies)); + + mutex_lock(&hdcp.lock); + + kfree(hdcp.en_ctrl); + + hdcp_request_dss(); + + /* Un-register HDCP callbacks to HDMI library */ + omapdss_hdmi_register_hdcp_callbacks(0, 0, 0); + + hdcp_release_dss(); + + misc_deregister(&mdev); + + /* Unmap HDMI WP / DESHDCP */ + iounmap(hdcp.hdmi_wp_base_addr); + iounmap(hdcp.deshdcp_base_addr); + + destroy_workqueue(hdcp.workqueue); + + mutex_unlock(&hdcp.lock); + + mutex_destroy(&hdcp.lock); +} + +/*----------------------------------------------------------------------------- + *----------------------------------------------------------------------------- + */ +module_init(hdcp_init); +module_exit(hdcp_exit); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("OMAP HDCP kernel module"); +MODULE_AUTHOR("Fabrice Olivero"); diff --git a/include/video/hdmi_ti_4xxx_ip.h b/include/video/hdmi_ti_4xxx_ip.h index 0d5b4fd..992c704 100644 --- a/include/video/hdmi_ti_4xxx_ip.h +++ b/include/video/hdmi_ti_4xxx_ip.h @@ -21,6 +21,10 @@ #ifndef _HDMI_TI_4xxx_ #define _HDMI_TI_4xxx_ +#define HDMI_HPD_LOW 0x10 +#define HDMI_HPD_HIGH 0x20 +#define HDMI_BCAP 0x40 +#define HDMI_RI_ERR 0x80 enum hdmi_pll_pwr { HDMI_PLLPWRCMD_ALLOFF = 0, HDMI_PLLPWRCMD_PLLONLY = 1, @@ -353,7 +357,7 @@ enum hdmi_core_infoframe { int hdmi_ti_4xxx_phy_init(struct hdmi_ip_data *ip_data); -void hdmi_ti_4xxx_phy_off(struct hdmi_ip_data *ip_data); +void hdmi_ti_4xxx_phy_off(struct hdmi_ip_data *ip_data, bool set_mode); int read_ti_4xxx_edid(struct hdmi_ip_data *ip_data, u8 *pedid, u16 max_length); void hdmi_ti_4xxx_wp_video_start(struct hdmi_ip_data *ip_data, bool start); int hdmi_ti_4xxx_pll_program(struct hdmi_ip_data *ip_data, @@ -361,6 +365,9 @@ int hdmi_ti_4xxx_pll_program(struct hdmi_ip_data *ip_data, int hdmi_ti_4xxx_set_pll_pwr(struct hdmi_ip_data *ip_data, enum hdmi_pll_pwr val); void hdmi_ti_4xxx_basic_configure(struct hdmi_ip_data *ip_data, struct hdmi_config *cfg); +int hdmi_ti_4xxx_rxdet(struct hdmi_ip_data *ip_data); +int hdmi_ti_4xxx_wp_get_video_state(struct hdmi_ip_data *ip_data); +u32 hdmi_ti_4xxx_irq_handler(struct hdmi_ip_data *ip_data); void hdmi_ti_4xxx_dump_regs(struct hdmi_ip_data *ip_data, struct seq_file *s); int hdmi_ti_4xxx_config_audio_acr(struct hdmi_ip_data *ip_data, u32 sample_freq, u32 *n, u32 *cts, u32 pclk); @@ -374,4 +381,5 @@ void hdmi_ti_4xxx_core_audio_config(struct hdmi_ip_data *ip_data, void hdmi_ti_4xxx_core_audio_infoframe_config(struct hdmi_ip_data *ip_data, struct hdmi_core_infoframe_audio *info_aud); void hdmi_ti_4xxx_audio_enable(struct hdmi_ip_data *ip_data, bool idle); +int hdmi_ti_4xxx_set_wait_soft_reset(struct hdmi_ip_data *ip_data); #endif diff --git a/sound/soc/codecs/omap-hdmi-codec.c b/sound/soc/codecs/omap-hdmi-codec.c index 8d89eed..c735700 100644 --- a/sound/soc/codecs/omap-hdmi-codec.c +++ b/sound/soc/codecs/omap-hdmi-codec.c @@ -48,34 +48,40 @@ #define HDMI_PLLCTRL 0x200 #define HDMI_PHY 0x300 +/* hdmi configuration params */ +struct hdmi_params { + int format; + int sample_freq; + int channels_nr; +}; + + /* codec private data */ -struct hdmi_data { +struct hdmi_codec_data { struct hdmi_audio_format audio_fmt; struct hdmi_audio_dma audio_dma; struct hdmi_core_audio_config audio_core_cfg; struct hdmi_core_infoframe_audio aud_if_cfg; struct hdmi_ip_data ip_data; struct omap_hwmod *oh; -}; + struct omap_dss_device *dssdev; + struct notifier_block notifier; + struct hdmi_params params; + int active; +} hdmi_data; -static int hdmi_audio_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) + +static int hdmi_audio_set_configuration(struct hdmi_codec_data *priv) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; - struct platform_device *pdev = to_platform_device(codec->dev); - struct hdmi_data *priv = snd_soc_codec_get_drvdata(codec); struct hdmi_audio_format *audio_format = &priv->audio_fmt; struct hdmi_audio_dma *audio_dma = &priv->audio_dma; struct hdmi_core_audio_config *core_cfg = &priv->audio_core_cfg; struct hdmi_core_infoframe_audio *aud_if_cfg = &priv->aud_if_cfg; - int err, n, cts, channels_nr, channel_alloc; + int err, n, cts, channel_alloc; enum hdmi_core_audio_sample_freq sample_freq; u32 pclk = omapdss_hdmi_get_pixel_clock(); - - switch (params_format(params)) { + switch (priv->params.format) { case SNDRV_PCM_FORMAT_S16_LE: core_cfg->i2s_cfg.word_max_length = HDMI_AUDIO_I2S_MAX_WORD_20BITS; @@ -107,7 +113,7 @@ static int hdmi_audio_hw_params(struct snd_pcm_substream *substream, } - switch (params_rate(params)) { + switch (priv->params.sample_freq) { case 32000: sample_freq = HDMI_AUDIO_FS_32000; break; @@ -121,8 +127,8 @@ static int hdmi_audio_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - err = hdmi_ti_4xxx_config_audio_acr(&priv->ip_data, params_rate(params), - &n, &cts, pclk); + err = hdmi_ti_4xxx_config_audio_acr(&priv->ip_data, + priv->params.sample_freq, &n, &cts, pclk); if (err < 0) return err; @@ -179,9 +185,8 @@ static int hdmi_audio_hw_params(struct snd_pcm_substream *substream, core_cfg->en_parallel_aud_input = true; /* Number of channels */ - channels_nr = params_channels(params); - switch (channels_nr) { + switch (priv->params.channels_nr) { case 2: core_cfg->layout = HDMI_AUDIO_LAYOUT_2CH; channel_alloc = 0x0; @@ -211,7 +216,7 @@ static int hdmi_audio_hw_params(struct snd_pcm_substream *substream, HDMI_AUDIO_I2S_SD3_EN; break; default: - dev_err(&pdev->dev, "Unsupported number of channels\n"); + pr_err("Unsupported number of channels\n"); return -EINVAL; } @@ -223,7 +228,7 @@ static int hdmi_audio_hw_params(struct snd_pcm_substream *substream, * info frame audio see doc CEA861-D page 74 */ aud_if_cfg->db1_coding_type = HDMI_INFOFRAME_AUDIO_DB1CT_FROM_STREAM; - aud_if_cfg->db1_channel_count = channels_nr; + aud_if_cfg->db1_channel_count = priv->params.channels_nr; aud_if_cfg->db2_sample_freq = HDMI_INFOFRAME_AUDIO_DB2SF_FROM_STREAM; aud_if_cfg->db2_sample_size = HDMI_INFOFRAME_AUDIO_DB2SS_FROM_STREAM; aud_if_cfg->db4_channel_alloc = channel_alloc; @@ -232,6 +237,42 @@ static int hdmi_audio_hw_params(struct snd_pcm_substream *substream, hdmi_ti_4xxx_core_audio_infoframe_config(&priv->ip_data, aud_if_cfg); return 0; + +} + +int hdmi_audio_notifier_callback(struct notifier_block *nb, + unsigned long arg, void *ptr) +{ + enum omap_dss_display_state state = arg; + + if (state == OMAP_DSS_DISPLAY_ACTIVE) { + /* this happens just after hdmi_power_on */ + if (hdmi_data.active) + hdmi_ti_4xxx_audio_enable(&hdmi_data.ip_data, 0); + hdmi_audio_set_configuration(&hdmi_data); + if (hdmi_data.active) + hdmi_ti_4xxx_audio_enable(&hdmi_data.ip_data, 1); + } + return 0; +} + +int hdmi_audio_match(struct omap_dss_device *dssdev, void *arg) +{ + return sysfs_streq(dssdev->name , "hdmi"); +} + +static int hdmi_audio_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct hdmi_codec_data *priv = snd_soc_codec_get_drvdata(codec); + + priv->params.format = params_format(params); + priv->params.sample_freq = params_rate(params); + priv->params.channels_nr = params_channels(params); + return hdmi_audio_set_configuration(priv); } static int hdmi_audio_trigger(struct snd_pcm_substream *substream, int cmd, @@ -239,7 +280,7 @@ static int hdmi_audio_trigger(struct snd_pcm_substream *substream, int cmd, { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_codec *codec = rtd->codec; - struct hdmi_data *priv = snd_soc_codec_get_drvdata(codec); + struct hdmi_codec_data *priv = snd_soc_codec_get_drvdata(codec); int err = 0; switch (cmd) { @@ -253,11 +294,12 @@ static int hdmi_audio_trigger(struct snd_pcm_substream *substream, int cmd, omap_hwmod_set_slave_idlemode(priv->oh, HWMOD_IDLEMODE_NO); hdmi_ti_4xxx_audio_enable(&priv->ip_data, 1); + priv->active = 1; break; - case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + priv->active = 0; hdmi_ti_4xxx_audio_enable(&priv->ip_data, 0); /* * switch back to smart-idle & wakeup capable @@ -283,52 +325,71 @@ static int hdmi_audio_startup(struct snd_pcm_substream *substream, } static int hdmi_probe(struct snd_soc_codec *codec) { - struct hdmi_data *priv; struct platform_device *pdev = to_platform_device(codec->dev); struct resource *hdmi_rsrc; + int ret = 0; - priv = kzalloc(sizeof(struct hdmi_data), GFP_KERNEL); - if (priv == NULL) - return -ENOMEM; - - snd_soc_codec_set_drvdata(codec, priv); - + snd_soc_codec_set_drvdata(codec, &hdmi_data); hdmi_rsrc = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!hdmi_rsrc) { dev_err(&pdev->dev, "Cannot obtain IORESOURCE_MEM HDMI\n"); - return -EINVAL; + ret = -EINVAL; + goto res_err; } - priv->oh = omap_hwmod_lookup("dss_hdmi"); + hdmi_data.oh = omap_hwmod_lookup("dss_hdmi"); - if (!priv->oh) { + if (!hdmi_data.oh) { dev_err(&pdev->dev, "can't find omap_hwmod for hdmi\n"); - return -ENODEV; + ret = -ENODEV; + goto res_err; } /* Base address taken from platform */ - priv->ip_data.base_wp = ioremap(hdmi_rsrc->start, + hdmi_data.ip_data.base_wp = ioremap(hdmi_rsrc->start, resource_size(hdmi_rsrc)); - if (!priv->ip_data.base_wp) { + if (!hdmi_data.ip_data.base_wp) { dev_err(&pdev->dev, "can't ioremap WP\n"); - return -ENOMEM; + ret = -ENOMEM; + goto res_err; + } + + hdmi_data.ip_data.hdmi_core_sys_offset = HDMI_CORE_SYS; + hdmi_data.ip_data.hdmi_core_av_offset = HDMI_CORE_AV; + hdmi_data.ip_data.hdmi_pll_offset = HDMI_PLLCTRL; + hdmi_data.ip_data.hdmi_phy_offset = HDMI_PHY; + + hdmi_data.dssdev = omap_dss_find_device(NULL, hdmi_audio_match); + + if (!hdmi_data.dssdev) { + dev_err(&pdev->dev, "can't find HDMI device\n"); + ret = -ENODEV; + goto dssdev_err; } - priv->ip_data.hdmi_core_sys_offset = HDMI_CORE_SYS; - priv->ip_data.hdmi_core_av_offset = HDMI_CORE_AV; - priv->ip_data.hdmi_pll_offset = HDMI_PLLCTRL; - priv->ip_data.hdmi_phy_offset = HDMI_PHY; + hdmi_data.notifier.notifier_call = hdmi_audio_notifier_callback; + blocking_notifier_chain_register(&hdmi_data.dssdev->state_notifiers, + &hdmi_data.notifier); return 0; + +dssdev_err: + iounmap(hdmi_data.ip_data.base_wp); +res_err: + return ret; + } static int hdmi_remove(struct snd_soc_codec *codec) { - struct hdmi_data *priv = snd_soc_codec_get_drvdata(codec); + struct hdmi_codec_data *priv = snd_soc_codec_get_drvdata(codec); + + blocking_notifier_chain_unregister(&priv->dssdev->state_notifiers, + &priv->notifier); iounmap(priv->ip_data.base_wp); kfree(priv); return 0; |