| /* |
| * Copyright (c) 2019, The Linux Foundation. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * * Neither the name of The Linux Foundation nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| |
| * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR |
| * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE |
| * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN |
| * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <drm.h> |
| #include <drm/sde_drm.h> |
| #include <drm/msm_drm.h> |
| #include <drm_logger.h> |
| #include <errno.h> |
| #include <string.h> |
| |
| #include <algorithm> |
| #include <map> |
| #include <memory> |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| #include <mutex> |
| |
| #include "drm_utils.h" |
| #include "drm_property.h" |
| #include "drm_connector.h" |
| |
| namespace sde_drm { |
| |
| using std::string; |
| using std::stringstream; |
| using std::pair; |
| using std::vector; |
| using std::unique_ptr; |
| using std::map; |
| using std::mutex; |
| using std::lock_guard; |
| using std::set; |
| |
| static uint8_t ON = 0; |
| static uint8_t DOZE = 1; |
| static uint8_t DOZE_SUSPEND = 2; |
| static uint8_t OFF = 5; |
| |
| // Connector FB Secure Modes |
| static uint8_t NON_SECURE = 0; |
| static uint8_t SECURE = 1; |
| |
| static uint8_t QSYNC_MODE_NONE = 0; |
| static uint8_t QSYNC_MODE_CONTINUOUS = 1; |
| |
| static uint8_t FRAME_TRIGGER_DEFAULT = 0; |
| static uint8_t FRAME_TRIGGER_SERIALIZE = 1; |
| static uint8_t FRAME_TRIGGER_POSTED_START = 2; |
| |
| static uint8_t DRM_MODE_COLORIMETRY_DEFAULT = 0; |
| static uint8_t DRM_MODE_COLORIMETRY_SMPTE_170M_YCC = 1; |
| static uint8_t DRM_MODE_COLORIMETRY_BT709_YCC = 2; |
| static uint8_t DRM_MODE_COLORIMETRY_XVYCC_601 = 3; |
| static uint8_t DRM_MODE_COLORIMETRY_XVYCC_709 = 4; |
| static uint8_t DRM_MODE_COLORIMETRY_SYCC_601 = 5; |
| static uint8_t DRM_MODE_COLORIMETRY_OPYCC_601 = 6; |
| static uint8_t DRM_MODE_COLORIMETRY_OPRGB = 7; |
| static uint8_t DRM_MODE_COLORIMETRY_BT2020_CYCC = 8; |
| static uint8_t DRM_MODE_COLORIMETRY_BT2020_RGB = 9; |
| static uint8_t DRM_MODE_COLORIMETRY_BT2020_YCC = 10; |
| static uint8_t DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65 = 11; |
| static uint8_t DRM_MODE_COLORIMETRY_DCI_P3_RGB_THEATER = 12; |
| |
| static void PopulatePowerModes(drmModePropertyRes *prop) { |
| for (auto i = 0; i < prop->count_enums; i++) { |
| string enum_name(prop->enums[i].name); |
| if (enum_name == "ON") { |
| ON = prop->enums[i].value; |
| } else if (enum_name == "LP1") { |
| DOZE = prop->enums[i].value; |
| } else if (enum_name == "LP2") { |
| DOZE_SUSPEND = prop->enums[i].value; |
| } else if (enum_name == "OFF") { |
| OFF = prop->enums[i].value; |
| } |
| } |
| } |
| |
| static void PopulateSecureModes(drmModePropertyRes *prop) { |
| for (auto i = 0; i < prop->count_enums; i++) { |
| string enum_name(prop->enums[i].name); |
| if (enum_name == "non_sec") { |
| NON_SECURE = prop->enums[i].value; |
| } else if (enum_name == "sec") { |
| SECURE = prop->enums[i].value; |
| } |
| } |
| } |
| |
| static void PopulateSupportedColorspaces(drmModePropertyRes *prop) { |
| for (auto i = 0; i < prop->count_enums; i++) { |
| string enum_name(prop->enums[i].name); |
| if (enum_name == "Default") { |
| DRM_MODE_COLORIMETRY_DEFAULT = prop->enums[i].value; |
| } else if (enum_name == "SMPTE_170M_YCC") { |
| DRM_MODE_COLORIMETRY_SMPTE_170M_YCC = prop->enums[i].value; |
| } else if (enum_name == "BT709_YCC") { |
| DRM_MODE_COLORIMETRY_BT709_YCC = prop->enums[i].value; |
| } else if (enum_name == "XVYCC_601") { |
| DRM_MODE_COLORIMETRY_XVYCC_601 = prop->enums[i].value; |
| } else if (enum_name == "XVYCC_709") { |
| DRM_MODE_COLORIMETRY_XVYCC_709 = prop->enums[i].value; |
| } else if (enum_name == "SYCC_601") { |
| DRM_MODE_COLORIMETRY_SYCC_601 = prop->enums[i].value; |
| } else if (enum_name == "opYCC_601") { |
| DRM_MODE_COLORIMETRY_OPYCC_601 = prop->enums[i].value; |
| } else if (enum_name == "opRGB") { |
| DRM_MODE_COLORIMETRY_OPRGB = prop->enums[i].value; |
| } else if (enum_name == "BT2020_CYCC") { |
| DRM_MODE_COLORIMETRY_BT2020_CYCC = prop->enums[i].value; |
| } else if (enum_name == "BT2020_RGB") { |
| DRM_MODE_COLORIMETRY_BT2020_RGB = prop->enums[i].value; |
| } else if (enum_name == "BT2020_YCC") { |
| DRM_MODE_COLORIMETRY_BT2020_YCC = prop->enums[i].value; |
| } else if (enum_name == "DCI_P3_RGB_D65") { |
| DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65 = prop->enums[i].value; |
| } else if (enum_name == "DCI_P3_RGB_Theater") { |
| DRM_MODE_COLORIMETRY_DCI_P3_RGB_THEATER = prop->enums[i].value; |
| } |
| } |
| } |
| |
| static DRMTopology GetTopologyEnum(const string &topology) { |
| if (topology == "sde_singlepipe") return DRMTopology::SINGLE_LM; |
| if (topology == "sde_singlepipe_dsc") return DRMTopology::SINGLE_LM_DSC; |
| if (topology == "sde_dualpipe") return DRMTopology::DUAL_LM; |
| if (topology == "sde_dualpipe_dsc") return DRMTopology::DUAL_LM_DSC; |
| if (topology == "sde_dualpipemerge") return DRMTopology::DUAL_LM_MERGE; |
| if (topology == "sde_dualpipemerge_dsc") return DRMTopology::DUAL_LM_MERGE_DSC; |
| if (topology == "sde_dualpipe_dscmerge") return DRMTopology::DUAL_LM_DSCMERGE; |
| if (topology == "sde_ppsplit") return DRMTopology::PPSPLIT; |
| return DRMTopology::UNKNOWN; |
| } |
| |
| static void PopulateQsyncModes(drmModePropertyRes *prop) { |
| for (auto i = 0; i < prop->count_enums; i++) { |
| string enum_name(prop->enums[i].name); |
| if (enum_name == "none") { |
| QSYNC_MODE_NONE = prop->enums[i].value; |
| } else if (enum_name == "continuous") { |
| QSYNC_MODE_CONTINUOUS = prop->enums[i].value; |
| } |
| } |
| } |
| |
| static void PopulateFrameTriggerModes(drmModePropertyRes *prop) { |
| for (auto i = 0; i < prop->count_enums; i++) { |
| string enum_name(prop->enums[i].name); |
| if (enum_name == "default") { |
| FRAME_TRIGGER_DEFAULT = prop->enums[i].value; |
| } else if (enum_name == "serilize_frame_trigger") { |
| FRAME_TRIGGER_SERIALIZE = prop->enums[i].value; |
| } else if (enum_name == "posted_start") { |
| FRAME_TRIGGER_POSTED_START = prop->enums[i].value; |
| } |
| } |
| } |
| |
| static int32_t GetColorspace(DRMColorspace drm_colorspace) { |
| uint32_t colorspace = 0; |
| switch (drm_colorspace) { |
| case (DRMColorspace::DEFAULT): |
| colorspace = DRM_MODE_COLORIMETRY_DEFAULT; |
| break; |
| case (DRMColorspace::SMPTE_170M_YCC): |
| colorspace = DRM_MODE_COLORIMETRY_SMPTE_170M_YCC; |
| break; |
| case (DRMColorspace::BT709_YCC): |
| colorspace = DRM_MODE_COLORIMETRY_BT709_YCC; |
| break; |
| case (DRMColorspace::XVYCC_601): |
| colorspace = DRM_MODE_COLORIMETRY_XVYCC_601; |
| break; |
| case (DRMColorspace::XVYCC_709): |
| colorspace = DRM_MODE_COLORIMETRY_XVYCC_709; |
| break; |
| case (DRMColorspace::SYCC_601): |
| colorspace = DRM_MODE_COLORIMETRY_SYCC_601; |
| break; |
| case (DRMColorspace::OPYCC_601): |
| colorspace = DRM_MODE_COLORIMETRY_OPYCC_601; |
| break; |
| case (DRMColorspace::OPRGB): |
| colorspace = DRM_MODE_COLORIMETRY_OPRGB; |
| break; |
| case (DRMColorspace::BT2020_CYCC): |
| colorspace = DRM_MODE_COLORIMETRY_BT2020_CYCC; |
| break; |
| case (DRMColorspace::BT2020_RGB): |
| colorspace = DRM_MODE_COLORIMETRY_BT2020_RGB; |
| break; |
| case (DRMColorspace::BT2020_YCC): |
| colorspace = DRM_MODE_COLORIMETRY_BT2020_YCC; |
| break; |
| case (DRMColorspace::DCI_P3_RGB_D65): |
| colorspace = DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65; |
| break; |
| case (DRMColorspace::DCI_P3_RGB_THEATER): |
| colorspace = DRM_MODE_COLORIMETRY_DCI_P3_RGB_THEATER; |
| break; |
| default: |
| colorspace = -1; |
| break; |
| } |
| return colorspace; |
| } |
| |
| #define __CLASS__ "DRMConnectorManager" |
| |
| void DRMConnectorManager::Init(drmModeRes *resource) { |
| lock_guard<mutex> lock(lock_); |
| for (int i = 0; i < resource->count_connectors; i++) { |
| unique_ptr<DRMConnector> conn(new DRMConnector(fd_)); |
| drmModeConnector *libdrm_conn = drmModeGetConnector(fd_, resource->connectors[i]); |
| if (libdrm_conn) { |
| conn->InitAndParse(libdrm_conn); |
| connector_pool_[resource->connectors[i]] = std::move(conn); |
| } else { |
| DRM_LOGE("Critical error: drmModeGetConnector() failed for connector %u.", |
| resource->connectors[i]); |
| } |
| } |
| } |
| |
| void DRMConnectorManager::Update() { |
| lock_guard<mutex> lock(lock_); |
| drmModeRes *resource = drmModeGetResources(fd_); |
| |
| if (NULL == resource) { |
| DRM_LOGE("drmModeGetResources() failed. Connector status not updated."); |
| return; |
| } |
| |
| // Build a map of the updated list of connector ids. |
| std::map<uint32_t, uint32_t> drm_connectors; |
| for (int i = 0; i < resource->count_connectors; i++) { |
| drm_connectors[resource->connectors[i]] = resource->connectors[i]; |
| } |
| |
| // Delete connectors in connector pool. |
| for (auto conn = connector_pool_.cbegin(); conn != connector_pool_.cend();) { |
| auto drmconn = drm_connectors.find(conn->first); |
| if (drmconn == drm_connectors.end()) { |
| // A DRM Connector in our pool was deleted. |
| if (conn->second->GetStatus() == DRMStatus::FREE) { |
| DRM_LOGD("Removing connector id %u from pool.", conn->first); |
| conn = connector_pool_.erase(conn); |
| } else { |
| // Physically removed DRM Connectors (displays) first go to disconnected state. When its |
| // reserved resources are freed up, they are removed from the driver's connector list. Do |
| // not remove DRM Connectors that are DRMStatus::BUSY. |
| DRM_LOGW("In-use connector id %u removed by DRM.", conn->first); |
| conn++; |
| } |
| } else { |
| // Remove DRM Connector present in both lists to ensure that only new connector ids remain. |
| drm_connectors.erase(drmconn); |
| conn++; |
| } |
| } |
| |
| // Add new connectors in connector pool. |
| for (auto &drmconn : drm_connectors) { |
| DRM_LOGD("Adding connector id %u to pool.", drmconn.first); |
| unique_ptr<DRMConnector> conn(new DRMConnector(fd_)); |
| drmModeConnector *libdrm_conn = drmModeGetConnector(fd_, drmconn.first); |
| if (libdrm_conn) { |
| conn->InitAndParse(libdrm_conn); |
| conn->SetSkipConnectorReload(true); |
| connector_pool_[drmconn.first] = std::move(conn); |
| } else { |
| DRM_LOGE("Critical error: drmModeGetConnector() failed for connector %u.", drmconn.first); |
| } |
| } |
| |
| drmModeFreeResources(resource); |
| } |
| |
| void DRMConnectorManager::DumpByID(uint32_t id) { |
| lock_guard<mutex> lock(lock_); |
| connector_pool_[id]->Dump(); |
| } |
| |
| void DRMConnectorManager::DumpAll() { |
| lock_guard<mutex> lock(lock_); |
| for (auto &conn : connector_pool_) { |
| conn.second->Dump(); |
| } |
| } |
| |
| void DRMConnectorManager::Perform(DRMOps code, uint32_t obj_id, drmModeAtomicReq *req, |
| va_list args) { |
| lock_guard<mutex> lock(lock_); |
| auto it = connector_pool_.find(obj_id); |
| if (it == connector_pool_.end()) { |
| DRM_LOGE("Invalid connector id %d", obj_id); |
| return; |
| } |
| |
| it->second->Perform(code, req, args); |
| } |
| |
| int DRMConnectorManager::GetConnectorInfo(uint32_t conn_id, DRMConnectorInfo *info) { |
| lock_guard<mutex> lock(lock_); |
| int ret = -ENODEV; |
| auto iter = connector_pool_.find(conn_id); |
| |
| if (iter != connector_pool_.end()) { |
| ret = connector_pool_[conn_id]->GetInfo(info); |
| } |
| |
| return ret; |
| } |
| |
| void DRMConnectorManager::GetConnectorList(std::vector<uint32_t> *conn_ids) { |
| lock_guard<mutex> lock(lock_); |
| if (!conn_ids) { |
| DRM_LOGE("No output parameter provided."); |
| return; |
| } |
| conn_ids->clear(); |
| for (auto &conn : connector_pool_) { |
| conn_ids->push_back(conn.first); |
| } |
| } |
| |
| static bool IsTVConnector(uint32_t type) { |
| return (type == DRM_MODE_CONNECTOR_TV || type == DRM_MODE_CONNECTOR_HDMIA || |
| type == DRM_MODE_CONNECTOR_HDMIB || type == DRM_MODE_CONNECTOR_DisplayPort || |
| type == DRM_MODE_CONNECTOR_VGA); |
| } |
| |
| int DRMConnectorManager::Reserve(DRMDisplayType disp_type, DRMDisplayToken *token) { |
| lock_guard<mutex> lock(lock_); |
| int ret = -ENODEV; |
| token->conn_id = 0; |
| |
| for (auto &conn : connector_pool_) { |
| if (conn.second->GetStatus() == DRMStatus::FREE) { |
| uint32_t conn_type; |
| conn.second->GetType(&conn_type); |
| if ((disp_type == DRMDisplayType::PERIPHERAL && conn_type == DRM_MODE_CONNECTOR_DSI) || |
| (disp_type == DRMDisplayType::VIRTUAL && conn_type == DRM_MODE_CONNECTOR_VIRTUAL) || |
| (disp_type == DRMDisplayType::TV && IsTVConnector(conn_type))) { |
| if (conn.second->IsConnected()) { |
| // Free-up previously reserved connector, if any. |
| if (token->conn_id) { |
| connector_pool_[token->conn_id]->Unlock(); |
| } |
| conn.second->Lock(); |
| token->conn_id = conn.first; |
| ret = 0; |
| break; |
| } else { |
| // Hold on to the first reserved connector. |
| if (token->conn_id) { |
| continue; |
| } |
| // Prefer a connector that is connected. Continue search. |
| conn.second->Lock(); |
| token->conn_id = conn.first; |
| ret = 0; |
| } |
| } |
| } |
| } |
| |
| return ret; |
| } |
| |
| int DRMConnectorManager::Reserve(uint32_t conn_id, DRMDisplayToken *token) { |
| lock_guard<mutex> lock(lock_); |
| int ret = -ENODEV; |
| |
| auto iter = connector_pool_.find(conn_id); |
| if ((iter != connector_pool_.end()) && (iter->second->GetStatus() == DRMStatus::FREE)) { |
| iter->second->Lock(); |
| token->conn_id = iter->first; |
| ret = 0; |
| } |
| |
| return ret; |
| } |
| |
| int DRMConnectorManager::GetPossibleEncoders(uint32_t connector_id, |
| set<uint32_t> *possible_encoders) { |
| lock_guard<mutex> lock(lock_); |
| return connector_pool_[connector_id]->GetPossibleEncoders(possible_encoders); |
| } |
| |
| |
| void DRMConnectorManager::Free(DRMDisplayToken *token) { |
| lock_guard<mutex> lock(lock_); |
| connector_pool_.at(token->conn_id)->Unlock(); |
| token->conn_id = 0; |
| } |
| |
| // ==============================================================================================// |
| |
| #undef __CLASS__ |
| #define __CLASS__ "DRMConnector" |
| |
| DRMConnector::~DRMConnector() { |
| if (drm_connector_) { |
| drmModeFreeConnector(drm_connector_); |
| } |
| } |
| |
| void DRMConnector::ParseProperties() { |
| drmModeObjectProperties *props = |
| drmModeObjectGetProperties(fd_, drm_connector_->connector_id, DRM_MODE_OBJECT_CONNECTOR); |
| if (!props || !props->props || !props->prop_values) { |
| drmModeFreeObjectProperties(props); |
| return; |
| } |
| |
| for (uint32_t j = 0; j < props->count_props; j++) { |
| drmModePropertyRes *info = drmModeGetProperty(fd_, props->props[j]); |
| if (!info) { |
| continue; |
| } |
| |
| string property_name(info->name); |
| DRMProperty prop_enum = prop_mgr_.GetPropertyEnum(property_name); |
| |
| if (prop_enum == DRMProperty::INVALID) { |
| DRM_LOGD("DRMProperty %s missing from global property mapping", info->name); |
| drmModeFreeProperty(info); |
| continue; |
| } |
| |
| if (prop_enum == DRMProperty::LP) { |
| PopulatePowerModes(info); |
| } else if (prop_enum == DRMProperty::FB_TRANSLATION_MODE) { |
| PopulateSecureModes(info); |
| } else if (prop_enum == DRMProperty::QSYNC_MODE) { |
| PopulateQsyncModes(info); |
| } else if (prop_enum == DRMProperty::FRAME_TRIGGER) { |
| PopulateFrameTriggerModes(info); |
| } else if (prop_enum == DRMProperty::COLORSPACE) { |
| PopulateSupportedColorspaces(info); |
| } |
| |
| prop_mgr_.SetPropertyId(prop_enum, info->prop_id); |
| drmModeFreeProperty(info); |
| } |
| |
| drmModeFreeObjectProperties(props); |
| } |
| |
| void DRMConnector::ParseCapabilities(uint64_t blob_id, DRMConnectorInfo *info) { |
| drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(fd_, blob_id); |
| if (!blob) { |
| return; |
| } |
| |
| const char *fmt_str = reinterpret_cast<const char *>(blob->data); |
| stringstream stream(fmt_str); |
| string line = {}; |
| const string display_type = "display type="; |
| const string panel_name = "panel name="; |
| const string panel_mode = "panel mode="; |
| const string dfps_support = "dfps support="; |
| const string pixel_formats = "pixel_formats="; |
| const string max_linewidth = "maxlinewidth="; |
| const string panel_orientation = "panel orientation="; |
| const string qsync_support = "qsync support="; |
| const string wb_ubwc = "wb_ubwc"; |
| const string dyn_bitclk_support = "dyn bitclk support="; |
| |
| while (std::getline(stream, line)) { |
| if (line.find(pixel_formats) != string::npos) { |
| vector<pair<uint32_t, uint64_t>> formats_supported; |
| ParseFormats(line.erase(0, pixel_formats.length()), &formats_supported); |
| info->formats_supported = move(formats_supported); |
| } else if (line.find(max_linewidth) != string::npos) { |
| info->max_linewidth = std::stoi(string(line, max_linewidth.length())); |
| } else if (line.find(display_type) != string::npos) { |
| info->is_primary = (string(line, display_type.length()) == "primary"); |
| } else if (line.find(panel_name) != string::npos) { |
| info->panel_name = string(line, panel_name.length()); |
| } else if (line.find(panel_mode) != string::npos) { |
| info->panel_mode = (string(line, panel_mode.length()) == "video") ? DRMPanelMode::VIDEO |
| : DRMPanelMode::COMMAND; |
| } else if (line.find(dfps_support) != string::npos) { |
| info->dynamic_fps = (string(line, dfps_support.length()) == "true"); |
| } else if (line.find(panel_orientation) != string::npos) { |
| if (string(line, panel_orientation.length()) == "horz flip") { |
| info->panel_orientation = DRMRotation::FLIP_H; |
| } else if (string(line, panel_orientation.length()) == "vert flip") { |
| info->panel_orientation = DRMRotation::FLIP_V; |
| } else if (string(line, panel_orientation.length()) == "horz & vert flip") { |
| info->panel_orientation = DRMRotation::ROT_180; |
| } |
| } else if (line.find(qsync_support) != string::npos) { |
| info->qsync_support = (string(line, qsync_support.length()) == "true"); |
| } else if (line.find(wb_ubwc) != string::npos) { |
| info->is_wb_ubwc_supported = true; |
| } else if (line.find(dyn_bitclk_support) != string::npos) { |
| info->dyn_bitclk_support = (string(line, dyn_bitclk_support.length()) == "true"); |
| } |
| } |
| |
| drmModeFreePropertyBlob(blob); |
| } |
| |
| void DRMConnector::ParseCapabilities(uint64_t blob_id, drm_panel_hdr_properties *hdr_info) { |
| drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(fd_, blob_id); |
| if (!blob) { |
| return; |
| } |
| |
| struct drm_panel_hdr_properties *hdr_data = (struct drm_panel_hdr_properties*)(blob->data); |
| |
| if (hdr_data) { |
| hdr_info->hdr_enabled = hdr_data->hdr_enabled; |
| hdr_info->peak_brightness = hdr_data->peak_brightness; |
| hdr_info->blackness_level = hdr_data->blackness_level; |
| for (int i = 0; i < DISPLAY_PRIMARIES_MAX; i++) { |
| hdr_info->display_primaries[i] = hdr_data->display_primaries[i]; |
| } |
| } |
| drmModeFreePropertyBlob(blob); |
| } |
| |
| void DRMConnector::ParseModeProperties(uint64_t blob_id, DRMConnectorInfo *info) { |
| drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(fd_, blob_id); |
| if (!blob) { |
| return; |
| } |
| |
| const char *fmt_str = reinterpret_cast<const char *>(blob->data); |
| stringstream stream(fmt_str); |
| string line = {}; |
| const string mode_name = "mode_name="; |
| const string topology = "topology="; |
| const string pu_num_roi = "partial_update_num_roi="; |
| const string pu_xstart = "partial_update_xstart="; |
| const string pu_ystart = "partial_update_ystart="; |
| const string pu_walign = "partial_update_walign="; |
| const string pu_halign = "partial_update_halign="; |
| const string pu_wmin = "partial_update_wmin="; |
| const string pu_hmin = "partial_update_hmin="; |
| const string pu_roimerge = "partial_update_roimerge="; |
| const string bit_clk_rate = "bit_clk_rate="; |
| const string mdp_transfer_time_us = "mdp_transfer_time_us="; |
| |
| // Map of parsed mode_name to mode_properties |
| map<string, DRMModeInfo> mode_props_map {}; |
| auto it = mode_props_map.end(); |
| while (std::getline(stream, line)) { |
| if (line.find(mode_name) != string::npos) { |
| string name(line, mode_name.length()); |
| it = mode_props_map.insert(make_pair(name, DRMModeInfo())).first; |
| } else if (line.find(topology) != string::npos) { |
| it->second.topology = GetTopologyEnum(string(line, topology.length())); |
| } else if (line.find(pu_num_roi) != string::npos) { |
| it->second.num_roi = std::stoi(string(line, pu_num_roi.length())); |
| } else if (line.find(pu_xstart) != string::npos) { |
| it->second.xstart = std::stoi(string(line, pu_xstart.length())); |
| } else if (line.find(pu_ystart) != string::npos) { |
| it->second.ystart = std::stoi(string(line, pu_ystart.length())); |
| } else if (line.find(pu_walign) != string::npos) { |
| it->second.walign = std::stoi(string(line, pu_walign.length())); |
| } else if (line.find(pu_halign) != string::npos) { |
| it->second.halign = std::stoi(string(line, pu_halign.length())); |
| } else if (line.find(pu_wmin) != string::npos) { |
| it->second.wmin = std::stoi(string(line, pu_wmin.length())); |
| } else if (line.find(pu_hmin) != string::npos) { |
| it->second.hmin = std::stoi(string(line, pu_hmin.length())); |
| } else if (line.find(pu_roimerge) != string::npos) { |
| it->second.roi_merge = std::stoi(string(line, pu_roimerge.length())); |
| } else if (line.find(bit_clk_rate) != string::npos) { |
| it->second.bit_clk_rate = std::stoi(string(line, bit_clk_rate.length())); |
| } else if (line.find(mdp_transfer_time_us) != string::npos) { |
| it->second.transfer_time_us = std::stoi(string(line, mdp_transfer_time_us.length())); |
| } |
| } |
| |
| for (uint32_t i = 0; i < info->modes.size(); i++) { |
| DRMModeInfo &mode_item = info->modes.at(i); |
| auto it = mode_props_map.find(string(mode_item.mode.name)); |
| if (it != mode_props_map.end()) { |
| mode_item.topology = it->second.topology; |
| mode_item.num_roi = it->second.num_roi; |
| mode_item.roi_merge = it->second.roi_merge; |
| mode_item.xstart = it->second.xstart; |
| mode_item.ystart = it->second.ystart; |
| mode_item.wmin = it->second.wmin; |
| mode_item.hmin = it->second.hmin; |
| mode_item.walign = it->second.walign; |
| mode_item.halign = it->second.halign; |
| mode_item.bit_clk_rate = it->second.bit_clk_rate; |
| mode_item.transfer_time_us = it->second.transfer_time_us; |
| } |
| } |
| |
| drmModeFreePropertyBlob(blob); |
| } |
| |
| void DRMConnector::ParseCapabilities(uint64_t blob_id, drm_msm_ext_hdr_properties *hdr_info) { |
| drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(fd_, blob_id); |
| if (!blob) { |
| return; |
| } |
| |
| struct drm_msm_ext_hdr_properties *hdr_cdata = (struct drm_msm_ext_hdr_properties*)(blob->data); |
| |
| if (hdr_cdata) { |
| hdr_info->hdr_supported = hdr_cdata->hdr_supported; |
| hdr_info->hdr_plus_supported = hdr_cdata->hdr_plus_supported; |
| hdr_info->hdr_eotf = hdr_cdata->hdr_eotf; |
| hdr_info->hdr_metadata_type_one = hdr_cdata->hdr_metadata_type_one; |
| hdr_info->hdr_max_luminance = hdr_cdata->hdr_max_luminance; |
| hdr_info->hdr_avg_luminance = hdr_cdata->hdr_avg_luminance; |
| hdr_info->hdr_min_luminance = hdr_cdata->hdr_min_luminance; |
| DRM_LOGI("hdr_supported = %d, hdr_plus_supported = %d, hdr_eotf = %d, " |
| "hdr_metadata_type_one = %d, hdr_max_luminance = %d, hdr_avg_luminance = %d, " |
| "hdr_min_luminance = %d\n", hdr_info->hdr_supported, |
| hdr_info->hdr_plus_supported, |
| hdr_info->hdr_eotf, hdr_info->hdr_metadata_type_one, hdr_info->hdr_max_luminance, |
| hdr_info->hdr_avg_luminance, hdr_info->hdr_min_luminance); |
| } |
| drmModeFreePropertyBlob(blob); |
| } |
| |
| void DRMConnector::ParseCapabilities(uint64_t blob_id, std::vector<uint8_t> *edid) { |
| drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(fd_, blob_id); |
| if (!blob) { |
| return; |
| } |
| |
| uint8_t *edid_blob = (uint8_t*)(blob->data); |
| uint32_t length = blob->length; |
| (*edid).assign(edid_blob, edid_blob + length); |
| |
| drmModeFreePropertyBlob(blob); |
| } |
| |
| int DRMConnector::GetInfo(DRMConnectorInfo *info) { |
| uint32_t conn_id = drm_connector_->connector_id; |
| if (!skip_connector_reload_ && (IsTVConnector(drm_connector_->connector_type) |
| || (DRM_MODE_CONNECTOR_VIRTUAL == drm_connector_->connector_type))) { |
| // Reload since for some connectors like Virtual and DP, modes may change. |
| drmModeConnectorPtr drm_connector = drmModeGetConnector(fd_, conn_id); |
| if (!drm_connector) { |
| // Connector resource not found. This could happen if a connector is removed before a commit |
| // was done on it. Mark the connector as disconnected for graceful teardown. Update 'info' |
| // with basic information from previously initialized drm_connector_ for graceful teardown. |
| info->is_connected = false; |
| info->modes.clear(); |
| info->type = drm_connector_->connector_type; |
| info->type_id = drm_connector_->connector_type_id; |
| DLOGW("Connector %u not found. Possibly removed.", conn_id); |
| return 0; |
| } |
| drmModeFreeConnector(drm_connector_); |
| drm_connector_ = drm_connector; |
| } |
| |
| SetSkipConnectorReload(false); // Reset skip_connector_reload_ setting. |
| info->modes.clear(); |
| if (!drm_connector_->count_modes) { |
| DRM_LOGW("Zero modes on connector %u.", conn_id); |
| } |
| for (auto i = 0; i < drm_connector_->count_modes; i++) { |
| DRMModeInfo modes_item {}; |
| modes_item.mode = drm_connector_->modes[i]; |
| info->modes.push_back(modes_item); |
| } |
| info->mmWidth = drm_connector_->mmWidth; |
| info->mmHeight = drm_connector_->mmHeight; |
| info->type = drm_connector_->connector_type; |
| info->type_id = drm_connector_->connector_type_id; |
| info->is_connected = IsConnected(); |
| |
| drmModeObjectProperties *props = |
| drmModeObjectGetProperties(fd_, drm_connector_->connector_id, DRM_MODE_OBJECT_CONNECTOR); |
| if (!props || !props->props || !props->prop_values) { |
| drmModeFreeObjectProperties(props); |
| return -ENODEV; |
| } |
| |
| uint32_t index = UINT32_MAX; |
| |
| if (prop_mgr_.IsPropertyAvailable(DRMProperty::HDR_PROPERTIES)) { |
| index = std::distance(props->props, |
| std::find(props->props, props->props + props->count_props, |
| prop_mgr_.GetPropertyId(DRMProperty::HDR_PROPERTIES))); |
| if (index < props->count_props) |
| ParseCapabilities(props->prop_values[index], &info->panel_hdr_prop); |
| } |
| |
| if (prop_mgr_.IsPropertyAvailable(DRMProperty::CAPABILITIES)) { |
| index = std::distance(props->props, |
| std::find(props->props, props->props + props->count_props, |
| prop_mgr_.GetPropertyId(DRMProperty::CAPABILITIES))); |
| if (index < props->count_props) |
| ParseCapabilities(props->prop_values[index], info); |
| } |
| |
| if (prop_mgr_.IsPropertyAvailable(DRMProperty::MODE_PROPERTIES)) { |
| index = std::distance(props->props, |
| std::find(props->props, props->props + props->count_props, |
| prop_mgr_.GetPropertyId(DRMProperty::MODE_PROPERTIES))); |
| if (index < props->count_props) |
| ParseModeProperties(props->prop_values[index], info); |
| } |
| |
| if (prop_mgr_.IsPropertyAvailable(DRMProperty::EXT_HDR_PROPERTIES)) { |
| index = std::distance(props->props, |
| std::find(props->props, props->props + props->count_props, |
| prop_mgr_.GetPropertyId(DRMProperty::EXT_HDR_PROPERTIES))); |
| if (index < props->count_props) |
| ParseCapabilities(props->prop_values[index], &info->ext_hdr_prop); |
| } |
| |
| if (prop_mgr_.IsPropertyAvailable(DRMProperty::TOPOLOGY_CONTROL)) { |
| index = std::distance(props->props, |
| std::find(props->props, props->props + props->count_props, |
| prop_mgr_.GetPropertyId(DRMProperty::TOPOLOGY_CONTROL))); |
| info->topology_control = props->prop_values[index]; |
| } |
| if (prop_mgr_.IsPropertyAvailable(DRMProperty::EDID)) { |
| index = std::distance(props->props, |
| std::find(props->props, props->props + props->count_props, |
| prop_mgr_.GetPropertyId(DRMProperty::EDID))); |
| ParseCapabilities(props->prop_values[index], &info->edid); |
| } |
| |
| if (prop_mgr_.IsPropertyAvailable(DRMProperty::SUPPORTED_COLORSPACES)) { |
| index = std::distance(props->props, |
| std::find(props->props, props->props + props->count_props, |
| prop_mgr_.GetPropertyId(DRMProperty::SUPPORTED_COLORSPACES))); |
| info->supported_colorspaces = props->prop_values[index]; |
| } |
| |
| drmModeFreeObjectProperties(props); |
| |
| return 0; |
| } |
| |
| void DRMConnector::InitAndParse(drmModeConnector *conn) { |
| drm_connector_ = conn; |
| ParseProperties(); |
| pp_mgr_ = std::unique_ptr<DRMPPManager>(new DRMPPManager(fd_)); |
| pp_mgr_->Init(prop_mgr_, DRM_MODE_OBJECT_CONNECTOR); |
| } |
| |
| void DRMConnector::Perform(DRMOps code, drmModeAtomicReq *req, va_list args) { |
| uint32_t obj_id = drm_connector_->connector_id; |
| |
| switch (code) { |
| case DRMOps::CONNECTOR_SET_CRTC: { |
| uint32_t crtc = va_arg(args, uint32_t); |
| drmModeAtomicAddProperty(req, obj_id, prop_mgr_.GetPropertyId(DRMProperty::CRTC_ID), crtc); |
| DRM_LOGD("Connector %d: Setting CRTC %d", obj_id, crtc); |
| } break; |
| |
| case DRMOps::CONNECTOR_GET_RETIRE_FENCE: { |
| int64_t *fence = va_arg(args, int64_t *); |
| *fence = -1; |
| uint32_t prop_id = prop_mgr_.GetPropertyId(DRMProperty::RETIRE_FENCE); |
| drmModeAtomicAddProperty(req, obj_id, prop_id, reinterpret_cast<uint64_t>(fence)); |
| } break; |
| |
| case DRMOps::CONNECTOR_SET_OUTPUT_RECT: { |
| DRMRect rect = va_arg(args, DRMRect); |
| drmModeAtomicAddProperty(req, obj_id, |
| prop_mgr_.GetPropertyId(DRMProperty::DST_X), rect.left); |
| drmModeAtomicAddProperty(req, obj_id, |
| prop_mgr_.GetPropertyId(DRMProperty::DST_Y), rect.top); |
| drmModeAtomicAddProperty(req, obj_id, prop_mgr_.GetPropertyId(DRMProperty::DST_W), |
| rect.right - rect.left); |
| drmModeAtomicAddProperty(req, obj_id, prop_mgr_.GetPropertyId(DRMProperty::DST_H), |
| rect.bottom - rect.top); |
| DRM_LOGD("Connector %d: Setting dst [x,y,w,h][%d,%d,%d,%d]", obj_id, rect.left, |
| rect.top, (rect.right - rect.left), (rect.bottom - rect.top)); |
| } break; |
| |
| case DRMOps::CONNECTOR_SET_OUTPUT_FB_ID: { |
| uint32_t fb_id = va_arg(args, uint32_t); |
| drmModeAtomicAddProperty(req, obj_id, prop_mgr_.GetPropertyId(DRMProperty::FB_ID), fb_id); |
| DRM_LOGD("Connector %d: Setting fb_id %d", obj_id, fb_id); |
| } break; |
| |
| case DRMOps::CONNECTOR_SET_POWER_MODE: { |
| int drm_power_mode = va_arg(args, int); |
| uint32_t power_mode = ON; |
| switch (drm_power_mode) { |
| case (int)DRMPowerMode::ON: |
| power_mode = ON; |
| break; |
| case (int)DRMPowerMode::DOZE: |
| power_mode = DOZE; |
| break; |
| case (int)DRMPowerMode::DOZE_SUSPEND: |
| power_mode = DOZE_SUSPEND; |
| break; |
| case (int)DRMPowerMode::OFF: |
| power_mode = OFF; |
| break; |
| default: |
| DRM_LOGE("Invalid power mode %d to set on connector %d", drm_power_mode, obj_id); |
| break; |
| } |
| drmModeAtomicAddProperty(req, obj_id, prop_mgr_.GetPropertyId(DRMProperty::LP), power_mode); |
| DRM_LOGD("Connector %d: Setting power_mode %d", obj_id, power_mode); |
| } break; |
| |
| case DRMOps::CONNECTOR_SET_ROI: { |
| uint32_t num_roi = va_arg(args, uint32_t); |
| DRMRect *conn_rois = va_arg(args, DRMRect*); |
| SetROI(req, obj_id, num_roi, conn_rois); |
| } break; |
| |
| case DRMOps::CONNECTOR_SET_AUTOREFRESH: { |
| uint32_t enable = va_arg(args, uint32_t); |
| drmModeAtomicAddProperty(req, obj_id, prop_mgr_.GetPropertyId(DRMProperty::AUTOREFRESH), |
| enable); |
| DRM_LOGD("Connector %d: Setting autorefresh %d", obj_id, enable); |
| } break; |
| |
| case DRMOps::CONNECTOR_SET_FB_SECURE_MODE: { |
| int secure_mode = va_arg(args, int); |
| uint32_t fb_secure_mode = (secure_mode == (int)DRMSecureMode::SECURE) ? SECURE : NON_SECURE; |
| drmModeAtomicAddProperty(req, obj_id, |
| prop_mgr_.GetPropertyId(DRMProperty::FB_TRANSLATION_MODE), |
| fb_secure_mode); |
| DRM_LOGD("Connector %d: Setting FB secure mode %d", obj_id, fb_secure_mode); |
| } break; |
| |
| case DRMOps::CONNECTOR_SET_POST_PROC: { |
| DRMPPFeatureInfo *data = va_arg(args, DRMPPFeatureInfo*); |
| if (data) |
| pp_mgr_->SetPPFeature(req, obj_id, *data); |
| } break; |
| |
| case DRMOps::CONNECTOR_SET_HDR_METADATA: { |
| drm_msm_ext_hdr_metadata *hdr_metadata = va_arg(args, drm_msm_ext_hdr_metadata *); |
| drmModeAtomicAddProperty(req, obj_id, prop_mgr_.GetPropertyId(DRMProperty::HDR_METADATA), |
| reinterpret_cast<uint64_t>(hdr_metadata)); |
| } break; |
| |
| case DRMOps::CONNECTOR_SET_QSYNC_MODE: { |
| if (!prop_mgr_.IsPropertyAvailable(DRMProperty::QSYNC_MODE)) { |
| return; |
| } |
| int drm_qsync_mode = va_arg(args, int); |
| uint32_t qsync_mode = |
| (drm_qsync_mode == (int)DRMQsyncMode::NONE) ? QSYNC_MODE_NONE : QSYNC_MODE_CONTINUOUS; |
| drmModeAtomicAddProperty(req, obj_id, prop_mgr_.GetPropertyId(DRMProperty::QSYNC_MODE), |
| qsync_mode); |
| DRM_LOGD("Connector %d: Setting Qsync mode %d", obj_id, qsync_mode); |
| } break; |
| |
| case DRMOps::CONNECTOR_SET_TOPOLOGY_CONTROL: { |
| uint32_t topology_control = va_arg(args, uint32_t); |
| drmModeAtomicAddProperty(req, obj_id, prop_mgr_.GetPropertyId(DRMProperty::TOPOLOGY_CONTROL), |
| topology_control); |
| } break; |
| |
| case DRMOps::CONNECTOR_SET_FRAME_TRIGGER: { |
| if (!prop_mgr_.IsPropertyAvailable(DRMProperty::FRAME_TRIGGER)) { |
| return; |
| } |
| int drm_frame_trigger_mode = va_arg(args, int); |
| DRMFrameTriggerMode mode = static_cast<DRMFrameTriggerMode>(drm_frame_trigger_mode); |
| int32_t frame_trigger_mode = -1; |
| switch (mode) { |
| case (DRMFrameTriggerMode::FRAME_DONE_WAIT_DEFAULT): |
| frame_trigger_mode = FRAME_TRIGGER_DEFAULT; |
| break; |
| case (DRMFrameTriggerMode::FRAME_DONE_WAIT_SERIALIZE): |
| frame_trigger_mode = FRAME_TRIGGER_SERIALIZE; |
| break; |
| case (DRMFrameTriggerMode::FRAME_DONE_WAIT_POSTED_START): |
| frame_trigger_mode = FRAME_TRIGGER_POSTED_START; |
| break; |
| default: |
| DRM_LOGE("Invalid frame trigger mode %d to set on connector %d", |
| drm_frame_trigger_mode, obj_id); |
| break; |
| } |
| if (frame_trigger_mode >= 0) { |
| uint32_t prop_id = prop_mgr_.GetPropertyId(DRMProperty::FRAME_TRIGGER); |
| int ret = drmModeAtomicAddProperty(req, obj_id, prop_id, frame_trigger_mode); |
| if (ret < 0) { |
| DRM_LOGE("AtomicAddProperty failed obj_id 0x%x, prop_id %d mode %d ret %d", |
| obj_id, prop_id, frame_trigger_mode, ret); |
| } else { |
| DRM_LOGD("Connector %d: Setting frame trigger mode %d", obj_id, frame_trigger_mode); |
| } |
| } |
| } break; |
| |
| case DRMOps::CONNECTOR_SET_COLORSPACE: { |
| if (!prop_mgr_.IsPropertyAvailable(DRMProperty::COLORSPACE)) { |
| return; |
| } |
| DRMColorspace drm_colorspace = static_cast<DRMColorspace>(va_arg(args, uint32_t)); |
| int32_t colorspace = 0; |
| colorspace = GetColorspace(drm_colorspace); |
| if (colorspace >= 0) { |
| uint32_t prop_id = prop_mgr_.GetPropertyId(DRMProperty::COLORSPACE); |
| int ret = drmModeAtomicAddProperty(req, obj_id, prop_id, colorspace); |
| if (ret < 0) { |
| DRM_LOGE("AtomicAddProperty failed obj_id 0x%x, prop_id %d mode %d ret %d", |
| obj_id, prop_id, colorspace, ret); |
| } else { |
| DRM_LOGD("Connector %d: Setting colorspace %d", obj_id, colorspace); |
| } |
| } else { |
| DRM_LOGE("Invalid colorspace %d", colorspace); |
| } |
| } break; |
| |
| default: |
| DRM_LOGE("Invalid opcode %d to set on connector %d", code, obj_id); |
| break; |
| } |
| } |
| |
| void DRMConnector::SetROI(drmModeAtomicReq *req, uint32_t obj_id, uint32_t num_roi, |
| DRMRect *conn_rois) { |
| #ifdef SDE_MAX_ROI_V1 |
| if (num_roi > SDE_MAX_ROI_V1 || !prop_mgr_.IsPropertyAvailable(DRMProperty::ROI_V1)) { |
| return; |
| } |
| if (!num_roi || !conn_rois) { |
| drmModeAtomicAddProperty(req, obj_id, prop_mgr_.GetPropertyId(DRMProperty::ROI_V1), 0); |
| DRM_LOGD("Connector ROI is set to NULL to indicate full frame update"); |
| return; |
| } |
| |
| static struct sde_drm_roi_v1 roi_v1 {}; |
| memset(&roi_v1, 0, sizeof(roi_v1)); |
| roi_v1.num_rects = num_roi; |
| |
| for (uint32_t i = 0; i < num_roi; i++) { |
| roi_v1.roi[i].x1 = conn_rois[i].left; |
| roi_v1.roi[i].x2 = conn_rois[i].right; |
| roi_v1.roi[i].y1 = conn_rois[i].top; |
| roi_v1.roi[i].y2 = conn_rois[i].bottom; |
| DRM_LOGD("Conn %d, ROI[l,t,b,r][%d %d %d %d]", obj_id, |
| roi_v1.roi[i].x1,roi_v1.roi[i].y1,roi_v1.roi[i].x2,roi_v1.roi[i].y2); |
| } |
| drmModeAtomicAddProperty(req, obj_id, prop_mgr_.GetPropertyId(DRMProperty::ROI_V1), |
| reinterpret_cast<uint64_t>(&roi_v1)); |
| #endif |
| } |
| |
| int DRMConnector::GetPossibleEncoders(set<uint32_t> *possible_encoders) { |
| if (!possible_encoders) { |
| return -EINVAL; |
| } |
| |
| uint32_t count_enc = drm_connector_->count_encoders; |
| if (count_enc == 0) { |
| DRM_LOGW("No possible encoders for connector %u", drm_connector_->connector_id); |
| } |
| |
| (*possible_encoders).clear(); |
| for (uint32_t i = 0; i < count_enc; i++) { |
| (*possible_encoders).insert(drm_connector_->encoders[i]); |
| } |
| |
| return 0; |
| } |
| |
| void DRMConnector::Dump() { |
| DRM_LOGE("id: %d\tenc_id: %d\tconn: %d\ttype: %d\tPhy: %dx%d\n", drm_connector_->connector_id, |
| drm_connector_->encoder_id, drm_connector_->connection, drm_connector_->connector_type, |
| drm_connector_->mmWidth, drm_connector_->mmHeight); |
| DRM_LOGE("Modes: \n"); |
| for (uint32_t i = 0; i < (uint32_t)drm_connector_->count_modes; i++) { |
| DRM_LOGE( |
| "Name: %s\tvref: %d\thdisp: %d\t hsync_s: %d\thsync_e:%d\thtotal: %d\t" |
| "vdisp: %d\tvsync_s: %d\tvsync_e: %d\tvtotal: %d\n", |
| drm_connector_->modes[i].name, drm_connector_->modes[i].vrefresh, |
| drm_connector_->modes[i].hdisplay, drm_connector_->modes[i].hsync_start, |
| drm_connector_->modes[i].hsync_end, drm_connector_->modes[i].htotal, |
| drm_connector_->modes[i].vdisplay, drm_connector_->modes[i].vsync_start, |
| drm_connector_->modes[i].vsync_end, drm_connector_->modes[i].vtotal); |
| } |
| } |
| |
| } // namespace sde_drm |