/*
// 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