text: /* $NetBSD: pickmode.c,v 1.4 2011/04/09 20:53:39 christos Exp $ */
  
  /*-
   * Copyright (c) 2006 The NetBSD Foundation
   * All rights reserved.
   *
   * this code was contributed to The NetBSD Foundation by Michael Lorenz
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in the
   *    documentation and/or other materials provided with the distribution.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS
   * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   * ARE DISCLAIMED.  IN NO EVENT SHALL THE NETBSD FOUNDATION BE LIABLE
   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
   * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */ 
  
  #include <sys/cdefs.h>
  __KERNEL_RCSID(0, "$NetBSD: pickmode.c,v 1.4 2011/04/09 20:53:39 christos Exp $");
  
  #include <sys/param.h>
  #include <dev/videomode/videomode.h>
  #if !defined(__minix)
  #include "opt_videomode.h"
  #else
  #include <lib/libkern/libkern.h>
  #include <minix/sysutil.h>
  #endif /* !defined(__minix) */
  
  #ifdef PICKMODE_DEBUG
  #define DPRINTF printf
  #else
  #define DPRINTF while (0) printf
  #endif
  
  const struct videomode *
  pick_mode_by_dotclock(int width, int height, int dotclock)
  {
  	const struct videomode *this, *best = NULL;
  	int i;
  
  	DPRINTF("%s: looking for %d x %d at up to %d kHz\n", __func__, width,
  	    height, dotclock);
  	for (i = 0; i < videomode_count; i++) {
  		this = &videomode_list[i];
  		if ((this->hdisplay != width) || (this->vdisplay != height) ||
  		    (this->dot_clock > dotclock))
  			continue;
  		if (best != NULL) {
  			if (this->dot_clock > best->dot_clock)
  				best = this;
  		} else
  			best = this;
  	}
  	if (best != NULL)
  		DPRINTF("found %s\n", best->name);
  
  	return best;
  }
  
  const struct videomode *
  pick_mode_by_ref(int width, int height, int refresh)
  {
  	const struct videomode *this, *best = NULL;
  	int mref, closest = 1000, i, diff;
  
  	DPRINTF("%s: looking for %d x %d at up to %d Hz\n", __func__, width,
  	    height, refresh);
  	for (i = 0; i < videomode_count; i++) {
  
  		this = &videomode_list[i];
  		mref = this->dot_clock * 1000 / (this->htotal * this->vtotal);
  		diff = abs(mref - refresh);
  		if ((this->hdisplay != width) || (this->vdisplay != height))
  			continue;
  		DPRINTF("%s in %d hz, diff %d\n", this->name, mref, diff);
  		if (best != NULL) {
  			if (diff < closest) {
  				best = this;
  				closest = diff;
  			}
  		} else {
  			best = this;
  			closest = diff;
  		}
  	}
  	if (best != NULL)
  		DPRINTF("found %s %d\n", best->name, best->dot_clock);
  
  	return best;
  }
  
  static inline void
  swap_modes(struct videomode *left, struct videomode *right)
  {
  	struct videomode temp;
  
  	temp = *left;
  	*left = *right;
  	*right = temp;
  }
  
  /*
   * Sort modes by refresh rate, aspect ratio (*), then resolution.
   * Preferred mode or largest mode is first in the list and other modes
   * are sorted on closest match to that mode.
   * (*) Note that the aspect ratio calculation treats "close" aspect ratios
   * (within 12.5%) as the same for this purpose.
   */
  #define	DIVIDE(x, y)	(((x) + ((y) / 2)) / (y))
  void
  sort_modes(struct videomode *modes, struct videomode **preferred, int nmodes)
  {
  	int aspect, refresh, hbest, vbest, abest, atemp, rbest, rtemp;
  	int i, j;
  	struct videomode *mtemp = NULL;
  
  	if (nmodes < 2)
  		return;
  
  	if (*preferred != NULL) {
  		/* Put the preferred mode first in the list */
  		aspect = (*preferred)->hdisplay * 100 / (*preferred)->vdisplay;
  		refresh = DIVIDE(DIVIDE((*preferred)->dot_clock * 1000,
  		    (*preferred)->htotal), (*preferred)->vtotal);
  		if (*preferred != modes) {
  			swap_modes(*preferred, modes);
  			*preferred = modes;
  		}
  	} else {
  		/*
  		 * Find the largest horizontal and vertical mode and put that
  		 * first in the list.  Preferred refresh rate is taken from
  		 * the first mode of this size.
  		 */
  		hbest = 0;
  		vbest = 0;
  		for (i = 0; i < nmodes; i++) {
  			if (modes[i].hdisplay > hbest) {
  				hbest = modes[i].hdisplay;
  				vbest = modes[i].vdisplay;
  				mtemp = &modes[i];
  			} else if (modes[i].hdisplay == hbest &&
  			    modes[i].vdisplay > vbest) {
  				vbest = modes[i].vdisplay;
  				mtemp = &modes[i];
  			}
  		}
  		aspect = mtemp->hdisplay * 100 / mtemp->vdisplay;
  		refresh = DIVIDE(DIVIDE(mtemp->dot_clock * 1000,
  		    mtemp->htotal), mtemp->vtotal);
  		if (mtemp != modes)
  			swap_modes(mtemp, modes);
  	}
  
  	/* Sort other modes by refresh rate, aspect ratio, then resolution */
  	for (j = 1; j < nmodes - 1; j++) {
  		rbest = 1000;
  		abest = 1000;
  		hbest = 0;
  		vbest = 0;
  		for (i = j; i < nmodes; i++) {
  			rtemp = abs(refresh -
  			    DIVIDE(DIVIDE(modes[i].dot_clock * 1000,
  			    modes[i].htotal), modes[i].vtotal));
  			atemp = (modes[i].hdisplay * 100 / modes[i].vdisplay);
  			if (rtemp < rbest) {
  				rbest = rtemp;
  				mtemp = &modes[i];
  			}
  			if (rtemp == rbest) {
  				/* Treat "close" aspect ratios as identical */
  				if (abs(abest - atemp) > (abest / 8) &&
  				    abs(aspect - atemp) < abs(aspect - abest)) {
  					abest = atemp;
  					mtemp = &modes[i];
  				}
  				if (atemp == abest ||
  				    abs(abest - atemp) <= (abest / 8)) {
  					if (modes[i].hdisplay > hbest) {
  						hbest = modes[i].hdisplay;
  						mtemp = &modes[i];
  					}
  					if (modes[i].hdisplay == hbest &&
  					    modes[i].vdisplay > vbest) {
  						vbest = modes[i].vdisplay;
  						mtemp = &modes[i];
  					}
  				}
  			}
  		}
  		if (mtemp != &modes[j])
  			swap_modes(mtemp, &modes[j]);
  	}
  }
  