Hi,
This series try to add the basic HDCP function support for bridge dw_hdmi driver. Beside this series also resend the patch from Sean Paul that try to introduce a common way for userspace to operate the HDCP function.
Userspace could chose to enable/disable the HDCP function throught the new standard properties "Content Protection", and driver would reflect the HDCP auth status to "Content Protection KSV" property.
Beside this series also base on Vladimir Zapolskiy's patch that help to introduce DDC support for dw_hdmi driver.
[Resend] Sean Pual: https://patchwork.kernel.org/patch/7279801 [Rebased on] Vladimir Zapolskiy: https://patchwork.kernel.org/patch/7279801
Thanks - Yakir
Sean Paul (1): drm: Add Content Protection properties to drm
Yakir Yang (1): drm: bridgw/dw_hdmi: add basic hdmi hdcp driver
drivers/gpu/drm/bridge/dw_hdmi.c | 355 ++++++++++++++++++++++++++++++++++++--- drivers/gpu/drm/bridge/dw_hdmi.h | 20 +++ drivers/gpu/drm/drm_crtc.c | 21 +++ drivers/gpu/drm/drm_sysfs.c | 46 +++++ include/drm/bridge/dw_hdmi.h | 17 ++ include/drm/drm_crtc.h | 3 + include/uapi/drm/drm_mode.h | 4 + 7 files changed, 447 insertions(+), 19 deletions(-)
From: Sean Paul seanpaul@chromium.org
Add new standard connector properties to track whether content protection (ex: hdcp) is desired by userspace. There are two properties involved, "Content Protection" and "Content Protection KSV".
The "Content Protection" property allows userspace to request protection on a connector. Set "Desired" to enable, "Undesired" to disable.
The "Content Protection KSV" property reflects the current state of protection. If the KSV is 0, the connection is not protected. Once the driver has enabled protection, it will update the the value with the KSV (or similarly unique identifier, if not using HDCP) of the first-hop device (sink or repeater).
Signed-off-by: Sean Paul seanpaul@chromium.org Signed-off-by: Yakir Yang ykk@rock-chips.com --- drivers/gpu/drm/drm_crtc.c | 21 +++++++++++++++++++++ drivers/gpu/drm/drm_sysfs.c | 46 +++++++++++++++++++++++++++++++++++++++++++++ include/drm/drm_crtc.h | 3 +++ include/uapi/drm/drm_mode.h | 4 ++++ 4 files changed, 74 insertions(+)
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 32dd134..de7ae6a 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -78,6 +78,13 @@ static const struct drm_prop_enum_list drm_plane_type_enum_list[] = { { DRM_PLANE_TYPE_CURSOR, "Cursor" }, };
+static struct drm_prop_enum_list drm_cp_enum_list[] = { + { DRM_MODE_CONTENT_PROTECTION_UNDESIRED, "Undesired" }, + { DRM_MODE_CONTENT_PROTECTION_DESIRED, "Desired" }, +}; + +DRM_ENUM_NAME_FN(drm_get_content_protection_name, drm_cp_enum_list) + /* * Optional properties */ @@ -1470,6 +1477,20 @@ static int drm_mode_create_standard_properties(struct drm_device *dev) return -ENOMEM; dev->mode_config.prop_mode_id = prop;
+ prop = drm_property_create_enum(dev, 0, + "Content Protection", drm_cp_enum_list, + ARRAY_SIZE(drm_cp_enum_list)); + if (!prop) + return -ENOMEM; + dev->mode_config.content_protection_property = prop; + + prop = drm_property_create_range(dev, DRM_MODE_PROP_IMMUTABLE, + "Content Protection KSV", 0, + 0xFFFFFFFFFF); + if (!prop) + return -ENOMEM; + dev->mode_config.content_protection_ksv_property = prop; + return 0; }
diff --git a/drivers/gpu/drm/drm_sysfs.c b/drivers/gpu/drm/drm_sysfs.c index c75f022..dc122b8 100644 --- a/drivers/gpu/drm/drm_sysfs.c +++ b/drivers/gpu/drm/drm_sysfs.c @@ -248,6 +248,48 @@ static ssize_t enabled_show(struct device *device, "disabled"); }
+static ssize_t content_protection_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct drm_connector *connector = to_drm_connector(device); + struct drm_device *dev = connector->dev; + struct drm_property *prop; + uint64_t cp; + int ret; + + prop = dev->mode_config.content_protection_property; + if (!prop) + return 0; + + ret = drm_object_property_get_value(&connector->base, prop, &cp); + if (ret) + return 0; + + return snprintf(buf, PAGE_SIZE, "%s\n", + drm_get_content_protection_name((int)cp)); +} + +static ssize_t content_protection_ksv_show(struct device *device, + struct device_attribute *attr, + char *buf) +{ + struct drm_connector *connector = to_drm_connector(device); + struct drm_device *dev = connector->dev; + struct drm_property *prop; + uint64_t ksv; + int ret; + + prop = dev->mode_config.content_protection_ksv_property; + if (!prop) + return 0; + + ret = drm_object_property_get_value(&connector->base, prop, &ksv); + if (ret) + return 0; + + return snprintf(buf, PAGE_SIZE, "%llx\n", ksv); +} + static ssize_t edid_show(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, char *buf, loff_t off, size_t count) @@ -399,12 +441,16 @@ static DEVICE_ATTR_RW(status); static DEVICE_ATTR_RO(enabled); static DEVICE_ATTR_RO(dpms); static DEVICE_ATTR_RO(modes); +static DEVICE_ATTR_RO(content_protection); +static DEVICE_ATTR_RO(content_protection_ksv);
static struct attribute *connector_dev_attrs[] = { &dev_attr_status.attr, &dev_attr_enabled.attr, &dev_attr_dpms.attr, &dev_attr_modes.attr, + &dev_attr_content_protection.attr, + &dev_attr_content_protection_ksv.attr, NULL };
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 173535a..ba2bc37 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -1118,6 +1118,8 @@ struct drm_mode_config { struct drm_property *prop_crtc_id; struct drm_property *prop_active; struct drm_property *prop_mode_id; + struct drm_property *content_protection_property; + struct drm_property *content_protection_ksv_property;
/* DVI-I properties */ struct drm_property *dvi_i_subconnector_property; @@ -1286,6 +1288,7 @@ extern void drm_encoder_cleanup(struct drm_encoder *encoder); extern const char *drm_get_connector_status_name(enum drm_connector_status status); extern const char *drm_get_subpixel_order_name(enum subpixel_order order); extern const char *drm_get_dpms_name(int val); +extern const char *drm_get_content_protection_name(int val); extern const char *drm_get_dvi_i_subconnector_name(int val); extern const char *drm_get_dvi_i_select_name(int val); extern const char *drm_get_tv_subconnector_name(int val); diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index 6c11ca4..7b08342 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -81,6 +81,10 @@ #define DRM_MODE_DPMS_SUSPEND 2 #define DRM_MODE_DPMS_OFF 3
+/* Content Protection Flags */ +#define DRM_MODE_CONTENT_PROTECTION_UNDESIRED 0 +#define DRM_MODE_CONTENT_PROTECTION_DESIRED 1 + /* Scaling mode options */ #define DRM_MODE_SCALE_NONE 0 /* Unmodified timing (display or software can still scale) */
Userspace could set the drm property "Content Protection" to "Desired", and then driver would start to authenticate. If the authentication successed, then the "Content Protection KSV" would be write with the right HDCP KSV KEY, and enable the HDCP encryption.
Besides driver export an interface for specific platform driver that used for passing the encrypted HDCP key.
Signed-off-by: Yakir Yang ykk@rock-chips.com --- drivers/gpu/drm/bridge/dw_hdmi.c | 355 ++++++++++++++++++++++++++++++++++++--- drivers/gpu/drm/bridge/dw_hdmi.h | 20 +++ include/drm/bridge/dw_hdmi.h | 17 ++ 3 files changed, 373 insertions(+), 19 deletions(-)
diff --git a/drivers/gpu/drm/bridge/dw_hdmi.c b/drivers/gpu/drm/bridge/dw_hdmi.c index 2c4dfc8..593a5b9 100644 --- a/drivers/gpu/drm/bridge/dw_hdmi.c +++ b/drivers/gpu/drm/bridge/dw_hdmi.c @@ -53,6 +53,13 @@ enum hdmi_datamap { YCbCr422_12B = 0x12, };
+enum dw_hdmi_hdcp_state { + DW_HDMI_HDCP_STATE_DISABLED, + DW_HDMI_HDCP_STATE_AUTH_START, + DW_HDMI_HDCP_STATE_AUTH_DONE, + DW_HDMI_HDCP_STATE_ENCRY_EN, +}; + static const u16 csc_coeff_default[3][4] = { { 0x2000, 0x0000, 0x0000, 0x0000 }, { 0x0000, 0x2000, 0x0000, 0x0000 }, @@ -97,7 +104,6 @@ struct hdmi_data_info { unsigned int enc_color_depth; unsigned int colorimetry; unsigned int pix_repet_factor; - unsigned int hdcp_enable; struct hdmi_vmode video_mode; };
@@ -154,6 +160,14 @@ struct dw_hdmi { unsigned int audio_n; bool audio_enable;
+ /* this mutex is used for sync hdcp key content */ + struct mutex hdcp_key_mutex; + struct dw_hdmi_hdcp_key_1x hdcp_key; + bool is_hdcp_key_present; + + struct mutex hdcp_state_mutex; + enum dw_hdmi_hdcp_state hdcp_state; + void (*write)(struct dw_hdmi *hdmi, u8 val, int offset); u8 (*read)(struct dw_hdmi *hdmi, int offset); }; @@ -1087,23 +1101,195 @@ static int dw_hdmi_phy_init(struct dw_hdmi *hdmi) return 0; }
-static void hdmi_tx_hdcp_config(struct dw_hdmi *hdmi) +static int hdmi_hdcp_wait_rmsts_ok(struct dw_hdmi *hdmi) { - u8 de; + unsigned long timeout;
- if (hdmi->hdmi_data.video_mode.mdataenablepolarity) - de = HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH; - else - de = HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_LOW; + timeout = jiffies + msecs_to_jiffies(100); + while ((hdmi_readb(hdmi, HDMI_HDCPREG_RMSTS) & DPK_WR_OK_STS) == 0) { + if (time_after(jiffies, timeout)) + return -EBUSY; + usleep_range(10, 15); + } + + return 0; +} + +static int _hdmi_write_hdcp_key(struct dw_hdmi *hdmi) +{ + struct dw_hdmi_hdcp_key_1x *key = &hdmi->hdcp_key; + int i, j; + int ret; + + /* Disable decryption logic */ + hdmi_writeb(hdmi, 0, HDMI_HDCPREG_RMCTL); + ret = hdmi_hdcp_wait_rmsts_ok(hdmi); + if (ret) + return ret; + + /* The useful data in ksv should be 5 byte */ + for (i = 4; i >= 0; i--) + hdmi_writeb(hdmi, key->ksv[i], HDMI_HDCPREG_DPK0 + i); + + ret = hdmi_hdcp_wait_rmsts_ok(hdmi); + if (ret) + return ret; + + /* Enable decryption logic */ + hdmi_writeb(hdmi, 1, HDMI_HDCPREG_RMCTL); + hdmi_writeb(hdmi, key->seed[1], HDMI_HDCPREG_SEED1); + hdmi_writeb(hdmi, key->seed[0], HDMI_HDCPREG_SEED0); + + /* Write encrypt device private key */ + for (i = 0; i < DW_HDMI_HDCP_DPK_LEN - 6; i += 7) { + for (j = 6; j >= 0; j--) + hdmi_writeb(hdmi, key->device_key[i + j], + HDMI_HDCPREG_DPK0 + j); + ret = hdmi_hdcp_wait_rmsts_ok(hdmi); + if (ret) + return ret; + } + + return 0; +} + +static void _hdmi_close_hdcp_auth(struct dw_hdmi *hdmi) +{ + WARN_ON(!mutex_is_locked(&hdmi->hdcp_state_mutex)); + + /* Disable hdcp encryption */ + hdmi_modb(hdmi, HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE, + HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK, HDMI_A_HDCPCFG1);
- /* disable rx detect */ + /* Disable the RX detect */ hdmi_modb(hdmi, HDMI_A_HDCPCFG0_RXDETECT_DISABLE, HDMI_A_HDCPCFG0_RXDETECT_MASK, HDMI_A_HDCPCFG0);
- hdmi_modb(hdmi, de, HDMI_A_VIDPOLCFG_DATAENPOL_MASK, HDMI_A_VIDPOLCFG); + hdmi->hdcp_state = DW_HDMI_HDCP_STATE_DISABLED; +} + +static void _hdmi_start_hdcp_auth(struct dw_hdmi *hdmi) +{ + WARN_ON(!mutex_is_locked(&hdmi->hdcp_state_mutex)); + + /* Make sure RX detect is off before we start */ + _hdmi_close_hdcp_auth(hdmi); + + /* Enable the RX detect; expect an interrupt after this */ + hdmi_modb(hdmi, HDMI_A_HDCPCFG0_RXDETECT_ENABLE, + HDMI_A_HDCPCFG0_RXDETECT_MASK, HDMI_A_HDCPCFG0);
+ hdmi->hdcp_state = DW_HDMI_HDCP_STATE_AUTH_START; +} + +static void _hdmi_enable_hdcp_encry(struct dw_hdmi *hdmi) +{ + WARN_ON(!mutex_is_locked(&hdmi->hdcp_state_mutex)); + WARN_ON(hdmi->hdcp_state != DW_HDMI_HDCP_STATE_AUTH_DONE); + + /* Enable hdcp encryption */ + hdmi_modb(hdmi, HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_ENABLE, + HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK, HDMI_A_HDCPCFG1); + + hdmi->hdcp_state = DW_HDMI_HDCP_STATE_ENCRY_EN; +} + +static void hdmi_disable_hdcp_encry(struct dw_hdmi *hdmi) +{ + mutex_lock(&hdmi->hdcp_state_mutex); + + /* Disable hdcp encryption */ hdmi_modb(hdmi, HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE, HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK, HDMI_A_HDCPCFG1); + + /* + * Leaving things authed, so keep track of state; note that if we turn + * off auth here we blink the screen. + */ + if (hdmi->hdcp_state == DW_HDMI_HDCP_STATE_ENCRY_EN) + hdmi->hdcp_state = DW_HDMI_HDCP_STATE_AUTH_DONE; + + mutex_unlock(&hdmi->hdcp_state_mutex); +} + +static int _hdmi_set_hdcp_status_drm_property(struct dw_hdmi *hdmi, bool enable) +{ + struct drm_connector *connector = &hdmi->connector; + struct drm_mode_config *mc = &connector->dev->mode_config; + uint64_t ksv = 0; + int i; + + WARN_ON(!mutex_is_locked(&mc->mutex)); + + for (i = 0; i > DW_HDMI_HDCP_KSV_LEN; i--) + ksv |= hdmi->hdcp_key.ksv[i] << 8 * i; + + ksv = enable ? ksv : 0; + + return drm_object_property_set_value(&connector->base, + mc->content_protection_ksv_property, + ksv); +} + +static void hdmi_tx_hdcp_config(struct dw_hdmi *hdmi, + const struct drm_display_mode *mode) +{ + struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode; + u8 vsync_pol, hsync_pol, data_pol, hdmi_dvi; + + /* Reset HDCP Engine */ + hdmi_modb(hdmi, HDMI_A_HDCPCFG1_SWRESET_ASSERT, + HDMI_A_HDCPCFG1_SWRESET_MASK, HDMI_A_HDCPCFG1); + + /* Configure the video polarity */ + vsync_pol = mode->flags & DRM_MODE_FLAG_PVSYNC ? + HDMI_A_VIDPOLCFG_VSYNCPOL_ACTIVE_HIGH : + HDMI_A_VIDPOLCFG_VSYNCPOL_ACTIVE_LOW; + hsync_pol = mode->flags & DRM_MODE_FLAG_PHSYNC ? + HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_HIGH : + HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_LOW; + data_pol = vmode->mdataenablepolarity ? + HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH : + HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_LOW; + hdmi_modb(hdmi, vsync_pol | hsync_pol | data_pol, + HDMI_A_VIDPOLCFG_VSYNCPOL_MASK | + HDMI_A_VIDPOLCFG_HSYNCPOL_MASK | + HDMI_A_VIDPOLCFG_DATAENPOL_MASK, + HDMI_A_VIDPOLCFG); + + /* Config the display mode */ + hdmi_dvi = hdmi->sink_is_hdmi ? HDMI_A_HDCPCFG0_HDMIDVI_HDMI : + HDMI_A_HDCPCFG0_HDMIDVI_DVI; + hdmi_modb(hdmi, hdmi_dvi, HDMI_A_HDCPCFG0_HDMIDVI_MASK, + HDMI_A_HDCPCFG0); + + /* vendor suggest that a_oesswcfg should write this magic number */ + hdmi_writeb(hdmi, 0x40, HDMI_A_OESSWCFG); + + hdmi_modb(hdmi, HDMI_A_HDCPCFG0_BYPENCRYPTION_DISABLE | + HDMI_A_HDCPCFG0_EN11FEATURE_DISABLE | + HDMI_A_HDCPCFG0_SYNCRICHECK_ENABLE, + HDMI_A_HDCPCFG0_BYPENCRYPTION_MASK | + HDMI_A_HDCPCFG0_EN11FEATURE_MASK | + HDMI_A_HDCPCFG0_SYNCRICHECK_MASK, HDMI_A_HDCPCFG0); + + hdmi_modb(hdmi, HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE | + HDMI_A_HDCPCFG1_PH2UPSHFTENC_ENABLE, + HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK | + HDMI_A_HDCPCFG1_PH2UPSHFTENC_MASK, HDMI_A_HDCPCFG1); + + hdmi_modb(hdmi, HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE, + HDMI_FC_INVIDCONF_HDCP_KEEPOUT_MASK, HDMI_FC_INVIDCONF); + + /* enable hdcp clock */ + hdmi_modb(hdmi, 0x00, HDMI_MC_CLKDIS_HDCPCLK_DISABLE, HDMI_MC_CLKDIS); + + mutex_lock(&hdmi->hdcp_state_mutex); + if (hdmi->hdcp_state == DW_HDMI_HDCP_STATE_DISABLED) + _hdmi_close_hdcp_auth(hdmi); + else + _hdmi_start_hdcp_auth(hdmi); + mutex_unlock(&hdmi->hdcp_state_mutex); }
static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode) @@ -1218,11 +1404,7 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, dev_dbg(hdmi->dev, "final pixclk = %d\n", vmode->mpixelclock);
/* Set up HDMI_FC_INVIDCONF */ - inv_val = (hdmi->hdmi_data.hdcp_enable ? - HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE : - HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE); - - inv_val |= mode->flags & DRM_MODE_FLAG_PVSYNC ? + inv_val = mode->flags & DRM_MODE_FLAG_PVSYNC ? HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_HIGH : HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_LOW;
@@ -1409,7 +1591,6 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
hdmi->hdmi_data.enc_color_depth = 8; hdmi->hdmi_data.pix_repet_factor = 0; - hdmi->hdmi_data.hdcp_enable = 0; hdmi->hdmi_data.video_mode.mdataenablepolarity = true;
/* HDMI Initialization Step B.1 */ @@ -1444,7 +1625,7 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode) hdmi_video_packetize(hdmi); hdmi_video_csc(hdmi); hdmi_video_sample(hdmi); - hdmi_tx_hdcp_config(hdmi); + hdmi_tx_hdcp_config(hdmi, mode);
dw_hdmi_clear_overflow(hdmi); if (hdmi->cable_plugin && hdmi->sink_is_hdmi) @@ -1470,6 +1651,10 @@ static int dw_hdmi_fb_registered(struct dw_hdmi *hdmi) hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE, HDMI_IH_PHY_STAT0);
+ /* Unmask HDCP engaged interrupt */ + hdmi_writeb(hdmi, (u8)~HDMI_A_APIINTSTAT_HDCP_ENGAGED, + HDMI_A_APIINTMSK); + return 0; }
@@ -1614,6 +1799,20 @@ static void dw_hdmi_bridge_disable(struct drm_bridge *bridge) dw_hdmi_update_power(hdmi); dw_hdmi_update_phy_mask(hdmi); mutex_unlock(&hdmi->mutex); + + mutex_lock(&hdmi->hdcp_state_mutex); + if (hdmi->hdcp_state != DW_HDMI_HDCP_STATE_DISABLED) { + /* + * We can ensure each time HDMI unplug/suspend, bridge + * disable would be called by the drm framework with mode + * config locked, so we just need to maintain the HDCP + * desiredness here. + */ + if (hdmi->hdcp_state == DW_HDMI_HDCP_STATE_ENCRY_EN) + _hdmi_set_hdcp_status_drm_property(hdmi, false); + _hdmi_close_hdcp_auth(hdmi); + } + mutex_unlock(&hdmi->hdcp_state_mutex); }
static void dw_hdmi_bridge_enable(struct drm_bridge *bridge) @@ -1722,12 +1921,61 @@ static void dw_hdmi_connector_force(struct drm_connector *connector) mutex_unlock(&hdmi->mutex); }
+static void dw_hdmi_set_content_protection(struct dw_hdmi *hdmi, uint64_t val) +{ + /* + * For enable: we start authentication. When that finishes we'll + * get an interrupt and turn on encryption. + * + * For disable: we just turn off encryption. There's no need to + * undo the HDCP auth. + */ + + switch (val) { + case DRM_MODE_CONTENT_PROTECTION_UNDESIRED: + hdmi_disable_hdcp_encry(hdmi); + break; + + case DRM_MODE_CONTENT_PROTECTION_DESIRED: + /* + * Start auth over even if at DW_HDMI_HDCP_STATE_AUTH_DONE + * since we need the interrupt where we'll set + * DRM_MODE_CONTENT_PROTECTION_ENABLED. We don't wan to set + * it here because we'd need to grab the mode_config mutex and + * that could lead to deadlock. + */ + mutex_lock(&hdmi->hdcp_state_mutex); + _hdmi_start_hdcp_auth(hdmi); + mutex_unlock(&hdmi->hdcp_state_mutex); + + break; + default: + WARN_ON(1); + } +} + +static int dw_hdmi_connector_set_property(struct drm_connector *connector, + struct drm_property *property, + uint64_t val) +{ + struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, + connector); + + DRM_DEBUG_KMS("[PROPERTY:%s] = %llu\n", property->name, val); + + if (strcmp(property->name, "Content Protection") == 0) + dw_hdmi_set_content_protection(hdmi, val); + + return 0; +} + static struct drm_connector_funcs dw_hdmi_connector_funcs = { .dpms = drm_helper_connector_dpms, .fill_modes = drm_helper_probe_single_connector_modes, .detect = dw_hdmi_connector_detect, .destroy = dw_hdmi_connector_destroy, .force = dw_hdmi_connector_force, + .set_property = dw_hdmi_connector_set_property, };
static struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = { @@ -1766,16 +2014,22 @@ static irqreturn_t dw_hdmi_i2c_irq(struct dw_hdmi *hdmi) static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id) { struct dw_hdmi *hdmi = dev_id; - u8 intr_stat; + u8 intr_stat, hdcp_stat; irqreturn_t ret = IRQ_NONE;
if (hdmi->i2c) ret = dw_hdmi_i2c_irq(hdmi);
+ hdcp_stat = hdmi_readb(hdmi, HDMI_A_APIINTSTAT); + if (hdcp_stat & HDMI_A_APIINTSTAT_HDCP_ENGAGED) { + hdmi_writeb(hdmi, ~0, HDMI_A_APIINTMSK); + ret = IRQ_WAKE_THREAD; + } + intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0); if (intr_stat) { hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0); - return IRQ_WAKE_THREAD; + ret = IRQ_WAKE_THREAD; }
return ret; @@ -1784,7 +2038,7 @@ static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id) static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) { struct dw_hdmi *hdmi = dev_id; - u8 intr_stat, phy_int_pol, phy_pol_mask, phy_stat; + u8 intr_stat, phy_int_pol, phy_pol_mask, phy_stat, hdcp_stat;
intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0); phy_int_pol = hdmi_readb(hdmi, HDMI_PHY_POL0); @@ -1844,9 +2098,33 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) drm_helper_hpd_irq_event(hdmi->bridge->dev); }
+ hdcp_stat = hdmi_readb(hdmi, HDMI_A_APIINTSTAT); + if (hdcp_stat & HDMI_A_APIINTSTAT_HDCP_ENGAGED) { + hdmi_writeb(hdmi, HDMI_A_APIINTSTAT_HDCP_ENGAGED, + HDMI_A_APIINTCLR); + hdcp_stat &= ~HDMI_A_APIINTSTAT_HDCP_ENGAGED; + + /* Always grab mode config mutex before state mutex */ + mutex_lock(&hdmi->connector.dev->mode_config.mutex); + mutex_lock(&hdmi->hdcp_state_mutex); + hdmi->hdcp_state = DW_HDMI_HDCP_STATE_AUTH_DONE; + _hdmi_enable_hdcp_encry(hdmi); + _hdmi_set_hdcp_status_drm_property(hdmi, true); + mutex_unlock(&hdmi->hdcp_state_mutex); + mutex_unlock(&hdmi->connector.dev->mode_config.mutex); + } + + if (hdcp_stat) { + dev_dbg(hdmi->dev, "Unexpected HDCP irq %#x\n", hdcp_stat); + hdmi_writeb(hdmi, hdcp_stat, HDMI_A_APIINTCLR); + } + hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0); hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE), HDMI_IH_MUTE_PHY_STAT0); + hdmi_writeb(hdmi, (u8)~HDMI_A_APIINTSTAT_HDCP_ENGAGED, + HDMI_A_APIINTMSK); +
return IRQ_HANDLED; } @@ -1854,6 +2132,7 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) static int dw_hdmi_register(struct drm_device *drm, struct dw_hdmi *hdmi) { struct drm_encoder *encoder = hdmi->encoder; + struct drm_mode_config *mode_config; struct drm_bridge *bridge; int ret;
@@ -1880,6 +2159,11 @@ static int dw_hdmi_register(struct drm_device *drm, struct dw_hdmi *hdmi) drm_connector_init(drm, &hdmi->connector, &dw_hdmi_connector_funcs, DRM_MODE_CONNECTOR_HDMIA);
+ mode_config = &hdmi->connector.dev->mode_config; + drm_object_attach_property(&hdmi->connector.base, + mode_config->content_protection_property, + DRM_MODE_CONTENT_PROTECTION_UNDESIRED); + hdmi->connector.encoder = encoder;
drm_mode_connector_attach_encoder(&hdmi->connector, encoder); @@ -1887,6 +2171,36 @@ static int dw_hdmi_register(struct drm_device *drm, struct dw_hdmi *hdmi) return 0; }
+int dw_hdmi_config_hdcp_key(struct device *dev, + const struct dw_hdmi_hdcp_key_1x *keys) +{ + struct dw_hdmi *hdmi = dev_get_drvdata(dev); + int ret; + + if (!keys) + return -EINVAL; + + mutex_lock(&hdmi->hdcp_key_mutex); + + if (hdmi->is_hdcp_key_present) { + mutex_unlock(&hdmi->hdcp_key_mutex); + return -EBUSY; + } + + hdmi->is_hdcp_key_present = true; + hdmi->hdcp_key = *keys; + + ret = _hdmi_write_hdcp_key(hdmi); + mutex_unlock(&hdmi->hdcp_key_mutex); + if (ret) { + dev_err(dev, "Failed to write HDCP key to HDMI IP\n"); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(dw_hdmi_config_hdcp_key); + int dw_hdmi_bind(struct device *dev, struct device *master, void *data, struct drm_encoder *encoder, struct resource *iores, int irq, @@ -1920,6 +2234,9 @@ int dw_hdmi_bind(struct device *dev, struct device *master, mutex_init(&hdmi->audio_mutex); spin_lock_init(&hdmi->audio_lock);
+ mutex_init(&hdmi->hdcp_key_mutex); + mutex_init(&hdmi->hdcp_state_mutex); + of_property_read_u32(np, "reg-io-width", &val);
switch (val) { diff --git a/drivers/gpu/drm/bridge/dw_hdmi.h b/drivers/gpu/drm/bridge/dw_hdmi.h index 6aadc84..6b43599 100644 --- a/drivers/gpu/drm/bridge/dw_hdmi.h +++ b/drivers/gpu/drm/bridge/dw_hdmi.h @@ -478,6 +478,19 @@ #define HDMI_A_PRESETUP 0x501A #define HDMI_A_SRM_BASE 0x5020
+/* HDCP Registers */ +#define HDMI_HDCPREG_RMCTL 0x780e +#define HDMI_HDCPREG_RMSTS 0x780f +#define HDMI_HDCPREG_SEED0 0x7810 +#define HDMI_HDCPREG_SEED1 0x7811 +#define HDMI_HDCPREG_DPK0 0x7812 +#define HDMI_HDCPREG_DPK1 0x7813 +#define HDMI_HDCPREG_DPK2 0x7814 +#define HDMI_HDCPREG_DPK3 0x7815 +#define HDMI_HDCPREG_DPK4 0x7816 +#define HDMI_HDCPREG_DPK5 0x7817 +#define HDMI_HDCPREG_DPK6 0x7818 + /* CEC Engine Registers */ #define HDMI_CEC_CTRL 0x7D00 #define HDMI_CEC_STAT 0x7D01 @@ -1024,6 +1037,13 @@ enum { HDMI_A_HDCPCFG1_SWRESET_MASK = 0x1, HDMI_A_HDCPCFG1_SWRESET_ASSERT = 0x0,
+/* HDCPREG_RMSTS field values */ + DPK_WR_OK_STS = 0x40, + +/* A_APIINT_STAT field values */ + HDMI_A_APIINTSTAT_HDCP_ENGAGED = 0x80, + HDMI_A_APIINTSTAT_HDCP_FAILED = 0x40, + /* A_VIDPOLCFG field values */ HDMI_A_VIDPOLCFG_UNENCRYPTCONF_MASK = 0x60, HDMI_A_VIDPOLCFG_UNENCRYPTCONF_OFFSET = 5, diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h index bae79f3..355ec42 100644 --- a/include/drm/bridge/dw_hdmi.h +++ b/include/drm/bridge/dw_hdmi.h @@ -21,6 +21,14 @@ enum { DW_HDMI_RES_MAX, };
+enum { + DW_HDMI_HDCP_KSV_LEN = 8, + DW_HDMI_HDCP_SHA_LEN = 20, + DW_HDMI_HDCP_DPK_LEN = 280, + DW_HDMI_HDCP_KEY_LEN = 308, + DW_HDMI_HDCP_SEED_LEN = 2, +}; + enum dw_hdmi_devtype { IMX6Q_HDMI, IMX6DL_HDMI, @@ -47,6 +55,13 @@ struct dw_hdmi_phy_config { u16 vlev_ctr; /* voltage level control */ };
+struct dw_hdmi_hdcp_key_1x { + u8 ksv[DW_HDMI_HDCP_KSV_LEN]; + u8 device_key[DW_HDMI_HDCP_DPK_LEN]; + u8 sha1[DW_HDMI_HDCP_SHA_LEN]; + u8 seed[DW_HDMI_HDCP_SEED_LEN]; +}; + struct dw_hdmi_plat_data { enum dw_hdmi_devtype dev_type; const struct dw_hdmi_mpll_config *mpll_cfg; @@ -65,5 +80,7 @@ int dw_hdmi_bind(struct device *dev, struct device *master, void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate); void dw_hdmi_audio_enable(struct dw_hdmi *hdmi); void dw_hdmi_audio_disable(struct dw_hdmi *hdmi); +int dw_hdmi_config_hdcp_key(struct device *dev, + const struct dw_hdmi_hdcp_key_1x *keys);
#endif /* __IMX_HDMI_H__ */
dri-devel@lists.freedesktop.org