1001 lines
22 KiB
C
1001 lines
22 KiB
C
/*
|
|
* hdmivrr.c: hdmi vrr interface.
|
|
*
|
|
* Copyright (c) 2015-2018, NVIDIA CORPORATION, All rights reserved.
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/module.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/atomic.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/random.h>
|
|
#if defined(CONFIG_TRUSTED_LITTLE_KERNEL) || defined(CONFIG_TRUSTY)
|
|
#include <linux/ote_protocol.h>
|
|
#endif
|
|
#if defined(CONFIG_TRUSTY)
|
|
#include <linux/trusty/trusty.h>
|
|
#include <linux/trusty/trusty_ipc.h>
|
|
#endif
|
|
#include "dc.h"
|
|
#include "dc_priv.h"
|
|
#include "edid.h"
|
|
#include "hdmi2.0.h"
|
|
#include "sor.h"
|
|
#include "sor_regs.h"
|
|
#include "hdmi_reg.h"
|
|
#include "hdmivrr.h"
|
|
|
|
#define VRR_AUTH_NAME "com.nvidia.tos.0179ed96-1adb-45a8-8dc69d08790252bb"
|
|
#define VRR_AUTH_UUID {0x0179ED96, 0x45A81ADB, 0x089DC68D, 0xBB520279}
|
|
#define HDMIVRR_POLL_MS 50
|
|
#define HDMIVRR_POLL_TIMEOUT_MS 1000
|
|
|
|
#define HDMIVRR_CHLNG_SRC_MON 0
|
|
#define HDMIVRR_CHLNG_SRC_DRV 1
|
|
|
|
#define NS_IN_MS (1000 * 1000)
|
|
|
|
static struct tegra_vrr *vrr_buf;
|
|
|
|
static int hdmivrr_i2c_read(struct tegra_hdmi *hdmi, size_t len, void *data)
|
|
{
|
|
int status;
|
|
struct tegra_dc *dc = hdmi->dc;
|
|
|
|
struct i2c_msg msg[] = {
|
|
{
|
|
.addr = 0x37,
|
|
.flags = I2C_M_RD,
|
|
.len = len,
|
|
.buf = data,
|
|
},
|
|
};
|
|
|
|
if (dc->vedid)
|
|
goto skip_hdmivrr_i2c;
|
|
|
|
tegra_dc_ddc_enable(dc, true);
|
|
|
|
status = i2c_transfer(hdmi->ddcci_i2c_client->adapter,
|
|
msg, ARRAY_SIZE(msg));
|
|
if (status != 1)
|
|
status = -EIO;
|
|
else
|
|
status = 0;
|
|
|
|
tegra_dc_ddc_enable(dc, false);
|
|
|
|
return status;
|
|
|
|
skip_hdmivrr_i2c:
|
|
return -EIO;
|
|
}
|
|
|
|
static int hdmivrr_i2c_write(struct tegra_hdmi *hdmi,
|
|
size_t len, const void *data)
|
|
{
|
|
int status;
|
|
u8 buf[len];
|
|
struct tegra_dc *dc = hdmi->dc;
|
|
|
|
struct i2c_msg msg[] = {
|
|
{
|
|
.addr = 0x37,
|
|
.flags = 0,
|
|
.len = len,
|
|
.buf = buf,
|
|
},
|
|
};
|
|
|
|
if (dc->vedid)
|
|
goto skip_hdmivrr_i2c;
|
|
|
|
memcpy(buf, data, len);
|
|
|
|
tegra_dc_ddc_enable(dc, true);
|
|
|
|
status = i2c_transfer(hdmi->ddcci_i2c_client->adapter,
|
|
msg, ARRAY_SIZE(msg));
|
|
|
|
if (status != 1)
|
|
status = -EIO;
|
|
else
|
|
status = 0;
|
|
|
|
tegra_dc_ddc_enable(dc, false);
|
|
return status;
|
|
|
|
skip_hdmivrr_i2c:
|
|
return -EIO;
|
|
}
|
|
|
|
static void hdmivrr_calc_checksum(char *buf, int len)
|
|
{
|
|
int i;
|
|
char cksum = 0x6e;
|
|
|
|
for (i = 0; i < len-1; i++)
|
|
cksum ^= buf[i];
|
|
buf[len-1] = cksum;
|
|
}
|
|
|
|
static bool hdmivrr_verify_checksum(char *buf, int len)
|
|
{
|
|
int i;
|
|
char cksum = 0x50;
|
|
|
|
for (i = 0; i < len-1; i++)
|
|
cksum ^= buf[i];
|
|
|
|
return buf[len-1] == cksum;
|
|
}
|
|
|
|
static bool hdmivrr_is_module_id_r2(u16 display_controller_id)
|
|
{
|
|
return (NV_MODULE_ID(display_controller_id) == NV_MODULE_ID_R2);
|
|
}
|
|
|
|
static int hdmivrr_set_vcp(struct tegra_hdmi *hdmi, u8 reg, u16 val)
|
|
{
|
|
int status;
|
|
char buf[SET_VCP_LEN] = {0x51, 0x84, 0x3, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
buf[SET_VCP_VCP_OFF] = reg;
|
|
buf[SET_VCP_SH_OFF] = (val >> 8) & 0xff;
|
|
buf[SET_VCP_SL_OFF] = val & 0xff;
|
|
hdmivrr_calc_checksum(buf, SET_VCP_LEN);
|
|
|
|
status = hdmivrr_i2c_write(hdmi, SET_VCP_LEN, buf);
|
|
|
|
return status;
|
|
}
|
|
|
|
static int hdmivrr_get_vcp(struct tegra_hdmi *hdmi,
|
|
u8 reg, u16 *val)
|
|
{
|
|
int status;
|
|
char wr_buf[GET_VCP_WR_LEN] = {0x51, 0x82, 0x01, 0x00, 0x00};
|
|
char rd_buf[GET_VCP_RD_LEN];
|
|
char ret_code;
|
|
|
|
wr_buf[GET_VCP_WR_VCP_OFF] = reg;
|
|
hdmivrr_calc_checksum(wr_buf, GET_VCP_WR_LEN);
|
|
|
|
status = hdmivrr_i2c_write(hdmi, GET_VCP_WR_LEN, wr_buf);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_i2c_read(hdmi, GET_VCP_RD_LEN, rd_buf);
|
|
ret_code = rd_buf[GET_VCP_RES_CODE_OFF];
|
|
if (ret_code)
|
|
return ret_code;
|
|
|
|
if (!hdmivrr_verify_checksum(rd_buf, GET_VCP_RD_LEN))
|
|
return -EINVAL;
|
|
*val = rd_buf[GET_VCP_SH_OFF] << 8 | rd_buf[GET_VCP_SL_OFF];
|
|
|
|
return status;
|
|
}
|
|
|
|
static int hdmivrr_table_write(struct tegra_hdmi *hdmi,
|
|
u8 reg, u8 len, u8 *val)
|
|
{
|
|
int status;
|
|
char buf[TABLE_WRITE_LEN] = {0x51, 0x00, 0xe7, 0x00, 0x00, 0x00};
|
|
u8 tlen;
|
|
|
|
buf[TABLE_WRITE_VCP_OFF] = reg;
|
|
buf[TABLE_WRITE_LEN_OFF] = 0x80 | (len + 4);
|
|
tlen = len + TABLE_WRITE_HEADER_LEN + 1;
|
|
memcpy(buf + TABLE_WRITE_DATA_OFF, val, len);
|
|
hdmivrr_calc_checksum(buf, tlen);
|
|
|
|
status = hdmivrr_i2c_write(hdmi, tlen, buf);
|
|
|
|
return status;
|
|
}
|
|
|
|
static int hdmivrr_table_read(struct tegra_hdmi *hdmi,
|
|
u8 reg, u8 len, u8 *val)
|
|
{
|
|
int status;
|
|
int tlen;
|
|
char wr_buf[TABLE_READ_WR_LEN] = {0x51, 0x84, 0xe2,
|
|
0x00, 0x00, 0x00, 0x00};
|
|
char rd_buf[TABLE_READ_RD_LEN];
|
|
|
|
wr_buf[TABLE_READ_WR_VCP_OFF] = reg;
|
|
hdmivrr_calc_checksum(wr_buf, TABLE_READ_WR_LEN);
|
|
status = hdmivrr_i2c_write(hdmi, TABLE_READ_WR_LEN, wr_buf);
|
|
if (status)
|
|
return status;
|
|
|
|
tlen = len + TABLE_READ_RD_HEADER_LEN + 1;
|
|
status = hdmivrr_i2c_read(hdmi, tlen, rd_buf);
|
|
|
|
if (!hdmivrr_verify_checksum(rd_buf, tlen))
|
|
return -EINVAL;
|
|
|
|
memcpy(val, rd_buf + TABLE_READ_RD_DATA_OFF, len);
|
|
|
|
return status;
|
|
}
|
|
|
|
static int hdmivrr_poll_vcp(struct tegra_hdmi *hdmi, u8 reg,
|
|
u16 exp_val, u32 poll_interval_ms, u32 timeout_ms)
|
|
{
|
|
unsigned long timeout_jf = jiffies + msecs_to_jiffies(timeout_ms);
|
|
u16 reg_val = 0;
|
|
int status;
|
|
|
|
do {
|
|
status = hdmivrr_get_vcp(hdmi, reg, ®_val);
|
|
if (status)
|
|
return status;
|
|
|
|
if (reg_val != exp_val)
|
|
msleep(poll_interval_ms);
|
|
else
|
|
return 0;
|
|
} while (time_after(timeout_jf, jiffies));
|
|
|
|
dev_err(&hdmi->dc->ndev->dev, "VRR VCP poll timeout\n");
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static int hdmivrr_wait_for_vcp_ready(struct tegra_hdmi *hdmi)
|
|
{
|
|
return hdmivrr_poll_vcp(hdmi, VCP_AUX_STAT, 0,
|
|
HDMIVRR_POLL_MS, HDMIVRR_POLL_TIMEOUT_MS);
|
|
}
|
|
|
|
static int hdmivrr_dpcd_read(struct tegra_hdmi *hdmi,
|
|
u32 reg, u8 len, u8 *val)
|
|
{
|
|
int status;
|
|
|
|
status = hdmivrr_wait_for_vcp_ready(hdmi);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_set_vcp(hdmi, VCP_AUX_ADDR_H, (reg>>16) & 0xffff);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_set_vcp(hdmi, VCP_AUX_ADDR_L, reg & 0xffff);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_set_vcp(hdmi, VCP_AUX_LENGTH, len);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_set_vcp(hdmi, VCP_AUX_STAT, VCP_AUX_STAT_RD);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_wait_for_vcp_ready(hdmi);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_table_read(hdmi, VCP_AUX_BUF, len, val);
|
|
|
|
return status;
|
|
}
|
|
|
|
static int hdmivrr_dpcd_write(struct tegra_hdmi *hdmi,
|
|
u32 reg, u8 len, u8 *val)
|
|
{
|
|
int status;
|
|
|
|
status = hdmivrr_wait_for_vcp_ready(hdmi);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_set_vcp(hdmi, VCP_AUX_ADDR_H, (reg>>16) & 0xffff);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_set_vcp(hdmi, VCP_AUX_ADDR_L, reg & 0xffff);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_set_vcp(hdmi, VCP_AUX_LENGTH, len);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_table_write(hdmi, VCP_AUX_BUF, len, val);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_set_vcp(hdmi, VCP_AUX_STAT, VCP_AUX_STAT_WR);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_wait_for_vcp_ready(hdmi);
|
|
if (status)
|
|
return status;
|
|
|
|
return status;
|
|
}
|
|
|
|
static int hdmivrr_dpcd_read_u8(struct tegra_hdmi *hdmi, u32 reg, u8 *val)
|
|
{
|
|
int status;
|
|
|
|
status = hdmivrr_dpcd_read(hdmi, reg, 1, val);
|
|
|
|
return status;
|
|
}
|
|
|
|
static int hdmivrr_dpcd_write_u8(struct tegra_hdmi *hdmi, u32 reg, u8 val)
|
|
{
|
|
int status;
|
|
|
|
status = hdmivrr_dpcd_write(hdmi, reg, 1, &val);
|
|
|
|
return status;
|
|
}
|
|
|
|
static int hdmivrr_dpcd_read_u32(struct tegra_hdmi *hdmi, u32 reg, u32 *val)
|
|
{
|
|
int status;
|
|
u8 buf[4];
|
|
|
|
status = hdmivrr_dpcd_read(hdmi, reg, 4, buf);
|
|
|
|
*val = buf[0]<<24 | buf[1] << 16 | buf[2] << 8 | buf[3];
|
|
|
|
return status;
|
|
}
|
|
|
|
__maybe_unused
|
|
static int hdmivrr_dpcd_write_u32(struct tegra_hdmi *hdmi, u32 reg, u32 val)
|
|
{
|
|
int status;
|
|
u8 buf[4];
|
|
|
|
buf[0] = (val >> 24) & 0xff;
|
|
buf[1] = (val >> 16) & 0xff;
|
|
buf[2] = (val >> 8) & 0xff;
|
|
buf[3] = (val >> 0) & 0xff;
|
|
status = hdmivrr_dpcd_write(hdmi, reg, 4, buf);
|
|
|
|
return status;
|
|
}
|
|
|
|
#if defined(CONFIG_TRUSTED_LITTLE_KERNEL) || defined(CONFIG_TRUSTY)
|
|
static void hdmivrr_te_service_commands(u32 ta_cmd, struct tegra_vrr *vrr)
|
|
{
|
|
|
|
int status;
|
|
#ifdef CONFIG_TRUSTED_LITTLE_KERNEL
|
|
u32 vrr_auth_uuid[4] = VRR_AUTH_UUID;
|
|
u32 uuid_size = sizeof(vrr_auth_uuid);
|
|
|
|
if (te_is_secos_dev_enabled()) {
|
|
status = te_launch_trusted_oper_tlk((u64 *)vrr_buf, PAGE_SIZE,
|
|
vrr->vrr_session_id,
|
|
vrr_auth_uuid,
|
|
ta_cmd, uuid_size);
|
|
if (status)
|
|
pr_err("VRR: Failed to launch operation, err = %d\n",
|
|
status);
|
|
}
|
|
#endif
|
|
#ifdef CONFIG_TRUSTY
|
|
if (is_trusty_dev_enabled()) {
|
|
status = te_launch_trusted_oper((u64 *)vrr_buf, 1024,
|
|
ta_cmd, vrr->ta_ctx);
|
|
|
|
if (status)
|
|
pr_err("VRR: Failed to launch operation, err = %d\n",
|
|
status);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Open the tz session for vrr service */
|
|
static int hdmivrr_te_init(struct tegra_vrr *vrr)
|
|
{
|
|
int status = -ENODEV;
|
|
|
|
#ifdef CONFIG_TRUSTED_LITTLE_KERNEL
|
|
u32 vrr_auth_uuid[4] = VRR_AUTH_UUID;
|
|
u32 uuid_size = sizeof(vrr_auth_uuid);
|
|
|
|
if (te_is_secos_dev_enabled()) {
|
|
status = te_open_trusted_session_tlk(vrr_auth_uuid, uuid_size,
|
|
&(vrr->vrr_session_id));
|
|
|
|
if (status)
|
|
pr_err("VRR: Failed to open session, err = %d\n",
|
|
status);
|
|
}
|
|
|
|
#endif
|
|
#ifdef CONFIG_TRUSTY
|
|
vrr->ta_ctx = NULL;
|
|
|
|
if (is_trusty_dev_enabled()) {
|
|
status = te_open_trusted_session(VRR_AUTH_NAME, &vrr->ta_ctx);
|
|
|
|
if (status)
|
|
pr_err("VRR: Failed to open session, err = %d\n",
|
|
status);
|
|
}
|
|
#endif
|
|
return status;
|
|
}
|
|
|
|
/* Close the tz session which was created for vrr */
|
|
static void hdmivrr_te_deinit(struct tegra_vrr *vrr)
|
|
{
|
|
#ifdef CONFIG_TRUSTED_LITTLE_KERNEL
|
|
u32 vrr_auth_uuid[4] = VRR_AUTH_UUID;
|
|
u32 uuid_size = sizeof(vrr_auth_uuid);
|
|
|
|
if (te_is_secos_dev_enabled()) {
|
|
te_close_trusted_session_tlk(vrr->vrr_session_id, vrr_auth_uuid,
|
|
uuid_size);
|
|
}
|
|
#endif
|
|
#ifdef CONFIG_TRUSTY
|
|
if (is_trusty_dev_enabled())
|
|
te_close_trusted_session(vrr->ta_ctx);
|
|
#endif
|
|
}
|
|
|
|
void tegra_hdmivrr_te_vrr_auth(struct tegra_vrr *vrr)
|
|
{
|
|
hdmivrr_te_service_commands(CMD_VRR_AUTH, vrr);
|
|
}
|
|
|
|
void tegra_hdmivrr_te_vrr_sec(struct tegra_vrr *vrr)
|
|
{
|
|
hdmivrr_te_service_commands(CMD_VRR_SEC, vrr);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Called from the DC driver to set VRR buffer. This call sets the address
|
|
* for the shared memory buffer between kernel & TZ.vrr->vrr_cmd = CMD_VRR_SEC
|
|
*/
|
|
int tegra_hdmivrr_te_set_buf(void *addr)
|
|
{
|
|
/* This address is mapped in kernel space */
|
|
vrr_buf = addr;
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_hdmivrr_is_vrr_capable(struct tegra_hdmi *hdmi)
|
|
{
|
|
int status;
|
|
u16 fw_ver;
|
|
|
|
status = hdmivrr_get_vcp(hdmi, VCP_NV_FW_VER, &fw_ver);
|
|
if (status)
|
|
return status;
|
|
|
|
if (fw_ver < NV_FW_MIN_VER)
|
|
return -EINVAL;
|
|
|
|
return status;
|
|
}
|
|
|
|
__maybe_unused
|
|
static int tegra_hdmivrr_page_init(struct tegra_hdmi *hdmi)
|
|
{
|
|
int status;
|
|
u16 page;
|
|
|
|
status = hdmivrr_get_vcp(hdmi, VCP_MAGIC1, &page);
|
|
if (status)
|
|
return status;
|
|
|
|
if (page == (('A' << 8) | 'U'))
|
|
return status;
|
|
|
|
status = hdmivrr_set_vcp(hdmi, VCP_MAGIC0, ('N'<<8) | 'V');
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_set_vcp(hdmi, VCP_MAGIC1, ('A'<<8) | 'U');
|
|
|
|
return status;
|
|
}
|
|
|
|
static int tegra_hdmivrr_auth_setup(struct tegra_hdmi *hdmi,
|
|
struct tegra_vrr *vrr)
|
|
{
|
|
int status = 0;
|
|
u8 oui[3] = {0x00, 0x04, 0x4b};
|
|
u8 buf[3];
|
|
u32 val32;
|
|
u8 val8;
|
|
|
|
status = hdmivrr_dpcd_write(hdmi, DPAUX_SOURCE_OUI, 3, oui);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_dpcd_read(hdmi, DPAUX_SOURCE_OUI, 3, buf);
|
|
if (status)
|
|
return status;
|
|
|
|
if (buf[0] != oui[0] || buf[1] != oui[1] || buf[2] != oui[2])
|
|
return -EINVAL;
|
|
|
|
status = hdmivrr_dpcd_read_u32(hdmi, DPAUX_AUTH_MAGIC, &val32);
|
|
if (status)
|
|
return status;
|
|
|
|
if (val32 != AUTH_MAGIC_NUM)
|
|
return -EINVAL;
|
|
|
|
status = hdmivrr_dpcd_read_u8(hdmi, DPAUX_AUTH_PROTOCOL, &val8);
|
|
if (status)
|
|
return status;
|
|
|
|
if (val8 != AUTH_PROTOCOL_VALID)
|
|
return -EINVAL;
|
|
|
|
status = hdmivrr_dpcd_read_u8(hdmi, DPAUX_AUTH_KEYNUM, &vrr->keynum);
|
|
if (status)
|
|
return status;
|
|
|
|
if (vrr->keynum != AUTH_KEYNUM_VALUE)
|
|
return -EINVAL;
|
|
|
|
status = hdmivrr_dpcd_read(hdmi, DPAUX_SERIALNUM, 9, vrr->serial);
|
|
|
|
return status;
|
|
}
|
|
|
|
static int tegra_hdmivrr_auth_sts_ready(struct tegra_hdmi *hdmi)
|
|
{
|
|
unsigned long timeout_jf = jiffies +
|
|
msecs_to_jiffies(HDMIVRR_POLL_TIMEOUT_MS);
|
|
int status = 0;
|
|
u8 auth_stat;
|
|
|
|
do {
|
|
status = hdmivrr_dpcd_read_u8(hdmi,
|
|
DPAUX_AUTH_STATUS, &auth_stat);
|
|
if (status)
|
|
return status;
|
|
|
|
if (auth_stat != AUTH_STATUS_READY)
|
|
msleep(HDMIVRR_POLL_MS);
|
|
else
|
|
return 0;
|
|
} while (time_after(timeout_jf, jiffies));
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static void tegra_hdmivrr_mac(struct tegra_hdmi *hdmi, struct tegra_vrr *vrr)
|
|
{
|
|
#if defined(CONFIG_TRUSTED_LITTLE_KERNEL) || defined(CONFIG_TRUSTY)
|
|
tegra_hdmivrr_te_vrr_auth(vrr);
|
|
#endif
|
|
}
|
|
|
|
static int tegra_hdmivrr_driver_unlock(struct tegra_hdmi *hdmi,
|
|
struct tegra_vrr *vrr)
|
|
{
|
|
int status = 0;
|
|
u8 digest[32];
|
|
|
|
status = tegra_hdmivrr_auth_setup(hdmi, vrr);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_dpcd_write_u8(hdmi, DPAUX_AUTH_CMD, AUTH_CMD_RESET);
|
|
if (status)
|
|
return status;
|
|
|
|
status = tegra_hdmivrr_auth_sts_ready(hdmi);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_dpcd_write_u8(hdmi, DPAUX_AUTH_CMD, AUTH_CMD_MONAUTH);
|
|
if (status)
|
|
return status;
|
|
|
|
status = tegra_hdmivrr_auth_sts_ready(hdmi);
|
|
if (status)
|
|
return status;
|
|
|
|
vrr->challenge_src = HDMIVRR_CHLNG_SRC_DRV;
|
|
tegra_hdmivrr_mac(hdmi, vrr);
|
|
|
|
status = hdmivrr_dpcd_write(hdmi, DPAUX_AUTH_CHALLENGE1, 16,
|
|
vrr->challenge);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_dpcd_write(hdmi, DPAUX_AUTH_CHALLENGE2, 16,
|
|
&vrr->challenge[16]);
|
|
if (status)
|
|
return status;
|
|
|
|
status = tegra_hdmivrr_auth_sts_ready(hdmi);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_dpcd_read(hdmi, DPAUX_AUTH_DIGEST1, 16, digest);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_dpcd_read(hdmi, DPAUX_AUTH_DIGEST2, 16, &digest[16]);
|
|
if (status)
|
|
return status;
|
|
|
|
if (memcmp(vrr->digest, digest, sizeof(digest))) {
|
|
dev_err(&hdmi->dc->ndev->dev, "VRR driver unlock digest mismatch\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_info(&hdmi->dc->ndev->dev, "VRR driver unlock succeeded\n");
|
|
|
|
return status;
|
|
}
|
|
|
|
static int tegra_hdmivrr_monitor_unlock(struct tegra_hdmi *hdmi,
|
|
struct tegra_vrr *vrr)
|
|
{
|
|
int status = 0;
|
|
u8 val8;
|
|
|
|
status = tegra_hdmivrr_auth_setup(hdmi, vrr);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_dpcd_read_u8(hdmi, DPAUX_LOCK_STATUS, &val8);
|
|
if (status)
|
|
return status;
|
|
|
|
if (val8 == LOCK_STATUS_UNLOCKED) {
|
|
dev_info(&hdmi->dc->ndev->dev, "VRR monitor unlocked\n");
|
|
return status;
|
|
}
|
|
|
|
status = hdmivrr_dpcd_write_u8(hdmi, DPAUX_AUTH_CMD, AUTH_CMD_RESET);
|
|
if (status)
|
|
return status;
|
|
|
|
status = tegra_hdmivrr_auth_sts_ready(hdmi);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_dpcd_write_u8(hdmi, DPAUX_AUTH_CMD, AUTH_CMD_DRVAUTH);
|
|
if (status)
|
|
return status;
|
|
|
|
status = tegra_hdmivrr_auth_sts_ready(hdmi);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_dpcd_read(hdmi, DPAUX_AUTH_CHALLENGE1, 16,
|
|
vrr->challenge);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_dpcd_read(hdmi, DPAUX_AUTH_CHALLENGE2, 16,
|
|
&vrr->challenge[16]);
|
|
if (status)
|
|
return status;
|
|
|
|
vrr->challenge_src = HDMIVRR_CHLNG_SRC_MON;
|
|
tegra_hdmivrr_mac(hdmi, vrr);
|
|
|
|
status = tegra_hdmivrr_auth_sts_ready(hdmi);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_dpcd_write(hdmi, DPAUX_AUTH_DIGEST1, 16, vrr->digest);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_dpcd_write(hdmi, DPAUX_AUTH_DIGEST2, 16,
|
|
&vrr->digest[16]);
|
|
if (status)
|
|
return status;
|
|
|
|
status = tegra_hdmivrr_auth_sts_ready(hdmi);
|
|
if (status)
|
|
return status;
|
|
|
|
status = hdmivrr_dpcd_read_u8(hdmi, DPAUX_LOCK_STATUS, &val8);
|
|
if (status)
|
|
return status;
|
|
|
|
if (val8 != LOCK_STATUS_UNLOCKED) {
|
|
dev_err(&hdmi->dc->ndev->dev, "VRR monitor unlock digest refused\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_info(&hdmi->dc->ndev->dev, "VRR monitor unlock succeeded\n");
|
|
|
|
return status;
|
|
}
|
|
|
|
__maybe_unused
|
|
static int tegra_hdmivrr_authentication(struct tegra_hdmi *hdmi,
|
|
struct tegra_vrr *vrr)
|
|
{
|
|
int status = 0;
|
|
|
|
status = tegra_hdmivrr_driver_unlock(hdmi, vrr);
|
|
if (status) {
|
|
dev_err(&hdmi->dc->ndev->dev, "VRR driver unlock failed\n");
|
|
return status;
|
|
}
|
|
|
|
status = tegra_hdmivrr_monitor_unlock(hdmi, vrr);
|
|
if (status) {
|
|
dev_err(&hdmi->dc->ndev->dev, "VRR monitor unlock failed\n");
|
|
return status;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
int tegra_hdmi_vrr_init(struct tegra_hdmi *hdmi)
|
|
{
|
|
struct tegra_dc *dc;
|
|
struct tegra_vrr *vrr;
|
|
struct i2c_adapter *i2c_adap;
|
|
int err = 0;
|
|
struct i2c_board_info i2c_dev_info = {
|
|
.type = "tegra_hdmi_ddcci",
|
|
.addr = 0x37,
|
|
};
|
|
|
|
if (!hdmi || !hdmi->dc || !hdmi->dc->out) {
|
|
err = -ENODEV;
|
|
goto fail;
|
|
}
|
|
|
|
dc = hdmi->dc;
|
|
vrr = dc->out->vrr;
|
|
|
|
i2c_adap = i2c_get_adapter(dc->out->ddc_bus);
|
|
if (!i2c_adap) {
|
|
dev_err(&dc->ndev->dev,
|
|
"hdmi: can't get adpater for ddcci bus %d\n",
|
|
dc->out->ddc_bus);
|
|
err = -EBUSY;
|
|
goto fail;
|
|
}
|
|
|
|
hdmi->ddcci_i2c_client = i2c_new_device(i2c_adap, &i2c_dev_info);
|
|
i2c_put_adapter(i2c_adap);
|
|
if (!hdmi->ddcci_i2c_client) {
|
|
dev_err(&dc->ndev->dev,
|
|
"hdmi: can't create ddcci i2c device\n");
|
|
err = -EBUSY;
|
|
goto fail;
|
|
}
|
|
|
|
fail:
|
|
return err;
|
|
}
|
|
|
|
static bool tegra_hdmivrr_fb_mode_is_compatible(struct tegra_hdmi *hdmi,
|
|
struct fb_videomode *m)
|
|
{
|
|
struct fb_videomode m_tmp;
|
|
|
|
/* Currently HDMI VRR is supported only in 1920x1080p 60Hz mode.
|
|
* 1920x1080p 60Hz CEA mode index is 16. Strip out vmode flags
|
|
* that we don't expect to find in this CEA mode before comparison,
|
|
* to ignore non-standard flags added during hotplug.
|
|
*
|
|
* Note fb_mode_is_equal() doesn't check for pixclock;
|
|
* use fb_mode_is_equal_tolerance() for that purpose */
|
|
|
|
m_tmp = *m;
|
|
m_tmp.vmode &= cea_modes[16].vmode;
|
|
return fb_mode_is_equal(&m_tmp, &cea_modes[16]) &&
|
|
fb_mode_is_equal_tolerance(&m_tmp, &cea_modes[16], 0);
|
|
}
|
|
|
|
/* If the monitor supports VRR, scan for VRR-capable modes.
|
|
* Make a copy of these modes, mark them as VRR-capable.
|
|
* and add them to the available list of modes. The original modes
|
|
* may still be used on systems that are not VRR-compatible.
|
|
*/
|
|
void tegra_hdmivrr_update_monspecs(struct tegra_dc *dc,
|
|
struct list_head *head)
|
|
{
|
|
struct tegra_vrr *vrr;
|
|
struct list_head *pos;
|
|
struct fb_modelist *modelist;
|
|
struct fb_videomode *m;
|
|
struct fb_videomode m_vrr;
|
|
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
|
|
u16 disp_controller_id = 0;
|
|
bool module_id_r2 = false;
|
|
|
|
if (!head)
|
|
return;
|
|
|
|
vrr = dc->out->vrr;
|
|
|
|
if (!vrr)
|
|
return;
|
|
|
|
if (!vrr->capability)
|
|
return;
|
|
|
|
if (!hdmivrr_get_vcp(hdmi, VCP_NV_DISP_CONTROLLER_ID,
|
|
&disp_controller_id))
|
|
module_id_r2 = hdmivrr_is_module_id_r2(disp_controller_id);
|
|
|
|
/* Check whether VRR modes were already added */
|
|
list_for_each(pos, head) {
|
|
modelist = list_entry(pos, struct fb_modelist, list);
|
|
m = &modelist->mode;
|
|
|
|
if (m->vmode & FB_VMODE_VRR)
|
|
return;
|
|
}
|
|
|
|
list_for_each(pos, head) {
|
|
modelist = list_entry(pos, struct fb_modelist, list);
|
|
m = &modelist->mode;
|
|
|
|
/* VRR modes will be added to the end of the list;
|
|
* don't add them twice. */
|
|
if (m->vmode & FB_VMODE_VRR)
|
|
break;
|
|
|
|
if ((m->vmode & FB_VMODE_IS_DETAILED) ||
|
|
!(m->vmode & FB_VMODE_IS_CEA))
|
|
continue;
|
|
|
|
/* Currently HDMI VRR is supported on
|
|
* Rev 2 monitors - in only 1920x1080p 60Hz mode.
|
|
* Rev 3/4 monitors - in all modes.
|
|
* Note: Rev 1 monitors do not have HDMI inputs.
|
|
*/
|
|
if (!module_id_r2 || (module_id_r2 &&
|
|
tegra_hdmivrr_fb_mode_is_compatible(hdmi, m))) {
|
|
m_vrr = *m;
|
|
m_vrr.vmode |= FB_VMODE_VRR;
|
|
fb_add_videomode(&m_vrr, head);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Active or deactivate VRR mode on the monitor side */
|
|
void _tegra_hdmivrr_activate(struct tegra_hdmi *hdmi, bool activate)
|
|
{
|
|
struct tegra_dc *dc = hdmi->dc;
|
|
struct tegra_vrr *vrr = dc->out->vrr;
|
|
struct fb_videomode *fbmode = tegra_fb_get_mode(dc);
|
|
int frametime_ms = (int)div64_s64(dc->frametime_ns, NS_IN_MS);
|
|
|
|
if (!vrr || !fbmode || !(dc->mode.vmode & FB_VMODE_VRR))
|
|
return;
|
|
|
|
if (activate) {
|
|
/*
|
|
Inform VRR monitor to turn on VRR mode by increase
|
|
vertical backporch by 2.
|
|
The monitor needs a few frames of standard timing
|
|
before activating VRR mode.
|
|
*/
|
|
msleep(frametime_ms * 20);
|
|
dc->mode.v_back_porch = fbmode->upper_margin + 2;
|
|
} else
|
|
dc->mode.v_back_porch = fbmode->upper_margin;
|
|
|
|
_tegra_dc_set_mode(dc, &dc->mode);
|
|
dc->mode_dirty = true;
|
|
tegra_dc_update_mode(dc);
|
|
msleep(frametime_ms * 2);
|
|
}
|
|
|
|
int tegra_hdmivrr_setup(struct tegra_hdmi *hdmi)
|
|
{
|
|
int status = 0;
|
|
struct tegra_dc *dc;
|
|
struct tegra_vrr *vrr;
|
|
|
|
if (!hdmi || !hdmi->dc || !hdmi->dc->out || !hdmi->dc->out->vrr)
|
|
return -ENODEV;
|
|
|
|
dc = hdmi->dc;
|
|
vrr = dc->out->vrr;
|
|
|
|
if (!(dc->out->vrr_hotplug_state == TEGRA_HPD_STATE_NORMAL))
|
|
goto exit;
|
|
|
|
status = tegra_hdmivrr_is_vrr_capable(hdmi);
|
|
if (status)
|
|
goto fail;
|
|
|
|
/* Let the session id live through out to save time and
|
|
* kill it when TV is unplugged.
|
|
*/
|
|
#if defined(CONFIG_TRUSTED_LITTLE_KERNEL)
|
|
if (!(vrr->vrr_session_id)) {
|
|
status = hdmivrr_te_init(vrr);
|
|
|
|
if (status)
|
|
goto fail;
|
|
}
|
|
#endif
|
|
#if defined(CONFIG_TRUSTY)
|
|
if ((!vrr->ta_ctx)) {
|
|
status = hdmivrr_te_init(vrr);
|
|
|
|
if (status)
|
|
goto fail;
|
|
}
|
|
#endif
|
|
status = tegra_hdmivrr_page_init(hdmi);
|
|
if (status)
|
|
goto fail;
|
|
|
|
status = tegra_hdmivrr_authentication(hdmi, vrr);
|
|
if (status)
|
|
goto fail;
|
|
|
|
vrr->capability = 1;
|
|
goto exit;
|
|
fail:
|
|
vrr->capability = 0;
|
|
exit:
|
|
return status;
|
|
}
|
|
|
|
int tegra_hdmivrr_disable(struct tegra_hdmi *hdmi)
|
|
{
|
|
struct tegra_dc *dc;
|
|
struct tegra_vrr *vrr;
|
|
|
|
if (!hdmi || !hdmi->dc || !hdmi->dc->out || !hdmi->dc->out->vrr)
|
|
return -EINVAL;
|
|
|
|
dc = hdmi->dc;
|
|
vrr = dc->out->vrr;
|
|
|
|
#if defined(CONFIG_TRUSTED_LITTLE_KERNEL)
|
|
if (vrr->vrr_session_id) {
|
|
hdmivrr_te_deinit(vrr);
|
|
vrr->vrr_session_id = 0;
|
|
}
|
|
#endif
|
|
#if defined(CONFIG_TRUSTY)
|
|
if (vrr->ta_ctx) {
|
|
hdmivrr_te_deinit(vrr);
|
|
vrr->ta_ctx = NULL;
|
|
}
|
|
#endif
|
|
vrr->capability = 0;
|
|
return 0;
|
|
}
|