1/* $NetBSD: pickmode.c,v 1.3 2011/04/09 18:22:31 jdc Exp $ */
2
3/*-
4 * Copyright (c) 2006 The NetBSD Foundation
5 * All rights reserved.
6 *
7 * this code was contributed to The NetBSD Foundation by Michael Lorenz
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS
19 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED.  IN NO EVENT SHALL THE NETBSD FOUNDATION BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
24 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include <sys/param.h>
32#include <sys/libkern.h>
33#include <dev/videomode/videomode.h>
34#include "opt_videomode.h"
35
36#ifdef PICKMODE_DEBUG
37#define DPRINTF printf
38#else
39#define DPRINTF while (0) printf
40#endif
41
42const struct videomode *
43pick_mode_by_dotclock(int width, int height, int dotclock)
44{
45	const struct videomode *this, *best = NULL;
46	int i;
47
48	DPRINTF("%s: looking for %d x %d at up to %d kHz\n", __func__, width,
49	    height, dotclock);
50	for (i = 0; i < videomode_count; i++) {
51		this = &videomode_list[i];
52		if ((this->hdisplay != width) || (this->vdisplay != height) ||
53		    (this->dot_clock > dotclock))
54			continue;
55		if (best != NULL) {
56			if (this->dot_clock > best->dot_clock)
57				best = this;
58		} else
59			best = this;
60	}
61	if (best != NULL)
62		DPRINTF("found %s\n", best->name);
63
64	return best;
65}
66
67const struct videomode *
68pick_mode_by_ref(int width, int height, int refresh)
69{
70	const struct videomode *this, *best = NULL;
71	int mref, closest = 1000, i, diff;
72
73	DPRINTF("%s: looking for %d x %d at up to %d Hz\n", __func__, width,
74	    height, refresh);
75	for (i = 0; i < videomode_count; i++) {
76		this = &videomode_list[i];
77		mref = this->dot_clock * 1000 / (this->htotal * this->vtotal);
78		diff = abs(mref - refresh);
79		if ((this->hdisplay != width) || (this->vdisplay != height))
80			continue;
81		DPRINTF("%s in %d hz, diff %d\n", this->name, mref, diff);
82		if (best != NULL) {
83			if (diff < closest) {
84				best = this;
85				closest = diff;
86			}
87		} else {
88			best = this;
89			closest = diff;
90		}
91	}
92	if (best != NULL)
93		DPRINTF("found %s %d\n", best->name, best->dot_clock);
94
95	return best;
96}
97
98static inline void
99swap_modes(struct videomode *left, struct videomode *right)
100{
101	struct videomode temp;
102
103	temp = *left;
104	*left = *right;
105	*right = temp;
106}
107
108/*
109 * Sort modes by refresh rate, aspect ratio (*), then resolution.
110 * Preferred mode or largest mode is first in the list and other modes
111 * are sorted on closest match to that mode.
112 * (*) Note that the aspect ratio calculation treats "close" aspect ratios
113 * (within 12.5%) as the same for this purpose.
114 */
115#define	DIVIDE(x, y)	(((x) + ((y) / 2)) / (y))
116void
117sort_modes(struct videomode *modes, struct videomode **preferred, int nmodes)
118{
119	int aspect, refresh, hbest, vbest, abest, atemp, rbest, rtemp;
120	int i, j;
121	struct videomode *mtemp = NULL;
122
123	if (nmodes < 2)
124		return;
125
126	if (*preferred != NULL) {
127		/* Put the preferred mode first in the list */
128		aspect = (*preferred)->hdisplay * 100 / (*preferred)->vdisplay;
129		refresh = DIVIDE(DIVIDE((*preferred)->dot_clock * 1000,
130		    (*preferred)->htotal), (*preferred)->vtotal);
131		if (*preferred != modes) {
132			swap_modes(*preferred, modes);
133			*preferred = modes;
134		}
135	} else {
136		/*
137		 * Find the largest horizontal and vertical mode and put that
138		 * first in the list.  Preferred refresh rate is taken from
139		 * the first mode of this size.
140		 */
141		hbest = 0;
142		vbest = 0;
143		for (i = 0; i < nmodes; i++) {
144			if (modes[i].hdisplay > hbest) {
145				hbest = modes[i].hdisplay;
146				vbest = modes[i].vdisplay;
147				mtemp = &modes[i];
148			} else if (modes[i].hdisplay == hbest &&
149			    modes[i].vdisplay > vbest) {
150				vbest = modes[i].vdisplay;
151				mtemp = &modes[i];
152			}
153		}
154		aspect = mtemp->hdisplay * 100 / mtemp->vdisplay;
155		refresh = DIVIDE(DIVIDE(mtemp->dot_clock * 1000,
156		    mtemp->htotal), mtemp->vtotal);
157		if (mtemp != modes)
158			swap_modes(mtemp, modes);
159	}
160
161	/* Sort other modes by refresh rate, aspect ratio, then resolution */
162	for (j = 1; j < nmodes - 1; j++) {
163		rbest = 1000;
164		abest = 1000;
165		hbest = 0;
166		vbest = 0;
167		for (i = j; i < nmodes; i++) {
168			rtemp = abs(refresh -
169			    DIVIDE(DIVIDE(modes[i].dot_clock * 1000,
170			    modes[i].htotal), modes[i].vtotal));
171			atemp = (modes[i].hdisplay * 100 / modes[i].vdisplay);
172			if (rtemp < rbest) {
173				rbest = rtemp;
174				mtemp = &modes[i];
175			}
176			if (rtemp == rbest) {
177				/* Treat "close" aspect ratios as identical */
178				if (abs(abest - atemp) > (abest / 8) &&
179				    abs(aspect - atemp) < abs(aspect - abest)) {
180					abest = atemp;
181					mtemp = &modes[i];
182				}
183				if (atemp == abest ||
184				    abs(abest - atemp) <= (abest / 8)) {
185					if (modes[i].hdisplay > hbest) {
186						hbest = modes[i].hdisplay;
187						mtemp = &modes[i];
188					}
189					if (modes[i].hdisplay == hbest &&
190					    modes[i].vdisplay > vbest) {
191						vbest = modes[i].vdisplay;
192						mtemp = &modes[i];
193					}
194				}
195			}
196		}
197		if (mtemp != &modes[j])
198			swap_modes(mtemp, &modes[j]);
199	}
200}
201