/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2013-2017 Red Hat * Copyright (C) 2018 DisplayLink (UK) Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include "config.h" #include "backends/native/meta-output-kms.h" #include #include #include #include "backends/meta-crtc.h" #include "backends/native/meta-kms.h" #include "backends/native/meta-kms-connector.h" #include "backends/native/meta-kms-device.h" #include "backends/native/meta-kms-mode.h" #include "backends/native/meta-kms-update.h" #include "backends/native/meta-kms-utils.h" #include "backends/native/meta-crtc-kms.h" #include "backends/native/meta-crtc-mode-kms.h" #define SYNC_TOLERANCE 0.01 /* 1 percent */ struct _MetaOutputKms { MetaOutputNative parent; MetaKmsConnector *kms_connector; }; G_DEFINE_TYPE (MetaOutputKms, meta_output_kms, META_TYPE_OUTPUT_NATIVE) MetaKmsConnector * meta_output_kms_get_kms_connector (MetaOutputKms *output_kms) { return output_kms->kms_connector; } void meta_output_kms_set_underscan (MetaOutputKms *output_kms, MetaKmsUpdate *kms_update) { MetaOutput *output = META_OUTPUT (output_kms); const MetaOutputInfo *output_info = meta_output_get_info (output); if (!output_info->supports_underscanning) return; if (meta_output_is_underscanning (output)) { MetaCrtc *crtc; const MetaCrtcConfig *crtc_config; const MetaCrtcModeInfo *crtc_mode_info; uint64_t hborder, vborder; crtc = meta_output_get_assigned_crtc (output); crtc_config = meta_crtc_get_config (crtc); crtc_mode_info = meta_crtc_mode_get_info (crtc_config->mode); hborder = MIN (128, (uint64_t) round (crtc_mode_info->width * 0.05)); vborder = MIN (128, (uint64_t) round (crtc_mode_info->height * 0.05)); g_debug ("Setting underscan of connector %s to %" G_GUINT64_FORMAT " x %" G_GUINT64_FORMAT, meta_kms_connector_get_name (output_kms->kms_connector), hborder, vborder); meta_kms_update_set_underscanning (kms_update, output_kms->kms_connector, hborder, vborder); } else { g_debug ("Unsetting underscan of connector %s", meta_kms_connector_get_name (output_kms->kms_connector)); meta_kms_update_unset_underscanning (kms_update, output_kms->kms_connector); } } static MetaPrivacyScreenState meta_output_kms_get_privacy_screen_state (MetaOutput *output) { MetaOutputKms *output_kms = META_OUTPUT_KMS (output); const MetaKmsConnectorState *connector_state; connector_state = meta_kms_connector_get_current_state (output_kms->kms_connector); return connector_state->privacy_screen_state; } static gboolean meta_output_kms_set_privacy_screen_enabled (MetaOutput *output, gboolean enabled, GError **error) { MetaGpu *gpu; MetaKms *kms; MetaKmsDevice *kms_device; MetaKmsUpdate *kms_update; MetaOutputKms *output_kms = META_OUTPUT_KMS (output); MetaKmsConnector *connector = meta_output_kms_get_kms_connector (output_kms); if (!meta_kms_connector_is_privacy_screen_supported (connector)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "No privacy screen support"); return FALSE; } gpu = meta_output_get_gpu (META_OUTPUT (output_kms)); kms_device = meta_gpu_kms_get_kms_device (META_GPU_KMS (gpu)); kms = meta_kms_device_get_kms (kms_device); kms_update = meta_kms_ensure_pending_update (kms, kms_device); meta_kms_update_set_privacy_screen (kms_update, connector, enabled); return TRUE; } uint32_t meta_output_kms_get_connector_id (MetaOutputKms *output_kms) { return meta_kms_connector_get_id (output_kms->kms_connector); } gboolean meta_output_kms_can_clone (MetaOutputKms *output_kms, MetaOutputKms *other_output_kms) { return meta_kms_connector_can_clone (output_kms->kms_connector, other_output_kms->kms_connector); } static GBytes * meta_output_kms_read_edid (MetaOutputNative *output_native) { MetaOutputKms *output_kms = META_OUTPUT_KMS (output_native); const MetaKmsConnectorState *connector_state; GBytes *edid_data; connector_state = meta_kms_connector_get_current_state (output_kms->kms_connector); edid_data = connector_state->edid_data; if (!edid_data) return NULL; return g_bytes_new_from_bytes (edid_data, 0, g_bytes_get_size (edid_data)); } static void add_common_modes (MetaOutputInfo *output_info, MetaGpuKms *gpu_kms) { MetaCrtcMode *crtc_mode; GPtrArray *array; unsigned i; unsigned max_hdisplay = 0; unsigned max_vdisplay = 0; float max_refresh_rate = 0.0; uint32_t max_pixel_clock = 0; MetaKmsDevice *kms_device; MetaKmsModeFlag flag_filter; GList *l; for (i = 0; i < output_info->n_modes; i++) { const MetaCrtcModeInfo *crtc_mode_info = meta_crtc_mode_get_info (output_info->modes[i]); max_hdisplay = MAX (max_hdisplay, crtc_mode_info->width); max_vdisplay = MAX (max_vdisplay, crtc_mode_info->height); max_refresh_rate = MAX (max_refresh_rate, crtc_mode_info->refresh_rate); max_pixel_clock = MAX (max_pixel_clock, crtc_mode_info->pixel_clock_khz); } max_refresh_rate = MAX (max_refresh_rate, 60.0); max_refresh_rate *= (1 + SYNC_TOLERANCE); kms_device = meta_gpu_kms_get_kms_device (gpu_kms); array = g_ptr_array_new (); if (max_hdisplay > max_vdisplay) flag_filter = META_KMS_MODE_FLAG_FALLBACK_LANDSCAPE; else flag_filter = META_KMS_MODE_FLAG_FALLBACK_PORTRAIT; for (l = meta_kms_device_get_fallback_modes (kms_device); l; l = l->next) { MetaKmsMode *fallback_mode = l->data; const drmModeModeInfo *drm_mode; float refresh_rate; gboolean is_duplicate = FALSE; if (!(meta_kms_mode_get_flags (fallback_mode) & flag_filter)) continue; drm_mode = meta_kms_mode_get_drm_mode (fallback_mode); refresh_rate = meta_calculate_drm_mode_refresh_rate (drm_mode); if (drm_mode->hdisplay > max_hdisplay || drm_mode->vdisplay > max_vdisplay || refresh_rate > max_refresh_rate || drm_mode->clock > max_pixel_clock) continue; for (i = 0; i < output_info->n_modes; i++) { const MetaCrtcModeInfo *crtc_mode_info = meta_crtc_mode_get_info (output_info->modes[i]); if (drm_mode->hdisplay == crtc_mode_info->width && drm_mode->vdisplay == crtc_mode_info->height && fabs (1 - (refresh_rate / crtc_mode_info->refresh_rate)) < SYNC_TOLERANCE) { is_duplicate = TRUE; break; } } if (is_duplicate) continue; crtc_mode = meta_gpu_kms_get_mode_from_kms_mode (gpu_kms, fallback_mode); g_ptr_array_add (array, crtc_mode); } output_info->modes = g_renew (MetaCrtcMode *, output_info->modes, output_info->n_modes + array->len); memcpy (output_info->modes + output_info->n_modes, array->pdata, array->len * sizeof (MetaCrtcMode *)); output_info->n_modes += array->len; g_ptr_array_free (array, TRUE); } static int compare_modes (const void *one, const void *two) { MetaCrtcMode *crtc_mode_one = *(MetaCrtcMode **) one; MetaCrtcMode *crtc_mode_two = *(MetaCrtcMode **) two; const MetaCrtcModeInfo *crtc_mode_info_one = meta_crtc_mode_get_info (crtc_mode_one); const MetaCrtcModeInfo *crtc_mode_info_two = meta_crtc_mode_get_info (crtc_mode_two); if (crtc_mode_info_one->width != crtc_mode_info_two->width) return crtc_mode_info_one->width > crtc_mode_info_two->width ? -1 : 1; if (crtc_mode_info_one->height != crtc_mode_info_two->height) return crtc_mode_info_one->height > crtc_mode_info_two->height ? -1 : 1; if (crtc_mode_info_one->refresh_rate != crtc_mode_info_two->refresh_rate) return (crtc_mode_info_one->refresh_rate > crtc_mode_info_two->refresh_rate ? -1 : 1); return g_strcmp0 (meta_crtc_mode_get_name (crtc_mode_one), meta_crtc_mode_get_name (crtc_mode_two)); } static gboolean init_output_modes (MetaOutputInfo *output_info, MetaGpuKms *gpu_kms, MetaKmsConnector *kms_connector, GError **error) { const MetaKmsConnectorState *connector_state; GList *l; int i; connector_state = meta_kms_connector_get_current_state (kms_connector); output_info->preferred_mode = NULL; output_info->n_modes = g_list_length (connector_state->modes); output_info->modes = g_new0 (MetaCrtcMode *, output_info->n_modes); for (l = connector_state->modes, i = 0; l; l = l->next, i++) { MetaKmsMode *kms_mode = l->data; const drmModeModeInfo *drm_mode = meta_kms_mode_get_drm_mode (kms_mode); MetaCrtcMode *crtc_mode; crtc_mode = meta_gpu_kms_get_mode_from_kms_mode (gpu_kms, kms_mode); output_info->modes[i] = crtc_mode; if (drm_mode->type & DRM_MODE_TYPE_PREFERRED) output_info->preferred_mode = output_info->modes[i]; } if (connector_state->has_scaling) { meta_topic (META_DEBUG_KMS, "Adding common modes to connector %u on %s", meta_kms_connector_get_id (kms_connector), meta_gpu_kms_get_file_path (gpu_kms)); add_common_modes (output_info, gpu_kms); } if (!output_info->modes) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "No modes available"); return FALSE; } qsort (output_info->modes, output_info->n_modes, sizeof (MetaCrtcMode *), compare_modes); if (!output_info->preferred_mode) output_info->preferred_mode = output_info->modes[0]; return TRUE; } static MetaConnectorType meta_kms_connector_type_from_drm (uint32_t drm_connector_type) { g_warn_if_fail (drm_connector_type < META_CONNECTOR_TYPE_META); return (MetaConnectorType) drm_connector_type; } MetaOutputKms * meta_output_kms_new (MetaGpuKms *gpu_kms, MetaKmsConnector *kms_connector, MetaOutput *old_output, GError **error) { MetaGpu *gpu = META_GPU (gpu_kms); uint32_t connector_id; uint32_t gpu_id; g_autoptr (MetaOutputInfo) output_info = NULL; MetaOutput *output; MetaOutputKms *output_kms; uint32_t drm_connector_type; const MetaKmsConnectorState *connector_state; GArray *crtcs; GList *l; gpu_id = meta_gpu_kms_get_id (gpu_kms); connector_id = meta_kms_connector_get_id (kms_connector); output_info = meta_output_info_new (); output_info->name = g_strdup (meta_kms_connector_get_name (kms_connector)); connector_state = meta_kms_connector_get_current_state (kms_connector); output_info->panel_orientation_transform = connector_state->panel_orientation_transform; if (meta_monitor_transform_is_rotated (output_info->panel_orientation_transform)) { output_info->width_mm = connector_state->height_mm; output_info->height_mm = connector_state->width_mm; } else { output_info->width_mm = connector_state->width_mm; output_info->height_mm = connector_state->height_mm; } if (!init_output_modes (output_info, gpu_kms, kms_connector, error)) return NULL; crtcs = g_array_new (FALSE, FALSE, sizeof (MetaCrtc *)); for (l = meta_gpu_get_crtcs (gpu); l; l = l->next) { MetaCrtcKms *crtc_kms = META_CRTC_KMS (l->data); MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms); uint32_t crtc_idx; crtc_idx = meta_kms_crtc_get_idx (kms_crtc); if (connector_state->common_possible_crtcs & (1 << crtc_idx)) g_array_append_val (crtcs, crtc_kms); } output_info->n_possible_crtcs = crtcs->len; output_info->possible_crtcs = (MetaCrtc **) g_array_free (crtcs, FALSE); output_info->suggested_x = connector_state->suggested_x; output_info->suggested_y = connector_state->suggested_y; output_info->hotplug_mode_update = connector_state->hotplug_mode_update; output_info->supports_underscanning = meta_kms_connector_is_underscanning_supported (kms_connector); meta_output_info_parse_edid (output_info, connector_state->edid_data); drm_connector_type = meta_kms_connector_get_connector_type (kms_connector); output_info->connector_type = meta_kms_connector_type_from_drm (drm_connector_type); output_info->tile_info = connector_state->tile_info; output = g_object_new (META_TYPE_OUTPUT_KMS, "id", ((uint64_t) gpu_id << 32) | connector_id, "gpu", gpu, "info", output_info, NULL); output_kms = META_OUTPUT_KMS (output); output_kms->kms_connector = kms_connector; if (connector_state->current_crtc_id) { for (l = meta_gpu_get_crtcs (gpu); l; l = l->next) { MetaCrtc *crtc = l->data; if (meta_crtc_get_id (crtc) == connector_state->current_crtc_id) { MetaOutputAssignment output_assignment; if (old_output) { output_assignment = (MetaOutputAssignment) { .is_primary = meta_output_is_primary (old_output), .is_presentation = meta_output_is_presentation (old_output), }; } else { output_assignment = (MetaOutputAssignment) { .is_primary = FALSE, .is_presentation = FALSE, }; } meta_output_assign_crtc (output, crtc, &output_assignment); break; } } } else { meta_output_unassign_crtc (output); } return output_kms; } static void meta_output_kms_init (MetaOutputKms *output_kms) { } static void meta_output_kms_class_init (MetaOutputKmsClass *klass) { MetaOutputNativeClass *output_native_class = META_OUTPUT_NATIVE_CLASS (klass); MetaOutputClass *output_class = META_OUTPUT_CLASS (klass); output_class->get_privacy_screen_state = meta_output_kms_get_privacy_screen_state; output_class->set_privacy_screen_enabled = meta_output_kms_set_privacy_screen_enabled; output_native_class->read_edid = meta_output_kms_read_edid; }