summaryrefslogblamecommitdiff
path: root/drivers/video/aty/radeon_monitor.c
blob: 2030ed813429f802114fff22ff4919aabc858f26 (plain) (tree)
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014





















































                                                        
                                                   










                                                                                     

                               



                                               
                                                        

















                                                                                      
                                                                







                                                                             

                                                                       
                                                       
                                                                  


                          
                                                      

                          















                                                                                   
                                  





                                               
                                                                  
























                                                                                           
                                          













































































































































































































































                                                                                                         



                              


























                                                                        
                                                                     



































































                                                                                                             
                                                   


                                                                                           
                                          











































                                                                                                
                                                   


                                                                                   
                                          






















                                                                                               
                                                   


                                                                                   
                                                   

















































































































































































































































































































































































































































                                                                                                             
#include "radeonfb.h"
#include "../edid.h"

static struct fb_var_screeninfo radeonfb_default_var = {
	.xres		= 640,
	.yres		= 480,
	.xres_virtual	= 640,
	.yres_virtual	= 480,
	.bits_per_pixel = 8,
	.red		= { .length = 8 },
	.green		= { .length = 8 },
	.blue		= { .length = 8 },
	.activate	= FB_ACTIVATE_NOW,
	.height		= -1,
	.width		= -1,
	.pixclock	= 39721,
	.left_margin	= 40,
	.right_margin	= 24,
	.upper_margin	= 32,
	.lower_margin	= 11,
	.hsync_len	= 96,
	.vsync_len	= 2,
	.vmode		= FB_VMODE_NONINTERLACED
};

static char *radeon_get_mon_name(int type)
{
	char *pret = NULL;

	switch (type) {
		case MT_NONE:
			pret = "no";
			break;
		case MT_CRT:
			pret = "CRT";
			break;
		case MT_DFP:
			pret = "DFP";
			break;
		case MT_LCD:
			pret = "LCD";
			break;
		case MT_CTV:
			pret = "CTV";
			break;
		case MT_STV:
			pret = "STV";
			break;
	}

	return pret;
}


#if defined(CONFIG_PPC_OF) || defined(CONFIG_SPARC)
/*
 * Try to find monitor informations & EDID data out of the Open Firmware
 * device-tree. This also contains some "hacks" to work around a few machine
 * models with broken OF probing by hard-coding known EDIDs for some Mac
 * laptops internal LVDS panel. (XXX: not done yet)
 */
static int __devinit radeon_parse_montype_prop(struct device_node *dp, u8 **out_EDID,
					       int hdno)
{
        static char *propnames[] = { "DFP,EDID", "LCD,EDID", "EDID",
				     "EDID1", "EDID2",  NULL };
	const u8 *pedid = NULL;
	const u8 *pmt = NULL;
	u8 *tmp;
        int i, mt = MT_NONE;  
	
	RTRACE("analyzing OF properties...\n");
	pmt = of_get_property(dp, "display-type", NULL);
	if (!pmt)
		return MT_NONE;
	RTRACE("display-type: %s\n", pmt);
	/* OF says "LCD" for DFP as well, we discriminate from the caller of this
	 * function
	 */
	if (!strcmp(pmt, "LCD") || !strcmp(pmt, "DFP"))
		mt = MT_DFP;
	else if (!strcmp(pmt, "CRT"))
		mt = MT_CRT;
	else {
		if (strcmp(pmt, "NONE") != 0)
			printk(KERN_WARNING "radeonfb: Unknown OF display-type: %s\n",
			       pmt);
		return MT_NONE;
	}

	for (i = 0; propnames[i] != NULL; ++i) {
		pedid = of_get_property(dp, propnames[i], NULL);
		if (pedid != NULL)
			break;
	}
	/* We didn't find the EDID in the leaf node, some cards will actually
	 * put EDID1/EDID2 in the parent, look for these (typically M6 tipb).
	 * single-head cards have hdno == -1 and skip this step
	 */
	if (pedid == NULL && dp->parent && (hdno != -1))
		pedid = of_get_property(dp->parent,
				(hdno == 0) ? "EDID1" : "EDID2", NULL);
	if (pedid == NULL && dp->parent && (hdno == 0))
		pedid = of_get_property(dp->parent, "EDID", NULL);
	if (pedid == NULL)
		return mt;

	tmp = kmemdup(pedid, EDID_LENGTH, GFP_KERNEL);
	if (!tmp)
		return mt;
	*out_EDID = tmp;
	return mt;
}

static int __devinit radeon_probe_OF_head(struct radeonfb_info *rinfo, int head_no,
					  u8 **out_EDID)
{
        struct device_node *dp;

	RTRACE("radeon_probe_OF_head\n");

        dp = rinfo->of_node;
        while (dp == NULL)
		return MT_NONE;

	if (rinfo->has_CRTC2) {
		const char *pname;
		int len, second = 0;

		dp = dp->child;
		do {
			if (!dp)
				return MT_NONE;
			pname = of_get_property(dp, "name", NULL);
			if (!pname)
				return MT_NONE;
			len = strlen(pname);
			RTRACE("head: %s (letter: %c, head_no: %d)\n",
			       pname, pname[len-1], head_no);
			if (pname[len-1] == 'A' && head_no == 0) {
				int mt = radeon_parse_montype_prop(dp, out_EDID, 0);
				/* Maybe check for LVDS_GEN_CNTL here ? I need to check out
				 * what OF does when booting with lid closed
				 */
				if (mt == MT_DFP && rinfo->is_mobility)
					mt = MT_LCD;
				return mt;
			} else if (pname[len-1] == 'B' && head_no == 1)
				return radeon_parse_montype_prop(dp, out_EDID, 1);
			second = 1;
			dp = dp->sibling;
		} while(!second);
	} else {
		if (head_no > 0)
			return MT_NONE;
		return radeon_parse_montype_prop(dp, out_EDID, -1);
	}
        return MT_NONE;
}
#endif /* CONFIG_PPC_OF || CONFIG_SPARC */


static int __devinit radeon_get_panel_info_BIOS(struct radeonfb_info *rinfo)
{
	unsigned long tmp, tmp0;
	char stmp[30];
	int i;

	if (!rinfo->bios_seg)
		return 0;

	if (!(tmp = BIOS_IN16(rinfo->fp_bios_start + 0x40))) {
		printk(KERN_ERR "radeonfb: Failed to detect DFP panel info using BIOS\n");
		rinfo->panel_info.pwr_delay = 200;
		return 0;
	}

	for(i=0; i<24; i++)
		stmp[i] = BIOS_IN8(tmp+i+1);
	stmp[24] = 0;
	printk("radeonfb: panel ID string: %s\n", stmp);
	rinfo->panel_info.xres = BIOS_IN16(tmp + 25);
	rinfo->panel_info.yres = BIOS_IN16(tmp + 27);
	printk("radeonfb: detected LVDS panel size from BIOS: %dx%d\n",
		rinfo->panel_info.xres, rinfo->panel_info.yres);

	rinfo->panel_info.pwr_delay = BIOS_IN16(tmp + 44);
	RTRACE("BIOS provided panel power delay: %d\n", rinfo->panel_info.pwr_delay);
	if (rinfo->panel_info.pwr_delay > 2000 || rinfo->panel_info.pwr_delay <= 0)
		rinfo->panel_info.pwr_delay = 2000;

	/*
	 * Some panels only work properly with some divider combinations
	 */
	rinfo->panel_info.ref_divider = BIOS_IN16(tmp + 46);
	rinfo->panel_info.post_divider = BIOS_IN8(tmp + 48);
	rinfo->panel_info.fbk_divider = BIOS_IN16(tmp + 49);
	if (rinfo->panel_info.ref_divider != 0 &&
	    rinfo->panel_info.fbk_divider > 3) {
		rinfo->panel_info.use_bios_dividers = 1;
		printk(KERN_INFO "radeondb: BIOS provided dividers will be used\n");
		RTRACE("ref_divider = %x\n", rinfo->panel_info.ref_divider);
		RTRACE("post_divider = %x\n", rinfo->panel_info.post_divider);
		RTRACE("fbk_divider = %x\n", rinfo->panel_info.fbk_divider);
	}
	RTRACE("Scanning BIOS table ...\n");
	for(i=0; i<32; i++) {
		tmp0 = BIOS_IN16(tmp+64+i*2);
		if (tmp0 == 0)
			break;
		RTRACE(" %d x %d\n", BIOS_IN16(tmp0), BIOS_IN16(tmp0+2));
		if ((BIOS_IN16(tmp0) == rinfo->panel_info.xres) &&
		    (BIOS_IN16(tmp0+2) == rinfo->panel_info.yres)) {
			rinfo->panel_info.hblank = (BIOS_IN16(tmp0+17) - BIOS_IN16(tmp0+19)) * 8;
			rinfo->panel_info.hOver_plus = ((BIOS_IN16(tmp0+21) -
							 BIOS_IN16(tmp0+19) -1) * 8) & 0x7fff;
			rinfo->panel_info.hSync_width = BIOS_IN8(tmp0+23) * 8;
			rinfo->panel_info.vblank = BIOS_IN16(tmp0+24) - BIOS_IN16(tmp0+26);
			rinfo->panel_info.vOver_plus = (BIOS_IN16(tmp0+28) & 0x7ff) - BIOS_IN16(tmp0+26);
			rinfo->panel_info.vSync_width = (BIOS_IN16(tmp0+28) & 0xf800) >> 11;
			rinfo->panel_info.clock = BIOS_IN16(tmp0+9);
			/* Assume high active syncs for now until ATI tells me more... maybe we
			 * can probe register values here ?
			 */
			rinfo->panel_info.hAct_high = 1;
			rinfo->panel_info.vAct_high = 1;
			/* Mark panel infos valid */
			rinfo->panel_info.valid = 1;

			RTRACE("Found panel in BIOS table:\n");
			RTRACE("  hblank: %d\n", rinfo->panel_info.hblank);
			RTRACE("  hOver_plus: %d\n", rinfo->panel_info.hOver_plus);
			RTRACE("  hSync_width: %d\n", rinfo->panel_info.hSync_width);
			RTRACE("  vblank: %d\n", rinfo->panel_info.vblank);
			RTRACE("  vOver_plus: %d\n", rinfo->panel_info.vOver_plus);
			RTRACE("  vSync_width: %d\n", rinfo->panel_info.vSync_width);
			RTRACE("  clock: %d\n", rinfo->panel_info.clock);
				
			return 1;
		}
	}
	RTRACE("Didn't find panel in BIOS table !\n");

	return 0;
}

/* Try to extract the connector informations from the BIOS. This
 * doesn't quite work yet, but it's output is still useful for
 * debugging
 */
static void __devinit radeon_parse_connector_info(struct radeonfb_info *rinfo)
{
	int offset, chips, connectors, tmp, i, conn, type;

	static char* __conn_type_table[16] = {
		"NONE", "Proprietary", "CRT", "DVI-I", "DVI-D", "Unknown", "Unknown",
		"Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown",
		"Unknown", "Unknown", "Unknown"
	};

	if (!rinfo->bios_seg)
		return;

	offset = BIOS_IN16(rinfo->fp_bios_start + 0x50);
	if (offset == 0) {
		printk(KERN_WARNING "radeonfb: No connector info table detected\n");
		return;
	}

	/* Don't do much more at this point but displaying the data if
	 * DEBUG is enabled
	 */
	chips = BIOS_IN8(offset++) >> 4;
	RTRACE("%d chips in connector info\n", chips);
	for (i = 0; i < chips; i++) {
		tmp = BIOS_IN8(offset++);
		connectors = tmp & 0x0f;
		RTRACE(" - chip %d has %d connectors\n", tmp >> 4, connectors);
		for (conn = 0; ; conn++) {
			tmp = BIOS_IN16(offset);
			if (tmp == 0)
				break;
			offset += 2;
			type = (tmp >> 12) & 0x0f;
			RTRACE("  * connector %d of type %d (%s) : %04x\n",
			       conn, type, __conn_type_table[type], tmp);
		}
	}
}


/*
 * Probe physical connection of a CRT. This code comes from XFree
 * as well and currently is only implemented for the CRT DAC, the
 * code for the TVDAC is commented out in XFree as "non working"
 */
static int __devinit radeon_crt_is_connected(struct radeonfb_info *rinfo, int is_crt_dac)
{
    int	          connected = 0;

    /* the monitor either wasn't connected or it is a non-DDC CRT.
     * try to probe it
     */
    if (is_crt_dac) {
	unsigned long ulOrigVCLK_ECP_CNTL;
	unsigned long ulOrigDAC_CNTL;
	unsigned long ulOrigDAC_EXT_CNTL;
	unsigned long ulOrigCRTC_EXT_CNTL;
	unsigned long ulData;
	unsigned long ulMask;

	ulOrigVCLK_ECP_CNTL = INPLL(VCLK_ECP_CNTL);

	ulData              = ulOrigVCLK_ECP_CNTL;
	ulData             &= ~(PIXCLK_ALWAYS_ONb
				| PIXCLK_DAC_ALWAYS_ONb);
	ulMask              = ~(PIXCLK_ALWAYS_ONb
				| PIXCLK_DAC_ALWAYS_ONb);
	OUTPLLP(VCLK_ECP_CNTL, ulData, ulMask);

	ulOrigCRTC_EXT_CNTL = INREG(CRTC_EXT_CNTL);
	ulData              = ulOrigCRTC_EXT_CNTL;
	ulData             |= CRTC_CRT_ON;
	OUTREG(CRTC_EXT_CNTL, ulData);
   
	ulOrigDAC_EXT_CNTL = INREG(DAC_EXT_CNTL);
	ulData             = ulOrigDAC_EXT_CNTL;
	ulData            &= ~DAC_FORCE_DATA_MASK;
	ulData            |=  (DAC_FORCE_BLANK_OFF_EN
			       |DAC_FORCE_DATA_EN
			       |DAC_FORCE_DATA_SEL_MASK);
	if ((rinfo->family == CHIP_FAMILY_RV250) ||
	    (rinfo->family == CHIP_FAMILY_RV280))
	    ulData |= (0x01b6 << DAC_FORCE_DATA_SHIFT);
	else
	    ulData |= (0x01ac << DAC_FORCE_DATA_SHIFT);

	OUTREG(DAC_EXT_CNTL, ulData);

	ulOrigDAC_CNTL     = INREG(DAC_CNTL);
	ulData             = ulOrigDAC_CNTL;
	ulData            |= DAC_CMP_EN;
	ulData            &= ~(DAC_RANGE_CNTL_MASK
			       | DAC_PDWN);
	ulData            |= 0x2;
	OUTREG(DAC_CNTL, ulData);

	mdelay(1);

	ulData     = INREG(DAC_CNTL);
	connected =  (DAC_CMP_OUTPUT & ulData) ? 1 : 0;
  
	ulData    = ulOrigVCLK_ECP_CNTL;
	ulMask    = 0xFFFFFFFFL;
	OUTPLLP(VCLK_ECP_CNTL, ulData, ulMask);

	OUTREG(DAC_CNTL,      ulOrigDAC_CNTL     );
	OUTREG(DAC_EXT_CNTL,  ulOrigDAC_EXT_CNTL );
	OUTREG(CRTC_EXT_CNTL, ulOrigCRTC_EXT_CNTL);
    }

    return connected ? MT_CRT : MT_NONE;
}

/*
 * Parse the "monitor_layout" string if any. This code is mostly
 * copied from XFree's radeon driver
 */
static int __devinit radeon_parse_monitor_layout(struct radeonfb_info *rinfo,
						 const char *monitor_layout)
{
	char s1[5], s2[5];
	int i = 0, second = 0;
	const char *s;

	if (!monitor_layout)
		return 0;

	s = monitor_layout;
	do {
		switch(*s) {
		case ',':
			s1[i] = '\0';
			i = 0;
			second = 1;
			break;
		case ' ':
		case '\0':
			break;
		default:
			if (i > 4)
				break;
			if (second)
				s2[i] = *s;
			else
				s1[i] = *s;
			i++;
		}

		if (i > 4)
			i = 4;

	} while (*s++);
	if (second)
		s2[i] = 0;
	else {
		s1[i] = 0;
		s2[0] = 0;
	}
	if (strcmp(s1, "CRT") == 0)
		rinfo->mon1_type = MT_CRT;
	else if (strcmp(s1, "TMDS") == 0)
		rinfo->mon1_type = MT_DFP;
	else if (strcmp(s1, "LVDS") == 0)
		rinfo->mon1_type = MT_LCD;

	if (strcmp(s2, "CRT") == 0)
		rinfo->mon2_type = MT_CRT;
	else if (strcmp(s2, "TMDS") == 0)
		rinfo->mon2_type = MT_DFP;
	else if (strcmp(s2, "LVDS") == 0)
		rinfo->mon2_type = MT_LCD;

	return 1;
}

/*
 * Probe display on both primary and secondary card's connector (if any)
 * by various available techniques (i2c, OF device tree, BIOS, ...) and
 * try to retrieve EDID. The algorithm here comes from XFree's radeon
 * driver
 */
void __devinit radeon_probe_screens(struct radeonfb_info *rinfo,
				    const char *monitor_layout, int ignore_edid)
{
#ifdef CONFIG_FB_RADEON_I2C
	int ddc_crt2_used = 0;	
#endif
	int tmp, i;

	radeon_parse_connector_info(rinfo);

	if (radeon_parse_monitor_layout(rinfo, monitor_layout)) {

		/*
		 * If user specified a monitor_layout option, use it instead
		 * of auto-detecting. Maybe we should only use this argument
		 * on the first radeon card probed or provide a way to specify
		 * a layout for each card ?
		 */

		RTRACE("Using specified monitor layout: %s", monitor_layout);
#ifdef CONFIG_FB_RADEON_I2C
		if (!ignore_edid) {
			if (rinfo->mon1_type != MT_NONE)
				if (!radeon_probe_i2c_connector(rinfo, ddc_dvi, &rinfo->mon1_EDID)) {
					radeon_probe_i2c_connector(rinfo, ddc_crt2, &rinfo->mon1_EDID);
					ddc_crt2_used = 1;
				}
			if (rinfo->mon2_type != MT_NONE)
				if (!radeon_probe_i2c_connector(rinfo, ddc_vga, &rinfo->mon2_EDID) &&
				    !ddc_crt2_used)
					radeon_probe_i2c_connector(rinfo, ddc_crt2, &rinfo->mon2_EDID);
		}
#endif /* CONFIG_FB_RADEON_I2C */
		if (rinfo->mon1_type == MT_NONE) {
			if (rinfo->mon2_type != MT_NONE) {
				rinfo->mon1_type = rinfo->mon2_type;
				rinfo->mon1_EDID = rinfo->mon2_EDID;
			} else {
				rinfo->mon1_type = MT_CRT;
				printk(KERN_INFO "radeonfb: No valid monitor, assuming CRT on first port\n");
			}
			rinfo->mon2_type = MT_NONE;
			rinfo->mon2_EDID = NULL;
		}
	} else {
		/*
		 * Auto-detecting display type (well... trying to ...)
		 */
		
		RTRACE("Starting monitor auto detection...\n");

#if DEBUG && defined(CONFIG_FB_RADEON_I2C)
		{
			u8 *EDIDs[4] = { NULL, NULL, NULL, NULL };
			int mon_types[4] = {MT_NONE, MT_NONE, MT_NONE, MT_NONE};
			int i;

			for (i = 0; i < 4; i++)
				mon_types[i] = radeon_probe_i2c_connector(rinfo,
									  i+1, &EDIDs[i]);
		}
#endif /* DEBUG */
		/*
		 * Old single head cards
		 */
		if (!rinfo->has_CRTC2) {
#if defined(CONFIG_PPC_OF) || defined(CONFIG_SPARC)
			if (rinfo->mon1_type == MT_NONE)
				rinfo->mon1_type = radeon_probe_OF_head(rinfo, 0,
									&rinfo->mon1_EDID);
#endif /* CONFIG_PPC_OF || CONFIG_SPARC */
#ifdef CONFIG_FB_RADEON_I2C
			if (rinfo->mon1_type == MT_NONE)
				rinfo->mon1_type =
					radeon_probe_i2c_connector(rinfo, ddc_dvi,
								   &rinfo->mon1_EDID);
			if (rinfo->mon1_type == MT_NONE)
				rinfo->mon1_type =
					radeon_probe_i2c_connector(rinfo, ddc_vga,
								   &rinfo->mon1_EDID);
			if (rinfo->mon1_type == MT_NONE)
				rinfo->mon1_type =
					radeon_probe_i2c_connector(rinfo, ddc_crt2,
								   &rinfo->mon1_EDID);	
#endif /* CONFIG_FB_RADEON_I2C */
			if (rinfo->mon1_type == MT_NONE)
				rinfo->mon1_type = MT_CRT;
			goto bail;
		}

		/*
		 * Check for cards with reversed DACs or TMDS controllers using BIOS
		 */
		if (rinfo->bios_seg &&
		    (tmp = BIOS_IN16(rinfo->fp_bios_start + 0x50))) {
			for (i = 1; i < 4; i++) {
				unsigned int tmp0;

				if (!BIOS_IN8(tmp + i*2) && i > 1)
					break;
				tmp0 = BIOS_IN16(tmp + i*2);
				if ((!(tmp0 & 0x01)) && (((tmp0 >> 8) & 0x0f) == ddc_dvi)) {
					rinfo->reversed_DAC = 1;
					printk(KERN_INFO "radeonfb: Reversed DACs detected\n");
				}
				if ((((tmp0 >> 8) & 0x0f) == ddc_dvi) && ((tmp0 >> 4) & 0x01)) {
					rinfo->reversed_TMDS = 1;
					printk(KERN_INFO "radeonfb: Reversed TMDS detected\n");
				}
			}
		}

		/*
		 * Probe primary head (DVI or laptop internal panel)
		 */
#if defined(CONFIG_PPC_OF) || defined(CONFIG_SPARC)
		if (rinfo->mon1_type == MT_NONE)
			rinfo->mon1_type = radeon_probe_OF_head(rinfo, 0,
								&rinfo->mon1_EDID);
#endif /* CONFIG_PPC_OF || CONFIG_SPARC */
#ifdef CONFIG_FB_RADEON_I2C
		if (rinfo->mon1_type == MT_NONE)
			rinfo->mon1_type = radeon_probe_i2c_connector(rinfo, ddc_dvi,
								      &rinfo->mon1_EDID);
		if (rinfo->mon1_type == MT_NONE) {
			rinfo->mon1_type = radeon_probe_i2c_connector(rinfo, ddc_crt2,
								      &rinfo->mon1_EDID);
			if (rinfo->mon1_type != MT_NONE)
				ddc_crt2_used = 1;
		}
#endif /* CONFIG_FB_RADEON_I2C */
		if (rinfo->mon1_type == MT_NONE && rinfo->is_mobility &&
		    ((rinfo->bios_seg && (INREG(BIOS_4_SCRATCH) & 4))
		     || (INREG(LVDS_GEN_CNTL) & LVDS_ON))) {
			rinfo->mon1_type = MT_LCD;
			printk("Non-DDC laptop panel detected\n");
		}
		if (rinfo->mon1_type == MT_NONE)
			rinfo->mon1_type = radeon_crt_is_connected(rinfo, rinfo->reversed_DAC);

		/*
		 * Probe secondary head (mostly VGA, can be DVI)
		 */
#if defined(CONFIG_PPC_OF) || defined(CONFIG_SPARC)
		if (rinfo->mon2_type == MT_NONE)
			rinfo->mon2_type = radeon_probe_OF_head(rinfo, 1,
								&rinfo->mon2_EDID);
#endif /* CONFIG_PPC_OF || defined(CONFIG_SPARC) */
#ifdef CONFIG_FB_RADEON_I2C
		if (rinfo->mon2_type == MT_NONE)
			rinfo->mon2_type = radeon_probe_i2c_connector(rinfo, ddc_vga,
								      &rinfo->mon2_EDID);
		if (rinfo->mon2_type == MT_NONE && !ddc_crt2_used)
			rinfo->mon2_type = radeon_probe_i2c_connector(rinfo, ddc_crt2,
								      &rinfo->mon2_EDID);
#endif /* CONFIG_FB_RADEON_I2C */
		if (rinfo->mon2_type == MT_NONE)
			rinfo->mon2_type = radeon_crt_is_connected(rinfo, !rinfo->reversed_DAC);

		/*
		 * If we only detected port 2, we swap them, if none detected,
		 * assume CRT (maybe fallback to old BIOS_SCRATCH stuff ? or look
		 * at FP registers ?)
		 */
		if (rinfo->mon1_type == MT_NONE) {
			if (rinfo->mon2_type != MT_NONE) {
				rinfo->mon1_type = rinfo->mon2_type;
				rinfo->mon1_EDID = rinfo->mon2_EDID;
			} else
				rinfo->mon1_type = MT_CRT;
			rinfo->mon2_type = MT_NONE;
			rinfo->mon2_EDID = NULL;
		}

		/*
		 * Deal with reversed TMDS
		 */
		if (rinfo->reversed_TMDS) {
			/* Always keep internal TMDS as primary head */
			if (rinfo->mon1_type == MT_DFP || rinfo->mon2_type == MT_DFP) {
				int tmp_type = rinfo->mon1_type;
				u8 *tmp_EDID = rinfo->mon1_EDID;
				rinfo->mon1_type = rinfo->mon2_type;
				rinfo->mon1_EDID = rinfo->mon2_EDID;
				rinfo->mon2_type = tmp_type;
				rinfo->mon2_EDID = tmp_EDID;
				if (rinfo->mon1_type == MT_CRT || rinfo->mon2_type == MT_CRT)
					rinfo->reversed_DAC ^= 1;
			}
		}
	}
	if (ignore_edid) {
		kfree(rinfo->mon1_EDID);
		rinfo->mon1_EDID = NULL;
		kfree(rinfo->mon2_EDID);
		rinfo->mon2_EDID = NULL;
	}

 bail:
	printk(KERN_INFO "radeonfb: Monitor 1 type %s found\n",
	       radeon_get_mon_name(rinfo->mon1_type));
	if (rinfo->mon1_EDID)
		printk(KERN_INFO "radeonfb: EDID probed\n");
	if (!rinfo->has_CRTC2)
		return;
	printk(KERN_INFO "radeonfb: Monitor 2 type %s found\n",
	       radeon_get_mon_name(rinfo->mon2_type));
	if (rinfo->mon2_EDID)
		printk(KERN_INFO "radeonfb: EDID probed\n");
}


/*
 * This functions applyes any arch/model/machine specific fixups
 * to the panel info. It may eventually alter EDID block as
 * well or whatever is specific to a given model and not probed
 * properly by the default code
 */
static void radeon_fixup_panel_info(struct radeonfb_info *rinfo)
{
#ifdef CONFIG_PPC_OF
	/*
	 * LCD Flat panels should use fixed dividers, we enfore that on
	 * PPC only for now...
	 */
	if (!rinfo->panel_info.use_bios_dividers && rinfo->mon1_type == MT_LCD
	    && rinfo->is_mobility) {
		int ppll_div_sel;
		u32 ppll_divn;
		ppll_div_sel = INREG8(CLOCK_CNTL_INDEX + 1) & 0x3;
		radeon_pll_errata_after_index(rinfo);
		ppll_divn = INPLL(PPLL_DIV_0 + ppll_div_sel);
		rinfo->panel_info.ref_divider = rinfo->pll.ref_div;
		rinfo->panel_info.fbk_divider = ppll_divn & 0x7ff;
		rinfo->panel_info.post_divider = (ppll_divn >> 16) & 0x7;
		rinfo->panel_info.use_bios_dividers = 1;

		printk(KERN_DEBUG "radeonfb: Using Firmware dividers 0x%08x "
		       "from PPLL %d\n",
		       rinfo->panel_info.fbk_divider |
		       (rinfo->panel_info.post_divider << 16),
		       ppll_div_sel);
	}
#endif /* CONFIG_PPC_OF */
}


/*
 * Fill up panel infos from a mode definition, either returned by the EDID
 * or from the default mode when we can't do any better
 */
static void radeon_var_to_panel_info(struct radeonfb_info *rinfo, struct fb_var_screeninfo *var)
{
	rinfo->panel_info.xres = var->xres;
	rinfo->panel_info.yres = var->yres;
	rinfo->panel_info.clock = 100000000 / var->pixclock;
	rinfo->panel_info.hOver_plus = var->right_margin;
	rinfo->panel_info.hSync_width = var->hsync_len;
       	rinfo->panel_info.hblank = var->left_margin +
		(var->right_margin + var->hsync_len);
	rinfo->panel_info.vOver_plus = var->lower_margin;
	rinfo->panel_info.vSync_width = var->vsync_len;
       	rinfo->panel_info.vblank = var->upper_margin +
		(var->lower_margin + var->vsync_len);
	rinfo->panel_info.hAct_high =
		(var->sync & FB_SYNC_HOR_HIGH_ACT) != 0;
	rinfo->panel_info.vAct_high =
		(var->sync & FB_SYNC_VERT_HIGH_ACT) != 0;
	rinfo->panel_info.valid = 1;
	/* We use a default of 200ms for the panel power delay, 
	 * I need to have a real schedule() instead of mdelay's in the panel code.
	 * we might be possible to figure out a better power delay either from
	 * MacOS OF tree or from the EDID block (proprietary extensions ?)
	 */
	rinfo->panel_info.pwr_delay = 200;
}

static void radeon_videomode_to_var(struct fb_var_screeninfo *var,
				    const struct fb_videomode *mode)
{
	var->xres = mode->xres;
	var->yres = mode->yres;
	var->xres_virtual = mode->xres;
	var->yres_virtual = mode->yres;
	var->xoffset = 0;
	var->yoffset = 0;
	var->pixclock = mode->pixclock;
	var->left_margin = mode->left_margin;
	var->right_margin = mode->right_margin;
	var->upper_margin = mode->upper_margin;
	var->lower_margin = mode->lower_margin;
	var->hsync_len = mode->hsync_len;
	var->vsync_len = mode->vsync_len;
	var->sync = mode->sync;
	var->vmode = mode->vmode;
}

/*
 * Build the modedb for head 1 (head 2 will come later), check panel infos
 * from either BIOS or EDID, and pick up the default mode
 */
void __devinit radeon_check_modes(struct radeonfb_info *rinfo, const char *mode_option)
{
	struct fb_info * info = rinfo->info;
	int has_default_mode = 0;

	/*
	 * Fill default var first
	 */
	info->var = radeonfb_default_var;
	INIT_LIST_HEAD(&info->modelist);

	/*
	 * First check out what BIOS has to say
	 */
	if (rinfo->mon1_type == MT_LCD)
		radeon_get_panel_info_BIOS(rinfo);

	/*
	 * Parse EDID detailed timings and deduce panel infos if any. Right now
	 * we only deal with first entry returned by parse_EDID, we may do better
	 * some day...
	 */
	if (!rinfo->panel_info.use_bios_dividers && rinfo->mon1_type != MT_CRT
	    && rinfo->mon1_EDID) {
		struct fb_var_screeninfo var;
		RTRACE("Parsing EDID data for panel info\n");
		if (fb_parse_edid(rinfo->mon1_EDID, &var) == 0) {
			if (var.xres >= rinfo->panel_info.xres &&
			    var.yres >= rinfo->panel_info.yres)
				radeon_var_to_panel_info(rinfo, &var);
		}
	}

	/*
	 * Do any additional platform/arch fixups to the panel infos
	 */
	radeon_fixup_panel_info(rinfo);

	/*
	 * If we have some valid panel infos, we setup the default mode based on
	 * those
	 */
	if (rinfo->mon1_type != MT_CRT && rinfo->panel_info.valid) {
		struct fb_var_screeninfo *var = &info->var;

		RTRACE("Setting up default mode based on panel info\n");
		var->xres = rinfo->panel_info.xres;
		var->yres = rinfo->panel_info.yres;
		var->xres_virtual = rinfo->panel_info.xres;
		var->yres_virtual = rinfo->panel_info.yres;
		var->xoffset = var->yoffset = 0;
		var->bits_per_pixel = 8;
		var->pixclock = 100000000 / rinfo->panel_info.clock;
		var->left_margin = (rinfo->panel_info.hblank - rinfo->panel_info.hOver_plus
				    - rinfo->panel_info.hSync_width);
		var->right_margin = rinfo->panel_info.hOver_plus;
		var->upper_margin = (rinfo->panel_info.vblank - rinfo->panel_info.vOver_plus
				     - rinfo->panel_info.vSync_width);
		var->lower_margin = rinfo->panel_info.vOver_plus;
		var->hsync_len = rinfo->panel_info.hSync_width;
		var->vsync_len = rinfo->panel_info.vSync_width;
		var->sync = 0;
		if (rinfo->panel_info.hAct_high)
			var->sync |= FB_SYNC_HOR_HIGH_ACT;
		if (rinfo->panel_info.vAct_high)
			var->sync |= FB_SYNC_VERT_HIGH_ACT;
		var->vmode = 0;
		has_default_mode = 1;
	}

	/*
	 * Now build modedb from EDID
	 */
	if (rinfo->mon1_EDID) {
		fb_edid_to_monspecs(rinfo->mon1_EDID, &info->monspecs);
		fb_videomode_to_modelist(info->monspecs.modedb,
					 info->monspecs.modedb_len,
					 &info->modelist);
		rinfo->mon1_modedb = info->monspecs.modedb;
		rinfo->mon1_dbsize = info->monspecs.modedb_len;
	}

	
	/*
	 * Finally, if we don't have panel infos we need to figure some (or
	 * we try to read it from card), we try to pick a default mode
	 * and create some panel infos. Whatever...
	 */
	if (rinfo->mon1_type != MT_CRT && !rinfo->panel_info.valid) {
		struct fb_videomode	*modedb;
		int			dbsize;
		char			modename[32];

		RTRACE("Guessing panel info...\n");
		if (rinfo->panel_info.xres == 0 || rinfo->panel_info.yres == 0) {
			u32 tmp = INREG(FP_HORZ_STRETCH) & HORZ_PANEL_SIZE;
			rinfo->panel_info.xres = ((tmp >> HORZ_PANEL_SHIFT) + 1) * 8;
			tmp = INREG(FP_VERT_STRETCH) & VERT_PANEL_SIZE;
			rinfo->panel_info.yres = (tmp >> VERT_PANEL_SHIFT) + 1;
		}
		if (rinfo->panel_info.xres == 0 || rinfo->panel_info.yres == 0) {
			printk(KERN_WARNING "radeonfb: Can't find panel size, going back to CRT\n");
			rinfo->mon1_type = MT_CRT;
			goto pickup_default;
		}
		printk(KERN_WARNING "radeonfb: Assuming panel size %dx%d\n",
		       rinfo->panel_info.xres, rinfo->panel_info.yres);
		modedb = rinfo->mon1_modedb;
		dbsize = rinfo->mon1_dbsize;
		snprintf(modename, 31, "%dx%d", rinfo->panel_info.xres, rinfo->panel_info.yres);
		if (fb_find_mode(&info->var, info, modename,
				 modedb, dbsize, NULL, 8) == 0) {
			printk(KERN_WARNING "radeonfb: Can't find mode for panel size, going back to CRT\n");
			rinfo->mon1_type = MT_CRT;
			goto pickup_default;
		}
		has_default_mode = 1;
		radeon_var_to_panel_info(rinfo, &info->var);
	}

 pickup_default:
	/*
	 * Apply passed-in mode option if any
	 */
	if (mode_option) {
		if (fb_find_mode(&info->var, info, mode_option,
				 info->monspecs.modedb,
				 info->monspecs.modedb_len, NULL, 8) != 0)
			has_default_mode = 1;
 	}

	/*
	 * Still no mode, let's pick up a default from the db
	 */
	if (!has_default_mode && info->monspecs.modedb != NULL) {
		struct fb_monspecs *specs = &info->monspecs;
		struct fb_videomode *modedb = NULL;

		/* get preferred timing */
		if (specs->misc & FB_MISC_1ST_DETAIL) {
			int i;

			for (i = 0; i < specs->modedb_len; i++) {
				if (specs->modedb[i].flag & FB_MODE_IS_FIRST) {
					modedb = &specs->modedb[i];
					break;
				}
			}
		} else {
			/* otherwise, get first mode in database */
			modedb = &specs->modedb[0];
		}
		if (modedb != NULL) {
			info->var.bits_per_pixel = 8;
			radeon_videomode_to_var(&info->var, modedb);
			has_default_mode = 1;
		}
	}
	if (1) {
		struct fb_videomode mode;
		/* Make sure that whatever mode got selected is actually in the
		 * modelist or the kernel may die
		 */
		fb_var_to_videomode(&mode, &info->var);
		fb_add_videomode(&mode, &info->modelist);
	}
}

/*
 * The code below is used to pick up a mode in check_var and
 * set_var. It should be made generic
 */

/*
 * This is used when looking for modes. We assign a "distance" value
 * to a mode in the modedb depending how "close" it is from what we
 * are looking for.
 * Currently, we don't compare that much, we could do better but
 * the current fbcon doesn't quite mind ;)
 */
static int radeon_compare_modes(const struct fb_var_screeninfo *var,
				const struct fb_videomode *mode)
{
	int distance = 0;

	distance = mode->yres - var->yres;
	distance += (mode->xres - var->xres)/2;
	return distance;
}

/*
 * This function is called by check_var, it gets the passed in mode parameter, and
 * outputs a valid mode matching the passed-in one as closely as possible.
 * We need something better ultimately. Things like fbcon basically pass us out
 * current mode with xres/yres hacked, while things like XFree will actually
 * produce a full timing that we should respect as much as possible.
 *
 * This is why I added the FB_ACTIVATE_FIND that is used by fbcon. Without this,
 * we do a simple spec match, that's all. With it, we actually look for a mode in
 * either our monitor modedb or the vesa one if none
 *
 */
int  radeon_match_mode(struct radeonfb_info *rinfo,
		       struct fb_var_screeninfo *dest,
		       const struct fb_var_screeninfo *src)
{
	const struct fb_videomode	*db = vesa_modes;
	int				i, dbsize = 34;
	int				has_rmx, native_db = 0;
	int				distance = INT_MAX;
	const struct fb_videomode	*candidate = NULL;

	/* Start with a copy of the requested mode */
	memcpy(dest, src, sizeof(struct fb_var_screeninfo));

	/* Check if we have a modedb built from EDID */
	if (rinfo->mon1_modedb) {
		db = rinfo->mon1_modedb;
		dbsize = rinfo->mon1_dbsize;
		native_db = 1;
	}

	/* Check if we have a scaler allowing any fancy mode */
	has_rmx = rinfo->mon1_type == MT_LCD || rinfo->mon1_type == MT_DFP;

	/* If we have a scaler and are passed FB_ACTIVATE_TEST or
	 * FB_ACTIVATE_NOW, just do basic checking and return if the
	 * mode match
	 */
	if ((src->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_TEST ||
	    (src->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) {
		/* We don't have an RMX, validate timings. If we don't have
	 	 * monspecs, we should be paranoid and not let use go above
		 * 640x480-60, but I assume userland knows what it's doing here
		 * (though I may be proven wrong...)
		 */
		if (has_rmx == 0 && rinfo->mon1_modedb)
			if (fb_validate_mode((struct fb_var_screeninfo *)src, rinfo->info))
				return -EINVAL;
		return 0;
	}

	/* Now look for a mode in the database */
	while (db) {
		for (i = 0; i < dbsize; i++) {
			int d;

			if (db[i].yres < src->yres)
				continue;	
			if (db[i].xres < src->xres)
				continue;
			d = radeon_compare_modes(src, &db[i]);
			/* If the new mode is at least as good as the previous one,
			 * then it's our new candidate
			 */
			if (d < distance) {
				candidate = &db[i];
				distance = d;
			}
		}
		db = NULL;
		/* If we have a scaler, we allow any mode from the database */
		if (native_db && has_rmx) {
			db = vesa_modes;
			dbsize = 34;
			native_db = 0;
		}
	}

	/* If we have found a match, return it */
	if (candidate != NULL) {
		radeon_videomode_to_var(dest, candidate);
		return 0;
	}

	/* If we haven't and don't have a scaler, fail */
	if (!has_rmx)
		return -EINVAL;

	return 0;
}