| /* |
| // 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 <HwcTrace.h> |
| #include <IDisplayDevice.h> |
| #include <DrmConfig.h> |
| #include <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) { |
| WTRACE("Drm object has been initialized"); |
| return true; |
| } |
| |
| const char *path = DrmConfig::getDrmPath(); |
| mDrmFd = open(path, O_RDWR, 0); |
| if (mDrmFd < 0) { |
| ETRACE("failed to open Drm, error: %s", strerror(errno)); |
| return false; |
| } |
| DTRACE("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) { |
| ETRACE("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]) { |
| ETRACE("fail to get drm resources connectors, error: %s", strerror(errno)); |
| continue; |
| } |
| |
| connector = drmModeGetConnector(mDrmFd, resources->connectors[i]); |
| if (!connector) { |
| ETRACE("drmModeGetConnector failed"); |
| continue; |
| } |
| |
| if (connector->connector_type != DrmConfig::getDrmConnector(device)) { |
| drmModeFreeConnector(connector); |
| continue; |
| } |
| |
| if (connector->connection != DRM_MODE_CONNECTED) { |
| ITRACE("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) { |
| ITRACE("Drm connector has encoder attached on device %d", device); |
| output->encoder = drmModeGetEncoder(mDrmFd, connector->encoder_id); |
| if (!output->encoder) { |
| ETRACE("failed to get encoder from a known encoder id"); |
| // fall through to get an encoder |
| } |
| } |
| if (!output->encoder) { |
| ITRACE("getting encoder for device %d", device); |
| drmModeEncoderPtr encoder; |
| for (int j = 0; j < resources->count_encoders; j++) { |
| if (!resources->encoders || !resources->encoders[j]) { |
| ETRACE("fail to get drm resources encoders, error: %s", strerror(errno)); |
| continue; |
| } |
| |
| encoder = drmModeGetEncoder(mDrmFd, resources->encoders[i]); |
| if (!encoder) { |
| ETRACE("drmModeGetEncoder failed"); |
| continue; |
| } |
| if (encoder->encoder_type == DrmConfig::getDrmEncoder(device)) { |
| output->encoder = encoder; |
| break; |
| } |
| drmModeFreeEncoder(encoder); |
| encoder = NULL; |
| } |
| } |
| if (!output->encoder) { |
| ETRACE("failed to get drm encoder"); |
| break; |
| } |
| |
| // get an attached crtc or spare crtc |
| if (output->encoder->crtc_id) { |
| ITRACE("Drm encoder has crtc attached on device %d", device); |
| output->crtc = drmModeGetCrtc(mDrmFd, output->encoder->crtc_id); |
| if (!output->crtc) { |
| ETRACE("failed to get crtc from a known crtc id"); |
| // fall through to get a spare crtc |
| } |
| } |
| if (!output->crtc) { |
| ITRACE("getting crtc for device %d", device); |
| drmModeCrtcPtr crtc; |
| for (int j = 0; j < resources->count_crtcs; j++) { |
| if (!resources->crtcs || !resources->crtcs[j]) { |
| ETRACE("fail to get drm resources crtcs, error: %s", strerror(errno)); |
| continue; |
| } |
| |
| crtc = drmModeGetCrtc(mDrmFd, resources->crtcs[j]); |
| if (!crtc) { |
| ETRACE("drmModeGetCrtc failed"); |
| continue; |
| } |
| if (crtc->buffer_id == 0) { |
| output->crtc = crtc; |
| break; |
| } |
| drmModeFreeCrtc(crtc); |
| } |
| } |
| if (!output->crtc) { |
| ETRACE("failed to get drm crtc"); |
| break; |
| } |
| |
| // current mode |
| if (output->crtc->mode_valid) { |
| ITRACE("mode is valid, kernel mode settings"); |
| memcpy(&output->mode, &output->crtc->mode, sizeof(drmModeModeInfo)); |
| ret = true; |
| } else { |
| ITRACE("mode is invalid, setting preferred mode"); |
| ret = initDrmMode(outputIndex); |
| } |
| |
| if (outputIndex == OUTPUT_PRIMARY) { |
| if (!readIoctl(DRM_PSB_PANEL_ORIENTATION, &output->panelOrientation, sizeof(int))) { |
| ETRACE("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 |
| WTRACE("device %d is disabled?", device); |
| ret = true; |
| } |
| resetOutput(outputIndex); |
| } else if (output->connected) { |
| ITRACE("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) { |
| VTRACE("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) { |
| WTRACE("Setting mode on invalid device %d", device); |
| return false; |
| } |
| |
| int outputIndex = getOutputIndex(device); |
| if (outputIndex < 0 ) { |
| ETRACE("invalid device"); |
| return false; |
| } |
| |
| DrmOutput *output= &mOutputs[outputIndex]; |
| if (!output->connected) { |
| ETRACE("device is not connected"); |
| return false; |
| } |
| |
| if (output->connector->count_modes <= 0) { |
| ETRACE("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) { |
| WTRACE("Setting mode on invalid device %d", device); |
| return false; |
| } |
| |
| int outputIndex = getOutputIndex(device); |
| if (outputIndex < 0 ) { |
| ETRACE("invalid device"); |
| return false; |
| } |
| |
| DrmOutput *output= &mOutputs[outputIndex]; |
| if (!output->connected) { |
| ETRACE("device is not connected"); |
| return false; |
| } |
| |
| if (output->connector->count_modes <= 0) { |
| ETRACE("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) { |
| ETRACE("drm is not initialized"); |
| return false; |
| } |
| |
| if (!data || !size) { |
| ETRACE("invalid parameters"); |
| return false; |
| } |
| |
| err = drmCommandWriteRead(mDrmFd, cmd, data, size); |
| if (err) { |
| WTRACE("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) { |
| ETRACE("drm is not initialized"); |
| return false; |
| } |
| |
| if (!data || !size) { |
| ETRACE("invalid parameters"); |
| return false; |
| } |
| |
| err = drmCommandWrite(mDrmFd, cmd, data, size); |
| if (err) { |
| WTRACE("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) { |
| ETRACE("drm is not initialized"); |
| return false; |
| } |
| |
| if (!data || !size) { |
| ETRACE("invalid parameters"); |
| return false; |
| } |
| |
| err = drmCommandRead(mDrmFd, cmd, data, size); |
| if (err) { |
| WTRACE("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) { |
| ETRACE("device is not connected"); |
| return false; |
| } |
| |
| if (output->mode.hdisplay == 0 || output->mode.vdisplay == 0) { |
| ETRACE("invalid width or height"); |
| return false; |
| } |
| |
| memcpy(&mode, &output->mode, sizeof(drmModeModeInfo)); |
| 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) { |
| ETRACE("device is not connected"); |
| return false; |
| } |
| |
| width = output->connector->mmWidth; |
| height = output->connector->mmHeight; |
| 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); |
| |
| 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) { |
| ETRACE("invalid mode %d", mode); |
| return false; |
| } |
| |
| DrmOutput *out = &mOutputs[output]; |
| if (!out->connected) { |
| ETRACE("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) { |
| ETRACE("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( |
| (buffer_handle_t)output->fbHandle); |
| output->fbHandle = 0; |
| } |
| } |
| |
| bool Drm::initDrmMode(int outputIndex) |
| { |
| DrmOutput *output= &mOutputs[outputIndex]; |
| if (output->connector->count_modes <= 0) { |
| ETRACE("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; |
| buffer_handle_t oldFbHandle = 0; |
| |
| drmModeModeInfo currentMode; |
| memcpy(¤tMode, &output->mode, sizeof(drmModeModeInfo)); |
| |
| if (isSameDrmMode(mode, ¤tMode)) |
| return true; |
| |
| |
| if (output->fbId) { |
| oldFbId = output->fbId; |
| output->fbId = 0; |
| } |
| |
| if (output->fbHandle) { |
| oldFbHandle = output->fbHandle; |
| output->fbHandle = 0; |
| } |
| |
| // allocate frame buffer |
| int stride = 0; |
| output->fbHandle = Hwcomposer::getInstance().getBufferManager()->allocFrameBuffer( |
| mode->hdisplay, mode->vdisplay, &stride); |
| if (output->fbHandle == 0) { |
| ETRACE("failed to allocate frame buffer"); |
| return false; |
| } |
| |
| uint32_t bo_handles[4] = {0}; |
| uint32_t pitches[4] = {0}; |
| uint32_t offsets[4] = {0}; |
| int ret = 0; |
| |
| // We use bo_handles[0] and bo_handles[1] to store buffer_handle_t |
| // to support 32 and 64 platforms. |
| bo_handles[0] = ((unsigned long)(output->fbHandle)) & 0xffffffff; |
| bo_handles[1] = ((unsigned long)(output->fbHandle) >> 32) & 0xffffffff; |
| pitches[0] = stride * DrmConfig::getFrameBufferBpp() / 8; |
| |
| ret = drmModeAddFB2( |
| mDrmFd, |
| mode->hdisplay, |
| mode->vdisplay, |
| DrmConfig::convertHalFormatToDrmFormat(DrmConfig::getFrameBufferFormat()), |
| bo_handles, |
| pitches, |
| offsets, |
| &output->fbId, |
| 0); |
| if (ret != 0) { |
| ETRACE("drmModeAddFB2 failed, error: %d", ret); |
| return false; |
| } |
| |
| ITRACE("mode set: %dx%d@%dHz", mode->hdisplay, mode->vdisplay, mode->vrefresh); |
| |
| ret = drmModeSetCrtc(mDrmFd, output->crtc->crtc_id, output->fbId, 0, 0, |
| &output->connector->connector_id, 1, mode); |
| if (ret == 0) { |
| //save mode |
| memcpy(&output->mode, mode, sizeof(drmModeModeInfo)); |
| } else { |
| ETRACE("drmModeSetCrtc failed. error: %d", ret); |
| } |
| |
| if (oldFbId) { |
| drmModeRmFB(mDrmFd, oldFbId); |
| } |
| |
| if (oldFbHandle) { |
| Hwcomposer::getInstance().getBufferManager()->freeFrameBuffer((buffer_handle_t)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: |
| ETRACE("invalid display device"); |
| break; |
| } |
| |
| return -1; |
| } |
| |
| int Drm::getPanelOrientation(int device) |
| { |
| int outputIndex = getOutputIndex(device); |
| if (outputIndex < 0) { |
| ETRACE("invalid device"); |
| return PANEL_ORIENTATION_0; |
| } |
| |
| DrmOutput *output= &mOutputs[outputIndex]; |
| if (output->connected == false) { |
| ETRACE("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) { |
| ETRACE("invalid device"); |
| return NULL; |
| } |
| |
| DrmOutput *output= &mOutputs[outputIndex]; |
| if (!output->connected) { |
| ETRACE("device is not connected"); |
| return NULL; |
| } |
| |
| if (output->connector->count_modes <= 0) { |
| ETRACE("invalid count of modes"); |
| return NULL; |
| } |
| |
| *modeCount = output->connector->count_modes; |
| return output->connector->modes; |
| } |
| |
| } // namespace intel |
| } // namespace android |
| |