return ret;
}
+static uint32_t
+u32distance(uint32_t a, uint32_t b)
+{
+ if (a < b)
+ return b - a;
+ else
+ return a - b;
+}
+
+/** Choose equivalent mode
+ *
+ * If the two modes are not equivalent, return NULL.
+ * Otherwise return the mode that is more likely to work in place of both.
+ *
+ * None of the fuzzy matching criteria in this function have any justification.
+ *
+ * typedef struct _drmModeModeInfo {
+ * uint32_t clock;
+ * uint16_t hdisplay, hsync_start, hsync_end, htotal, hskew;
+ * uint16_t vdisplay, vsync_start, vsync_end, vtotal, vscan;
+ *
+ * uint32_t vrefresh;
+ *
+ * uint32_t flags;
+ * uint32_t type;
+ * char name[DRM_DISPLAY_MODE_LEN];
+ * } drmModeModeInfo, *drmModeModeInfoPtr;
+ */
+static const drmModeModeInfo *
+drm_mode_pick_equivalent(const drmModeModeInfo *a, const drmModeModeInfo *b)
+{
+ uint32_t refresh_a, refresh_b;
+
+ if (a->hdisplay != b->hdisplay || a->vdisplay != b->vdisplay)
+ return NULL;
+
+ if (a->flags != b->flags)
+ return NULL;
+
+ /* kHz */
+ if (u32distance(a->clock, b->clock) > 500)
+ return NULL;
+
+ refresh_a = drm_refresh_rate_mHz(a);
+ refresh_b = drm_refresh_rate_mHz(b);
+ if (u32distance(refresh_a, refresh_b) > 50)
+ return NULL;
+
+ if ((a->type ^ b->type) & DRM_MODE_TYPE_PREFERRED) {
+ if (a->type & DRM_MODE_TYPE_PREFERRED)
+ return a;
+ else
+ return b;
+ }
+
+ return a;
+}
+
+/* If the given mode info is not already in the list, add it.
+ * If it is in the list, either keep the existing or replace it,
+ * depending on which one is "better".
+ */
+static int
+drm_output_try_add_mode(struct drm_output *output, const drmModeModeInfo *info)
+{
+ struct weston_mode *base;
+ struct drm_mode *mode;
+ struct drm_backend *backend;
+ const drmModeModeInfo *chosen = NULL;
+
+ assert(info);
+
+ wl_list_for_each(base, &output->base.mode_list, link) {
+ mode = to_drm_mode(base);
+ chosen = drm_mode_pick_equivalent(&mode->mode_info, info);
+ if (chosen)
+ break;
+ }
+
+ if (chosen == info) {
+ backend = to_drm_backend(output->base.compositor);
+ drm_output_destroy_mode(backend, mode);
+ chosen = NULL;
+ }
+
+ if (!chosen) {
+ mode = drm_output_add_mode(output, info);
+ if (!mode)
+ return -1;
+ }
+ /* else { the equivalent mode is already in the list } */
+
+ return 0;
+}
+
/** Rewrite the output's mode list
*
* @param output The output.
struct drm_backend *backend = to_drm_backend(output->base.compositor);
struct weston_head *head_base;
struct drm_head *head;
- struct drm_mode *mode;
int i;
+ int ret;
assert(!output->base.enabled);
drm_mode_list_destroy(backend, &output->base.mode_list);
- /* XXX: needs a strategy for combining mode lists from multiple heads */
- head_base = weston_output_get_first_head(&output->base);
- assert(head_base);
- head = to_drm_head(head_base);
-
- for (i = 0; i < head->connector->count_modes; i++) {
- mode = drm_output_add_mode(output, &head->connector->modes[i]);
- if (!mode)
- return -1;
+ wl_list_for_each(head_base, &output->base.head_list, output_link) {
+ head = to_drm_head(head_base);
+ for (i = 0; i < head->connector->count_modes; i++) {
+ ret = drm_output_try_add_mode(output,
+ &head->connector->modes[i]);
+ if (ret < 0)
+ return -1;
+ }
}
return 0;