// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * Copyright (c) 2019 Amlogic, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "meson_crtc.h" #include "meson_dptx.h" #include "meson_venc.h" static struct dptx_common *connector_to_dptx_common(struct drm_connector *connector) { struct meson_dp_tx *meson_dptx = NULL; meson_dptx = connector_to_meson_dptx(connector); return conn_to_dptx_common(meson_dptx->dptx_dev); } static struct meson_tx_dev *conn_dev_to_meson_tx_dev(struct device *dev) { struct dptx_common *dptx_comm = NULL; struct drm_connector *connector = dev_get_drvdata(dev); dptx_comm = connector_to_dptx_common(connector); return &dptx_comm->base; } static int meson_dptx_mode_probed_add(int count, struct tx_timing *timings, struct drm_connector *connector) { int i; struct drm_display_mode *mode; struct tx_timing *timing = timings; for (i = 0; i < count; i++, timing++) { mode = drm_mode_create(connector->dev); if (!mode) { DRM_ERROR("drm mode create failed.\n"); continue; } meson_connector_fill_mode_timing(mode, timing, false); drm_mode_probed_add(connector, mode); meson_tx_add_alter_mode(connector, mode); DRM_DEBUG("add mode [%s]\n", mode->name); } return 0; } static int meson_dptx_get_modes(struct drm_connector *connector) { struct edid *edid; unsigned char *raw_edid = NULL; struct meson_dp_tx *meson_dptx = connector_to_meson_dptx(connector); struct dptx_common *tx_comm = connector_to_dptx_common(connector); struct meson_dptx_connector_state *dptx_state = to_dptx_connector_state(connector->state); struct tx_timing *timing_list = NULL; u64 sequence_id, new_sequence_id; int count = 0; unsigned long flags = 0; u32 vrr_cap = 0; if (!meson_dptx || !meson_dptx->dptx_dev || !tx_comm) return count; raw_edid = meson_tx_get_raw_edid(&tx_comm->base); if (raw_edid) { edid = (struct edid *)raw_edid; drm_connector_update_edid_property(connector, edid); } sequence_id = dptx_state->base_state.sequence_id; new_sequence_id = meson_tx_get_hpd_hw_sequence_id(&tx_comm->base); /* * After set mode, hwc will update the connector. * In order to prevent the edid from being parsed every time, * the sequence_id judgment is added, and the edid is only parsed * when the hot_plug time occurs. */ if (sequence_id != new_sequence_id) { dptx_state->base_state.sequence_id = new_sequence_id; spin_lock_irqsave(&tx_comm->edid_spinlock, flags); meson_tx_clear_rx_cap(&tx_comm->base.rxcap); dptx_common_edid_parse(tx_comm); DRM_INFO("%s[%d] edid_parsing:%d\n", __func__, __LINE__, tx_comm->base.rxcap.edid_parsing); spin_unlock_irqrestore(&tx_comm->edid_spinlock, flags); } /* get vrr capability */ vrr_cap = dptx_common_get_vrr_cap(tx_comm); DRM_DEBUG("support vrr_cap[%d]\n", vrr_cap); drm_connector_set_vrr_capable_property(connector, vrr_cap); count = dptx_get_mode_list(tx_comm, &timing_list); if (count > 0) { meson_dptx_mode_probed_add(count, timing_list, connector); vfree(timing_list); timing_list = NULL; } DRM_INFO("%s get %d modes\n", __func__, count); return count; } static enum drm_mode_status meson_dptx_check_mode(struct drm_connector *connector, struct drm_display_mode *mode) { return MODE_OK; } static int meson_dptx_atomic_check(struct drm_connector *connector, struct drm_atomic_state *state) { struct meson_dptx_connector_state *new_state, *curr_state; struct drm_crtc_state *new_crtc_state = NULL; struct am_meson_crtc_state *meson_crtc_state; struct dptx_common *tx_comm = connector_to_dptx_common(connector); unsigned int dptx_content_type = dptx_common_get_content_types(tx_comm); if (!state) { DRM_ERROR("state is NULL.\n"); return -EINVAL; } curr_state = to_dptx_connector_state (drm_atomic_get_old_connector_state(state, connector)); new_state = to_dptx_connector_state (drm_atomic_get_new_connector_state(state, connector)); /*check content type.*/ if (((1 << new_state->base.content_type) & dptx_content_type) == 0) { DRM_ERROR("check content type[%d-%u] fail\n", new_state->base.content_type, dptx_content_type); return -EINVAL; } if (new_state->base.crtc) new_crtc_state = drm_atomic_get_new_crtc_state(state, new_state->base.crtc); else return 0; meson_crtc_state = to_am_meson_crtc_state(new_crtc_state); if (state->allow_modeset && new_crtc_state) { DRM_DEBUG("update[%d], frac_mode[%d %d]\n", new_state->update, curr_state->base_state.para.frac_mode, new_state->base_state.para.frac_mode); if (new_state->update) new_crtc_state->connectors_changed = true; if (new_state->base_state.para.frac_mode != curr_state->base_state.para.frac_mode) new_crtc_state->mode_changed = true; } DRM_DEBUG("%px %px\n", curr_state, new_state); return 0; } static struct drm_encoder *meson_dptx_best_encoder(struct drm_connector *connector) { struct meson_dp_tx *meson_dptx = connector_to_meson_dptx(connector); return &meson_dptx->encoder; } static const struct drm_connector_helper_funcs meson_dptx_connector_helper_funcs = { .get_modes = meson_dptx_get_modes, .mode_valid = meson_dptx_check_mode, .atomic_check = meson_dptx_atomic_check, .best_encoder = meson_dptx_best_encoder, }; static enum drm_connector_status meson_dptx_connector_detect (struct drm_connector *connector, bool force) { struct dptx_common *tx_comm = connector_to_dptx_common(connector); int hpd_state = meson_tx_get_hpd_state(&tx_comm->base); DRM_DEBUG("dptx_connector_detect [%d]\n", hpd_state); return hpd_state == 1 ? connector_status_connected : connector_status_disconnected; } static int _get_hdr_info(const struct hdr_info *hdr) { int hdr_cap_value = 0; const struct hdr10_plus_info *hdr10p = &hdr->hdr10plus_info; /* * hdr_cap_value: * bit0 SDR * bit1 HDR * bit2 SMPTE2084 * bit3 HLG * bit4 HDR10PLUS */ hdr_cap_value = hdr->hdr_support; /* hdr10plus_supported */ if (hdr10p->ieeeoui == HDR10_PLUS_IEEE_OUI && hdr10p->application_version != 0xFF) hdr_cap_value |= BIT(4); return hdr_cap_value; } static int get_hdr_info_rx(struct dptx_common *tx_comm) { const struct hdr_info *hdr = dptx_common_get_hdr_info_rx(tx_comm); return _get_hdr_info(hdr); } static int get_sink_type(struct dptx_common *tx_comm) { /* * TODO: Repeater check for HDMI sink. * Need confirm for DP. */ return BIT(2); } static int get_dc_cap(struct dptx_common *tx_comm) { struct rx_cap *prxcap = &tx_comm->base.rxcap; const struct dv_info *dv = &prxcap->dv_info; int i, dc_cap_mask = 0; 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->video_cap.y420_vic[i]) { /* 420,8bit */ dc_cap_mask |= BIT(COLOR_YCBCR420_8BIT); break; } } if (prxcap->native_Mode & CAP_BIT5_YCBCR_444_MASK) { if (prxcap->dc_y444) { if (prxcap->dc_36bit || dv->sup_10b_12b_444 == 0x2) /* 444,12bit */ dc_cap_mask |= BIT(COLOR_YCBCR444_12BIT); if (prxcap->dc_30bit || dv->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 & CAP_BIT4_YCBCR_422_MASK) /* 422,12bit */ dc_cap_mask |= BIT(COLOR_YCBCR422_12BIT); if (prxcap->dc_36bit || dv->sup_10b_12b_444 == 0x2) /* rgb,12bit */ dc_cap_mask |= BIT(COLOR_RGB_12BIT); if (prxcap->dc_30bit || dv->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; } void get_dptx_metadata(struct dptx_common *tx_comm, struct meson_hdr_static_metadata *mHdrMetaDataValue) { const struct hdr_info hdrinfo = tx_comm->base.rxcap.hdr_info; mHdrMetaDataValue->lumi_max = hdrinfo.lumi_max; mHdrMetaDataValue->lumi_min = hdrinfo.lumi_min; mHdrMetaDataValue->lumi_avg = hdrinfo.lumi_avg; } static int meson_dptx_connector_atomic_set_property (struct drm_connector *connector, struct drm_connector_state *state, struct drm_property *property, uint64_t val) { struct meson_dp_tx *meson_dptx = connector_to_meson_dptx(connector); struct meson_dptx_connector_state *dptx_state = to_dptx_connector_state(state); struct tx_color_attr *attr = &dptx_state->color_attr_para; struct meson_tx_state *tx_state = &dptx_state->base_state; if (property == meson_dptx->color_depth_prop) attr->bitdepth = val; else if (property == meson_dptx->color_space_prop) attr->colorformat = val; else if (property == meson_dptx->hdcp_content_type0_pri_prop) { meson_dptx->hdcp_content_type0_pri = val; dptx_state->hdcp_force_st = true; } else if (property == meson_dptx->hdcp_content_type0_pri_store_prop) meson_dptx->hdcp_content_type0_pri = val; else if (property == meson_dptx->update_attr_prop) dptx_state->update = true; else if (property == meson_dptx->avmute_prop) dptx_state->avmute = val; else if (property == meson_dptx->hdr_priority_prop) tx_state->hdr_priority = val; else if (property == meson_dptx->frac_rate_policy_prop) DRM_DEBUG("frac rate property was not used\n"); else return -EINVAL; return 0; } static int meson_dptx_connector_atomic_get_property (struct drm_connector *connector, const struct drm_connector_state *state, struct drm_property *property, uint64_t *val) { struct meson_dp_tx *meson_dptx = connector_to_meson_dptx(connector); struct meson_dptx_connector_state *dptx_state = to_dptx_connector_state(state); struct tx_color_attr *attr = &dptx_state->color_attr_para; struct dptx_common *tx_comm = connector_to_dptx_common(connector); struct meson_tx_state *tx_state = &dptx_state->base_state; struct meson_hdr_static_metadata mHdrMetaDataValue; struct drm_property_blob *new_metadata, *old_metadata; bool replaced; if (property == meson_dptx->color_depth_prop) *val = attr->bitdepth; else if (property == meson_dptx->color_space_prop) *val = attr->colorformat; else if (property == meson_dptx->type_prop) *val = meson_dptx->dptx_type; else if (property == meson_dptx->hdcp_mode_prop) *val = dptx_common_get_hdcp_mode(tx_comm); else if (property == meson_dptx->hdcp_content_type0_pri_prop) *val = meson_dptx->hdcp_content_type0_pri; else if (property == meson_dptx->hdcp_content_type0_pri_store_prop) *val = meson_dptx->hdcp_content_type0_pri; else if (property == meson_dptx->hdcp_user_prop) *val = dptx_common_get_hdcp_user_state(tx_comm); else if (property == meson_dptx->hdr_cap_rx_prop) *val = get_hdr_info_rx(tx_comm); else if (property == meson_dptx->ready_prop) *val = dptx_common_get_ready_state(tx_comm); else if (property == meson_dptx->frac_rate_policy_prop) *val = tx_comm->base.sw_fmt_para.frac_mode; else if (property == meson_dptx->update_attr_prop) *val = 0; else if (property == meson_dptx->avmute_prop) *val = dptx_state->avmute; else if (property == meson_dptx->dc_cap_prop) *val = get_dc_cap(tx_comm); else if (property == meson_dptx->edid_valid_prop) *val = dptx_common_get_edid_valid_state(tx_comm); else if (property == meson_dptx->dptx_hdr_status_prop) *val = dptx_common_get_hdr_status(tx_comm); else if (property == meson_dptx->hdr_priority_prop) *val = tx_state->hdr_priority; else if (property == meson_dptx->sink_type_prop) *val = get_sink_type(tx_comm); else if (property == meson_dptx->contenttype_cap_prop) *val = dptx_common_get_content_types(tx_comm); else if (property == meson_dptx->static_meta_prop) { get_dptx_metadata(tx_comm, &mHdrMetaDataValue); old_metadata = dptx_state->metadata; new_metadata = drm_property_create_blob(connector->dev, sizeof(mHdrMetaDataValue), &mHdrMetaDataValue); if (IS_ERR(new_metadata)) { DRM_ERROR("create metadata blob fail.\n"); return -EINVAL; } replaced = drm_property_replace_blob(&dptx_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 = (dptx_state->metadata) ? dptx_state->metadata->base.id : 0; } else { return -EINVAL; } return 0; } static void meson_dptx_connector_destroy(struct drm_connector *connector) { drm_connector_unregister(connector); drm_connector_cleanup(connector); } static void meson_dptx_reset(struct drm_connector *connector) { struct dptx_common *dptx_comm; struct meson_tx_state *tx_state; struct meson_dptx_connector_state *dptx_state; dptx_comm = connector_to_dptx_common(connector); dptx_state = kzalloc(sizeof(*dptx_state), GFP_KERNEL); if (!dptx_state) return; if (connector->state) __drm_atomic_helper_connector_destroy_state(connector->state); kfree(connector->state); __drm_atomic_helper_connector_reset(connector, &dptx_state->base); tx_state = &dptx_state->base_state; tx_state->sequence_id = ~0ULL; } struct drm_connector_state *meson_dptx_atomic_duplicate_state (struct drm_connector *connector) { struct meson_tx_state *tx_state, *curr_tx_state; struct meson_dptx_connector_state *new_state; struct meson_dptx_connector_state *curr_state = to_dptx_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); tx_state = &new_state->base_state; curr_tx_state = &curr_state->base_state; tx_state->sequence_id = curr_tx_state->sequence_id; tx_state->para.frac_mode = curr_tx_state->para.frac_mode; new_state->update = false; new_state->color_attr_para.colorformat = curr_state->color_attr_para.colorformat; new_state->color_attr_para.bitdepth = curr_state->color_attr_para.bitdepth; tx_state->hdr_priority = curr_tx_state->hdr_priority; new_state->metadata = curr_state->metadata; return &new_state->base; } static void meson_dptx_atomic_destroy_state(struct drm_connector *connector, struct drm_connector_state *state) { struct meson_dptx_connector_state *dptx_state; dptx_state = to_dptx_connector_state(state); __drm_atomic_helper_connector_destroy_state(&dptx_state->base); kfree(dptx_state); } static void meson_dptx_atomic_print_state(struct drm_printer *p, const struct drm_connector_state *state) { struct drm_connector *connector = state->connector; struct meson_dp_tx *meson_dptx = connector_to_meson_dptx(connector); struct meson_dptx_connector_state *dptx_state = to_dptx_connector_state(state); struct dptx_common *tx_comm = connector_to_dptx_common(connector); struct meson_connector_dev *conn_dev = meson_dptx->dptx_dev; struct meson_tx_dev *tx_dev = to_meson_tx_dev(conn_dev); struct meson_tx_state *tx_state = &dptx_state->base_state; drm_printf(p, "\t\t update:[%d]\n", dptx_state->update); drm_printf(p, "\t\t cs: [%d], cd: [%d]\n", dptx_state->color_attr_para.colorformat, dptx_state->color_attr_para.bitdepth); drm_printf(p, "\t\t avmute:[%d]\n", dptx_state->avmute); drm_printf(p, "\t\t hdr_cap_rx:[%d]\n", get_hdr_info_rx(tx_comm)); drm_printf(p, "\t\t ready:[%d]\n", tx_dev->ready); drm_printf(p, "\t\t VRR_CAP:[%u]\n", dptx_common_get_vrr_cap(tx_comm)); drm_printf(p, "\t\t hdcp_user:[%d]\n", dptx_common_get_hdcp_user_state(tx_comm)); drm_printf(p, "\t\t dc_cap:[%d]\n", get_dc_cap(tx_comm)); drm_printf(p, "\t\t edid_valid:[%d]\n", dptx_common_get_edid_valid_state(tx_comm)); drm_printf(p, "\t\t hdr_priority:[%d]\n", tx_state->hdr_priority); drm_printf(p, "\t\t dptx_hdr_status:[%d]\n", dptx_common_get_hdr_status(tx_comm)); drm_printf(p, "\t\t sink_type:[%d]\n", get_sink_type(tx_comm)); drm_printf(p, "\t\t contenttype_cap:[%d]\n", dptx_common_get_content_types(tx_comm)); drm_printf(p, "\t\t frac_rate_policy:[%d]\n", tx_state->para.frac_mode); } static void help_bist_mode_show(struct drm_printer *p) { int i; static const char * const bist_help[] = { [VENC_BIST_PTTN_OFF] = "off", [VENC_BIST_PTTN_BLACK] = "black", [VENC_BIST_PTTN_WHITE] = "white", [VENC_BIST_PTTN_RED] = "red", [VENC_BIST_PTTN_GREEN] = "green", [VENC_BIST_PTTN_BLUE] = "blue", [VENC_BIST_PTTN_LINE] = "line", [VENC_BIST_PTTN_DOT] = "dot", [VENC_BIST_PTTN_COLORBAR] = "colorbar", [VENC_BIST_PTTN_CROSSING] = "crossing", [VENC_BIST_PTTN_GRAY] = "gray", [VENC_BIST_PTTN_WINDOW] = "window", }; if (!p) return; drm_printf(p, "###### bist_mode help ######\n"); for (i = 0; i < ARRAY_SIZE(bist_help); i++) { if (bist_help[i]) drm_printf(p, " %d: %s\n", i, bist_help[i]); } drm_printf(p, "\n"); } static void help_venc_cfg_show(struct drm_printer *p) { static const char *help_msg = " bit[3] bist type, bit[2] VENC type, bit[1]: venc_index, bit[0]: enable\n" " e.g. echo 1003 > venc_cfg for red pattern\n"; if (!p) return; drm_printf(p, "###### venc_cfg help ######\n"); drm_printf(p, "%s\n", help_msg); } static void help_debug_show(struct drm_printer *p) { static const char *help_msg = " dptx core reg\n" " read: echo rd ADDR > debug\n" " write: echo wr ADDR VAL > debug\n" " dump: echo dumpd ADDR0 ADDR1 > debug\n" " clkctrl reg\n" " read: echo rc ADDR > debug\n" " write: echo wc ADDR VAL > debug\n" " clkmsr\n" " read: echo clkmsr > debug\n" " aux reg\n" " read: echo aux rd ADDR SIZE > debug\n" " write: echo aux wr ADDR SIZE VALUE1 VALUE2 ... > debug\n" " dpcd: echo aux dpcd > debug # DPCD cap and ext_cap\n" " sink_count: echo aux sink_count > debug # DPCD 0x200~0x205\n" " link train\n" " lr: echo lt_force_lr 0x14 > debug # 0x14, 0xa or 0x6\n" " lc: echo lt_force_lc 1 > debug # 1, 2, or 4\n" " ssc: echo lt_force_ssc 1 > debug # 0 or 1\n" " edid\n" " echo load 00FFFFFFFFFFFF00... > debug\n" " hpd\n" " echo hpd_lock 1 > debug # 0 or 1\n" " echo fake_plug 1 > debug # 0 or 1\n"; if (!p) return; drm_printf(p, "###### debug help ######\n"); drm_printf(p, "%s\n", help_msg); } static ssize_t help_show(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, char *buf, loff_t off, size_t count) { struct drm_print_iterator iter; struct drm_printer printer; ssize_t ret; iter.data = buf; iter.start = off; iter.remain = count; printer = drm_coredump_printer(&iter); /* add more sysfs help here */ help_bist_mode_show(&printer); help_debug_show(&printer); help_venc_cfg_show(&printer); ret = count - iter.remain; return ret; } static struct bin_attribute help_attr = { .attr.name = "help", .attr.mode = 0444, .read = help_show, }; static ssize_t venc_cfg_show(struct device *dev, struct device_attribute *attr, char *buf) { return sysfs_emit(buf, "no support show\n"); } /* bit[3] bist type, bit[2] VENC type, bit[1]: venc_index, bit[0]: enable * e.g. echo 1003 > venc_cfg for red pattern */ static ssize_t venc_cfg_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct drm_connector *connector = dev_get_drvdata(dev); struct dptx_common *dptx_comm = connector_to_dptx_common(connector); struct meson_dp_tx *meson_dptx = connector_to_meson_dptx(connector); u32 enc_type = VENC_ENCP; u32 bist_type = VENC_BIST_PTTN_COLORBAR; if (buf[1] == '0' || buf[1] == '1') meson_dptx->enc_idx = buf[1] - '0'; else meson_dptx->enc_idx = 0; if (buf[2] == '0' || buf[2] == '1') enc_type = buf[2] - '0'; else enc_type = VENC_ENCP; if (buf[3]) bist_type = buf[3] - '0'; else bist_type = VENC_BIST_PTTN_COLORBAR; DRM_INFO("cfg venc_idx: %d, cfg venc_type: %s, bist type: %d\n", meson_dptx->enc_idx, enc_type == VENC_ENCL ? "ENCL" : "ENCP", bist_type); if (buf[0] == '1') { meson_venc_mode_set(meson_dptx->venc, meson_dptx->enc_idx, enc_type, bist_type, &dptx_comm->base.sw_fmt_para); DRM_INFO("enable venc\n"); } else if (buf[0] == '0') { meson_venc_mode_disable(meson_dptx->venc, meson_dptx->enc_idx, enc_type); DRM_INFO("disable venc\n"); } else { return -EINVAL; } return count; } static DEVICE_ATTR_RW(venc_cfg); static ssize_t bist_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct drm_connector *connector = dev_get_drvdata(dev); struct meson_dp_tx *meson_dptx = connector_to_meson_dptx(connector); return snprintf(buf, PAGE_SIZE, "%d\n", meson_dptx->bist_mode); } static ssize_t bist_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct drm_connector *connector = dev_get_drvdata(dev); struct dptx_common *dptx_comm = connector_to_dptx_common(connector); struct meson_dp_tx *meson_dptx = connector_to_meson_dptx(connector); u32 val = 0; int ret; ret = kstrtouint(buf, 10, &val); if (ret < 0 || val >= VENC_BIST_PTTN_MAX) return count; DRM_INFO("set bist_mode %d\n", val); meson_dptx->bist_mode = val; meson_venc_bist_mode_set(meson_dptx->venc, meson_dptx->enc_idx, VENC_ENCP, meson_dptx->bist_mode, &dptx_comm->base.sw_fmt_para); return count; } static DEVICE_ATTR_RW(bist_mode); static int meson_dptx_late_register(struct drm_connector *connector) { int ret; struct dptx_common *dptx_comm = connector_to_dptx_common(connector); meson_tx_sysfs_create(&dptx_comm->base); ret = sysfs_create_bin_file(&connector->kdev->kobj, &help_attr); if (ret) DRM_ERROR("Failed to create help node: %d\n", ret); ret = device_create_file(connector->kdev, &dev_attr_venc_cfg); if (ret) DRM_ERROR("Failed to create venc_cfg node: %d\n", ret); ret = device_create_file(connector->kdev, &dev_attr_bist_mode); if (ret) DRM_ERROR("Failed to create bist_mode node: %d\n", ret); return ret; } static void meson_dptx_early_unregister(struct drm_connector *connector) { struct dptx_common *dptx_comm = connector_to_dptx_common(connector); sysfs_remove_bin_file(&connector->kdev->kobj, &help_attr); device_remove_file(connector->kdev, &dev_attr_venc_cfg); device_remove_file(connector->kdev, &dev_attr_bist_mode); meson_tx_sysfs_remove(&dptx_comm->base); } static const struct drm_connector_funcs meson_dptx_connector_funcs = { .detect = meson_dptx_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, .atomic_set_property = meson_dptx_connector_atomic_set_property, .atomic_get_property = meson_dptx_connector_atomic_get_property, .destroy = meson_dptx_connector_destroy, .reset = meson_dptx_reset, .atomic_duplicate_state = meson_dptx_atomic_duplicate_state, .atomic_destroy_state = meson_dptx_atomic_destroy_state, .atomic_print_state = meson_dptx_atomic_print_state, .late_register = meson_dptx_late_register, .early_unregister = meson_dptx_early_unregister, }; static void meson_dptx_set_hdcp_result(struct meson_dp_tx *am_dptx, int result) { struct drm_connector *connector = &am_dptx->base.connector; if (result == HDCP_AUTH_OK) { am_dptx->hdcp_state = DP_HDCP_RESULT_SUCCESS; am_dptx->content_protection = DRM_MODE_CONTENT_PROTECTION_ENABLED; schedule_work(&am_dptx->hdcp_update_work); DRM_DEBUG("hdcp [%d] set result ok.\n", am_dptx->hdcp_mode); } else if (result == HDCP_AUTH_FAIL) { am_dptx->hdcp_state = DP_HDCP_RESULT_FAIL; /*no event needed when fail.*/ DRM_DEBUG("hdcp [%d] set result fail.\n", am_dptx->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) { am_dptx->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; schedule_work(&am_dptx->hdcp_update_work); } } } static int meson_dptx_get_hdcp_request(struct meson_dp_tx *am_dptx, int request_type_mask) { unsigned int hdcp_rx_type = am_dptx->hdcp_rx_type; struct drm_connector *connector = &am_dptx->base.connector; struct dptx_common *tx_comm = connector_to_dptx_common(connector); unsigned int hdcp_tx_type; hdcp_tx_type = drm_dptx_common_get_tx_hdcp_cap(tx_comm); /* * for bootup case, some project not have tee notify to load hdcp key, * need to get rx_cap by dptx api. * for hotplug case, rx_cap is updated in hpd callback. */ if (hdcp_rx_type == 0) hdcp_rx_type = drm_dptx_common_get_rx_hdcp_cap(tx_comm); DRM_INFO("%s usr_type: %d, hdcp cap: %d,%d\n", __func__, request_type_mask, hdcp_tx_type, hdcp_rx_type); return hdcp_tx_type & hdcp_rx_type & request_type_mask; } void meson_dptx_update_hdcp(struct meson_dp_tx *am_dptx, int hdcp_request) { int hdcp_request_mode = HDCP_NULL; int hdcp_request_mask = HDCP_NULL; int hdcp_is_same = 0; struct drm_connector *conn = &am_dptx->base.connector; struct dptx_common *dptx_comm = connector_to_dptx_common(conn); DRM_INFO("meson update hdcp_request:%d\n", hdcp_request); /*Undesired, disable hdcp.*/ if (am_dptx->hdcp_request_content_protection == DRM_MODE_CONTENT_PROTECTION_UNDESIRED) hdcp_request = DP_HDCP_STATE_STOP; if (hdcp_request == DP_HDCP_STATE_STOP || hdcp_request == DP_HDCP_STATE_DISCONNECT) { dptx_common_set_hdcp_mode(dptx_comm, HDCP_NULL, hdcp_request); am_dptx->hdcp_mode = HDCP_NULL; am_dptx->hdcp_state = hdcp_request; meson_dptx_set_hdcp_result(am_dptx, HDCP_AUTH_UNKNOWN); return; } if (am_dptx->hdcp_request_content_type == DRM_MODE_HDCP_CONTENT_TYPE0) hdcp_request_mask = HDCP_MODE14 | HDCP_MODE22; else hdcp_request_mask = HDCP_MODE22; if (am_dptx->hdcp_request_content_type == DRM_MODE_HDCP_CONTENT_TYPE0) { if (am_dptx->hdcp_content_type0_pri) hdcp_request_mask = HDCP_MODE14; } hdcp_request_mode = meson_dptx_get_hdcp_request(am_dptx, hdcp_request_mask); /*mode is same, try to re-use last state*/ if (hdcp_request_mode & 2) { if (am_dptx->hdcp_mode == HDCP_MODE22) hdcp_is_same = 1; } else { if (am_dptx->hdcp_mode == HDCP_MODE14 && (hdcp_request_mode & 1)) hdcp_is_same = 1; } if (hdcp_is_same) { switch (am_dptx->hdcp_state) { case DP_HDCP_STATE_START: DRM_INFO("waiting hdcp result.\n"); return; case DP_HDCP_RESULT_SUCCESS: DRM_INFO("is auth ok.\n"); meson_dptx_set_hdcp_result(am_dptx, HDCP_AUTH_OK); return; case DP_HDCP_RESULT_FAIL: default: DRM_ERROR("meet stopped hdcp stat\n"); break; }; } if (am_dptx->hdcp_force) { am_dptx->hdcp_force = false; DRM_INFO("force hdcp\n"); } else { dptx_common_set_hdcp_mode(dptx_comm, HDCP_NULL, DP_HDCP_STATE_STOP); am_dptx->hdcp_mode = HDCP_NULL; am_dptx->hdcp_state = DP_HDCP_STATE_STOP; } meson_dptx_set_hdcp_result(am_dptx, HDCP_AUTH_UNKNOWN); if (hdcp_request_mode != HDCP_NULL && (hdcp_request == DP_HDCP_STATE_START || hdcp_request == DP_HDCP_FORCE || hdcp_request == DP_HDCP_KEY_READY)) { if (hdcp_request_mode & 2) am_dptx->hdcp_mode = HDCP_MODE22; else am_dptx->hdcp_mode = HDCP_MODE14; am_dptx->hdcp_state = DP_HDCP_STATE_START; dptx_common_set_hdcp_mode(dptx_comm, hdcp_request_mode, hdcp_request); } else { DRM_ERROR("No valid hdcp mode exit, maybe hdcp havenot init.\n"); } } static void meson_dptx_update(struct drm_connector_state *new_state, struct drm_connector_state *old_state) { struct drm_connector *connector = new_state->connector; struct meson_dp_tx *meson_dptx = connector_to_meson_dptx(connector); struct meson_dptx_connector_state *new_dptx_state = to_dptx_connector_state(new_state); int hdcp_request; 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) || new_dptx_state->hdcp_force_st) { /*check hdcp property update*/ meson_dptx->hdcp_request_content_type = new_state->hdcp_content_type; meson_dptx->hdcp_request_content_protection = new_state->content_protection; if (new_dptx_state->hdcp_force_st) { meson_dptx->hdcp_force = true; hdcp_request = DP_HDCP_FORCE; } else { hdcp_request = DP_HDCP_STATE_START; } if (new_state->crtc && !drm_atomic_crtc_needs_modeset(new_state->crtc->state)) meson_dptx_update_hdcp(meson_dptx, hdcp_request); } } static void meson_dptx_hdcp_notify(void *data, int type, int result) { struct meson_dp_tx *am_dptx = (struct meson_dp_tx *)data; struct drm_connector *connector = &am_dptx->base.connector; struct dptx_common *dptx_comm = connector_to_dptx_common(connector); struct drm_modeset_lock *mode_lock = &connector->dev->mode_config.connection_mutex; 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 (meson_tx_get_hpd_state(&dptx_comm->base)) am_dptx->hdcp_rx_type = drm_dptx_common_get_rx_hdcp_cap(dptx_comm); meson_dptx_update_hdcp(am_dptx, DP_HDCP_KEY_READY); goto end; } am_dptx->hdcp_mode = type; if (0) { if (type != am_dptx->hdcp_mode) { DRM_DEBUG("notify type is mismatch[%d]-[%d]\n", type, am_dptx->hdcp_mode); goto end; } } if (result == HDCP_AUTH_OK) meson_dptx_set_hdcp_result(am_dptx, HDCP_AUTH_OK); else if (result == HDCP_AUTH_FAIL) meson_dptx_set_hdcp_result(am_dptx, HDCP_AUTH_FAIL); else DRM_ERROR("HDCP report unknown result [%d-%d]\n", type, result); meson_drm_kms_helper_hdcp_event(connector, type, result); end: drm_modeset_unlock(mode_lock); } static void meson_dptx_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_meson_crtc *amcrtc = to_am_meson_crtc(crtc_state->crtc); struct meson_dp_tx *meson_dptx = encoder_to_meson_dptx(encoder); struct meson_connector_dev *tx_dev = meson_dptx->dptx_dev; struct drm_display_mode *adj_mode = &crtc_state->adjusted_mode; char *modename = adj_mode->name; update_curr_vout_server(amcrtc->base.index + 1, tx_dev->vout_serv); meson_crtc_state->viu_mux = vout_func_get_viu_mux(amcrtc->vout_index, tx_dev->vout_serv, modename); } static void meson_dptx_encoder_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct drm_connector_state *conn_state, *old_conn_state; struct meson_dptx_connector_state *dptx_conn_state, *old_dptx_conn_state; struct drm_crtc_state *crtc_state; struct am_meson_crtc_state *meson_crtc_state; struct am_meson_crtc *amcrtc; struct drm_display_mode *mode; struct meson_dp_tx *meson_dptx = encoder_to_meson_dptx(encoder); struct meson_connector_dev *conn_dev = meson_dptx->dptx_dev; struct meson_tx_dev *tx_dev = to_meson_tx_dev(conn_dev); struct drm_connector *conn = &meson_dptx->base.connector; conn_state = drm_atomic_get_new_connector_state(state, conn); dptx_conn_state = to_dptx_connector_state(conn_state); old_conn_state = drm_atomic_get_old_connector_state(state, conn); old_dptx_conn_state = to_dptx_connector_state(old_conn_state); amcrtc = to_am_meson_crtc(conn_state->crtc); crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); mode = &crtc_state->adjusted_mode; meson_crtc_state = to_am_meson_crtc_state(crtc_state); meson_dptx_update_hdcp(meson_dptx, DP_HDCP_STATE_START); meson_vout_notify_mode_change(amcrtc->vout_index, meson_crtc_state->vmode, EVENT_MODE_SET_START); /* assume hdmi_if_index = venc_idx by default */ meson_tx_build_clk_param(tx_dev, &dptx_conn_state->base_state.para); meson_venc_mode_set(meson_dptx->venc, meson_dptx->enc_idx, VENC_ENCP, meson_dptx->bist_mode, &dptx_conn_state->base_state.para); meson_tx_do_mode_setting(tx_dev, &dptx_conn_state->base_state, &old_dptx_conn_state->base_state); meson_vout_notify_mode_change(amcrtc->vout_index, meson_crtc_state->vmode, EVENT_MODE_SET_FINISH); meson_vout_update_mode_name(amcrtc->vout_index, mode->name, "dptx"); } static void meson_dptx_encoder_atomic_disable(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct am_meson_crtc *amcrtc; struct drm_connector_state *old_conn_state; struct meson_dp_tx *meson_dptx = encoder_to_meson_dptx(encoder); struct drm_connector *conn = &meson_dptx->base.connector; DRM_INFO("[%s]\n", __func__); old_conn_state = drm_atomic_get_old_connector_state(state, conn); amcrtc = to_am_meson_crtc(old_conn_state->crtc); meson_venc_mode_disable(meson_dptx->venc, meson_dptx->enc_idx, VENC_ENCP); meson_dptx_update_hdcp(meson_dptx, DP_HDCP_STATE_STOP); } static enum drm_mode_status meson_dptx_encoder_mode_valid(struct drm_encoder *crtc, const struct drm_display_mode *mode) { return MODE_OK; } static int meson_dptx_encoder_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { int ret = 0; bool is_alter; struct tx_timing *timing; struct dptx_common *dptx_comm = connector_to_dptx_common(conn_state->connector); struct am_meson_crtc_state *meson_crtc_state = to_am_meson_crtc_state(crtc_state); struct drm_display_mode *adj_mode = &crtc_state->adjusted_mode; struct meson_dptx_connector_state *dptx_state = to_dptx_connector_state(conn_state); struct meson_tx_state *tx_state = &dptx_state->base_state; struct tx_color_attr *attr = &dptx_state->color_attr_para; dptx_state = to_dptx_connector_state(conn_state); is_alter = meson_tx_is_alter_mode(adj_mode); if (is_alter) { tx_state->para.frac_mode = true; meson_crtc_state->frac = 1; } else { tx_state->para.frac_mode = false; meson_crtc_state->frac = 0; } timing = kzalloc(sizeof(*timing), GFP_KERNEL); if (!timing) return -ENOMEM; meson_drm_mode_build_tx_timing(adj_mode, timing); meson_tx_format_para_init(&dptx_comm->base, timing, tx_state->para.frac_mode, attr->colorformat, bitdepth_to_colordepth(attr->bitdepth), 0, &tx_state->para); ret = meson_tx_validate_mode(&dptx_comm->base, tx_state); if (!ret) meson_crtc_state->preset_vmode = VMODE_DPTX; else DRM_ERROR("validate fail %d\n", ret); kfree(timing); return ret; } static const struct drm_encoder_helper_funcs meson_dptx_encoder_helper_funcs = { .atomic_mode_set = meson_dptx_encoder_atomic_mode_set, .atomic_enable = meson_dptx_encoder_atomic_enable, .atomic_disable = meson_dptx_encoder_atomic_disable, .atomic_check = meson_dptx_encoder_atomic_check, .mode_valid = meson_dptx_encoder_mode_valid, }; static const struct drm_encoder_funcs meson_dptx_encoder_funcs = { .destroy = drm_encoder_cleanup, }; static void meson_dptx_hdcp_work_func(struct work_struct *work) { struct meson_dp_tx *am_dptx = container_of(work, struct meson_dp_tx, hdcp_update_work); struct drm_connector *connector = &am_dptx->base.connector; struct drm_modeset_lock *mode_lock = &connector->dev->mode_config.connection_mutex; drm_modeset_lock(mode_lock, NULL); drm_hdcp_update_content_protection(connector, am_dptx->content_protection); drm_modeset_unlock(mode_lock); } static void meson_dptx_hpd_cb(void *data) { struct meson_connector *meson_con = (struct meson_connector *)data; struct drm_connector *connector = &meson_con->connector; struct meson_dp_tx *meson_dptx = connector_to_meson_dptx(connector); struct dptx_common *tx_comm = connector_to_dptx_common(connector); struct drm_modeset_lock *mode_lock = &connector->dev->mode_config.connection_mutex; DRM_INFO("drm dptx hpd notify\n"); if (!meson_tx_get_hpd_state(&tx_comm->base)) { drm_modeset_lock(mode_lock, NULL); meson_dptx_update_hdcp(meson_dptx, DP_HDCP_STATE_DISCONNECT); drm_modeset_unlock(mode_lock); } meson_drm_kms_helper_hotplug_event(connector, meson_tx_get_hpd_state(&tx_comm->base)); } struct meson_range_prop_info { u64 init_val; u64 min; u64 max; u32 flags; char *prop_name; }; static struct meson_range_prop_info range_props[] = { {0, 0, 1, 0, "hdcp_user"}, {0, 0, 64, 0, "hdr_cap_rx"}, {0, 0, 1 << COLOR_MAX_ATTR, 0, "dc_cap"}, {0, 0, 1023, 0, "contenttype_cap"}, {0, 0, 1, 0, "UPDATE"}, {0, 0, 1, 0, "MESON_DRM_DPTX_PROP_AVMUTE"}, {0, 0, 1, 0, "ready"}, {0, 0, 1, 0, "FRAC_RATE_POLICY"}, {0, 0, 1, 0, "edid_valid"}, {0, 0, 8, 0, "sink_type"}, }; static void meson_dptx_create_range_property(struct drm_device *drm_dev, struct meson_dp_tx *meson_dptx) { int i; struct meson_range_prop_info *info; struct drm_property *prop; struct drm_property **props = &meson_dptx->hdcp_user_prop; for (i = 0; i < ARRAY_SIZE(range_props); i++, props++) { info = &range_props[i]; prop = drm_property_create_range(drm_dev, info->flags, info->prop_name, info->min, info->max); if (prop) { *(props) = prop; drm_object_attach_property(&meson_dptx->base.connector.base, prop, info->init_val); } else { DRM_ERROR("Failed to %s property\n", info->prop_name); } } } static const struct drm_prop_enum_list dptx_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_dptx_init_dptx_hdr_status_property(struct drm_device *drm_dev, struct meson_dp_tx *meson_dptx) { struct drm_property *prop; prop = drm_property_create_enum(drm_dev, 0, "dptx_hdr_status", dptx_hdr_status_enum_list, ARRAY_SIZE(dptx_hdr_status_enum_list)); if (prop) { meson_dptx->dptx_hdr_status_prop = prop; drm_object_attach_property(&meson_dptx->base.connector.base, prop, 0); } else { DRM_ERROR("Failed to dptx_hdr_status property\n"); } } /* Optional colorspace properties. */ static const struct drm_prop_enum_list 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_dptx_init_colorspace_property(struct drm_device *drm_dev, struct meson_dp_tx *meson_dptx, struct meson_tx_state *state) { struct drm_property *prop; struct dptx_common *dptx_comm; int colorformat = state->para.cs; dptx_comm = conn_to_dptx_common(meson_dptx->dptx_dev); prop = drm_property_create_enum(drm_dev, 0, "color_space", color_space_enum_list, ARRAY_SIZE(color_space_enum_list)); if (prop) { meson_dptx->color_space_prop = prop; drm_object_attach_property(&meson_dptx->base.connector.base, prop, colorformat); } else { DRM_ERROR("Failed to color_space property\n"); } } static void meson_dptx_init_colordepth_property(struct drm_device *drm_dev, struct meson_dp_tx *meson_dptx, struct meson_tx_state *state) { struct drm_property *prop; struct dptx_common *dptx_comm; int bitdepth = state->para.cd; dptx_comm = conn_to_dptx_common(meson_dptx->dptx_dev); prop = drm_property_create_range(drm_dev, 0, "color_depth", 0, 16); if (prop) { meson_dptx->color_depth_prop = prop; drm_object_attach_property(&meson_dptx->base.connector.base, prop, colordepth_to_bitdepth(bitdepth)); } else { DRM_ERROR("Failed to color_depth property\n"); } } static void meson_dptx_init_hdcp_content_type0_pri_property(struct drm_device *drm_dev, struct meson_dp_tx *meson_dptx) { struct drm_property *prop; prop = drm_property_create_bool(drm_dev, 0, "HDCP_CONTENT_TYPE0_PRIORITY"); if (prop) { meson_dptx->hdcp_content_type0_pri_prop = prop; drm_object_attach_property(&meson_dptx->base.connector.base, prop, 0); } else { DRM_ERROR("Failed to HDCP_CONTENT_TYPE0_PRIORITY property\n"); } } static void meson_dptx_init_hdcp_content_type0_pri_store_property(struct drm_device *drm_dev, struct meson_dp_tx *meson_dptx) { struct drm_property *prop; prop = drm_property_create_bool(drm_dev, 0, "HDCP_CONTENT_TYPE0_PRI_STORE"); if (prop) { meson_dptx->hdcp_content_type0_pri_store_prop = prop; drm_object_attach_property(&meson_dptx->base.connector.base, prop, 0); } else { DRM_ERROR("Failed to HDCP_CONTENT_TYPE0_PRI_STORE property\n"); } } static void meson_hdmitx_init_hdcp_mode_property(struct drm_device *drm_dev, struct meson_dp_tx *meson_dptx) { struct drm_property *prop; prop = drm_property_create_range(drm_dev, 0, "hdcp_mode", 0, 36); if (prop) { meson_dptx->hdcp_mode_prop = prop; drm_object_attach_property(&meson_dptx->base.connector.base, prop, 0); } else { DRM_ERROR("Failed to hdcp_mode property\n"); } } static void meson_dptx_init_hdr_priority_property(struct drm_device *drm_dev, struct meson_dp_tx *meson_dptx) { struct drm_property *prop; u32 hdr_priority; struct dptx_common *dptx_comm = conn_to_dptx_common(meson_dptx->dptx_dev); dptx_get_hdr_priority(dptx_comm, &hdr_priority); prop = drm_property_create_range(drm_dev, 0, "hdr_priority", 0, UINT_MAX); if (prop) { meson_dptx->hdr_priority_prop = prop; drm_object_attach_property(&meson_dptx->base.connector.base, prop, hdr_priority); } else { DRM_ERROR("Failed to allm property\n"); } } static void meson_dptx_init_static_meta_property(struct drm_device *drm_dev, struct meson_dp_tx *meson_dptx) { struct drm_property *prop; prop = drm_property_create(drm_dev, DRM_MODE_PROP_BLOB, "HDR_STATIC_META", 0); if (prop) { meson_dptx->static_meta_prop = prop; drm_object_attach_property(&meson_dptx->base.connector.base, prop, 0); } else { DRM_ERROR("Failed to create hdr static metadata property\n"); } } static int meson_drm_probe_venc(struct meson_dp_tx *meson_dptx, struct device *dev) { struct platform_device *venc_pdev; struct device_node *venc_node; venc_node = of_parse_phandle(dev->of_node, "tx_venc", 0); if (!venc_node) { dev_err(dev, "cannot find venc device\n"); return -ENXIO; } venc_pdev = of_find_device_by_node(venc_node); if (venc_pdev) { meson_dptx->venc = platform_get_drvdata(venc_pdev); meson_dptx->venc_pdev = venc_pdev; } of_node_put(venc_node); if (!venc_pdev) { dev_err(dev, "%s: venc driver is not ready\n", __func__); return -EPROBE_DEFER; } if (!meson_dptx->venc) { put_device(&venc_pdev->dev); dev_err(dev, "%s: venc driver is not ready\n", __func__); return -EPROBE_DEFER; } return 0; } static void meson_dptx_parse_venc_idx(struct device *dev, u32 *enc_idx) { int ret; ret = of_property_read_u32(dev->of_node, "enc_idx", enc_idx); if (ret) DRM_ERROR("enc_idx invalid\n"); } int meson_dptx_dev_bind(struct drm_device *drm, int type, struct meson_connector_dev *intf) { int ret; struct meson_drm *priv = drm->dev_private; struct meson_dp_tx *meson_dptx; struct meson_tx_dev *tx_dev; struct drm_connector *connector; struct drm_encoder *encoder; struct meson_connector *mesonconn; struct dptx_common *tx_comm; int encoder_type = DRM_MODE_ENCODER_TMDS; struct connector_hpd_cb hpd_cb; struct meson_tx_state *tx_state; int connector_type = type; char *connector_name = NULL; struct drm_property *type_prop = NULL; struct connector_hdcp_cb hdcp_cb; int hdcp_ctl_lvl; meson_dptx = devm_kzalloc(drm->dev, sizeof(*meson_dptx), GFP_KERNEL); if (!meson_dptx) return -ENOMEM; meson_dptx->dptx_dev = intf; tx_dev = to_meson_tx_dev(intf); tx_comm = to_dptx_common(tx_dev); hdcp_ctl_lvl = tx_comm->hdcptx_comm.hdcp_ctl_lvl; DRM_DEBUG("hdcp_ctl_lvl=%d\n", hdcp_ctl_lvl); if (hdcp_ctl_lvl == 0) { meson_dptx->hdcp_request_content_type = DRM_MODE_HDCP_CONTENT_TYPE0; meson_dptx->hdcp_request_content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; } else if (hdcp_ctl_lvl == 1) { meson_dptx->hdcp_request_content_type = DRM_MODE_HDCP_CONTENT_TYPE0; meson_dptx->hdcp_request_content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; } else if (hdcp_ctl_lvl == 2) { meson_dptx->hdcp_request_content_type = DRM_MODE_HDCP_CONTENT_TYPE0; meson_dptx->hdcp_request_content_protection = DRM_MODE_CONTENT_PROTECTION_UNDESIRED; } else { DRM_ERROR("no HDCP func registered.\n"); } mesonconn = &meson_dptx->base; mesonconn->drm_priv = priv; mesonconn->update = meson_dptx_update; mesonconn->connector_type = type; mesonconn->data = tx_dev; meson_drm_connector_event_init(mesonconn); encoder = &meson_dptx->encoder; connector = &meson_dptx->base.connector; intf->conn = connector; meson_dptx->dptx_type = type; INIT_WORK(&meson_dptx->hdcp_update_work, meson_dptx_hdcp_work_func); DRM_INFO("[%s]: type = %d\n", __func__, type); switch (type) { case DRM_MODE_CONNECTOR_MESON_TX_DP_A: connector_type = DRM_MODE_CONNECTOR_DisplayPort; connector_name = "DP-A"; break; case DRM_MODE_CONNECTOR_MESON_TX_DP_B: connector_type = DRM_MODE_CONNECTOR_DisplayPort; connector_name = "DP-B"; break; case DRM_MODE_CONNECTOR_MESON_TX_DP_C: connector_type = DRM_MODE_CONNECTOR_DisplayPort; connector_name = "DP-C"; break; default: connector_type = DRM_MODE_CONNECTOR_Unknown; encoder_type = DRM_MODE_ENCODER_NONE; break; }; intf->connector_type = type; meson_drm_probe_venc(meson_dptx, priv->dev); meson_dptx_parse_venc_idx(tx_dev->pdev, &meson_dptx->enc_idx); /* Connector */ connector->polled = DRM_CONNECTOR_POLL_HPD; connector->ycbcr_420_allowed = true; drm_connector_helper_add(connector, &meson_dptx_connector_helper_funcs); ret = drm_connector_init(drm, connector, &meson_dptx_connector_funcs, connector_type); if (ret) { dev_err(priv->dev, "Failed to init dp tx connector\n"); return ret; } /*update name to amlogic name*/ if (connector_name) { kfree(connector->name); connector->name = kasprintf(GFP_KERNEL, "%s", connector_name); if (!connector->name) DRM_ERROR("alloc name failed\n"); } /* Encoder */ encoder->possible_crtcs = priv->of_conf.crtc_masks[ENCODER_HDMI]; drm_encoder_helper_add(encoder, &meson_dptx_encoder_helper_funcs); ret = drm_encoder_init(drm, encoder, &meson_dptx_encoder_funcs, encoder_type, "dptx_encoder"); if (ret) { dev_err(priv->dev, "Failed to init hdmi encoder\n"); return ret; } drm_connector_attach_encoder(connector, encoder); /* register hpd callback */ hpd_cb.callback = meson_dptx_hpd_cb; hpd_cb.data = mesonconn; meson_tx_register_hpd_cb(tx_dev, &hpd_cb); /* register connector sysfs device translate to meson_tx_dev */ dptx_register_dev_to_txdev_xlate(tx_dev, conn_dev_to_meson_tx_dev); /* init dptx property */ tx_state = kzalloc(sizeof(*tx_state), GFP_KERNEL); if (!tx_state) return -ENOMEM; /*hdcp init, default is disable state*/ drm_connector_attach_content_protection_property(connector, true); hdcp_cb.hdcp_notify = meson_dptx_hdcp_notify; hdcp_cb.data = meson_dptx; drm_dptx_common_register_hdcp_notify(tx_comm, &hdcp_cb); meson_dptx->hdcp_mode = HDCP_NULL; meson_dptx->hdcp_state = DP_HDCP_STATE_DISCONNECT; meson_dptx->bist_mode = VENC_BIST_PTTN_OFF; drm_connector_attach_content_type_property(connector); drm_connector_attach_vrr_capable_property(connector); meson_tx_get_init_state(tx_dev, tx_state); meson_dptx_init_dptx_hdr_status_property(drm, meson_dptx); meson_dptx_init_colordepth_property(drm, meson_dptx, tx_state); meson_dptx_init_colorspace_property(drm, meson_dptx, tx_state); meson_dptx_init_hdcp_content_type0_pri_property(drm, meson_dptx); meson_dptx_init_hdcp_content_type0_pri_store_property(drm, meson_dptx); meson_hdmitx_init_hdcp_mode_property(drm, meson_dptx); meson_dptx_create_range_property(drm, meson_dptx); meson_dptx_init_static_meta_property(drm, meson_dptx); meson_dptx_init_hdr_priority_property(drm, meson_dptx); /*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) { meson_dptx->type_prop = type_prop; drm_object_attach_property(&meson_dptx->base.connector.base, type_prop, type); } else { DRM_ERROR("Failed to create property %s\n", MESON_CONNECTOR_TYPE_PROP_NAME); } kfree(tx_state); return 0; } int meson_dptx_dev_unbind(struct drm_device *drm, int type, struct meson_connector_dev *intf) { struct drm_connector *connector = intf->conn; struct meson_dp_tx *meson_dptx = connector_to_meson_dptx(connector); cancel_work_sync(&meson_dptx->hdcp_update_work); return 0; }