From 76ad38c73480e7892cf537a3c8ee83a2ed79a222 Mon Sep 17 00:00:00 2001 From: Oliver Brown Date: Tue, 5 Feb 2019 03:10:01 +0200 Subject: [PATCH] MLK-20892-2 drm: imx: hdp: Add support for HDCP Adding support for HDCP 1.4 and 2.2 based upon upstream 4.19 kernel use of "Content Protection" connector property. Signed-off-by: Oliver Brown --- drivers/gpu/drm/imx/hdp/Makefile | 2 +- drivers/gpu/drm/imx/hdp/imx-hdcp-private.h | 25 + drivers/gpu/drm/imx/hdp/imx-hdcp.c | 689 +++++++++++++++++++++ drivers/gpu/drm/imx/hdp/imx-hdcp.h | 26 + drivers/gpu/drm/imx/hdp/imx-hdp.c | 45 +- drivers/gpu/drm/imx/hdp/imx-hdp.h | 7 +- 6 files changed, 790 insertions(+), 4 deletions(-) create mode 100644 drivers/gpu/drm/imx/hdp/imx-hdcp-private.h create mode 100644 drivers/gpu/drm/imx/hdp/imx-hdcp.c create mode 100644 drivers/gpu/drm/imx/hdp/imx-hdcp.h diff --git a/drivers/gpu/drm/imx/hdp/Makefile b/drivers/gpu/drm/imx/hdp/Makefile index d0f8ab8c3fa2..84c7267642fc 100644 --- a/drivers/gpu/drm/imx/hdp/Makefile +++ b/drivers/gpu/drm/imx/hdp/Makefile @@ -1,5 +1,5 @@ obj-$(CONFIG_DRM_IMX_HDP) += imx-hdp.o \ - imx-dp.o imx-hdmi.o imx-arc.o\ + imx-dp.o imx-hdmi.o imx-arc.o imx-hdcp.o\ API_AFE_ss28fdsoi_kiran_hdmitx.o \ API_AFE_t28hpc_hdmitx.o \ ss28fdsoi_hdmitx_table.o \ diff --git a/drivers/gpu/drm/imx/hdp/imx-hdcp-private.h b/drivers/gpu/drm/imx/hdp/imx-hdcp-private.h new file mode 100644 index 000000000000..a8aa12d8f865 --- /dev/null +++ b/drivers/gpu/drm/imx/hdp/imx-hdcp-private.h @@ -0,0 +1,25 @@ +/* + * Copyright 2017-2019 NXP + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _IMX_HDCP_PRIVATE_H_ +#define _IMX_HDCP_PRIVATE_H_ + +struct imx_hdcp { + struct mutex mutex; + u64 value; /* protected by hdcp_mutex */ + struct delayed_work check_work; + struct work_struct prop_work; + u8 bus_type; + u8 config; +}; +#endif diff --git a/drivers/gpu/drm/imx/hdp/imx-hdcp.c b/drivers/gpu/drm/imx/hdp/imx-hdcp.c new file mode 100644 index 000000000000..4f10737953d9 --- /dev/null +++ b/drivers/gpu/drm/imx/hdp/imx-hdcp.c @@ -0,0 +1,689 @@ +/* + * Copyright 2019 NXP + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "imx-hdp.h" +#include "imx-hdcp-private.h" + +static uint8_t recv_id[] = { + 0x8B, 0xA4, 0x47, 0x42, 0xFB +}; + +static uint8_t m[] = { + 0xF9, 0xF1, 0x30, 0xA8, + 0x2D, 0x5B, 0xE5, 0xC3, + 0xE1, 0x7A, 0xB0, 0xFD, + 0x0F, 0x54, 0x40, 0x52 +}; + +static uint8_t km[] = { + 0xCA, 0x9F, 0x83, 0x95, + 0x70, 0xD0, 0xD0, 0xF9, + 0xCF, 0xE4, 0xEB, 0x54, + 0x7E, 0x09, 0xFA, 0x3B +}; + +static uint8_t ekhkm[] = { + 0xE6, 0x57, 0x8E, 0xBC, + 0xC7, 0x68, 0x44, 0x87, + 0x88, 0x8A, 0x9B, 0xD7, + 0xD6, 0xAE, 0x38, 0xBE +}; + +static char const *g_last_error[16] = { + "No Error", + "HPD is down", + "SRM failure", + "Signature verification error", + "h tag != h", + "V tag diff v", + "Locality check", + "DDC error", + "REAUTH_REQ", + "Topology error", + "Verify receiver ID list failed", + "HDCP_RSVD1 was not 0,0,0", + "HDMI capability or mode", + "RI result was different than expected", + "WatchDog expired", + "Repeater integrity failed" +}; + +static char const *g_rx_type[4] = { + "Unknown", "HDCP 1", "HDCP 2", "Unknown" }; + +#define HDCP_MAX_RECEIVERS 32 +#define HDCP_RECEIVER_ID_SIZE_BYTES 5 +#define HPD_EVENT 1 +#define HDCP_STATUS_SIZE 0x5 +#define HDCP_PORT_STS_AUTH 0x1 +#define HDCP_PORT_STS_REPEATER 0x2 +#define HDCP_PORT_STS_REPEATER 0x2 +#define HDCP_PORT_STS_TYPE_MASK 0xc +#define HDCP_PORT_STS_TYPE_SHIFT 0x2 +#define HDCP_PORT_STS_AUTH_STREAM_ID_SHIFT 0x4 +#define HDCP_PORT_STS_AUTH_STREAM_ID_MASK 0x10 +#define HDCP_PORT_STS_LAST_ERR_SHIFT 0x5 +#define HDCP_PORT_STS_LAST_ERR_MASK (0x0F << 5) +#define GET_HDCP_PORT_STS_LAST_ERR(__sts__) \ + (((__sts__) & HDCP_PORT_STS_LAST_ERR_MASK) >> \ + HDCP_PORT_STS_LAST_ERR_SHIFT) +#define HDCP_PORT_STS_1_1_FEATURES 0x200 + +#define HDCP_DPCD_CERTRX_ADDRESS 0x6900B + +#define HDCP_CONFIG_NONE ((u8) 0) +#define HDCP_CONFIG_1_4 ((u8) 1) /* use HDCP 1.4 only */ +#define HDCP_CONFIG_2_2 ((u8) 2) /* use HDCP 2.2 only */ +/* use All HDCP versions */ +#define HDCP_CONFIG_ALL (HDCP_CONFIG_1_4 | HDCP_CONFIG_2_2) + +static void imx_hdcp_print_port_status(uint16_t sts) +{ + DRM_DEBUG_KMS("INFO: HDCP Port Status: 0x%04x\n", sts); + DRM_DEBUG_KMS(" Authenticated: %d\n", + (bool)(sts & HDCP_PORT_STS_AUTH)); + DRM_DEBUG_KMS(" Receiver is repeater: %d\n", + (bool)(sts & HDCP_PORT_STS_REPEATER)); + DRM_DEBUG_KMS(" RX Type: %s\n", + g_rx_type[((sts & HDCP_PORT_STS_TYPE_MASK) + >> HDCP_PORT_STS_TYPE_SHIFT)]); + DRM_DEBUG_KMS(" AuthStreamId: %d\n", + (bool)(sts & HDCP_PORT_STS_AUTH_STREAM_ID_MASK)); + DRM_DEBUG_KMS(" Last Error: %s\n", + g_last_error[((sts & HDCP_PORT_STS_LAST_ERR_MASK) + >> HDCP_PORT_STS_LAST_ERR_SHIFT)]); + DRM_DEBUG_KMS(" Enable 1.1 Features: %d\n", + (bool)(sts & HDCP_PORT_STS_1_1_FEATURES)); +} + +static unsigned int wait4event(struct imx_hdp *hdp, + unsigned int *events, + unsigned int event_to_wait) +{ + unsigned int reg_events; + unsigned int returned_events; + unsigned int event_mask = 1U << event_to_wait | + 1U << EVENT_ID_HDCPTX_STATUS; + int timeout = 50000; /* 50000 * 10 u is at least ~500 miliseconds */ + + while ((event_mask & *events) == 0) { + if (timeout-- == 0) + return 0; + udelay(10); + CDN_API_Get_Event(&hdp->state, ®_events); + *events |= reg_events; + } + + returned_events = *events & event_mask; + *events &= ~event_mask; + return returned_events; +} + +static inline +uint16_t imx_hdcp_get_port_status(u8 hdcp_status[HDCP_STATUS_SIZE]) +{ + return (hdcp_status[0] << 8) | hdcp_status[1]; +} + +static u16 imx_hdcp_get_status(struct imx_hdp *hdp) +{ + CDN_BUS_TYPE bt = hdp->hdcp.bus_type; + u8 hdcp_status[HDCP_STATUS_SIZE]; + u16 hdcp_port_status; + + DRM_DEBUG_KMS("INFO: Reading HDCP TX status\n"); + CDN_API_HDCP_TX_STATUS_REQ_blocking(&hdp->state, hdcp_status, bt); + + hdcp_port_status = imx_hdcp_get_port_status(hdcp_status); + + return hdcp_port_status; +} + +static inline unsigned char check_event(unsigned int events, + unsigned int tested) +{ + if ((events & (1 << tested)) == 0) + return 0; + return 1; +} + +/* Prints status. Returns error code (0 = no error) */ +static unsigned char imx_hdcp_handle_status(unsigned short status) +{ + imx_hdcp_print_port_status(status); + if (status & HDCP_PORT_STS_LAST_ERR_MASK) + DRM_ERROR("ERROR: HDCP error was set to %s\n", + g_last_error[((status & HDCP_PORT_STS_LAST_ERR_MASK) + >> HDCP_PORT_STS_LAST_ERR_SHIFT)]); + return GET_HDCP_PORT_STS_LAST_ERR(status); +} + +static int imx_hdcp_set_config(struct imx_hdp *hdp, u8 hdcp_config) +{ + CDN_API_STATUS ret; + CDN_BUS_TYPE bt = hdp->hdcp.bus_type; + uint8_t apb_cfg_dpcd_sts = 0; + uint8_t apb_cfg_hdcp_sts = 0; + uint8_t apb_cfg_capb_sts = 0; + uint8_t hdcp_cfg; + + unsigned int events = 0; + unsigned int retEvents; + unsigned short hdcp_port_status; + + if (bt == CDN_BUS_TYPE_SAPB) { + DRM_DEBUG_KMS("INFO: Switching HDCP Commands to SAPB.\n"); + ret = CDN_API_ApbConf_blocking + (&hdp->state, + 0, 0, /* set DPCD to APB (don't lock) */ + 1, 0, /* set HDCP to SAPB (don't lock) */ + 0, 0, /* set CAPB owner CPU (don't lock) */ + &apb_cfg_dpcd_sts, + &apb_cfg_hdcp_sts, + &apb_cfg_capb_sts); + + if (ret != CDN_OK) { + DRM_ERROR("Failed to set APB configuration.\n"); + return -1; + } + if (apb_cfg_hdcp_sts == 1) { /* 1 - locked */ + DRM_ERROR("Failed to switch HDCP to SAPB Mailbox\n"); + return -1; + } + DRM_DEBUG_KMS("INFO: HDCP switched to SAPB\n"); + } else + DRM_DEBUG_KMS("INFO: Leaving HDCP to APB\n"); + + /* HDCP 2.2(and/or 1.4) | activate | km-key | 0 */ + hdcp_cfg = hdcp_config | 0x04 | 0x10 | (HDCP_CONTENT_TYPE_0 << 3); + + DRM_DEBUG_KMS("INFO: Enabling HDCP...\n"); + CDN_API_HDCP_TX_CONFIGURATION_blocking(&hdp->state, hdcp_cfg, bt); + + /* Wait until HDCP_TX_STATUS EVENT appears */ + DRM_DEBUG_KMS("INFO: wait4event -> EVENT_ID_HDCPTX_STATUS\n"); + retEvents = wait4event(hdp, &events, EVENT_ID_HDCPTX_STATUS); + + /* Set TX STATUS REQUEST */ + DRM_DEBUG_KMS("INFO: Getting port status\n"); + hdcp_port_status = imx_hdcp_get_status(hdp); + if (imx_hdcp_handle_status(hdcp_port_status) != 0) + return -1; + + return 0; +} + +static int imx_hdcp_auth_check(struct imx_hdp *hdp) +{ + u32 events; + u16 hdcp_port_status; + + /* Wait until HDCP_TX_STATUS EVENT appears */ + events = wait4event(hdp, &events, EVENT_ID_HDCPTX_STATUS); + if (events == 0) + return -1; + DRM_DEBUG_KMS("HDCP: EVENT_ID_HDCPTX_STATUS\n"); + + hdcp_port_status = imx_hdcp_get_status(hdp); + if (imx_hdcp_handle_status(hdcp_port_status) != 0) + return -1; + + if (hdcp_port_status & 1) { + DRM_INFO("Authentication completed successfully!\n"); + return 0; + } + DRM_ERROR("Authentication failed\n"); + return -1; +} + +static int imx_hdcp_check_receviers(struct imx_hdp *hdp) +{ + CDN_BUS_TYPE bt = hdp->hdcp.bus_type; + unsigned int events = 0; + unsigned int ret_events; + uint8_t hdcp_num_rec; + uint8_t hdcp_rec_id[HDCP_MAX_RECEIVERS][HDCP_RECEIVER_ID_SIZE_BYTES]; + uint8_t i; + + DRM_DEBUG_KMS("INFO: Waiting for Receiver ID valid event\n"); + ret_events = wait4event(hdp, &events, + EVENT_ID_HDCPTX_IS_RECEIVER_ID_VALID); + DRM_DEBUG_KMS("INFO: Requesting Receivers ID's\n"); + + hdcp_num_rec = 0; + memset(&hdcp_rec_id, 0, sizeof(hdcp_rec_id)); + + CDN_API_HDCP_TX_IS_RECEIVER_ID_VALID_REQ_blocking + (&hdp->state, + &hdcp_num_rec, + (unsigned char *) + hdcp_rec_id, + bt + ); + + DRM_DEBUG_KMS("INFO: Number of Receivers: %d\n", hdcp_num_rec); + + for (i = 0; i < hdcp_num_rec; ++i) { + DRM_INFO("\tReveiver ID%2d: %.2X%.2X%.2X%.2X%.2X\n", + i, + hdcp_rec_id[i][0], + hdcp_rec_id[i][1], + hdcp_rec_id[i][2], + hdcp_rec_id[i][3], + hdcp_rec_id[i][4] + ); + } + + /* fixme: Check Receiver ID's against revocation list */ + + DRM_DEBUG_KMS("INFO: Responding with Receiver ID's OK!\n"); + CDN_API_HDCP_TX_RESPOND_RECEIVER_ID_VALID_blocking(&hdp->state, 1, + hdp->hdcp.bus_type); + + return 0; +} + +static inline int imx_hdcp_auth_22(struct imx_hdp *hdp) +{ + CDN_BUS_TYPE bt = hdp->hdcp.bus_type; + unsigned char kmStored = 0; + unsigned int events = 0; + unsigned int retEvents; + unsigned short hdcp_port_status; + u8 resp[HDCP_STATUS_SIZE]; + S_HDCP_TRANS_PAIRING_DATA pairing; + + DRM_DEBUG_KMS("HDCP: Start 2.2 Authentication\n"); + + /* Wait until HDCP2_TX_IS_KM_STORED EVENT appears */ + retEvents = 0; + DRM_DEBUG_KMS("INFO: Wait until HDCP2_TX_IS_KM_STORED EVENT appears\n"); + while (check_event(retEvents, EVENT_ID_HDCPTX_IS_KM_STORED) == 0) { + DRM_DEBUG_KMS("INFO: Waiting FOR _IS_KM_STORED EVENT\n"); + retEvents = wait4event(hdp, &events, + EVENT_ID_HDCPTX_IS_KM_STORED); + if (retEvents == 0) { + /* time out occurred, stop hdcp and return error */ + CDN_API_HDCP_TX_CONFIGURATION_blocking(&hdp->state, + 0, bt); + return -1; + } + if (check_event(retEvents, EVENT_ID_HDCPTX_STATUS) != 0) { + hdcp_port_status = imx_hdcp_get_status(hdp); + if (imx_hdcp_handle_status(hdcp_port_status) != 0) + return -1; + } + } + + DRM_DEBUG_KMS("HDCP: EVENT_ID_HDCPTX_IS_KM_STORED\n"); + + /* Set HDCP2 TX KM STORED REQUEST */ + CDN_API_HDCP2_TX_IS_KM_STORED_REQ_blocking(&hdp->state, resp, bt); + DRM_DEBUG_KMS("HDCP: CDN_API_HDCP2_TX_IS_KM_STORED_REQ_blocking\n"); + DRM_DEBUG_KMS("HDCP: Receiver ID: 0x%x%x%x%x%x\n", + resp[0], resp[1], resp[2], resp[3], resp[4]); + + /* Check if KM is stored */ + if (kmStored != 0) { + DRM_DEBUG_KMS("INFO: KM is stored\n"); + /* Set HDCP2 TX RESPOND KM with stored KM */ + memcpy(pairing.receiver_id, recv_id, sizeof(recv_id)); + memcpy(pairing.m, m, sizeof(m)); + memcpy(pairing.km, km, sizeof(km)); + memcpy(pairing.ekh, ekhkm, sizeof(ekhkm)); + CDN_API_HDCP2_TX_RESPOND_KM_blocking(&hdp->state, &pairing, bt); + DRM_DEBUG_KMS("HDCP: CDN_API_HDCP2_TX_RESPOND_KM_blocking\n"); + } else { /* KM is not stored */ + DRM_DEBUG_KMS("INFO: KM is not stored\n"); + /* Set HDCP2 TX RESPOND KM with empty data */ + CDN_API_HDCP2_TX_RESPOND_KM_blocking(&hdp->state, NULL, bt); + } + + imx_hdcp_check_receviers(hdp); + + /* Check if KM is not stored */ + if (kmStored == 0) { + /* Wait until HDCP2_TX_STORE_KM EVENT appears */ + retEvents = 0; + while (check_event(retEvents, EVENT_ID_HDCPTX_STORE_KM) == 0) { + retEvents = wait4event(hdp, &events, + EVENT_ID_HDCPTX_STORE_KM); + if (check_event(retEvents, EVENT_ID_HDCPTX_STATUS) + != 0) { + hdcp_port_status = imx_hdcp_get_status(hdp); + if (imx_hdcp_handle_status(hdcp_port_status) + != 0) + return -1; + } + } + DRM_DEBUG_KMS("HDCP: EVENT_ID_HDCPTX_STORE_KM\n"); + + /* Set HDCP2_TX_STORE_KM REQUEST */ + CDN_API_HDCP2_TX_STORE_KM_REQ_blocking(&hdp->state, &pairing, + bt); + DRM_DEBUG_KMS("HDCP: CDN_API_HDCP2_TX_STORE_KM_REQ_blocking"); + } + + return 0; +} + +static inline int imx_hdcp_auth_14(struct imx_hdp *hdp) +{ + DRM_DEBUG_KMS("HDCP: Starting 1.4 Authentication\n"); + return imx_hdcp_check_receviers(hdp); +} + +static int imx_hdcp_auth(struct imx_hdp *hdp, u8 hdcp_config) +{ + int ret; + + DRM_DEBUG_KMS("HDCP: Start Authentication\n"); + ret = imx_hdcp_set_config(hdp, hdcp_config); + if (ret) { + DRM_ERROR("imx_hdcp_set_config failed\n"); + return -1; + } + if (hdcp_config == HDCP_TX_1) + ret = imx_hdcp_auth_14(hdp); + else + ret = imx_hdcp_auth_22(hdp); + + if (ret) { + DRM_ERROR("imx_hdcp_auth_%s failed\n", + (hdcp_config == HDCP_TX_1) ? "14" : "22"); + return -1; + } + + ret = imx_hdcp_auth_check(hdp); + if (ret) + ret = imx_hdcp_auth_check(hdp); + return ret; +} + +static int _imx_hdcp_disable(struct imx_hdp *hdp) +{ + int ret = 0; + uint8_t hdcp_cfg = 0; + CDN_BUS_TYPE bt = hdp->hdcp.bus_type; + + DRM_DEBUG_KMS("[%s:%d] HDCP is being disabled...\n", + hdp->connector.name, hdp->connector.base.id); + DRM_DEBUG_KMS("INFO: Disabling HDCP...\n"); + + ret = CDN_API_HDCP_TX_CONFIGURATION_blocking(&hdp->state, hdcp_cfg, bt); + + DRM_DEBUG_KMS("HDCP is disabled\n"); + + return ret; +} + + +static int _imx_hdcp_enable(struct imx_hdp *hdp) +{ + int i, ret = 0, tries = 3; + + DRM_DEBUG_KMS("[%s:%d] HDCP is being enabled...\n", + hdp->connector.name, hdp->connector.base.id); + + /* Incase of authentication failures, HDCP spec expects reauth. */ + for (i = 0; i < tries; i++) { + if (hdp->hdcp.config & HDCP_CONFIG_2_2) { + ret = imx_hdcp_auth(hdp, HDCP_TX_2); + if (ret == 0) + return 0; + } + _imx_hdcp_disable(hdp); + if (hdp->hdcp.config & HDCP_CONFIG_1_4) { + ret = imx_hdcp_auth(hdp, HDCP_TX_1); + if (!ret) + return 0; + } + DRM_DEBUG_KMS("HDCP Auth failure (%d)\n", ret); + + /* Ensuring HDCP encryption and signalling are stopped. */ + _imx_hdcp_disable(hdp); + } + + DRM_ERROR("HDCP authentication failed (%d tries/%d)\n", tries, ret); + return ret; +} + +static void imx_hdcp_check_work(struct work_struct *work) +{ + struct imx_hdcp *hdcp = container_of(work, + struct imx_hdcp, + check_work.work); + struct imx_hdp *hdp = container_of(hdcp, + struct imx_hdp, + hdcp); + if (!imx_hdcp_check_link(hdp)) + schedule_delayed_work(&hdcp->check_work, + DRM_HDCP_CHECK_PERIOD_MS); +} + + +static void imx_hdcp_prop_work(struct work_struct *work) +{ + struct imx_hdcp *hdcp = container_of(work, + struct imx_hdcp, + prop_work); + struct imx_hdp *hdp = container_of(hdcp, + struct imx_hdp, + hdcp); + + struct drm_device *dev = hdp->drm_dev; + struct drm_connector_state *state; + + drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); + mutex_lock(&hdp->hdcp.mutex); + + /* + * This worker is only used to flip between ENABLED/DESIRED. Either of + * those to UNDESIRED is handled by core. If hdcp_value == UNDESIRED, + * we're running just after hdcp has been disabled, so just exit + */ + if (hdp->hdcp.value != DRM_MODE_CONTENT_PROTECTION_UNDESIRED) { + state = hdp->connector.state; + state->content_protection = hdp->hdcp.value; + } + + mutex_unlock(&hdp->hdcp.mutex); + drm_modeset_unlock(&dev->mode_config.connection_mutex); +} + +static void show_hdcp_supported(struct imx_hdp *hdp) +{ + if ((hdp->hdcp.config & (HDCP_CONFIG_1_4 | HDCP_CONFIG_2_2)) == + (HDCP_CONFIG_1_4 | HDCP_CONFIG_2_2)) + DRM_INFO("Both HDCP 1.4 and 2 2 are enabled\n"); + else if (hdp->hdcp.config & HDCP_CONFIG_1_4) + DRM_INFO("Only HDCP 1.4 is enabled\n"); + else if (hdp->hdcp.config & HDCP_CONFIG_2_2) + DRM_INFO("Only HDCP 2.2 is enabled\n"); + else + DRM_INFO("HDCP is disabled\n"); +} + +int imx_hdcp_init(struct imx_hdp *hdp, struct device_node *of_node) +{ + int ret; + const char *compat; + u32 temp; + + ret = of_property_read_string(of_node, "compatible", &compat); + if (ret) { + DRM_ERROR("Failed to compatible dts string\n"); + return ret; + } + + ret = of_property_read_u32(of_node, "hdcp-config", &temp); + if (ret) { + /* hdp->hdcp_config = HDCP_CONFIG_ALL; */ + /* using highest level by default */ + hdp->hdcp.config = HDCP_CONFIG_2_2; + DRM_INFO("Failed to get HDCP config - using HDCP 2.2 only\n"); + } else { + hdp->hdcp.config = temp; + show_hdcp_supported(hdp); + } + + if (!check_hdcp_enabled()) + return -EPERM; + + if (!strstr(compat, "hdmi")) + return -EPERM; + + if (strstr(compat, "imx8mq")) + hdp->hdcp.bus_type = CDN_BUS_TYPE_SAPB; + else + hdp->hdcp.bus_type = CDN_BUS_TYPE_APB; + + ret = drm_connector_attach_content_protection_property(&hdp->connector); + if (ret) + return ret; + + /*connector->hdcp_shim = hdcp_shim;*/ + mutex_init(&hdp->hdcp.mutex); + INIT_DELAYED_WORK(&hdp->hdcp.check_work, imx_hdcp_check_work); + INIT_WORK(&hdp->hdcp.prop_work, imx_hdcp_prop_work); + return 0; +} + +int imx_hdcp_enable(struct imx_hdp *hdp) +{ + int ret; + + mutex_lock(&hdp->hdcp.mutex); + + ret = _imx_hdcp_enable(hdp); + if (ret) + goto out; + + hdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_ENABLED; + schedule_work(&hdp->hdcp.prop_work); + schedule_delayed_work(&hdp->hdcp.check_work, + DRM_HDCP_CHECK_PERIOD_MS); +out: + mutex_unlock(&hdp->hdcp.mutex); + return ret; +} + +int imx_hdcp_disable(struct imx_hdp *hdp) +{ + int ret = 0; + + mutex_lock(&hdp->hdcp.mutex); + if (hdp->hdcp.value != DRM_MODE_CONTENT_PROTECTION_UNDESIRED) { + hdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; + ret = _imx_hdcp_disable(hdp); + } + mutex_unlock(&hdp->hdcp.mutex); + cancel_delayed_work_sync(&hdp->hdcp.check_work); + + return ret; +} + +void imx_hdcp_atomic_check(struct drm_connector *connector, + struct drm_connector_state *old_state, + struct drm_connector_state *new_state) +{ + uint64_t old_cp = old_state->content_protection; + uint64_t new_cp = new_state->content_protection; + struct drm_crtc_state *crtc_state; + + if (!new_state->crtc) { + /* + * If the connector is being disabled with CP enabled, mark it + * desired so it's re-enabled when the connector is brought back + */ + if (old_cp == DRM_MODE_CONTENT_PROTECTION_ENABLED) + new_state->content_protection = + DRM_MODE_CONTENT_PROTECTION_DESIRED; + return; + } + + /* + * Nothing to do if the state didn't change, or HDCP was activated since + * the last commit + */ + if (old_cp == new_cp || + (old_cp == DRM_MODE_CONTENT_PROTECTION_DESIRED && + new_cp == DRM_MODE_CONTENT_PROTECTION_ENABLED)) + return; + + crtc_state = drm_atomic_get_new_crtc_state(new_state->state, + new_state->crtc); + crtc_state->mode_changed = true; +} + +int imx_hdcp_check_link(struct imx_hdp *hdp) +{ + int ret = 0; + u16 hdcp_port_status; + u8 hdcp_last_error; + + mutex_lock(&hdp->hdcp.mutex); + + if (hdp->hdcp.value == DRM_MODE_CONTENT_PROTECTION_UNDESIRED) + goto out; + + /* get port status */ + hdcp_port_status = imx_hdcp_get_status(hdp); + hdcp_last_error = GET_HDCP_PORT_STS_LAST_ERR(hdcp_port_status); + if (hdcp_last_error) + DRM_ERROR("HDCP error no: %u\n", hdcp_last_error); + + if (hdcp_port_status & HDCP_PORT_STS_AUTH) { + if (hdp->hdcp.value != + DRM_MODE_CONTENT_PROTECTION_UNDESIRED) { + hdp->hdcp.value = + DRM_MODE_CONTENT_PROTECTION_ENABLED; + schedule_work(&hdp->hdcp.prop_work); + } + DRM_DEBUG_KMS("INFO: HDCP authenticated\n"); + /* HDCP is authenticated*/ + goto out; + } + + DRM_DEBUG_KMS("[%s:%d] HDCP link failed, retrying authentication\n", + hdp->connector.name, hdp->connector.base.id); + + ret = _imx_hdcp_disable(hdp); + if (ret) { + DRM_ERROR("Failed to disable hdcp (%d)\n", ret); + hdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_DESIRED; + schedule_work(&hdp->hdcp.prop_work); + goto out; + } + + ret = _imx_hdcp_enable(hdp); + if (ret) { + DRM_ERROR("Failed to enable hdcp (%d)\n", ret); + hdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_DESIRED; + schedule_work(&hdp->hdcp.prop_work); + goto out; + } + +out: + mutex_unlock(&hdp->hdcp.mutex); + return ret; +} + diff --git a/drivers/gpu/drm/imx/hdp/imx-hdcp.h b/drivers/gpu/drm/imx/hdp/imx-hdcp.h new file mode 100644 index 000000000000..4404a4446386 --- /dev/null +++ b/drivers/gpu/drm/imx/hdp/imx-hdcp.h @@ -0,0 +1,26 @@ +/* + * Copyright 2017-2019 NXP + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _IMX_HDCP_H_ +#define _IMX_HDCP_H_ + +bool is_hdcp_supported(struct imx_hdp *hdp); +int imx_hdcp_init(struct imx_hdp *hdp, struct device_node *of_node); +int imx_hdcp_enable(struct imx_hdp *hdp); +int imx_hdcp_disable(struct imx_hdp *hdp); +void imx_hdcp_atomic_check(struct drm_connector *connector, + struct drm_connector_state *old_state, + struct drm_connector_state *new_state); +int imx_hdcp_check_link(struct imx_hdp *hdp); + +#endif diff --git a/drivers/gpu/drm/imx/hdp/imx-hdp.c b/drivers/gpu/drm/imx/hdp/imx-hdp.c index 306023afa8c0..72a042d8c62a 100644 --- a/drivers/gpu/drm/imx/hdp/imx-hdp.c +++ b/drivers/gpu/drm/imx/hdp/imx-hdp.c @@ -24,6 +24,8 @@ #include "imx-hdp.h" #include "imx-hdmi.h" +#include "imx-hdcp.h" +#include "imx-hdcp-private.h" #include "imx-dp.h" #include "../imx-drm.h" @@ -953,7 +955,6 @@ static void imx_hdp_connector_force(struct drm_connector *connector) { struct imx_hdp *hdp = container_of(connector, struct imx_hdp, connector); - mutex_lock(&hdp->mutex); hdp->force = connector->force; mutex_unlock(&hdp->mutex); @@ -989,6 +990,34 @@ static int imx_hdp_set_property(struct drm_connector *connector, return 0; } +static int imx_hdp_connector_atomic_check(struct drm_connector *conn, + struct drm_connector_state *new_state) +{ + struct drm_connector_state *old_state = + drm_atomic_get_old_connector_state(new_state->state, conn); + struct drm_crtc_state *crtc_state; + + imx_hdcp_atomic_check(conn, old_state, new_state); + + if (!new_state->crtc) + return 0; + + crtc_state = drm_atomic_get_new_crtc_state(new_state->state, + new_state->crtc); + + /* + * These properties are handled by fastset, and might not end up in a + * modeset. + */ + if (new_state->picture_aspect_ratio != + old_state->picture_aspect_ratio || + new_state->content_type != old_state->content_type || + new_state->scaling_mode != old_state->scaling_mode) + crtc_state->mode_changed = true; + + return 0; +} + static const struct drm_connector_funcs imx_hdp_connector_funcs = { .fill_modes = drm_helper_probe_single_connector_modes, .detect = imx_hdp_connector_detect, @@ -1004,6 +1033,8 @@ static const struct drm_connector_helper_funcs imx_hdp_connector_helper_funcs = { .get_modes = imx_hdp_connector_get_modes, .mode_valid = imx_hdp_connector_mode_valid, + .atomic_check = imx_hdp_connector_atomic_check, + }; static const struct drm_bridge_funcs imx_hdp_bridge_funcs = { @@ -1017,6 +1048,8 @@ static void imx_hdp_imx_encoder_disable(struct drm_encoder *encoder) { struct imx_hdp *hdp = container_of(encoder, struct imx_hdp, encoder); + imx_hdcp_disable(hdp); + imx_hdp_call(hdp, pixel_link_sync_ctrl_disable, &hdp->state); imx_hdp_call(hdp, pixel_link_invalidate, &hdp->state); } @@ -1057,6 +1090,10 @@ static void imx_hdp_imx_encoder_enable(struct drm_encoder *encoder) hdp->ops->write_hdr_metadata(&hdp->state, &frame); + if (conn_state->content_protection == + DRM_MODE_CONTENT_PROTECTION_DESIRED) + imx_hdcp_enable(hdp); + out: imx_hdp_call(hdp, pixel_link_validate, &hdp->state); imx_hdp_call(hdp, pixel_link_sync_ctrl_enable, &hdp->state); @@ -1405,6 +1442,7 @@ static int imx_hdp_imx_bind(struct device *dev, struct device *master, return -ENOMEM; hdp->dev = &pdev->dev; + hdp->drm_dev = drm; encoder = &hdp->encoder; bridge = &hdp->bridge; connector = &hdp->connector; @@ -1498,7 +1536,6 @@ static int imx_hdp_imx_bind(struct device *dev, struct device *master, imx_hdp_state_init(hdp); hdp->dual_mode = false; - ret = imx_hdp_call(hdp, clock_init, &hdp->clks); if (ret < 0) { DRM_ERROR("Failed to initialize clock\n"); @@ -1587,6 +1624,10 @@ static int imx_hdp_imx_bind(struct device *dev, struct device *master, dp_aux_init(&hdp->state, dev); } + ret = imx_hdcp_init(hdp, pdev->dev.of_node); + if (ret < 0) + DRM_WARN("Failed to initialize HDCP\n"); + INIT_DELAYED_WORK(&hdp->hotplug_work, hotplug_work_func); /* Check cable states before enable irq */ diff --git a/drivers/gpu/drm/imx/hdp/imx-hdp.h b/drivers/gpu/drm/imx/hdp/imx-hdp.h index 7234aeca3afb..e2e8ae68fea4 100644 --- a/drivers/gpu/drm/imx/hdp/imx-hdp.h +++ b/drivers/gpu/drm/imx/hdp/imx-hdp.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 NXP + * Copyright 2017-2019 NXP * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -28,6 +28,7 @@ #include #include "../../../../mxc/hdp/all.h" #include "../../../../mxc/hdp-cec/imx-hdp-cec.h" +#include "imx-hdcp-private.h" #define HDP_DUAL_MODE_MIN_PCLK_RATE 300000 /* KHz */ #define HDP_SINGLE_MODE_MAX_WIDTH 1920 @@ -209,11 +210,13 @@ struct imx_hdp { struct drm_connector connector; struct drm_encoder encoder; struct drm_bridge bridge; + struct drm_device *drm_dev; struct edid *edid; char cable_state; struct hdp_mem mem; + struct imx_hdcp hdcp; u8 is_cec; u8 is_edp; @@ -221,6 +224,7 @@ struct imx_hdp { u8 is_digpll_dp_pclock; u8 no_edid; u8 audio_type; + u8 is_hdcp; u32 dp_lane_mapping; u32 dp_link_rate; u32 dp_num_lanes; @@ -263,5 +267,6 @@ void imx_hdp_register_audio_driver(struct device *dev); void imx_arc_power_up(state_struct *state); void imx_arc_calibrate(state_struct *state); void imx_arc_config(state_struct *state); +int imx_hdcp_check_link(struct imx_hdp *hdp); #endif -- 2.17.1