/* -*- 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-crtc-kms.h"

#include "backends/meta-backend-private.h"
#include "backends/meta-logical-monitor.h"
#include "backends/native/meta-gpu-kms.h"
#include "backends/native/meta-output-kms.h"
#include "backends/native/meta-kms-device.h"
#include "backends/native/meta-kms-plane.h"
#include "backends/native/meta-kms-update.h"

#define ALL_TRANSFORMS_MASK ((1 << META_MONITOR_N_TRANSFORMS) - 1)

typedef struct _MetaCrtcKms
{
  MetaKmsCrtc *kms_crtc;

  MetaKmsPlane *primary_plane;
} MetaCrtcKms;

static GQuark kms_crtc_crtc_kms_quark;

gboolean
meta_crtc_kms_is_transform_handled (MetaCrtc             *crtc,
                                    MetaMonitorTransform  transform)
{
  MetaCrtcKms *crtc_kms = crtc->driver_private;

  if (!crtc_kms->primary_plane)
    return FALSE;

  return meta_kms_plane_is_transform_handled (crtc_kms->primary_plane,
                                              transform);
}

void
meta_crtc_kms_apply_transform (MetaCrtc               *crtc,
                               MetaKmsPlaneAssignment *kms_plane_assignment)
{
  MetaCrtcKms *crtc_kms = crtc->driver_private;
  MetaMonitorTransform hw_transform;

  hw_transform = crtc->transform;
  if (!meta_crtc_kms_is_transform_handled (crtc, hw_transform))
    hw_transform = META_MONITOR_TRANSFORM_NORMAL;
  if (!meta_crtc_kms_is_transform_handled (crtc, hw_transform))
    return;

  meta_kms_plane_update_set_rotation (crtc_kms->primary_plane,
                                      kms_plane_assignment,
                                      hw_transform);
}

void
meta_crtc_kms_assign_primary_plane (MetaCrtc      *crtc,
                                    uint32_t       fb_id,
                                    MetaKmsUpdate *kms_update)
{
  MetaRectangle logical_monitor_rect;
  int x, y;
  MetaFixed16Rectangle src_rect;
  MetaFixed16Rectangle dst_rect;
  MetaKmsCrtc *kms_crtc;
  MetaKmsDevice *kms_device;
  MetaKmsPlane *primary_kms_plane;
  MetaKmsPlaneAssignment *plane_assignment;

  logical_monitor_rect =
    meta_logical_monitor_get_layout (crtc->logical_monitor);
  x = crtc->rect.x - logical_monitor_rect.x;
  y = crtc->rect.y - logical_monitor_rect.y;
  src_rect = (MetaFixed16Rectangle) {
    .x = meta_fixed_16_from_int (x),
    .y = meta_fixed_16_from_int (y),
    .width = meta_fixed_16_from_int (crtc->rect.width),
    .height = meta_fixed_16_from_int (crtc->rect.height),
  };
  dst_rect = (MetaFixed16Rectangle) {
    .x = meta_fixed_16_from_int (0),
    .y = meta_fixed_16_from_int (0),
    .width = meta_fixed_16_from_int (crtc->rect.width),
    .height = meta_fixed_16_from_int (crtc->rect.height),
  };

  kms_crtc = meta_crtc_kms_get_kms_crtc (crtc);
  kms_device = meta_kms_crtc_get_device (kms_crtc);
  primary_kms_plane = meta_kms_device_get_primary_plane_for (kms_device,
                                                             kms_crtc);
  plane_assignment = meta_kms_update_assign_plane (kms_update,
                                                   kms_crtc,
                                                   primary_kms_plane,
                                                   fb_id,
                                                   src_rect,
                                                   dst_rect);
  meta_crtc_kms_apply_transform (crtc, plane_assignment);
}

static GList *
generate_crtc_connector_list (MetaGpu  *gpu,
                              MetaCrtc *crtc)
{
  GList *connectors = NULL;
  GList *l;

  for (l = meta_gpu_get_outputs (gpu); l; l = l->next)
    {
      MetaOutput *output = l->data;
      MetaCrtc *assigned_crtc;

      assigned_crtc = meta_output_get_assigned_crtc (output);
      if (assigned_crtc == crtc)
        {
          MetaKmsConnector *kms_connector =
            meta_output_kms_get_kms_connector (output);

          connectors = g_list_prepend (connectors, kms_connector);
        }
    }

  return connectors;
}

void
meta_crtc_kms_set_mode (MetaCrtc      *crtc,
                        MetaKmsUpdate *kms_update)
{
  MetaGpu *gpu = meta_crtc_get_gpu (crtc);
  GList *connectors;
  drmModeModeInfo *mode;

  connectors = generate_crtc_connector_list (gpu, crtc);

  if (connectors)
    {
      mode = crtc->current_mode->driver_private;

      g_debug ("Setting CRTC (%ld) mode to %s", crtc->crtc_id, mode->name);
    }
  else
    {
      mode = NULL;

      g_debug ("Unsetting CRTC (%ld) mode", crtc->crtc_id);
    }

  meta_kms_update_mode_set (kms_update,
                            meta_crtc_kms_get_kms_crtc (crtc),
                            g_steal_pointer (&connectors),
                            mode);
}

void
meta_crtc_kms_page_flip (MetaCrtc                      *crtc,
                         const MetaKmsPageFlipFeedback *page_flip_feedback,
                         gpointer                       user_data,
                         MetaKmsUpdate                 *kms_update)
{
  meta_kms_update_page_flip (kms_update,
                             meta_crtc_kms_get_kms_crtc (crtc),
                             page_flip_feedback,
                             user_data);
}

MetaKmsCrtc *
meta_crtc_kms_get_kms_crtc (MetaCrtc *crtc)
{
  MetaCrtcKms *crtc_kms = crtc->driver_private;

  return crtc_kms->kms_crtc;
}

/**
 * meta_crtc_kms_get_modifiers:
 * @crtc: a #MetaCrtc object that has to be a #MetaCrtcKms
 * @format: a DRM pixel format
 *
 * Returns a pointer to a #GArray containing all the supported
 * modifiers for the given DRM pixel format on the CRTC's primary
 * plane. The array element type is uint64_t.
 *
 * The caller must not modify or destroy the array or its contents.
 *
 * Returns NULL if the modifiers are not known or the format is not
 * supported.
 */
GArray *
meta_crtc_kms_get_modifiers (MetaCrtc *crtc,
                             uint32_t  format)
{
  MetaCrtcKms *crtc_kms = crtc->driver_private;

  return meta_kms_plane_get_modifiers_for_format (crtc_kms->primary_plane,
                                                  format);
}

/**
 * meta_crtc_kms_copy_drm_format_list:
 * @crtc: a #MetaCrtc object that has to be a #MetaCrtcKms
 *
 * Returns a new #GArray that the caller must destroy. The array
 * contains all the DRM pixel formats the CRTC supports on
 * its primary plane. The array element type is uint32_t.
 */
GArray *
meta_crtc_kms_copy_drm_format_list (MetaCrtc *crtc)
{
  MetaCrtcKms *crtc_kms = crtc->driver_private;

  return meta_kms_plane_copy_drm_format_list (crtc_kms->primary_plane);
}

/**
 * meta_crtc_kms_supports_format:
 * @crtc: a #MetaCrtc object that has to be a #MetaCrtcKms
 * @drm_format: a DRM pixel format
 *
 * Returns true if the CRTC supports the format on its primary plane.
 */
gboolean
meta_crtc_kms_supports_format (MetaCrtc *crtc,
                               uint32_t  drm_format)
{
  MetaCrtcKms *crtc_kms = crtc->driver_private;

  return meta_kms_plane_is_format_supported (crtc_kms->primary_plane,
                                             drm_format);
}

MetaCrtc *
meta_crtc_kms_from_kms_crtc (MetaKmsCrtc *kms_crtc)
{
  return g_object_get_qdata (G_OBJECT (kms_crtc), kms_crtc_crtc_kms_quark);
}

static void
meta_crtc_destroy_notify (MetaCrtc *crtc)
{
  g_free (crtc->driver_private);
}

MetaCrtc *
meta_create_kms_crtc (MetaGpuKms  *gpu_kms,
                      MetaKmsCrtc *kms_crtc)
{
  MetaGpu *gpu = META_GPU (gpu_kms);
  MetaKmsDevice *kms_device;
  MetaCrtc *crtc;
  MetaCrtcKms *crtc_kms;
  MetaKmsPlane *primary_plane;
  const MetaKmsCrtcState *crtc_state;

  kms_device = meta_gpu_kms_get_kms_device (gpu_kms);
  primary_plane = meta_kms_device_get_primary_plane_for (kms_device,
                                                         kms_crtc);
  crtc_state = meta_kms_crtc_get_current_state (kms_crtc);

  crtc = g_object_new (META_TYPE_CRTC, NULL);
  crtc->gpu = gpu;
  crtc->crtc_id = meta_kms_crtc_get_id (kms_crtc);
  crtc->rect = crtc_state->rect;
  crtc->is_dirty = FALSE;
  crtc->transform = META_MONITOR_TRANSFORM_NORMAL;
  crtc->all_transforms = ALL_TRANSFORMS_MASK;

  if (crtc_state->is_drm_mode_valid)
    {
      GList *l;

      for (l = meta_gpu_get_modes (gpu); l; l = l->next)
        {
          MetaCrtcMode *mode = l->data;

          if (meta_drm_mode_equal (&crtc_state->drm_mode, mode->driver_private))
            {
              crtc->current_mode = mode;
              break;
            }
        }
    }

  crtc_kms = g_new0 (MetaCrtcKms, 1);
  crtc_kms->kms_crtc = kms_crtc;
  crtc_kms->primary_plane = primary_plane;

  crtc->driver_private = crtc_kms;
  crtc->driver_notify = (GDestroyNotify) meta_crtc_destroy_notify;

  if (!kms_crtc_crtc_kms_quark)
    {
      kms_crtc_crtc_kms_quark =
        g_quark_from_static_string ("meta-kms-crtc-crtc-kms-quark");
    }

  g_object_set_qdata (G_OBJECT (kms_crtc), kms_crtc_crtc_kms_quark, crtc);

  return crtc;
}
