/* // Copyright (c) 2014 Intel Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. */ #include <common/utils/HwcTrace.h> #include <common/base/Drm.h> #include <DrmConfig.h> #include <Hwcomposer.h> #include <ExternalDevice.h> namespace android { namespace intel { ExternalDevice::ExternalDevice(uint32_t disp, Hwcomposer& hwc, DisplayPlaneManager& dpm) : PhysicalDevice(disp, DEVICE_EXTERNAL, hwc, dpm), mHdcpControl(NULL), mAbortModeSettingCond(), mPendingDrmMode(), mHotplugEventPending(false) { CTRACE(); } ExternalDevice::~ExternalDevice() { CTRACE(); } bool ExternalDevice::initialize() { if (!PhysicalDevice::initialize()) { DEINIT_AND_RETURN_FALSE("failed to initialize physical device"); } mHdcpControl = createHdcpControl(); if (!mHdcpControl) { DEINIT_AND_RETURN_FALSE("failed to create HDCP control"); } mHotplugEventPending = false; if (mConnected) { mHdcpControl->startHdcpAsync(HdcpLinkStatusListener, this); } UeventObserver *observer = Hwcomposer::getInstance().getUeventObserver(); if (observer) { observer->registerListener( DrmConfig::getHotplugString(), hotplugEventListener, this); } else { ELOGTRACE("Uevent observer is NULL"); } return true; } void ExternalDevice::deinitialize() { // abort mode settings if it is in the middle mAbortModeSettingCond.signal(); if (mThread.get()) { mThread->join(); mThread = NULL; } if (mHdcpControl) { mHdcpControl->stopHdcp(); delete mHdcpControl; mHdcpControl = 0; } mHotplugEventPending = false; PhysicalDevice::deinitialize(); } bool ExternalDevice::blank(bool blank) { if (!PhysicalDevice::blank(blank)) { return false; } if (blank) { mHdcpControl->stopHdcp(); } else if (mConnected) { mHdcpControl->startHdcpAsync(HdcpLinkStatusListener, this); } return true; } bool ExternalDevice::setDrmMode(drmModeModeInfo& value) { if (!mConnected) { WLOGTRACE("external device is not connected"); return false; } if (mThread.get()) { mThread->join(); mThread = NULL; } Drm *drm = Hwcomposer::getInstance().getDrm(); drmModeModeInfo mode; drm->getModeInfo(mType, mode); if (drm->isSameDrmMode(&value, &mode)) return true; // any issue here by faking connection status? mConnected = false; mPendingDrmMode = value; // setting mode in a working thread mThread = new ModeSettingThread(this); if (!mThread.get()) { ELOGTRACE("failed to create mode settings thread"); return false; } mThread->run("ModeSettingsThread", PRIORITY_URGENT_DISPLAY); return true; } bool ExternalDevice::threadLoop() { // one-time execution setDrmMode(); return false; } void ExternalDevice::setDrmMode() { ILOGTRACE("start mode setting..."); Drm *drm = Hwcomposer::getInstance().getDrm(); mConnected = false; mHwc.hotplug(mDisp, false); { Mutex::Autolock lock(mLock); // TODO: make timeout value flexible, or wait until surface flinger // acknowledges hot unplug event. status_t err = mAbortModeSettingCond.waitRelative(mLock, milliseconds(20)); if (err != -ETIMEDOUT) { ILOGTRACE("Mode settings is interrupted"); mHwc.hotplug(mDisp, true); return; } } // TODO: potential threading issue with onHotplug callback mHdcpControl->stopHdcp(); if (!drm->setDrmMode(mType, mPendingDrmMode)) { ELOGTRACE("failed to set Drm mode"); mHwc.hotplug(mDisp, true); return; } if (!PhysicalDevice::updateDisplayConfigs()) { ELOGTRACE("failed to update display configs"); mHwc.hotplug(mDisp, true); return; } mConnected = true; mHotplugEventPending = true; // delay sending hotplug event until HDCP is authenticated if (mHdcpControl->startHdcpAsync(HdcpLinkStatusListener, this) == false) { ELOGTRACE("startHdcpAsync() failed; HDCP is not enabled"); mHotplugEventPending = false; mHwc.hotplug(mDisp, true); } } void ExternalDevice::HdcpLinkStatusListener(bool success, void *userData) { if (userData == NULL) { return; } ExternalDevice *p = (ExternalDevice*)userData; p->HdcpLinkStatusListener(success); } void ExternalDevice::HdcpLinkStatusListener(bool success) { if (mHotplugEventPending) { DLOGTRACE("HDCP authentication status %d, sending hotplug event...", success); mHwc.hotplug(mDisp, mConnected); mHotplugEventPending = false; } } void ExternalDevice::hotplugEventListener(void *data) { ExternalDevice *pThis = (ExternalDevice*)data; if (pThis) { pThis->hotplugListener(); } } void ExternalDevice::hotplugListener() { bool ret; CTRACE(); // abort mode settings if it is in the middle mAbortModeSettingCond.signal(); // remember the current connection status before detection bool connected = mConnected; // detect display configs ret = detectDisplayConfigs(); if (ret == false) { ELOGTRACE("failed to detect display config"); return; } ILOGTRACE("hotpug event: %d", mConnected); if (connected == mConnected) { WLOGTRACE("same connection status detected, hotplug event ignored"); return; } if (mConnected == false) { mHotplugEventPending = false; mHdcpControl->stopHdcp(); mHwc.hotplug(mDisp, mConnected); } else { DLOGTRACE("start HDCP asynchronously..."); // delay sending hotplug event till HDCP is authenticated. mHotplugEventPending = true; ret = mHdcpControl->startHdcpAsync(HdcpLinkStatusListener, this); if (ret == false) { ELOGTRACE("failed to start HDCP"); mHotplugEventPending = false; mHwc.hotplug(mDisp, mConnected); } } mActiveDisplayConfig = 0; } void ExternalDevice::setRefreshRate(int hz) { RETURN_VOID_IF_NOT_INIT(); ILOGTRACE("setting refresh rate to %d", hz); if (mBlank) { WLOGTRACE("external device is blank"); return; } Drm *drm = Hwcomposer::getInstance().getDrm(); drmModeModeInfo mode; if (!drm->getModeInfo(IDisplayDevice::DEVICE_EXTERNAL, mode)) return; if (hz == 0 && (mode.type & DRM_MODE_TYPE_PREFERRED)) return; if (hz == (int)mode.vrefresh) return; ILOGTRACE("changing refresh rate from %d to %d", mode.vrefresh, hz); mHdcpControl->stopHdcp(); drm->setRefreshRate(IDisplayDevice::DEVICE_EXTERNAL, hz); mHotplugEventPending = false; mHdcpControl->startHdcpAsync(HdcpLinkStatusListener, this); } bool ExternalDevice::getDisplaySize(int *width, int *height) { #ifndef INTEL_SUPPORT_HDMI_PRIMARY return PhysicalDevice::getDisplaySize(width, height); #else if (mConnected) return PhysicalDevice::getDisplaySize(width, height); if (!width || !height) return false; *width = 1920; *height = 1080; return true; #endif } bool ExternalDevice::getDisplayConfigs(uint32_t *configs, size_t *numConfigs) { #ifndef INTEL_SUPPORT_HDMI_PRIMARY return PhysicalDevice::getDisplayConfigs(configs, numConfigs); #else if (mConnected) return PhysicalDevice::getDisplayConfigs(configs, numConfigs); if (!configs || !numConfigs) return false; *configs = 0; *numConfigs = 1; return true; #endif } bool ExternalDevice::getDisplayAttributes(uint32_t config, const uint32_t *attributes, int32_t *values) { #ifndef INTEL_SUPPORT_HDMI_PRIMARY return PhysicalDevice::getDisplayAttributes(config, attributes, values); #else if (mConnected) return PhysicalDevice::getDisplayAttributes(config, attributes, values); if (!attributes || !values) return false; int i = 0; while (attributes[i] != HWC_DISPLAY_NO_ATTRIBUTE) { switch (attributes[i]) { case HWC_DISPLAY_VSYNC_PERIOD: values[i] = 1e9 / 60; break; case HWC_DISPLAY_WIDTH: values[i] = 1920; break; case HWC_DISPLAY_HEIGHT: values[i] = 1080; break; case HWC_DISPLAY_DPI_X: values[i] = 1; break; case HWC_DISPLAY_DPI_Y: values[i] = 1; break; default: ELOGTRACE("unknown attribute %d", attributes[i]); break; } i++; } return true; #endif } int ExternalDevice::getActiveConfig() { if (!mConnected) { return 0; } return mActiveDisplayConfig; } bool ExternalDevice::setActiveConfig(int index) { if (!mConnected) { if (index == 0) return true; else return false; } // for now we will only permit the frequency change. In the future // we may need to set mode as well. if (index >= 0 && index < static_cast<int>(mDisplayConfigs.size())) { DisplayConfig *config = mDisplayConfigs.itemAt(index); setRefreshRate(config->getRefreshRate()); mActiveDisplayConfig = index; return true; } else { return false; } return true; } } // namespace intel } // namespace android