blob: e451acbca31017f17b2b377152f3077e7ef1138d [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/vout_notify.h>
#include <linux/amlogic/media/vout/hdmi_tx/hdmi_tx_module.h>
#include <linux/amlogic/media/vout/hdmi_tx/hdmi_common.h>
#include <linux/amlogic/media/vout/hdmi_tx/meson_drm_hdmitx.h>
#include <linux/miscdevice.h>
#include <linux/amlogic/media/vout/hdmi_tx/meson_drm_hdmitx.h>
#include "meson_hdmi.h"
#include "meson_hdcp.h"
#include "meson_vpu.h"
#include "meson_crtc.h"
#define HDMITX_ATTR_LEN_MAX 16
#define HDMITX_MAX_BPC 12
static struct am_hdmi_tx am_hdmi_info;
/*for hw limitiation, 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,
.vrefresh = 50,
.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
};
struct hdmitx_color_attr dv_color_attr_list[] = {
{COLORSPACE_YUV444, 8}, //"444,8bit"
{COLORSPACE_RESERVED, COLORDEPTH_RESERVED}
};
struct hdmitx_color_attr dv_ll_color_attr_list[] = {
{COLORSPACE_YUV422, 12}, //"422,12bit"
{COLORSPACE_RESERVED, COLORDEPTH_RESERVED}
};
/* this is prior selected list of
* 4k2k50hz, 4k2k60hz smpte50hz, smpte60hz
*/
struct hdmitx_color_attr color_attr_list[] = {
{COLORSPACE_YUV420, 10}, //"420,10bit"
{COLORSPACE_YUV422, 12}, //"422,12bit"
{COLORSPACE_YUV420, 8}, //"420,8bit"
{COLORSPACE_YUV444, 8}, //"444,8bit"
{COLORSPACE_RGB444, 8}, //"rgb,8bit"
{COLORSPACE_RESERVED, COLORDEPTH_RESERVED}
};
/* this is prior selected list of other display mode */
struct hdmitx_color_attr other_color_attr_list[] = {
{COLORSPACE_YUV444, 10}, //"444,10bit"
{COLORSPACE_YUV422, 12}, //"422,12bit"
{COLORSPACE_RGB444, 10}, //"rgb,10bit"
{COLORSPACE_YUV444, 8}, //"444,8bit"
{COLORSPACE_RGB444, 8}, //"rgb,8bit"
{COLORSPACE_RESERVED, 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"
static void convert_attrstr(char *attr_str,
struct hdmitx_color_attr *attr_param)
{
attr_param->colorformat = COLORSPACE_RESERVED;
attr_param->bitdepth = COLORDEPTH_RESERVED;
if (strstr(attr_str, "420"))
attr_param->colorformat = COLORSPACE_YUV420;
else if (strstr(attr_str, "422"))
attr_param->colorformat = COLORSPACE_YUV422;
else if (strstr(attr_str, "444"))
attr_param->colorformat = COLORSPACE_YUV444;
else if (strstr(attr_str, "rgb"))
attr_param->colorformat = COLORSPACE_RGB444;
/*parse colorspace success*/
if (attr_param->colorformat != COLORSPACE_RESERVED) {
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 void build_hdmitx_attr_str(char *attr_str, u32 format, u32 bit_depth)
{
const char *colorspace;
switch (format) {
case COLORSPACE_YUV420:
colorspace = "420";
break;
case COLORSPACE_YUV422:
colorspace = "422";
break;
case COLORSPACE_YUV444:
colorspace = "444";
break;
case COLORSPACE_RGB444:
colorspace = "rgb";
break;
default:
colorspace = "rgb";
DRM_ERROR("Unknown colospace valu %d\n", format);
break;
};
sprintf(attr_str, "%s,%dbit", colorspace, bit_depth);
DRM_INFO("%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;
/* filter some color value options, aimed at some modes. */
if (crtc_state->crtc_eotf_type ==
HDMI_EOTF_MESON_DOLBYVISION) {
attr_list = dv_color_attr_list;
} else if (crtc_state->crtc_eotf_type ==
HDMI_EOTF_MESON_DOLBYVISION_LL) {
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 am_meson_crtc_state *crtc_state,
struct am_hdmitx_connector_state *conn_state,
struct hdmitx_color_attr *test_attr)
{
char *outputmode = crtc_state->base.adjusted_mode.name;
struct hdmitx_color_attr *attr_list = NULL;
char attr_str[HDMITX_ATTR_LEN_MAX];
u8 max_bpc = conn_state->base.max_bpc;
if (test_attr->colorformat == COLORSPACE_RESERVED ||
test_attr->bitdepth > max_bpc)
return false;
attr_list = meson_hdmitx_get_candidate_attr_list(crtc_state);
do {
if (attr_list->colorformat == COLORSPACE_RESERVED)
break;
if (attr_list->colorformat == test_attr->colorformat &&
attr_list->bitdepth == test_attr->bitdepth) {
build_hdmitx_attr_str(attr_str,
attr_list->colorformat, attr_list->bitdepth);
if (drm_hdmitx_chk_mode_attr_sup(outputmode,
attr_str)) {
DRM_INFO("%s success [%d]+[%d]\n", __func__,
attr_list->colorformat,
attr_list->bitdepth);
break;
}
}
} while (attr_list++);
if (attr_list->colorformat == COLORSPACE_RESERVED)
return false;
else
return true;
}
static int meson_hdmitx_decide_color_attr
(struct am_meson_crtc_state *crtc_state,
u8 max_bpc, struct hdmitx_color_attr *attr)
{
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 == COLORSPACE_RESERVED)
break;
if (attr_list->bitdepth <= max_bpc) {
build_hdmitx_attr_str(attr_str,
attr_list->colorformat, attr_list->bitdepth);
if (drm_hdmitx_chk_mode_attr_sup(outputmode,
attr_str)) {
attr->colorformat = attr_list->colorformat;
attr->bitdepth = attr_list->bitdepth;
DRM_INFO("%s get fmt attr [%d]+[%d]\n",
__func__,
attr->colorformat,
attr->bitdepth);
break;
}
}
} while (attr_list++);
if (attr_list->colorformat == COLORSPACE_RESERVED) {
DRM_ERROR("%s no attr found, reset to 444,8bit.\n", __func__);
attr->colorformat = COLORSPACE_RGB444;
attr->bitdepth = 8;
}
DRM_DEBUG_KMS("[%s]:[%s,bpc:%d,eotf:%d]=>attr[%d,%d]\n", __func__,
outputmode, max_bpc, crtc_state->crtc_eotf_type,
attr->colorformat, attr->bitdepth);
return 0;
}
static int meson_hdmitx_setup_color_attr(struct hdmitx_color_attr *attr)
{
char hdmitx_attr_str[HDMITX_ATTR_LEN_MAX];
build_hdmitx_attr_str(hdmitx_attr_str,
attr->colorformat, attr->bitdepth);
drm_hdmitx_setup_attr(hdmitx_attr_str);
DRM_DEBUG_KMS("%s:[%s]\n", __func__, hdmitx_attr_str);
return 0;
}
char *am_meson_hdmi_get_voutmode(struct drm_display_mode *mode)
{
return mode->name;
}
int meson_hdmitx_get_modes(struct drm_connector *connector)
{
struct edid *edid;
int *vics;
int count = 0, i = 0, len = 0;
struct drm_display_mode *mode, *pref_mode = NULL;
struct hdmi_format_para *hdmi_para;
struct hdmi_cea_timing *timing;
char *strp = NULL;
u32 num, den;
edid = (struct edid *)drm_hdmitx_get_raw_edid();
drm_connector_update_edid_property(connector, edid);
/*add modes from hdmitx instead of edid*/
count = drm_hdmitx_get_vic_list(&vics);
if (count) {
for (i = 0; i < count; i++) {
hdmi_para = hdmi_get_fmt_paras(vics[i]);
timing = &hdmi_para->timing;
if (hdmi_para->vic == HDMI_UNKNOWN) {
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, hdmi_para->hdmitx_vinfo.name, DRM_DISPLAY_MODE_LEN);
/* remove _4x3 suffix, in case misunderstand */
strp = strstr(mode->name, "_4x3");
if (strp)
*strp = '\0';
/*
* filter 4k420 mode, 4k420 mode end with "420"
* 2160p60hz420 to 2160p60hz
*/
strp = strstr(mode->name, "420");
if (strp) {
len = strlen(mode->name) - strlen("420");
if (!strcmp(mode->name + len, "420"))
*strp = '\0';
}
mode->type = DRM_MODE_TYPE_DRIVER;
num = hdmi_para->hdmitx_vinfo.sync_duration_num;
den = hdmi_para->hdmitx_vinfo.sync_duration_den;
mode->vrefresh = (int)DIV_ROUND_CLOSEST(num, den);
mode->clock = timing->pixel_freq;
mode->hdisplay = timing->h_active;
mode->hsync_start = timing->h_active + timing->h_front;
mode->hsync_end = timing->h_active + timing->h_front + timing->h_sync;
mode->htotal = timing->h_total;
mode->hskew = 0;
mode->flags |= timing->hsync_polarity ?
DRM_MODE_FLAG_PHSYNC : DRM_MODE_FLAG_NHSYNC;
mode->vdisplay = timing->v_active;
if (hdmi_para->hdmitx_vinfo.field_height !=
hdmi_para->hdmitx_vinfo.height) {
/* follow general rule to use full vidsplay while
* amlogic vout use half value.
*/
mode->vdisplay = mode->vdisplay << 1;
mode->flags |= DRM_MODE_FLAG_INTERLACE;
}
mode->vsync_start = mode->vdisplay + timing->v_front;
mode->vsync_end = mode->vdisplay + timing->v_front + timing->v_sync;
mode->vtotal = timing->v_total;
mode->vscan = 0;
mode->flags |= timing->vsync_polarity ?
DRM_MODE_FLAG_PVSYNC : DRM_MODE_FLAG_NVSYNC;
/*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 (!(mode->flags & DRM_MODE_FLAG_INTERLACE) &&
((mode->hdisplay == 1920 && mode->vdisplay == 1080) ||
(mode->hdisplay == 1280 && mode->vdisplay == 720))) {
if (!pref_mode)
pref_mode = mode;
else if (pref_mode->hdisplay < mode->hdisplay)
pref_mode = mode;
else if (pref_mode->hdisplay == mode->hdisplay &&
pref_mode->vrefresh < mode->vrefresh)
pref_mode = mode;
}
}
drm_mode_probed_add(connector, mode);
DRM_DEBUG("add mode [%s]\n", mode->name);
}
if (pref_mode)
pref_mode->type |= DRM_MODE_TYPE_PREFERRED;
kfree(vics);
} else {
DRM_ERROR("drm_hdmitx_get_vic_list return 0.\n");
}
/*TODO:add dummy mode temp.*/
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++;
}
return count;
}
/* 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)
{
int hpdstat = drm_hdmitx_detect_hpd();
DRM_DEBUG("am_hdmi_connector_detect [%d]\n", hpdstat);
return hpdstat == 1 ?
connector_status_connected : connector_status_disconnected;
}
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);
if (property == am_hdmi->update_attr_prop) {
hdmitx_state->update = true;
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 am_hdmi_tx *am_hdmi = connector_to_am_hdmi(connector);
if (property == am_hdmi->update_attr_prop) {
*val = 0;
return 0;
}
return -EINVAL;
}
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 = drm_hdmitx_get_contenttypes();
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->base.crtc)
new_crtc_state = drm_atomic_get_new_crtc_state(state,
new_hdmitx_state->base.crtc);
/*check content type.*/
if (((1 << new_hdmitx_state->base.content_type) &
hdmitx_content_type) == 0) {
DRM_ERROR("[%s] check contentype[%d-%u] fail\n",
__func__,
new_hdmitx_state->base.content_type,
hdmitx_content_type);
return -EINVAL;
}
/*force set mode.*/
if (new_crtc_state && new_hdmitx_state->update)
new_crtc_state->connectors_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_attr_para.colorformat = COLORSPACE_RESERVED;
new_state->color_attr_para.bitdepth = COLORDEPTH_RESERVED;
new_state->pref_hdr_policy = cur_state->pref_hdr_policy;
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;
char hdmitx_attr[16];
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 = drm_hdmitx_get_hdr_priority();
/*drm api need update state, so need delay attch when create state.*/
if (!connector->max_bpc_property)
drm_connector_attach_max_bpc_property
(connector, 8, HDMITX_MAX_BPC);
/*read attr from hdmitx, its from uboot*/
drm_hdmitx_get_attr(hdmitx_attr);
convert_attrstr(hdmitx_attr, &hdmitx_state->color_attr_para);
}
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 hdcp_state:[%d]\n", am_hdmi_info.hdcp_state);
drm_printf(p, "\t\t color attr:[%d,%d], hdr_policy[%d]\n",
hdmitx_state->color_attr_para.colorformat,
hdmitx_state->color_attr_para.bitdepth,
hdmitx_state->pref_hdr_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;
meson_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()) {
meson_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()) {
meson_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);
}
}
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_hdcp_get_valid_type(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_hdcp_locked(void)
{
struct drm_connector *connector = &am_hdmi_info.base.connector;
struct drm_modeset_lock *mode_lock =
&connector->dev->mode_config.connection_mutex;
drm_modeset_lock(mode_lock, NULL);
meson_hdmitx_update_hdcp();
drm_modeset_unlock(mode_lock);
}
void meson_hdmitx_update(struct drm_connector_state *new_state,
struct drm_connector_state *old_state)
{
if (new_state->content_type != old_state->content_type)
drm_hdmitx_set_contenttype(new_state->content_type);
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 (!drm_atomic_crtc_needs_modeset(new_state->crtc->state))
meson_hdmitx_update_hdcp();
}
}
static void meson_hdmitx_hdcp_notify(int type, int result)
{
if (type == HDCP_KEY_UPDATE && result == HDCP_AUTH_UNKNOWN) {
DRM_ERROR("HDCP statue changed, need re-run hdcp\n");
meson_hdmitx_update_hdcp_locked();
return;
}
if (type != am_hdmi_info.hdcp_mode) {
DRM_DEBUG("notify type is mismatch[%d]-[%d]\n",
type, am_hdmi_info.hdcp_mode);
return;
}
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_ERROR("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);
}
}
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,
.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 = drm_hdmitx_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_ERROR("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 = drm_hdmitx_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)
{
u8 crtc_eotf_type = HDMI_EOTF_TRADITIONAL_GAMMA_SDR;
int ret = 0;
struct drm_display_mode *mode = &meson_crtc_state->base.adjusted_mode;
/* 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)
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) {
/*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);
}
meson_crtc_state->crtc_eotf_type = crtc_eotf_type;
DRM_DEBUG_KMS("%s: [%u->%u]\n", __func__,
hdmitx_state->pref_hdr_policy,
meson_crtc_state->crtc_eotf_type);
return 0;
}
/*Calculate parameters 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)
{
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;
bool update_attr = false;
if (am_hdmi_info.android_path)
return;
DRM_DEBUG_KMS("%s: enter\n", __func__);
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->colorformat != COLORSPACE_RESERVED) {
if (meson_hdmitx_test_color_attr(meson_crtc_state,
hdmitx_state, attr)) {
update_attr = false;
} else {
update_attr = true;
DRM_DEBUG_KMS("%s: force attr fail[%d-%d]\n",
__func__, attr->colorformat, attr->bitdepth);
}
} else {
update_attr = true;
}
if (update_attr) {
meson_hdmitx_decide_color_attr(meson_crtc_state,
hdmitx_state->base.max_bpc, attr);
hdmitx_state->update = true;
}
meson_hdmitx_setup_color_attr(attr);
}
void meson_hdmitx_encoder_atomic_enable(struct drm_encoder *encoder,
struct drm_atomic_state *state)
{
enum vmode_e vmode = get_current_vmode();
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 am_hdmitx_connector_state *meson_conn_state =
to_am_hdmitx_connector_state(conn_state);
if (vmode == VMODE_HDMI) {
DRM_INFO("[%s]\n", __func__);
} else {
DRM_INFO("[%s] fail! vmode:%d\n", __func__, vmode);
return;
}
if (meson_crtc_state->uboot_mode_init == 1 &&
meson_conn_state->update != 1)
vmode |= VMODE_INIT_BIT_MASK;
set_vout_mode_pre_process(vmode);
set_vout_vmode(vmode);
set_vout_mode_post_process(vmode);
if (!am_hdmi_info.android_path) {
drm_hdmitx_avmute(0);
meson_hdmitx_update_hdcp();
}
}
void meson_hdmitx_encoder_atomic_disable(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);
DRM_INFO("[%s]\n", __func__);
if (am_hdmi_info.android_path ||
meson_crtc_state->uboot_mode_init == 1)
return;
drm_hdmitx_avmute(1);
meson_hdmitx_stop_hdcp();
msleep(100);
}
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,
};
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 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_hpd_cb(void *data)
{
struct am_hdmi_tx *am_hdmi = (struct am_hdmi_tx *)data;
#ifdef CONFIG_CEC_NOTIFIER
struct edid *pedid;
#endif
DRM_INFO("drm hdmitx hpd notify\n");
if (drm_hdmitx_detect_hpd() == 0 && !am_hdmi->android_path)
meson_hdmitx_disconnect_hdcp();
#ifdef CONFIG_CEC_NOTIFIER
if (drm_hdmitx_detect_hpd()) {
DRM_INFO("%s[%d]\n", __func__, __LINE__);
pedid = (struct edid *)drm_hdmitx_get_raw_edid();
cec_notifier_set_phys_addr_from_edid(am_hdmi->cec_notifier,
pedid);
} else {
DRM_INFO("%s[%d]\n", __func__, __LINE__);
cec_notifier_set_phys_addr(am_hdmi->cec_notifier,
CEC_PHYS_ADDR_INVALID);
}
#endif
drm_helper_hpd_irq_event(am_hdmi->base.connector.dev);
}
static int am_meson_hdmi_bind(struct device *dev,
struct device *master, void *data)
{
struct drm_device *drm = data;
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 drm_hdmitx_hpd_cb hpd_cb;
int ret;
#ifdef CONFIG_CEC_NOTIFIER
struct edid *pedid;
#endif
DRM_INFO("[%s] in\n", __func__);
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;
/* 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,
DRM_MODE_CONNECTOR_HDMIA);
if (ret) {
dev_err(priv->dev, "Failed to init hdmi tx connector\n");
return ret;
}
connector->interlace_allowed = 1;
/* Encoder */
drm_encoder_helper_add(encoder, &meson_hdmitx_encoder_helper_funcs);
ret = drm_encoder_init(drm, encoder, &meson_hdmitx_encoder_funcs,
DRM_MODE_ENCODER_TMDS, "am_hdmi_encoder");
if (ret) {
dev_err(priv->dev, "Failed to init hdmi encoder\n");
return ret;
}
encoder->possible_crtcs = BIT(0);
drm_connector_attach_encoder(connector, encoder);
/*hpd irq moved to amhdmitx, registe call back */
hpd_cb.callback = meson_hdmitx_hpd_cb;
hpd_cb.data = &am_hdmi_info;
drm_hdmitx_register_hpd_cb(&hpd_cb);
/*hdcp init, default is disable state*/
if (!am_hdmi_info.android_path) {
drm_connector_attach_content_protection_property(connector, true);
meson_hdcp_reg_result_notify(meson_hdmitx_hdcp_notify);
am_hdmi_info.hdcp_mode = HDCP_NULL;
am_hdmi_info.hdcp_state = HDCP_STATE_DISCONNECT;
}
drm_connector_attach_content_type_property(connector);
/*amlogic prop*/
meson_hdmitx_init_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 (drm_hdmitx_detect_hpd()) {
DRM_INFO("%s[%d]\n", __func__, __LINE__);
pedid = (struct edid *)drm_hdmitx_get_raw_edid();
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
DRM_INFO("[%s] out\n", __func__);
return 0;
}
static void am_meson_hdmi_unbind(struct device *dev,
struct device *master, void *data)
{
am_hdmi_info.base.connector.funcs->destroy(&am_hdmi_info.base.connector);
am_hdmi_info.encoder.funcs->destroy(&am_hdmi_info.encoder);
}
static const struct component_ops am_meson_hdmi_ops = {
.bind = am_meson_hdmi_bind,
.unbind = am_meson_hdmi_unbind,
};
static int am_meson_hdmi_probe(struct platform_device *pdev)
{
struct hdmitx_dev *hdmitx_dev = get_hdmitx_device();
DRM_INFO("[%s] in\n", __func__);
memset(&am_hdmi_info, 0, sizeof(am_hdmi_info));
if (hdmitx_dev->hdcp_ctl_lvl == 0) {
am_hdmi_info.android_path = true;
} else {
meson_hdcp_init();
if (hdmitx_dev->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;
}
}
#ifdef CONFIG_CEC_NOTIFIER
am_hdmi_info.cec_notifier = cec_notifier_get(&pdev->dev);
#endif
return component_add(&pdev->dev, &am_meson_hdmi_ops);
}
static int am_meson_hdmi_remove(struct platform_device *pdev)
{
meson_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
component_del(&pdev->dev, &am_meson_hdmi_ops);
return 0;
}
static struct platform_driver am_meson_hdmi_pltfm_driver = {
.probe = am_meson_hdmi_probe,
.remove = am_meson_hdmi_remove,
.driver = {
.name = "meson-amhdmitx",
.of_match_table = am_meson_hdmi_dt_ids,
},
};
int __init am_meson_hdmi_init(void)
{
return platform_driver_register(&am_meson_hdmi_pltfm_driver);
}
void __exit am_meson_hdmi_exit(void)
{
platform_driver_unregister(&am_meson_hdmi_pltfm_driver);
}
#ifndef MODULE
module_init(am_meson_hdmi_init);
module_exit(am_meson_hdmi_exit);
#endif
MODULE_AUTHOR("MultiMedia Amlogic <multimedia-sh@amlogic.com>");
MODULE_DESCRIPTION("Amlogic Meson Drm HDMI driver");
MODULE_LICENSE("GPL");