hwc: Add support for frame rate change on HDMI devices
When an HDMI display is connected, we report all the available
configurations to the framework by translating the EDID data.
This information can be queried, and subsequently used to change
the frame rate of the HDMI display.
Note: this change does not provide support for changing the resolution
of the HDMI display.
Change-Id: I4b8b07e2886efe2a01480aed9d82d36075bb342c
diff --git a/libhdmi/hdmi.cpp b/libhdmi/hdmi.cpp
index 4fb7cfa..1aee664 100644
--- a/libhdmi/hdmi.cpp
+++ b/libhdmi/hdmi.cpp
@@ -101,15 +101,16 @@
}
readCEUnderscanInfo();
readResolution();
- // TODO: Move this to activate
/* Used for changing the resolution
- * getUserMode will get the preferred
- * mode set thru adb shell */
- mCurrentMode = getUserMode();
- if (mCurrentMode == -1) {
+ * getUserConfig will get the preferred
+ * config index set thru adb shell */
+ mActiveConfig = getUserConfig();
+ if (mActiveConfig == -1) {
//Get the best mode and set
- mCurrentMode = getBestMode();
+ mActiveConfig = getBestConfig();
}
+ // Set the mode corresponding to the active index
+ mCurrentMode = mEDIDModes[mActiveConfig];
setAttributes();
// set system property
property_set("hw.hdmiON", "1");
@@ -121,6 +122,14 @@
&& !strcmp(value, "true")) {
mMDPDownscaleEnabled = true;
}
+
+ // XXX: A debug property can be used to enable resolution change for
+ // testing purposes: debug.hwc.enable_resolution_change
+ mEnableResolutionChange = false;
+ if(property_get("debug.hwc.enable_resolution_change", value, "false")
+ && !strcmp(value, "true")) {
+ mEnableResolutionChange = true;
+ }
return 0;
}
@@ -425,8 +434,8 @@
return -1;
}
-/// Returns the user mode set(if any) using adb shell
-int HDMIDisplay::getUserMode() {
+/// Returns the index of the user mode set(if any) using adb shell
+int HDMIDisplay::getUserConfig() {
/* Based on the property set the resolution */
char property_value[PROPERTY_VALUE_MAX];
property_get("hw.hdmi.resolution", property_value, "-1");
@@ -434,15 +443,16 @@
// We dont support interlaced modes
if(isValidMode(mode) && !isInterlacedMode(mode)) {
ALOGD_IF(DEBUG, "%s: setting the HDMI mode = %d", __FUNCTION__, mode);
- return mode;
+ return getModeIndex(mode);
}
return -1;
}
-// Get the best mode for the current HD TV
-int HDMIDisplay::getBestMode() {
+// Get the index of the best mode for the current HD TV
+int HDMIDisplay::getBestConfig() {
int bestOrder = 0;
int bestMode = HDMI_VFRMT_640x480p60_4_3;
+ int bestModeIndex = -1;
// for all the edid read, get the best mode
for(int i = 0; i < mModeCount; i++) {
int mode = mEDIDModes[i];
@@ -450,9 +460,19 @@
if (order > bestOrder) {
bestOrder = order;
bestMode = mode;
+ bestModeIndex = i;
}
}
- return bestMode;
+ // If we fail to read from EDID when HDMI is connected, then
+ // mModeCount will be 0 and bestModeIndex will be invalid.
+ // In this case, we populate the mEDIDModes structure with
+ // a default mode at index 0.
+ if (bestModeIndex == -1) {
+ bestModeIndex = 0;
+ mModeCount = 1;
+ mEDIDModes[bestModeIndex] = bestMode;
+ }
+ return bestModeIndex;
}
inline bool HDMIDisplay::isValidMode(int ID)
@@ -696,4 +716,79 @@
mPrimaryWidth = primaryWidth;
}
+int HDMIDisplay::setActiveConfig(int newConfig) {
+ if(newConfig < 0 || newConfig > mModeCount) {
+ ALOGE("%s Invalid configuration %d", __FUNCTION__, newConfig);
+ return -EINVAL;
+ }
+
+ // XXX: Currently, we only support a change in frame rate.
+ // We need to validate the new config before proceeding.
+ if (!isValidConfigChange(newConfig)) {
+ ALOGE("%s Invalid configuration %d", __FUNCTION__, newConfig);
+ return -EINVAL;
+ }
+
+ mCurrentMode = mEDIDModes[newConfig];
+ mActiveConfig = newConfig;
+ activateDisplay();
+ ALOGD("%s config(%d) mode(%d)", __FUNCTION__, mActiveConfig, mCurrentMode);
+ return 0;
+}
+
+// returns false if the xres or yres of the new config do
+// not match the current config
+bool HDMIDisplay::isValidConfigChange(int newConfig) {
+ int newMode = mEDIDModes[newConfig];
+ uint32_t width = 0, height = 0, refresh = 0;
+ getAttrForConfig(newConfig, width, height, refresh);
+ return ((mXres == width) && (mYres == height)) || mEnableResolutionChange;
+}
+
+int HDMIDisplay::getModeIndex(int mode) {
+ int modeIndex = -1;
+ for(int i = 0; i < mModeCount; i++) {
+ if(mode == mEDIDModes[i]) {
+ modeIndex = i;
+ break;
+ }
+ }
+ return modeIndex;
+}
+
+int HDMIDisplay::getAttrForConfig(int config, uint32_t& xres,
+ uint32_t& yres, uint32_t& refresh) const {
+ if(config < 0 || config > mModeCount) {
+ ALOGE("%s Invalid configuration %d", __FUNCTION__, config);
+ return -EINVAL;
+ }
+ int mode = mEDIDModes[config];
+ uint32_t fps = 0;
+ // Retrieve the mode attributes from gEDIDData
+ for (int dataIndex = 0; dataIndex < gEDIDCount; dataIndex++) {
+ if (gEDIDData[dataIndex].mMode == mode) {
+ xres = gEDIDData[dataIndex].mWidth;
+ yres = gEDIDData[dataIndex].mHeight;
+ fps = gEDIDData[dataIndex].mFps;
+ }
+ }
+ refresh = (uint32_t) 1000000000l / fps;
+ ALOGD_IF(DEBUG, "%s xres(%d) yres(%d) fps(%d) refresh(%d)", __FUNCTION__,
+ xres, yres, fps, refresh);
+ return 0;
+}
+
+int HDMIDisplay::getDisplayConfigs(uint32_t* configs,
+ size_t* numConfigs) const {
+ if (*numConfigs <= 0) {
+ ALOGE("%s Invalid number of configs (%d)", __FUNCTION__, *numConfigs);
+ return -EINVAL;
+ }
+ *numConfigs = mModeCount;
+ for (int configIndex = 0; configIndex < mModeCount; configIndex++) {
+ configs[configIndex] = (uint32_t)configIndex;
+ }
+ return 0;
+}
+
};
diff --git a/libhdmi/hdmi.h b/libhdmi/hdmi.h
index 605d9be..d262a63 100644
--- a/libhdmi/hdmi.h
+++ b/libhdmi/hdmi.h
@@ -68,6 +68,11 @@
/* when HDMI is an EXTERNAL display, PRIMARY display attributes are needed
for scaling mode */
void setPrimaryAttributes(uint32_t primaryWidth, uint32_t primaryHeight);
+ int getActiveConfig() const { return mActiveConfig; };
+ int setActiveConfig(int newConfig);
+ int getAttrForConfig(int config, uint32_t& xres,
+ uint32_t& yres, uint32_t& refresh) const;
+ int getDisplayConfigs(uint32_t* configs, size_t* numConfigs) const;
private:
int getModeCount() const;
@@ -80,17 +85,26 @@
bool writeHPDOption(int userOption) const;
bool isValidMode(int mode);
int getModeOrder(int mode);
- int getUserMode();
- int getBestMode();
+ int getUserConfig();
+ int getBestConfig();
bool isInterlacedMode(int mode);
void resetInfo();
void setAttributes();
void getAttrForMode(uint32_t& width, uint32_t& height, uint32_t& fps);
int openDeviceNode(const char* node, int fileMode) const;
+ int getModeIndex(int mode);
+ bool isValidConfigChange(int newConfig);
int mFd;
int mFbNum;
+ // mCurrentMode is the HDMI video format that corresponds to the mEDIDMode
+ // entry referenced by mActiveConfig
int mCurrentMode;
+ // mActiveConfig is the index correponding to the currently active mode for
+ // the HDMI display. It basically indexes the mEDIDMode array
+ int mActiveConfig;
+ // mEDIDModes contains a list of HDMI video formats (modes) supported by the
+ // HDMI display
int mEDIDModes[64];
int mModeCount;
fb_var_screeninfo mVInfo;
@@ -102,6 +116,7 @@
// Downscale feature switch, set via system property
// sys.hwc.mdp_downscale_enabled
bool mMDPDownscaleEnabled;
+ bool mEnableResolutionChange;
int mDisplayId;
};
diff --git a/libhwcomposer/hwc.cpp b/libhwcomposer/hwc.cpp
index b5ba073..a7eb561 100644
--- a/libhwcomposer/hwc.cpp
+++ b/libhwcomposer/hwc.cpp
@@ -187,6 +187,12 @@
}
}
+static bool isHotPluggable(hwc_context_t *ctx, int dpy) {
+ return ((dpy == HWC_DISPLAY_EXTERNAL) ||
+ ((dpy == HWC_DISPLAY_PRIMARY) &&
+ ctx->mHDMIDisplay->isHDMIPrimaryDisplay()));
+}
+
static void reset(hwc_context_t *ctx, int numDisplays,
hwc_display_contents_1_t** displays) {
@@ -735,40 +741,54 @@
int hwc_getDisplayConfigs(struct hwc_composer_device_1* dev, int disp,
uint32_t* configs, size_t* numConfigs) {
- int ret = 0;
hwc_context_t* ctx = (hwc_context_t*)(dev);
- //Currently we allow only 1 config, reported as config id # 0
- //This config is passed in to getDisplayAttributes. Ignored for now.
+
+ Locker::Autolock _l(ctx->mDrawLock);
+ bool hotPluggable = isHotPluggable(ctx, disp);
+ bool isVirtualDisplay = (disp == HWC_DISPLAY_VIRTUAL);
+ // If hotpluggable or virtual displays are inactive return error
+ if ((hotPluggable || isVirtualDisplay) && !ctx->dpyAttr[disp].connected) {
+ ALOGE("%s display (%d) is inactive", __FUNCTION__, disp);
+ return -EINVAL;
+ }
+
+ if (*numConfigs <= 0) {
+ ALOGE("%s Invalid number of configs (%d)", __FUNCTION__, *numConfigs);
+ return -EINVAL;
+ }
+
switch(disp) {
case HWC_DISPLAY_PRIMARY:
- if(*numConfigs > 0) {
+ if (hotPluggable) {
+ ctx->mHDMIDisplay->getDisplayConfigs(configs, numConfigs);
+ } else {
configs[0] = 0;
*numConfigs = 1;
}
- ret = 0; //NO_ERROR
break;
case HWC_DISPLAY_EXTERNAL:
+ ctx->mHDMIDisplay->getDisplayConfigs(configs, numConfigs);
+ break;
case HWC_DISPLAY_VIRTUAL:
- ret = -1; //Not connected
- if(ctx->dpyAttr[disp].connected) {
- ret = 0; //NO_ERROR
- if(*numConfigs > 0) {
- configs[0] = 0;
- *numConfigs = 1;
- }
- }
+ configs[0] = 0;
+ *numConfigs = 1;
break;
}
- return ret;
+ return 0;
}
int hwc_getDisplayAttributes(struct hwc_composer_device_1* dev, int disp,
- uint32_t /*config*/, const uint32_t* attributes, int32_t* values) {
+ uint32_t config, const uint32_t* attributes, int32_t* values) {
hwc_context_t* ctx = (hwc_context_t*)(dev);
- //If hotpluggable displays(i.e, HDMI, WFD) are inactive return error
- if( (disp != HWC_DISPLAY_PRIMARY) && !ctx->dpyAttr[disp].connected) {
- return -1;
+
+ Locker::Autolock _l(ctx->mDrawLock);
+ bool hotPluggable = isHotPluggable(ctx, disp);
+ bool isVirtualDisplay = (disp == HWC_DISPLAY_VIRTUAL);
+ // If hotpluggable or virtual displays are inactive return error
+ if ((hotPluggable || isVirtualDisplay) && !ctx->dpyAttr[disp].connected) {
+ ALOGE("%s display (%d) is inactive", __FUNCTION__, disp);
+ return -EINVAL;
}
//From HWComposer
@@ -784,16 +804,27 @@
const size_t NUM_DISPLAY_ATTRIBUTES = (sizeof(DISPLAY_ATTRIBUTES) /
sizeof(DISPLAY_ATTRIBUTES)[0]);
+ uint32_t xres = 0, yres = 0, refresh = 0;
+ int ret = 0;
+ if (hotPluggable) {
+ ret = ctx->mHDMIDisplay->getAttrForConfig(config, xres, yres, refresh);
+ if(ret < 0) {
+ ALOGE("%s Error getting attributes for config %d", config);
+ return ret;
+ }
+ }
+
for (size_t i = 0; i < NUM_DISPLAY_ATTRIBUTES - 1; i++) {
switch (attributes[i]) {
case HWC_DISPLAY_VSYNC_PERIOD:
- values[i] = ctx->dpyAttr[disp].vsync_period;
+ values[i] =
+ hotPluggable ? refresh : ctx->dpyAttr[disp].vsync_period;
break;
case HWC_DISPLAY_WIDTH:
if (ctx->dpyAttr[disp].customFBSize)
values[i] = ctx->dpyAttr[disp].xres_new;
else
- values[i] = ctx->dpyAttr[disp].xres;
+ values[i] = hotPluggable ? xres : ctx->dpyAttr[disp].xres;
ALOGD("%s disp = %d, width = %d",__FUNCTION__, disp,
values[i]);
@@ -802,7 +833,7 @@
if (ctx->dpyAttr[disp].customFBSize)
values[i] = ctx->dpyAttr[disp].yres_new;
else
- values[i] = ctx->dpyAttr[disp].yres;
+ values[i] = hotPluggable ? yres : ctx->dpyAttr[disp].yres;
ALOGD("%s disp = %d, height = %d",__FUNCTION__, disp,
values[i]);
break;
@@ -849,15 +880,49 @@
strlcpy(buff, aBuf.string(), buff_len);
}
-int hwc_getActiveConfig(struct hwc_composer_device_1* /*dev*/, int /*disp*/) {
- //Supports only the default config (0th index) for now
- return 0;
+int hwc_getActiveConfig(struct hwc_composer_device_1* dev, int disp)
+{
+ hwc_context_t* ctx = (hwc_context_t*)(dev);
+
+ Locker::Autolock _l(ctx->mDrawLock);
+ bool hotPluggable = isHotPluggable(ctx, disp);
+ bool isVirtualDisplay = (disp == HWC_DISPLAY_VIRTUAL);
+ // If hotpluggable or virtual displays are inactive return error
+ if ((hotPluggable || isVirtualDisplay) && !ctx->dpyAttr[disp].connected) {
+ ALOGE("%s display (%d) is inactive", __FUNCTION__, disp);
+ return -EINVAL;
+ }
+
+ // For use cases when primary panel is the default interface we only have
+ // the default config (0th index)
+ if (!hotPluggable) {
+ return 0;
+ }
+
+ return ctx->mHDMIDisplay->getActiveConfig();
}
-int hwc_setActiveConfig(struct hwc_composer_device_1* /*dev*/, int /*disp*/,
- int index) {
- //Supports only the default config (0th index) for now
- return (index == 0) ? index : -EINVAL;
+int hwc_setActiveConfig(struct hwc_composer_device_1* dev, int disp, int index)
+{
+ hwc_context_t* ctx = (hwc_context_t*)(dev);
+
+ Locker::Autolock _l(ctx->mDrawLock);
+ bool hotPluggable = isHotPluggable(ctx, disp);
+ bool isVirtualDisplay = (disp == HWC_DISPLAY_VIRTUAL);
+ // If hotpluggable or virtual displays are inactive return error
+ if ((hotPluggable || isVirtualDisplay) && !ctx->dpyAttr[disp].connected) {
+ ALOGE("%s display (%d) is inactive", __FUNCTION__, disp);
+ return -EINVAL;
+ }
+
+ // For use cases when primary panel is the default interface we only have
+ // the default config (0th index)
+ if (!hotPluggable) {
+ // Primary and virtual supports only the default config (0th index)
+ return (index == 0) ? index : -EINVAL;
+ }
+
+ return ctx->mHDMIDisplay->setActiveConfig(index);
}
static int hwc_device_close(struct hw_device_t *dev)