drm/linux-core/drm_modes.c

332 lines
8.2 KiB
C

/*
* Copyright © 1997-2003 by The XFree86 Project, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Except as contained in this notice, the name of the copyright holder(s)
* and author(s) shall not be used in advertising or otherwise to promote
* the sale, use or other dealings in this Software without prior written
* authorization from the copyright holder(s) and author(s).
*/
/*
* Copyright © 2007 Dave Airlie
*/
#include <linux/list.h>
#include "drmP.h"
#include "drm.h"
#include "drm_crtc.h"
void drm_mode_debug_printmodeline(struct drm_device *dev,
struct drm_display_mode *mode)
{
DRM_DEBUG("Modeline \"%s\" %d %d %d %d %d %d %d %d %d %d\n",
mode->name, mode->vrefresh, mode->clock,
mode->hdisplay, mode->hsync_start,
mode->hsync_end, mode->htotal,
mode->vdisplay, mode->vsync_start,
mode->vsync_end, mode->vtotal);
}
EXPORT_SYMBOL(drm_mode_debug_printmodeline);
void drm_mode_list_concat(struct list_head *head, struct list_head *new)
{
struct list_head *entry, *tmp;
list_for_each_safe(entry, tmp, head) {
list_move_tail(entry, new);
}
}
int drm_mode_width(struct drm_display_mode *mode)
{
return mode->hdisplay;
}
EXPORT_SYMBOL(drm_mode_width);
int drm_mode_height(struct drm_display_mode *mode)
{
return mode->vdisplay;
}
EXPORT_SYMBOL(drm_mode_height);
int drm_mode_vrefresh(struct drm_display_mode *mode)
{
int refresh = 0;
if (mode->vrefresh > 0)
refresh = mode->vrefresh;
else if (mode->htotal > 0 && mode->vtotal > 0) {
refresh = ((mode->clock * 1000) * 1000) / mode->htotal / mode->vtotal;
if (mode->flags & V_INTERLACE)
refresh *= 2;
if (mode->flags & V_DBLSCAN)
refresh /= 2;
if (mode->vscan > 1)
refresh /= mode->vscan;
}
return refresh;
}
EXPORT_SYMBOL(drm_mode_vrefresh);
void drm_mode_set_crtcinfo(struct drm_display_mode *p, int adjust_flags)
{
if ((p == NULL) || ((p->type & DRM_MODE_TYPE_CRTC_C) == DRM_MODE_TYPE_BUILTIN))
return;
p->crtc_hdisplay = p->hdisplay;
p->crtc_hsync_start = p->hsync_start;
p->crtc_hsync_end = p->hsync_end;
p->crtc_htotal = p->htotal;
p->crtc_hskew = p->hskew;
p->crtc_vdisplay = p->vdisplay;
p->crtc_vsync_start = p->vsync_start;
p->crtc_vsync_end = p->vsync_end;
p->crtc_vtotal = p->vtotal;
if (p->flags & V_INTERLACE) {
if (adjust_flags & CRTC_INTERLACE_HALVE_V) {
p->crtc_vdisplay /= 2;
p->crtc_vsync_start /= 2;
p->crtc_vsync_end /= 2;
p->crtc_vtotal /= 2;
}
p->crtc_vtotal |= 1;
}
if (p->flags & V_DBLSCAN) {
p->crtc_vdisplay *= 2;
p->crtc_vsync_start *= 2;
p->crtc_vsync_end *= 2;
p->crtc_vtotal *= 2;
}
if (p->vscan > 1) {
p->crtc_vdisplay *= p->vscan;
p->crtc_vsync_start *= p->vscan;
p->crtc_vsync_end *= p->vscan;
p->crtc_vtotal *= p->vscan;
}
p->crtc_vblank_start = min(p->crtc_vsync_start, p->crtc_vdisplay);
p->crtc_vblank_end = min(p->crtc_vsync_end, p->crtc_vtotal);
p->crtc_hblank_start = min(p->crtc_hsync_start, p->crtc_hdisplay);
p->crtc_hblank_end = min(p->crtc_hsync_end, p->crtc_htotal);
p->crtc_hadjusted = false;
p->crtc_vadjusted = false;
}
EXPORT_SYMBOL(drm_mode_set_crtcinfo);
struct drm_display_mode *drm_mode_duplicate(struct drm_device *dev, struct drm_display_mode *mode)
{
struct drm_display_mode *nmode;
int new_id;
nmode = drm_crtc_mode_create(dev);
if (!nmode)
return NULL;
new_id = nmode->mode_id;
*nmode = *mode;
nmode->mode_id = new_id;
INIT_LIST_HEAD(&nmode->head);
return nmode;
}
EXPORT_SYMBOL(drm_mode_duplicate);
bool drm_mode_equal(struct drm_display_mode *mode1, struct drm_display_mode *mode2)
{
if (mode1->clock == mode2->clock &&
mode1->hdisplay == mode2->hdisplay &&
mode1->hsync_start == mode2->hsync_start &&
mode1->hsync_end == mode2->hsync_end &&
mode1->htotal == mode2->htotal &&
mode1->hskew == mode2->hskew &&
mode1->vdisplay == mode2->vdisplay &&
mode1->vsync_start == mode2->vsync_start &&
mode1->vsync_end == mode2->vsync_end &&
mode1->vtotal == mode2->vtotal &&
mode1->vscan == mode2->vscan &&
mode1->flags == mode2->flags)
return true;
return false;
}
EXPORT_SYMBOL(drm_mode_equal);
void drm_mode_validate_size(struct drm_device *dev,
struct list_head *mode_list,
int maxX, int maxY, int maxPitch)
{
struct drm_display_mode *mode;
list_for_each_entry(mode, mode_list, head) {
if (maxPitch > 0 && mode->hdisplay > maxPitch)
mode->status = MODE_BAD_WIDTH;
if (maxX > 0 && mode->hdisplay > maxX)
mode->status = MODE_VIRTUAL_X;
if (maxY > 0 && mode->vdisplay > maxY)
mode->status = MODE_VIRTUAL_Y;
}
}
EXPORT_SYMBOL(drm_mode_validate_size);
void drm_mode_validate_clocks(struct drm_device *dev,
struct list_head *mode_list,
int *min, int *max, int n_ranges)
{
struct drm_display_mode *mode;
int i;
list_for_each_entry(mode, mode_list, head) {
bool good = false;
for (i = 0; i < n_ranges; i++) {
if (mode->clock >= min[i] && mode->clock <= max[i]) {
good = true;
break;
}
}
if (!good)
mode->status = MODE_CLOCK_RANGE;
}
}
EXPORT_SYMBOL(drm_mode_validate_clocks);
void drm_mode_prune_invalid(struct drm_device *dev,
struct list_head *mode_list, bool verbose)
{
struct drm_display_mode *mode, *t;
list_for_each_entry_safe(mode, t, mode_list, head) {
if (mode->status != MODE_OK) {
list_del(&mode->head);
if (verbose)
DRM_DEBUG("Not using %s mode %d\n", mode->name, mode->status);
kfree(mode);
}
}
}
static int drm_mode_compare(struct list_head *lh_a, struct list_head *lh_b)
{
struct drm_display_mode *a = list_entry(lh_a, struct drm_display_mode, head);
struct drm_display_mode *b = list_entry(lh_b, struct drm_display_mode, head);
int diff;
diff = ((b->type & DRM_MODE_TYPE_PREFERRED) != 0) -
((a->type & DRM_MODE_TYPE_PREFERRED) != 0);
if (diff)
return diff;
diff = b->hdisplay * b->vdisplay - a->hdisplay * a->vdisplay;
if (diff)
return diff;
diff = b->clock - a->clock;
return diff;
}
/* list sort from Mark J Roberts (mjr@znex.org) */
void list_sort(struct list_head *head, int (*cmp)(struct list_head *a, struct list_head *b))
{
struct list_head *p, *q, *e, *list, *tail, *oldhead;
int insize, nmerges, psize, qsize, i;
list = head->next;
list_del(head);
insize = 1;
for (;;) {
p = oldhead = list;
list = tail = NULL;
nmerges = 0;
while (p) {
nmerges++;
q = p;
psize = 0;
for (i = 0; i < insize; i++) {
psize++;
q = q->next == oldhead ? NULL : q->next;
if (!q)
break;
}
qsize = insize;
while (psize > 0 || (qsize > 0 && q)) {
if (!psize) {
e = q;
q = q->next;
qsize--;
if (q == oldhead)
q = NULL;
} else if (!qsize || !q) {
e = p;
p = p->next;
psize--;
if (p == oldhead)
p = NULL;
} else if (cmp(p, q) <= 0) {
e = p;
p = p->next;
psize--;
if (p == oldhead)
p = NULL;
} else {
e = q;
q = q->next;
qsize--;
if (q == oldhead)
q = NULL;
}
if (tail)
tail->next = e;
else
list = e;
e->prev = tail;
tail = e;
}
p = q;
}
tail->next = list;
list->prev = tail;
if (nmerges <= 1)
break;
insize *= 2;
}
head->next = list;
head->prev = list->prev;
list->prev->next = head;
list->prev = head;
}
void drm_mode_sort(struct list_head *mode_list)
{
list_sort(mode_list, drm_mode_compare);
}