/*
// 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 <fcntl.h>
#include <errno.h>
#include <common/utils/HwcTrace.h>
#include <IDisplayDevice.h>
#include <DrmConfig.h>
#include <common/base/Drm.h>
#include <Hwcomposer.h>
namespace android {
namespace intel {
Drm::Drm()
: mDrmFd(0),
mLock(),
mInitialized(false)
{
memset(&mOutputs, 0, sizeof(mOutputs));
}
Drm::~Drm()
{
WARN_IF_NOT_DEINIT();
}
bool Drm::initialize()
{
if (mInitialized) {
WLOGTRACE("Drm object has been initialized");
return true;
}
const char *path = DrmConfig::getDrmPath();
mDrmFd = open(path, O_RDWR, 0);
if (mDrmFd < 0) {
ELOGTRACE("failed to open Drm, error: %s", strerror(errno));
return false;
}
DLOGTRACE("mDrmFd = %d", mDrmFd);
memset(&mOutputs, 0, sizeof(mOutputs));
mInitialized = true;
return true;
}
void Drm::deinitialize()
{
for (int i = 0; i < OUTPUT_MAX; i++) {
resetOutput(i);
}
if (mDrmFd) {
close(mDrmFd);
mDrmFd = 0;
}
mInitialized = false;
}
bool Drm::detect(int device)
{
RETURN_FALSE_IF_NOT_INIT();
Mutex::Autolock _l(mLock);
int outputIndex = getOutputIndex(device);
if (outputIndex < 0 ) {
return false;
}
resetOutput(outputIndex);
// get drm resources
drmModeResPtr resources = drmModeGetResources(mDrmFd);
if (!resources) {
ELOGTRACE("fail to get drm resources, error: %s", strerror(errno));
return false;
}
drmModeConnectorPtr connector = NULL;
DrmOutput *output = &mOutputs[outputIndex];
bool ret = false;
// find connector for the given device
for (int i = 0; i < resources->count_connectors; i++) {
if (!resources->connectors || !resources->connectors[i]) {
ELOGTRACE("fail to get drm resources connectors, error: %s", strerror(errno));
continue;
}
connector = drmModeGetConnector(mDrmFd, resources->connectors[i]);
if (!connector) {
ELOGTRACE("drmModeGetConnector failed");
continue;
}
if (connector->connector_type != DrmConfig::getDrmConnector(device)) {
drmModeFreeConnector(connector);
continue;
}
if (connector->connection != DRM_MODE_CONNECTED) {
ILOGTRACE("device %d is not connected", device);
drmModeFreeConnector(connector);
ret = true;
break;
}
output->connector = connector;
output->connected = true;
// get proper encoder for the given connector
if (connector->encoder_id) {
ILOGTRACE("Drm connector has encoder attached on device %d", device);
output->encoder = drmModeGetEncoder(mDrmFd, connector->encoder_id);
if (!output->encoder) {
ELOGTRACE("failed to get encoder from a known encoder id");
// fall through to get an encoder
}
}
if (!output->encoder) {
ILOGTRACE("getting encoder for device %d", device);
drmModeEncoderPtr encoder;
for (int j = 0; j < resources->count_encoders; j++) {
if (!resources->encoders || !resources->encoders[j]) {
ELOGTRACE("fail to get drm resources encoders, error: %s", strerror(errno));
continue;
}
encoder = drmModeGetEncoder(mDrmFd, resources->encoders[i]);
if (!encoder) {
ELOGTRACE("drmModeGetEncoder failed");
continue;
}
if (encoder->encoder_type == DrmConfig::getDrmEncoder(device)) {
output->encoder = encoder;
break;
}
drmModeFreeEncoder(encoder);
encoder = NULL;
}
}
if (!output->encoder) {
ELOGTRACE("failed to get drm encoder");
break;
}
// get an attached crtc or spare crtc
if (output->encoder->crtc_id) {
ILOGTRACE("Drm encoder has crtc attached on device %d", device);
output->crtc = drmModeGetCrtc(mDrmFd, output->encoder->crtc_id);
if (!output->crtc) {
ELOGTRACE("failed to get crtc from a known crtc id");
// fall through to get a spare crtc
}
}
if (!output->crtc) {
ILOGTRACE("getting crtc for device %d", device);
drmModeCrtcPtr crtc;
for (int j = 0; j < resources->count_crtcs; j++) {
if (!resources->crtcs || !resources->crtcs[j]) {
ELOGTRACE("fail to get drm resources crtcs, error: %s", strerror(errno));
continue;
}
crtc = drmModeGetCrtc(mDrmFd, resources->crtcs[j]);
if (!crtc) {
ELOGTRACE("drmModeGetCrtc failed");
continue;
}
// check if legal crtc to the encoder
if (output->encoder->possible_crtcs & (1<<j)) {
if (crtc->buffer_id == 0) {
output->crtc = crtc;
break;
}
}
drmModeFreeCrtc(crtc);
}
}
if (!output->crtc) {
ELOGTRACE("failed to get drm crtc");
break;
}
// current mode
if (output->crtc->mode_valid) {
ILOGTRACE("mode is valid, kernel mode settings");
memcpy(&output->mode, &output->crtc->mode, sizeof(drmModeModeInfo));
//output->fbId = output->crtc->buffer_id;
ret = true;
} else {
ELOGTRACE("mode is invalid. Kernel mode setting is not completed");
ret = false;
}
if (outputIndex == OUTPUT_PRIMARY) {
if (!readIoctl(DRM_PSB_PANEL_ORIENTATION, &output->panelOrientation, sizeof(int))) {
ELOGTRACE("failed to get device %d orientation", device);
output->panelOrientation = PANEL_ORIENTATION_0;
}
} else {
output->panelOrientation = PANEL_ORIENTATION_0;
}
break;
}
if (!ret) {
if (output->connector == NULL && outputIndex != OUTPUT_PRIMARY) {
// a fatal failure on primary device
// non fatal on secondary device
WLOGTRACE("device %d is disabled?", device);
ret = true;
}
resetOutput(outputIndex);
} else if (output->connected) {
ILOGTRACE("mode is: %dx%d@%dHz", output->mode.hdisplay, output->mode.vdisplay, output->mode.vrefresh);
}
drmModeFreeResources(resources);
return ret;
}
bool Drm::isSameDrmMode(drmModeModeInfoPtr value,
drmModeModeInfoPtr base) const
{
if (base->hdisplay == value->hdisplay &&
base->vdisplay == value->vdisplay &&
base->vrefresh == value->vrefresh &&
(base->flags & value->flags) == value->flags) {
VLOGTRACE("Drm mode is not changed");
return true;
}
return false;
}
bool Drm::setDrmMode(int device, drmModeModeInfo& value)
{
RETURN_FALSE_IF_NOT_INIT();
Mutex::Autolock _l(mLock);
if (device != IDisplayDevice::DEVICE_EXTERNAL) {
WLOGTRACE("Setting mode on invalid device %d", device);
return false;
}
int outputIndex = getOutputIndex(device);
if (outputIndex < 0 ) {
ELOGTRACE("invalid device");
return false;
}
DrmOutput *output= &mOutputs[outputIndex];
if (!output->connected) {
ELOGTRACE("device is not connected");
return false;
}
if (output->connector->count_modes <= 0) {
ELOGTRACE("invalid count of modes");
return false;
}
drmModeModeInfoPtr mode;
int index = 0;
for (int i = 0; i < output->connector->count_modes; i++) {
mode = &output->connector->modes[i];
if (mode->type & DRM_MODE_TYPE_PREFERRED) {
index = i;
}
if (isSameDrmMode(&value, mode)) {
index = i;
break;
}
}
mode = &output->connector->modes[index];
return setDrmMode(outputIndex, mode);
}
bool Drm::setRefreshRate(int device, int hz)
{
RETURN_FALSE_IF_NOT_INIT();
Mutex::Autolock _l(mLock);
if (device != IDisplayDevice::DEVICE_EXTERNAL) {
WLOGTRACE("Setting mode on invalid device %d", device);
return false;
}
int outputIndex = getOutputIndex(device);
if (outputIndex < 0 ) {
ELOGTRACE("invalid device");
return false;
}
DrmOutput *output= &mOutputs[outputIndex];
if (!output->connected) {
ELOGTRACE("device is not connected");
return false;
}
if (output->connector->count_modes <= 0) {
ELOGTRACE("invalid count of modes");
return false;
}
drmModeModeInfoPtr mode;
int index = 0;
for (int i = 0; i < output->connector->count_modes; i++) {
mode = &output->connector->modes[i];
if (mode->type & DRM_MODE_TYPE_PREFERRED) {
index = i;
}
if (mode->hdisplay == output->mode.hdisplay &&
mode->vdisplay == output->mode.vdisplay &&
mode->vrefresh == (uint32_t)hz) {
index = i;
break;
}
}
mode = &output->connector->modes[index];
return setDrmMode(outputIndex, mode);
}
bool Drm::writeReadIoctl(unsigned long cmd, void *data,
unsigned long size)
{
int err;
if (mDrmFd <= 0) {
ELOGTRACE("drm is not initialized");
return false;
}
if (!data || !size) {
ELOGTRACE("invalid parameters");
return false;
}
err = drmCommandWriteRead(mDrmFd, cmd, data, size);
if (err) {
WLOGTRACE("failed to call %ld ioctl with failure %d", cmd, err);
return false;
}
return true;
}
bool Drm::writeIoctl(unsigned long cmd, void *data,
unsigned long size)
{
int err;
if (mDrmFd <= 0) {
ELOGTRACE("drm is not initialized");
return false;
}
if (!data || !size) {
ELOGTRACE("invalid parameters");
return false;
}
err = drmCommandWrite(mDrmFd, cmd, data, size);
if (err) {
WLOGTRACE("failed to call %ld ioctl with failure %d", cmd, err);
return false;
}
return true;
}
bool Drm::readIoctl(unsigned long cmd, void *data,
unsigned long size)
{
int err;
if (mDrmFd <= 0) {
ELOGTRACE("drm is not initialized");
return false;
}
if (!data || !size) {
ELOGTRACE("invalid parameters");
return false;
}
err = drmCommandRead(mDrmFd, cmd, data, size);
if (err) {
WLOGTRACE("failed to call %ld ioctl with failure %d", cmd, err);
return false;
}
return true;
}
int Drm::getDrmFd() const
{
return mDrmFd;
}
bool Drm::getModeInfo(int device, drmModeModeInfo& mode)
{
Mutex::Autolock _l(mLock);
int outputIndex = getOutputIndex(device);
if (outputIndex < 0 ) {
return false;
}
DrmOutput *output= &mOutputs[outputIndex];
if (output->connected == false) {
ELOGTRACE("device is not connected");
return false;
}
if (output->mode.hdisplay == 0 || output->mode.vdisplay == 0) {
ELOGTRACE("invalid width or height");
return false;
}
memcpy(&mode, &output->mode, sizeof(drmModeModeInfo));
#ifdef INTEL_SUPPORT_HDMI_PRIMARY
// FIXME: use default fb size instead of hdmi mode, because to
// support hdmi primary, we cannot report dynamic mode to SF.
mode.hdisplay = DEFAULT_DRM_FB_WIDTH;
mode.vdisplay = DEFAULT_DRM_FB_HEIGHT;
#endif
return true;
}
bool Drm::getPhysicalSize(int device, uint32_t& width, uint32_t& height)
{
Mutex::Autolock _l(mLock);
int outputIndex = getOutputIndex(device);
if (outputIndex < 0 ) {
return false;
}
DrmOutput *output= &mOutputs[outputIndex];
if (output->connected == false) {
ELOGTRACE("device is not connected");
return false;
}
width = output->connector->mmWidth;
height = output->connector->mmHeight;
return true;
}
bool Drm::getDisplayResolution(int device, uint32_t& width, uint32_t& height)
{
Mutex::Autolock _l(mLock);
int outputIndex = getOutputIndex(device);
if (outputIndex < 0) {
return false;
}
DrmOutput *output= &mOutputs[outputIndex];
if (output->connected == false) {
ELOGTRACE("device is not connected");
return false;
}
width = output->mode.hdisplay;
height = output->mode.vdisplay;
if (!width || !height) {
ELOGTRACE("invalid width or height");
return false;
}
return true;
}
bool Drm::isConnected(int device)
{
Mutex::Autolock _l(mLock);
int output = getOutputIndex(device);
if (output < 0 ) {
return false;
}
return mOutputs[output].connected;
}
bool Drm::setDpmsMode(int device, int mode)
{
Mutex::Autolock _l(mLock);
#ifdef INTEL_SUPPORT_HDMI_PRIMARY
device = IDisplayDevice::DEVICE_EXTERNAL;
#endif
int output = getOutputIndex(device);
if (output < 0 ) {
return false;
}
if (mode != IDisplayDevice::DEVICE_DISPLAY_OFF &&
mode != IDisplayDevice::DEVICE_DISPLAY_STANDBY &&
mode != IDisplayDevice::DEVICE_DISPLAY_ON) {
ELOGTRACE("invalid mode %d", mode);
return false;
}
DrmOutput *out = &mOutputs[output];
if (!out->connected) {
ELOGTRACE("device is not connected");
return false;
}
drmModePropertyPtr props;
for (int i = 0; i < out->connector->count_props; i++) {
props = drmModeGetProperty(mDrmFd, out->connector->props[i]);
if (!props) {
continue;
}
if (strcmp(props->name, "DPMS") == 0) {
int ret = drmModeConnectorSetProperty(
mDrmFd,
out->connector->connector_id,
props->prop_id,
(mode == IDisplayDevice::DEVICE_DISPLAY_ON) ? DRM_MODE_DPMS_ON :
IDisplayDevice::DEVICE_DISPLAY_STANDBY == mode ?
DRM_MODE_DPMS_STANDBY : DRM_MODE_DPMS_OFF);
drmModeFreeProperty(props);
if (ret != 0) {
ELOGTRACE("unable to set DPMS %d", mode);
return false;
} else {
return true;
}
}
drmModeFreeProperty(props);
}
return false;
}
void Drm::resetOutput(int index)
{
DrmOutput *output = &mOutputs[index];
output->connected = false;
memset(&output->mode, 0, sizeof(drmModeModeInfo));
if (output->connector) {
drmModeFreeConnector(output->connector);
output->connector = 0;
}
if (output->encoder) {
drmModeFreeEncoder(output->encoder);
output->encoder = 0;
}
if (output->crtc) {
drmModeFreeCrtc(output->crtc);
output->crtc = 0;
}
if (output->fbId) {
drmModeRmFB(mDrmFd, output->fbId);
output->fbId = 0;
}
if (output->fbHandle) {
Hwcomposer::getInstance().getBufferManager()->freeFrameBuffer(output->fbHandle);
output->fbHandle = 0;
}
}
bool Drm::initDrmMode(int outputIndex)
{
DrmOutput *output= &mOutputs[outputIndex];
if (output->connector->count_modes <= 0) {
ELOGTRACE("invalid count of modes");
return false;
}
drmModeModeInfoPtr mode;
int index = 0;
for (int i = 0; i < output->connector->count_modes; i++) {
mode = &output->connector->modes[i];
if (mode->type & DRM_MODE_TYPE_PREFERRED) {
index = i;
break;
}
}
return setDrmMode(outputIndex, &output->connector->modes[index]);
}
bool Drm::setDrmMode(int index, drmModeModeInfoPtr mode)
{
DrmOutput *output = &mOutputs[index];
int oldFbId = 0;
int oldFbHandle = 0;
// reuse current frame buffer if there is no resolution change
int fbId = -1;
drmModeModeInfo currentMode;
memcpy(¤tMode, &output->mode, sizeof(drmModeModeInfo));
if (isSameDrmMode(mode, ¤tMode))
return true;
if (currentMode.hdisplay != mode->hdisplay ||
currentMode.vdisplay != mode->vdisplay) {
oldFbId = output->fbId;
oldFbHandle = output->fbHandle;
// allocate frame buffer
int stride = 0;
#ifdef INTEL_SUPPORT_HDMI_PRIMARY
output->fbHandle = Hwcomposer::getInstance().getBufferManager()->allocFrameBuffer(
DEFAULT_DRM_FB_WIDTH, DEFAULT_DRM_FB_HEIGHT, &stride);
#else
output->fbHandle = Hwcomposer::getInstance().getBufferManager()->allocFrameBuffer(
mode->hdisplay, mode->vdisplay, &stride);
#endif
if (output->fbHandle == 0) {
ELOGTRACE("failed to allocate frame buffer");
return false;
}
int ret = 0;
ret = drmModeAddFB(
mDrmFd,
#ifdef INTEL_SUPPORT_HDMI_PRIMARY
DEFAULT_DRM_FB_WIDTH,
DEFAULT_DRM_FB_HEIGHT,
#else
mode->hdisplay,
mode->vdisplay,
#endif
DrmConfig::getFrameBufferDepth(),
DrmConfig::getFrameBufferBpp(),
stride,
output->fbHandle,
&output->fbId);
if (ret != 0) {
ELOGTRACE("drmModeAddFB failed, error: %d", ret);
return false;
}
fbId = output->fbId;
}
ILOGTRACE("mode set: %dx%d@%dHz", mode->hdisplay, mode->vdisplay, mode->vrefresh);
int ret = drmModeSetCrtc(mDrmFd, output->crtc->crtc_id, fbId, 0, 0,
&output->connector->connector_id, 1, mode);
if (ret == 0) {
//save mode
memcpy(&output->mode, mode, sizeof(drmModeModeInfo));
} else {
ELOGTRACE("drmModeSetCrtc failed. error: %d", ret);
}
if (oldFbId) {
drmModeRmFB(mDrmFd, oldFbId);
}
if (oldFbHandle) {
Hwcomposer::getInstance().getBufferManager()->freeFrameBuffer(oldFbHandle);
}
return ret == 0;
}
int Drm::getOutputIndex(int device)
{
switch (device) {
case IDisplayDevice::DEVICE_PRIMARY:
return OUTPUT_PRIMARY;
case IDisplayDevice::DEVICE_EXTERNAL:
return OUTPUT_EXTERNAL;
default:
ELOGTRACE("invalid display device");
break;
}
return -1;
}
int Drm::getPanelOrientation(int device)
{
int outputIndex = getOutputIndex(device);
if (outputIndex < 0) {
ELOGTRACE("invalid device");
return PANEL_ORIENTATION_0;
}
DrmOutput *output= &mOutputs[outputIndex];
if (output->connected == false) {
ELOGTRACE("device is not connected");
return PANEL_ORIENTATION_0;
}
return output->panelOrientation;
}
// HWC 1.4 requires that we return all of the compatible configs in getDisplayConfigs
// this is needed so getActiveConfig/setActiveConfig work correctly. It is up to the
// user space to decide what speed to send.
drmModeModeInfoPtr Drm::detectAllConfigs(int device, int *modeCount)
{
RETURN_NULL_IF_NOT_INIT();
Mutex::Autolock _l(mLock);
if (modeCount != NULL)
*modeCount = 0;
else
return NULL;
int outputIndex = getOutputIndex(device);
if (outputIndex < 0) {
ELOGTRACE("invalid device");
return NULL;
}
DrmOutput *output= &mOutputs[outputIndex];
if (!output->connected) {
ELOGTRACE("device is not connected");
return NULL;
}
if (output->connector->count_modes <= 0) {
ELOGTRACE("invalid count of modes");
return NULL;
}
*modeCount = output->connector->count_modes;
return output->connector->modes;
}
} // namespace intel
} // namespace android