| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (c) 2019 MediaTek Inc. |
| */ |
| |
| #include <drm/drm_atomic_helper.h> |
| #include <drm/drm_connector.h> |
| #include <drm/drm_crtc_helper.h> |
| #include <drm/drm_probe_helper.h> |
| #include <drm/drm_hdcp.h> |
| |
| #include <media/cec.h> |
| |
| #include "mtk_drm_ddp.h" |
| #include "mtk_drm_drv.h" |
| #include "mtk_drm_gem.h" |
| #include "mtk_drm_fb.h" |
| #include "mtk_hdmi.h" |
| #include "hdmictrl.h" |
| #include "hdmi_ctrl.h" |
| #include "hdmihdcp.h" |
| |
| struct drm_device *hdmi_drm_dev; |
| struct drm_connector *hdmi_drm_connector; |
| struct drm_connector_state *hdmi_drm_state; |
| struct drm_encoder *hdmi_encoder; |
| struct drm_display_mode mtk_drm_hdmi_mode; |
| struct drm_property *prop_hdcp_status; |
| struct drm_property *prop_hdcp_tx_ver; |
| struct drm_property *prop_hdcp_rx_ver; |
| |
| static const struct drm_display_mode mtk_drm_hdmi_default_mode = { |
| .clock = 1500, |
| .hdisplay = 1920, |
| .hsync_start = 2008, |
| .hsync_end = 2052, |
| .htotal = 2250, |
| .vdisplay = 1080, |
| .vsync_start = 1084, |
| .vsync_end = 1089, |
| .vtotal = 1125, |
| }; |
| |
| #define MAX_VIC 120 |
| static int timing; |
| static int vic2timing[MAX_VIC] = { |
| 0xff,// 1 640x480 59.94 |
| 0x02,// 2 720x480 59.94 |
| 0x02,// 3 720x480 59.94 |
| 0x04,// 4 1280x720 60 |
| 0x06,// 5 1920x1080 60 int |
| 0x00,// 6 1440x480 59.94 int |
| 0x00,// 7 1440x480 59.94 int |
| 0xff,// 8 1440x240 60.054 |
| 0xff,// 9 1440x240 60.054 |
| 0x00,// 10 2880x480 59.94 int |
| 0x00,// 11 2880x480 59.94 int |
| 0xff,// 12 2880x240 60.054 |
| 0xff,// 13 2880x240 60.054 |
| 0x02,// 14 1440x480 59.94 |
| 0x02,// 15 1440x480 59.94 |
| 0x0d,// 16 1920x1080 60 |
| 0x03,// 17 720x576 50 |
| 0x03,// 18 720x576 50 |
| 0x05,// 19 1280x720 50 |
| 0x07,// 20 1920x1080 50 int |
| 0x01,// 21 1440x576 50 int |
| 0x01,// 22 1440x576 50 int |
| 0xff,// 23 1440x288 50 |
| 0xff,// 24 1440x288 50 |
| 0x01,// 25 2880x576 50 int |
| 0x01,// 26 2880x576 50 int |
| 0xff,// 27 2880x288 50 |
| 0xff,// 28 2880x288 50 |
| 0x03,// 29 1440x576 50 |
| 0x03,// 30 1440x576 50 |
| 0x0e,// 31 1920x1080 50 |
| 0x0a,// 32 1920x1080 24 |
| 0x09,// 33 1920x1080 25 |
| 0x08,// 34 1920x1080 30 |
| 0x02,// 35 2880x480 59.94 |
| 0x02,// 36 2880x480 59.94 |
| 0x03,// 37 2880x576 50 |
| 0x03,// 38 2880x576 50 |
| 0x07,// 39 1920x1080 50 int |
| 0x07,// 40 1920x1080 100 int |
| 0x05,// 41 1280x720 100 |
| 0x03,// 42 720x576 100 |
| 0x03,// 43 720x576 100 |
| 0x01,// 44 1440x576 100 int |
| 0x01,// 45 1440x576 100 int |
| 0x06,// 46 1920x1080 120 int |
| 0x04,// 47 1280x720 120 |
| 0x02,// 48 720x480 119.88 |
| 0x02,// 49 720x480 119.88 |
| 0x00,// 50 1440x480 119.88 int |
| 0x00,// 51 1440x480 119.88 int |
| 0x03,// 52 720x576 200 |
| 0x03,// 53 720x576 200 |
| 0x01,// 54 1440x576 200 int |
| 0x01,// 55 1440x576 200 int |
| 0x02,// 56 720x480 239.76 |
| 0x02,// 57 720x480 239.76 |
| 0x00,// 58 1440x480 239.76 int |
| 0x00,// 59 1440x480 239.76 int |
| 0xff,// 60 1280x720 24 |
| 0xff,// 61 1280x720 25 |
| 0xff,// 62 1280x720 30 |
| 0x0d,// 63 1920x1080 120 |
| 0x0e,// 64 1920x1080 100 |
| 0xff,// 65 1280x720 24 |
| 0xff,// 66 1280x720 25 |
| 0xff,// 67 1280x720 30 |
| 0x05,// 68 1280x720 50 |
| 0x04,// 69 1280x720 60 |
| 0x05,// 70 1280x720 100 |
| 0x04,// 71 1280x720 120 |
| 0x0a,// 72 1920x1080 24 |
| 0x09,// 73 1920x1080 25 |
| 0x08,// 74 1920x1080 30 |
| 0x0e,// 75 1920x1080 50 |
| 0x0d,// 76 1920x1080 60 |
| 0x0e,// 77 1920x1080 100 |
| 0x0d,// 78 1920x1080 120 |
| 0xff,// 79 1680x720 24 |
| 0xff,// 80 1680x720 25 |
| 0xff,// 81 1680x720 30 |
| 0xff,// 82 1680x720 50 |
| 0xff,// 83 1680x720 60 |
| 0xff,// 84 1680x720 100 |
| 0xff,// 85 1680x720 120 |
| 0xff,// 86 2560x1080 24 |
| 0xff,// 87 2560x1080 25 |
| 0xff,// 88 2560x1080 30 |
| 0xff,// 89 2560x1080 50 |
| 0xff,// 90 2560x1080 60 |
| 0xff,// 91 2560x1080 100 |
| 0xff,// 92 2560x1080 120 |
| 0x16,// 93 3840x2160 24 |
| 0x17,// 94 3840x2160 25 |
| 0x19,// 95 3840x2160 30 |
| 0x1c,// 96 3840x2160 50 |
| 0x1b,// 97 3840x2160 60 |
| 0x1a,// 98 4090x2160 24 |
| 0xff,// 99 4090x2160 25 |
| 0xff,// 100 4090x2160 30 |
| 0x1e,// 101 4090x2160 50 |
| 0x1d,// 102 4090x2160 60 |
| 0x16,// 103 3840x2160 24 |
| 0x17,// 104 3840x2160 25 |
| 0x19,// 105 3840x2160 30 |
| 0x1e,// 106 3840x2160 50 |
| 0x1d,// 107 3840x2160 60 |
| 0xff,// 108 1280x720 48 |
| 0xff,// 109 1280x720 48 |
| 0xff,// 110 1680x720 48 |
| 0xff,// 111 1920x1080 48 |
| 0xff,// 112 1920x1080 48 |
| 0xff,// 113 2560x1080 48 |
| 0xff,// 114 3840x2160 48 |
| 0xff,// 115 4096x2160 48 |
| 0xff,// 116 3840x2160 48 |
| 0xff,// 117 3840x2160 100 |
| 0xff,// 118 3840x2160 120 |
| 0xff,// 119 3840x2160 100 |
| 0xff // 120 3840x2160 120 |
| }; |
| |
| u32 mtk_hdmi_display_mode_to_hdmi_timing(struct drm_display_mode *mode) |
| { |
| struct hdmi_avi_infoframe frame; |
| u32 hdmi_timing; |
| |
| DRM_DEBUG_DRIVER("\n"); |
| |
| frame.video_code = drm_match_cea_mode(mode); |
| if (frame.video_code < 1 || frame.video_code > MAX_VIC) { |
| pr_info("video_code valied %d\n", __LINE__); |
| return 0xff; |
| } |
| |
| hdmi_timing = vic2timing[frame.video_code-1]; |
| |
| DRM_DEBUG_DRIVER("VIC %d; timing 0x%x\n", frame.video_code, hdmi_timing); |
| |
| return hdmi_timing; |
| } |
| |
| void mtk_hdmi_notify_hpd_irq_event(void) |
| { |
| pr_info("%s 0x%p\n", __func__, hdmi_drm_dev); |
| if (hdmi_drm_dev) |
| drm_helper_hpd_irq_event(hdmi_drm_dev); |
| } |
| |
| static void mtk_hdmi_encoder_destroy(struct drm_encoder *encoder) |
| { |
| DRM_DEBUG_DRIVER("\n"); |
| |
| drm_encoder_cleanup(encoder); |
| } |
| |
| static bool mtk_hdmi_encoder_mode_fixup(struct drm_encoder *encoder, |
| const struct drm_display_mode *mode, |
| struct drm_display_mode *adjusted_mode) |
| { |
| DRM_DEBUG_DRIVER("\n"); |
| |
| return true; |
| } |
| |
| static void mtk_hdmi_encoder_mode_set(struct drm_encoder *encoder, |
| struct drm_display_mode *mode, |
| struct drm_display_mode *adjusted_mode) |
| { |
| struct hdmi_avi_infoframe frame; |
| |
| DRM_DEBUG_DRIVER("\n"); |
| pr_info("mode/adjust hactive %d/%d\n", mode->hdisplay, adjusted_mode->hdisplay); |
| pr_info("mode/adjust vactive %d/%d\n", mode->vdisplay, adjusted_mode->vdisplay); |
| pr_info("mode/adjust clock %d/%d\n", mode->clock, adjusted_mode->clock); |
| |
| frame.video_code = drm_match_cea_mode(mode); |
| if (frame.video_code < 1 || frame.video_code > MAX_VIC) { |
| pr_info("video_code valied %d\n", __LINE__); |
| return; |
| } |
| |
| timing = vic2timing[frame.video_code-1]; |
| |
| pr_info("VIC %d; timing 0x%x\n", frame.video_code, timing); |
| |
| drm_mode_copy(&mtk_drm_hdmi_mode, adjusted_mode); |
| } |
| |
| static void mtk_hdmi_encoder_disable(struct drm_encoder *encoder) |
| { |
| DRM_DEBUG_DRIVER("\n"); |
| } |
| |
| static void mtk_hdmi_encoder_enable(struct drm_encoder *encoder) |
| { |
| // int ret = 0; |
| |
| DRM_DEBUG_DRIVER("\n"); |
| pr_info("%s %d\n", __func__, timing); |
| |
| hdmi_video_config(timing); |
| /* |
| * drm_connector_set_link_status_property(hdmi_drm_connector, |
| * DRM_MODE_LINK_STATUS_GOOD); |
| */ |
| |
| /* |
| * hdmi_enablehdcp(0x1); |
| * ret = drm_connector_attach_content_protection_property(hdmi_drm_connector, |
| * hdcp2_version_flag); |
| * if (ret) { |
| * pr_info("drm_connector_attach_content_protection_property error\n"); |
| * } |
| */ |
| } |
| |
| static int mtk_hdmi_atomic_check(struct drm_encoder *encoder, |
| struct drm_crtc_state *crtc_state, |
| struct drm_connector_state *conn_state) |
| { |
| return 0; |
| } |
| |
| static enum drm_connector_status mtk_hdmi_connector_detect( |
| struct drm_connector *connector, bool force) |
| { |
| enum drm_connector_status hdmi_plug_status; |
| |
| DRM_DEBUG_DRIVER("\n"); |
| if (hdmi_hotplugstate == HDMI_STATE_HOT_PLUG_OUT) |
| hdmi_plug_status = connector_status_disconnected; |
| else |
| hdmi_plug_status = connector_status_connected; |
| |
| pr_info("%s %d %d\n", |
| __func__, |
| hdmi_hotplugstate, |
| hdmi_plug_status); |
| return hdmi_plug_status; |
| } |
| |
| static int mtk_hdmi_connector_get_modes(struct drm_connector *connector) |
| { |
| int ret; |
| struct edid *edid = NULL; |
| |
| DRM_DEBUG_DRIVER("\n"); |
| pr_info("%s edid %d\n", __func__, sizeof(struct edid)); |
| |
| edid = (struct edid *)drm_bEdidData; |
| |
| drm_connector_update_edid_property(connector, edid); |
| ret = drm_add_edid_modes(connector, edid); |
| |
| return ret; |
| } |
| |
| static struct drm_encoder *mtk_hdmi_connector_best_encoder( |
| struct drm_connector *connector) |
| { |
| return hdmi_encoder; |
| } |
| |
| enum drm_mode_status mtk_hdmi_connector_mode_valid(struct drm_connector *connector, |
| struct drm_display_mode *mode) |
| { |
| struct hdmi_avi_infoframe frame; |
| |
| DRM_DEBUG_DRIVER("\n"); |
| |
| if (mode == NULL) |
| return -1; |
| |
| frame.video_code = drm_match_cea_mode(mode); |
| |
| if ((frame.video_code < 1) || (frame.video_code > MAX_VIC)) { |
| pr_info("video_code valied %d\n", __LINE__); |
| return MODE_NOMODE; |
| } |
| |
| timing = vic2timing[frame.video_code-1]; |
| if ((timing == HDMI_VIDEO_1920x1080p_24Hz) && (mode->clock != 74250)) |
| timing = HDMI_VIDEO_1920x1080p_23Hz; |
| else if ((timing == HDMI_VIDEO_1920x1080p_30Hz) && (mode->clock != 74250)) |
| timing = HDMI_VIDEO_1920x1080p_29Hz; |
| else if ((timing == HDMI_VIDEO_3840x2160P_24HZ) && (mode->clock != 297000)) |
| timing = HDMI_VIDEO_3840x2160P_23_976HZ; |
| else if ((timing == HDMI_VIDEO_3840x2160P_30HZ) && (mode->clock != 297000)) |
| timing = HDMI_VIDEO_3840x2160P_29_97HZ; |
| else if ((timing == HDMI_VIDEO_1280x720p_60Hz) && (mode->clock != 74250)) |
| timing = HDMI_VIDEO_1280x720p_59_94Hz; |
| else if ((timing == HDMI_VIDEO_1920x1080p_60Hz) && (mode->clock != 148500)) |
| timing = HDMI_VIDEO_1920x1080p_59_94Hz; |
| else if ((timing == HDMI_VIDEO_3840x2160P_60HZ) && (mode->clock != 594000)) |
| timing = HDMI_VIDEO_3840x2160P_59_94HZ; |
| else if ((timing == HDMI_VIDEO_4096x2160P_60HZ) && (mode->clock != 594000)) |
| timing = HDMI_VIDEO_4096x2160P_59_94HZ; |
| pr_info("mode valied VIC %d; timing 0x%x\n", frame.video_code, timing); |
| |
| if (mode && ((timing == 0xff) || (frame.video_code == 0x0) || (frame.video_code > 120))) |
| return MODE_NOMODE; |
| |
| pr_info("mode valied\n"); |
| |
| return MODE_OK; |
| } |
| |
| void mtk_attach_colorspace_property(struct drm_connector *connector) |
| { |
| if (!drm_mode_create_hdmi_colorspace_property(connector)) |
| drm_object_attach_property(&connector->base, |
| connector->colorspace_property, 0); |
| } |
| |
| static const struct drm_prop_enum_list hdcp_status_props[] = { |
| { dsHDCP_STATUS_UNPOWERED, "HDCP_UNPWR"}, |
| { dsHDCP_STATUS_UNAUTHENTICATED, "HDCP_UNAUTH"}, |
| { dsHDCP_STATUS_AUTHENTICATED, "HDCP_AUTH_DONE"}, |
| { (dsHDCP_STATUS_AUTHENTICATIONFAILURE), "HDCP_AUTH_FAIL"}, |
| { (dsHDCP_STATUS_INPROGRESS), "HDCP_DOING"}, |
| { dsHDCP_STATUS_PORTDISABLED, "HDCP_PLUG_OUT"}, |
| { dsHDCP_STATUS_MAX, "HDCP_MAX"}, |
| }; |
| |
| static const struct drm_prop_enum_list hdcp_ver_props[] = { |
| { dsHDCP_VERSION_1X, "HDCP_1x"}, |
| { dsHDCP_VERSION_2X, "HDCP_2x"}, |
| { dsHDCP_VERSION_MAX, "HDCP_non"}, |
| }; |
| |
| static void mtk_hdmi_add_properties(struct drm_connector *connector) |
| { |
| pr_info("%s %d\n", __func__, __LINE__); |
| mtk_attach_colorspace_property(connector); |
| drm_connector_attach_content_type_property(connector); |
| drm_connector_attach_max_bpc_property(connector, 8, 12); |
| |
| /* create hdcp info property */ |
| prop_hdcp_status = drm_property_create_enum(connector->dev, 0, "hdcp_status", |
| hdcp_status_props, ARRAY_SIZE(hdcp_status_props)); |
| if (!prop_hdcp_status) { |
| pr_info("cannot create hdcp status property\n"); |
| return; |
| } |
| pr_info("create hdcp status property success\n"); |
| drm_object_attach_property(&connector->base, prop_hdcp_status, 0); |
| |
| prop_hdcp_tx_ver = drm_property_create_enum(connector->dev, 0, "hdcp_tx_ver", |
| hdcp_ver_props, ARRAY_SIZE(hdcp_ver_props)); |
| if (!prop_hdcp_tx_ver) { |
| pr_info("cannot create hdcp tx ver property\n"); |
| return; |
| } |
| pr_info("create hdcp tx ver property success\n"); |
| drm_object_attach_property(&connector->base, prop_hdcp_tx_ver, 0); |
| |
| prop_hdcp_rx_ver = drm_property_create_enum(connector->dev, 0, "hdcp_rx_ver", |
| hdcp_ver_props, ARRAY_SIZE(hdcp_ver_props)); |
| if (!prop_hdcp_rx_ver) { |
| pr_info("cannot create hdcp rx ver property\n"); |
| return; |
| } |
| pr_info("create hdcp rx ver property success\n"); |
| drm_object_attach_property(&connector->base, prop_hdcp_rx_ver, 0); |
| } |
| |
| static int hdmi_conn_atomic_get_property( |
| struct drm_connector *conn, |
| const struct drm_connector_state *state, |
| struct drm_property *property, |
| uint64_t *val) |
| { |
| if (property == prop_hdcp_status) { |
| *val = hdmi_get_hdcp_status(); |
| } else if (property == prop_hdcp_tx_ver) { |
| if (hdcp2_version_flag == true) |
| *val = dsHDCP_VERSION_2X; |
| else |
| *val = dsHDCP_VERSION_1X; |
| } else if (property == prop_hdcp_rx_ver) { |
| if (hdcp2_version_flag == true) |
| *val = dsHDCP_VERSION_2X; |
| else |
| *val = dsHDCP_VERSION_1X; |
| } else { |
| pr_info("[hdmi_tx]un-support property\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * static void mtk_connector_reset(struct drm_connector *connector) |
| * { |
| * struct drm_connector_state *state; |
| * |
| * pr_info("connector reset %p\n", connector->state); |
| * |
| * if (connector->state) { |
| * __drm_atomic_helper_plane_destroy_state(connector->state); |
| * |
| * state = to_mtk_plane_state(connector->state); |
| * memset(state, 0, sizeof(*state)); |
| * } else { |
| * state = kzalloc(sizeof(*state), GFP_KERNEL); |
| * if (!state) |
| * return; |
| * connector->state = &state->base; |
| * } |
| * } |
| */ |
| |
| static const struct drm_encoder_funcs mtk_hdmi_encoder_funcs = { |
| .destroy = mtk_hdmi_encoder_destroy, |
| }; |
| |
| static const struct drm_encoder_helper_funcs mtk_hdmi_encoder_helper_funcs = { |
| .mode_fixup = mtk_hdmi_encoder_mode_fixup, |
| .mode_set = mtk_hdmi_encoder_mode_set, |
| .disable = mtk_hdmi_encoder_disable, |
| .enable = mtk_hdmi_encoder_enable, |
| .atomic_check = mtk_hdmi_atomic_check, |
| }; |
| |
| static const struct drm_connector_funcs mtk_hdmi_connector_funcs = { |
| .dpms = drm_helper_connector_dpms, |
| .detect = mtk_hdmi_connector_detect, |
| .fill_modes = drm_helper_probe_single_connector_modes, |
| .destroy = drm_connector_cleanup, |
| .reset = drm_atomic_helper_connector_reset, |
| .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, |
| .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, |
| .atomic_get_property = hdmi_conn_atomic_get_property, |
| }; |
| |
| static const struct drm_connector_helper_funcs |
| mtk_hdmi_connector_helper_funcs = { |
| .get_modes = mtk_hdmi_connector_get_modes, |
| .best_encoder = mtk_hdmi_connector_best_encoder, |
| .mode_valid = mtk_hdmi_connector_mode_valid, |
| }; |
| |
| void mtk_drm_hdmi_enc_init(struct drm_device *drm_dev) |
| { |
| int ret; |
| struct device *dev = drm_dev->dev; |
| |
| DRM_DEBUG_DRIVER("%s\n", dev_name(dev)); |
| |
| hdmi_encoder = devm_kzalloc(dev, sizeof(*hdmi_encoder), GFP_KERNEL); |
| if (!hdmi_encoder) { |
| DRM_INFO("%s alloc hdmi_encoder fail\n", dev_name(dev)); |
| return; |
| } |
| |
| ret = drm_encoder_init(drm_dev, hdmi_encoder, &mtk_hdmi_encoder_funcs, |
| DRM_MODE_ENCODER_TMDS, NULL); |
| if (ret) { |
| DRM_DEV_INFO(dev, "Failed to initialize decoder: %d\n", ret); |
| } |
| drm_encoder_helper_add(hdmi_encoder, &mtk_hdmi_encoder_helper_funcs); |
| |
| hdmi_encoder->possible_crtcs = 1; |
| |
| } |
| |
| void mtk_drm_hdmi_conn_init(struct drm_device *drm_dev) |
| { |
| int ret; |
| struct device *dev = drm_dev->dev; |
| |
| DRM_DEBUG_DRIVER("%s %s\n", dev_name(dev), dev_name(drm_dev->dev)); |
| |
| hdmi_drm_connector = devm_kzalloc(dev, sizeof(*hdmi_drm_connector), GFP_KERNEL); |
| if (!hdmi_drm_connector) { |
| DRM_INFO("%s alloc hdmi_connector fail\n", dev_name(dev)); |
| return; |
| } |
| |
| hdmi_drm_state = kzalloc(sizeof(*hdmi_drm_state), GFP_KERNEL); |
| if (!hdmi_drm_state) { |
| DRM_INFO("%s alloc hdmi_drm_state fail\n", dev_name(dev)); |
| return; |
| } |
| |
| __drm_atomic_helper_connector_reset(hdmi_drm_connector, |
| hdmi_drm_state); |
| |
| ret = drm_connector_init(drm_dev, hdmi_drm_connector, &mtk_hdmi_connector_funcs, |
| DRM_MODE_CONNECTOR_HDMIA); |
| if (ret) { |
| DRM_ERROR("Failed to connector init to drm\n"); |
| } |
| |
| drm_connector_helper_add(hdmi_drm_connector, |
| &mtk_hdmi_connector_helper_funcs); |
| |
| hdmi_drm_connector->dpms = DRM_MODE_DPMS_OFF; |
| hdmi_drm_connector->ycbcr_420_allowed = true; |
| hdmi_drm_connector->polled = DRM_CONNECTOR_POLL_HPD; |
| |
| mtk_hdmi_add_properties(hdmi_drm_connector); |
| DRM_INFO("drm_connector_attach_encoder pre\n"); |
| |
| ret = drm_connector_attach_encoder(hdmi_drm_connector, hdmi_encoder); |
| if (ret) { |
| DRM_INFO("Failed to attach encoder\n"); |
| } |
| |
| DRM_DEBUG_DRIVER("connector %d encoder %d\n", hdmi_drm_connector->base.id, |
| hdmi_encoder->base.id); |
| } |
| |
| void mtk_drm_hdmi_enc_conn_init(struct drm_device *drm_dev) |
| { |
| DRM_DEBUG_DRIVER("0x%p\n", drm_dev); |
| |
| hdmi_drm_dev = drm_dev; |
| |
| mtk_drm_hdmi_enc_init(drm_dev); |
| mtk_drm_hdmi_conn_init(drm_dev); |
| } |
| |