1278798Shselasky/* $NetBSD: pickmode.c,v 1.3 2011/04/09 18:22:31 jdc Exp $ */
2278798Shselasky/* $FreeBSD$ */
3278798Shselasky
4278798Shselasky/*-
5278798Shselasky * Copyright (c) 2006 The NetBSD Foundation
6278798Shselasky * All rights reserved.
7278798Shselasky *
8278798Shselasky * this code was contributed to The NetBSD Foundation by Michael Lorenz
9278798Shselasky *
10278798Shselasky * Redistribution and use in source and binary forms, with or without
11278798Shselasky * modification, are permitted provided that the following conditions
12278798Shselasky * are met:
13278798Shselasky * 1. Redistributions of source code must retain the above copyright
14278798Shselasky *    notice, this list of conditions and the following disclaimer.
15278798Shselasky * 2. Redistributions in binary form must reproduce the above copyright
16278798Shselasky *    notice, this list of conditions and the following disclaimer in the
17278798Shselasky *    documentation and/or other materials provided with the distribution.
18278798Shselasky *
19278798Shselasky * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS
20278798Shselasky * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21278798Shselasky * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22278798Shselasky * ARE DISCLAIMED.  IN NO EVENT SHALL THE NETBSD FOUNDATION BE LIABLE
23278798Shselasky * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24278798Shselasky * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
25278798Shselasky * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26278798Shselasky * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27278798Shselasky * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28278798Shselasky * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29278798Shselasky * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30278798Shselasky */
31278798Shselasky
32278798Shselasky#include <sys/cdefs.h>
33278798Shselasky__FBSDID("$FreeBSD$");
34278798Shselasky
35278798Shselasky#include <sys/param.h>
36278798Shselasky#include <sys/libkern.h>
37278798Shselasky#include <dev/videomode/videomode.h>
38278798Shselasky#include "opt_videomode.h"
39278798Shselasky
40278798Shselasky#ifdef PICKMODE_DEBUG
41278798Shselasky#define DPRINTF printf
42278798Shselasky#else
43278798Shselasky#define DPRINTF while (0) printf
44278798Shselasky#endif
45278798Shselasky
46278798Shselaskyconst struct videomode *
47278798Shselaskypick_mode_by_dotclock(int width, int height, int dotclock)
48278798Shselasky{
49278798Shselasky	const struct videomode *this, *best = NULL;
50278798Shselasky	int i;
51278798Shselasky
52278798Shselasky	DPRINTF("%s: looking for %d x %d at up to %d kHz\n", __func__, width,
53278798Shselasky	    height, dotclock);
54278798Shselasky	for (i = 0; i < videomode_count; i++) {
55278798Shselasky		this = &videomode_list[i];
56278798Shselasky		if ((this->hdisplay != width) || (this->vdisplay != height) ||
57278798Shselasky		    (this->dot_clock > dotclock))
58278798Shselasky			continue;
59278798Shselasky		if (best != NULL) {
60278798Shselasky			if (this->dot_clock > best->dot_clock)
61278798Shselasky				best = this;
62278798Shselasky		} else
63278798Shselasky			best = this;
64278798Shselasky	}
65278798Shselasky	if (best != NULL)
66278798Shselasky		DPRINTF("found %s\n", best->name);
67278798Shselasky
68278798Shselasky	return best;
69278798Shselasky}
70278798Shselasky
71278798Shselaskyconst struct videomode *
72278798Shselaskypick_mode_by_ref(int width, int height, int refresh)
73278798Shselasky{
74278798Shselasky	const struct videomode *this, *best = NULL;
75278798Shselasky	int mref, closest = 1000, i, diff;
76278798Shselasky
77278798Shselasky	DPRINTF("%s: looking for %d x %d at up to %d Hz\n", __func__, width,
78278798Shselasky	    height, refresh);
79278798Shselasky	for (i = 0; i < videomode_count; i++) {
80278798Shselasky
81278798Shselasky		this = &videomode_list[i];
82278798Shselasky		mref = this->dot_clock * 1000 / (this->htotal * this->vtotal);
83278798Shselasky		diff = abs(mref - refresh);
84278798Shselasky		if ((this->hdisplay != width) || (this->vdisplay != height))
85278798Shselasky			continue;
86278798Shselasky		DPRINTF("%s in %d hz, diff %d\n", this->name, mref, diff);
87278798Shselasky		if (best != NULL) {
88278798Shselasky			if (diff < closest) {
89278798Shselasky				best = this;
90278798Shselasky				closest = diff;
91278798Shselasky			}
92278798Shselasky		} else {
93278798Shselasky			best = this;
94278798Shselasky			closest = diff;
95278798Shselasky		}
96278798Shselasky	}
97278798Shselasky	if (best != NULL)
98278798Shselasky		DPRINTF("found %s %d\n", best->name, best->dot_clock);
99278798Shselasky
100278798Shselasky	return best;
101278798Shselasky}
102278798Shselasky
103278798Shselaskystatic inline void
104278798Shselaskyswap_modes(struct videomode *left, struct videomode *right)
105278798Shselasky{
106278798Shselasky	struct videomode temp;
107278798Shselasky
108278798Shselasky	temp = *left;
109278798Shselasky	*left = *right;
110278798Shselasky	*right = temp;
111278798Shselasky}
112278798Shselasky
113278798Shselasky/*
114278798Shselasky * Sort modes by refresh rate, aspect ratio (*), then resolution.
115278798Shselasky * Preferred mode or largest mode is first in the list and other modes
116278798Shselasky * are sorted on closest match to that mode.
117278798Shselasky * (*) Note that the aspect ratio calculation treats "close" aspect ratios
118278798Shselasky * (within 12.5%) as the same for this purpose.
119278798Shselasky */
120278798Shselasky#define	DIVIDE(x, y)	(((x) + ((y) / 2)) / (y))
121278798Shselaskyvoid
122278798Shselaskysort_modes(struct videomode *modes, struct videomode **preferred, int nmodes)
123278798Shselasky{
124278798Shselasky	int aspect, refresh, hbest, vbest, abest, atemp, rbest, rtemp;
125278798Shselasky	int i, j;
126278798Shselasky	struct videomode *mtemp = NULL;
127278798Shselasky
128278798Shselasky	if (nmodes < 2)
129278798Shselasky		return;
130278798Shselasky
131278798Shselasky	if (*preferred != NULL) {
132278798Shselasky		/* Put the preferred mode first in the list */
133278798Shselasky		aspect = (*preferred)->hdisplay * 100 / (*preferred)->vdisplay;
134278798Shselasky		refresh = DIVIDE(DIVIDE((*preferred)->dot_clock * 1000,
135278798Shselasky		    (*preferred)->htotal), (*preferred)->vtotal);
136278798Shselasky		if (*preferred != modes) {
137278798Shselasky			swap_modes(*preferred, modes);
138278798Shselasky			*preferred = modes;
139278798Shselasky		}
140278798Shselasky	} else {
141278798Shselasky		/*
142278798Shselasky		 * Find the largest horizontal and vertical mode and put that
143278798Shselasky		 * first in the list.  Preferred refresh rate is taken from
144278798Shselasky		 * the first mode of this size.
145278798Shselasky		 */
146278798Shselasky		hbest = 0;
147278798Shselasky		vbest = 0;
148278798Shselasky		for (i = 0; i < nmodes; i++) {
149278798Shselasky			if (modes[i].hdisplay > hbest) {
150278798Shselasky				hbest = modes[i].hdisplay;
151278798Shselasky				vbest = modes[i].vdisplay;
152278798Shselasky				mtemp = &modes[i];
153278798Shselasky			} else if (modes[i].hdisplay == hbest &&
154278798Shselasky			    modes[i].vdisplay > vbest) {
155278798Shselasky				vbest = modes[i].vdisplay;
156278798Shselasky				mtemp = &modes[i];
157278798Shselasky			}
158278798Shselasky		}
159278798Shselasky		aspect = mtemp->hdisplay * 100 / mtemp->vdisplay;
160278798Shselasky		refresh = DIVIDE(DIVIDE(mtemp->dot_clock * 1000,
161278798Shselasky		    mtemp->htotal), mtemp->vtotal);
162278798Shselasky		if (mtemp != modes)
163278798Shselasky			swap_modes(mtemp, modes);
164278798Shselasky	}
165278798Shselasky
166278798Shselasky	/* Sort other modes by refresh rate, aspect ratio, then resolution */
167278798Shselasky	for (j = 1; j < nmodes - 1; j++) {
168278798Shselasky		rbest = 1000;
169278798Shselasky		abest = 1000;
170278798Shselasky		hbest = 0;
171278798Shselasky		vbest = 0;
172278798Shselasky		for (i = j; i < nmodes; i++) {
173278798Shselasky			rtemp = abs(refresh -
174278798Shselasky			    DIVIDE(DIVIDE(modes[i].dot_clock * 1000,
175278798Shselasky			    modes[i].htotal), modes[i].vtotal));
176278798Shselasky			atemp = (modes[i].hdisplay * 100 / modes[i].vdisplay);
177278798Shselasky			if (rtemp < rbest) {
178278798Shselasky				rbest = rtemp;
179278798Shselasky				mtemp = &modes[i];
180278798Shselasky			}
181278798Shselasky			if (rtemp == rbest) {
182278798Shselasky				/* Treat "close" aspect ratios as identical */
183278798Shselasky				if (abs(abest - atemp) > (abest / 8) &&
184278798Shselasky				    abs(aspect - atemp) < abs(aspect - abest)) {
185278798Shselasky					abest = atemp;
186278798Shselasky					mtemp = &modes[i];
187278798Shselasky				}
188278798Shselasky				if (atemp == abest ||
189278798Shselasky				    abs(abest - atemp) <= (abest / 8)) {
190278798Shselasky					if (modes[i].hdisplay > hbest) {
191278798Shselasky						hbest = modes[i].hdisplay;
192278798Shselasky						mtemp = &modes[i];
193278798Shselasky					}
194278798Shselasky					if (modes[i].hdisplay == hbest &&
195278798Shselasky					    modes[i].vdisplay > vbest) {
196278798Shselasky						vbest = modes[i].vdisplay;
197278798Shselasky						mtemp = &modes[i];
198278798Shselasky					}
199278798Shselasky				}
200278798Shselasky			}
201278798Shselasky		}
202278798Shselasky		if (mtemp != &modes[j])
203278798Shselasky			swap_modes(mtemp, &modes[j]);
204278798Shselasky	}
205278798Shselasky}
206