/*
// 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 <HwcTrace.h>
#include <Drm.h>
#include <DrmConfig.h>
#include <Hwcomposer.h>
#include <ExternalDevice.h>
namespace android {
namespace intel {
ExternalDevice::ExternalDevice(Hwcomposer& hwc, DeviceControlFactory* controlFactory)
: PhysicalDevice(DEVICE_EXTERNAL, hwc, controlFactory),
mHdcpControl(NULL),
mAbortModeSettingCond(),
mPendingDrmMode(),
mHotplugEventPending(false),
mExpectedRefreshRate(0)
{
CTRACE();
}
ExternalDevice::~ExternalDevice()
{
CTRACE();
}
bool ExternalDevice::initialize()
{
if (!PhysicalDevice::initialize()) {
DEINIT_AND_RETURN_FALSE("failed to initialize physical device");
}
mHdcpControl = mControlFactory->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 {
ETRACE("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::setDrmMode(drmModeModeInfo& value)
{
if (!mConnected) {
WTRACE("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()) {
ETRACE("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()
{
ITRACE("start mode setting...");
Drm *drm = Hwcomposer::getInstance().getDrm();
mConnected = false;
mHwc.hotplug(mType, 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) {
ITRACE("Mode settings is interrupted");
mHwc.hotplug(mType, true);
return;
}
}
// TODO: potential threading issue with onHotplug callback
mHdcpControl->stopHdcp();
if (!drm->setDrmMode(mType, mPendingDrmMode)) {
ETRACE("failed to set Drm mode");
mHwc.hotplug(mType, true);
return;
}
if (!PhysicalDevice::updateDisplayConfigs()) {
ETRACE("failed to update display configs");
mHwc.hotplug(mType, true);
return;
}
mConnected = true;
mHotplugEventPending = true;
// delay sending hotplug event until HDCP is authenticated
if (mHdcpControl->startHdcpAsync(HdcpLinkStatusListener, this) == false) {
ETRACE("startHdcpAsync() failed; HDCP is not enabled");
mHotplugEventPending = false;
mHwc.hotplug(mType, true);
}
mExpectedRefreshRate = 0;
}
void ExternalDevice::HdcpLinkStatusListener(bool success, void *userData)
{
if (userData == NULL) {
return;
}
ExternalDevice *p = (ExternalDevice*)userData;
p->HdcpLinkStatusListener(success);
}
void ExternalDevice::HdcpLinkStatusListener(bool success)
{
if (!success) {
ETRACE("HDCP is not authenticated, disabling dynamic vsync");
mHwc.getVsyncManager()->enableDynamicVsync(false);
}
if (mHotplugEventPending) {
DTRACE("HDCP authentication status %d, sending hotplug event...", success);
mHwc.hotplug(mType, mConnected);
mHotplugEventPending = false;
}
if (success) {
ITRACE("HDCP authenticated, enabling dynamic vsync");
mHwc.getVsyncManager()->enableDynamicVsync(true);
}
}
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) {
ETRACE("failed to detect display config");
return;
}
ITRACE("hotpug event: %d", mConnected);
if (connected == mConnected) {
WTRACE("same connection status detected, hotplug event ignored");
return;
}
if (mConnected == false) {
mHotplugEventPending = false;
mHwc.getVsyncManager()->resetVsyncSource();
mHdcpControl->stopHdcp();
mHwc.hotplug(mType, mConnected);
} else {
DTRACE("start HDCP asynchronously...");
// delay sending hotplug event till HDCP is authenticated.
mHotplugEventPending = true;
ret = mHdcpControl->startHdcpAsync(HdcpLinkStatusListener, this);
if (ret == false) {
ETRACE("failed to start HDCP");
mHotplugEventPending = false;
mHwc.hotplug(mType, mConnected);
}
}
mActiveDisplayConfig = 0;
}
int ExternalDevice::getRefreshRate()
{
Drm *drm = Hwcomposer::getInstance().getDrm();
drmModeModeInfo mode;
if (!drm->getModeInfo(IDisplayDevice::DEVICE_EXTERNAL, mode))
return 0;
return mode.vrefresh;
}
void ExternalDevice::setRefreshRate(int hz)
{
RETURN_VOID_IF_NOT_INIT();
ITRACE("setting refresh rate to %d", hz);
if (mBlank) {
WTRACE("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;
if (mExpectedRefreshRate != 0 &&
mExpectedRefreshRate == hz && mHotplugEventPending) {
ITRACE("Ignore a new refresh setting event because there is a same event is handling");
return;
}
mExpectedRefreshRate = hz;
ITRACE("changing refresh rate from %d to %d", mode.vrefresh, hz);
mHwc.getVsyncManager()->enableDynamicVsync(false);
mHdcpControl->stopHdcp();
drm->setRefreshRate(IDisplayDevice::DEVICE_EXTERNAL, hz);
mHotplugEventPending = false;
mHdcpControl->startHdcpAsync(HdcpLinkStatusListener, this);
mHwc.getVsyncManager()->enableDynamicVsync(true);
}
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