| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Cadence MHDP8546 DP bridge driver. |
| * |
| * Copyright (C) 2020 Cadence Design Systems, Inc. |
| * |
| */ |
| |
| #include <linux/io.h> |
| #include <linux/iopoll.h> |
| |
| #include <linux/unaligned.h> |
| |
| #include <drm/display/drm_hdcp_helper.h> |
| |
| #include "cdns-mhdp8546-hdcp.h" |
| |
| static int cdns_mhdp_secure_mailbox_read(struct cdns_mhdp_device *mhdp) |
| { |
| int ret, empty; |
| |
| WARN_ON(!mutex_is_locked(&mhdp->mbox_mutex)); |
| |
| ret = readx_poll_timeout(readl, mhdp->sapb_regs + CDNS_MAILBOX_EMPTY, |
| empty, !empty, MAILBOX_RETRY_US, |
| MAILBOX_TIMEOUT_US); |
| if (ret < 0) |
| return ret; |
| |
| return readl(mhdp->sapb_regs + CDNS_MAILBOX_RX_DATA) & 0xff; |
| } |
| |
| static int cdns_mhdp_secure_mailbox_write(struct cdns_mhdp_device *mhdp, |
| u8 val) |
| { |
| int ret, full; |
| |
| WARN_ON(!mutex_is_locked(&mhdp->mbox_mutex)); |
| |
| ret = readx_poll_timeout(readl, mhdp->sapb_regs + CDNS_MAILBOX_FULL, |
| full, !full, MAILBOX_RETRY_US, |
| MAILBOX_TIMEOUT_US); |
| if (ret < 0) |
| return ret; |
| |
| writel(val, mhdp->sapb_regs + CDNS_MAILBOX_TX_DATA); |
| |
| return 0; |
| } |
| |
| static int cdns_mhdp_secure_mailbox_recv_header(struct cdns_mhdp_device *mhdp, |
| u8 module_id, |
| u8 opcode, |
| u16 req_size) |
| { |
| u32 mbox_size, i; |
| u8 header[4]; |
| int ret; |
| |
| /* read the header of the message */ |
| for (i = 0; i < sizeof(header); i++) { |
| ret = cdns_mhdp_secure_mailbox_read(mhdp); |
| if (ret < 0) |
| return ret; |
| |
| header[i] = ret; |
| } |
| |
| mbox_size = get_unaligned_be16(header + 2); |
| |
| if (opcode != header[0] || module_id != header[1] || |
| (opcode != HDCP_TRAN_IS_REC_ID_VALID && req_size != mbox_size)) { |
| for (i = 0; i < mbox_size; i++) |
| if (cdns_mhdp_secure_mailbox_read(mhdp) < 0) |
| break; |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int cdns_mhdp_secure_mailbox_recv_data(struct cdns_mhdp_device *mhdp, |
| u8 *buff, u16 buff_size) |
| { |
| int ret; |
| u32 i; |
| |
| for (i = 0; i < buff_size; i++) { |
| ret = cdns_mhdp_secure_mailbox_read(mhdp); |
| if (ret < 0) |
| return ret; |
| |
| buff[i] = ret; |
| } |
| |
| return 0; |
| } |
| |
| static int cdns_mhdp_secure_mailbox_send(struct cdns_mhdp_device *mhdp, |
| u8 module_id, |
| u8 opcode, |
| u16 size, |
| u8 *message) |
| { |
| u8 header[4]; |
| int ret; |
| u32 i; |
| |
| header[0] = opcode; |
| header[1] = module_id; |
| put_unaligned_be16(size, header + 2); |
| |
| for (i = 0; i < sizeof(header); i++) { |
| ret = cdns_mhdp_secure_mailbox_write(mhdp, header[i]); |
| if (ret) |
| return ret; |
| } |
| |
| for (i = 0; i < size; i++) { |
| ret = cdns_mhdp_secure_mailbox_write(mhdp, message[i]); |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int cdns_mhdp_hdcp_get_status(struct cdns_mhdp_device *mhdp, |
| u16 *hdcp_port_status) |
| { |
| u8 hdcp_status[HDCP_STATUS_SIZE]; |
| int ret; |
| |
| mutex_lock(&mhdp->mbox_mutex); |
| ret = cdns_mhdp_secure_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX, |
| HDCP_TRAN_STATUS_CHANGE, 0, NULL); |
| if (ret) |
| goto err_get_hdcp_status; |
| |
| ret = cdns_mhdp_secure_mailbox_recv_header(mhdp, MB_MODULE_ID_HDCP_TX, |
| HDCP_TRAN_STATUS_CHANGE, |
| sizeof(hdcp_status)); |
| if (ret) |
| goto err_get_hdcp_status; |
| |
| ret = cdns_mhdp_secure_mailbox_recv_data(mhdp, hdcp_status, |
| sizeof(hdcp_status)); |
| if (ret) |
| goto err_get_hdcp_status; |
| |
| *hdcp_port_status = ((u16)(hdcp_status[0] << 8) | hdcp_status[1]); |
| |
| err_get_hdcp_status: |
| mutex_unlock(&mhdp->mbox_mutex); |
| |
| return ret; |
| } |
| |
| static u8 cdns_mhdp_hdcp_handle_status(struct cdns_mhdp_device *mhdp, |
| u16 status) |
| { |
| u8 err = GET_HDCP_PORT_STS_LAST_ERR(status); |
| |
| if (err) |
| dev_dbg(mhdp->dev, "HDCP Error = %d", err); |
| |
| return err; |
| } |
| |
| static int cdns_mhdp_hdcp_rx_id_valid_response(struct cdns_mhdp_device *mhdp, |
| u8 valid) |
| { |
| int ret; |
| |
| mutex_lock(&mhdp->mbox_mutex); |
| ret = cdns_mhdp_secure_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX, |
| HDCP_TRAN_RESPOND_RECEIVER_ID_VALID, |
| 1, &valid); |
| mutex_unlock(&mhdp->mbox_mutex); |
| |
| return ret; |
| } |
| |
| static int cdns_mhdp_hdcp_rx_id_valid(struct cdns_mhdp_device *mhdp, |
| u8 *recv_num, u8 *hdcp_rx_id) |
| { |
| u8 rec_id_hdr[2]; |
| u8 status; |
| int ret; |
| |
| mutex_lock(&mhdp->mbox_mutex); |
| ret = cdns_mhdp_secure_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX, |
| HDCP_TRAN_IS_REC_ID_VALID, 0, NULL); |
| if (ret) |
| goto err_rx_id_valid; |
| |
| ret = cdns_mhdp_secure_mailbox_recv_header(mhdp, MB_MODULE_ID_HDCP_TX, |
| HDCP_TRAN_IS_REC_ID_VALID, |
| sizeof(status)); |
| if (ret) |
| goto err_rx_id_valid; |
| |
| ret = cdns_mhdp_secure_mailbox_recv_data(mhdp, rec_id_hdr, 2); |
| if (ret) |
| goto err_rx_id_valid; |
| |
| *recv_num = rec_id_hdr[0]; |
| |
| ret = cdns_mhdp_secure_mailbox_recv_data(mhdp, hdcp_rx_id, 5 * *recv_num); |
| |
| err_rx_id_valid: |
| mutex_unlock(&mhdp->mbox_mutex); |
| |
| return ret; |
| } |
| |
| static int cdns_mhdp_hdcp_km_stored_resp(struct cdns_mhdp_device *mhdp, |
| u32 size, u8 *km) |
| { |
| int ret; |
| |
| mutex_lock(&mhdp->mbox_mutex); |
| ret = cdns_mhdp_secure_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX, |
| HDCP2X_TX_RESPOND_KM, size, km); |
| mutex_unlock(&mhdp->mbox_mutex); |
| |
| return ret; |
| } |
| |
| static int cdns_mhdp_hdcp_tx_is_km_stored(struct cdns_mhdp_device *mhdp, |
| u8 *resp, u32 size) |
| { |
| int ret; |
| |
| mutex_lock(&mhdp->mbox_mutex); |
| ret = cdns_mhdp_secure_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX, |
| HDCP2X_TX_IS_KM_STORED, 0, NULL); |
| if (ret) |
| goto err_is_km_stored; |
| |
| ret = cdns_mhdp_secure_mailbox_recv_header(mhdp, MB_MODULE_ID_HDCP_TX, |
| HDCP2X_TX_IS_KM_STORED, |
| size); |
| if (ret) |
| goto err_is_km_stored; |
| |
| ret = cdns_mhdp_secure_mailbox_recv_data(mhdp, resp, size); |
| err_is_km_stored: |
| mutex_unlock(&mhdp->mbox_mutex); |
| |
| return ret; |
| } |
| |
| static int cdns_mhdp_hdcp_tx_config(struct cdns_mhdp_device *mhdp, |
| u8 hdcp_cfg) |
| { |
| int ret; |
| |
| mutex_lock(&mhdp->mbox_mutex); |
| ret = cdns_mhdp_secure_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX, |
| HDCP_TRAN_CONFIGURATION, 1, &hdcp_cfg); |
| mutex_unlock(&mhdp->mbox_mutex); |
| |
| return ret; |
| } |
| |
| static int cdns_mhdp_hdcp_set_config(struct cdns_mhdp_device *mhdp, |
| u8 hdcp_config, bool enable) |
| { |
| u16 hdcp_port_status; |
| u32 ret_event; |
| u8 hdcp_cfg; |
| int ret; |
| |
| hdcp_cfg = hdcp_config | (enable ? 0x04 : 0) | |
| (HDCP_CONTENT_TYPE_0 << 3); |
| cdns_mhdp_hdcp_tx_config(mhdp, hdcp_cfg); |
| ret_event = cdns_mhdp_wait_for_sw_event(mhdp, CDNS_HDCP_TX_STATUS); |
| if (!ret_event) |
| return -1; |
| |
| ret = cdns_mhdp_hdcp_get_status(mhdp, &hdcp_port_status); |
| if (ret || cdns_mhdp_hdcp_handle_status(mhdp, hdcp_port_status)) |
| return -1; |
| |
| return 0; |
| } |
| |
| static int cdns_mhdp_hdcp_auth_check(struct cdns_mhdp_device *mhdp) |
| { |
| u16 hdcp_port_status; |
| u32 ret_event; |
| int ret; |
| |
| ret_event = cdns_mhdp_wait_for_sw_event(mhdp, CDNS_HDCP_TX_STATUS); |
| if (!ret_event) |
| return -1; |
| |
| ret = cdns_mhdp_hdcp_get_status(mhdp, &hdcp_port_status); |
| if (ret || cdns_mhdp_hdcp_handle_status(mhdp, hdcp_port_status)) |
| return -1; |
| |
| if (hdcp_port_status & 1) { |
| dev_dbg(mhdp->dev, "Authentication completed successfully!\n"); |
| return 0; |
| } |
| |
| dev_dbg(mhdp->dev, "Authentication failed\n"); |
| |
| return -1; |
| } |
| |
| static int cdns_mhdp_hdcp_check_receviers(struct cdns_mhdp_device *mhdp) |
| { |
| u8 hdcp_rec_id[HDCP_MAX_RECEIVERS][HDCP_RECEIVER_ID_SIZE_BYTES]; |
| u8 hdcp_num_rec; |
| u32 ret_event; |
| |
| ret_event = cdns_mhdp_wait_for_sw_event(mhdp, |
| CDNS_HDCP_TX_IS_RCVR_ID_VALID); |
| if (!ret_event) |
| return -1; |
| |
| hdcp_num_rec = 0; |
| memset(&hdcp_rec_id, 0, sizeof(hdcp_rec_id)); |
| cdns_mhdp_hdcp_rx_id_valid(mhdp, &hdcp_num_rec, (u8 *)hdcp_rec_id); |
| cdns_mhdp_hdcp_rx_id_valid_response(mhdp, 1); |
| |
| return 0; |
| } |
| |
| static int cdns_mhdp_hdcp_auth_22(struct cdns_mhdp_device *mhdp) |
| { |
| u8 resp[HDCP_STATUS_SIZE]; |
| u16 hdcp_port_status; |
| u32 ret_event; |
| int ret; |
| |
| dev_dbg(mhdp->dev, "HDCP: Start 2.2 Authentication\n"); |
| ret_event = cdns_mhdp_wait_for_sw_event(mhdp, |
| CDNS_HDCP2_TX_IS_KM_STORED); |
| if (!ret_event) |
| return -1; |
| |
| if (ret_event & CDNS_HDCP_TX_STATUS) { |
| mhdp->sw_events &= ~CDNS_HDCP_TX_STATUS; |
| ret = cdns_mhdp_hdcp_get_status(mhdp, &hdcp_port_status); |
| if (ret || cdns_mhdp_hdcp_handle_status(mhdp, hdcp_port_status)) |
| return -1; |
| } |
| |
| cdns_mhdp_hdcp_tx_is_km_stored(mhdp, resp, sizeof(resp)); |
| cdns_mhdp_hdcp_km_stored_resp(mhdp, 0, NULL); |
| |
| if (cdns_mhdp_hdcp_check_receviers(mhdp)) |
| return -1; |
| |
| return 0; |
| } |
| |
| static inline int cdns_mhdp_hdcp_auth_14(struct cdns_mhdp_device *mhdp) |
| { |
| dev_dbg(mhdp->dev, "HDCP: Starting 1.4 Authentication\n"); |
| return cdns_mhdp_hdcp_check_receviers(mhdp); |
| } |
| |
| static int cdns_mhdp_hdcp_auth(struct cdns_mhdp_device *mhdp, |
| u8 hdcp_config) |
| { |
| int ret; |
| |
| ret = cdns_mhdp_hdcp_set_config(mhdp, hdcp_config, true); |
| if (ret) |
| goto auth_failed; |
| |
| if (hdcp_config == HDCP_TX_1) |
| ret = cdns_mhdp_hdcp_auth_14(mhdp); |
| else |
| ret = cdns_mhdp_hdcp_auth_22(mhdp); |
| |
| if (ret) |
| goto auth_failed; |
| |
| ret = cdns_mhdp_hdcp_auth_check(mhdp); |
| if (ret) |
| ret = cdns_mhdp_hdcp_auth_check(mhdp); |
| |
| auth_failed: |
| return ret; |
| } |
| |
| static int _cdns_mhdp_hdcp_disable(struct cdns_mhdp_device *mhdp) |
| { |
| int ret; |
| |
| dev_dbg(mhdp->dev, "[%s:%d] HDCP is being disabled...\n", |
| mhdp->connector.name, mhdp->connector.base.id); |
| |
| ret = cdns_mhdp_hdcp_set_config(mhdp, 0, false); |
| |
| return ret; |
| } |
| |
| static int _cdns_mhdp_hdcp_enable(struct cdns_mhdp_device *mhdp, u8 content_type) |
| { |
| int ret = -EINVAL; |
| int tries = 3; |
| u32 i; |
| |
| for (i = 0; i < tries; i++) { |
| if (content_type == DRM_MODE_HDCP_CONTENT_TYPE0 || |
| content_type == DRM_MODE_HDCP_CONTENT_TYPE1) { |
| ret = cdns_mhdp_hdcp_auth(mhdp, HDCP_TX_2); |
| if (!ret) |
| return 0; |
| _cdns_mhdp_hdcp_disable(mhdp); |
| } |
| |
| if (content_type == DRM_MODE_HDCP_CONTENT_TYPE0) { |
| ret = cdns_mhdp_hdcp_auth(mhdp, HDCP_TX_1); |
| if (!ret) |
| return 0; |
| _cdns_mhdp_hdcp_disable(mhdp); |
| } |
| } |
| |
| dev_err(mhdp->dev, "HDCP authentication failed (%d tries/%d)\n", |
| tries, ret); |
| |
| return ret; |
| } |
| |
| static int cdns_mhdp_hdcp_check_link(struct cdns_mhdp_device *mhdp) |
| { |
| u16 hdcp_port_status; |
| int ret = 0; |
| |
| mutex_lock(&mhdp->hdcp.mutex); |
| if (mhdp->hdcp.value == DRM_MODE_CONTENT_PROTECTION_UNDESIRED) |
| goto out; |
| |
| ret = cdns_mhdp_hdcp_get_status(mhdp, &hdcp_port_status); |
| if (!ret && hdcp_port_status & HDCP_PORT_STS_AUTH) |
| goto out; |
| |
| dev_err(mhdp->dev, |
| "[%s:%d] HDCP link failed, retrying authentication\n", |
| mhdp->connector.name, mhdp->connector.base.id); |
| |
| ret = _cdns_mhdp_hdcp_disable(mhdp); |
| if (ret) { |
| mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_DESIRED; |
| schedule_work(&mhdp->hdcp.prop_work); |
| goto out; |
| } |
| |
| ret = _cdns_mhdp_hdcp_enable(mhdp, mhdp->hdcp.hdcp_content_type); |
| if (ret) { |
| mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_DESIRED; |
| schedule_work(&mhdp->hdcp.prop_work); |
| } |
| out: |
| mutex_unlock(&mhdp->hdcp.mutex); |
| return ret; |
| } |
| |
| static void cdns_mhdp_hdcp_check_work(struct work_struct *work) |
| { |
| struct delayed_work *d_work = to_delayed_work(work); |
| struct cdns_mhdp_hdcp *hdcp = container_of(d_work, |
| struct cdns_mhdp_hdcp, |
| check_work); |
| struct cdns_mhdp_device *mhdp = container_of(hdcp, |
| struct cdns_mhdp_device, |
| hdcp); |
| |
| if (!cdns_mhdp_hdcp_check_link(mhdp)) |
| schedule_delayed_work(&hdcp->check_work, |
| DRM_HDCP_CHECK_PERIOD_MS); |
| } |
| |
| static void cdns_mhdp_hdcp_prop_work(struct work_struct *work) |
| { |
| struct cdns_mhdp_hdcp *hdcp = container_of(work, |
| struct cdns_mhdp_hdcp, |
| prop_work); |
| struct cdns_mhdp_device *mhdp = container_of(hdcp, |
| struct cdns_mhdp_device, |
| hdcp); |
| struct drm_device *dev = mhdp->connector.dev; |
| struct drm_connector_state *state; |
| |
| drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); |
| mutex_lock(&mhdp->hdcp.mutex); |
| if (mhdp->hdcp.value != DRM_MODE_CONTENT_PROTECTION_UNDESIRED) { |
| state = mhdp->connector.state; |
| state->content_protection = mhdp->hdcp.value; |
| } |
| mutex_unlock(&mhdp->hdcp.mutex); |
| drm_modeset_unlock(&dev->mode_config.connection_mutex); |
| } |
| |
| int cdns_mhdp_hdcp_set_lc(struct cdns_mhdp_device *mhdp, u8 *val) |
| { |
| int ret; |
| |
| mutex_lock(&mhdp->mbox_mutex); |
| ret = cdns_mhdp_secure_mailbox_send(mhdp, MB_MODULE_ID_HDCP_GENERAL, |
| HDCP_GENERAL_SET_LC_128, |
| 16, val); |
| mutex_unlock(&mhdp->mbox_mutex); |
| |
| return ret; |
| } |
| |
| int |
| cdns_mhdp_hdcp_set_public_key_param(struct cdns_mhdp_device *mhdp, |
| struct cdns_hdcp_tx_public_key_param *val) |
| { |
| int ret; |
| |
| mutex_lock(&mhdp->mbox_mutex); |
| ret = cdns_mhdp_secure_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX, |
| HDCP2X_TX_SET_PUBLIC_KEY_PARAMS, |
| sizeof(*val), (u8 *)val); |
| mutex_unlock(&mhdp->mbox_mutex); |
| |
| return ret; |
| } |
| |
| int cdns_mhdp_hdcp_enable(struct cdns_mhdp_device *mhdp, u8 content_type) |
| { |
| int ret; |
| |
| mutex_lock(&mhdp->hdcp.mutex); |
| ret = _cdns_mhdp_hdcp_enable(mhdp, content_type); |
| if (ret) |
| goto out; |
| |
| mhdp->hdcp.hdcp_content_type = content_type; |
| mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_ENABLED; |
| schedule_work(&mhdp->hdcp.prop_work); |
| schedule_delayed_work(&mhdp->hdcp.check_work, |
| DRM_HDCP_CHECK_PERIOD_MS); |
| out: |
| mutex_unlock(&mhdp->hdcp.mutex); |
| return ret; |
| } |
| |
| int cdns_mhdp_hdcp_disable(struct cdns_mhdp_device *mhdp) |
| { |
| int ret = 0; |
| |
| mutex_lock(&mhdp->hdcp.mutex); |
| if (mhdp->hdcp.value != DRM_MODE_CONTENT_PROTECTION_UNDESIRED) { |
| mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; |
| schedule_work(&mhdp->hdcp.prop_work); |
| ret = _cdns_mhdp_hdcp_disable(mhdp); |
| } |
| mutex_unlock(&mhdp->hdcp.mutex); |
| cancel_delayed_work_sync(&mhdp->hdcp.check_work); |
| |
| return ret; |
| } |
| |
| void cdns_mhdp_hdcp_init(struct cdns_mhdp_device *mhdp) |
| { |
| INIT_DELAYED_WORK(&mhdp->hdcp.check_work, cdns_mhdp_hdcp_check_work); |
| INIT_WORK(&mhdp->hdcp.prop_work, cdns_mhdp_hdcp_prop_work); |
| mutex_init(&mhdp->hdcp.mutex); |
| } |