blob: e9e000e7d76c566be2173643f022bc23aea6170a [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <drm/drm_modeset_helper.h>
#include <drm/drmP.h>
#include <drm/drm_edid.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_connector.h>
#include <drm/drm_hdcp.h>
#include <drm/drm_modeset_lock.h>
#include <linux/component.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/jiffies.h>
#include <linux/kthread.h>
#include <linux/device.h>
#include <linux/workqueue.h>
#include <linux/amlogic/media/vout/hdmitx_common/hdmitx_common.h>
#include <linux/amlogic/media/vout/hdmitx_common/hdmitx_types.h>
#include <linux/amlogic/media/amvecm/amvecm.h>
#include <linux/miscdevice.h>
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
#include <drm/drm_debugfs.h>
#endif
#include <drm/amlogic/meson_connector_dev.h>
#include <vout/vout_serve/vout_func.h>
#include <enhancement/amvecm/amcsc.h>
#include "meson_hdmi.h"
#include "meson_vpu.h"
#include "meson_crtc.h"
#include "../media/vout/hdmitx_common/hdmitx_check_valid.h"
#define HDMITX_ATTR_LEN_MAX 16
#define HDMITX_MAX_BPC 12
struct am_hdmi_tx am_hdmi_info;
bool attr_force_debugfs;
char attr_debugfs[16];
/*for hw limitation, limit to 1080p/720p for recovery ui.*/
static bool hdmitx_set_smaller_pref = true;
/*TODO:will remove later.*/
static struct drm_display_mode dummy_mode = {
.name = "dummy_l",
.type = DRM_MODE_TYPE_USERDEF,
.status = MODE_OK,
.clock = 25000,
.hdisplay = 720,
.hsync_start = 736,
.hsync_end = 798,
.htotal = 858,
.hskew = 0,
.vdisplay = 480,
.vsync_start = 489,
.vsync_end = 495,
.vtotal = 525,
.vscan = 0,
.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
};
struct hdmitx_color_attr dv_color_attr_list[] = {
{HDMI_COLORSPACE_YUV444, 8}, //"444,8bit"
{HDMI_COLORSPACE_RESERVED6, COLORDEPTH_RESERVED}
};
struct hdmitx_color_attr dv_ll_color_attr_list[] = {
{HDMI_COLORSPACE_YUV422, 12}, //"422,12bit"
{HDMI_COLORSPACE_RESERVED6, COLORDEPTH_RESERVED}
};
/* this is prior selected list of
* 4k2k50hz, 4k2k60hz smpte50hz, smpte60hz
*/
struct hdmitx_color_attr color_attr_list[] = {
{HDMI_COLORSPACE_YUV420, 10}, //"420,10bit"
{HDMI_COLORSPACE_YUV422, 12}, //"422,12bit"
{HDMI_COLORSPACE_YUV420, 8}, //"420,8bit"
{HDMI_COLORSPACE_YUV444, 8}, //"444,8bit"
{HDMI_COLORSPACE_RGB, 8}, //"rgb,8bit"
{HDMI_COLORSPACE_RESERVED6, COLORDEPTH_RESERVED}
};
/* this is prior selected list of other display mode */
struct hdmitx_color_attr other_color_attr_list[] = {
{HDMI_COLORSPACE_YUV444, 10}, //"444,10bit"
{HDMI_COLORSPACE_YUV422, 12}, //"422,12bit"
{HDMI_COLORSPACE_RGB, 10}, //"rgb,10bit"
{HDMI_COLORSPACE_YUV444, 8}, //"444,8bit"
{HDMI_COLORSPACE_RGB, 8}, //"rgb,8bit"
{HDMI_COLORSPACE_RESERVED6, COLORDEPTH_RESERVED}
};
#define MODE_4K2K24HZ "2160p24hz"
#define MODE_4K2K25HZ "2160p25hz"
#define MODE_4K2K30HZ "2160p30hz"
#define MODE_4K2K50HZ "2160p50hz"
#define MODE_4K2K60HZ "2160p60hz"
#define MODE_4K2KSMPTE "smpte24hz"
#define MODE_4K2KSMPTE30HZ "smpte30hz"
#define MODE_4K2KSMPTE50HZ "smpte50hz"
#define MODE_4K2KSMPTE60HZ "smpte60hz"
void convert_attrstr(char *attr_str,
struct hdmitx_color_attr *attr_param)
{
attr_param->colorformat = HDMI_COLORSPACE_YUV444;
attr_param->bitdepth = 8;
if (strstr(attr_str, "420"))
attr_param->colorformat = HDMI_COLORSPACE_YUV420;
else if (strstr(attr_str, "422"))
attr_param->colorformat = HDMI_COLORSPACE_YUV422;
else if (strstr(attr_str, "444"))
attr_param->colorformat = HDMI_COLORSPACE_YUV444;
else if (strstr(attr_str, "rgb"))
attr_param->colorformat = HDMI_COLORSPACE_RGB;
/*parse colorspace success*/
if (attr_param->colorformat != HDMI_COLORSPACE_RESERVED6) {
if (strstr(attr_str, "12bit"))
attr_param->bitdepth = 12;
else if (strstr(attr_str, "10bit"))
attr_param->bitdepth = 10;
else if (strstr(attr_str, "8bit"))
attr_param->bitdepth = 8;
}
}
static enum hdmi_color_depth bitdepth_to_colordepth(int bitdepth)
{
enum hdmi_color_depth color_depth;
switch (bitdepth) {
case 8:
color_depth = COLORDEPTH_24B;
break;
case 10:
color_depth = COLORDEPTH_30B;
break;
case 12:
color_depth = COLORDEPTH_36B;
break;
case 16:
color_depth = COLORDEPTH_48B;
break;
default:
color_depth = COLORDEPTH_24B;
break;
}
return color_depth;
}
static int colordepth_to_bitdepth(enum hdmi_color_depth color_depth)
{
int bitdepth;
switch (color_depth) {
case COLORDEPTH_24B:
bitdepth = 8;
break;
case COLORDEPTH_30B:
bitdepth = 10;
break;
case COLORDEPTH_36B:
bitdepth = 12;
break;
case COLORDEPTH_48B:
bitdepth = 16;
break;
default:
bitdepth = 8;
break;
}
return bitdepth;
}
static void build_hdmitx_attr_str(char *attr_str, u32 format, u32 bit_depth)
{
const char *colorspace;
switch (format) {
case HDMI_COLORSPACE_YUV420:
colorspace = "420";
break;
case HDMI_COLORSPACE_YUV422:
colorspace = "422";
break;
case HDMI_COLORSPACE_YUV444:
colorspace = "444";
break;
case HDMI_COLORSPACE_RGB:
colorspace = "rgb";
break;
default:
colorspace = "rgb";
DRM_ERROR("Unknown colospace value %d\n", format);
break;
};
sprintf(attr_str, "%s,%dbit", colorspace, bit_depth);
DRM_DEBUG("%s:%s = %u+%u\n", __func__, attr_str, format, bit_depth);
}
static struct hdmitx_color_attr *meson_hdmitx_get_candidate_attr_list
(struct am_meson_crtc_state *crtc_state)
{
const char *outputmode = crtc_state->base.adjusted_mode.name;
struct hdmitx_color_attr *attr_list = NULL;
enum hdmi_hdr_status hdr_status = SDR;
if (am_hdmi_info.recovery_mode)
hdr_status = am_hdmi_info.hdmitx_dev->get_hdmi_hdr_status();
/* filter some color value options, aimed at some modes. */
if (crtc_state->crtc_eotf_type ==
HDMI_EOTF_MESON_DOLBYVISION ||
hdr_status == dolbyvision_std) {
attr_list = dv_color_attr_list;
} else if (crtc_state->crtc_eotf_type ==
HDMI_EOTF_MESON_DOLBYVISION_LL ||
hdr_status == dolbyvision_lowlatency) {
attr_list = dv_ll_color_attr_list;
} else if (!strcmp(outputmode, MODE_4K2K60HZ) ||
!strcmp(outputmode, MODE_4K2K50HZ) ||
!strcmp(outputmode, MODE_4K2KSMPTE60HZ) ||
!strcmp(outputmode, MODE_4K2KSMPTE50HZ)) {
attr_list = color_attr_list;
} else {
attr_list = other_color_attr_list;
}
return attr_list;
}
static bool meson_hdmitx_test_color_attr(struct hdmitx_common *common,
struct am_meson_crtc_state *crtc_state,
struct hdmitx_color_attr *test_attr, u64 sequence_id)
{
struct hdmitx_common_state comm_state;
char *outputmode = crtc_state->base.adjusted_mode.name;
struct hdmitx_color_attr *attr_list = NULL;
char attr_str[HDMITX_ATTR_LEN_MAX];
if (test_attr->colorformat == HDMI_COLORSPACE_RESERVED6)
return false;
attr_list = meson_hdmitx_get_candidate_attr_list(crtc_state);
do {
if (attr_list->colorformat == HDMI_COLORSPACE_RESERVED6)
break;
if (attr_list->colorformat == test_attr->colorformat &&
attr_list->bitdepth == test_attr->bitdepth) {
memset(&comm_state, 0, sizeof(comm_state));
comm_state.state_sequence_id = sequence_id;
build_hdmitx_attr_str(attr_str,
attr_list->colorformat, attr_list->bitdepth);
if (!hdmitx_common_validate_mode_locked(common, &comm_state, outputmode,
attr_str, false)) {
DRM_INFO("%s success [%d]+[%d]\n", __func__,
attr_list->colorformat,
attr_list->bitdepth);
break;
}
}
} while (attr_list++);
if (attr_list->colorformat == HDMI_COLORSPACE_RESERVED6)
return false;
else
return true;
}
static int meson_hdmitx_decide_color_attr
(struct hdmitx_common *common, struct am_meson_crtc_state *crtc_state,
struct hdmitx_color_attr *attr, u64 sequence_id)
{
struct hdmitx_common_state comm_state;
char *outputmode = crtc_state->base.adjusted_mode.name;
struct hdmitx_color_attr *attr_list = NULL;
char attr_str[HDMITX_ATTR_LEN_MAX];
if (!outputmode) {
DRM_ERROR("%s current mode empty.\n", __func__);
return -EINVAL;
}
attr_list = meson_hdmitx_get_candidate_attr_list(crtc_state);
do {
if (attr_list->colorformat == HDMI_COLORSPACE_RESERVED6)
break;
memset(&comm_state, 0, sizeof(comm_state));
comm_state.state_sequence_id = sequence_id;
build_hdmitx_attr_str(attr_str,
attr_list->colorformat, attr_list->bitdepth);
if (!hdmitx_common_validate_mode_locked(common, &comm_state, outputmode,
attr_str, false)) {
attr->colorformat = attr_list->colorformat;
attr->bitdepth = attr_list->bitdepth;
DRM_DEBUG("%s get fmt attr [%d]+[%d]\n",
__func__,
attr->colorformat,
attr->bitdepth);
break;
}
} while (attr_list++);
if (attr_list->colorformat == HDMI_COLORSPACE_RESERVED6) {
DRM_DEBUG("%s no attr found, reset to 444,8bit.\n", __func__);
attr->colorformat = HDMI_COLORSPACE_RGB;
attr->bitdepth = 8;
}
DRM_DEBUG_KMS("[%s]:[%s,eotf:%d]=>attr[%d,%d]\n", __func__,
outputmode, crtc_state->crtc_eotf_type,
attr->colorformat, attr->bitdepth);
return 0;
}
static int meson_hdmitx_mode_probed_add(int count, int *vics,
struct drm_connector *connector, bool edid_vic)
{
struct drm_display_mode *mode, *pref_mode = NULL;
struct drm_hdmitx_timing_para para;
struct am_hdmi_tx *am_hdmitx = connector_to_am_hdmi(connector);
#if defined(CONFIG_ARCH_MESON_ODROID_COMMON)
struct hdmitx_common *tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
#endif
bool pref_flag;
struct meson_drm *priv;
struct meson_of_conf *conf;
char *strp = NULL;
int i;
int ret;
if (!am_hdmitx) {
DRM_ERROR("am_hdmitx is NULL!\n");
return -EINVAL;
}
priv = am_hdmitx->base.drm_priv;
conf = &priv->of_conf;
for (i = 0; i < count; i++) {
ret = hdmitx_common_get_timing_para(vics[i], &para);
if (ret < 0) {
DRM_ERROR("Get hdmi para by vic [%d] failed.\n", vics[i]);
continue;
}
mode = drm_mode_create(connector->dev);
if (!mode) {
DRM_ERROR("drm mode create failed.\n");
continue;
}
strncpy(mode->name, para.name, DRM_DISPLAY_MODE_LEN);
mode->name[DRM_DISPLAY_MODE_LEN - 1] = '\0';
/* remove _4x3 suffix, in case misunderstand */
strp = strstr(mode->name, "_4x3");
if (strp)
*strp = '\0';
mode->type = DRM_MODE_TYPE_DRIVER;
mode->clock = para.pixel_freq;
mode->hdisplay = para.h_active;
mode->hsync_start = para.h_active + para.h_front;
mode->hsync_end = para.h_active + para.h_front + para.h_sync;
mode->htotal = para.h_total;
/* for 480i/576i, horizontal timing is repeated */
if (para.pix_repeat_factor == 1) {
mode->hdisplay >>= 1;
mode->hsync_start >>= 1;
mode->hsync_end >>= 1;
mode->htotal >>= 1;
mode->clock >>= 1;
}
/*use hskew to distinguish whether it's qms mode or edid mode*/
if (edid_vic)
mode->hskew = 1;
else
mode->hskew = 0;
mode->flags |= para.h_pol ?
DRM_MODE_FLAG_PHSYNC : DRM_MODE_FLAG_NHSYNC;
mode->vdisplay = para.v_active;
mode->vsync_start = para.v_active + para.v_front;
mode->vsync_end = para.v_active + para.v_front + para.v_sync;
mode->vtotal = para.v_total;
mode->vscan = 0;
mode->flags |= para.v_pol ?
DRM_MODE_FLAG_PVSYNC : DRM_MODE_FLAG_NVSYNC;
if (!para.pi_mode)
mode->flags |= DRM_MODE_FLAG_INTERLACE;
/*for recovery ui*/
if (hdmitx_set_smaller_pref) {
/*
* select 1080P mode with hightest refresh rate first,
* if not find then select 720p mode as pref mode
*/
if (conf->pref_mode && (!strcmp(conf->pref_mode, "2160p"))) {
pref_flag = (!(mode->flags & DRM_MODE_FLAG_INTERLACE)) &&
((mode->hdisplay == 3840 && mode->vdisplay == 2160) ||
(mode->hdisplay == 1920 && mode->vdisplay == 1080) ||
(mode->hdisplay == 1280 && mode->vdisplay == 720));
DRM_DEBUG("mode_name is %s, pref_flag = %d\n",
conf->pref_mode, pref_flag);
} else {
pref_flag = (!(mode->flags & DRM_MODE_FLAG_INTERLACE)) &&
((mode->hdisplay == 1920 && mode->vdisplay == 1080) ||
(mode->hdisplay == 1280 && mode->vdisplay == 720));
DRM_DEBUG("mode_name is %s, pref_flag = %d\n",
conf->pref_mode, pref_flag);
}
if (pref_flag) {
if (!pref_mode)
pref_mode = mode;
else if (pref_mode->hdisplay < mode->hdisplay)
pref_mode = mode;
else if (pref_mode->hdisplay == mode->hdisplay &&
(drm_mode_vrefresh(pref_mode) < drm_mode_vrefresh(mode)))
pref_mode = mode;
}
}
#if defined(CONFIG_ARCH_MESON_ODROID_COMMON)
if (count == 1) {
pref_mode = mode;
} else {
if (vics[i] == tx_comm->rxcap.dtd[0].vic)
pref_mode = mode;
}
#endif
drm_mode_probed_add(connector, mode);
DRM_DEBUG("add mode [%s] [%d]\n", mode->name, mode->hskew);
}
if (pref_mode)
pref_mode->type |= DRM_MODE_TYPE_PREFERRED;
return ret;
}
int meson_hdmitx_get_modes(struct drm_connector *connector)
{
u32 vrr_cap = 0;
struct edid *edid;
int *vics;
int count = 0, count_qms = 0, i = 0, j = 0;
struct drm_display_mode *mode;
struct am_hdmi_tx *am_hdmitx = connector_to_am_hdmi(connector);
struct hdmitx_common *tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
struct hdmitx_vrr_mode_group *groups;
struct hdmitx_vrr_mode_group *group;
int *vrr_list;
int *tmp;
int num_group = 0;
if (!am_hdmitx) {
DRM_ERROR("am_hdmitx is NULL!\n");
return count;
}
edid = (struct edid *)hdmitx_get_raw_edid(tx_comm);
am_hdmitx->sequence_id = hdmitx_get_hpd_hw_sequence_id(tx_comm);
drm_connector_update_edid_property(connector, edid);
vrr_list = kcalloc(MAX_VRR_GROUP_VIC_NUM, sizeof(int), GFP_KERNEL);
tmp = kcalloc(MAX_VRR_GROUP_VIC_NUM, sizeof(int), GFP_KERNEL);
groups = kcalloc(MAX_VRR_MODE_GROUP, sizeof(*groups), GFP_KERNEL);
if (!groups || !tmp || !vrr_list) {
DRM_ERROR("%s alloc fail\n", __func__);
goto end;
}
if (am_hdmi_info.hdmitx_dev->get_vrr_mode_group)
num_group = am_hdmi_info.hdmitx_dev->get_vrr_mode_group(groups,
MAX_VRR_MODE_GROUP);
/* get vrr capability */
if (am_hdmitx->hdmitx_dev->get_vrr_cap) {
vrr_cap = am_hdmitx->hdmitx_dev->get_vrr_cap();
drm_connector_set_vrr_capable_property(connector, vrr_cap & QMS_VRR_SUP);
} else {
drm_connector_set_vrr_capable_property(connector, false);
}
/*add modes from hdmitx instead of edid*/
count = hdmitx_common_get_vic_list(&vics);
if (vrr_cap && count) {
int src = 0, dst = 0;
for (i = 0; i < num_group; i++) {
group = &groups[i];
for (j = 0; j < ARRAY_SIZE(group->qms_vic_lists); j++) {
tmp[count_qms] = group->qms_vic_lists[j];
DRM_DEBUG("%s__%d__%d__%zd\n", __func__,
__LINE__, tmp[count_qms],
ARRAY_SIZE(group->qms_vic_lists));
count_qms++;
}
}
/*remove duplicate vics array variables*/
while (src < count_qms) {
bool exist = false;
for (i = 0; i < count; i++) {
if (tmp[src] == vics[i]) {
src++;
exist = true;
break;
}
}
if (!exist)
vrr_list[dst++] = tmp[src++];
}
count_qms = dst;
}
if (count) {
meson_hdmitx_mode_probed_add(count, vics, connector, true);
kfree(vics);
}
if (count_qms)
meson_hdmitx_mode_probed_add(count_qms, vrr_list, connector, false);
/*TODO:add dummy mode temp.*/
if (am_hdmitx->base.drm_priv->dummyl_from_hdmitx) {
mode = drm_mode_duplicate(connector->dev, &dummy_mode);
if (!mode) {
DRM_INFO("[%s:%d]dup dummy mode failed.\n", __func__,
__LINE__);
} else {
drm_mode_probed_add(connector, mode);
count++;
}
}
connector->display_info.monitor_range.max_vfreq = am_hdmi_info.max_vfreq;
connector->display_info.monitor_range.min_vfreq = am_hdmi_info.min_vfreq;
end:
kfree(vrr_list);
kfree(tmp);
kfree(groups);
return count + count_qms;
}
/* drm_display_mode : hdmi_format_para
* hdisp : h_active
* hsync_start(hss) : h_active + h_front
* hsync_end(hse) : h_active + h_front + h_sync
* htotal : h_total
*/
enum drm_mode_status meson_hdmitx_check_mode(struct drm_connector *connector,
struct drm_display_mode *mode)
{
return MODE_OK;
}
static struct drm_encoder *meson_hdmitx_best_encoder
(struct drm_connector *connector)
{
struct am_hdmi_tx *am_hdmi = connector_to_am_hdmi(connector);
return &am_hdmi->encoder;
}
static enum drm_connector_status am_hdmitx_connector_detect
(struct drm_connector *connector, bool force)
{
struct hdmitx_common *tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
int hpdstat = hdmitx_get_hpd_state(tx_comm);
DRM_DEBUG("am_hdmi_connector_detect [%d]\n", hpdstat);
return hpdstat == 1 ?
connector_status_connected : connector_status_disconnected;
}
static int _get_hdr_info(const struct hdr_info *hdr, enum hdmi_info_index hdr_info_index)
{
int hdr_cap_value = 0;
struct hdmitx_common *tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
const struct hdr10_plus_info *hdr10p = &hdr->hdr10plus_info;
unsigned int hdr_priority = tx_comm->hdr_priority;
/* just mask hdr_cap's hdr capability */
if (hdr_priority == 2 && hdr_info_index == hdmi_info_1)
return 0;
/*hdr10plugsupported*/
if (hdr10p->ieeeoui == HDR10_PLUS_IEEE_OUI &&
hdr10p->application_version != 0xFF)
hdr_cap_value |= 1 << 0;
/*
*HDR Static Metadata:
*Supported EOTF:
*/
/*Traditional SDR*/
hdr_cap_value |= (!!(hdr->hdr_support & 0x1)) << 1;
/*Traditional HDR*/
hdr_cap_value |= (!!(hdr->hdr_support & 0x2)) << 2;
/*SMPTE ST 2084*/
hdr_cap_value |= (!!(hdr->hdr_support & 0x4)) << 3;
/*Hybrid Log-Gamma*/
hdr_cap_value |= (!!(hdr->hdr_support & 0x8)) << 4;
/*Supported SMD type1*/
hdr_cap_value |= hdr->static_metadata_type1 << 5;
return hdr_cap_value;
}
static int get_hdr_info(void)
{
enum hdmi_info_index hdr_info_index = hdmi_info_1;
const struct hdr_info *hdr = hdmitx_common_get_hdr_info();
return _get_hdr_info(hdr, hdr_info_index);
}
static int get_hdr_info_rx(void)
{
enum hdmi_info_index hdr_info_index = hdmi_info_2;
const struct hdr_info *hdr = hdmitx_common_get_hdr_info_rx();
return _get_hdr_info(hdr, hdr_info_index);
}
static int __get_dv_info(const struct dv_info *dv, enum hdmi_info_index dv_info_index)
{
struct hdmitx_common *tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
unsigned int hdr_priority = tx_comm->hdr_priority;
int dv_flag = 0;
/* just mask dv_cap's dv capability */
if (hdr_priority && dv_info_index == hdmi_info_1)
return 0;
/*The Rx don't support DolbyVision*/
if (dv->ieeeoui != DV_IEEE_OUI || dv->block_flag != CORRECT)
return 0;
/*DolbyVision RX support list:*/
if (dv->ver == 0) {
/*VSVDB Version: bit0,1*/
dv_flag |= 0 << 0;
/*2160p%shz: 1*/
dv_flag |= (dv->sup_2160p60hz ? 1 : 0) << 2;
/*Support mode:*/
/*DV_RGB_444_8BIT*/
dv_flag |= 1 << 3;
/*DV_YCbCr_422_12BIT*/
dv_flag |= (dv->sup_yuv422_12bit ? 1 : 0) << 4;
}
if (dv->ver == 1) {
/*VSVDB Version: %d-byte*/
dv_flag |= 1 << 0;
if (dv->length == 0xB) {
/*2160p%shz: 1*/
dv_flag |= (dv->sup_2160p60hz ? 1 : 0) << 2;
/*Support mode:*/
/*DV_RGB_444_8BIT*/
dv_flag |= 1 << 3;
/*DV_YCbCr_422_12BIT*/
dv_flag |= (dv->sup_yuv422_12bit ? 1 : 0) << 4;
/*LL_YCbCr_422_12BIT*/
if (dv->low_latency == 0x01)
dv_flag |= 1 << 5;
}
if (dv->length == 0xE) {
/*2160p%shz: 1*/
dv_flag |= (dv->sup_2160p60hz ? 1 : 0) << 2;
/*Support mode:*/
/*DV_RGB_444_8BIT*/
dv_flag |= 1 << 3;
/*DV_YCbCr_422_12BIT*/
dv_flag |= (dv->sup_yuv422_12bit ? 1 : 0) << 4;
}
}
if (dv->ver == 2) {
/*VSVDB Version:*/
dv_flag |= 2 << 0;
/*2160p%shz: 1*/
dv_flag |= (dv->sup_2160p60hz ? 1 : 0) << 2;
/*Support mode:*/
if (dv->Interface != 0x00 && dv->Interface != 0x01) {
/*DV_RGB_444_8BIT*/
dv_flag |= 1 << 3;
/*DV_YCbCr_422_12BIT*/
if (dv->sup_yuv422_12bit)
dv_flag |= (dv->sup_yuv422_12bit ? 1 : 0) << 4;
}
/*LL_YCbCr_422_12BIT*/
dv_flag |= 1 << 5;
if (dv->Interface == 0x01 || dv->Interface == 0x03) {
if (dv->sup_10b_12b_444 == 0x1) {
/*LL_RGB_444_10BITT*/
dv_flag |= 1 << 6;
}
if (dv->sup_10b_12b_444 == 0x2) {
/*LL_RGB_444_12BIT*/
dv_flag |= 1 << 7;
}
}
}
return dv_flag;
}
static int get_dv_info(void)
{
enum hdmi_info_index dv_info_index = hdmi_info_1;
const struct dv_info *dv = hdmitx_common_get_dv_info();
return __get_dv_info(dv, dv_info_index);
}
static int get_dv_info_rx(void)
{
enum hdmi_info_index dv_info_index = hdmi_info_2;
const struct dv_info *dv = hdmitx_common_get_dv_info_rx();
return __get_dv_info(dv, dv_info_index);
}
static int hdcp_rx_ver(void)
{
/* Detect RX support HDCP14
* Here, must assume RX support HDCP14, otherwise affect 1A-03
*/
if (am_hdmi_info.hdcp_rx_type == 0x3)
return 36;
else
return 14;
return 0;
}
/*like hdmitx hdcp_mode node*/
static int get_hdcp_mode(void)
{
int hdcp_mode_flag = 0;
if (am_hdmi_info.hdcp_mode) {
hdcp_mode_flag |= am_hdmi_info.hdcp_mode;
if (am_hdmi_info.hdcp_state == HDCP_STATE_SUCCESS)
hdcp_mode_flag |= 1 << 3;
else if (am_hdmi_info.hdcp_state == HDCP_STATE_FAIL)
hdcp_mode_flag |= 0 << 3;
} else {
return 0;
}
return hdcp_mode_flag;
}
static int get_sink_type(void)
{
int sink_type_flag = 0;
struct hdmitx_common *tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
if (!tx_comm->hpd_state) {
/* none */
sink_type_flag = BIT(0);
return sink_type_flag;
}
if (tx_comm->rxcap.vsdb_phy_addr.b)
/* repeater */
sink_type_flag = BIT(1);
else
/* sink */
sink_type_flag = BIT(2);
return sink_type_flag;
}
void get_metadata(struct meson_hdr_static_metadata *mHdrMetaDataValue)
{
struct hdmitx_common *tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
const struct hdr_info hdrinfo = tx_comm->rxcap.hdr_info;
mHdrMetaDataValue->lumi_max = hdrinfo.lumi_max;
mHdrMetaDataValue->lumi_min = hdrinfo.lumi_min;
mHdrMetaDataValue->lumi_avg = hdrinfo.lumi_avg;
}
static bool get_allm_cap(void)
{
bool allm_cap_flag = 0;
struct hdmitx_common *tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
if (tx_comm->rxcap.allm)
allm_cap_flag = 1;
else
allm_cap_flag = 0;
return allm_cap_flag;
}
static int get_dc_cap(void)
{
struct hdmitx_common *tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
struct rx_cap *prxcap = &tx_comm->rxcap;
const struct dv_info *dv = &prxcap->dv_info;
const struct dv_info *dv2 = &prxcap->dv_info2;
int i, dc_cap_mask = 0;
/* DVI case, only rgb,8bit */
if (prxcap->ieeeoui != HDMI_IEEE_OUI) {
/* rgb,8bit */
dc_cap_mask |= BIT(COLOR_RGB_8BIT);
return dc_cap_mask;
}
if (prxcap->dc_36bit_420)
/* 420,12bit */
dc_cap_mask |= BIT(COLOR_YCBCR420_12BIT);
if (prxcap->dc_30bit_420)
/* 420,10bit */
dc_cap_mask |= BIT(COLOR_YCBCR420_10BIT);
for (i = 0; i < Y420_VIC_MAX_NUM; i++) {
if (prxcap->y420_vic[i]) {
/* 420,8bit */
dc_cap_mask |= BIT(COLOR_YCBCR420_8BIT);
break;
}
}
if (prxcap->native_Mode & (1 << 5)) {
if (prxcap->dc_y444) {
if (prxcap->dc_36bit || dv->sup_10b_12b_444 == 0x2 ||
dv2->sup_10b_12b_444 == 0x2)
/* 444,12bit */
dc_cap_mask |= BIT(COLOR_YCBCR444_12BIT);
if (prxcap->dc_30bit || dv->sup_10b_12b_444 == 0x1 ||
dv2->sup_10b_12b_444 == 0x1) {
/* 444,10bit */
dc_cap_mask |= BIT(COLOR_YCBCR444_10BIT);
}
}
/* 444,8bit */
dc_cap_mask |= BIT(COLOR_YCBCR444_8BIT);
}
/* y422, not check dc */
if (prxcap->native_Mode & (1 << 4))
/* 422,12bit */
dc_cap_mask |= BIT(COLOR_YCBCR422_12BIT);
if (prxcap->dc_36bit || dv->sup_10b_12b_444 == 0x2 ||
dv2->sup_10b_12b_444 == 0x2)
/* rgb,12bit */
dc_cap_mask |= BIT(COLOR_RGB_12BIT);
if (prxcap->dc_30bit || dv->sup_10b_12b_444 == 0x1 ||
dv2->sup_10b_12b_444 == 0x1)
/* rgb,10bit */
dc_cap_mask |= BIT(COLOR_RGB_10BIT);
/* rgb,8bit */
dc_cap_mask |= BIT(COLOR_RGB_8BIT);
return dc_cap_mask;
}
static int am_hdmitx_connector_atomic_set_property
(struct drm_connector *connector,
struct drm_connector_state *state,
struct drm_property *property, uint64_t val)
{
struct am_hdmitx_connector_state *hdmitx_state =
to_am_hdmitx_connector_state(state);
struct am_hdmi_tx *am_hdmi = connector_to_am_hdmi(connector);
struct hdmitx_color_attr *attr = &hdmitx_state->color_attr_para;
struct hdmitx_common *tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
DRM_DEBUG("%s\n", __func__);
if (property == am_hdmi->update_attr_prop) {
hdmitx_state->update = true;
return 0;
} else if (property == am_hdmi->color_space_prop) {
attr->colorformat = val;
return 0;
} else if (property == am_hdmi->color_depth_prop) {
attr->bitdepth = val;
hdmitx_state->color_force = true;
return 0;
} else if (property == am_hdmi->avmute_prop) {
hdmitx_state->avmute = val;
return 0;
} else if (property == am_hdmi->allm_prop) {
hdmitx_state->allm_mode = val;
return 0;
} else if (property == am_hdmi->hdr_priority_prop) {
hdmitx_state->hdr_priority = val;
return 0;
} else if (property == am_hdmi->ready_prop) {
hdmitx_state->ready = val;
return 0;
} else if (property == am_hdmi->frac_rate_policy_prop) {
hdmitx_state->frac_rate_policy = val;
tx_comm->frac_rate_policy = val;
return 0;
}
return -EINVAL;
}
static int am_hdmitx_connector_atomic_get_property
(struct drm_connector *connector,
const struct drm_connector_state *state,
struct drm_property *property, uint64_t *val)
{
struct hdmitx_common *tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
struct am_hdmi_tx *am_hdmi = connector_to_am_hdmi(connector);
struct am_hdmitx_connector_state *hdmitx_state =
to_am_hdmitx_connector_state(state);
struct hdmitx_color_attr *attr = &hdmitx_state->color_attr_para;
struct meson_hdr_static_metadata mHdrMetaDataValue;
struct drm_property_blob *new_metadata, *old_metadata;
bool replaced;
if (property == am_hdmi->update_attr_prop) {
*val = 0;
return 0;
} else if (property == am_hdmi->color_space_prop) {
*val = attr->colorformat;
return 0;
} else if (property == am_hdmi->color_depth_prop) {
*val = attr->bitdepth;
return 0;
} else if (property == am_hdmi->avmute_prop) {
*val = hdmitx_state->avmute;
return 0;
} else if (property == am_hdmi->hdmi_hdr_status_prop) {
*val = am_hdmi_info.hdmitx_dev->get_hdmi_hdr_status();
return 0;
} else if (property == am_hdmi->hdr_cap_property) {
*val = get_hdr_info();
return 0;
} else if (property == am_hdmi->hdr_cap_rx_prop) {
*val = get_hdr_info_rx();
return 0;
} else if (property == am_hdmi->dv_cap_property) {
*val = get_dv_info();
return 0;
} else if (property == am_hdmi->dv_cap_rx_property) {
*val = get_dv_info_rx();
return 0;
} else if (property == am_hdmi->hdcp_ver_prop) {
*val = hdcp_rx_ver();
return 0;
} else if (property == am_hdmi->hdcp_mode_property) {
*val = get_hdcp_mode();
return 0;
} else if (property == am_hdmi->hdcp_topo_property) {
*val = am_hdmi_info.hdmitx_dev->get_dw_hdcp_topo_info();
return 0;
} else if (property == am_hdmi->contenttype_cap_prop) {
*val = hdmitx_common_get_contenttypes();
return 0;
} else if (property == am_hdmi->allm_prop) {
*val = hdmitx_state->allm_mode;
return 0;
} else if (property == am_hdmi->hdr_priority_prop) {
*val = hdmitx_state->hdr_priority;
return 0;
} else if (property == am_hdmi->ready_prop) {
*val = hdmitx_common_get_ready_state(tx_comm);
return 0;
} else if (property == am_hdmi->type_prop) {
*val = am_hdmi->hdmi_type;
return 0;
} else if (property == am_hdmi->edid_valid_prop) {
*val = hdmitx_common_get_edid_valid_state(tx_comm);
return 0;
} else if (property == am_hdmi->hdcp_user_prop) {
*val = hdmitx_common_get_hdcp_user_state(tx_comm);
return 0;
} else if (property == am_hdmi->frac_rate_policy_prop) {
*val = tx_comm->frac_rate_policy;
return 0;
} else if (property == am_hdmi->hdmi_used_prop) {
*val = hdmitx_common_get_hdmi_used_state(tx_comm);
return 0;
} else if (property == am_hdmi->sink_type_prop) {
*val = get_sink_type();
return 0;
} else if (property == am_hdmi->static_meta_prop) {
get_metadata(&mHdrMetaDataValue);
old_metadata = hdmitx_state->metadata;
new_metadata = drm_property_create_blob(connector->dev,
sizeof(mHdrMetaDataValue), &mHdrMetaDataValue);
if (IS_ERR(new_metadata)) {
DRM_ERROR("%s, create metadata blob fail.\n", __func__);
return 0;
}
replaced = drm_property_replace_blob(&hdmitx_state->metadata,
new_metadata);
if (replaced && old_metadata)
drm_property_blob_put(old_metadata);
if (!replaced && new_metadata)
drm_property_blob_put(new_metadata);
*val = (hdmitx_state->metadata) ? hdmitx_state->metadata->base.id : 0;
return 0;
} else if (property == am_hdmi->allm_cap_prop) {
*val = get_allm_cap();
return 0;
} else if (property == am_hdmi->dc_cap_prop) {
*val = get_dc_cap();
return 0;
}
return -EINVAL;
}
#ifdef CONFIG_DEBUG_FS
static ssize_t meson_connector_attr_write(struct file *file, const char __user *ubuf,
size_t len, loff_t *offp)
{
if (len > sizeof(attr_debugfs) - 1)
return -EINVAL;
if (copy_from_user(attr_debugfs, ubuf, len))
return -EFAULT;
if (attr_debugfs[len - 1] == '\n')
attr_debugfs[len - 1] = '\0';
attr_debugfs[len] = '\0';
attr_force_debugfs = true;
return len;
}
static int meson_connector_attr_show(struct seq_file *sf, void *data)
{
struct hdmitx_common *tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
hdmitx_get_attr(tx_comm, attr_debugfs);
seq_printf(sf, "hdmitx_attr: %s\n", attr_debugfs);
return 0;
}
static int meson_connector_attr_open(struct inode *inode, struct file *file)
{
struct drm_connector *connector = inode->i_private;
return single_open(file, meson_connector_attr_show, connector);
}
static const struct file_operations meson_connector_attr_fops = {
.owner = THIS_MODULE,
.open = meson_connector_attr_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.write = meson_connector_attr_write,
};
static int meson_connector_debugfs_init(struct drm_connector *connector,
struct dentry *root)
{
struct dentry *entry;
entry = debugfs_create_file("hdmitx_attr", 0644, root,
connector, &meson_connector_attr_fops);
if (!entry) {
DRM_ERROR("create attr debugfs node error\n");
debugfs_remove_recursive(root);
}
return 0;
}
#endif
static int am_hdmitx_connector_late_register(struct drm_connector *connector)
{
#ifdef CONFIG_DEBUG_FS
struct am_hdmi_tx *am_hdmitx = connector_to_am_hdmi(connector);
meson_connector_debugfs_init(&am_hdmitx->base.connector, connector->debugfs_entry);
#endif
return 0;
}
static void am_hdmitx_connector_destroy(struct drm_connector *connector)
{
drm_connector_unregister(connector);
drm_connector_cleanup(connector);
}
int meson_hdmitx_atomic_check(struct drm_connector *connector,
struct drm_atomic_state *state)
{
struct am_hdmitx_connector_state *new_hdmitx_state, *old_hdmitx_state;
struct drm_crtc_state *new_crtc_state = NULL;
unsigned int hdmitx_content_type = hdmitx_common_get_contenttypes();
struct am_meson_crtc_state *meson_crtc_state;
if (!state) {
DRM_ERROR("state is NULL.\n");
return -EINVAL;
}
old_hdmitx_state = to_am_hdmitx_connector_state
(drm_atomic_get_old_connector_state(state, connector));
new_hdmitx_state = to_am_hdmitx_connector_state
(drm_atomic_get_new_connector_state(state, connector));
if (!new_hdmitx_state || !old_hdmitx_state) {
DRM_ERROR("hdmitx_state is NULL.\n");
return -EINVAL;
}
/*check content type.*/
if (((1 << new_hdmitx_state->base.content_type) &
hdmitx_content_type) == 0) {
DRM_ERROR("[%s] check content type[%d-%u] fail\n",
__func__,
new_hdmitx_state->base.content_type,
hdmitx_content_type);
return -EINVAL;
}
if (new_hdmitx_state->base.crtc)
new_crtc_state = drm_atomic_get_new_crtc_state(state,
new_hdmitx_state->base.crtc);
else
return 0;
meson_crtc_state = to_am_meson_crtc_state(new_crtc_state);
if (state->allow_modeset && new_crtc_state) {
if (!am_hdmi_info.hdmitx_on && !am_hdmi_info.android_path) {
new_crtc_state->connectors_changed = true;
DRM_ERROR("hdmitx_on changed, force modeset.\n");
}
/*force set mode.*/
if (new_hdmitx_state->update)
new_crtc_state->connectors_changed = true;
if (new_hdmitx_state->color_force) {
new_crtc_state->mode_changed = true;
if (new_hdmitx_state->color_attr_para.colorformat !=
old_hdmitx_state->color_attr_para.colorformat ||
new_hdmitx_state->color_attr_para.bitdepth !=
old_hdmitx_state->color_attr_para.bitdepth) {
meson_crtc_state->attr_changed = true;
}
}
if (new_hdmitx_state->frac_rate_policy !=
old_hdmitx_state->frac_rate_policy) {
new_crtc_state->mode_changed = true;
}
}
return 0;
}
struct drm_connector_state *meson_hdmitx_atomic_duplicate_state
(struct drm_connector *connector)
{
struct am_hdmitx_connector_state *new_state;
struct am_hdmitx_connector_state *cur_state =
to_am_hdmitx_connector_state(connector->state);
new_state = kzalloc(sizeof(*new_state), GFP_KERNEL);
if (!new_state)
return NULL;
__drm_atomic_helper_connector_duplicate_state(connector,
&new_state->base);
new_state->update = false;
new_state->color_force = false;
new_state->color_attr_para.colorformat = cur_state->color_attr_para.colorformat;
new_state->color_attr_para.bitdepth = cur_state->color_attr_para.bitdepth;
new_state->hdr_priority = cur_state->hdr_priority;
new_state->pref_hdr_policy = cur_state->pref_hdr_policy;
new_state->allm_mode = cur_state->allm_mode;
cur_state->hcs.state_sequence_id = am_hdmi_info.sequence_id;
new_state->frac_rate_policy = cur_state->frac_rate_policy;
new_state->metadata = cur_state->metadata;
memcpy(&new_state->hcs, &cur_state->hcs, sizeof(struct hdmitx_common_state));
return &new_state->base;
}
void meson_hdmitx_atomic_destroy_state(struct drm_connector *connector,
struct drm_connector_state *state)
{
struct am_hdmitx_connector_state *hdmitx_state;
hdmitx_state = to_am_hdmitx_connector_state(state);
__drm_atomic_helper_connector_destroy_state(&hdmitx_state->base);
kfree(hdmitx_state);
}
/*similar to drm_atomic_helper_connector_reset*/
void meson_hdmitx_reset(struct drm_connector *connector)
{
struct am_hdmitx_connector_state *hdmitx_state;
hdmitx_state = kzalloc(sizeof(*hdmitx_state), GFP_KERNEL);
if (!hdmitx_state)
return;
if (connector->state)
__drm_atomic_helper_connector_destroy_state(connector->state);
kfree(connector->state);
__drm_atomic_helper_connector_reset(connector, &hdmitx_state->base);
hdmitx_state->base.hdcp_content_type = am_hdmi_info.hdcp_request_content_type;
hdmitx_state->base.content_protection = am_hdmi_info.hdcp_request_content_protection;
hdmitx_state->pref_hdr_policy = MESON_PREF_DV;
hdmitx_state->color_attr_para.colorformat = HDMI_COLORSPACE_RGB;
hdmitx_state->color_attr_para.bitdepth = 8;
/*drm api need update state, so need delay attach when create state.*/
if (!connector->max_bpc_property)
drm_connector_attach_max_bpc_property
(connector, 8, HDMITX_MAX_BPC);
}
void meson_hdmitx_atomic_print_state(struct drm_printer *p,
const struct drm_connector_state *state)
{
struct am_hdmitx_connector_state *hdmitx_state =
to_am_hdmitx_connector_state(state);
drm_printf(p, "\tdrm hdmitx state:\n");
drm_printf(p, "\t\t android_path:[%d]\n", am_hdmi_info.android_path);
drm_printf(p, "\t\t VRR_CAP:[%u]\n", am_hdmi_info.hdmitx_dev->get_vrr_cap ?
am_hdmi_info.hdmitx_dev->get_vrr_cap() : 0);
drm_printf(p, "\t\t hdr_cap:[%d]\n", get_hdr_info());
drm_printf(p, "\t\t dv_cap:[%d]\n", get_dv_info());
drm_printf(p, "\t\t contenttype_cap:[%d]\n",
hdmitx_common_get_contenttypes());
drm_printf(p, "\t\t avmute:[%d]\n", hdmitx_state->avmute);
drm_printf(p, "\t\t hdmi_hdr_status:[%d]\n",
am_hdmi_info.hdmitx_dev->get_hdmi_hdr_status());
drm_printf(p, "\t\t hdr_policy[%d]\n", hdmitx_state->pref_hdr_policy);
drm_printf(p, "\t\t allm_mode:[%d]\n", hdmitx_state->allm_mode);
drm_printf(p, "\t\t hdcp_ver:[%d]\n", hdcp_rx_ver());
drm_printf(p, "\t\t hdcp_mode:[%d]\n", get_hdcp_mode());
drm_printf(p, "\t\t hdcp_state:[%d]\n", am_hdmi_info.hdcp_state);
drm_printf(p, "\t\t drm raw property:\n");
drm_printf(p, "\t\t\t cs: [%d], cd: [%d], hdr_priority: [%d]\n",
hdmitx_state->color_attr_para.colorformat,
hdmitx_state->color_attr_para.bitdepth,
hdmitx_state->hdr_priority);
drm_printf(p, "\t\t drm to hdmitx timing state:\n");
drm_printf(p, "\t\t\t vic:[%d], cs:[%d], cd:[%d], name:[%s]",
hdmitx_state->hcs.para.vic, hdmitx_state->hcs.para.cs,
hdmitx_state->hcs.para.cd, hdmitx_state->hcs.para.name);
drm_printf(p, "\t\t\t frac_rate_policy:[%d]\n", hdmitx_state->frac_rate_policy);
}
static bool meson_hdmitx_is_hdcp_running(void)
{
if (am_hdmi_info.hdcp_state == HDCP_STATE_DISCONNECT ||
am_hdmi_info.hdcp_state == HDCP_STATE_STOP)
return false;
if (am_hdmi_info.hdcp_mode == HDCP_NULL)
DRM_ERROR("hdcp mode should NOT null for state [%d]\n",
am_hdmi_info.hdcp_state);
return true;
}
static void meson_hdmitx_set_hdcp_result(int result)
{
struct drm_connector *connector = &am_hdmi_info.base.connector;
struct drm_modeset_lock *mode_lock =
&connector->dev->mode_config.connection_mutex;
bool locked_outer = drm_modeset_is_locked(mode_lock);
if (result == HDCP_AUTH_OK) {
am_hdmi_info.hdcp_state = HDCP_STATE_SUCCESS;
if (!locked_outer)
drm_modeset_lock(mode_lock, NULL);
drm_hdcp_update_content_protection(connector, DRM_MODE_CONTENT_PROTECTION_ENABLED);
if (!locked_outer)
drm_modeset_unlock(mode_lock);
DRM_DEBUG("hdcp [%d] set result ok.\n", am_hdmi_info.hdcp_mode);
} else if (result == HDCP_AUTH_FAIL) {
am_hdmi_info.hdcp_state = HDCP_STATE_FAIL;
/*no event needed when fail.*/
DRM_ERROR("hdcp [%d] set result fail.\n", am_hdmi_info.hdcp_mode);
} else if (result == HDCP_AUTH_UNKNOWN) {
/*reset property value to DESIRED.*/
if (connector->state &&
connector->state->content_protection ==
DRM_MODE_CONTENT_PROTECTION_ENABLED) {
if (!locked_outer)
drm_modeset_lock(mode_lock, NULL);
drm_hdcp_update_content_protection(connector,
DRM_MODE_CONTENT_PROTECTION_DESIRED);
if (!locked_outer)
drm_modeset_unlock(mode_lock);
}
}
}
static void meson_hdmitx_start_hdcp(int hdcp_mode)
{
if (hdcp_mode == HDCP_NULL)
return;
am_hdmi_info.hdcp_mode = hdcp_mode;
am_hdmi_info.hdcp_state = HDCP_STATE_START;
am_hdmi_info.hdmitx_dev->hdcp_enable(hdcp_mode);
DRM_DEBUG("start hdcp [%d-%d]...\n",
am_hdmi_info.hdcp_request_content_type, am_hdmi_info.hdcp_mode);
}
static void meson_hdmitx_stop_hdcp(void)
{
if (meson_hdmitx_is_hdcp_running()) {
am_hdmi_info.hdmitx_dev->hdcp_disable();
am_hdmi_info.hdcp_state = HDCP_STATE_STOP;
am_hdmi_info.hdcp_mode = HDCP_NULL;
meson_hdmitx_set_hdcp_result(HDCP_AUTH_UNKNOWN);
}
}
static void meson_hdmitx_disconnect_hdcp(void)
{
if (meson_hdmitx_is_hdcp_running()) {
am_hdmi_info.hdmitx_dev->hdcp_disconnect();
am_hdmi_info.hdcp_state = HDCP_STATE_DISCONNECT;
am_hdmi_info.hdcp_mode = HDCP_NULL;
meson_hdmitx_set_hdcp_result(HDCP_AUTH_UNKNOWN);
}
}
static int meson_hdmitx_get_hdcp_request(struct am_hdmi_tx *tx,
int request_type_mask)
{
struct meson_hdmitx_dev *tx_dev = tx->hdmitx_dev;
int type;
unsigned int hdcp_tx_type = tx_dev->get_tx_hdcp_cap();
unsigned int hdcp_rx_type = am_hdmi_info.hdcp_rx_type;
/*
* for bootup case, some project not have tee notify to load hdcp key,
* need to get rx_cap by hdmitx api.
* for hotplug case, rx_cap is updated in hpd callback.
*/
if (hdcp_rx_type == 0)
hdcp_rx_type = tx_dev->get_rx_hdcp_cap();
DRM_INFO("%s usr_type: %d, hdcp cap: %d,%d\n",
__func__, request_type_mask,
hdcp_tx_type, hdcp_rx_type);
switch (hdcp_tx_type & 0x3) {
case 0x3:
if ((hdcp_rx_type & 0x2) && (request_type_mask & 0x2))
type = HDCP_MODE22;
else if ((hdcp_rx_type & 0x1) && (request_type_mask & 0x1))
type = HDCP_MODE14;
else
type = HDCP_NULL;
break;
case 0x2:
if ((hdcp_rx_type & 0x2) && (request_type_mask & 0x2))
type = HDCP_MODE22;
else
type = HDCP_NULL;
break;
case 0x1:
if ((hdcp_rx_type & 0x1) && (request_type_mask & 0x1))
type = HDCP_MODE14;
else
type = HDCP_NULL;
break;
default:
type = HDCP_NULL;
DRM_INFO("[%s]: TX no hdcp key\n", __func__);
break;
}
return type;
}
void meson_hdmitx_update_hdcp(void)
{
int hdcp_request_mode = HDCP_NULL;
int hdcp_request_mask = HDCP_NULL;
DRM_DEBUG("%s\n", __func__);
/*Undesired, disable hdcp.*/
if (am_hdmi_info.hdcp_request_content_protection == DRM_MODE_CONTENT_PROTECTION_UNDESIRED) {
meson_hdmitx_stop_hdcp();
return;
}
if (am_hdmi_info.hdcp_request_content_type == DRM_MODE_HDCP_CONTENT_TYPE0)
hdcp_request_mask = HDCP_MODE14 | HDCP_MODE22;
else
hdcp_request_mask = HDCP_MODE22;
hdcp_request_mode = meson_hdmitx_get_hdcp_request(&am_hdmi_info, hdcp_request_mask);
/*mode is same, try to re-use last state*/
if (hdcp_request_mode == am_hdmi_info.hdcp_mode) {
switch (am_hdmi_info.hdcp_state) {
case HDCP_STATE_START:
DRM_INFO("waiting hdcp result.\n");
return;
case HDCP_STATE_SUCCESS:
meson_hdmitx_set_hdcp_result(HDCP_AUTH_OK);
return;
case HDCP_STATE_FAIL:
default:
DRM_ERROR("meet stopped hdcp stat\n");
break;
};
}
meson_hdmitx_stop_hdcp();
if (hdcp_request_mode != HDCP_NULL)
meson_hdmitx_start_hdcp(hdcp_request_mode);
else
DRM_ERROR("No valid hdcp mode exit, maybe hdcp havenot init.\n");
}
void meson_hdmitx_update(struct drm_connector_state *new_state,
struct drm_connector_state *old_state)
{
int mute_op = OFF_AVMUTE;
struct hdmitx_common *tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
struct am_hdmitx_connector_state *old_hdmitx_state =
to_am_hdmitx_connector_state(old_state);
struct am_hdmitx_connector_state *new_hdmitx_state =
to_am_hdmitx_connector_state(new_state);
if (new_state->content_type != old_state->content_type)
hdmitx_common_set_contenttype(new_state->content_type);
mute_op = new_hdmitx_state->avmute ? SET_AVMUTE : CLR_AVMUTE;
if (new_hdmitx_state->avmute != old_hdmitx_state->avmute)
hdmitx_common_avmute_locked(tx_comm, mute_op, AVMUTE_PATH_DRM);
if (new_hdmitx_state->allm_mode != old_hdmitx_state->allm_mode)
hdmitx_common_set_allm_mode(tx_comm,
new_hdmitx_state->allm_mode);
if (am_hdmi_info.android_path)
return;
/*Linux only implement*/
if (old_state->hdcp_content_type
!= new_state->hdcp_content_type ||
(old_state->content_protection !=
new_state->content_protection &&
new_state->content_protection !=
DRM_MODE_CONTENT_PROTECTION_ENABLED)) {
/*check hdcp property update*/
am_hdmi_info.hdcp_request_content_type =
new_state->hdcp_content_type;
am_hdmi_info.hdcp_request_content_protection =
new_state->content_protection;
if (new_state->crtc && !drm_atomic_crtc_needs_modeset(new_state->crtc->state))
meson_hdmitx_update_hdcp();
}
}
static void meson_hdmitx_hdcp_notify(void *data, int type, int result)
{
struct drm_connector *connector = &am_hdmi_info.base.connector;
struct hdmitx_common *tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
struct drm_modeset_lock *mode_lock =
&connector->dev->mode_config.connection_mutex;
bool locked_outer = drm_modeset_is_locked(mode_lock);
if (!locked_outer)
drm_modeset_lock(mode_lock, NULL);
if (type == HDCP_KEY_UPDATE && result == HDCP_AUTH_UNKNOWN) {
DRM_INFO("HDCP statue changed, need re-run hdcp\n");
if (hdmitx_get_hpd_state(tx_comm))
am_hdmi_info.hdcp_rx_type = am_hdmi_info.hdmitx_dev->get_rx_hdcp_cap();
if (!am_hdmi_info.hdmitx_on)
goto end;
meson_hdmitx_update_hdcp();
goto end;
}
if (!am_hdmi_info.hdmitx_on)
goto end;
if (type != am_hdmi_info.hdcp_mode) {
DRM_DEBUG("notify type is mismatch[%d]-[%d]\n",
type, am_hdmi_info.hdcp_mode);
goto end;
}
if (result == HDCP_AUTH_OK) {
meson_hdmitx_set_hdcp_result(HDCP_AUTH_OK);
} else if (result == HDCP_AUTH_FAIL) {
if (type == HDCP_MODE14) {
meson_hdmitx_set_hdcp_result(HDCP_AUTH_FAIL);
} else if (type == HDCP_MODE22) {
if (am_hdmi_info.hdcp_request_content_type == DRM_MODE_HDCP_CONTENT_TYPE0) {
DRM_INFO("ContentType0 hdcp 22 -> hdcp14.\n");
meson_hdmitx_stop_hdcp();
meson_hdmitx_start_hdcp(HDCP_MODE14);
} else {
meson_hdmitx_set_hdcp_result(HDCP_AUTH_FAIL);
}
}
} else {
DRM_ERROR("HDCP report unknown result [%d-%d]\n", type, result);
}
end:
if (!locked_outer)
drm_modeset_unlock(mode_lock);
return;
}
static const struct drm_connector_helper_funcs am_hdmi_connector_helper_funcs = {
.get_modes = meson_hdmitx_get_modes,
.mode_valid = meson_hdmitx_check_mode,
.atomic_check = meson_hdmitx_atomic_check,
.best_encoder = meson_hdmitx_best_encoder,
};
static const struct drm_connector_funcs am_hdmi_connector_funcs = {
.detect = am_hdmitx_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.atomic_set_property = am_hdmitx_connector_atomic_set_property,
.atomic_get_property = am_hdmitx_connector_atomic_get_property,
.late_register = am_hdmitx_connector_late_register,
.destroy = am_hdmitx_connector_destroy,
.reset = meson_hdmitx_reset,
.atomic_duplicate_state = meson_hdmitx_atomic_duplicate_state,
.atomic_destroy_state = meson_hdmitx_atomic_destroy_state,
.atomic_print_state = meson_hdmitx_atomic_print_state,
};
static int meson_hdmitx_update_dv_eotf(struct drm_display_mode *mode,
u8 *crtc_eotf_type)
{
const struct dv_info *dvcap = hdmitx_common_get_dv_info();
u8 dv_types = 0;
u8 eotf_type = *crtc_eotf_type;
if (dvcap->ieeeoui != DV_IEEE_OUI || dvcap->block_flag != CORRECT)
return -ENODEV;
DRM_INFO("Mode %s,%d\n", mode->name, mode->flags);
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
return -EINVAL;
if (strstr(mode->name, "2160p60hz") ||
strstr(mode->name, "2160p50hz")) {
if (!dvcap->sup_2160p60hz)
return -ERANGE;
}
/*refer to sink_dv_support()*/
if (dvcap->ver == 2) {
if (dvcap->Interface <= 1)
dv_types = 2; /* LL only */
else
dv_types = 3; /* STD & LL */
} else if (dvcap->low_latency) {
dv_types = 3; /* STD & LL */
} else {
dv_types = 1; /* STD only */
}
/*When pref mode not supported, update to other mode.*/
if (eotf_type == HDMI_EOTF_MESON_DOLBYVISION_LL &&
!(dv_types & 2)) {
eotf_type = HDMI_EOTF_MESON_DOLBYVISION;
}
if (eotf_type == HDMI_EOTF_MESON_DOLBYVISION &&
!(dv_types & 1)) {
eotf_type = HDMI_EOTF_MESON_DOLBYVISION_LL;
}
if (*crtc_eotf_type != eotf_type) {
DRM_INFO("[%s] change eotf [%u]->[%u]\n",
__func__, *crtc_eotf_type, eotf_type);
*crtc_eotf_type = eotf_type;
}
return 0;
}
/*check hdr10&hlg cap*/
static int meson_hdmitx_update_hdr_eotf(struct drm_display_mode *mode,
u8 *crtc_eotf_type)
{
/*refer to sink_hdr_support()*/
const u8 hdr10_bit = BIT(2);
const u8 hlg_bit = BIT(3);
const struct hdr_info *hdrcap = hdmitx_common_get_hdr_info();
/*hdr core can support all the hdr types.*/
if ((hdrcap->hdr_support & hdr10_bit) ||
(hdrcap->hdr_support & hlg_bit) ||
(hdrcap->hdr10plus_info.ieeeoui == HDR10_PLUS_IEEE_OUI &&
hdrcap->hdr10plus_info.application_version == 1)) {
return 0;
}
return -ENODEV;
}
static int meson_hdmitx_decide_eotf_type
(struct am_meson_crtc_state *meson_crtc_state,
struct am_hdmitx_connector_state *hdmitx_state)
{
struct hdmitx_common *tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
u8 crtc_eotf_type = HDMI_EOTF_TRADITIONAL_GAMMA_SDR;
int ret = 0;
struct drm_display_mode *mode = &meson_crtc_state->base.adjusted_mode;
/*If the HDMI cable is not plugin before starting the board,pref_hdr_policy
*may not be available in am_meson_update_output_state() function
*/
hdmitx_state->pref_hdr_policy = tx_comm->hdr_priority;
/* TODO:hdr priority handled by hdmitx, and dont support dynamic set.
* Currently checking is to confirm crtc_eotf_type == dv/dv_ll mode,
* we need special setting for dv.
*/
if (hdmitx_state->pref_hdr_policy == MESON_PREF_DV) {
if (meson_crtc_state->dv_mode)
crtc_eotf_type = HDMI_EOTF_MESON_DOLBYVISION_LL;
else
crtc_eotf_type = HDMI_EOTF_MESON_DOLBYVISION;
} else if (hdmitx_state->pref_hdr_policy == MESON_PREF_HDR) {
crtc_eotf_type = HDMI_EOTF_SMPTE_ST2084;
} else {
crtc_eotf_type = HDMI_EOTF_TRADITIONAL_GAMMA_SDR;
}
DRM_DEBUG_KMS("%s: default eotf [%u]\n", __func__, crtc_eotf_type);
if (crtc_eotf_type == HDMI_EOTF_MESON_DOLBYVISION ||
crtc_eotf_type == HDMI_EOTF_MESON_DOLBYVISION_LL) {
/*check if dv core valid*/
if (meson_crtc_state->crtc_dv_enable)
ret = 0;
else
ret = -ENODEV;
/*check dv cap & mode */
if (ret == 0)
ret = meson_hdmitx_update_dv_eotf(mode,
&crtc_eotf_type);
if (ret < 0) {
crtc_eotf_type = HDMI_EOTF_SMPTE_ST2084;/*try hdr10*/
DRM_DEBUG_KMS("hdmitx dv eotf check fail [%d]\n", ret);
}
DRM_DEBUG_KMS("%s: dv check dv eotf finish => [%u]\n",
__func__, crtc_eotf_type);
}
if (crtc_eotf_type == HDMI_EOTF_SMPTE_ST2084) {
/*check if hdr core valid*/
if (meson_crtc_state->crtc_hdr_enable)
ret = 0;
else
ret = -ENODEV;
if (ret == 0)
ret = meson_hdmitx_update_hdr_eotf(mode,
&crtc_eotf_type);
if (ret < 0) {
crtc_eotf_type = HDMI_EOTF_TRADITIONAL_GAMMA_SDR;
DRM_INFO("hdmitx hdr eotf check fail [%d]\n", ret);
}
DRM_DEBUG_KMS("%s: HDR10 check eotf => [%u]\n",
__func__, crtc_eotf_type);
}
if (!meson_crtc_state->crtc_eotf_by_property_flag)
meson_crtc_state->crtc_eotf_type = crtc_eotf_type;
else
meson_crtc_state->crtc_eotf_type = meson_crtc_state->eotf_type_by_property;
DRM_DEBUG_KMS("%s: [%u->%u]\n", __func__,
hdmitx_state->pref_hdr_policy,
meson_crtc_state->crtc_eotf_type);
return 0;
}
static void meson_hdmitx_cal_brr(struct am_hdmi_tx *hdmitx,
struct am_meson_crtc_state *crtc_state,
struct drm_display_mode *adj_mode)
{
int i, ret, vic, brr = 60;
int num_group;
struct hdmitx_vrr_mode_group *group;
struct drm_hdmitx_timing_para para;
struct hdmitx_vrr_mode_group *groups;
groups = kcalloc(MAX_VRR_MODE_GROUP, sizeof(*groups), GFP_KERNEL);
if (!groups) {
DRM_ERROR("%s alloc fail\n", __func__);
return;
}
num_group = hdmitx->hdmitx_dev->get_vrr_mode_group(groups,
MAX_VRR_MODE_GROUP);
vic = HDMI_0_UNKNOWN;
for (i = 0; i < num_group; i++) {
group = &groups[i];
if (group->width == adj_mode->hdisplay &&
group->height == adj_mode->vdisplay) {
if (group->vrr_max / VRR_DIV >= brr) {
brr = group->vrr_max / VRR_DIV;
vic = group->brr_vic;
am_hdmi_info.min_vfreq = group->vrr_min / VRR_DIV;
am_hdmi_info.max_vfreq = group->vrr_max / VRR_DIV;
}
}
}
ret = hdmitx_common_get_timing_para(vic, &para);
if (ret < 0) {
DRM_ERROR("%s, Get hdmi para by vic [%d] failed.\n",
__func__, vic);
}
if (am_hdmi_info.max_vfreq < drm_mode_vrefresh(adj_mode)) {
memset(crtc_state->brr_mode, 0, DRM_DISPLAY_MODE_LEN);
crtc_state->valid_brr = 0;
} else {
strncpy(crtc_state->brr_mode, para.name,
DRM_DISPLAY_MODE_LEN);
crtc_state->brr_mode[DRM_DISPLAY_MODE_LEN - 1] = '\0';
crtc_state->valid_brr = 1;
}
DRM_INFO("%s, %d, %d, %s, %d\n", __func__, vic, brr, crtc_state->brr_mode,
crtc_state->valid_brr);
crtc_state->brr = brr;
kfree(groups);
}
static int meson_hdmitx_choose_preset_mode(struct am_hdmi_tx *hdmitx,
struct am_meson_crtc *amcrtc,
struct am_meson_crtc_state *meson_crtc_state,
char *modename, enum hdmi_vic vic)
{
enum vmode_e vout_mode;
struct hdmitx_common *common = am_hdmi_info.hdmitx_dev->hdmitx_common;
meson_crtc_state->preset_vmode = VMODE_INVALID;
/* check if hdmi can support this mode. if not, set vmode to dummy*/
vout_mode = vout_func_validate_vmode(amcrtc->vout_index, modename, 0);
DRM_INFO(" %s validate vmode %s, %x\n", __func__, modename, vout_mode);
if (vout_mode != VMODE_HDMI && hdmitx_get_hpd_state(common)) {
DRM_INFO("no matched hdmi mode\n");
return -EINVAL;
} else if (vout_mode == VMODE_DUMMY_ENCL) {
meson_crtc_state->preset_vmode = VMODE_DUMMY_ENCL;
return 0;
}
if (hdmitx_common_check_valid_para_of_vic(common, vic)) {
vout_mode = vout_func_validate_vmode(amcrtc->vout_index, "dummy_l", 0);
meson_crtc_state->preset_vmode = vout_mode;
} else {
meson_crtc_state->preset_vmode = VMODE_HDMI;
}
DRM_INFO("%s update %s expect %x\n", __func__, modename, vout_mode);
return 0;
}
/*Calculate preset_mode and eotf_type before enable crtc&encoder.*/
void meson_hdmitx_encoder_atomic_mode_set(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
enum hdmi_vic vic;
struct am_meson_crtc_state *meson_crtc_state =
to_am_meson_crtc_state(crtc_state);
struct am_hdmitx_connector_state *hdmitx_state =
to_am_hdmitx_connector_state(conn_state);
struct hdmitx_color_attr *attr = &hdmitx_state->color_attr_para;
struct drm_display_mode *adj_mode = &crtc_state->adjusted_mode;
struct am_hdmi_tx *hdmitx = encoder_to_am_hdmi(encoder);
struct am_meson_crtc *amcrtc = to_am_meson_crtc(crtc_state->crtc);
struct hdmitx_common *tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
char *modename = adj_mode->name;
int ret = 0;
DRM_DEBUG("%s[%d]: enter\n", __func__, __LINE__);
if (am_hdmi_info.android_path)
return;
vic = hdmitx_common_parse_vic_in_edid(tx_comm, modename);
if (vic == HDMI_0_UNKNOWN) {
DRM_ERROR("invalid vic for %s\n", modename);
return;
}
if (meson_hdmitx_choose_preset_mode(hdmitx, amcrtc,
meson_crtc_state, modename, hdmitx_state->hcs.para.vic) < 0)
return;
DRM_INFO("%s enter:attr[%d-%d]\n", __func__,
attr->colorformat, attr->bitdepth);
meson_hdmitx_decide_eotf_type(meson_crtc_state, hdmitx_state);
/*check force attr info: from uboot set or debugfs;
*for uboot: it may not support dv, but kernel support dv, the attr
*from uboot is not valid.
*/
if (attr_force_debugfs) {
attr_force_debugfs = false;
convert_attrstr(attr_debugfs, attr);
ret = hdmitx_common_build_format_para(tx_comm,
&hdmitx_state->hcs.para, vic,
tx_comm->frac_rate_policy,
attr->colorformat,
bitdepth_to_colordepth(attr->bitdepth),
HDMI_QUANTIZATION_RANGE_FULL);
if (ret < 0)
DRM_ERROR("format para build fail\n");
DRM_INFO("debugfs attr\n");
}
}
static
int meson_encoder_vrr_change(struct drm_encoder *encoder,
struct drm_atomic_state *state)
{
struct drm_connector *connector;
struct drm_connector_state *conn_state;
struct drm_crtc *crtc;
struct drm_crtc_state *new_state, *old_state;
struct drm_display_mode *new_mode, *old_mode;
struct am_meson_crtc_state *meson_crtc_state, *old_crtc_state;
char *brr_name;
connector = drm_atomic_get_new_connector_for_encoder(state, encoder);
if (!connector)
return 0;
conn_state = drm_atomic_get_new_connector_state(state, connector);
if (!conn_state)
return 0;
crtc = conn_state->crtc;
new_state = drm_atomic_get_new_crtc_state(state, crtc);
old_state = drm_atomic_get_old_crtc_state(state, crtc);
if (!new_state || !old_state) {
DRM_INFO("%s crtc state is NULL!\n", __func__);
return 0;
}
new_mode = &new_state->adjusted_mode;
old_mode = &old_state->adjusted_mode;
meson_crtc_state = to_am_meson_crtc_state(new_state);
old_crtc_state = to_am_meson_crtc_state(old_state);
if (new_mode->hdisplay != old_mode->hdisplay ||
new_mode->vdisplay != old_mode->vdisplay ||
meson_crtc_state->attr_changed ||
meson_crtc_state->brr_update ||
(new_mode->flags & DRM_MODE_FLAG_INTERLACE))
return meson_crtc_state->seamless;
if (new_state->vrr_enabled) {
if (old_state->vrr_enabled) {
/*qms->qms*/
meson_crtc_state->seamless = true;
} else {
/*allm -> qms, new vrr_enable 1,brr_update 0*/
brr_name = meson_crtc_state->brr_mode;
if (!strcmp(old_mode->name, brr_name))
meson_crtc_state->seamless = true;
else
meson_crtc_state->seamless = false;
}
} else {
if (old_state->vrr_enabled) {
/*qms->allm*/
brr_name = old_crtc_state->brr_mode;
if (!strcmp(old_mode->name, brr_name) &&
!strcmp(new_mode->name, brr_name))
meson_crtc_state->seamless = true;
else
meson_crtc_state->seamless = false;
} else {
/*none qms-> none qms*/
meson_crtc_state->seamless = false;
}
}
DRM_DEBUG("[%s], seamless is %d\n", __func__, meson_crtc_state->seamless);
return meson_crtc_state->seamless;
}
void meson_hdmitx_encoder_atomic_enable(struct drm_encoder *encoder,
struct drm_atomic_state *state)
{
struct am_meson_crtc_state *meson_crtc_state =
to_am_meson_crtc_state(encoder->crtc->state);
struct drm_connector_state *conn_state =
drm_atomic_get_new_connector_state(state,
&am_hdmi_info.base.connector);
struct drm_connector_state *old_conn_state =
drm_atomic_get_old_connector_state(state,
&am_hdmi_info.base.connector);
struct am_hdmitx_connector_state *meson_conn_state =
to_am_hdmitx_connector_state(conn_state);
struct am_hdmitx_connector_state *old_meson_conn_state =
to_am_hdmitx_connector_state(old_conn_state);
struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
int dst_vrefresh, mode_vrefresh = drm_mode_vrefresh(mode);
struct am_meson_crtc *amcrtc = to_am_meson_crtc(encoder->crtc);
enum vmode_e vmode = meson_crtc_state->vmode;
struct hdmitx_common *tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
DRM_DEBUG("%s[%d]\n", __func__, __LINE__);
if ((vmode & VMODE_MODE_BIT_MASK) != VMODE_HDMI) {
DRM_INFO("[%s] skip vmode[%d]\n", __func__, vmode);
return;
}
if (meson_crtc_state->seamless) {
dst_vrefresh = meson_crtc_state->base.vrr_enabled ? mode_vrefresh : 0;
DRM_INFO("%s, set frame rate: %d\n", __func__, dst_vrefresh);
am_hdmi_info.hdmitx_dev->set_vframe_rate_hint(dst_vrefresh * 100, NULL);
return;
}
if (meson_crtc_state->uboot_mode_init == 1 &&
meson_conn_state->update != 1)
vmode |= VMODE_INIT_BIT_MASK;
if (!meson_crtc_state->seamless) {
hdmitx_set_hdr_priority(am_hdmi_info.hdmitx_dev->hdmitx_common,
meson_conn_state->hdr_priority);
meson_vout_notify_mode_change(amcrtc->vout_index,
vmode, EVENT_MODE_SET_START);
meson_conn_state->hcs.mode = vmode;
hdmitx_common_do_mode_setting(am_hdmi_info.hdmitx_dev->hdmitx_common,
&meson_conn_state->hcs,
&old_meson_conn_state->hcs);
meson_vout_notify_mode_change(amcrtc->vout_index,
vmode, EVENT_MODE_SET_FINISH);
meson_vout_update_mode_name(amcrtc->vout_index, mode->name, "hdmitx");
}
am_hdmi_info.hdmitx_on = 1;
if (!am_hdmi_info.android_path) {
hdmitx_common_avmute_locked(tx_comm, CLR_AVMUTE, AVMUTE_PATH_DRM);
meson_hdmitx_update_hdcp();
}
if (meson_crtc_state->base.vrr_enabled) {
am_hdmi_info.hdmitx_dev->set_vframe_rate_hint(mode_vrefresh * 100, NULL);
DRM_INFO("%s, vrr set rate hint, %d\n", __func__,
mode_vrefresh * 100);
}
}
void meson_hdmitx_encoder_atomic_disable(struct drm_encoder *encoder,
struct drm_atomic_state *state)
{
struct hdmitx_common *tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
struct hdmitx_hw_common *hw_comm = am_hdmi_info.hdmitx_dev->hw_common;
struct am_meson_crtc_state *meson_crtc_state =
to_am_meson_crtc_state(encoder->crtc->state);
if (meson_crtc_state->seamless)
return;
DRM_INFO("[%s]\n", __func__);
if (am_hdmi_info.android_path ||
meson_crtc_state->uboot_mode_init == 1)
return;
am_hdmi_info.hdmitx_on = 0;
hdmitx_common_avmute_locked(tx_comm, SET_AVMUTE, AVMUTE_PATH_DRM);
msleep(100);
/* there's about 300ms delay after hdmitx encoder disable and
* before hdmitx_module_disable(disable phy), need to disable
* hdmitx phy asap for TV better detection
*/
hdmitx_hw_set_phy(hw_comm, 0);
meson_hdmitx_stop_hdcp();
msleep(100);
}
/*
* The flow matching mode + attr was originally in atomic_mode_set,
* now put this flow into encoder_atomic_check.
*/
static int meson_hdmitx_encoder_autoselect_attr(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
enum hdmi_vic vic;
struct am_meson_crtc_state *meson_crtc_state =
to_am_meson_crtc_state(crtc_state);
struct am_hdmitx_connector_state *hdmitx_state =
to_am_hdmitx_connector_state(conn_state);
struct hdmitx_color_attr *attr = &hdmitx_state->color_attr_para;
struct drm_display_mode *adj_mode = &crtc_state->adjusted_mode;
struct hdmitx_common *tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
bool update_attr = false;
char *modename = adj_mode->name;
int ret = 0;
u64 sequence_id = hdmitx_state->hcs.state_sequence_id;
vic = hdmitx_common_parse_vic_in_edid(tx_comm, modename);
if (vic == HDMI_0_UNKNOWN) {
DRM_ERROR("invalid vic for %s\n", modename);
return -EINVAL;
}
meson_hdmitx_decide_eotf_type(meson_crtc_state, hdmitx_state);
if (hdmitx_state->color_force) {
char attr_str[16];
build_hdmitx_attr_str(attr_str, attr->colorformat, attr->bitdepth);
if (hdmitx_common_chk_mode_attr_sup(modename, attr_str)) {
DRM_DEBUG("color property setting successfully\n");
} else {
hdmitx_state->color_force = false;
DRM_DEBUG("color property setting failed\n");
}
}
if (!hdmitx_state->color_force) {
if (attr->colorformat != HDMI_COLORSPACE_RESERVED6) {
if (meson_hdmitx_test_color_attr(tx_comm, meson_crtc_state,
attr, sequence_id)) {
update_attr = false;
} else {
update_attr = true;
DRM_DEBUG("%s: force attr fail[%d-%d]\n",
__func__, attr->colorformat, attr->bitdepth);
}
} else {
update_attr = true;
}
if (update_attr) {
meson_hdmitx_decide_color_attr(tx_comm, meson_crtc_state,
attr, sequence_id);
hdmitx_state->update = true;
}
}
ret = hdmitx_common_build_format_para(tx_comm, &hdmitx_state->hcs.para,
vic, tx_comm->frac_rate_policy,
attr->colorformat,
bitdepth_to_colordepth(attr->bitdepth),
HDMI_QUANTIZATION_RANGE_FULL);
if (ret < 0)
DRM_ERROR("format para build fail\n");
DRM_INFO("driver autoselect attr:[%s][%d][%d]\n",
hdmitx_state->hcs.para.name, hdmitx_state->hcs.para.cs, hdmitx_state->hcs.para.cd);
return ret;
}
static int meson_hdmitx_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct am_meson_crtc_state *meson_crtc_state =
to_am_meson_crtc_state(crtc_state);
struct am_hdmitx_connector_state *hdmitx_state =
to_am_hdmitx_connector_state(conn_state);
struct hdmitx_color_attr *attr = &hdmitx_state->color_attr_para;
struct drm_display_mode *adj_mode = &crtc_state->adjusted_mode;
char *modename = adj_mode->name;
struct hdmitx_common *common = am_hdmi_info.hdmitx_dev->hdmitx_common;
u64 sequence_id = hdmitx_state->hcs.state_sequence_id;
int ret = 0;
char attr_str[HDMITX_ATTR_LEN_MAX];
u32 brr = 0, qms_en = 0;
/* do not atomic check if hpd is low*/
if (strstr(modename, "dummy") || !hdmitx_get_hpd_state(common))
return 0;
if (meson_crtc_state->uboot_mode_init == 1) {
hdmitx_get_qms_init_state(common, &brr, &qms_en);
if (brr && qms_en)
crtc_state->vrr_enabled = true;
}
if (crtc_state->vrr_enabled &&
!(adj_mode->flags & DRM_MODE_FLAG_INTERLACE)) {
meson_hdmitx_cal_brr(&am_hdmi_info, meson_crtc_state, adj_mode);
modename = meson_crtc_state->brr_mode;
}
meson_encoder_vrr_change(encoder, conn_state->state);
DRM_DEBUG("%s[%d]: enter\n", __func__, __LINE__);
if (meson_crtc_state->uboot_mode_init == 1) {
DRM_INFO("%s[%d] uboot get: %d\n", __func__, __LINE__, common->frac_rate_policy);
hdmitx_get_init_state(common, &hdmitx_state->hcs);
attr->colorformat = hdmitx_state->hcs.para.cs;
attr->bitdepth = colordepth_to_bitdepth(hdmitx_state->hcs.para.cd);
hdmitx_state->hdr_priority = hdmitx_state->hcs.hdr_priority;
hdmitx_state->frac_rate_policy = common->frac_rate_policy;
}
/*The recovery mode not have composer to set attr*/
if (!meson_crtc_state->uboot_mode_init && am_hdmi_info.recovery_mode)
meson_hdmitx_decide_color_attr(common, meson_crtc_state,
attr, sequence_id);
build_hdmitx_attr_str(attr_str, attr->colorformat, attr->bitdepth);
ret = hdmitx_common_validate_mode_locked(common, &hdmitx_state->hcs,
modename, attr_str, meson_crtc_state->valid_brr);
/*
* ret == 0
* mode and attr are supported, don't need to match mode and attr
* ret != 0
* RDK without mode policy, Android or Yocto mode policy selects an wrong mode + attr,
* need to match mode and attr
*/
if (ret) {
ret = meson_hdmitx_encoder_autoselect_attr(encoder, crtc_state, conn_state);
}
DRM_DEBUG("vic:%d, cs:%d, cd:%d ret:%d\n", hdmitx_state->hcs.para.vic,
hdmitx_state->hcs.para.cs, hdmitx_state->hcs.para.cd, ret);
return ret;
}
static const struct drm_encoder_helper_funcs meson_hdmitx_encoder_helper_funcs = {
.atomic_mode_set = meson_hdmitx_encoder_atomic_mode_set,
.atomic_enable = meson_hdmitx_encoder_atomic_enable,
.atomic_disable = meson_hdmitx_encoder_atomic_disable,
.atomic_check = meson_hdmitx_encoder_atomic_check,
};
static const struct drm_encoder_funcs meson_hdmitx_encoder_funcs = {
.destroy = drm_encoder_cleanup,
};
static const struct of_device_id am_meson_hdmi_dt_ids[] = {
{ .compatible = "amlogic, drm-amhdmitx", },
{},
};
MODULE_DEVICE_TABLE(of, am_meson_hdmi_dt_ids);
static const struct drm_prop_enum_list hdmi_hdr_status_enum_list[] = {
{ HDR10PLUS_VSIF, "HDR10Plus-VSIF" },
{ dolbyvision_std, "DolbyVision-Std" },
{ dolbyvision_lowlatency, "DolbyVision-Lowlatency" },
{ HDR10_GAMMA_ST2084, "HDR10-GAMMA_ST2084" },
{ HDR10_others, "HDR10-others" },
{ HDR10_GAMMA_HLG, "HDR10-GAMMA_HLG" },
{ SDR, "SDR" }
};
static void meson_hdmitx_init_hdmi_hdr_status_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create_enum(drm_dev, 0, "hdmi_hdr_status",
hdmi_hdr_status_enum_list,
ARRAY_SIZE(hdmi_hdr_status_enum_list));
if (prop) {
am_hdmi->hdmi_hdr_status_prop = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to hdmi_hdr_status property\n");
}
}
static void meson_hdmitx_init_hdcp_ver_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create_range(drm_dev, 0,
"hdcp_ver", 0, 36);
if (prop) {
am_hdmi->hdcp_ver_prop = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to hdcp_ver property\n");
}
}
static void meson_hdmitx_init_hdcp_mode_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create_range(drm_dev, 0,
"hdcp_mode", 0, 36);
if (prop) {
am_hdmi->hdcp_mode_property = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to hdcp_mode property\n");
}
}
static void meson_hdmitx_init_hdcp_topo_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create_bool(drm_dev, 0, "hdcp_topo");
if (prop) {
am_hdmi->hdcp_topo_property = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to hdcp_topo property\n");
}
}
/* Optional colorspace properties. */
static const struct drm_prop_enum_list hdmi_color_space_enum_list[] = {
{ HDMI_COLORSPACE_RGB, "RGB" },
{ HDMI_COLORSPACE_YUV422, "422" },
{ HDMI_COLORSPACE_YUV444, "444" },
{ HDMI_COLORSPACE_YUV420, "420" },
{ HDMI_COLORSPACE_RESERVED6, "HDMI_COLORSPACE_RESERVED6" }
};
static void meson_hdmitx_init_colorspace_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
char attr_init[16];
struct hdmitx_color_attr attr_param;
struct hdmitx_common *common = am_hdmi_info.hdmitx_dev->hdmitx_common;
hdmitx_get_attr(common, attr_init);
convert_attrstr(attr_init, &attr_param);
prop = drm_property_create_enum(drm_dev, 0, "color_space",
hdmi_color_space_enum_list,
ARRAY_SIZE(hdmi_color_space_enum_list));
if (prop) {
am_hdmi->color_space_prop = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop,
attr_param.colorformat);
} else {
DRM_ERROR("Failed to color_space property\n");
}
}
static void meson_hdmitx_init_colordepth_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
char attr_init[16];
struct hdmitx_color_attr attr_param;
struct hdmitx_common *common = am_hdmi_info.hdmitx_dev->hdmitx_common;
hdmitx_get_attr(common, attr_init);
convert_attrstr(attr_init, &attr_param);
prop = drm_property_create_range(drm_dev, 0,
"color_depth", 0, 16);
if (prop) {
am_hdmi->color_depth_prop = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop,
attr_param.bitdepth);
} else {
DRM_ERROR("Failed to color_depth property\n");
}
}
static void meson_hdmitx_init_hdr_cap_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create_range(drm_dev, 0,
"hdr_cap", 0, 64);
if (prop) {
am_hdmi->hdr_cap_property = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to hdr_cap property\n");
}
}
static void meson_hdmitx_init_dv_cap_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create_range(drm_dev, 0,
"dv_cap", 0, 256);
if (prop) {
am_hdmi->dv_cap_property = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to dv_cap property\n");
}
}
static void meson_hdmitx_init_dv_cap_rx_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create_range(drm_dev, 0,
"dv_cap_rx", 0, 256);
if (prop) {
am_hdmi->dv_cap_rx_property = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to dv_cap_rx property\n");
}
}
static void meson_hdmitx_init_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create_bool(drm_dev, 0, "UPDATE");
if (prop) {
am_hdmi->update_attr_prop = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to UPDATE property\n");
}
}
static void meson_hdmitx_init_avmute_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create_bool(drm_dev, 0, "MESON_DRM_HDMITX_PROP_AVMUTE");
if (prop) {
am_hdmi->avmute_prop = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to AVMUTE property\n");
}
}
static void meson_hdmitx_init_ready_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create_bool(drm_dev, 0, "ready");
if (prop) {
am_hdmi->ready_prop = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to init ready property\n");
}
}
static void meson_hdmitx_init_frac_rate_policy_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create_bool(drm_dev, 0, "FRAC_RATE_POLICY");
if (prop) {
am_hdmi->frac_rate_policy_prop = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to init FRAC_RATE_POLICY property\n");
}
}
static void meson_hdmitx_init_hdmi_used_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create_bool(drm_dev, 0, "hdmi_used");
if (prop) {
am_hdmi->hdmi_used_prop = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to init hdmi_used property\n");
}
}
static void meson_hdmitx_init_sink_type_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create_range(drm_dev, 0, "sink_type", 0, 8);
if (prop) {
am_hdmi->sink_type_prop = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to init sink_type property\n");
}
}
static void meson_hdmitx_init_contenttype_cap_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create_range(drm_dev, 0,
"contenttype_cap", 0, 1023);
if (prop) {
am_hdmi->contenttype_cap_prop = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to contenttype_cap property\n");
}
}
static void meson_hdmitx_init_allm_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create_range(drm_dev, 0,
"allm", 0, 3);
if (prop) {
am_hdmi->allm_prop = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to allm property\n");
}
}
static void meson_hdmitx_init_hdr_priority_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
u32 hdr_priority;
hdmitx_get_hdr_priority(am_hdmi_info.hdmitx_dev->hdmitx_common, &hdr_priority);
prop = drm_property_create_range(drm_dev, 0,
"hdr_priority", 0, UINT_MAX);
if (prop) {
am_hdmi->hdr_priority_prop = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, hdr_priority);
} else {
DRM_ERROR("Failed to allm property\n");
}
}
static void meson_hdmitx_init_edid_valid_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create_bool(drm_dev, 0, "edid_valid");
if (prop) {
am_hdmi->edid_valid_prop = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to init edid_valid property\n");
}
}
static void meson_hdmitx_init_hdcp_user_prop(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create_bool(drm_dev, 0, "hdcp_user");
if (prop) {
am_hdmi->hdcp_user_prop = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to init hdcp_user property\n");
}
}
static void meson_hdmitx_init_hdr_cap_rx_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create_range(drm_dev, 0,
"hdr_cap_rx", 0, 64);
if (prop) {
am_hdmi->hdr_cap_rx_prop = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to hdr_cap_rx property\n");
}
}
static void meson_hdmitx_init_static_meta_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create(drm_dev, DRM_MODE_PROP_BLOB, "HDR_STATIC_META", 0);
if (prop) {
am_hdmi->static_meta_prop = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to create hdr static metadata property\n");
}
}
static void meson_hdmitx_init_allm_cap_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create_bool(drm_dev, 0, "allm_cap");
if (prop) {
am_hdmi->allm_cap_prop = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to init allm_cap property\n");
}
}
static void meson_hdmitx_init_dc_cap_property(struct drm_device *drm_dev,
struct am_hdmi_tx *am_hdmi)
{
struct drm_property *prop;
prop = drm_property_create_range(drm_dev, 0, "dc_cap", 0, 1 << COLOR_MAX_ATTR);
if (prop) {
am_hdmi->dc_cap_prop = prop;
drm_object_attach_property(&am_hdmi->base.connector.base, prop, 0);
} else {
DRM_ERROR("Failed to init dc_cap property\n");
}
}
static void meson_hdmitx_hpd_cb(void *data)
{
struct am_hdmi_tx *am_hdmi = (struct am_hdmi_tx *)data;
struct drm_connector *connector = &am_hdmi_info.base.connector;
struct hdmitx_common *tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
struct drm_modeset_lock *mode_lock =
&connector->dev->mode_config.connection_mutex;
#ifdef CONFIG_CEC_NOTIFIER
struct edid *pedid;
#endif
DRM_INFO("drm hdmitx hpd notify\n");
if (!hdmitx_get_hpd_state(tx_comm) && !am_hdmi->android_path) {
drm_modeset_lock(mode_lock, NULL);
meson_hdmitx_disconnect_hdcp();
am_hdmi_info.hdmitx_on = 0;
drm_modeset_unlock(mode_lock);
}
/*get hdcp ver property immediately after plugin in case hdcp14
*authentication snow screen issue
*/
if (hdmitx_get_hpd_state(tx_comm))
am_hdmi_info.hdcp_rx_type = am_hdmi_info.hdmitx_dev->get_rx_hdcp_cap();
#ifdef CONFIG_CEC_NOTIFIER
if (hdmitx_get_hpd_state(tx_comm)) {
DRM_DEBUG("%s[%d]\n", __func__, __LINE__);
pedid = (struct edid *)hdmitx_get_raw_edid(tx_comm);
cec_notifier_set_phys_addr_from_edid(am_hdmi->cec_notifier,
pedid);
} else {
DRM_DEBUG("%s[%d]\n", __func__, __LINE__);
cec_notifier_set_phys_addr(am_hdmi->cec_notifier,
CEC_PHYS_ADDR_INVALID);
}
#endif
drm_kms_helper_hotplug_event(am_hdmi->base.connector.dev);
}
int meson_hdmitx_dev_bind(struct drm_device *drm,
int type, struct meson_connector_dev *intf)
{
struct meson_drm *priv = drm->dev_private;
struct am_hdmi_tx *am_hdmi;
struct drm_connector *connector;
struct drm_encoder *encoder;
struct meson_connector *mesonconn;
struct connector_hpd_cb hpd_cb;
struct hdmitx_common *tx_comm;
int ret;
int connector_type = type;
char *connector_name = NULL;
int encoder_type = DRM_MODE_ENCODER_TMDS;
struct drm_property *type_prop = NULL;
#ifdef CONFIG_CEC_NOTIFIER
struct edid *pedid;
#endif
struct connector_hdcp_cb hdcp_cb;
int hdcp_ctl_lvl;
u32 crtc_mask = 0;
DRM_DEBUG("%s [%d]\n", __func__, __LINE__);
memset(&am_hdmi_info, 0, sizeof(am_hdmi_info));
am_hdmi_info.hdmitx_dev = to_meson_hdmitx_dev(intf);
tx_comm = am_hdmi_info.hdmitx_dev->hdmitx_common;
hdcp_ctl_lvl = tx_comm->hdcp_ctl_lvl;
DRM_DEBUG("hdcp_ctl_lvl=%d\n", hdcp_ctl_lvl);
if (hdcp_ctl_lvl == 0) {
am_hdmi_info.android_path = true;
} else if (am_hdmi_info.hdmitx_dev->hdcp_init) {
am_hdmi_info.hdmitx_dev->hdcp_init();
if (hdcp_ctl_lvl == 1) {
/*TODO: for westeros start hdcp by driver, will move to userspace.*/
am_hdmi_info.hdcp_request_content_type =
DRM_MODE_HDCP_CONTENT_TYPE0;
am_hdmi_info.hdcp_request_content_protection =
DRM_MODE_CONTENT_PROTECTION_DESIRED;
} else {
am_hdmi_info.hdcp_request_content_type =
DRM_MODE_HDCP_CONTENT_TYPE0;
am_hdmi_info.hdcp_request_content_protection =
DRM_MODE_CONTENT_PROTECTION_UNDESIRED;
}
} else {
DRM_ERROR("%s no HDCP func registered.\n", __func__);
am_hdmi_info.android_path = true;
}
#ifdef CONFIG_CEC_NOTIFIER
//am_hdmi_info.cec_notifier = cec_notifier_conn_register(dev, NULL, NULL);
am_hdmi_info.cec_notifier = NULL;
#endif
am_hdmi = &am_hdmi_info;
mesonconn = &am_hdmi->base;
mesonconn->drm_priv = priv;
mesonconn->update = meson_hdmitx_update;
encoder = &am_hdmi->encoder;
connector = &am_hdmi->base.connector;
am_hdmi->hdmi_type = type;
switch (type) {
case DRM_MODE_CONNECTOR_MESON_HDMIA_A:
connector_type = DRM_MODE_CONNECTOR_HDMIA;
connector_name = "HDMI-A-A";
break;
case DRM_MODE_CONNECTOR_MESON_HDMIA_B:
connector_type = DRM_MODE_CONNECTOR_HDMIA;
connector_name = "HDMI-A-B";
break;
case DRM_MODE_CONNECTOR_MESON_HDMIA_C:
connector_type = DRM_MODE_CONNECTOR_HDMIA;
connector_name = "HDMI-A-C";
break;
case DRM_MODE_CONNECTOR_MESON_HDMIB_A:
connector_type = DRM_MODE_CONNECTOR_HDMIB;
connector_name = "HDMI-B-A";
break;
case DRM_MODE_CONNECTOR_MESON_HDMIB_B:
connector_type = DRM_MODE_CONNECTOR_HDMIB;
connector_name = "HDMI-B-B";
break;
case DRM_MODE_CONNECTOR_MESON_HDMIB_C:
connector_type = DRM_MODE_CONNECTOR_HDMIB;
connector_name = "HDMI-B-C";
break;
default:
connector_type = DRM_MODE_CONNECTOR_Unknown;
encoder_type = DRM_MODE_ENCODER_NONE;
break;
};
/* Connector */
connector->polled = DRM_CONNECTOR_POLL_HPD;
drm_connector_helper_add(connector,
&am_hdmi_connector_helper_funcs);
ret = drm_connector_init(drm, connector, &am_hdmi_connector_funcs,
connector_type);
if (ret) {
dev_err(priv->dev, "Failed to init hdmi tx connector\n");
return ret;
}
connector->interlace_allowed = 1;
/*update name to amlogic name*/
if (connector_name) {
kfree(connector->name);
connector->name = kasprintf(GFP_KERNEL, "%s", connector_name);
if (!connector->name)
DRM_ERROR("[%s]: alloc name failed\n", __func__);
}
/*
*Encoder possible_crtcs priority reference connector crtc_sel.
*/
crtc_mask = meson_crtc_mask(drm);
if (intf->crtc_sel)
encoder->possible_crtcs = intf->crtc_sel & crtc_mask;
else
encoder->possible_crtcs = BIT(0);
drm_encoder_helper_add(encoder, &meson_hdmitx_encoder_helper_funcs);
ret = drm_encoder_init(drm, encoder, &meson_hdmitx_encoder_funcs,
encoder_type, "am_hdmi_encoder");
if (ret) {
dev_err(priv->dev, "Failed to init hdmi encoder\n");
return ret;
}
drm_connector_attach_encoder(connector, encoder);
/*hpd irq moved to amhdmitx, register call back */
hpd_cb.callback = meson_hdmitx_hpd_cb;
hpd_cb.data = &am_hdmi_info;
hdmitx_register_hpd_cb(tx_comm, &hpd_cb);
/*hdcp init, default is disable state*/
if (!am_hdmi_info.android_path) {
drm_connector_attach_content_protection_property(connector, true);
hdcp_cb.hdcp_notify = meson_hdmitx_hdcp_notify;
hdcp_cb.data = &am_hdmi_info;
am_hdmi_info.hdmitx_dev->register_hdcp_notify(&hdcp_cb);
am_hdmi_info.hdcp_mode = HDCP_NULL;
am_hdmi_info.hdcp_state = HDCP_STATE_DISCONNECT;
}
am_hdmi_info.recovery_mode = priv->recovery_mode;
drm_connector_attach_content_type_property(connector);
drm_connector_attach_vrr_capable_property(connector);
/*amlogic prop*/
meson_hdmitx_init_property(drm, am_hdmi);
meson_hdmitx_init_colordepth_property(drm, am_hdmi);
meson_hdmitx_init_colorspace_property(drm, am_hdmi);
meson_hdmitx_init_avmute_property(drm, am_hdmi);
meson_hdmitx_init_hdmi_hdr_status_property(drm, am_hdmi);
/*Getting hdr cap, don't similar to hdmitx sys hdr_cap node*/
meson_hdmitx_init_hdr_cap_property(drm, am_hdmi);
meson_hdmitx_init_hdr_cap_rx_property(drm, am_hdmi);
meson_hdmitx_init_static_meta_property(drm, am_hdmi);
meson_hdmitx_init_dv_cap_property(drm, am_hdmi);
meson_hdmitx_init_dv_cap_rx_property(drm, am_hdmi);
meson_hdmitx_init_hdcp_ver_property(drm, am_hdmi);
meson_hdmitx_init_hdcp_mode_property(drm, am_hdmi);
meson_hdmitx_init_hdcp_topo_property(drm, am_hdmi);
meson_hdmitx_init_contenttype_cap_property(drm, am_hdmi);
meson_hdmitx_init_allm_property(drm, am_hdmi);
meson_hdmitx_init_hdr_priority_property(drm, am_hdmi);
meson_hdmitx_init_ready_property(drm, am_hdmi);
meson_hdmitx_init_edid_valid_property(drm, am_hdmi);
meson_hdmitx_init_hdcp_user_prop(drm, am_hdmi);
meson_hdmitx_init_frac_rate_policy_property(drm, am_hdmi);
meson_hdmitx_init_hdmi_used_property(drm, am_hdmi);
meson_hdmitx_init_sink_type_property(drm, am_hdmi);
meson_hdmitx_init_allm_cap_property(drm, am_hdmi);
meson_hdmitx_init_dc_cap_property(drm, am_hdmi);
/*TODO:update compat_mode for drm driver, remove later.*/
priv->compat_mode = am_hdmi_info.android_path;
/* notifier phy addr to cec when boot
* so that to not miss any hpd event
*/
#ifdef CONFIG_CEC_NOTIFIER
if (hdmitx_get_hpd_state(tx_comm)) {
DRM_DEBUG("%s[%d]\n", __func__, __LINE__);
pedid = (struct edid *)hdmitx_get_raw_edid(tx_comm);
cec_notifier_set_phys_addr_from_edid(am_hdmi->cec_notifier,
pedid);
} else {
cec_notifier_set_phys_addr(am_hdmi->cec_notifier,
CEC_PHYS_ADDR_INVALID);
}
#endif
/*prop for userspace to acquire prop*/
type_prop = drm_property_create_range(drm, DRM_MODE_PROP_IMMUTABLE,
MESON_CONNECTOR_TYPE_PROP_NAME, 0, INT_MAX);
if (type_prop) {
am_hdmi->type_prop = type_prop;
drm_object_attach_property(&am_hdmi->base.connector.base,
type_prop, type);
} else {
DRM_ERROR("%s: Failed to create property %s\n",
__func__, MESON_CONNECTOR_TYPE_PROP_NAME);
}
DRM_DEBUG("%s out[%d]\n", __func__, __LINE__);
return 0;
}
int meson_hdmitx_dev_unbind(struct drm_device *drm,
int type, int connector_id)
{
if (am_hdmi_info.hdmitx_dev->hdcp_exit)
am_hdmi_info.hdmitx_dev->hdcp_exit();
#ifdef CONFIG_CEC_NOTIFIER
if (am_hdmi_info.cec_notifier) {
cec_notifier_set_phys_addr(am_hdmi_info.cec_notifier,
CEC_PHYS_ADDR_INVALID);
//cec_notifier_put(am_hdmi_info.cec_notifier);
}
#endif
am_hdmi_info.base.connector.funcs->destroy(&am_hdmi_info.base.connector);
am_hdmi_info.encoder.funcs->destroy(&am_hdmi_info.encoder);
return 0;
}
int am_meson_mode_testattr_ioctl(struct drm_device *dev,
void *data, struct drm_file *file_priv)
{
struct drm_mode_test_attr *f = data;
if (hdmitx_common_chk_mode_attr_sup(f->modename, f->attr))
f->valid = 1;
else
f->valid = 0;
return 0;
}
int am_meson_hdmi_get_vrr_range(struct drm_device *dev,
void *data, struct drm_file *file_priv)
{
int i, num_group = 0;
struct hdmitx_vrr_mode_group *hdmi_groups;
struct drm_vrr_mode_groups *groups = data;
hdmi_groups = kcalloc(MAX_VRR_MODE_GROUP, sizeof(*hdmi_groups), GFP_KERNEL);
if (!hdmi_groups)
return -ENOMEM;
num_group = am_hdmi_info.hdmitx_dev->get_vrr_mode_group(hdmi_groups,
MAX_VRR_MODE_GROUP);
if (!num_group) {
DRM_ERROR("get vrr error or not support qms\n");
kfree(hdmi_groups);
return -EINVAL;
}
for (i = 0; i < num_group; i++) {
groups->groups[i].brr_vic = hdmi_groups[i].brr_vic;
groups->groups[i].brr = hdmi_groups[i].brr;
groups->groups[i].width = hdmi_groups[i].width;
groups->groups[i].height = hdmi_groups[i].height;
groups->groups[i].vrr_min = hdmi_groups[i].vrr_min / VRR_DIV;
groups->groups[i].vrr_max = hdmi_groups[i].vrr_max / VRR_DIV;
memcpy(groups->groups[i].modename, hdmi_groups[i].modename, DRM_DISPLAY_MODE_LEN);
}
groups->num = num_group;
kfree(hdmi_groups);
return num_group;
}