1 /*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define LOG_TAG "hwc-display-configs"
18
19 #include "HwcDisplayConfigs.h"
20
21 #include <cmath>
22 #include <cstring>
23
24 #include "drm/DrmConnector.h"
25 #include "utils/log.h"
26
27 constexpr uint32_t kHeadlessModeDisplayWidthMm = 163;
28 constexpr uint32_t kHeadlessModeDisplayHeightMm = 122;
29 constexpr uint32_t kHeadlessModeDisplayWidthPx = 1024;
30 constexpr uint32_t kHeadlessModeDisplayHeightPx = 768;
31 constexpr uint32_t kHeadlessModeDisplayVRefresh = 60;
32 constexpr uint32_t kSyncLen = 10;
33 constexpr uint32_t kBackPorch = 10;
34 constexpr uint32_t kFrontPorch = 10;
35 constexpr uint32_t kHzInKHz = 1000;
36
37 namespace android {
38
39 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
40 uint32_t HwcDisplayConfigs::last_config_id = 1;
41
GenFakeMode(uint16_t width,uint16_t height)42 void HwcDisplayConfigs::GenFakeMode(uint16_t width, uint16_t height) {
43 hwc_configs.clear();
44
45 last_config_id++;
46 preferred_config_id = active_config_id = last_config_id;
47 auto headless_drm_mode_info = (drmModeModeInfo){
48 .hdisplay = width,
49 .vdisplay = height,
50 .vrefresh = kHeadlessModeDisplayVRefresh,
51 .name = "VIRTUAL-MODE",
52 };
53
54 if (width == 0 || height == 0) {
55 strcpy(headless_drm_mode_info.name, "HEADLESS-MODE");
56 headless_drm_mode_info.hdisplay = kHeadlessModeDisplayWidthPx;
57 headless_drm_mode_info.vdisplay = kHeadlessModeDisplayHeightPx;
58 }
59
60 /* We need a valid mode to pass the kernel validation */
61
62 headless_drm_mode_info.hsync_start = headless_drm_mode_info.hdisplay +
63 kFrontPorch;
64 headless_drm_mode_info.hsync_end = headless_drm_mode_info.hsync_start +
65 kSyncLen;
66 headless_drm_mode_info.htotal = headless_drm_mode_info.hsync_end + kBackPorch;
67
68 headless_drm_mode_info.vsync_start = headless_drm_mode_info.vdisplay +
69 kFrontPorch;
70 headless_drm_mode_info.vsync_end = headless_drm_mode_info.vsync_start +
71 kSyncLen;
72 headless_drm_mode_info.vtotal = headless_drm_mode_info.vsync_end + kBackPorch;
73
74 headless_drm_mode_info.clock = (headless_drm_mode_info.htotal *
75 headless_drm_mode_info.vtotal *
76 headless_drm_mode_info.vrefresh) /
77 kHzInKHz;
78
79 hwc_configs[active_config_id] = (HwcDisplayConfig){
80 .id = active_config_id,
81 .group_id = 1,
82 .mode = DrmMode(&headless_drm_mode_info),
83 };
84
85 mm_width = kHeadlessModeDisplayWidthMm;
86 mm_height = kHeadlessModeDisplayHeightMm;
87 }
88
89 // NOLINTNEXTLINE (readability-function-cognitive-complexity): Fixme
Update(DrmConnector & connector)90 HWC2::Error HwcDisplayConfigs::Update(DrmConnector &connector) {
91 /* In case UpdateModes will fail we will still have one mode for headless
92 * mode
93 */
94 GenFakeMode(0, 0);
95 /* Read real configs */
96 auto ret = connector.UpdateModes();
97 if (ret != 0) {
98 ALOGE("Failed to update display modes %d", ret);
99 return HWC2::Error::BadDisplay;
100 }
101
102 if (connector.GetModes().empty()) {
103 ALOGE("No modes reported by KMS");
104 return HWC2::Error::BadDisplay;
105 }
106
107 hwc_configs.clear();
108 mm_width = connector.GetMmWidth();
109 mm_height = connector.GetMmHeight();
110
111 preferred_config_id = 0;
112 uint32_t preferred_config_group_id = 0;
113
114 auto first_config_id = last_config_id;
115 uint32_t last_group_id = 1;
116
117 /* Group modes */
118 for (const auto &mode : connector.GetModes()) {
119 /* Find group for the new mode or create new group */
120 uint32_t group_found = 0;
121 for (auto &hwc_config : hwc_configs) {
122 if (mode.GetRawMode().hdisplay ==
123 hwc_config.second.mode.GetRawMode().hdisplay &&
124 mode.GetRawMode().vdisplay ==
125 hwc_config.second.mode.GetRawMode().vdisplay) {
126 group_found = hwc_config.second.group_id;
127 }
128 }
129 if (group_found == 0) {
130 group_found = last_group_id++;
131 }
132
133 bool disabled = false;
134 if ((mode.GetRawMode().flags & DRM_MODE_FLAG_3D_MASK) != 0) {
135 ALOGI("Disabling display mode %s (Modes with 3D flag aren't supported)",
136 mode.GetName().c_str());
137 disabled = true;
138 }
139
140 /* Add config */
141 hwc_configs[last_config_id] = {
142 .id = last_config_id,
143 .group_id = group_found,
144 .mode = mode,
145 .disabled = disabled,
146 };
147
148 /* Chwck if the mode is preferred */
149 if ((mode.GetRawMode().type & DRM_MODE_TYPE_PREFERRED) != 0 &&
150 preferred_config_id == 0) {
151 preferred_config_id = last_config_id;
152 preferred_config_group_id = group_found;
153 }
154
155 last_config_id++;
156 }
157
158 /* We must have preferred mode. Set first mode as preferred
159 * in case KMS haven't reported anything. */
160 if (preferred_config_id == 0) {
161 preferred_config_id = first_config_id;
162 preferred_config_group_id = 1;
163 }
164
165 for (uint32_t group = 1; group < last_group_id; group++) {
166 bool has_interlaced = false;
167 bool has_progressive = false;
168 for (auto &hwc_config : hwc_configs) {
169 if (hwc_config.second.group_id != group || hwc_config.second.disabled) {
170 continue;
171 }
172
173 if (hwc_config.second.IsInterlaced()) {
174 has_interlaced = true;
175 } else {
176 has_progressive = true;
177 }
178 }
179
180 auto has_both = has_interlaced && has_progressive;
181 if (!has_both) {
182 continue;
183 }
184
185 bool group_contains_preferred_interlaced = false;
186 if (group == preferred_config_group_id &&
187 hwc_configs[preferred_config_id].IsInterlaced()) {
188 group_contains_preferred_interlaced = true;
189 }
190
191 for (auto &hwc_config : hwc_configs) {
192 if (hwc_config.second.group_id != group || hwc_config.second.disabled) {
193 continue;
194 }
195
196 auto disable = group_contains_preferred_interlaced
197 ? !hwc_config.second.IsInterlaced()
198 : hwc_config.second.IsInterlaced();
199
200 if (disable) {
201 ALOGI(
202 "Group %i: Disabling display mode %s (This group should consist "
203 "of %s modes)",
204 group, hwc_config.second.mode.GetName().c_str(),
205 group_contains_preferred_interlaced ? "interlaced" : "progressive");
206
207 hwc_config.second.disabled = true;
208 }
209 }
210 }
211
212 /* Group should not contain 2 modes with FPS delta less than ~1HZ
213 * otherwise android.graphics.cts.SetFrameRateTest CTS will fail
214 */
215 constexpr float kMinFpsDelta = 1.0; // FPS
216 for (uint32_t m1 = first_config_id; m1 < last_config_id; m1++) {
217 for (uint32_t m2 = first_config_id; m2 < last_config_id; m2++) {
218 if (m1 != m2 && hwc_configs[m1].group_id == hwc_configs[m2].group_id &&
219 !hwc_configs[m1].disabled && !hwc_configs[m2].disabled &&
220 fabsf(hwc_configs[m1].mode.GetVRefresh() -
221 hwc_configs[m2].mode.GetVRefresh()) < kMinFpsDelta) {
222 ALOGI(
223 "Group %i: Disabling display mode %s (Refresh rate value is "
224 "too close to existing mode %s)",
225 hwc_configs[m2].group_id, hwc_configs[m2].mode.GetName().c_str(),
226 hwc_configs[m1].mode.GetName().c_str());
227
228 hwc_configs[m2].disabled = true;
229 }
230 }
231 }
232
233 return HWC2::Error::None;
234 }
235
236 } // namespace android
237