aboutsummaryrefslogblamecommitdiffstats
path: root/drivers/gpu/drm/nouveau/nouveau_bios.c
blob: e7e69ccce5c9985ba77b7e7790582f984c8efdb9 (plain) (tree)
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
4239
4240
4241
4242
4243
4244
4245
4246
4247
4248
4249
4250
4251
4252
4253
4254
4255
4256
4257
4258
4259
4260
4261
4262
4263
4264
4265
4266
4267
4268
4269
4270
4271
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
4294
4295
4296
4297
4298
4299
4300
4301
4302
4303
4304
4305
4306
4307
4308
4309
4310
4311
4312
4313
4314
4315
4316
4317
4318
4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
4335
4336
4337
4338
4339
4340
4341
4342
4343
4344
4345
4346
4347
4348
4349
4350
4351
4352
4353
4354
4355
4356
4357
4358
4359
4360
4361
4362
4363
4364
4365
4366
4367
4368
4369
4370
4371
4372
4373
4374
4375
4376
4377
4378
4379
4380
4381
4382
4383
4384
4385
4386
4387
4388
4389
4390
4391
4392
4393
4394
4395
4396
4397
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
4417
4418
4419
4420
4421
4422
4423
4424
4425
4426
4427
4428
4429
4430
4431
4432
4433
4434
4435
4436
4437
4438
4439
4440
4441
4442
4443
4444
4445
4446
4447
4448
4449
4450
4451
4452
4453
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
4464
4465
4466
4467
4468
4469
4470
4471
4472
4473
4474
4475
4476
4477
4478
4479
4480
4481
4482
4483
4484
4485
4486
4487
4488
4489
4490
4491
4492
4493
4494
4495
4496
4497
4498
4499
4500
4501
4502
4503
4504
4505
4506
4507
4508
4509
4510
4511
4512
4513



























                                                                             
                            


























































































































































                                                                               





                                              





                                              

  

                   


                                                                

                                
                          
                              

                            


                                                                        
                                      
 
                                    
                                                                              
                                                 
 

                                                                  










                                                                              
                                         
                                                                        
                                          
                                                                           


                                                                  
                                    


                                 

                                                     
                                                                           

                                                               

                                            









                                                     




                                                                            
                                                                      




















































                                                                               
                        
                                                                


                                                                               
                                                                



                                                                                


                                                                                
         

                 





























































































                                                                                
                                      

































































































































































































































                                                                                            
                                      








































                                                                          
                                               









                                                                              
                                            







                                                                              












































































                                                                                                                    



                                                                
                                                     



                                                                               
                                                               
 
                                                                     




                                                             
                                                           


                                                                   
 










                                                                     


                                                

                                                       











                                                                              
                                               











                                                                               
                                                                          



                                            
                                                       







                                                                             
          






























                                                                             
                                 

                            
                           









                                                                                
                               







                                                                       
                   

 
          

































                                                                              
                 

 
          


































                                                                                
                                 

                            
                           











                                                                                
                               


















                                                                                 
                   

 
          
















                                                                              
                         
 
                 

 
          



























                                                                             
                          

















                                                                             
                  

 
          














                                                                                              
                 

 
          
















                                                                             
                         







                                                                                                          
                 

 
          























































































































                                                                                


























                                                                         
                                 



                            
                           


















                                                                        
                   

 
          



























                                                                              
                                 



                            
                           





                                                                           
                           





                                                                                
                               








                                                                           
                   

 
          















                                                                           
                         




                                                           
                 

 
          




















                                                                                
                                                          

                                               

                                

                            
                           






                                                                         
                               

                                     
                                                             

                                                              
                                         
 




                                                                    


                                                                       
                                                           
 

                                   
 






                                                                    

         
                   

 
          


















                                                                                
                                                          

                                               

                                

                            
                           






                                                                         
                               

                                     



                                                             

                                                                       









                                                                    

         
                   

 
          
















                                                                              
                                                          
                                               
                            





                                      
                           






                                                                         
                               












                                                                         
                                    

         
                   

 
          























                                                                              
                         






                                                                            
                               






                                                                       
                 

 
          



















                                                                             
                                



                            
                           





                                                                          
                               








                                                                  
                   

 
          





















                                                                              
                            



                              
                           















                                                                          
                   

 
          



















                                                                            
                         







                                                                            
                 

 
          















                                                                         
                         


                                                                 
                 

 
          















                                                                               
                                


                            
                           



                                                                
                   

 
          






















                                                                               
                         





























                                                                              
                 

 
          


















                                                                             
                                


                            
                           










                                                                      
                   

 
          














                                                                              
                         







                                                                                
                 

 
          


























                                                                               
                          

















                                                                                
                  

 
          
















                                                                               
                         


                                                        
                 

 
          














































                                                                                
                                         
                         
















                                                                                
                 

 
          
































                                                                                
                  

 
          



















                                                                                                                                                          
                               






























                                                                               
                 

 
          


















                                                                                                                                                          
                               








                                                        
                 

 
          


















                                                                            
                               



                                                               
                 

 
          


















                                                                         
                         

























































                                                                             
                         



                                                                              
                 

 
          













                                                                       
                         








                                                                                 
                 

 
          



















                                                                          
                         












                                                                                                          
                 

 
          

















                                                                          
                          





                                                                          
                  

 
          






















                                                                                            
                         













                                                                                                
                 

 
          










                                                                        
                 

 
          










                                                                          
                         



                                                                                  
                 

 
          













                                                                        
                         








                                                                   
                 

 
          















                                                                             
                         









                                                                                                          
                 

 
          















                                                                                
                         









                                                                                                          
                 

 
          





















                                                                            
                         







                                                                           
                 

 
          
















                                                                            
                         




                                                                                
                 

 
          















                                                                          
                         





                                    
                 

 
          
























                                                                                
                                                         


                            
                           


                                                                         
                                                                            











                                                                          
                                   



                                                                             
                   

 
          








                                                                      
                 

 
          








                                                                      
                 

 
          










                                                                        
                                                                      
                                                             

              


                                                                          

         

                            
 


                                                                       
 
                                                                              
 
                                                                         
 



                                                                         

                                                   
                                                            
                                                           











                                               
                 

 
          
























                                                                               


                                                               


                      

                            
                           




                                                                       
                               
















                                                                                
                   

 
          















                                                                               
                         


                                                         
                 

 
          
















                                                                    
                                


                            
                           





                                                                      
                   

 
          









                                                                            
                 

 
          































                                                                               
                          

                                                                  
                  

 
          






















                                                                      
                          

                                  
                  

 
          
















                                                                         

                                               



                                                                
                               





                                                                           
                               


                            
                           

                    
                                                  




                                                                             
                                   







                                                                             
                                   


                 
                   

 
          















                                                                            

                                               



                                                                   
                               





                                                                           
                               


                            
                           

                    
                                               


                                                                                
                                   


                 
                   




                                                                                                                      






                                                                                        


                                                                                        













                                                                                        
                                                                                














                                                                                        
                                                                                  


















                                                                                        

  














                                                                              
                              













                                                                                
                                          




                                                                         




















                                                                           









































































                                                                                                        
                                               















                                                                                                                             
                                               













                                                                                                                                


















                                                                             



















                                                                                                                         
                                               

                                                           

















                                                                                                     



                                                                 

                                                           


                                                 

                                        
                                                                     

                                                 

                                                   
                         


                                                                       

                 
                                                       
























                                                                                                                    
                                               




































































































































                                                                                                                         
                                                     































                                                                             
                                                           





































                                                                              
                                                                       






















                                                                                
                                               



















































                                                                                                 
                                                                               
















                                                                                
                                               


                                                                             
                                                   





































































                                                                                                                                   
                  





                                                                           
 

                                                                            
                                                           

                                                             





                                                                                                                














                                                                          













                                                                                 
                                               
































                                                                       
                                               







                                                                   
                                                   






































                                                                                
                                               




















































                                                                             
                                                                      























                                                                              


                                          
                                                                         



                                                                           
                                                                 



                                          
                                                                         



                                                                           
                                                                 






                                                   
                                                                         



                                                                           
                                                                 










                                                                          
                                                                 





                                                                   
                                                                        



                                                                          
                                                                 

















                                                                                         

                                               

























































                                                                                          

                                                  
























































































































































































































































































































                                                                                                                 

























                                                                                           













                                                                                            
                                                    




































































                                                                                                         
                                                                                 


























































































































                                                                                                               

                                                                                








































                                                                                                            







                                                                       

                                                                                     
                

                                                                                     

         



















































































































































































































                                                                                                            



                                                              
                                             









































































































                                                                                         



                                                                   

















































                                                                             


                                   
                                                      







                                                                      
                                               

              

                                                       

                                 
                                                





















                                                            
                          




                                                            
                                                   
                                    
 
                                               


                                    





                                                         





                                           
                                                           






                                                              
                                        






                                                                               
                                               






                                                                               
                                               
































                                                                               
                                               

                                              
                                                 

                            
                                                





                              




































                                                              
           














                                                                                


                                              
                                                              
                                              
                                                                      


                       
                                             
                                                                      















                                                                      
                               



                                                     
 
                                                            
                                                             




















                                                  

                                                    

                                                                             


















                                                                            
                                                                                


                              





                                                                             


         
                                                             








                                                            
                                                                           









                                                                        
                                                                        

























                                                                              
                                                                     









                                                                 
                                                                




                                                                        
                                 
















                                                                        
                                                                







                                                                   
                                          






















                                                                                
                                                 








                                                                         
                                         

























                                                                               
                               
                             

                      









                                                           
                                                                

                                                                        








                                            
                                          



                                                   
                                                  












                                                                          
         





                                                                            





                                                              

                                                           







                                                                            





                                                                   





                      
                                                                          

                                                         
                                                     

                 

                                                                     
            
                                                                     


                           

                                                                          




                    
                                                                          














































                                                                                

                                                                           
 
                                                                
                                           
                                           





                                                               
                                      




                                                                             
 
                                                                    
                            
                                                               

                                                             
                                                                            







                                                           
                                   
                                                                          
                                                        
 
                                               

                             
                                                       




                                                       

                                                                       










                                                                        
                                                                   














































                                                                                


                                                                     

         























                                                                                         
                                                                   






                                                                              
                                

                                                 





                                        




                                           
                                           
                                                            






                                                                             
                                 

                       

                                   






                                                                             





                                                                    
                                                              
                                                      



                                                                    


                                 


                                                                   

         




                                                                              





                                     
                                           




















































































                                                                                                                   
                                               































                                                                             
                                               

                                                 
                                


                                              
                                  




                                                                
                                               

                                               
                                











                                                                
                                               

























                                                                              
                                               





























                                                                           
                                                         
                                                           
                                                                           












                                                                
                                               


                                    
                                  







                                                                
                                               



                                         








































                                                                         
                
                           





















                                                                              
/*
 * Copyright 2005-2006 Erik Waling
 * Copyright 2006 Stephane Marchesin
 * Copyright 2007-2009 Stuart Bennett
 *
 * 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 AUTHORS 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.
 */

#include "drmP.h"
#define NV_DEBUG_NOTRACE
#include "nouveau_drv.h"
#include "nouveau_hw.h"
#include "nouveau_encoder.h"

/* these defines are made up */
#define NV_CIO_CRE_44_HEADA 0x0
#define NV_CIO_CRE_44_HEADB 0x3
#define FEATURE_MOBILE 0x10	/* also FEATURE_QUADRO for BMP */
#define LEGACY_I2C_CRT 0x80
#define LEGACY_I2C_PANEL 0x81
#define LEGACY_I2C_TV 0x82

#define EDID1_LEN 128

#define BIOSLOG(sip, fmt, arg...) NV_DEBUG(sip->dev, fmt, ##arg)
#define LOG_OLD_VALUE(x)

#define ROM16(x) le16_to_cpu(*(uint16_t *)&(x))
#define ROM32(x) le32_to_cpu(*(uint32_t *)&(x))

struct init_exec {
	bool execute;
	bool repeat;
};

static bool nv_cksum(const uint8_t *data, unsigned int length)
{
	/*
	 * There's a few checksums in the BIOS, so here's a generic checking
	 * function.
	 */
	int i;
	uint8_t sum = 0;

	for (i = 0; i < length; i++)
		sum += data[i];

	if (sum)
		return true;

	return false;
}

static int
score_vbios(struct drm_device *dev, const uint8_t *data, const bool writeable)
{
	if (!(data[0] == 0x55 && data[1] == 0xAA)) {
		NV_TRACEWARN(dev, "... BIOS signature not found\n");
		return 0;
	}

	if (nv_cksum(data, data[2] * 512)) {
		NV_TRACEWARN(dev, "... BIOS checksum invalid\n");
		/* if a ro image is somewhat bad, it's probably all rubbish */
		return writeable ? 2 : 1;
	} else
		NV_TRACE(dev, "... appears to be valid\n");

	return 3;
}

static void load_vbios_prom(struct drm_device *dev, uint8_t *data)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	uint32_t pci_nv_20, save_pci_nv_20;
	int pcir_ptr;
	int i;

	if (dev_priv->card_type >= NV_50)
		pci_nv_20 = 0x88050;
	else
		pci_nv_20 = NV_PBUS_PCI_NV_20;

	/* enable ROM access */
	save_pci_nv_20 = nvReadMC(dev, pci_nv_20);
	nvWriteMC(dev, pci_nv_20,
		  save_pci_nv_20 & ~NV_PBUS_PCI_NV_20_ROM_SHADOW_ENABLED);

	/* bail if no rom signature */
	if (nv_rd08(dev, NV_PROM_OFFSET) != 0x55 ||
	    nv_rd08(dev, NV_PROM_OFFSET + 1) != 0xaa)
		goto out;

	/* additional check (see note below) - read PCI record header */
	pcir_ptr = nv_rd08(dev, NV_PROM_OFFSET + 0x18) |
		   nv_rd08(dev, NV_PROM_OFFSET + 0x19) << 8;
	if (nv_rd08(dev, NV_PROM_OFFSET + pcir_ptr) != 'P' ||
	    nv_rd08(dev, NV_PROM_OFFSET + pcir_ptr + 1) != 'C' ||
	    nv_rd08(dev, NV_PROM_OFFSET + pcir_ptr + 2) != 'I' ||
	    nv_rd08(dev, NV_PROM_OFFSET + pcir_ptr + 3) != 'R')
		goto out;

	/* on some 6600GT/6800LE prom reads are messed up.  nvclock alleges a
	 * a good read may be obtained by waiting or re-reading (cargocult: 5x)
	 * each byte.  we'll hope pramin has something usable instead
	 */
	for (i = 0; i < NV_PROM_SIZE; i++)
		data[i] = nv_rd08(dev, NV_PROM_OFFSET + i);

out:
	/* disable ROM access */
	nvWriteMC(dev, pci_nv_20,
		  save_pci_nv_20 | NV_PBUS_PCI_NV_20_ROM_SHADOW_ENABLED);
}

static void load_vbios_pramin(struct drm_device *dev, uint8_t *data)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	uint32_t old_bar0_pramin = 0;
	int i;

	if (dev_priv->card_type >= NV_50) {
		uint32_t vbios_vram = (nv_rd32(dev, 0x619f04) & ~0xff) << 8;

		if (!vbios_vram)
			vbios_vram = (nv_rd32(dev, 0x1700) << 16) + 0xf0000;

		old_bar0_pramin = nv_rd32(dev, 0x1700);
		nv_wr32(dev, 0x1700, vbios_vram >> 16);
	}

	/* bail if no rom signature */
	if (nv_rd08(dev, NV_PRAMIN_OFFSET) != 0x55 ||
	    nv_rd08(dev, NV_PRAMIN_OFFSET + 1) != 0xaa)
		goto out;

	for (i = 0; i < NV_PROM_SIZE; i++)
		data[i] = nv_rd08(dev, NV_PRAMIN_OFFSET + i);

out:
	if (dev_priv->card_type >= NV_50)
		nv_wr32(dev, 0x1700, old_bar0_pramin);
}

static void load_vbios_pci(struct drm_device *dev, uint8_t *data)
{
	void __iomem *rom = NULL;
	size_t rom_len;
	int ret;

	ret = pci_enable_rom(dev->pdev);
	if (ret)
		return;

	rom = pci_map_rom(dev->pdev, &rom_len);
	if (!rom)
		goto out;
	memcpy_fromio(data, rom, rom_len);
	pci_unmap_rom(dev->pdev, rom);

out:
	pci_disable_rom(dev->pdev);
}

struct methods {
	const char desc[8];
	void (*loadbios)(struct drm_device *, uint8_t *);
	const bool rw;
};

static struct methods nv04_methods[] = {
	{ "PROM", load_vbios_prom, false },
	{ "PRAMIN", load_vbios_pramin, true },
	{ "PCIROM", load_vbios_pci, true },
};

static struct methods nv50_methods[] = {
	{ "PRAMIN", load_vbios_pramin, true },
	{ "PROM", load_vbios_prom, false },
	{ "PCIROM", load_vbios_pci, true },
};

#define METHODCNT 3

static bool NVShadowVBIOS(struct drm_device *dev, uint8_t *data)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct methods *methods;
	int i;
	int testscore = 3;
	int scores[METHODCNT];

	if (nouveau_vbios) {
		methods = nv04_methods;
		for (i = 0; i < METHODCNT; i++)
			if (!strcasecmp(nouveau_vbios, methods[i].desc))
				break;

		if (i < METHODCNT) {
			NV_INFO(dev, "Attempting to use BIOS image from %s\n",
				methods[i].desc);

			methods[i].loadbios(dev, data);
			if (score_vbios(dev, data, methods[i].rw))
				return true;
		}

		NV_ERROR(dev, "VBIOS source \'%s\' invalid\n", nouveau_vbios);
	}

	if (dev_priv->card_type < NV_50)
		methods = nv04_methods;
	else
		methods = nv50_methods;

	for (i = 0; i < METHODCNT; i++) {
		NV_TRACE(dev, "Attempting to load BIOS image from %s\n",
			 methods[i].desc);
		data[0] = data[1] = 0;	/* avoid reuse of previous image */
		methods[i].loadbios(dev, data);
		scores[i] = score_vbios(dev, data, methods[i].rw);
		if (scores[i] == testscore)
			return true;
	}

	while (--testscore > 0) {
		for (i = 0; i < METHODCNT; i++) {
			if (scores[i] == testscore) {
				NV_TRACE(dev, "Using BIOS image from %s\n",
					 methods[i].desc);
				methods[i].loadbios(dev, data);
				return true;
			}
		}
	}

	NV_ERROR(dev, "No valid BIOS image found\n");
	return false;
}

struct init_tbl_entry {
	char *name;
	uint8_t id;
	/* Return:
	 *  > 0: success, length of opcode
	 *    0: success, but abort further parsing of table (INIT_DONE etc)
	 *  < 0: failure, table parsing will be aborted
	 */
	int (*handler)(struct nvbios *, uint16_t, struct init_exec *);
};

struct bit_entry {
	uint8_t id[2];
	uint16_t length;
	uint16_t offset;
};

static int parse_init_table(struct nvbios *, unsigned int, struct init_exec *);

#define MACRO_INDEX_SIZE	2
#define MACRO_SIZE		8
#define CONDITION_SIZE		12
#define IO_FLAG_CONDITION_SIZE	9
#define IO_CONDITION_SIZE	5
#define MEM_INIT_SIZE		66

static void still_alive(void)
{
#if 0
	sync();
	msleep(2);
#endif
}

static uint32_t
munge_reg(struct nvbios *bios, uint32_t reg)
{
	struct drm_nouveau_private *dev_priv = bios->dev->dev_private;
	struct dcb_entry *dcbent = bios->display.output;

	if (dev_priv->card_type < NV_50)
		return reg;

	if (reg & 0x40000000) {
		BUG_ON(!dcbent);

		reg += (ffs(dcbent->or) - 1) * 0x800;
		if ((reg & 0x20000000) && !(dcbent->sorconf.link & 1))
			reg += 0x00000080;
	}

	reg &= ~0x60000000;
	return reg;
}

static int
valid_reg(struct nvbios *bios, uint32_t reg)
{
	struct drm_nouveau_private *dev_priv = bios->dev->dev_private;
	struct drm_device *dev = bios->dev;

	/* C51 has misaligned regs on purpose. Marvellous */
	if (reg & 0x2 ||
	    (reg & 0x1 && dev_priv->vbios.chip_version != 0x51))
		NV_ERROR(dev, "======= misaligned reg 0x%08X =======\n", reg);

	/* warn on C51 regs that haven't been verified accessible in tracing */
	if (reg & 0x1 && dev_priv->vbios.chip_version == 0x51 &&
	    reg != 0x130d && reg != 0x1311 && reg != 0x60081d)
		NV_WARN(dev, "=== C51 misaligned reg 0x%08X not verified ===\n",
			reg);

	if (reg >= (8*1024*1024)) {
		NV_ERROR(dev, "=== reg 0x%08x out of mapped bounds ===\n", reg);
		return 0;
	}

	return 1;
}

static bool
valid_idx_port(struct nvbios *bios, uint16_t port)
{
	struct drm_nouveau_private *dev_priv = bios->dev->dev_private;
	struct drm_device *dev = bios->dev;

	/*
	 * If adding more ports here, the read/write functions below will need
	 * updating so that the correct mmio range (PRMCIO, PRMDIO, PRMVIO) is
	 * used for the port in question
	 */
	if (dev_priv->card_type < NV_50) {
		if (port == NV_CIO_CRX__COLOR)
			return true;
		if (port == NV_VIO_SRX)
			return true;
	} else {
		if (port == NV_CIO_CRX__COLOR)
			return true;
	}

	NV_ERROR(dev, "========== unknown indexed io port 0x%04X ==========\n",
		 port);

	return false;
}

static bool
valid_port(struct nvbios *bios, uint16_t port)
{
	struct drm_device *dev = bios->dev;

	/*
	 * If adding more ports here, the read/write functions below will need
	 * updating so that the correct mmio range (PRMCIO, PRMDIO, PRMVIO) is
	 * used for the port in question
	 */
	if (port == NV_VIO_VSE2)
		return true;

	NV_ERROR(dev, "========== unknown io port 0x%04X ==========\n", port);

	return false;
}

static uint32_t
bios_rd32(struct nvbios *bios, uint32_t reg)
{
	uint32_t data;

	reg = munge_reg(bios, reg);
	if (!valid_reg(bios, reg))
		return 0;

	/*
	 * C51 sometimes uses regs with bit0 set in the address. For these
	 * cases there should exist a translation in a BIOS table to an IO
	 * port address which the BIOS uses for accessing the reg
	 *
	 * These only seem to appear for the power control regs to a flat panel,
	 * and the GPIO regs at 0x60081*.  In C51 mmio traces the normal regs
	 * for 0x1308 and 0x1310 are used - hence the mask below.  An S3
	 * suspend-resume mmio trace from a C51 will be required to see if this
	 * is true for the power microcode in 0x14.., or whether the direct IO
	 * port access method is needed
	 */
	if (reg & 0x1)
		reg &= ~0x1;

	data = nv_rd32(bios->dev, reg);

	BIOSLOG(bios, "	Read:  Reg: 0x%08X, Data: 0x%08X\n", reg, data);

	return data;
}

static void
bios_wr32(struct nvbios *bios, uint32_t reg, uint32_t data)
{
	struct drm_nouveau_private *dev_priv = bios->dev->dev_private;

	reg = munge_reg(bios, reg);
	if (!valid_reg(bios, reg))
		return;

	/* see note in bios_rd32 */
	if (reg & 0x1)
		reg &= 0xfffffffe;

	LOG_OLD_VALUE(bios_rd32(bios, reg));
	BIOSLOG(bios, "	Write: Reg: 0x%08X, Data: 0x%08X\n", reg, data);

	if (dev_priv->vbios.execute) {
		still_alive();
		nv_wr32(bios->dev, reg, data);
	}
}

static uint8_t
bios_idxprt_rd(struct nvbios *bios, uint16_t port, uint8_t index)
{
	struct drm_nouveau_private *dev_priv = bios->dev->dev_private;
	struct drm_device *dev = bios->dev;
	uint8_t data;

	if (!valid_idx_port(bios, port))
		return 0;

	if (dev_priv->card_type < NV_50) {
		if (port == NV_VIO_SRX)
			data = NVReadVgaSeq(dev, bios->state.crtchead, index);
		else	/* assume NV_CIO_CRX__COLOR */
			data = NVReadVgaCrtc(dev, bios->state.crtchead, index);
	} else {
		uint32_t data32;

		data32 = bios_rd32(bios, NV50_PDISPLAY_VGACRTC(index & ~3));
		data = (data32 >> ((index & 3) << 3)) & 0xff;
	}

	BIOSLOG(bios, "	Indexed IO read:  Port: 0x%04X, Index: 0x%02X, "
		      "Head: 0x%02X, Data: 0x%02X\n",
		port, index, bios->state.crtchead, data);
	return data;
}

static void
bios_idxprt_wr(struct nvbios *bios, uint16_t port, uint8_t index, uint8_t data)
{
	struct drm_nouveau_private *dev_priv = bios->dev->dev_private;
	struct drm_device *dev = bios->dev;

	if (!valid_idx_port(bios, port))
		return;

	/*
	 * The current head is maintained in the nvbios member  state.crtchead.
	 * We trap changes to CR44 and update the head variable and hence the
	 * register set written.
	 * As CR44 only exists on CRTC0, we update crtchead to head0 in advance
	 * of the write, and to head1 after the write
	 */
	if (port == NV_CIO_CRX__COLOR && index == NV_CIO_CRE_44 &&
	    data != NV_CIO_CRE_44_HEADB)
		bios->state.crtchead = 0;

	LOG_OLD_VALUE(bios_idxprt_rd(bios, port, index));
	BIOSLOG(bios, "	Indexed IO write: Port: 0x%04X, Index: 0x%02X, "
		      "Head: 0x%02X, Data: 0x%02X\n",
		port, index, bios->state.crtchead, data);

	if (bios->execute && dev_priv->card_type < NV_50) {
		still_alive();
		if (port == NV_VIO_SRX)
			NVWriteVgaSeq(dev, bios->state.crtchead, index, data);
		else	/* assume NV_CIO_CRX__COLOR */
			NVWriteVgaCrtc(dev, bios->state.crtchead, index, data);
	} else
	if (bios->execute) {
		uint32_t data32, shift = (index & 3) << 3;

		still_alive();

		data32  = bios_rd32(bios, NV50_PDISPLAY_VGACRTC(index & ~3));
		data32 &= ~(0xff << shift);
		data32 |= (data << shift);
		bios_wr32(bios, NV50_PDISPLAY_VGACRTC(index & ~3), data32);
	}

	if (port == NV_CIO_CRX__COLOR &&
	    index == NV_CIO_CRE_44 && data == NV_CIO_CRE_44_HEADB)
		bios->state.crtchead = 1;
}

static uint8_t
bios_port_rd(struct nvbios *bios, uint16_t port)
{
	uint8_t data, head = bios->state.crtchead;

	if (!valid_port(bios, port))
		return 0;

	data = NVReadPRMVIO(bios->dev, head, NV_PRMVIO0_OFFSET + port);

	BIOSLOG(bios, "	IO read:  Port: 0x%04X, Head: 0x%02X, Data: 0x%02X\n",
		port, head, data);

	return data;
}

static void
bios_port_wr(struct nvbios *bios, uint16_t port, uint8_t data)
{
	int head = bios->state.crtchead;

	if (!valid_port(bios, port))
		return;

	LOG_OLD_VALUE(bios_port_rd(bios, port));
	BIOSLOG(bios, "	IO write: Port: 0x%04X, Head: 0x%02X, Data: 0x%02X\n",
		port, head, data);

	if (!bios->execute)
		return;

	still_alive();
	NVWritePRMVIO(bios->dev, head, NV_PRMVIO0_OFFSET + port, data);
}

static bool
io_flag_condition_met(struct nvbios *bios, uint16_t offset, uint8_t cond)
{
	/*
	 * The IO flag condition entry has 2 bytes for the CRTC port; 1 byte
	 * for the CRTC index; 1 byte for the mask to apply to the value
	 * retrieved from the CRTC; 1 byte for the shift right to apply to the
	 * masked CRTC value; 2 bytes for the offset to the flag array, to
	 * which the shifted value is added; 1 byte for the mask applied to the
	 * value read from the flag array; and 1 byte for the value to compare
	 * against the masked byte from the flag table.
	 */

	uint16_t condptr = bios->io_flag_condition_tbl_ptr + cond * IO_FLAG_CONDITION_SIZE;
	uint16_t crtcport = ROM16(bios->data[condptr]);
	uint8_t crtcindex = bios->data[condptr + 2];
	uint8_t mask = bios->data[condptr + 3];
	uint8_t shift = bios->data[condptr + 4];
	uint16_t flagarray = ROM16(bios->data[condptr + 5]);
	uint8_t flagarraymask = bios->data[condptr + 7];
	uint8_t cmpval = bios->data[condptr + 8];
	uint8_t data;

	BIOSLOG(bios, "0x%04X: Port: 0x%04X, Index: 0x%02X, Mask: 0x%02X, "
		      "Shift: 0x%02X, FlagArray: 0x%04X, FAMask: 0x%02X, "
		      "Cmpval: 0x%02X\n",
		offset, crtcport, crtcindex, mask, shift, flagarray, flagarraymask, cmpval);

	data = bios_idxprt_rd(bios, crtcport, crtcindex);

	data = bios->data[flagarray + ((data & mask) >> shift)];
	data &= flagarraymask;

	BIOSLOG(bios, "0x%04X: Checking if 0x%02X equals 0x%02X\n",
		offset, data, cmpval);

	return (data == cmpval);
}

static bool
bios_condition_met(struct nvbios *bios, uint16_t offset, uint8_t cond)
{
	/*
	 * The condition table entry has 4 bytes for the address of the
	 * register to check, 4 bytes for a mask to apply to the register and
	 * 4 for a test comparison value
	 */

	uint16_t condptr = bios->condition_tbl_ptr + cond * CONDITION_SIZE;
	uint32_t reg = ROM32(bios->data[condptr]);
	uint32_t mask = ROM32(bios->data[condptr + 4]);
	uint32_t cmpval = ROM32(bios->data[condptr + 8]);
	uint32_t data;

	BIOSLOG(bios, "0x%04X: Cond: 0x%02X, Reg: 0x%08X, Mask: 0x%08X\n",
		offset, cond, reg, mask);

	data = bios_rd32(bios, reg) & mask;

	BIOSLOG(bios, "0x%04X: Checking if 0x%08X equals 0x%08X\n",
		offset, data, cmpval);

	return (data == cmpval);
}

static bool
io_condition_met(struct nvbios *bios, uint16_t offset, uint8_t cond)
{
	/*
	 * The IO condition entry has 2 bytes for the IO port address; 1 byte
	 * for the index to write to io_port; 1 byte for the mask to apply to
	 * the byte read from io_port+1; and 1 byte for the value to compare
	 * against the masked byte.
	 */

	uint16_t condptr = bios->io_condition_tbl_ptr + cond * IO_CONDITION_SIZE;
	uint16_t io_port = ROM16(bios->data[condptr]);
	uint8_t port_index = bios->data[condptr + 2];
	uint8_t mask = bios->data[condptr + 3];
	uint8_t cmpval = bios->data[condptr + 4];

	uint8_t data = bios_idxprt_rd(bios, io_port, port_index) & mask;

	BIOSLOG(bios, "0x%04X: Checking if 0x%02X equals 0x%02X\n",
		offset, data, cmpval);

	return (data == cmpval);
}

static int
nv50_pll_set(struct drm_device *dev, uint32_t reg, uint32_t clk)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	uint32_t reg0 = nv_rd32(dev, reg + 0);
	uint32_t reg1 = nv_rd32(dev, reg + 4);
	struct nouveau_pll_vals pll;
	struct pll_lims pll_limits;
	int ret;

	ret = get_pll_limits(dev, reg, &pll_limits);
	if (ret)
		return ret;

	clk = nouveau_calc_pll_mnp(dev, &pll_limits, clk, &pll);
	if (!clk)
		return -ERANGE;

	reg0 = (reg0 & 0xfff8ffff) | (pll.log2P << 16);
	reg1 = (reg1 & 0xffff0000) | (pll.N1 << 8) | pll.M1;

	if (dev_priv->vbios.execute) {
		still_alive();
		nv_wr32(dev, reg + 4, reg1);
		nv_wr32(dev, reg + 0, reg0);
	}

	return 0;
}

static int
setPLL(struct nvbios *bios, uint32_t reg, uint32_t clk)
{
	struct drm_device *dev = bios->dev;
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	/* clk in kHz */
	struct pll_lims pll_lim;
	struct nouveau_pll_vals pllvals;
	int ret;

	if (dev_priv->card_type >= NV_50)
		return nv50_pll_set(dev, reg, clk);

	/* high regs (such as in the mac g5 table) are not -= 4 */
	ret = get_pll_limits(dev, reg > 0x405c ? reg : reg - 4, &pll_lim);
	if (ret)
		return ret;

	clk = nouveau_calc_pll_mnp(dev, &pll_lim, clk, &pllvals);
	if (!clk)
		return -ERANGE;

	if (bios->execute) {
		still_alive();
		nouveau_hw_setpll(dev, reg, &pllvals);
	}

	return 0;
}

static int dcb_entry_idx_from_crtchead(struct drm_device *dev)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;

	/*
	 * For the results of this function to be correct, CR44 must have been
	 * set (using bios_idxprt_wr to set crtchead), CR58 set for CR57 = 0,
	 * and the DCB table parsed, before the script calling the function is
	 * run.  run_digital_op_script is example of how to do such setup
	 */

	uint8_t dcb_entry = NVReadVgaCrtc5758(dev, bios->state.crtchead, 0);

	if (dcb_entry > bios->dcb.entries) {
		NV_ERROR(dev, "CR58 doesn't have a valid DCB entry currently "
				"(%02X)\n", dcb_entry);
		dcb_entry = 0x7f;	/* unused / invalid marker */
	}

	return dcb_entry;
}

static int
read_dcb_i2c_entry(struct drm_device *dev, int dcb_version, uint8_t *i2ctable, int index, struct dcb_i2c_entry *i2c)
{
	uint8_t dcb_i2c_ver = dcb_version, headerlen = 0, entry_len = 4;
	int i2c_entries = DCB_MAX_NUM_I2C_ENTRIES;
	int recordoffset = 0, rdofs = 1, wrofs = 0;
	uint8_t port_type = 0;

	if (!i2ctable)
		return -EINVAL;

	if (dcb_version >= 0x30) {
		if (i2ctable[0] != dcb_version) /* necessary? */
			NV_WARN(dev,
				"DCB I2C table version mismatch (%02X vs %02X)\n",
				i2ctable[0], dcb_version);
		dcb_i2c_ver = i2ctable[0];
		headerlen = i2ctable[1];
		if (i2ctable[2] <= DCB_MAX_NUM_I2C_ENTRIES)
			i2c_entries = i2ctable[2];
		else
			NV_WARN(dev,
				"DCB I2C table has more entries than indexable "
				"(%d entries, max %d)\n", i2ctable[2],
				DCB_MAX_NUM_I2C_ENTRIES);
		entry_len = i2ctable[3];
		/* [4] is i2c_default_indices, read in parse_dcb_table() */
	}
	/*
	 * It's your own fault if you call this function on a DCB 1.1 BIOS --
	 * the test below is for DCB 1.2
	 */
	if (dcb_version < 0x14) {
		recordoffset = 2;
		rdofs = 0;
		wrofs = 1;
	}

	if (index == 0xf)
		return 0;
	if (index >= i2c_entries) {
		NV_ERROR(dev, "DCB I2C index too big (%d >= %d)\n",
			 index, i2ctable[2]);
		return -ENOENT;
	}
	if (i2ctable[headerlen + entry_len * index + 3] == 0xff) {
		NV_ERROR(dev, "DCB I2C entry invalid\n");
		return -EINVAL;
	}

	if (dcb_i2c_ver >= 0x30) {
		port_type = i2ctable[headerlen + recordoffset + 3 + entry_len * index];

		/*
		 * Fixup for chips using same address offset for read and
		 * write.
		 */
		if (port_type == 4)	/* seen on C51 */
			rdofs = wrofs = 1;
		if (port_type >= 5)	/* G80+ */
			rdofs = wrofs = 0;
	}

	if (dcb_i2c_ver >= 0x40) {
		if (port_type != 5 && port_type != 6)
			NV_WARN(dev, "DCB I2C table has port type %d\n", port_type);

		i2c->entry = ROM32(i2ctable[headerlen + recordoffset + entry_len * index]);
	}

	i2c->port_type = port_type;
	i2c->read = i2ctable[headerlen + recordoffset + rdofs + entry_len * index];
	i2c->write = i2ctable[headerlen + recordoffset + wrofs + entry_len * index];

	return 0;
}

static struct nouveau_i2c_chan *
init_i2c_device_find(struct drm_device *dev, int i2c_index)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct dcb_table *dcb = &dev_priv->vbios.dcb;

	if (i2c_index == 0xff) {
		/* note: dcb_entry_idx_from_crtchead needs pre-script set-up */
		int idx = dcb_entry_idx_from_crtchead(dev), shift = 0;
		int default_indices = dcb->i2c_default_indices;

		if (idx != 0x7f && dcb->entry[idx].i2c_upper_default)
			shift = 4;

		i2c_index = (default_indices >> shift) & 0xf;
	}
	if (i2c_index == 0x80)	/* g80+ */
		i2c_index = dcb->i2c_default_indices & 0xf;
	else
	if (i2c_index == 0x81)
		i2c_index = (dcb->i2c_default_indices & 0xf0) >> 4;

	if (i2c_index > DCB_MAX_NUM_I2C_ENTRIES) {
		NV_ERROR(dev, "invalid i2c_index 0x%x\n", i2c_index);
		return NULL;
	}

	/* Make sure i2c table entry has been parsed, it may not
	 * have been if this is a bus not referenced by a DCB encoder
	 */
	read_dcb_i2c_entry(dev, dcb->version, dcb->i2c_table,
			   i2c_index, &dcb->i2c[i2c_index]);

	return nouveau_i2c_find(dev, i2c_index);
}

static uint32_t
get_tmds_index_reg(struct drm_device *dev, uint8_t mlv)
{
	/*
	 * For mlv < 0x80, it is an index into a table of TMDS base addresses.
	 * For mlv == 0x80 use the "or" value of the dcb_entry indexed by
	 * CR58 for CR57 = 0 to index a table of offsets to the basic
	 * 0x6808b0 address.
	 * For mlv == 0x81 use the "or" value of the dcb_entry indexed by
	 * CR58 for CR57 = 0 to index a table of offsets to the basic
	 * 0x6808b0 address, and then flip the offset by 8.
	 */

	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;
	const int pramdac_offset[13] = {
		0, 0, 0x8, 0, 0x2000, 0, 0, 0, 0x2008, 0, 0, 0, 0x2000 };
	const uint32_t pramdac_table[4] = {
		0x6808b0, 0x6808b8, 0x6828b0, 0x6828b8 };

	if (mlv >= 0x80) {
		int dcb_entry, dacoffset;

		/* note: dcb_entry_idx_from_crtchead needs pre-script set-up */
		dcb_entry = dcb_entry_idx_from_crtchead(dev);
		if (dcb_entry == 0x7f)
			return 0;
		dacoffset = pramdac_offset[bios->dcb.entry[dcb_entry].or];
		if (mlv == 0x81)
			dacoffset ^= 8;
		return 0x6808b0 + dacoffset;
	} else {
		if (mlv >= ARRAY_SIZE(pramdac_table)) {
			NV_ERROR(dev, "Magic Lookup Value too big (%02X)\n",
									mlv);
			return 0;
		}
		return pramdac_table[mlv];
	}
}

static int
init_io_restrict_prog(struct nvbios *bios, uint16_t offset,
		      struct init_exec *iexec)
{
	/*
	 * INIT_IO_RESTRICT_PROG   opcode: 0x32 ('2')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (16 bit): CRTC port
	 * offset + 3  (8  bit): CRTC index
	 * offset + 4  (8  bit): mask
	 * offset + 5  (8  bit): shift
	 * offset + 6  (8  bit): count
	 * offset + 7  (32 bit): register
	 * offset + 11 (32 bit): configuration 1
	 * ...
	 *
	 * Starting at offset + 11 there are "count" 32 bit values.
	 * To find out which value to use read index "CRTC index" on "CRTC
	 * port", AND this value with "mask" and then bit shift right "shift"
	 * bits.  Read the appropriate value using this index and write to
	 * "register"
	 */

	uint16_t crtcport = ROM16(bios->data[offset + 1]);
	uint8_t crtcindex = bios->data[offset + 3];
	uint8_t mask = bios->data[offset + 4];
	uint8_t shift = bios->data[offset + 5];
	uint8_t count = bios->data[offset + 6];
	uint32_t reg = ROM32(bios->data[offset + 7]);
	uint8_t config;
	uint32_t configval;
	int len = 11 + count * 4;

	if (!iexec->execute)
		return len;

	BIOSLOG(bios, "0x%04X: Port: 0x%04X, Index: 0x%02X, Mask: 0x%02X, "
		      "Shift: 0x%02X, Count: 0x%02X, Reg: 0x%08X\n",
		offset, crtcport, crtcindex, mask, shift, count, reg);

	config = (bios_idxprt_rd(bios, crtcport, crtcindex) & mask) >> shift;
	if (config > count) {
		NV_ERROR(bios->dev,
			 "0x%04X: Config 0x%02X exceeds maximal bound 0x%02X\n",
			 offset, config, count);
		return -EINVAL;
	}

	configval = ROM32(bios->data[offset + 11 + config * 4]);

	BIOSLOG(bios, "0x%04X: Writing config %02X\n", offset, config);

	bios_wr32(bios, reg, configval);

	return len;
}

static int
init_repeat(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_REPEAT   opcode: 0x33 ('3')
	 *
	 * offset      (8 bit): opcode
	 * offset + 1  (8 bit): count
	 *
	 * Execute script following this opcode up to INIT_REPEAT_END
	 * "count" times
	 */

	uint8_t count = bios->data[offset + 1];
	uint8_t i;

	/* no iexec->execute check by design */

	BIOSLOG(bios, "0x%04X: Repeating following segment %d times\n",
		offset, count);

	iexec->repeat = true;

	/*
	 * count - 1, as the script block will execute once when we leave this
	 * opcode -- this is compatible with bios behaviour as:
	 * a) the block is always executed at least once, even if count == 0
	 * b) the bios interpreter skips to the op following INIT_END_REPEAT,
	 * while we don't
	 */
	for (i = 0; i < count - 1; i++)
		parse_init_table(bios, offset + 2, iexec);

	iexec->repeat = false;

	return 2;
}

static int
init_io_restrict_pll(struct nvbios *bios, uint16_t offset,
		     struct init_exec *iexec)
{
	/*
	 * INIT_IO_RESTRICT_PLL   opcode: 0x34 ('4')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (16 bit): CRTC port
	 * offset + 3  (8  bit): CRTC index
	 * offset + 4  (8  bit): mask
	 * offset + 5  (8  bit): shift
	 * offset + 6  (8  bit): IO flag condition index
	 * offset + 7  (8  bit): count
	 * offset + 8  (32 bit): register
	 * offset + 12 (16 bit): frequency 1
	 * ...
	 *
	 * Starting at offset + 12 there are "count" 16 bit frequencies (10kHz).
	 * Set PLL register "register" to coefficients for frequency n,
	 * selected by reading index "CRTC index" of "CRTC port" ANDed with
	 * "mask" and shifted right by "shift".
	 *
	 * If "IO flag condition index" > 0, and condition met, double
	 * frequency before setting it.
	 */

	uint16_t crtcport = ROM16(bios->data[offset + 1]);
	uint8_t crtcindex = bios->data[offset + 3];
	uint8_t mask = bios->data[offset + 4];
	uint8_t shift = bios->data[offset + 5];
	int8_t io_flag_condition_idx = bios->data[offset + 6];
	uint8_t count = bios->data[offset + 7];
	uint32_t reg = ROM32(bios->data[offset + 8]);
	uint8_t config;
	uint16_t freq;
	int len = 12 + count * 2;

	if (!iexec->execute)
		return len;

	BIOSLOG(bios, "0x%04X: Port: 0x%04X, Index: 0x%02X, Mask: 0x%02X, "
		      "Shift: 0x%02X, IO Flag Condition: 0x%02X, "
		      "Count: 0x%02X, Reg: 0x%08X\n",
		offset, crtcport, crtcindex, mask, shift,
		io_flag_condition_idx, count, reg);

	config = (bios_idxprt_rd(bios, crtcport, crtcindex) & mask) >> shift;
	if (config > count) {
		NV_ERROR(bios->dev,
			 "0x%04X: Config 0x%02X exceeds maximal bound 0x%02X\n",
			 offset, config, count);
		return -EINVAL;
	}

	freq = ROM16(bios->data[offset + 12 + config * 2]);

	if (io_flag_condition_idx > 0) {
		if (io_flag_condition_met(bios, offset, io_flag_condition_idx)) {
			BIOSLOG(bios, "0x%04X: Condition fulfilled -- "
				      "frequency doubled\n", offset);
			freq *= 2;
		} else
			BIOSLOG(bios, "0x%04X: Condition not fulfilled -- "
				      "frequency unchanged\n", offset);
	}

	BIOSLOG(bios, "0x%04X: Reg: 0x%08X, Config: 0x%02X, Freq: %d0kHz\n",
		offset, reg, config, freq);

	setPLL(bios, reg, freq * 10);

	return len;
}

static int
init_end_repeat(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_END_REPEAT   opcode: 0x36 ('6')
	 *
	 * offset      (8 bit): opcode
	 *
	 * Marks the end of the block for INIT_REPEAT to repeat
	 */

	/* no iexec->execute check by design */

	/*
	 * iexec->repeat flag necessary to go past INIT_END_REPEAT opcode when
	 * we're not in repeat mode
	 */
	if (iexec->repeat)
		return 0;

	return 1;
}

static int
init_copy(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_COPY   opcode: 0x37 ('7')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (32 bit): register
	 * offset + 5  (8  bit): shift
	 * offset + 6  (8  bit): srcmask
	 * offset + 7  (16 bit): CRTC port
	 * offset + 9  (8 bit): CRTC index
	 * offset + 10  (8 bit): mask
	 *
	 * Read index "CRTC index" on "CRTC port", AND with "mask", OR with
	 * (REGVAL("register") >> "shift" & "srcmask") and write-back to CRTC
	 * port
	 */

	uint32_t reg = ROM32(bios->data[offset + 1]);
	uint8_t shift = bios->data[offset + 5];
	uint8_t srcmask = bios->data[offset + 6];
	uint16_t crtcport = ROM16(bios->data[offset + 7]);
	uint8_t crtcindex = bios->data[offset + 9];
	uint8_t mask = bios->data[offset + 10];
	uint32_t data;
	uint8_t crtcdata;

	if (!iexec->execute)
		return 11;

	BIOSLOG(bios, "0x%04X: Reg: 0x%08X, Shift: 0x%02X, SrcMask: 0x%02X, "
		      "Port: 0x%04X, Index: 0x%02X, Mask: 0x%02X\n",
		offset, reg, shift, srcmask, crtcport, crtcindex, mask);

	data = bios_rd32(bios, reg);

	if (shift < 0x80)
		data >>= shift;
	else
		data <<= (0x100 - shift);

	data &= srcmask;

	crtcdata  = bios_idxprt_rd(bios, crtcport, crtcindex) & mask;
	crtcdata |= (uint8_t)data;
	bios_idxprt_wr(bios, crtcport, crtcindex, crtcdata);

	return 11;
}

static int
init_not(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_NOT   opcode: 0x38 ('8')
	 *
	 * offset      (8  bit): opcode
	 *
	 * Invert the current execute / no-execute condition (i.e. "else")
	 */
	if (iexec->execute)
		BIOSLOG(bios, "0x%04X: ------ Skipping following commands  ------\n", offset);
	else
		BIOSLOG(bios, "0x%04X: ------ Executing following commands ------\n", offset);

	iexec->execute = !iexec->execute;
	return 1;
}

static int
init_io_flag_condition(struct nvbios *bios, uint16_t offset,
		       struct init_exec *iexec)
{
	/*
	 * INIT_IO_FLAG_CONDITION   opcode: 0x39 ('9')
	 *
	 * offset      (8 bit): opcode
	 * offset + 1  (8 bit): condition number
	 *
	 * Check condition "condition number" in the IO flag condition table.
	 * If condition not met skip subsequent opcodes until condition is
	 * inverted (INIT_NOT), or we hit INIT_RESUME
	 */

	uint8_t cond = bios->data[offset + 1];

	if (!iexec->execute)
		return 2;

	if (io_flag_condition_met(bios, offset, cond))
		BIOSLOG(bios, "0x%04X: Condition fulfilled -- continuing to execute\n", offset);
	else {
		BIOSLOG(bios, "0x%04X: Condition not fulfilled -- skipping following commands\n", offset);
		iexec->execute = false;
	}

	return 2;
}

static int
init_dp_condition(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_DP_CONDITION   opcode: 0x3A ('')
	 *
	 * offset      (8 bit): opcode
	 * offset + 1  (8 bit): "sub" opcode
	 * offset + 2  (8 bit): unknown
	 *
	 */

	struct bit_displayport_encoder_table *dpe = NULL;
	struct dcb_entry *dcb = bios->display.output;
	struct drm_device *dev = bios->dev;
	uint8_t cond = bios->data[offset + 1];
	int dummy;

	BIOSLOG(bios, "0x%04X: subop 0x%02X\n", offset, cond);

	if (!iexec->execute)
		return 3;

	dpe = nouveau_bios_dp_table(dev, dcb, &dummy);
	if (!dpe) {
		NV_ERROR(dev, "0x%04X: INIT_3A: no encoder table!!\n", offset);
		return -EINVAL;
	}

	switch (cond) {
	case 0:
	{
		struct dcb_connector_table_entry *ent =
			&bios->dcb.connector.entry[dcb->connector];

		if (ent->type != DCB_CONNECTOR_eDP)
			iexec->execute = false;
	}
		break;
	case 1:
	case 2:
		if (!(dpe->unknown & cond))
			iexec->execute = false;
		break;
	case 5:
	{
		struct nouveau_i2c_chan *auxch;
		int ret;

		auxch = nouveau_i2c_find(dev, bios->display.output->i2c_index);
		if (!auxch)
			return -ENODEV;

		ret = nouveau_dp_auxch(auxch, 9, 0xd, &cond, 1);
		if (ret)
			return ret;

		if (cond & 1)
			iexec->execute = false;
	}
		break;
	default:
		NV_WARN(dev, "0x%04X: unknown INIT_3A op: %d\n", offset, cond);
		break;
	}

	if (iexec->execute)
		BIOSLOG(bios, "0x%04X: continuing to execute\n", offset);
	else
		BIOSLOG(bios, "0x%04X: skipping following commands\n", offset);

	return 3;
}

static int
init_op_3b(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_3B   opcode: 0x3B ('')
	 *
	 * offset      (8 bit): opcode
	 * offset + 1  (8 bit): crtc index
	 *
	 */

	uint8_t or = ffs(bios->display.output->or) - 1;
	uint8_t index = bios->data[offset + 1];
	uint8_t data;

	if (!iexec->execute)
		return 2;

	data = bios_idxprt_rd(bios, 0x3d4, index);
	bios_idxprt_wr(bios, 0x3d4, index, data & ~(1 << or));
	return 2;
}

static int
init_op_3c(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_3C   opcode: 0x3C ('')
	 *
	 * offset      (8 bit): opcode
	 * offset + 1  (8 bit): crtc index
	 *
	 */

	uint8_t or = ffs(bios->display.output->or) - 1;
	uint8_t index = bios->data[offset + 1];
	uint8_t data;

	if (!iexec->execute)
		return 2;

	data = bios_idxprt_rd(bios, 0x3d4, index);
	bios_idxprt_wr(bios, 0x3d4, index, data | (1 << or));
	return 2;
}

static int
init_idx_addr_latched(struct nvbios *bios, uint16_t offset,
		      struct init_exec *iexec)
{
	/*
	 * INIT_INDEX_ADDRESS_LATCHED   opcode: 0x49 ('I')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (32 bit): control register
	 * offset + 5  (32 bit): data register
	 * offset + 9  (32 bit): mask
	 * offset + 13 (32 bit): data
	 * offset + 17 (8  bit): count
	 * offset + 18 (8  bit): address 1
	 * offset + 19 (8  bit): data 1
	 * ...
	 *
	 * For each of "count" address and data pairs, write "data n" to
	 * "data register", read the current value of "control register",
	 * and write it back once ANDed with "mask", ORed with "data",
	 * and ORed with "address n"
	 */

	uint32_t controlreg = ROM32(bios->data[offset + 1]);
	uint32_t datareg = ROM32(bios->data[offset + 5]);
	uint32_t mask = ROM32(bios->data[offset + 9]);
	uint32_t data = ROM32(bios->data[offset + 13]);
	uint8_t count = bios->data[offset + 17];
	int len = 18 + count * 2;
	uint32_t value;
	int i;

	if (!iexec->execute)
		return len;

	BIOSLOG(bios, "0x%04X: ControlReg: 0x%08X, DataReg: 0x%08X, "
		      "Mask: 0x%08X, Data: 0x%08X, Count: 0x%02X\n",
		offset, controlreg, datareg, mask, data, count);

	for (i = 0; i < count; i++) {
		uint8_t instaddress = bios->data[offset + 18 + i * 2];
		uint8_t instdata = bios->data[offset + 19 + i * 2];

		BIOSLOG(bios, "0x%04X: Address: 0x%02X, Data: 0x%02X\n",
			offset, instaddress, instdata);

		bios_wr32(bios, datareg, instdata);
		value  = bios_rd32(bios, controlreg) & mask;
		value |= data;
		value |= instaddress;
		bios_wr32(bios, controlreg, value);
	}

	return len;
}

static int
init_io_restrict_pll2(struct nvbios *bios, uint16_t offset,
		      struct init_exec *iexec)
{
	/*
	 * INIT_IO_RESTRICT_PLL2   opcode: 0x4A ('J')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (16 bit): CRTC port
	 * offset + 3  (8  bit): CRTC index
	 * offset + 4  (8  bit): mask
	 * offset + 5  (8  bit): shift
	 * offset + 6  (8  bit): count
	 * offset + 7  (32 bit): register
	 * offset + 11 (32 bit): frequency 1
	 * ...
	 *
	 * Starting at offset + 11 there are "count" 32 bit frequencies (kHz).
	 * Set PLL register "register" to coefficients for frequency n,
	 * selected by reading index "CRTC index" of "CRTC port" ANDed with
	 * "mask" and shifted right by "shift".
	 */

	uint16_t crtcport = ROM16(bios->data[offset + 1]);
	uint8_t crtcindex = bios->data[offset + 3];
	uint8_t mask = bios->data[offset + 4];
	uint8_t shift = bios->data[offset + 5];
	uint8_t count = bios->data[offset + 6];
	uint32_t reg = ROM32(bios->data[offset + 7]);
	int len = 11 + count * 4;
	uint8_t config;
	uint32_t freq;

	if (!iexec->execute)
		return len;

	BIOSLOG(bios, "0x%04X: Port: 0x%04X, Index: 0x%02X, Mask: 0x%02X, "
		      "Shift: 0x%02X, Count: 0x%02X, Reg: 0x%08X\n",
		offset, crtcport, crtcindex, mask, shift, count, reg);

	if (!reg)
		return len;

	config = (bios_idxprt_rd(bios, crtcport, crtcindex) & mask) >> shift;
	if (config > count) {
		NV_ERROR(bios->dev,
			 "0x%04X: Config 0x%02X exceeds maximal bound 0x%02X\n",
			 offset, config, count);
		return -EINVAL;
	}

	freq = ROM32(bios->data[offset + 11 + config * 4]);

	BIOSLOG(bios, "0x%04X: Reg: 0x%08X, Config: 0x%02X, Freq: %dkHz\n",
		offset, reg, config, freq);

	setPLL(bios, reg, freq);

	return len;
}

static int
init_pll2(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_PLL2   opcode: 0x4B ('K')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (32 bit): register
	 * offset + 5  (32 bit): freq
	 *
	 * Set PLL register "register" to coefficients for frequency "freq"
	 */

	uint32_t reg = ROM32(bios->data[offset + 1]);
	uint32_t freq = ROM32(bios->data[offset + 5]);

	if (!iexec->execute)
		return 9;

	BIOSLOG(bios, "0x%04X: Reg: 0x%04X, Freq: %dkHz\n",
		offset, reg, freq);

	setPLL(bios, reg, freq);
	return 9;
}

static int
init_i2c_byte(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_I2C_BYTE   opcode: 0x4C ('L')
	 *
	 * offset      (8 bit): opcode
	 * offset + 1  (8 bit): DCB I2C table entry index
	 * offset + 2  (8 bit): I2C slave address
	 * offset + 3  (8 bit): count
	 * offset + 4  (8 bit): I2C register 1
	 * offset + 5  (8 bit): mask 1
	 * offset + 6  (8 bit): data 1
	 * ...
	 *
	 * For each of "count" registers given by "I2C register n" on the device
	 * addressed by "I2C slave address" on the I2C bus given by
	 * "DCB I2C table entry index", read the register, AND the result with
	 * "mask n" and OR it with "data n" before writing it back to the device
	 */

	uint8_t i2c_index = bios->data[offset + 1];
	uint8_t i2c_address = bios->data[offset + 2] >> 1;
	uint8_t count = bios->data[offset + 3];
	struct nouveau_i2c_chan *chan;
	int len = 4 + count * 3;
	int ret, i;

	if (!iexec->execute)
		return len;

	BIOSLOG(bios, "0x%04X: DCBI2CIndex: 0x%02X, I2CAddress: 0x%02X, "
		      "Count: 0x%02X\n",
		offset, i2c_index, i2c_address, count);

	chan = init_i2c_device_find(bios->dev, i2c_index);
	if (!chan)
		return -ENODEV;

	for (i = 0; i < count; i++) {
		uint8_t reg = bios->data[offset + 4 + i * 3];
		uint8_t mask = bios->data[offset + 5 + i * 3];
		uint8_t data = bios->data[offset + 6 + i * 3];
		union i2c_smbus_data val;

		ret = i2c_smbus_xfer(&chan->adapter, i2c_address, 0,
				     I2C_SMBUS_READ, reg,
				     I2C_SMBUS_BYTE_DATA, &val);
		if (ret < 0)
			return ret;

		BIOSLOG(bios, "0x%04X: I2CReg: 0x%02X, Value: 0x%02X, "
			      "Mask: 0x%02X, Data: 0x%02X\n",
			offset, reg, val.byte, mask, data);

		if (!bios->execute)
			continue;

		val.byte &= mask;
		val.byte |= data;
		ret = i2c_smbus_xfer(&chan->adapter, i2c_address, 0,
				     I2C_SMBUS_WRITE, reg,
				     I2C_SMBUS_BYTE_DATA, &val);
		if (ret < 0)
			return ret;
	}

	return len;
}

static int
init_zm_i2c_byte(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_ZM_I2C_BYTE   opcode: 0x4D ('M')
	 *
	 * offset      (8 bit): opcode
	 * offset + 1  (8 bit): DCB I2C table entry index
	 * offset + 2  (8 bit): I2C slave address
	 * offset + 3  (8 bit): count
	 * offset + 4  (8 bit): I2C register 1
	 * offset + 5  (8 bit): data 1
	 * ...
	 *
	 * For each of "count" registers given by "I2C register n" on the device
	 * addressed by "I2C slave address" on the I2C bus given by
	 * "DCB I2C table entry index", set the register to "data n"
	 */

	uint8_t i2c_index = bios->data[offset + 1];
	uint8_t i2c_address = bios->data[offset + 2] >> 1;
	uint8_t count = bios->data[offset + 3];
	struct nouveau_i2c_chan *chan;
	int len = 4 + count * 2;
	int ret, i;

	if (!iexec->execute)
		return len;

	BIOSLOG(bios, "0x%04X: DCBI2CIndex: 0x%02X, I2CAddress: 0x%02X, "
		      "Count: 0x%02X\n",
		offset, i2c_index, i2c_address, count);

	chan = init_i2c_device_find(bios->dev, i2c_index);
	if (!chan)
		return -ENODEV;

	for (i = 0; i < count; i++) {
		uint8_t reg = bios->data[offset + 4 + i * 2];
		union i2c_smbus_data val;

		val.byte = bios->data[offset + 5 + i * 2];

		BIOSLOG(bios, "0x%04X: I2CReg: 0x%02X, Data: 0x%02X\n",
			offset, reg, val.byte);

		if (!bios->execute)
			continue;

		ret = i2c_smbus_xfer(&chan->adapter, i2c_address, 0,
				     I2C_SMBUS_WRITE, reg,
				     I2C_SMBUS_BYTE_DATA, &val);
		if (ret < 0)
			return ret;
	}

	return len;
}

static int
init_zm_i2c(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_ZM_I2C   opcode: 0x4E ('N')
	 *
	 * offset      (8 bit): opcode
	 * offset + 1  (8 bit): DCB I2C table entry index
	 * offset + 2  (8 bit): I2C slave address
	 * offset + 3  (8 bit): count
	 * offset + 4  (8 bit): data 1
	 * ...
	 *
	 * Send "count" bytes ("data n") to the device addressed by "I2C slave
	 * address" on the I2C bus given by "DCB I2C table entry index"
	 */

	uint8_t i2c_index = bios->data[offset + 1];
	uint8_t i2c_address = bios->data[offset + 2] >> 1;
	uint8_t count = bios->data[offset + 3];
	int len = 4 + count;
	struct nouveau_i2c_chan *chan;
	struct i2c_msg msg;
	uint8_t data[256];
	int i;

	if (!iexec->execute)
		return len;

	BIOSLOG(bios, "0x%04X: DCBI2CIndex: 0x%02X, I2CAddress: 0x%02X, "
		      "Count: 0x%02X\n",
		offset, i2c_index, i2c_address, count);

	chan = init_i2c_device_find(bios->dev, i2c_index);
	if (!chan)
		return -ENODEV;

	for (i = 0; i < count; i++) {
		data[i] = bios->data[offset + 4 + i];

		BIOSLOG(bios, "0x%04X: Data: 0x%02X\n", offset, data[i]);
	}

	if (bios->execute) {
		msg.addr = i2c_address;
		msg.flags = 0;
		msg.len = count;
		msg.buf = data;
		if (i2c_transfer(&chan->adapter, &msg, 1) != 1)
			return -EIO;
	}

	return len;
}

static int
init_tmds(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_TMDS   opcode: 0x4F ('O')	(non-canon name)
	 *
	 * offset      (8 bit): opcode
	 * offset + 1  (8 bit): magic lookup value
	 * offset + 2  (8 bit): TMDS address
	 * offset + 3  (8 bit): mask
	 * offset + 4  (8 bit): data
	 *
	 * Read the data reg for TMDS address "TMDS address", AND it with mask
	 * and OR it with data, then write it back
	 * "magic lookup value" determines which TMDS base address register is
	 * used -- see get_tmds_index_reg()
	 */

	uint8_t mlv = bios->data[offset + 1];
	uint32_t tmdsaddr = bios->data[offset + 2];
	uint8_t mask = bios->data[offset + 3];
	uint8_t data = bios->data[offset + 4];
	uint32_t reg, value;

	if (!iexec->execute)
		return 5;

	BIOSLOG(bios, "0x%04X: MagicLookupValue: 0x%02X, TMDSAddr: 0x%02X, "
		      "Mask: 0x%02X, Data: 0x%02X\n",
		offset, mlv, tmdsaddr, mask, data);

	reg = get_tmds_index_reg(bios->dev, mlv);
	if (!reg)
		return -EINVAL;

	bios_wr32(bios, reg,
		  tmdsaddr | NV_PRAMDAC_FP_TMDS_CONTROL_WRITE_DISABLE);
	value = (bios_rd32(bios, reg + 4) & mask) | data;
	bios_wr32(bios, reg + 4, value);
	bios_wr32(bios, reg, tmdsaddr);

	return 5;
}

static int
init_zm_tmds_group(struct nvbios *bios, uint16_t offset,
		   struct init_exec *iexec)
{
	/*
	 * INIT_ZM_TMDS_GROUP   opcode: 0x50 ('P')	(non-canon name)
	 *
	 * offset      (8 bit): opcode
	 * offset + 1  (8 bit): magic lookup value
	 * offset + 2  (8 bit): count
	 * offset + 3  (8 bit): addr 1
	 * offset + 4  (8 bit): data 1
	 * ...
	 *
	 * For each of "count" TMDS address and data pairs write "data n" to
	 * "addr n".  "magic lookup value" determines which TMDS base address
	 * register is used -- see get_tmds_index_reg()
	 */

	uint8_t mlv = bios->data[offset + 1];
	uint8_t count = bios->data[offset + 2];
	int len = 3 + count * 2;
	uint32_t reg;
	int i;

	if (!iexec->execute)
		return len;

	BIOSLOG(bios, "0x%04X: MagicLookupValue: 0x%02X, Count: 0x%02X\n",
		offset, mlv, count);

	reg = get_tmds_index_reg(bios->dev, mlv);
	if (!reg)
		return -EINVAL;

	for (i = 0; i < count; i++) {
		uint8_t tmdsaddr = bios->data[offset + 3 + i * 2];
		uint8_t tmdsdata = bios->data[offset + 4 + i * 2];

		bios_wr32(bios, reg + 4, tmdsdata);
		bios_wr32(bios, reg, tmdsaddr);
	}

	return len;
}

static int
init_cr_idx_adr_latch(struct nvbios *bios, uint16_t offset,
		      struct init_exec *iexec)
{
	/*
	 * INIT_CR_INDEX_ADDRESS_LATCHED   opcode: 0x51 ('Q')
	 *
	 * offset      (8 bit): opcode
	 * offset + 1  (8 bit): CRTC index1
	 * offset + 2  (8 bit): CRTC index2
	 * offset + 3  (8 bit): baseaddr
	 * offset + 4  (8 bit): count
	 * offset + 5  (8 bit): data 1
	 * ...
	 *
	 * For each of "count" address and data pairs, write "baseaddr + n" to
	 * "CRTC index1" and "data n" to "CRTC index2"
	 * Once complete, restore initial value read from "CRTC index1"
	 */
	uint8_t crtcindex1 = bios->data[offset + 1];
	uint8_t crtcindex2 = bios->data[offset + 2];
	uint8_t baseaddr = bios->data[offset + 3];
	uint8_t count = bios->data[offset + 4];
	int len = 5 + count;
	uint8_t oldaddr, data;
	int i;

	if (!iexec->execute)
		return len;

	BIOSLOG(bios, "0x%04X: Index1: 0x%02X, Index2: 0x%02X, "
		      "BaseAddr: 0x%02X, Count: 0x%02X\n",
		offset, crtcindex1, crtcindex2, baseaddr, count);

	oldaddr = bios_idxprt_rd(bios, NV_CIO_CRX__COLOR, crtcindex1);

	for (i = 0; i < count; i++) {
		bios_idxprt_wr(bios, NV_CIO_CRX__COLOR, crtcindex1,
				     baseaddr + i);
		data = bios->data[offset + 5 + i];
		bios_idxprt_wr(bios, NV_CIO_CRX__COLOR, crtcindex2, data);
	}

	bios_idxprt_wr(bios, NV_CIO_CRX__COLOR, crtcindex1, oldaddr);

	return len;
}

static int
init_cr(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_CR   opcode: 0x52 ('R')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (8  bit): CRTC index
	 * offset + 2  (8  bit): mask
	 * offset + 3  (8  bit): data
	 *
	 * Assign the value of at "CRTC index" ANDed with mask and ORed with
	 * data back to "CRTC index"
	 */

	uint8_t crtcindex = bios->data[offset + 1];
	uint8_t mask = bios->data[offset + 2];
	uint8_t data = bios->data[offset + 3];
	uint8_t value;

	if (!iexec->execute)
		return 4;

	BIOSLOG(bios, "0x%04X: Index: 0x%02X, Mask: 0x%02X, Data: 0x%02X\n",
		offset, crtcindex, mask, data);

	value  = bios_idxprt_rd(bios, NV_CIO_CRX__COLOR, crtcindex) & mask;
	value |= data;
	bios_idxprt_wr(bios, NV_CIO_CRX__COLOR, crtcindex, value);

	return 4;
}

static int
init_zm_cr(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_ZM_CR   opcode: 0x53 ('S')
	 *
	 * offset      (8 bit): opcode
	 * offset + 1  (8 bit): CRTC index
	 * offset + 2  (8 bit): value
	 *
	 * Assign "value" to CRTC register with index "CRTC index".
	 */

	uint8_t crtcindex = ROM32(bios->data[offset + 1]);
	uint8_t data = bios->data[offset + 2];

	if (!iexec->execute)
		return 3;

	bios_idxprt_wr(bios, NV_CIO_CRX__COLOR, crtcindex, data);

	return 3;
}

static int
init_zm_cr_group(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_ZM_CR_GROUP   opcode: 0x54 ('T')
	 *
	 * offset      (8 bit): opcode
	 * offset + 1  (8 bit): count
	 * offset + 2  (8 bit): CRTC index 1
	 * offset + 3  (8 bit): value 1
	 * ...
	 *
	 * For "count", assign "value n" to CRTC register with index
	 * "CRTC index n".
	 */

	uint8_t count = bios->data[offset + 1];
	int len = 2 + count * 2;
	int i;

	if (!iexec->execute)
		return len;

	for (i = 0; i < count; i++)
		init_zm_cr(bios, offset + 2 + 2 * i - 1, iexec);

	return len;
}

static int
init_condition_time(struct nvbios *bios, uint16_t offset,
		    struct init_exec *iexec)
{
	/*
	 * INIT_CONDITION_TIME   opcode: 0x56 ('V')
	 *
	 * offset      (8 bit): opcode
	 * offset + 1  (8 bit): condition number
	 * offset + 2  (8 bit): retries / 50
	 *
	 * Check condition "condition number" in the condition table.
	 * Bios code then sleeps for 2ms if the condition is not met, and
	 * repeats up to "retries" times, but on one C51 this has proved
	 * insufficient.  In mmiotraces the driver sleeps for 20ms, so we do
	 * this, and bail after "retries" times, or 2s, whichever is less.
	 * If still not met after retries, clear execution flag for this table.
	 */

	uint8_t cond = bios->data[offset + 1];
	uint16_t retries = bios->data[offset + 2] * 50;
	unsigned cnt;

	if (!iexec->execute)
		return 3;

	if (retries > 100)
		retries = 100;

	BIOSLOG(bios, "0x%04X: Condition: 0x%02X, Retries: 0x%02X\n",
		offset, cond, retries);

	if (!bios->execute) /* avoid 2s delays when "faking" execution */
		retries = 1;

	for (cnt = 0; cnt < retries; cnt++) {
		if (bios_condition_met(bios, offset, cond)) {
			BIOSLOG(bios, "0x%04X: Condition met, continuing\n",
								offset);
			break;
		} else {
			BIOSLOG(bios, "0x%04X: "
				"Condition not met, sleeping for 20ms\n",
								offset);
			msleep(20);
		}
	}

	if (!bios_condition_met(bios, offset, cond)) {
		NV_WARN(bios->dev,
			"0x%04X: Condition still not met after %dms, "
			"skipping following opcodes\n", offset, 20 * retries);
		iexec->execute = false;
	}

	return 3;
}

static int
init_zm_reg_sequence(struct nvbios *bios, uint16_t offset,
		     struct init_exec *iexec)
{
	/*
	 * INIT_ZM_REG_SEQUENCE   opcode: 0x58 ('X')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (32 bit): base register
	 * offset + 5  (8  bit): count
	 * offset + 6  (32 bit): value 1
	 * ...
	 *
	 * Starting at offset + 6 there are "count" 32 bit values.
	 * For "count" iterations set "base register" + 4 * current_iteration
	 * to "value current_iteration"
	 */

	uint32_t basereg = ROM32(bios->data[offset + 1]);
	uint32_t count = bios->data[offset + 5];
	int len = 6 + count * 4;
	int i;

	if (!iexec->execute)
		return len;

	BIOSLOG(bios, "0x%04X: BaseReg: 0x%08X, Count: 0x%02X\n",
		offset, basereg, count);

	for (i = 0; i < count; i++) {
		uint32_t reg = basereg + i * 4;
		uint32_t data = ROM32(bios->data[offset + 6 + i * 4]);

		bios_wr32(bios, reg, data);
	}

	return len;
}

static int
init_sub_direct(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_SUB_DIRECT   opcode: 0x5B ('[')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (16 bit): subroutine offset (in bios)
	 *
	 * Calls a subroutine that will execute commands until INIT_DONE
	 * is found.
	 */

	uint16_t sub_offset = ROM16(bios->data[offset + 1]);

	if (!iexec->execute)
		return 3;

	BIOSLOG(bios, "0x%04X: Executing subroutine at 0x%04X\n",
		offset, sub_offset);

	parse_init_table(bios, sub_offset, iexec);

	BIOSLOG(bios, "0x%04X: End of 0x%04X subroutine\n", offset, sub_offset);

	return 3;
}

static int
init_copy_nv_reg(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_COPY_NV_REG   opcode: 0x5F ('_')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (32 bit): src reg
	 * offset + 5  (8  bit): shift
	 * offset + 6  (32 bit): src mask
	 * offset + 10 (32 bit): xor
	 * offset + 14 (32 bit): dst reg
	 * offset + 18 (32 bit): dst mask
	 *
	 * Shift REGVAL("src reg") right by (signed) "shift", AND result with
	 * "src mask", then XOR with "xor". Write this OR'd with
	 * (REGVAL("dst reg") AND'd with "dst mask") to "dst reg"
	 */

	uint32_t srcreg = *((uint32_t *)(&bios->data[offset + 1]));
	uint8_t shift = bios->data[offset + 5];
	uint32_t srcmask = *((uint32_t *)(&bios->data[offset + 6]));
	uint32_t xor = *((uint32_t *)(&bios->data[offset + 10]));
	uint32_t dstreg = *((uint32_t *)(&bios->data[offset + 14]));
	uint32_t dstmask = *((uint32_t *)(&bios->data[offset + 18]));
	uint32_t srcvalue, dstvalue;

	if (!iexec->execute)
		return 22;

	BIOSLOG(bios, "0x%04X: SrcReg: 0x%08X, Shift: 0x%02X, SrcMask: 0x%08X, "
		      "Xor: 0x%08X, DstReg: 0x%08X, DstMask: 0x%08X\n",
		offset, srcreg, shift, srcmask, xor, dstreg, dstmask);

	srcvalue = bios_rd32(bios, srcreg);

	if (shift < 0x80)
		srcvalue >>= shift;
	else
		srcvalue <<= (0x100 - shift);

	srcvalue = (srcvalue & srcmask) ^ xor;

	dstvalue = bios_rd32(bios, dstreg) & dstmask;

	bios_wr32(bios, dstreg, dstvalue | srcvalue);

	return 22;
}

static int
init_zm_index_io(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_ZM_INDEX_IO   opcode: 0x62 ('b')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (16 bit): CRTC port
	 * offset + 3  (8  bit): CRTC index
	 * offset + 4  (8  bit): data
	 *
	 * Write "data" to index "CRTC index" of "CRTC port"
	 */
	uint16_t crtcport = ROM16(bios->data[offset + 1]);
	uint8_t crtcindex = bios->data[offset + 3];
	uint8_t data = bios->data[offset + 4];

	if (!iexec->execute)
		return 5;

	bios_idxprt_wr(bios, crtcport, crtcindex, data);

	return 5;
}

static int
init_compute_mem(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_COMPUTE_MEM   opcode: 0x63 ('c')
	 *
	 * offset      (8 bit): opcode
	 *
	 * This opcode is meant to set NV_PFB_CFG0 (0x100200) appropriately so
	 * that the hardware can correctly calculate how much VRAM it has
	 * (and subsequently report that value in NV_PFB_CSTATUS (0x10020C))
	 *
	 * The implementation of this opcode in general consists of two parts:
	 * 1) determination of the memory bus width
	 * 2) determination of how many of the card's RAM pads have ICs attached
	 *
	 * 1) is done by a cunning combination of writes to offsets 0x1c and
	 * 0x3c in the framebuffer, and seeing whether the written values are
	 * read back correctly. This then affects bits 4-7 of NV_PFB_CFG0
	 *
	 * 2) is done by a cunning combination of writes to an offset slightly
	 * less than the maximum memory reported by NV_PFB_CSTATUS, then seeing
	 * if the test pattern can be read back. This then affects bits 12-15 of
	 * NV_PFB_CFG0
	 *
	 * In this context a "cunning combination" may include multiple reads
	 * and writes to varying locations, often alternating the test pattern
	 * and 0, doubtless to make sure buffers are filled, residual charges
	 * on tracks are removed etc.
	 *
	 * Unfortunately, the "cunning combination"s mentioned above, and the
	 * changes to the bits in NV_PFB_CFG0 differ with nearly every bios
	 * trace I have.
	 *
	 * Therefore, we cheat and assume the value of NV_PFB_CFG0 with which
	 * we started was correct, and use that instead
	 */

	/* no iexec->execute check by design */

	/*
	 * This appears to be a NOP on G8x chipsets, both io logs of the VBIOS
	 * and kmmio traces of the binary driver POSTing the card show nothing
	 * being done for this opcode.  why is it still listed in the table?!
	 */

	struct drm_nouveau_private *dev_priv = bios->dev->dev_private;

	if (dev_priv->card_type >= NV_40)
		return 1;

	/*
	 * On every card I've seen, this step gets done for us earlier in
	 * the init scripts
	uint8_t crdata = bios_idxprt_rd(dev, NV_VIO_SRX, 0x01);
	bios_idxprt_wr(dev, NV_VIO_SRX, 0x01, crdata | 0x20);
	 */

	/*
	 * This also has probably been done in the scripts, but an mmio trace of
	 * s3 resume shows nvidia doing it anyway (unlike the NV_VIO_SRX write)
	 */
	bios_wr32(bios, NV_PFB_REFCTRL, NV_PFB_REFCTRL_VALID_1);

	/* write back the saved configuration value */
	bios_wr32(bios, NV_PFB_CFG0, bios->state.saved_nv_pfb_cfg0);

	return 1;
}

static int
init_reset(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_RESET   opcode: 0x65 ('e')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (32 bit): register
	 * offset + 5  (32 bit): value1
	 * offset + 9  (32 bit): value2
	 *
	 * Assign "value1" to "register", then assign "value2" to "register"
	 */

	uint32_t reg = ROM32(bios->data[offset + 1]);
	uint32_t value1 = ROM32(bios->data[offset + 5]);
	uint32_t value2 = ROM32(bios->data[offset + 9]);
	uint32_t pci_nv_19, pci_nv_20;

	/* no iexec->execute check by design */

	pci_nv_19 = bios_rd32(bios, NV_PBUS_PCI_NV_19);
	bios_wr32(bios, NV_PBUS_PCI_NV_19, 0);
	bios_wr32(bios, reg, value1);

	udelay(10);

	bios_wr32(bios, reg, value2);
	bios_wr32(bios, NV_PBUS_PCI_NV_19, pci_nv_19);

	pci_nv_20 = bios_rd32(bios, NV_PBUS_PCI_NV_20);
	pci_nv_20 &= ~NV_PBUS_PCI_NV_20_ROM_SHADOW_ENABLED;	/* 0xfffffffe */
	bios_wr32(bios, NV_PBUS_PCI_NV_20, pci_nv_20);

	return 13;
}

static int
init_configure_mem(struct nvbios *bios, uint16_t offset,
		   struct init_exec *iexec)
{
	/*
	 * INIT_CONFIGURE_MEM   opcode: 0x66 ('f')
	 *
	 * offset      (8 bit): opcode
	 *
	 * Equivalent to INIT_DONE on bios version 3 or greater.
	 * For early bios versions, sets up the memory registers, using values
	 * taken from the memory init table
	 */

	/* no iexec->execute check by design */

	uint16_t meminitoffs = bios->legacy.mem_init_tbl_ptr + MEM_INIT_SIZE * (bios_idxprt_rd(bios, NV_CIO_CRX__COLOR, NV_CIO_CRE_SCRATCH4__INDEX) >> 4);
	uint16_t seqtbloffs = bios->legacy.sdr_seq_tbl_ptr, meminitdata = meminitoffs + 6;
	uint32_t reg, data;

	if (bios->major_version > 2)
		return -ENODEV;

	bios_idxprt_wr(bios, NV_VIO_SRX, NV_VIO_SR_CLOCK_INDEX, bios_idxprt_rd(
		       bios, NV_VIO_SRX, NV_VIO_SR_CLOCK_INDEX) | 0x20);

	if (bios->data[meminitoffs] & 1)
		seqtbloffs = bios->legacy.ddr_seq_tbl_ptr;

	for (reg = ROM32(bios->data[seqtbloffs]);
	     reg != 0xffffffff;
	     reg = ROM32(bios->data[seqtbloffs += 4])) {

		switch (reg) {
		case NV_PFB_PRE:
			data = NV_PFB_PRE_CMD_PRECHARGE;
			break;
		case NV_PFB_PAD:
			data = NV_PFB_PAD_CKE_NORMAL;
			break;
		case NV_PFB_REF:
			data = NV_PFB_REF_CMD_REFRESH;
			break;
		default:
			data = ROM32(bios->data[meminitdata]);
			meminitdata += 4;
			if (data == 0xffffffff)
				continue;
		}

		bios_wr32(bios, reg, data);
	}

	return 1;
}

static int
init_configure_clk(struct nvbios *bios, uint16_t offset,
		   struct init_exec *iexec)
{
	/*
	 * INIT_CONFIGURE_CLK   opcode: 0x67 ('g')
	 *
	 * offset      (8 bit): opcode
	 *
	 * Equivalent to INIT_DONE on bios version 3 or greater.
	 * For early bios versions, sets up the NVClk and MClk PLLs, using
	 * values taken from the memory init table
	 */

	/* no iexec->execute check by design */

	uint16_t meminitoffs = bios->legacy.mem_init_tbl_ptr + MEM_INIT_SIZE * (bios_idxprt_rd(bios, NV_CIO_CRX__COLOR, NV_CIO_CRE_SCRATCH4__INDEX) >> 4);
	int clock;

	if (bios->major_version > 2)
		return -ENODEV;

	clock = ROM16(bios->data[meminitoffs + 4]) * 10;
	setPLL(bios, NV_PRAMDAC_NVPLL_COEFF, clock);

	clock = ROM16(bios->data[meminitoffs + 2]) * 10;
	if (bios->data[meminitoffs] & 1) /* DDR */
		clock *= 2;
	setPLL(bios, NV_PRAMDAC_MPLL_COEFF, clock);

	return 1;
}

static int
init_configure_preinit(struct nvbios *bios, uint16_t offset,
		       struct init_exec *iexec)
{
	/*
	 * INIT_CONFIGURE_PREINIT   opcode: 0x68 ('h')
	 *
	 * offset      (8 bit): opcode
	 *
	 * Equivalent to INIT_DONE on bios version 3 or greater.
	 * For early bios versions, does early init, loading ram and crystal
	 * configuration from straps into CR3C
	 */

	/* no iexec->execute check by design */

	uint32_t straps = bios_rd32(bios, NV_PEXTDEV_BOOT_0);
	uint8_t cr3c = ((straps << 2) & 0xf0) | (straps & (1 << 6));

	if (bios->major_version > 2)
		return -ENODEV;

	bios_idxprt_wr(bios, NV_CIO_CRX__COLOR,
			     NV_CIO_CRE_SCRATCH4__INDEX, cr3c);

	return 1;
}

static int
init_io(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_IO   opcode: 0x69 ('i')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (16 bit): CRTC port
	 * offset + 3  (8  bit): mask
	 * offset + 4  (8  bit): data
	 *
	 * Assign ((IOVAL("crtc port") & "mask") | "data") to "crtc port"
	 */

	struct drm_nouveau_private *dev_priv = bios->dev->dev_private;
	uint16_t crtcport = ROM16(bios->data[offset + 1]);
	uint8_t mask = bios->data[offset + 3];
	uint8_t data = bios->data[offset + 4];

	if (!iexec->execute)
		return 5;

	BIOSLOG(bios, "0x%04X: Port: 0x%04X, Mask: 0x%02X, Data: 0x%02X\n",
		offset, crtcport, mask, data);

	/*
	 * I have no idea what this does, but NVIDIA do this magic sequence
	 * in the places where this INIT_IO happens..
	 */
	if (dev_priv->card_type >= NV_50 && crtcport == 0x3c3 && data == 1) {
		int i;

		bios_wr32(bios, 0x614100, (bios_rd32(
			  bios, 0x614100) & 0x0fffffff) | 0x00800000);

		bios_wr32(bios, 0x00e18c, bios_rd32(
			  bios, 0x00e18c) | 0x00020000);

		bios_wr32(bios, 0x614900, (bios_rd32(
			  bios, 0x614900) & 0x0fffffff) | 0x00800000);

		bios_wr32(bios, 0x000200, bios_rd32(
			  bios, 0x000200) & ~0x40000000);

		mdelay(10);

		bios_wr32(bios, 0x00e18c, bios_rd32(
			  bios, 0x00e18c) & ~0x00020000);

		bios_wr32(bios, 0x000200, bios_rd32(
			  bios, 0x000200) | 0x40000000);

		bios_wr32(bios, 0x614100, 0x00800018);
		bios_wr32(bios, 0x614900, 0x00800018);

		mdelay(10);

		bios_wr32(bios, 0x614100, 0x10000018);
		bios_wr32(bios, 0x614900, 0x10000018);

		for (i = 0; i < 3; i++)
			bios_wr32(bios, 0x614280 + (i*0x800), bios_rd32(
				  bios, 0x614280 + (i*0x800)) & 0xf0f0f0f0);

		for (i = 0; i < 2; i++)
			bios_wr32(bios, 0x614300 + (i*0x800), bios_rd32(
				  bios, 0x614300 + (i*0x800)) & 0xfffff0f0);

		for (i = 0; i < 3; i++)
			bios_wr32(bios, 0x614380 + (i*0x800), bios_rd32(
				  bios, 0x614380 + (i*0x800)) & 0xfffff0f0);

		for (i = 0; i < 2; i++)
			bios_wr32(bios, 0x614200 + (i*0x800), bios_rd32(
				  bios, 0x614200 + (i*0x800)) & 0xfffffff0);

		for (i = 0; i < 2; i++)
			bios_wr32(bios, 0x614108 + (i*0x800), bios_rd32(
				  bios, 0x614108 + (i*0x800)) & 0x0fffffff);
		return 5;
	}

	bios_port_wr(bios, crtcport, (bios_port_rd(bios, crtcport) & mask) |
									data);
	return 5;
}

static int
init_sub(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_SUB   opcode: 0x6B ('k')
	 *
	 * offset      (8 bit): opcode
	 * offset + 1  (8 bit): script number
	 *
	 * Execute script number "script number", as a subroutine
	 */

	uint8_t sub = bios->data[offset + 1];

	if (!iexec->execute)
		return 2;

	BIOSLOG(bios, "0x%04X: Calling script %d\n", offset, sub);

	parse_init_table(bios,
			 ROM16(bios->data[bios->init_script_tbls_ptr + sub * 2]),
			 iexec);

	BIOSLOG(bios, "0x%04X: End of script %d\n", offset, sub);

	return 2;
}

static int
init_ram_condition(struct nvbios *bios, uint16_t offset,
		   struct init_exec *iexec)
{
	/*
	 * INIT_RAM_CONDITION   opcode: 0x6D ('m')
	 *
	 * offset      (8 bit): opcode
	 * offset + 1  (8 bit): mask
	 * offset + 2  (8 bit): cmpval
	 *
	 * Test if (NV_PFB_BOOT_0 & "mask") equals "cmpval".
	 * If condition not met skip subsequent opcodes until condition is
	 * inverted (INIT_NOT), or we hit INIT_RESUME
	 */

	uint8_t mask = bios->data[offset + 1];
	uint8_t cmpval = bios->data[offset + 2];
	uint8_t data;

	if (!iexec->execute)
		return 3;

	data = bios_rd32(bios, NV_PFB_BOOT_0) & mask;

	BIOSLOG(bios, "0x%04X: Checking if 0x%08X equals 0x%08X\n",
		offset, data, cmpval);

	if (data == cmpval)
		BIOSLOG(bios, "0x%04X: Condition fulfilled -- continuing to execute\n", offset);
	else {
		BIOSLOG(bios, "0x%04X: Condition not fulfilled -- skipping following commands\n", offset);
		iexec->execute = false;
	}

	return 3;
}

static int
init_nv_reg(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_NV_REG   opcode: 0x6E ('n')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (32 bit): register
	 * offset + 5  (32 bit): mask
	 * offset + 9  (32 bit): data
	 *
	 * Assign ((REGVAL("register") & "mask") | "data") to "register"
	 */

	uint32_t reg = ROM32(bios->data[offset + 1]);
	uint32_t mask = ROM32(bios->data[offset + 5]);
	uint32_t data = ROM32(bios->data[offset + 9]);

	if (!iexec->execute)
		return 13;

	BIOSLOG(bios, "0x%04X: Reg: 0x%08X, Mask: 0x%08X, Data: 0x%08X\n",
		offset, reg, mask, data);

	bios_wr32(bios, reg, (bios_rd32(bios, reg) & mask) | data);

	return 13;
}

static int
init_macro(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_MACRO   opcode: 0x6F ('o')
	 *
	 * offset      (8 bit): opcode
	 * offset + 1  (8 bit): macro number
	 *
	 * Look up macro index "macro number" in the macro index table.
	 * The macro index table entry has 1 byte for the index in the macro
	 * table, and 1 byte for the number of times to repeat the macro.
	 * The macro table entry has 4 bytes for the register address and
	 * 4 bytes for the value to write to that register
	 */

	uint8_t macro_index_tbl_idx = bios->data[offset + 1];
	uint16_t tmp = bios->macro_index_tbl_ptr + (macro_index_tbl_idx * MACRO_INDEX_SIZE);
	uint8_t macro_tbl_idx = bios->data[tmp];
	uint8_t count = bios->data[tmp + 1];
	uint32_t reg, data;
	int i;

	if (!iexec->execute)
		return 2;

	BIOSLOG(bios, "0x%04X: Macro: 0x%02X, MacroTableIndex: 0x%02X, "
		      "Count: 0x%02X\n",
		offset, macro_index_tbl_idx, macro_tbl_idx, count);

	for (i = 0; i < count; i++) {
		uint16_t macroentryptr = bios->macro_tbl_ptr + (macro_tbl_idx + i) * MACRO_SIZE;

		reg = ROM32(bios->data[macroentryptr]);
		data = ROM32(bios->data[macroentryptr + 4]);

		bios_wr32(bios, reg, data);
	}

	return 2;
}

static int
init_done(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_DONE   opcode: 0x71 ('q')
	 *
	 * offset      (8  bit): opcode
	 *
	 * End the current script
	 */

	/* mild retval abuse to stop parsing this table */
	return 0;
}

static int
init_resume(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_RESUME   opcode: 0x72 ('r')
	 *
	 * offset      (8  bit): opcode
	 *
	 * End the current execute / no-execute condition
	 */

	if (iexec->execute)
		return 1;

	iexec->execute = true;
	BIOSLOG(bios, "0x%04X: ---- Executing following commands ----\n", offset);

	return 1;
}

static int
init_time(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_TIME   opcode: 0x74 ('t')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (16 bit): time
	 *
	 * Sleep for "time" microseconds.
	 */

	unsigned time = ROM16(bios->data[offset + 1]);

	if (!iexec->execute)
		return 3;

	BIOSLOG(bios, "0x%04X: Sleeping for 0x%04X microseconds\n",
		offset, time);

	if (time < 1000)
		udelay(time);
	else
		msleep((time + 900) / 1000);

	return 3;
}

static int
init_condition(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_CONDITION   opcode: 0x75 ('u')
	 *
	 * offset      (8 bit): opcode
	 * offset + 1  (8 bit): condition number
	 *
	 * Check condition "condition number" in the condition table.
	 * If condition not met skip subsequent opcodes until condition is
	 * inverted (INIT_NOT), or we hit INIT_RESUME
	 */

	uint8_t cond = bios->data[offset + 1];

	if (!iexec->execute)
		return 2;

	BIOSLOG(bios, "0x%04X: Condition: 0x%02X\n", offset, cond);

	if (bios_condition_met(bios, offset, cond))
		BIOSLOG(bios, "0x%04X: Condition fulfilled -- continuing to execute\n", offset);
	else {
		BIOSLOG(bios, "0x%04X: Condition not fulfilled -- skipping following commands\n", offset);
		iexec->execute = false;
	}

	return 2;
}

static int
init_io_condition(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_IO_CONDITION  opcode: 0x76
	 *
	 * offset      (8 bit): opcode
	 * offset + 1  (8 bit): condition number
	 *
	 * Check condition "condition number" in the io condition table.
	 * If condition not met skip subsequent opcodes until condition is
	 * inverted (INIT_NOT), or we hit INIT_RESUME
	 */

	uint8_t cond = bios->data[offset + 1];

	if (!iexec->execute)
		return 2;

	BIOSLOG(bios, "0x%04X: IO condition: 0x%02X\n", offset, cond);

	if (io_condition_met(bios, offset, cond))
		BIOSLOG(bios, "0x%04X: Condition fulfilled -- continuing to execute\n", offset);
	else {
		BIOSLOG(bios, "0x%04X: Condition not fulfilled -- skipping following commands\n", offset);
		iexec->execute = false;
	}

	return 2;
}

static int
init_index_io(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_INDEX_IO   opcode: 0x78 ('x')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (16 bit): CRTC port
	 * offset + 3  (8  bit): CRTC index
	 * offset + 4  (8  bit): mask
	 * offset + 5  (8  bit): data
	 *
	 * Read value at index "CRTC index" on "CRTC port", AND with "mask",
	 * OR with "data", write-back
	 */

	uint16_t crtcport = ROM16(bios->data[offset + 1]);
	uint8_t crtcindex = bios->data[offset + 3];
	uint8_t mask = bios->data[offset + 4];
	uint8_t data = bios->data[offset + 5];
	uint8_t value;

	if (!iexec->execute)
		return 6;

	BIOSLOG(bios, "0x%04X: Port: 0x%04X, Index: 0x%02X, Mask: 0x%02X, "
		      "Data: 0x%02X\n",
		offset, crtcport, crtcindex, mask, data);

	value = (bios_idxprt_rd(bios, crtcport, crtcindex) & mask) | data;
	bios_idxprt_wr(bios, crtcport, crtcindex, value);

	return 6;
}

static int
init_pll(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_PLL   opcode: 0x79 ('y')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (32 bit): register
	 * offset + 5  (16 bit): freq
	 *
	 * Set PLL register "register" to coefficients for frequency (10kHz)
	 * "freq"
	 */

	uint32_t reg = ROM32(bios->data[offset + 1]);
	uint16_t freq = ROM16(bios->data[offset + 5]);

	if (!iexec->execute)
		return 7;

	BIOSLOG(bios, "0x%04X: Reg: 0x%08X, Freq: %d0kHz\n", offset, reg, freq);

	setPLL(bios, reg, freq * 10);

	return 7;
}

static int
init_zm_reg(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_ZM_REG   opcode: 0x7A ('z')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (32 bit): register
	 * offset + 5  (32 bit): value
	 *
	 * Assign "value" to "register"
	 */

	uint32_t reg = ROM32(bios->data[offset + 1]);
	uint32_t value = ROM32(bios->data[offset + 5]);

	if (!iexec->execute)
		return 9;

	if (reg == 0x000200)
		value |= 1;

	bios_wr32(bios, reg, value);

	return 9;
}

static int
init_ram_restrict_pll(struct nvbios *bios, uint16_t offset,
		      struct init_exec *iexec)
{
	/*
	 * INIT_RAM_RESTRICT_PLL   opcode: 0x87 ('')
	 *
	 * offset      (8 bit): opcode
	 * offset + 1  (8 bit): PLL type
	 * offset + 2 (32 bit): frequency 0
	 *
	 * Uses the RAMCFG strap of PEXTDEV_BOOT as an index into the table at
	 * ram_restrict_table_ptr.  The value read from there is used to select
	 * a frequency from the table starting at 'frequency 0' to be
	 * programmed into the PLL corresponding to 'type'.
	 *
	 * The PLL limits table on cards using this opcode has a mapping of
	 * 'type' to the relevant registers.
	 */

	struct drm_device *dev = bios->dev;
	uint32_t strap = (bios_rd32(bios, NV_PEXTDEV_BOOT_0) & 0x0000003c) >> 2;
	uint8_t index = bios->data[bios->ram_restrict_tbl_ptr + strap];
	uint8_t type = bios->data[offset + 1];
	uint32_t freq = ROM32(bios->data[offset + 2 + (index * 4)]);
	uint8_t *pll_limits = &bios->data[bios->pll_limit_tbl_ptr], *entry;
	int len = 2 + bios->ram_restrict_group_count * 4;
	int i;

	if (!iexec->execute)
		return len;

	if (!bios->pll_limit_tbl_ptr || (pll_limits[0] & 0xf0) != 0x30) {
		NV_ERROR(dev, "PLL limits table not version 3.x\n");
		return len; /* deliberate, allow default clocks to remain */
	}

	entry = pll_limits + pll_limits[1];
	for (i = 0; i < pll_limits[3]; i++, entry += pll_limits[2]) {
		if (entry[0] == type) {
			uint32_t reg = ROM32(entry[3]);

			BIOSLOG(bios, "0x%04X: "
				      "Type %02x Reg 0x%08x Freq %dKHz\n",
				offset, type, reg, freq);

			setPLL(bios, reg, freq);
			return len;
		}
	}

	NV_ERROR(dev, "PLL type 0x%02x not found in PLL limits table", type);
	return len;
}

static int
init_8c(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_8C   opcode: 0x8C ('')
	 *
	 * NOP so far....
	 *
	 */

	return 1;
}

static int
init_8d(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_8D   opcode: 0x8D ('')
	 *
	 * NOP so far....
	 *
	 */

	return 1;
}

static int
init_gpio(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_GPIO   opcode: 0x8E ('')
	 *
	 * offset      (8 bit): opcode
	 *
	 * Loop over all entries in the DCB GPIO table, and initialise
	 * each GPIO according to various values listed in each entry
	 */

	struct drm_nouveau_private *dev_priv = bios->dev->dev_private;
	const uint32_t nv50_gpio_ctl[2] = { 0xe100, 0xe28c };
	int i;

	if (dev_priv->card_type != NV_50) {
		NV_ERROR(bios->dev, "INIT_GPIO on unsupported chipset\n");
		return -ENODEV;
	}

	if (!iexec->execute)
		return 1;

	for (i = 0; i < bios->dcb.gpio.entries; i++) {
		struct dcb_gpio_entry *gpio = &bios->dcb.gpio.entry[i];
		uint32_t r, s, v;

		BIOSLOG(bios, "0x%04X: Entry: 0x%08X\n", offset, gpio->entry);

		nv50_gpio_set(bios->dev, gpio->tag, gpio->state_default);

		/* The NVIDIA binary driver doesn't appear to actually do
		 * any of this, my VBIOS does however.
		 */
		/* Not a clue, needs de-magicing */
		r = nv50_gpio_ctl[gpio->line >> 4];
		s = (gpio->line & 0x0f);
		v = bios_rd32(bios, r) & ~(0x00010001 << s);
		switch ((gpio->entry & 0x06000000) >> 25) {
		case 1:
			v |= (0x00000001 << s);
			break;
		case 2:
			v |= (0x00010000 << s);
			break;
		default:
			break;
		}
		bios_wr32(bios, r, v);
	}

	return 1;
}

static int
init_ram_restrict_zm_reg_group(struct nvbios *bios, uint16_t offset,
			       struct init_exec *iexec)
{
	/*
	 * INIT_RAM_RESTRICT_ZM_REG_GROUP   opcode: 0x8F ('')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (32 bit): reg
	 * offset + 5  (8  bit): regincrement
	 * offset + 6  (8  bit): count
	 * offset + 7  (32 bit): value 1,1
	 * ...
	 *
	 * Use the RAMCFG strap of PEXTDEV_BOOT as an index into the table at
	 * ram_restrict_table_ptr. The value read from here is 'n', and
	 * "value 1,n" gets written to "reg". This repeats "count" times and on
	 * each iteration 'm', "reg" increases by "regincrement" and
	 * "value m,n" is used. The extent of n is limited by a number read
	 * from the 'M' BIT table, herein called "blocklen"
	 */

	uint32_t reg = ROM32(bios->data[offset + 1]);
	uint8_t regincrement = bios->data[offset + 5];
	uint8_t count = bios->data[offset + 6];
	uint32_t strap_ramcfg, data;
	/* previously set by 'M' BIT table */
	uint16_t blocklen = bios->ram_restrict_group_count * 4;
	int len = 7 + count * blocklen;
	uint8_t index;
	int i;


	if (!iexec->execute)
		return len;

	if (!blocklen) {
		NV_ERROR(bios->dev,
			 "0x%04X: Zero block length - has the M table "
			 "been parsed?\n", offset);
		return -EINVAL;
	}

	strap_ramcfg = (bios_rd32(bios, NV_PEXTDEV_BOOT_0) >> 2) & 0xf;
	index = bios->data[bios->ram_restrict_tbl_ptr + strap_ramcfg];

	BIOSLOG(bios, "0x%04X: Reg: 0x%08X, RegIncrement: 0x%02X, "
		      "Count: 0x%02X, StrapRamCfg: 0x%02X, Index: 0x%02X\n",
		offset, reg, regincrement, count, strap_ramcfg, index);

	for (i = 0; i < count; i++) {
		data = ROM32(bios->data[offset + 7 + index * 4 + blocklen * i]);

		bios_wr32(bios, reg, data);

		reg += regincrement;
	}

	return len;
}

static int
init_copy_zm_reg(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_COPY_ZM_REG   opcode: 0x90 ('')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (32 bit): src reg
	 * offset + 5  (32 bit): dst reg
	 *
	 * Put contents of "src reg" into "dst reg"
	 */

	uint32_t srcreg = ROM32(bios->data[offset + 1]);
	uint32_t dstreg = ROM32(bios->data[offset + 5]);

	if (!iexec->execute)
		return 9;

	bios_wr32(bios, dstreg, bios_rd32(bios, srcreg));

	return 9;
}

static int
init_zm_reg_group_addr_latched(struct nvbios *bios, uint16_t offset,
			       struct init_exec *iexec)
{
	/*
	 * INIT_ZM_REG_GROUP_ADDRESS_LATCHED   opcode: 0x91 ('')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (32 bit): dst reg
	 * offset + 5  (8  bit): count
	 * offset + 6  (32 bit): data 1
	 * ...
	 *
	 * For each of "count" values write "data n" to "dst reg"
	 */

	uint32_t reg = ROM32(bios->data[offset + 1]);
	uint8_t count = bios->data[offset + 5];
	int len = 6 + count * 4;
	int i;

	if (!iexec->execute)
		return len;

	for (i = 0; i < count; i++) {
		uint32_t data = ROM32(bios->data[offset + 6 + 4 * i]);
		bios_wr32(bios, reg, data);
	}

	return len;
}

static int
init_reserved(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_RESERVED   opcode: 0x92 ('')
	 *
	 * offset      (8 bit): opcode
	 *
	 * Seemingly does nothing
	 */

	return 1;
}

static int
init_96(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_96   opcode: 0x96 ('')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (32 bit): sreg
	 * offset + 5  (8  bit): sshift
	 * offset + 6  (8  bit): smask
	 * offset + 7  (8  bit): index
	 * offset + 8  (32 bit): reg
	 * offset + 12 (32 bit): mask
	 * offset + 16 (8  bit): shift
	 *
	 */

	uint16_t xlatptr = bios->init96_tbl_ptr + (bios->data[offset + 7] * 2);
	uint32_t reg = ROM32(bios->data[offset + 8]);
	uint32_t mask = ROM32(bios->data[offset + 12]);
	uint32_t val;

	val = bios_rd32(bios, ROM32(bios->data[offset + 1]));
	if (bios->data[offset + 5] < 0x80)
		val >>= bios->data[offset + 5];
	else
		val <<= (0x100 - bios->data[offset + 5]);
	val &= bios->data[offset + 6];

	val   = bios->data[ROM16(bios->data[xlatptr]) + val];
	val <<= bios->data[offset + 16];

	if (!iexec->execute)
		return 17;

	bios_wr32(bios, reg, (bios_rd32(bios, reg) & mask) | val);
	return 17;
}

static int
init_97(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_97   opcode: 0x97 ('')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (32 bit): register
	 * offset + 5  (32 bit): mask
	 * offset + 9  (32 bit): value
	 *
	 * Adds "value" to "register" preserving the fields specified
	 * by "mask"
	 */

	uint32_t reg = ROM32(bios->data[offset + 1]);
	uint32_t mask = ROM32(bios->data[offset + 5]);
	uint32_t add = ROM32(bios->data[offset + 9]);
	uint32_t val;

	val = bios_rd32(bios, reg);
	val = (val & mask) | ((val + add) & ~mask);

	if (!iexec->execute)
		return 13;

	bios_wr32(bios, reg, val);
	return 13;
}

static int
init_auxch(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_AUXCH   opcode: 0x98 ('')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (32 bit): address
	 * offset + 5  (8  bit): count
	 * offset + 6  (8  bit): mask 0
	 * offset + 7  (8  bit): data 0
	 *  ...
	 *
	 */

	struct drm_device *dev = bios->dev;
	struct nouveau_i2c_chan *auxch;
	uint32_t addr = ROM32(bios->data[offset + 1]);
	uint8_t count = bios->data[offset + 5];
	int len = 6 + count * 2;
	int ret, i;

	if (!bios->display.output) {
		NV_ERROR(dev, "INIT_AUXCH: no active output\n");
		return -EINVAL;
	}

	auxch = init_i2c_device_find(dev, bios->display.output->i2c_index);
	if (!auxch) {
		NV_ERROR(dev, "INIT_AUXCH: couldn't get auxch %d\n",
			 bios->display.output->i2c_index);
		return -ENODEV;
	}

	if (!iexec->execute)
		return len;

	offset += 6;
	for (i = 0; i < count; i++, offset += 2) {
		uint8_t data;

		ret = nouveau_dp_auxch(auxch, 9, addr, &data, 1);
		if (ret) {
			NV_ERROR(dev, "INIT_AUXCH: rd auxch fail %d\n", ret);
			return ret;
		}

		data &= bios->data[offset + 0];
		data |= bios->data[offset + 1];

		ret = nouveau_dp_auxch(auxch, 8, addr, &data, 1);
		if (ret) {
			NV_ERROR(dev, "INIT_AUXCH: wr auxch fail %d\n", ret);
			return ret;
		}
	}

	return len;
}

static int
init_zm_auxch(struct nvbios *bios, uint16_t offset, struct init_exec *iexec)
{
	/*
	 * INIT_ZM_AUXCH   opcode: 0x99 ('')
	 *
	 * offset      (8  bit): opcode
	 * offset + 1  (32 bit): address
	 * offset + 5  (8  bit): count
	 * offset + 6  (8  bit): data 0
	 *  ...
	 *
	 */

	struct drm_device *dev = bios->dev;
	struct nouveau_i2c_chan *auxch;
	uint32_t addr = ROM32(bios->data[offset + 1]);
	uint8_t count = bios->data[offset + 5];
	int len = 6 + count;
	int ret, i;

	if (!bios->display.output) {
		NV_ERROR(dev, "INIT_ZM_AUXCH: no active output\n");
		return -EINVAL;
	}

	auxch = init_i2c_device_find(dev, bios->display.output->i2c_index);
	if (!auxch) {
		NV_ERROR(dev, "INIT_ZM_AUXCH: couldn't get auxch %d\n",
			 bios->display.output->i2c_index);
		return -ENODEV;
	}

	if (!iexec->execute)
		return len;

	offset += 6;
	for (i = 0; i < count; i++, offset++) {
		ret = nouveau_dp_auxch(auxch, 8, addr, &bios->data[offset], 1);
		if (ret) {
			NV_ERROR(dev, "INIT_ZM_AUXCH: wr auxch fail %d\n", ret);
			return ret;
		}
	}

	return len;
}

static struct init_tbl_entry itbl_entry[] = {
	/* command name                       , id  , length  , offset  , mult    , command handler                 */
	/* INIT_PROG (0x31, 15, 10, 4) removed due to no example of use */
	{ "INIT_IO_RESTRICT_PROG"             , 0x32, init_io_restrict_prog           },
	{ "INIT_REPEAT"                       , 0x33, init_repeat                     },
	{ "INIT_IO_RESTRICT_PLL"              , 0x34, init_io_restrict_pll            },
	{ "INIT_END_REPEAT"                   , 0x36, init_end_repeat                 },
	{ "INIT_COPY"                         , 0x37, init_copy                       },
	{ "INIT_NOT"                          , 0x38, init_not                        },
	{ "INIT_IO_FLAG_CONDITION"            , 0x39, init_io_flag_condition          },
	{ "INIT_DP_CONDITION"                 , 0x3A, init_dp_condition               },
	{ "INIT_OP_3B"                        , 0x3B, init_op_3b                      },
	{ "INIT_OP_3C"                        , 0x3C, init_op_3c                      },
	{ "INIT_INDEX_ADDRESS_LATCHED"        , 0x49, init_idx_addr_latched           },
	{ "INIT_IO_RESTRICT_PLL2"             , 0x4A, init_io_restrict_pll2           },
	{ "INIT_PLL2"                         , 0x4B, init_pll2                       },
	{ "INIT_I2C_BYTE"                     , 0x4C, init_i2c_byte                   },
	{ "INIT_ZM_I2C_BYTE"                  , 0x4D, init_zm_i2c_byte                },
	{ "INIT_ZM_I2C"                       , 0x4E, init_zm_i2c                     },
	{ "INIT_TMDS"                         , 0x4F, init_tmds                       },
	{ "INIT_ZM_TMDS_GROUP"                , 0x50, init_zm_tmds_group              },
	{ "INIT_CR_INDEX_ADDRESS_LATCHED"     , 0x51, init_cr_idx_adr_latch           },
	{ "INIT_CR"                           , 0x52, init_cr                         },
	{ "INIT_ZM_CR"                        , 0x53, init_zm_cr                      },
	{ "INIT_ZM_CR_GROUP"                  , 0x54, init_zm_cr_group                },
	{ "INIT_CONDITION_TIME"               , 0x56, init_condition_time             },
	{ "INIT_ZM_REG_SEQUENCE"              , 0x58, init_zm_reg_sequence            },
	/* INIT_INDIRECT_REG (0x5A, 7, 0, 0) removed due to no example of use */
	{ "INIT_SUB_DIRECT"                   , 0x5B, init_sub_direct                 },
	{ "INIT_COPY_NV_REG"                  , 0x5F, init_copy_nv_reg                },
	{ "INIT_ZM_INDEX_IO"                  , 0x62, init_zm_index_io                },
	{ "INIT_COMPUTE_MEM"                  , 0x63, init_compute_mem                },
	{ "INIT_RESET"                        , 0x65, init_reset                      },
	{ "INIT_CONFIGURE_MEM"                , 0x66, init_configure_mem              },
	{ "INIT_CONFIGURE_CLK"                , 0x67, init_configure_clk              },
	{ "INIT_CONFIGURE_PREINIT"            , 0x68, init_configure_preinit          },
	{ "INIT_IO"                           , 0x69, init_io                         },
	{ "INIT_SUB"                          , 0x6B, init_sub                        },
	{ "INIT_RAM_CONDITION"                , 0x6D, init_ram_condition              },
	{ "INIT_NV_REG"                       , 0x6E, init_nv_reg                     },
	{ "INIT_MACRO"                        , 0x6F, init_macro                      },
	{ "INIT_DONE"                         , 0x71, init_done                       },
	{ "INIT_RESUME"                       , 0x72, init_resume                     },
	/* INIT_RAM_CONDITION2 (0x73, 9, 0, 0) removed due to no example of use */
	{ "INIT_TIME"                         , 0x74, init_time                       },
	{ "INIT_CONDITION"                    , 0x75, init_condition                  },
	{ "INIT_IO_CONDITION"                 , 0x76, init_io_condition               },
	{ "INIT_INDEX_IO"                     , 0x78, init_index_io                   },
	{ "INIT_PLL"                          , 0x79, init_pll                        },
	{ "INIT_ZM_REG"                       , 0x7A, init_zm_reg                     },
	{ "INIT_RAM_RESTRICT_PLL"             , 0x87, init_ram_restrict_pll           },
	{ "INIT_8C"                           , 0x8C, init_8c                         },
	{ "INIT_8D"                           , 0x8D, init_8d                         },
	{ "INIT_GPIO"                         , 0x8E, init_gpio                       },
	{ "INIT_RAM_RESTRICT_ZM_REG_GROUP"    , 0x8F, init_ram_restrict_zm_reg_group  },
	{ "INIT_COPY_ZM_REG"                  , 0x90, init_copy_zm_reg                },
	{ "INIT_ZM_REG_GROUP_ADDRESS_LATCHED" , 0x91, init_zm_reg_group_addr_latched  },
	{ "INIT_RESERVED"                     , 0x92, init_reserved                   },
	{ "INIT_96"                           , 0x96, init_96                         },
	{ "INIT_97"                           , 0x97, init_97                         },
	{ "INIT_AUXCH"                        , 0x98, init_auxch                      },
	{ "INIT_ZM_AUXCH"                     , 0x99, init_zm_auxch                   },
	{ NULL                                , 0   , NULL                            }
};

#define MAX_TABLE_OPS 1000

static int
parse_init_table(struct nvbios *bios, unsigned int offset,
		 struct init_exec *iexec)
{
	/*
	 * Parses all commands in an init table.
	 *
	 * We start out executing all commands found in the init table. Some
	 * opcodes may change the status of iexec->execute to SKIP, which will
	 * cause the following opcodes to perform no operation until the value
	 * is changed back to EXECUTE.
	 */

	int count = 0, i, ret;
	uint8_t id;

	/*
	 * Loop until INIT_DONE causes us to break out of the loop
	 * (or until offset > bios length just in case... )
	 * (and no more than MAX_TABLE_OPS iterations, just in case... )
	 */
	while ((offset < bios->length) && (count++ < MAX_TABLE_OPS)) {
		id = bios->data[offset];

		/* Find matching id in itbl_entry */
		for (i = 0; itbl_entry[i].name && (itbl_entry[i].id != id); i++)
			;

		if (!itbl_entry[i].name) {
			NV_ERROR(bios->dev,
				 "0x%04X: Init table command not found: "
				 "0x%02X\n", offset, id);
			return -ENOENT;
		}

		BIOSLOG(bios, "0x%04X: [ (0x%02X) - %s ]\n", offset,
			itbl_entry[i].id, itbl_entry[i].name);

		/* execute eventual command handler */
		ret = (*itbl_entry[i].handler)(bios, offset, iexec);
		if (ret < 0) {
			NV_ERROR(bios->dev, "0x%04X: Failed parsing init "
				 "table opcode: %s %d\n", offset,
				 itbl_entry[i].name, ret);
		}

		if (ret <= 0)
			break;

		/*
		 * Add the offset of the current command including all data
		 * of that command. The offset will then be pointing on the
		 * next op code.
		 */
		offset += ret;
	}

	if (offset >= bios->length)
		NV_WARN(bios->dev,
			"Offset 0x%04X greater than known bios image length.  "
			"Corrupt image?\n", offset);
	if (count >= MAX_TABLE_OPS)
		NV_WARN(bios->dev,
			"More than %d opcodes to a table is unlikely, "
			"is the bios image corrupt?\n", MAX_TABLE_OPS);

	return 0;
}

static void
parse_init_tables(struct nvbios *bios)
{
	/* Loops and calls parse_init_table() for each present table. */

	int i = 0;
	uint16_t table;
	struct init_exec iexec = {true, false};

	if (bios->old_style_init) {
		if (bios->init_script_tbls_ptr)
			parse_init_table(bios, bios->init_script_tbls_ptr, &iexec);
		if (bios->extra_init_script_tbl_ptr)
			parse_init_table(bios, bios->extra_init_script_tbl_ptr, &iexec);

		return;
	}

	while ((table = ROM16(bios->data[bios->init_script_tbls_ptr + i]))) {
		NV_INFO(bios->dev,
			"Parsing VBIOS init table %d at offset 0x%04X\n",
			i / 2, table);
		BIOSLOG(bios, "0x%04X: ------ Executing following commands ------\n", table);

		parse_init_table(bios, table, &iexec);
		i += 2;
	}
}

static uint16_t clkcmptable(struct nvbios *bios, uint16_t clktable, int pxclk)
{
	int compare_record_len, i = 0;
	uint16_t compareclk, scriptptr = 0;

	if (bios->major_version < 5) /* pre BIT */
		compare_record_len = 3;
	else
		compare_record_len = 4;

	do {
		compareclk = ROM16(bios->data[clktable + compare_record_len * i]);
		if (pxclk >= compareclk * 10) {
			if (bios->major_version < 5) {
				uint8_t tmdssub = bios->data[clktable + 2 + compare_record_len * i];
				scriptptr = ROM16(bios->data[bios->init_script_tbls_ptr + tmdssub * 2]);
			} else
				scriptptr = ROM16(bios->data[clktable + 2 + compare_record_len * i]);
			break;
		}
		i++;
	} while (compareclk);

	return scriptptr;
}

static void
run_digital_op_script(struct drm_device *dev, uint16_t scriptptr,
		      struct dcb_entry *dcbent, int head, bool dl)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;
	struct init_exec iexec = {true, false};

	NV_TRACE(dev, "0x%04X: Parsing digital output script table\n",
		 scriptptr);
	bios_idxprt_wr(bios, NV_CIO_CRX__COLOR, NV_CIO_CRE_44,
		       head ? NV_CIO_CRE_44_HEADB : NV_CIO_CRE_44_HEADA);
	/* note: if dcb entries have been merged, index may be misleading */
	NVWriteVgaCrtc5758(dev, head, 0, dcbent->index);
	parse_init_table(bios, scriptptr, &iexec);

	nv04_dfp_bind_head(dev, dcbent, head, dl);
}

static int call_lvds_manufacturer_script(struct drm_device *dev, struct dcb_entry *dcbent, int head, enum LVDS_script script)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;
	uint8_t sub = bios->data[bios->fp.xlated_entry + script] + (bios->fp.link_c_increment && dcbent->or & OUTPUT_C ? 1 : 0);
	uint16_t scriptofs = ROM16(bios->data[bios->init_script_tbls_ptr + sub * 2]);

	if (!bios->fp.xlated_entry || !sub || !scriptofs)
		return -EINVAL;

	run_digital_op_script(dev, scriptofs, dcbent, head, bios->fp.dual_link);

	if (script == LVDS_PANEL_OFF) {
		/* off-on delay in ms */
		msleep(ROM16(bios->data[bios->fp.xlated_entry + 7]));
	}
#ifdef __powerpc__
	/* Powerbook specific quirks */
	if ((dev->pci_device & 0xffff) == 0x0179 ||
	    (dev->pci_device & 0xffff) == 0x0189 ||
	    (dev->pci_device & 0xffff) == 0x0329) {
		if (script == LVDS_RESET) {
			nv_write_tmds(dev, dcbent->or, 0, 0x02, 0x72);

		} else if (script == LVDS_PANEL_ON) {
			bios_wr32(bios, NV_PBUS_DEBUG_DUALHEAD_CTL,
				  bios_rd32(bios, NV_PBUS_DEBUG_DUALHEAD_CTL)
				  | (1 << 31));
			bios_wr32(bios, NV_PCRTC_GPIO_EXT,
				  bios_rd32(bios, NV_PCRTC_GPIO_EXT) | 1);

		} else if (script == LVDS_PANEL_OFF) {
			bios_wr32(bios, NV_PBUS_DEBUG_DUALHEAD_CTL,
				  bios_rd32(bios, NV_PBUS_DEBUG_DUALHEAD_CTL)
				  & ~(1 << 31));
			bios_wr32(bios, NV_PCRTC_GPIO_EXT,
				  bios_rd32(bios, NV_PCRTC_GPIO_EXT) & ~3);
		}
	}
#endif

	return 0;
}

static int run_lvds_table(struct drm_device *dev, struct dcb_entry *dcbent, int head, enum LVDS_script script, int pxclk)
{
	/*
	 * The BIT LVDS table's header has the information to setup the
	 * necessary registers. Following the standard 4 byte header are:
	 * A bitmask byte and a dual-link transition pxclk value for use in
	 * selecting the init script when not using straps; 4 script pointers
	 * for panel power, selected by output and on/off; and 8 table pointers
	 * for panel init, the needed one determined by output, and bits in the
	 * conf byte. These tables are similar to the TMDS tables, consisting
	 * of a list of pxclks and script pointers.
	 */
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;
	unsigned int outputset = (dcbent->or == 4) ? 1 : 0;
	uint16_t scriptptr = 0, clktable;

	/*
	 * For now we assume version 3.0 table - g80 support will need some
	 * changes
	 */

	switch (script) {
	case LVDS_INIT:
		return -ENOSYS;
	case LVDS_BACKLIGHT_ON:
	case LVDS_PANEL_ON:
		scriptptr = ROM16(bios->data[bios->fp.lvdsmanufacturerpointer + 7 + outputset * 2]);
		break;
	case LVDS_BACKLIGHT_OFF:
	case LVDS_PANEL_OFF:
		scriptptr = ROM16(bios->data[bios->fp.lvdsmanufacturerpointer + 11 + outputset * 2]);
		break;
	case LVDS_RESET:
		clktable = bios->fp.lvdsmanufacturerpointer + 15;
		if (dcbent->or == 4)
			clktable += 8;

		if (dcbent->lvdsconf.use_straps_for_mode) {
			if (bios->fp.dual_link)
				clktable += 4;
			if (bios->fp.if_is_24bit)
				clktable += 2;
		} else {
			/* using EDID */
			int cmpval_24bit = (dcbent->or == 4) ? 4 : 1;

			if (bios->fp.dual_link) {
				clktable += 4;
				cmpval_24bit <<= 1;
			}

			if (bios->fp.strapless_is_24bit & cmpval_24bit)
				clktable += 2;
		}

		clktable = ROM16(bios->data[clktable]);
		if (!clktable) {
			NV_ERROR(dev, "Pixel clock comparison table not found\n");
			return -ENOENT;
		}
		scriptptr = clkcmptable(bios, clktable, pxclk);
	}

	if (!scriptptr) {
		NV_ERROR(dev, "LVDS output init script not found\n");
		return -ENOENT;
	}
	run_digital_op_script(dev, scriptptr, dcbent, head, bios->fp.dual_link);

	return 0;
}

int call_lvds_script(struct drm_device *dev, struct dcb_entry *dcbent, int head, enum LVDS_script script, int pxclk)
{
	/*
	 * LVDS operations are multiplexed in an effort to present a single API
	 * which works with two vastly differing underlying structures.
	 * This acts as the demux
	 */

	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;
	uint8_t lvds_ver = bios->data[bios->fp.lvdsmanufacturerpointer];
	uint32_t sel_clk_binding, sel_clk;
	int ret;

	if (bios->fp.last_script_invoc == (script << 1 | head) || !lvds_ver ||
	    (lvds_ver >= 0x30 && script == LVDS_INIT))
		return 0;

	if (!bios->fp.lvds_init_run) {
		bios->fp.lvds_init_run = true;
		call_lvds_script(dev, dcbent, head, LVDS_INIT, pxclk);
	}

	if (script == LVDS_PANEL_ON && bios->fp.reset_after_pclk_change)
		call_lvds_script(dev, dcbent, head, LVDS_RESET, pxclk);
	if (script == LVDS_RESET && bios->fp.power_off_for_reset)
		call_lvds_script(dev, dcbent, head, LVDS_PANEL_OFF, pxclk);

	NV_TRACE(dev, "Calling LVDS script %d:\n", script);

	/* don't let script change pll->head binding */
	sel_clk_binding = bios_rd32(bios, NV_PRAMDAC_SEL_CLK) & 0x50000;

	if (lvds_ver < 0x30)
		ret = call_lvds_manufacturer_script(dev, dcbent, head, script);
	else
		ret = run_lvds_table(dev, dcbent, head, script, pxclk);

	bios->fp.last_script_invoc = (script << 1 | head);

	sel_clk = NVReadRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK) & ~0x50000;
	NVWriteRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK, sel_clk | sel_clk_binding);
	/* some scripts set a value in NV_PBUS_POWERCTRL_2 and break video overlay */
	nvWriteMC(dev, NV_PBUS_POWERCTRL_2, 0);

	return ret;
}

struct lvdstableheader {
	uint8_t lvds_ver, headerlen, recordlen;
};

static int parse_lvds_manufacturer_table_header(struct drm_device *dev, struct nvbios *bios, struct lvdstableheader *lth)
{
	/*
	 * BMP version (0xa) LVDS table has a simple header of version and
	 * record length. The BIT LVDS table has the typical BIT table header:
	 * version byte, header length byte, record length byte, and a byte for
	 * the maximum number of records that can be held in the table.
	 */

	uint8_t lvds_ver, headerlen, recordlen;

	memset(lth, 0, sizeof(struct lvdstableheader));

	if (bios->fp.lvdsmanufacturerpointer == 0x0) {
		NV_ERROR(dev, "Pointer to LVDS manufacturer table invalid\n");
		return -EINVAL;
	}

	lvds_ver = bios->data[bios->fp.lvdsmanufacturerpointer];

	switch (lvds_ver) {
	case 0x0a:	/* pre NV40 */
		headerlen = 2;
		recordlen = bios->data[bios->fp.lvdsmanufacturerpointer + 1];
		break;
	case 0x30:	/* NV4x */
		headerlen = bios->data[bios->fp.lvdsmanufacturerpointer + 1];
		if (headerlen < 0x1f) {
			NV_ERROR(dev, "LVDS table header not understood\n");
			return -EINVAL;
		}
		recordlen = bios->data[bios->fp.lvdsmanufacturerpointer + 2];
		break;
	case 0x40:	/* G80/G90 */
		headerlen = bios->data[bios->fp.lvdsmanufacturerpointer + 1];
		if (headerlen < 0x7) {
			NV_ERROR(dev, "LVDS table header not understood\n");
			return -EINVAL;
		}
		recordlen = bios->data[bios->fp.lvdsmanufacturerpointer + 2];
		break;
	default:
		NV_ERROR(dev,
			 "LVDS table revision %d.%d not currently supported\n",
			 lvds_ver >> 4, lvds_ver & 0xf);
		return -ENOSYS;
	}

	lth->lvds_ver = lvds_ver;
	lth->headerlen = headerlen;
	lth->recordlen = recordlen;

	return 0;
}

static int
get_fp_strap(struct drm_device *dev, struct nvbios *bios)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;

	/*
	 * The fp strap is normally dictated by the "User Strap" in
	 * PEXTDEV_BOOT_0[20:16], but on BMP cards when bit 2 of the
	 * Internal_Flags struct at 0x48 is set, the user strap gets overriden
	 * by the PCI subsystem ID during POST, but not before the previous user
	 * strap has been committed to CR58 for CR57=0xf on head A, which may be
	 * read and used instead
	 */

	if (bios->major_version < 5 && bios->data[0x48] & 0x4)
		return NVReadVgaCrtc5758(dev, 0, 0xf) & 0xf;

	if (dev_priv->card_type >= NV_50)
		return (bios_rd32(bios, NV_PEXTDEV_BOOT_0) >> 24) & 0xf;
	else
		return (bios_rd32(bios, NV_PEXTDEV_BOOT_0) >> 16) & 0xf;
}

static int parse_fp_mode_table(struct drm_device *dev, struct nvbios *bios)
{
	uint8_t *fptable;
	uint8_t fptable_ver, headerlen = 0, recordlen, fpentries = 0xf, fpindex;
	int ret, ofs, fpstrapping;
	struct lvdstableheader lth;

	if (bios->fp.fptablepointer == 0x0) {
		/* Apple cards don't have the fp table; the laptops use DDC */
		/* The table is also missing on some x86 IGPs */
#ifndef __powerpc__
		NV_ERROR(dev, "Pointer to flat panel table invalid\n");
#endif
		bios->digital_min_front_porch = 0x4b;
		return 0;
	}

	fptable = &bios->data[bios->fp.fptablepointer];
	fptable_ver = fptable[0];

	switch (fptable_ver) {
	/*
	 * BMP version 0x5.0x11 BIOSen have version 1 like tables, but no
	 * version field, and miss one of the spread spectrum/PWM bytes.
	 * This could affect early GF2Go parts (not seen any appropriate ROMs
	 * though). Here we assume that a version of 0x05 matches this case
	 * (combining with a BMP version check would be better), as the
	 * common case for the panel type field is 0x0005, and that is in
	 * fact what we are reading the first byte of.
	 */
	case 0x05:	/* some NV10, 11, 15, 16 */
		recordlen = 42;
		ofs = -1;
		break;
	case 0x10:	/* some NV15/16, and NV11+ */
		recordlen = 44;
		ofs = 0;
		break;
	case 0x20:	/* NV40+ */
		headerlen = fptable[1];
		recordlen = fptable[2];
		fpentries = fptable[3];
		/*
		 * fptable[4] is the minimum
		 * RAMDAC_FP_HCRTC -> RAMDAC_FP_HSYNC_START gap
		 */
		bios->digital_min_front_porch = fptable[4];
		ofs = -7;
		break;
	default:
		NV_ERROR(dev,
			 "FP table revision %d.%d not currently supported\n",
			 fptable_ver >> 4, fptable_ver & 0xf);
		return -ENOSYS;
	}

	if (!bios->is_mobile) /* !mobile only needs digital_min_front_porch */
		return 0;

	ret = parse_lvds_manufacturer_table_header(dev, bios, &lth);
	if (ret)
		return ret;

	if (lth.lvds_ver == 0x30 || lth.lvds_ver == 0x40) {
		bios->fp.fpxlatetableptr = bios->fp.lvdsmanufacturerpointer +
							lth.headerlen + 1;
		bios->fp.xlatwidth = lth.recordlen;
	}
	if (bios->fp.fpxlatetableptr == 0x0) {
		NV_ERROR(dev, "Pointer to flat panel xlat table invalid\n");
		return -EINVAL;
	}

	fpstrapping = get_fp_strap(dev, bios);

	fpindex = bios->data[bios->fp.fpxlatetableptr +
					fpstrapping * bios->fp.xlatwidth];

	if (fpindex > fpentries) {
		NV_ERROR(dev, "Bad flat panel table index\n");
		return -ENOENT;
	}

	/* nv4x cards need both a strap value and fpindex of 0xf to use DDC */
	if (lth.lvds_ver > 0x10)
		bios->fp_no_ddc = fpstrapping != 0xf || fpindex != 0xf;

	/*
	 * If either the strap or xlated fpindex value are 0xf there is no
	 * panel using a strap-derived bios mode present.  this condition
	 * includes, but is different from, the DDC panel indicator above
	 */
	if (fpstrapping == 0xf || fpindex == 0xf)
		return 0;

	bios->fp.mode_ptr = bios->fp.fptablepointer + headerlen +
			    recordlen * fpindex + ofs;

	NV_TRACE(dev, "BIOS FP mode: %dx%d (%dkHz pixel clock)\n",
		 ROM16(bios->data[bios->fp.mode_ptr + 11]) + 1,
		 ROM16(bios->data[bios->fp.mode_ptr + 25]) + 1,
		 ROM16(bios->data[bios->fp.mode_ptr + 7]) * 10);

	return 0;
}

bool nouveau_bios_fp_mode(struct drm_device *dev, struct drm_display_mode *mode)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;
	uint8_t *mode_entry = &bios->data[bios->fp.mode_ptr];

	if (!mode)	/* just checking whether we can produce a mode */
		return bios->fp.mode_ptr;

	memset(mode, 0, sizeof(struct drm_display_mode));
	/*
	 * For version 1.0 (version in byte 0):
	 * bytes 1-2 are "panel type", including bits on whether Colour/mono,
	 * single/dual link, and type (TFT etc.)
	 * bytes 3-6 are bits per colour in RGBX
	 */
	mode->clock = ROM16(mode_entry[7]) * 10;
	/* bytes 9-10 is HActive */
	mode->hdisplay = ROM16(mode_entry[11]) + 1;
	/*
	 * bytes 13-14 is HValid Start
	 * bytes 15-16 is HValid End
	 */
	mode->hsync_start = ROM16(mode_entry[17]) + 1;
	mode->hsync_end = ROM16(mode_entry[19]) + 1;
	mode->htotal = ROM16(mode_entry[21]) + 1;
	/* bytes 23-24, 27-30 similarly, but vertical */
	mode->vdisplay = ROM16(mode_entry[25]) + 1;
	mode->vsync_start = ROM16(mode_entry[31]) + 1;
	mode->vsync_end = ROM16(mode_entry[33]) + 1;
	mode->vtotal = ROM16(mode_entry[35]) + 1;
	mode->flags |= (mode_entry[37] & 0x10) ?
			DRM_MODE_FLAG_PHSYNC : DRM_MODE_FLAG_NHSYNC;
	mode->flags |= (mode_entry[37] & 0x1) ?
			DRM_MODE_FLAG_PVSYNC : DRM_MODE_FLAG_NVSYNC;
	/*
	 * bytes 38-39 relate to spread spectrum settings
	 * bytes 40-43 are something to do with PWM
	 */

	mode->status = MODE_OK;
	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
	drm_mode_set_name(mode);
	return bios->fp.mode_ptr;
}

int nouveau_bios_parse_lvds_table(struct drm_device *dev, int pxclk, bool *dl, bool *if_is_24bit)
{
	/*
	 * The LVDS table header is (mostly) described in
	 * parse_lvds_manufacturer_table_header(): the BIT header additionally
	 * contains the dual-link transition pxclk (in 10s kHz), at byte 5 - if
	 * straps are not being used for the panel, this specifies the frequency
	 * at which modes should be set up in the dual link style.
	 *
	 * Following the header, the BMP (ver 0xa) table has several records,
	 * indexed by a separate xlat table, indexed in turn by the fp strap in
	 * EXTDEV_BOOT. Each record had a config byte, followed by 6 script
	 * numbers for use by INIT_SUB which controlled panel init and power,
	 * and finally a dword of ms to sleep between power off and on
	 * operations.
	 *
	 * In the BIT versions, the table following the header serves as an
	 * integrated config and xlat table: the records in the table are
	 * indexed by the FP strap nibble in EXTDEV_BOOT, and each record has
	 * two bytes - the first as a config byte, the second for indexing the
	 * fp mode table pointed to by the BIT 'D' table
	 *
	 * DDC is not used until after card init, so selecting the correct table
	 * entry and setting the dual link flag for EDID equipped panels,
	 * requiring tests against the native-mode pixel clock, cannot be done
	 * until later, when this function should be called with non-zero pxclk
	 */
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;
	int fpstrapping = get_fp_strap(dev, bios), lvdsmanufacturerindex = 0;
	struct lvdstableheader lth;
	uint16_t lvdsofs;
	int ret, chip_version = bios->chip_version;

	ret = parse_lvds_manufacturer_table_header(dev, bios, &lth);
	if (ret)
		return ret;

	switch (lth.lvds_ver) {
	case 0x0a:	/* pre NV40 */
		lvdsmanufacturerindex = bios->data[
					bios->fp.fpxlatemanufacturertableptr +
					fpstrapping];

		/* we're done if this isn't the EDID panel case */
		if (!pxclk)
			break;

		if (chip_version < 0x25) {
			/* nv17 behaviour
			 *
			 * It seems the old style lvds script pointer is reused
			 * to select 18/24 bit colour depth for EDID panels.
			 */
			lvdsmanufacturerindex =
				(bios->legacy.lvds_single_a_script_ptr & 1) ?
									2 : 0;
			if (pxclk >= bios->fp.duallink_transition_clk)
				lvdsmanufacturerindex++;
		} else if (chip_version < 0x30) {
			/* nv28 behaviour (off-chip encoder)
			 *
			 * nv28 does a complex dance of first using byte 121 of
			 * the EDID to choose the lvdsmanufacturerindex, then
			 * later attempting to match the EDID manufacturer and
			 * product IDs in a table (signature 'pidt' (panel id
			 * table?)), setting an lvdsmanufacturerindex of 0 and
			 * an fp strap of the match index (or 0xf if none)
			 */
			lvdsmanufacturerindex = 0;
		} else {
			/* nv31, nv34 behaviour */
			lvdsmanufacturerindex = 0;
			if (pxclk >= bios->fp.duallink_transition_clk)
				lvdsmanufacturerindex = 2;
			if (pxclk >= 140000)
				lvdsmanufacturerindex = 3;
		}

		/*
		 * nvidia set the high nibble of (cr57=f, cr58) to
		 * lvdsmanufacturerindex in this case; we don't
		 */
		break;
	case 0x30:	/* NV4x */
	case 0x40:	/* G80/G90 */
		lvdsmanufacturerindex = fpstrapping;
		break;
	default:
		NV_ERROR(dev, "LVDS table revision not currently supported\n");
		return -ENOSYS;
	}

	lvdsofs = bios->fp.xlated_entry = bios->fp.lvdsmanufacturerpointer + lth.headerlen + lth.recordlen * lvdsmanufacturerindex;
	switch (lth.lvds_ver) {
	case 0x0a:
		bios->fp.power_off_for_reset = bios->data[lvdsofs] & 1;
		bios->fp.reset_after_pclk_change = bios->data[lvdsofs] & 2;
		bios->fp.dual_link = bios->data[lvdsofs] & 4;
		bios->fp.link_c_increment = bios->data[lvdsofs] & 8;
		*if_is_24bit = bios->data[lvdsofs] & 16;
		break;
	case 0x30:
	case 0x40:
		/*
		 * No sign of the "power off for reset" or "reset for panel
		 * on" bits, but it's safer to assume we should
		 */
		bios->fp.power_off_for_reset = true;
		bios->fp.reset_after_pclk_change = true;

		/*
		 * It's ok lvdsofs is wrong for nv4x edid case; dual_link is
		 * over-written, and if_is_24bit isn't used
		 */
		bios->fp.dual_link = bios->data[lvdsofs] & 1;
		bios->fp.if_is_24bit = bios->data[lvdsofs] & 2;
		bios->fp.strapless_is_24bit = bios->data[bios->fp.lvdsmanufacturerpointer + 4];
		bios->fp.duallink_transition_clk = ROM16(bios->data[bios->fp.lvdsmanufacturerpointer + 5]) * 10;
		break;
	}

	/* Dell Latitude D620 reports a too-high value for the dual-link
	 * transition freq, causing us to program the panel incorrectly.
	 *
	 * It doesn't appear the VBIOS actually uses its transition freq
	 * (90000kHz), instead it uses the "Number of LVDS channels" field
	 * out of the panel ID structure (http://www.spwg.org/).
	 *
	 * For the moment, a quirk will do :)
	 */
	if ((dev->pdev->device == 0x01d7) &&
	    (dev->pdev->subsystem_vendor == 0x1028) &&
	    (dev->pdev->subsystem_device == 0x01c2)) {
		bios->fp.duallink_transition_clk = 80000;
	}

	/* set dual_link flag for EDID case */
	if (pxclk && (chip_version < 0x25 || chip_version > 0x28))
		bios->fp.dual_link = (pxclk >= bios->fp.duallink_transition_clk);

	*dl = bios->fp.dual_link;

	return 0;
}

static uint8_t *
bios_output_config_match(struct drm_device *dev, struct dcb_entry *dcbent,
			 uint16_t record, int record_len, int record_nr)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;
	uint32_t entry;
	uint16_t table;
	int i, v;

	for (i = 0; i < record_nr; i++, record += record_len) {
		table = ROM16(bios->data[record]);
		if (!table)
			continue;
		entry = ROM32(bios->data[table]);

		v = (entry & 0x000f0000) >> 16;
		if (!(v & dcbent->or))
			continue;

		v = (entry & 0x000000f0) >> 4;
		if (v != dcbent->location)
			continue;

		v = (entry & 0x0000000f);
		if (v != dcbent->type)
			continue;

		return &bios->data[table];
	}

	return NULL;
}

void *
nouveau_bios_dp_table(struct drm_device *dev, struct dcb_entry *dcbent,
		      int *length)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;
	uint8_t *table;

	if (!bios->display.dp_table_ptr) {
		NV_ERROR(dev, "No pointer to DisplayPort table\n");
		return NULL;
	}
	table = &bios->data[bios->display.dp_table_ptr];

	if (table[0] != 0x20 && table[0] != 0x21) {
		NV_ERROR(dev, "DisplayPort table version 0x%02x unknown\n",
			 table[0]);
		return NULL;
	}

	*length = table[4];
	return bios_output_config_match(dev, dcbent,
					bios->display.dp_table_ptr + table[1],
					table[2], table[3]);
}

int
nouveau_bios_run_display_table(struct drm_device *dev, struct dcb_entry *dcbent,
			       uint32_t sub, int pxclk)
{
	/*
	 * The display script table is located by the BIT 'U' table.
	 *
	 * It contains an array of pointers to various tables describing
	 * a particular output type.  The first 32-bits of the output
	 * tables contains similar information to a DCB entry, and is
	 * used to decide whether that particular table is suitable for
	 * the output you want to access.
	 *
	 * The "record header length" field here seems to indicate the
	 * offset of the first configuration entry in the output tables.
	 * This is 10 on most cards I've seen, but 12 has been witnessed
	 * on DP cards, and there's another script pointer within the
	 * header.
	 *
	 * offset + 0   ( 8 bits): version
	 * offset + 1   ( 8 bits): header length
	 * offset + 2   ( 8 bits): record length
	 * offset + 3   ( 8 bits): number of records
	 * offset + 4   ( 8 bits): record header length
	 * offset + 5   (16 bits): pointer to first output script table
	 */

	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;
	uint8_t *table = &bios->data[bios->display.script_table_ptr];
	uint8_t *otable = NULL;
	uint16_t script;
	int i = 0;

	if (!bios->display.script_table_ptr) {
		NV_ERROR(dev, "No pointer to output script table\n");
		return 1;
	}

	/*
	 * Nothing useful has been in any of the pre-2.0 tables I've seen,
	 * so until they are, we really don't need to care.
	 */
	if (table[0] < 0x20)
		return 1;

	if (table[0] != 0x20 && table[0] != 0x21) {
		NV_ERROR(dev, "Output script table version 0x%02x unknown\n",
			 table[0]);
		return 1;
	}

	/*
	 * The output script tables describing a particular output type
	 * look as follows:
	 *
	 * offset + 0   (32 bits): output this table matches (hash of DCB)
	 * offset + 4   ( 8 bits): unknown
	 * offset + 5   ( 8 bits): number of configurations
	 * offset + 6   (16 bits): pointer to some script
	 * offset + 8   (16 bits): pointer to some script
	 *
	 * headerlen == 10
	 * offset + 10           : configuration 0
	 *
	 * headerlen == 12
	 * offset + 10           : pointer to some script
	 * offset + 12           : configuration 0
	 *
	 * Each config entry is as follows:
	 *
	 * offset + 0   (16 bits): unknown, assumed to be a match value
	 * offset + 2   (16 bits): pointer to script table (clock set?)
	 * offset + 4   (16 bits): pointer to script table (reset?)
	 *
	 * There doesn't appear to be a count value to say how many
	 * entries exist in each script table, instead, a 0 value in
	 * the first 16-bit word seems to indicate both the end of the
	 * list and the default entry.  The second 16-bit word in the
	 * script tables is a pointer to the script to execute.
	 */

	NV_DEBUG_KMS(dev, "Searching for output entry for %d %d %d\n",
			dcbent->type, dcbent->location, dcbent->or);
	otable = bios_output_config_match(dev, dcbent, table[1] +
					  bios->display.script_table_ptr,
					  table[2], table[3]);
	if (!otable) {
		NV_ERROR(dev, "Couldn't find matching output script table\n");
		return 1;
	}

	if (pxclk < -2 || pxclk > 0) {
		/* Try to find matching script table entry */
		for (i = 0; i < otable[5]; i++) {
			if (ROM16(otable[table[4] + i*6]) == sub)
				break;
		}

		if (i == otable[5]) {
			NV_ERROR(dev, "Table 0x%04x not found for %d/%d, "
				      "using first\n",
				 sub, dcbent->type, dcbent->or);
			i = 0;
		}
	}

	if (pxclk == 0) {
		script = ROM16(otable[6]);
		if (!script) {
			NV_DEBUG_KMS(dev, "output script 0 not found\n");
			return 1;
		}

		NV_TRACE(dev, "0x%04X: parsing output script 0\n", script);
		nouveau_bios_run_init_table(dev, script, dcbent);
	} else
	if (pxclk == -1) {
		script = ROM16(otable[8]);
		if (!script) {
			NV_DEBUG_KMS(dev, "output script 1 not found\n");
			return 1;
		}

		NV_TRACE(dev, "0x%04X: parsing output script 1\n", script);
		nouveau_bios_run_init_table(dev, script, dcbent);
	} else
	if (pxclk == -2) {
		if (table[4] >= 12)
			script = ROM16(otable[10]);
		else
			script = 0;
		if (!script) {
			NV_DEBUG_KMS(dev, "output script 2 not found\n");
			return 1;
		}

		NV_TRACE(dev, "0x%04X: parsing output script 2\n", script);
		nouveau_bios_run_init_table(dev, script, dcbent);
	} else
	if (pxclk > 0) {
		script = ROM16(otable[table[4] + i*6 + 2]);
		if (script)
			script = clkcmptable(bios, script, pxclk);
		if (!script) {
			NV_ERROR(dev, "clock script 0 not found\n");
			return 1;
		}

		NV_TRACE(dev, "0x%04X: parsing clock script 0\n", script);
		nouveau_bios_run_init_table(dev, script, dcbent);
	} else
	if (pxclk < 0) {
		script = ROM16(otable[table[4] + i*6 + 4]);
		if (script)
			script = clkcmptable(bios, script, -pxclk);
		if (!script) {
			NV_DEBUG_KMS(dev, "clock script 1 not found\n");
			return 1;
		}

		NV_TRACE(dev, "0x%04X: parsing clock script 1\n", script);
		nouveau_bios_run_init_table(dev, script, dcbent);
	}

	return 0;
}


int run_tmds_table(struct drm_device *dev, struct dcb_entry *dcbent, int head, int pxclk)
{
	/*
	 * the pxclk parameter is in kHz
	 *
	 * This runs the TMDS regs setting code found on BIT bios cards
	 *
	 * For ffs(or) == 1 use the first table, for ffs(or) == 2 and
	 * ffs(or) == 3, use the second.
	 */

	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;
	int cv = bios->chip_version;
	uint16_t clktable = 0, scriptptr;
	uint32_t sel_clk_binding, sel_clk;

	/* pre-nv17 off-chip tmds uses scripts, post nv17 doesn't */
	if (cv >= 0x17 && cv != 0x1a && cv != 0x20 &&
	    dcbent->location != DCB_LOC_ON_CHIP)
		return 0;

	switch (ffs(dcbent->or)) {
	case 1:
		clktable = bios->tmds.output0_script_ptr;
		break;
	case 2:
	case 3:
		clktable = bios->tmds.output1_script_ptr;
		break;
	}

	if (!clktable) {
		NV_ERROR(dev, "Pixel clock comparison table not found\n");
		return -EINVAL;
	}

	scriptptr = clkcmptable(bios, clktable, pxclk);

	if (!scriptptr) {
		NV_ERROR(dev, "TMDS output init script not found\n");
		return -ENOENT;
	}

	/* don't let script change pll->head binding */
	sel_clk_binding = bios_rd32(bios, NV_PRAMDAC_SEL_CLK) & 0x50000;
	run_digital_op_script(dev, scriptptr, dcbent, head, pxclk >= 165000);
	sel_clk = NVReadRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK) & ~0x50000;
	NVWriteRAMDAC(dev, 0, NV_PRAMDAC_SEL_CLK, sel_clk | sel_clk_binding);

	return 0;
}

int get_pll_limits(struct drm_device *dev, uint32_t limit_match, struct pll_lims *pll_lim)
{
	/*
	 * PLL limits table
	 *
	 * Version 0x10: NV30, NV31
	 * One byte header (version), one record of 24 bytes
	 * Version 0x11: NV36 - Not implemented
	 * Seems to have same record style as 0x10, but 3 records rather than 1
	 * Version 0x20: Found on Geforce 6 cards
	 * Trivial 4 byte BIT header. 31 (0x1f) byte record length
	 * Version 0x21: Found on Geforce 7, 8 and some Geforce 6 cards
	 * 5 byte header, fifth byte of unknown purpose. 35 (0x23) byte record
	 * length in general, some (integrated) have an extra configuration byte
	 * Version 0x30: Found on Geforce 8, separates the register mapping
	 * from the limits tables.
	 */

	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;
	int cv = bios->chip_version, pllindex = 0;
	uint8_t pll_lim_ver = 0, headerlen = 0, recordlen = 0, entries = 0;
	uint32_t crystal_strap_mask, crystal_straps;

	if (!bios->pll_limit_tbl_ptr) {
		if (cv == 0x30 || cv == 0x31 || cv == 0x35 || cv == 0x36 ||
		    cv >= 0x40) {
			NV_ERROR(dev, "Pointer to PLL limits table invalid\n");
			return -EINVAL;
		}
	} else
		pll_lim_ver = bios->data[bios->pll_limit_tbl_ptr];

	crystal_strap_mask = 1 << 6;
	/* open coded dev->twoHeads test */
	if (cv > 0x10 && cv != 0x15 && cv != 0x1a && cv != 0x20)
		crystal_strap_mask |= 1 << 22;
	crystal_straps = nvReadEXTDEV(dev, NV_PEXTDEV_BOOT_0) &
							crystal_strap_mask;

	switch (pll_lim_ver) {
	/*
	 * We use version 0 to indicate a pre limit table bios (single stage
	 * pll) and load the hard coded limits instead.
	 */
	case 0:
		break;
	case 0x10:
	case 0x11:
		/*
		 * Strictly v0x11 has 3 entries, but the last two don't seem
		 * to get used.
		 */
		headerlen = 1;
		recordlen = 0x18;
		entries = 1;
		pllindex = 0;
		break;
	case 0x20:
	case 0x21:
	case 0x30:
	case 0x40:
		headerlen = bios->data[bios->pll_limit_tbl_ptr + 1];
		recordlen = bios->data[bios->pll_limit_tbl_ptr + 2];
		entries = bios->data[bios->pll_limit_tbl_ptr + 3];
		break;
	default:
		NV_ERROR(dev, "PLL limits table revision 0x%X not currently "
				"supported\n", pll_lim_ver);
		return -ENOSYS;
	}

	/* initialize all members to zero */
	memset(pll_lim, 0, sizeof(struct pll_lims));

	if (pll_lim_ver == 0x10 || pll_lim_ver == 0x11) {
		uint8_t *pll_rec = &bios->data[bios->pll_limit_tbl_ptr + headerlen + recordlen * pllindex];

		pll_lim->vco1.minfreq = ROM32(pll_rec[0]);
		pll_lim->vco1.maxfreq = ROM32(pll_rec[4]);
		pll_lim->vco2.minfreq = ROM32(pll_rec[8]);
		pll_lim->vco2.maxfreq = ROM32(pll_rec[12]);
		pll_lim->vco1.min_inputfreq = ROM32(pll_rec[16]);
		pll_lim->vco2.min_inputfreq = ROM32(pll_rec[20]);
		pll_lim->vco1.max_inputfreq = pll_lim->vco2.max_inputfreq = INT_MAX;

		/* these values taken from nv30/31/36 */
		pll_lim->vco1.min_n = 0x1;
		if (cv == 0x36)
			pll_lim->vco1.min_n = 0x5;
		pll_lim->vco1.max_n = 0xff;
		pll_lim->vco1.min_m = 0x1;
		pll_lim->vco1.max_m = 0xd;
		pll_lim->vco2.min_n = 0x4;
		/*
		 * On nv30, 31, 36 (i.e. all cards with two stage PLLs with this
		 * table version (apart from nv35)), N2 is compared to
		 * maxN2 (0x46) and 10 * maxM2 (0x4), so set maxN2 to 0x28 and
		 * save a comparison
		 */
		pll_lim->vco2.max_n = 0x28;
		if (cv == 0x30 || cv == 0x35)
			/* only 5 bits available for N2 on nv30/35 */
			pll_lim->vco2.max_n = 0x1f;
		pll_lim->vco2.min_m = 0x1;
		pll_lim->vco2.max_m = 0x4;
		pll_lim->max_log2p = 0x7;
		pll_lim->max_usable_log2p = 0x6;
	} else if (pll_lim_ver == 0x20 || pll_lim_ver == 0x21) {
		uint16_t plloffs = bios->pll_limit_tbl_ptr + headerlen;
		uint32_t reg = 0; /* default match */
		uint8_t *pll_rec;
		int i;

		/*
		 * First entry is default match, if nothing better. warn if
		 * reg field nonzero
		 */
		if (ROM32(bios->data[plloffs]))
			NV_WARN(dev, "Default PLL limit entry has non-zero "
				       "register field\n");

		if (limit_match > MAX_PLL_TYPES)
			/* we've been passed a reg as the match */
			reg = limit_match;
		else /* limit match is a pll type */
			for (i = 1; i < entries && !reg; i++) {
				uint32_t cmpreg = ROM32(bios->data[plloffs + recordlen * i]);

				if (limit_match == NVPLL &&
				    (cmpreg == NV_PRAMDAC_NVPLL_COEFF || cmpreg == 0x4000))
					reg = cmpreg;
				if (limit_match == MPLL &&
				    (cmpreg == NV_PRAMDAC_MPLL_COEFF || cmpreg == 0x4020))
					reg = cmpreg;
				if (limit_match == VPLL1 &&
				    (cmpreg == NV_PRAMDAC_VPLL_COEFF || cmpreg == 0x4010))
					reg = cmpreg;
				if (limit_match == VPLL2 &&
				    (cmpreg == NV_RAMDAC_VPLL2 || cmpreg == 0x4018))
					reg = cmpreg;
			}

		for (i = 1; i < entries; i++)
			if (ROM32(bios->data[plloffs + recordlen * i]) == reg) {
				pllindex = i;
				break;
			}

		pll_rec = &bios->data[plloffs + recordlen * pllindex];

		BIOSLOG(bios, "Loading PLL limits for reg 0x%08x\n",
			pllindex ? reg : 0);

		/*
		 * Frequencies are stored in tables in MHz, kHz are more
		 * useful, so we convert.
		 */

		/* What output frequencies can each VCO generate? */
		pll_lim->vco1.minfreq = ROM16(pll_rec[4]) * 1000;
		pll_lim->vco1.maxfreq = ROM16(pll_rec[6]) * 1000;
		pll_lim->vco2.minfreq = ROM16(pll_rec[8]) * 1000;
		pll_lim->vco2.maxfreq = ROM16(pll_rec[10]) * 1000;

		/* What input frequencies they accept (past the m-divider)? */
		pll_lim->vco1.min_inputfreq = ROM16(pll_rec[12]) * 1000;
		pll_lim->vco2.min_inputfreq = ROM16(pll_rec[14]) * 1000;
		pll_lim->vco1.max_inputfreq = ROM16(pll_rec[16]) * 1000;
		pll_lim->vco2.max_inputfreq = ROM16(pll_rec[18]) * 1000;

		/* What values are accepted as multiplier and divider? */
		pll_lim->vco1.min_n = pll_rec[20];
		pll_lim->vco1.max_n = pll_rec[21];
		pll_lim->vco1.min_m = pll_rec[22];
		pll_lim->vco1.max_m = pll_rec[23];
		pll_lim->vco2.min_n = pll_rec[24];
		pll_lim->vco2.max_n = pll_rec[25];
		pll_lim->vco2.min_m = pll_rec[26];
		pll_lim->vco2.max_m = pll_rec[27];

		pll_lim->max_usable_log2p = pll_lim->max_log2p = pll_rec[29];
		if (pll_lim->max_log2p > 0x7)
			/* pll decoding in nv_hw.c assumes never > 7 */
			NV_WARN(dev, "Max log2 P value greater than 7 (%d)\n",
				pll_lim->max_log2p);
		if (cv < 0x60)
			pll_lim->max_usable_log2p = 0x6;
		pll_lim->log2p_bias = pll_rec[30];

		if (recordlen > 0x22)
			pll_lim->refclk = ROM32(pll_rec[31]);

		if (recordlen > 0x23 && pll_rec[35])
			NV_WARN(dev,
				"Bits set in PLL configuration byte (%x)\n",
				pll_rec[35]);

		/* C51 special not seen elsewhere */
		if (cv == 0x51 && !pll_lim->refclk) {
			uint32_t sel_clk = bios_rd32(bios, NV_PRAMDAC_SEL_CLK);

			if (((limit_match == NV_PRAMDAC_VPLL_COEFF || limit_match == VPLL1) && sel_clk & 0x20) ||
			    ((limit_match == NV_RAMDAC_VPLL2 || limit_match == VPLL2) && sel_clk & 0x80)) {
				if (bios_idxprt_rd(bios, NV_CIO_CRX__COLOR, NV_CIO_CRE_CHIP_ID_INDEX) < 0xa3)
					pll_lim->refclk = 200000;
				else
					pll_lim->refclk = 25000;
			}
		}
	} else if (pll_lim_ver == 0x30) { /* ver 0x30 */
		uint8_t *entry = &bios->data[bios->pll_limit_tbl_ptr + headerlen];
		uint8_t *record = NULL;
		int i;

		BIOSLOG(bios, "Loading PLL limits for register 0x%08x\n",
			limit_match);

		for (i = 0; i < entries; i++, entry += recordlen) {
			if (ROM32(entry[3]) == limit_match) {
				record = &bios->data[ROM16(entry[1])];
				break;
			}
		}

		if (!record) {
			NV_ERROR(dev, "Register 0x%08x not found in PLL "
				 "limits table", limit_match);
			return -ENOENT;
		}

		pll_lim->vco1.minfreq = ROM16(record[0]) * 1000;
		pll_lim->vco1.maxfreq = ROM16(record[2]) * 1000;
		pll_lim->vco2.minfreq = ROM16(record[4]) * 1000;
		pll_lim->vco2.maxfreq = ROM16(record[6]) * 1000;
		pll_lim->vco1.min_inputfreq = ROM16(record[8]) * 1000;
		pll_lim->vco2.min_inputfreq = ROM16(record[10]) * 1000;
		pll_lim->vco1.max_inputfreq = ROM16(record[12]) * 1000;
		pll_lim->vco2.max_inputfreq = ROM16(record[14]) * 1000;
		pll_lim->vco1.min_n = record[16];
		pll_lim->vco1.max_n = record[17];
		pll_lim->vco1.min_m = record[18];
		pll_lim->vco1.max_m = record[19];
		pll_lim->vco2.min_n = record[20];
		pll_lim->vco2.max_n = record[21];
		pll_lim->vco2.min_m = record[22];
		pll_lim->vco2.max_m = record[23];
		pll_lim->max_usable_log2p = pll_lim->max_log2p = record[25];
		pll_lim->log2p_bias = record[27];
		pll_lim->refclk = ROM32(record[28]);
	} else if (pll_lim_ver) { /* ver 0x40 */
		uint8_t *entry = &bios->data[bios->pll_limit_tbl_ptr + headerlen];
		uint8_t *record = NULL;
		int i;

		BIOSLOG(bios, "Loading PLL limits for register 0x%08x\n",
			limit_match);

		for (i = 0; i < entries; i++, entry += recordlen) {
			if (ROM32(entry[3]) == limit_match) {
				record = &bios->data[ROM16(entry[1])];
				break;
			}
		}

		if (!record) {
			NV_ERROR(dev, "Register 0x%08x not found in PLL "
				 "limits table", limit_match);
			return -ENOENT;
		}

		pll_lim->vco1.minfreq = ROM16(record[0]) * 1000;
		pll_lim->vco1.maxfreq = ROM16(record[2]) * 1000;
		pll_lim->vco1.min_inputfreq = ROM16(record[4]) * 1000;
		pll_lim->vco1.max_inputfreq = ROM16(record[6]) * 1000;
		pll_lim->vco1.min_m = record[8];
		pll_lim->vco1.max_m = record[9];
		pll_lim->vco1.min_n = record[10];
		pll_lim->vco1.max_n = record[11];
		pll_lim->min_p = record[12];
		pll_lim->max_p = record[13];
		/* where did this go to?? */
		if (limit_match == 0x00614100 || limit_match == 0x00614900)
			pll_lim->refclk = 27000;
		else
			pll_lim->refclk = 100000;
	}

	/*
	 * By now any valid limit table ought to have set a max frequency for
	 * vco1, so if it's zero it's either a pre limit table bios, or one
	 * with an empty limit table (seen on nv18)
	 */
	if (!pll_lim->vco1.maxfreq) {
		pll_lim->vco1.minfreq = bios->fminvco;
		pll_lim->vco1.maxfreq = bios->fmaxvco;
		pll_lim->vco1.min_inputfreq = 0;
		pll_lim->vco1.max_inputfreq = INT_MAX;
		pll_lim->vco1.min_n = 0x1;
		pll_lim->vco1.max_n = 0xff;
		pll_lim->vco1.min_m = 0x1;
		if (crystal_straps == 0) {
			/* nv05 does this, nv11 doesn't, nv10 unknown */
			if (cv < 0x11)
				pll_lim->vco1.min_m = 0x7;
			pll_lim->vco1.max_m = 0xd;
		} else {
			if (cv < 0x11)
				pll_lim->vco1.min_m = 0x8;
			pll_lim->vco1.max_m = 0xe;
		}
		if (cv < 0x17 || cv == 0x1a || cv == 0x20)
			pll_lim->max_log2p = 4;
		else
			pll_lim->max_log2p = 5;
		pll_lim->max_usable_log2p = pll_lim->max_log2p;
	}

	if (!pll_lim->refclk)
		switch (crystal_straps) {
		case 0:
			pll_lim->refclk = 13500;
			break;
		case (1 << 6):
			pll_lim->refclk = 14318;
			break;
		case (1 << 22):
			pll_lim->refclk = 27000;
			break;
		case (1 << 22 | 1 << 6):
			pll_lim->refclk = 25000;
			break;
		}

	NV_DEBUG(dev, "pll.vco1.minfreq: %d\n", pll_lim->vco1.minfreq);
	NV_DEBUG(dev, "pll.vco1.maxfreq: %d\n", pll_lim->vco1.maxfreq);
	NV_DEBUG(dev, "pll.vco1.min_inputfreq: %d\n", pll_lim->vco1.min_inputfreq);
	NV_DEBUG(dev, "pll.vco1.max_inputfreq: %d\n", pll_lim->vco1.max_inputfreq);
	NV_DEBUG(dev, "pll.vco1.min_n: %d\n", pll_lim->vco1.min_n);
	NV_DEBUG(dev, "pll.vco1.max_n: %d\n", pll_lim->vco1.max_n);
	NV_DEBUG(dev, "pll.vco1.min_m: %d\n", pll_lim->vco1.min_m);
	NV_DEBUG(dev, "pll.vco1.max_m: %d\n", pll_lim->vco1.max_m);
	if (pll_lim->vco2.maxfreq) {
		NV_DEBUG(dev, "pll.vco2.minfreq: %d\n", pll_lim->vco2.minfreq);
		NV_DEBUG(dev, "pll.vco2.maxfreq: %d\n", pll_lim->vco2.maxfreq);
		NV_DEBUG(dev, "pll.vco2.min_inputfreq: %d\n", pll_lim->vco2.min_inputfreq);
		NV_DEBUG(dev, "pll.vco2.max_inputfreq: %d\n", pll_lim->vco2.max_inputfreq);
		NV_DEBUG(dev, "pll.vco2.min_n: %d\n", pll_lim->vco2.min_n);
		NV_DEBUG(dev, "pll.vco2.max_n: %d\n", pll_lim->vco2.max_n);
		NV_DEBUG(dev, "pll.vco2.min_m: %d\n", pll_lim->vco2.min_m);
		NV_DEBUG(dev, "pll.vco2.max_m: %d\n", pll_lim->vco2.max_m);
	}
	if (!pll_lim->max_p) {
		NV_DEBUG(dev, "pll.max_log2p: %d\n", pll_lim->max_log2p);
		NV_DEBUG(dev, "pll.log2p_bias: %d\n", pll_lim->log2p_bias);
	} else {
		NV_DEBUG(dev, "pll.min_p: %d\n", pll_lim->min_p);
		NV_DEBUG(dev, "pll.max_p: %d\n", pll_lim->max_p);
	}
	NV_DEBUG(dev, "pll.refclk: %d\n", pll_lim->refclk);

	return 0;
}

static void parse_bios_version(struct drm_device *dev, struct nvbios *bios, uint16_t offset)
{
	/*
	 * offset + 0  (8 bits): Micro version
	 * offset + 1  (8 bits): Minor version
	 * offset + 2  (8 bits): Chip version
	 * offset + 3  (8 bits): Major version
	 */

	bios->major_version = bios->data[offset + 3];
	bios->chip_version = bios->data[offset + 2];
	NV_TRACE(dev, "Bios version %02x.%02x.%02x.%02x\n",
		 bios->data[offset + 3], bios->data[offset + 2],
		 bios->data[offset + 1], bios->data[offset]);
}

static void parse_script_table_pointers(struct nvbios *bios, uint16_t offset)
{
	/*
	 * Parses the init table segment for pointers used in script execution.
	 *
	 * offset + 0  (16 bits): init script tables pointer
	 * offset + 2  (16 bits): macro index table pointer
	 * offset + 4  (16 bits): macro table pointer
	 * offset + 6  (16 bits): condition table pointer
	 * offset + 8  (16 bits): io condition table pointer
	 * offset + 10 (16 bits): io flag condition table pointer
	 * offset + 12 (16 bits): init function table pointer
	 */

	bios->init_script_tbls_ptr = ROM16(bios->data[offset]);
	bios->macro_index_tbl_ptr = ROM16(bios->data[offset + 2]);
	bios->macro_tbl_ptr = ROM16(bios->data[offset + 4]);
	bios->condition_tbl_ptr = ROM16(bios->data[offset + 6]);
	bios->io_condition_tbl_ptr = ROM16(bios->data[offset + 8]);
	bios->io_flag_condition_tbl_ptr = ROM16(bios->data[offset + 10]);
	bios->init_function_tbl_ptr = ROM16(bios->data[offset + 12]);
}

static int parse_bit_A_tbl_entry(struct drm_device *dev, struct nvbios *bios, struct bit_entry *bitentry)
{
	/*
	 * Parses the load detect values for g80 cards.
	 *
	 * offset + 0 (16 bits): loadval table pointer
	 */

	uint16_t load_table_ptr;
	uint8_t version, headerlen, entrylen, num_entries;

	if (bitentry->length != 3) {
		NV_ERROR(dev, "Do not understand BIT A table\n");
		return -EINVAL;
	}

	load_table_ptr = ROM16(bios->data[bitentry->offset]);

	if (load_table_ptr == 0x0) {
		NV_ERROR(dev, "Pointer to BIT loadval table invalid\n");
		return -EINVAL;
	}

	version = bios->data[load_table_ptr];

	if (version != 0x10) {
		NV_ERROR(dev, "BIT loadval table version %d.%d not supported\n",
			 version >> 4, version & 0xF);
		return -ENOSYS;
	}

	headerlen = bios->data[load_table_ptr + 1];
	entrylen = bios->data[load_table_ptr + 2];
	num_entries = bios->data[load_table_ptr + 3];

	if (headerlen != 4 || entrylen != 4 || num_entries != 2) {
		NV_ERROR(dev, "Do not understand BIT loadval table\n");
		return -EINVAL;
	}

	/* First entry is normal dac, 2nd tv-out perhaps? */
	bios->dactestval = ROM32(bios->data[load_table_ptr + headerlen]) & 0x3ff;

	return 0;
}

static int parse_bit_C_tbl_entry(struct drm_device *dev, struct nvbios *bios, struct bit_entry *bitentry)
{
	/*
	 * offset + 8  (16 bits): PLL limits table pointer
	 *
	 * There's more in here, but that's unknown.
	 */

	if (bitentry->length < 10) {
		NV_ERROR(dev, "Do not understand BIT C table\n");
		return -EINVAL;
	}

	bios->pll_limit_tbl_ptr = ROM16(bios->data[bitentry->offset + 8]);

	return 0;
}

static int parse_bit_display_tbl_entry(struct drm_device *dev, struct nvbios *bios, struct bit_entry *bitentry)
{
	/*
	 * Parses the flat panel table segment that the bit entry points to.
	 * Starting at bitentry->offset:
	 *
	 * offset + 0  (16 bits): ??? table pointer - seems to have 18 byte
	 * records beginning with a freq.
	 * offset + 2  (16 bits): mode table pointer
	 */

	if (bitentry->length != 4) {
		NV_ERROR(dev, "Do not understand BIT display table\n");
		return -EINVAL;
	}

	bios->fp.fptablepointer = ROM16(bios->data[bitentry->offset + 2]);

	return 0;
}

static int parse_bit_init_tbl_entry(struct drm_device *dev, struct nvbios *bios, struct bit_entry *bitentry)
{
	/*
	 * Parses the init table segment that the bit entry points to.
	 *
	 * See parse_script_table_pointers for layout
	 */

	if (bitentry->length < 14) {
		NV_ERROR(dev, "Do not understand init table\n");
		return -EINVAL;
	}

	parse_script_table_pointers(bios, bitentry->offset);

	if (bitentry->length >= 16)
		bios->some_script_ptr = ROM16(bios->data[bitentry->offset + 14]);
	if (bitentry->length >= 18)
		bios->init96_tbl_ptr = ROM16(bios->data[bitentry->offset + 16]);

	return 0;
}

static int parse_bit_i_tbl_entry(struct drm_device *dev, struct nvbios *bios, struct bit_entry *bitentry)
{
	/*
	 * BIT 'i' (info?) table
	 *
	 * offset + 0  (32 bits): BIOS version dword (as in B table)
	 * offset + 5  (8  bits): BIOS feature byte (same as for BMP?)
	 * offset + 13 (16 bits): pointer to table containing DAC load
	 * detection comparison values
	 *
	 * There's other things in the table, purpose unknown
	 */

	uint16_t daccmpoffset;
	uint8_t dacver, dacheaderlen;

	if (bitentry->length < 6) {
		NV_ERROR(dev, "BIT i table too short for needed information\n");
		return -EINVAL;
	}

	parse_bios_version(dev, bios, bitentry->offset);

	/*
	 * bit 4 seems to indicate a mobile bios (doesn't suffer from BMP's
	 * Quadro identity crisis), other bits possibly as for BMP feature byte
	 */
	bios->feature_byte = bios->data[bitentry->offset + 5];
	bios->is_mobile = bios->feature_byte & FEATURE_MOBILE;

	if (bitentry->length < 15) {
		NV_WARN(dev, "BIT i table not long enough for DAC load "
			       "detection comparison table\n");
		return -EINVAL;
	}

	daccmpoffset = ROM16(bios->data[bitentry->offset + 13]);

	/* doesn't exist on g80 */
	if (!daccmpoffset)
		return 0;

	/*
	 * The first value in the table, following the header, is the
	 * comparison value, the second entry is a comparison value for
	 * TV load detection.
	 */

	dacver = bios->data[daccmpoffset];
	dacheaderlen = bios->data[daccmpoffset + 1];

	if (dacver != 0x00 && dacver != 0x10) {
		NV_WARN(dev, "DAC load detection comparison table version "
			       "%d.%d not known\n", dacver >> 4, dacver & 0xf);
		return -ENOSYS;
	}

	bios->dactestval = ROM32(bios->data[daccmpoffset + dacheaderlen]);
	bios->tvdactestval = ROM32(bios->data[daccmpoffset + dacheaderlen + 4]);

	return 0;
}

static int parse_bit_lvds_tbl_entry(struct drm_device *dev, struct nvbios *bios, struct bit_entry *bitentry)
{
	/*
	 * Parses the LVDS table segment that the bit entry points to.
	 * Starting at bitentry->offset:
	 *
	 * offset + 0  (16 bits): LVDS strap xlate table pointer
	 */

	if (bitentry->length != 2) {
		NV_ERROR(dev, "Do not understand BIT LVDS table\n");
		return -EINVAL;
	}

	/*
	 * No idea if it's still called the LVDS manufacturer table, but
	 * the concept's close enough.
	 */
	bios->fp.lvdsmanufacturerpointer = ROM16(bios->data[bitentry->offset]);

	return 0;
}

static int
parse_bit_M_tbl_entry(struct drm_device *dev, struct nvbios *bios,
		      struct bit_entry *bitentry)
{
	/*
	 * offset + 2  (8  bits): number of options in an
	 * 	INIT_RAM_RESTRICT_ZM_REG_GROUP opcode option set
	 * offset + 3  (16 bits): pointer to strap xlate table for RAM
	 * 	restrict option selection
	 *
	 * There's a bunch of bits in this table other than the RAM restrict
	 * stuff that we don't use - their use currently unknown
	 */

	/*
	 * Older bios versions don't have a sufficiently long table for
	 * what we want
	 */
	if (bitentry->length < 0x5)
		return 0;

	if (bitentry->id[1] < 2) {
		bios->ram_restrict_group_count = bios->data[bitentry->offset + 2];
		bios->ram_restrict_tbl_ptr = ROM16(bios->data[bitentry->offset + 3]);
	} else {
		bios->ram_restrict_group_count = bios->data[bitentry->offset + 0];
		bios->ram_restrict_tbl_ptr = ROM16(bios->data[bitentry->offset + 1]);
	}

	return 0;
}

static int parse_bit_tmds_tbl_entry(struct drm_device *dev, struct nvbios *bios, struct bit_entry *bitentry)
{
	/*
	 * Parses the pointer to the TMDS table
	 *
	 * Starting at bitentry->offset:
	 *
	 * offset + 0  (16 bits): TMDS table pointer
	 *
	 * The TMDS table is typically found just before the DCB table, with a
	 * characteristic signature of 0x11,0x13 (1.1 being version, 0x13 being
	 * length?)
	 *
	 * At offset +7 is a pointer to a script, which I don't know how to
	 * run yet.
	 * At offset +9 is a pointer to another script, likewise
	 * Offset +11 has a pointer to a table where the first word is a pxclk
	 * frequency and the second word a pointer to a script, which should be
	 * run if the comparison pxclk frequency is less than the pxclk desired.
	 * This repeats for decreasing comparison frequencies
	 * Offset +13 has a pointer to a similar table
	 * The selection of table (and possibly +7/+9 script) is dictated by
	 * "or" from the DCB.
	 */

	uint16_t tmdstableptr, script1, script2;

	if (bitentry->length != 2) {
		NV_ERROR(dev, "Do not understand BIT TMDS table\n");
		return -EINVAL;
	}

	tmdstableptr = ROM16(bios->data[bitentry->offset]);

	if (tmdstableptr == 0x0) {
		NV_ERROR(dev, "Pointer to TMDS table invalid\n");
		return -EINVAL;
	}

	/* nv50+ has v2.0, but we don't parse it atm */
	if (bios->data[tmdstableptr] != 0x11) {
		NV_WARN(dev,
			"TMDS table revision %d.%d not currently supported\n",
			bios->data[tmdstableptr] >> 4, bios->data[tmdstableptr] & 0xf);
		return -ENOSYS;
	}

	/*
	 * These two scripts are odd: they don't seem to get run even when
	 * they are not stubbed.
	 */
	script1 = ROM16(bios->data[tmdstableptr + 7]);
	script2 = ROM16(bios->data[tmdstableptr + 9]);
	if (bios->data[script1] != 'q' || bios->data[script2] != 'q')
		NV_WARN(dev, "TMDS table script pointers not stubbed\n");

	bios->tmds.output0_script_ptr = ROM16(bios->data[tmdstableptr + 11]);
	bios->tmds.output1_script_ptr = ROM16(bios->data[tmdstableptr + 13]);

	return 0;
}

static int
parse_bit_U_tbl_entry(struct drm_device *dev, struct nvbios *bios,
		      struct bit_entry *bitentry)
{
	/*
	 * Parses the pointer to the G80 output script tables
	 *
	 * Starting at bitentry->offset:
	 *
	 * offset + 0  (16 bits): output script table pointer
	 */

	uint16_t outputscripttableptr;

	if (bitentry->length != 3) {
		NV_ERROR(dev, "Do not understand BIT U table\n");
		return -EINVAL;
	}

	outputscripttableptr = ROM16(bios->data[bitentry->offset]);
	bios->display.script_table_ptr = outputscripttableptr;
	return 0;
}

static int
parse_bit_displayport_tbl_entry(struct drm_device *dev, struct nvbios *bios,
				struct bit_entry *bitentry)
{
	bios->display.dp_table_ptr = ROM16(bios->data[bitentry->offset]);
	return 0;
}

struct bit_table {
	const char id;
	int (* const parse_fn)(struct drm_device *, struct nvbios *, struct bit_entry *);
};

#define BIT_TABLE(id, funcid) ((struct bit_table){ id, parse_bit_##funcid##_tbl_entry })

static int
parse_bit_table(struct nvbios *bios, const uint16_t bitoffset,
		struct bit_table *table)
{
	struct drm_device *dev = bios->dev;
	uint8_t maxentries = bios->data[bitoffset + 4];
	int i, offset;
	struct bit_entry bitentry;

	for (i = 0, offset = bitoffset + 6; i < maxentries; i++, offset += 6) {
		bitentry.id[0] = bios->data[offset];

		if (bitentry.id[0] != table->id)
			continue;

		bitentry.id[1] = bios->data[offset + 1];
		bitentry.length = ROM16(bios->data[offset + 2]);
		bitentry.offset = ROM16(bios->data[offset + 4]);

		return table->parse_fn(dev, bios, &bitentry);
	}

	NV_INFO(dev, "BIT table '%c' not found\n", table->id);
	return -ENOSYS;
}

static int
parse_bit_structure(struct nvbios *bios, const uint16_t bitoffset)
{
	int ret;

	/*
	 * The only restriction on parsing order currently is having 'i' first
	 * for use of bios->*_version or bios->feature_byte while parsing;
	 * functions shouldn't be actually *doing* anything apart from pulling
	 * data from the image into the bios struct, thus no interdependencies
	 */
	ret = parse_bit_table(bios, bitoffset, &BIT_TABLE('i', i));
	if (ret) /* info? */
		return ret;
	if (bios->major_version >= 0x60) /* g80+ */
		parse_bit_table(bios, bitoffset, &BIT_TABLE('A', A));
	ret = parse_bit_table(bios, bitoffset, &BIT_TABLE('C', C));
	if (ret)
		return ret;
	parse_bit_table(bios, bitoffset, &BIT_TABLE('D', display));
	ret = parse_bit_table(bios, bitoffset, &BIT_TABLE('I', init));
	if (ret)
		return ret;
	parse_bit_table(bios, bitoffset, &BIT_TABLE('M', M)); /* memory? */
	parse_bit_table(bios, bitoffset, &BIT_TABLE('L', lvds));
	parse_bit_table(bios, bitoffset, &BIT_TABLE('T', tmds));
	parse_bit_table(bios, bitoffset, &BIT_TABLE('U', U));
	parse_bit_table(bios, bitoffset, &BIT_TABLE('d', displayport));

	return 0;
}

static int parse_bmp_structure(struct drm_device *dev, struct nvbios *bios, unsigned int offset)
{
	/*
	 * Parses the BMP structure for useful things, but does not act on them
	 *
	 * offset +   5: BMP major version
	 * offset +   6: BMP minor version
	 * offset +   9: BMP feature byte
	 * offset +  10: BCD encoded BIOS version
	 *
	 * offset +  18: init script table pointer (for bios versions < 5.10h)
	 * offset +  20: extra init script table pointer (for bios
	 * versions < 5.10h)
	 *
	 * offset +  24: memory init table pointer (used on early bios versions)
	 * offset +  26: SDR memory sequencing setup data table
	 * offset +  28: DDR memory sequencing setup data table
	 *
	 * offset +  54: index of I2C CRTC pair to use for CRT output
	 * offset +  55: index of I2C CRTC pair to use for TV output
	 * offset +  56: index of I2C CRTC pair to use for flat panel output
	 * offset +  58: write CRTC index for I2C pair 0
	 * offset +  59: read CRTC index for I2C pair 0
	 * offset +  60: write CRTC index for I2C pair 1
	 * offset +  61: read CRTC index for I2C pair 1
	 *
	 * offset +  67: maximum internal PLL frequency (single stage PLL)
	 * offset +  71: minimum internal PLL frequency (single stage PLL)
	 *
	 * offset +  75: script table pointers, as described in
	 * parse_script_table_pointers
	 *
	 * offset +  89: TMDS single link output A table pointer
	 * offset +  91: TMDS single link output B table pointer
	 * offset +  95: LVDS single link output A table pointer
	 * offset + 105: flat panel timings table pointer
	 * offset + 107: flat panel strapping translation table pointer
	 * offset + 117: LVDS manufacturer panel config table pointer
	 * offset + 119: LVDS manufacturer strapping translation table pointer
	 *
	 * offset + 142: PLL limits table pointer
	 *
	 * offset + 156: minimum pixel clock for LVDS dual link
	 */

	uint8_t *bmp = &bios->data[offset], bmp_version_major, bmp_version_minor;
	uint16_t bmplength;
	uint16_t legacy_scripts_offset, legacy_i2c_offset;

	/* load needed defaults in case we can't parse this info */
	bios->dcb.i2c[0].write = NV_CIO_CRE_DDC_WR__INDEX;
	bios->dcb.i2c[0].read = NV_CIO_CRE_DDC_STATUS__INDEX;
	bios->dcb.i2c[1].write = NV_CIO_CRE_DDC0_WR__INDEX;
	bios->dcb.i2c[1].read = NV_CIO_CRE_DDC0_STATUS__INDEX;
	bios->digital_min_front_porch = 0x4b;
	bios->fmaxvco = 256000;
	bios->fminvco = 128000;
	bios->fp.duallink_transition_clk = 90000;

	bmp_version_major = bmp[5];
	bmp_version_minor = bmp[6];

	NV_TRACE(dev, "BMP version %d.%d\n",
		 bmp_version_major, bmp_version_minor);

	/*
	 * Make sure that 0x36 is blank and can't be mistaken for a DCB
	 * pointer on early versions
	 */
	if (bmp_version_major < 5)
		*(uint16_t *)&bios->data[0x36] = 0;

	/*
	 * Seems that the minor version was 1 for all major versions prior
	 * to 5. Version 6 could theoretically exist, but I suspect BIT
	 * happened instead.
	 */
	if ((bmp_version_major < 5 && bmp_version_minor != 1) || bmp_version_major > 5) {
		NV_ERROR(dev, "You have an unsupported BMP version. "
				"Please send in your bios\n");
		return -ENOSYS;
	}

	if (bmp_version_major == 0)
		/* nothing that's currently useful in this version */
		return 0;
	else if (bmp_version_major == 1)
		bmplength = 44; /* exact for 1.01 */
	else if (bmp_version_major == 2)
		bmplength = 48; /* exact for 2.01 */
	else if (bmp_version_major == 3)
		bmplength = 54;
		/* guessed - mem init tables added in this version */
	else if (bmp_version_major == 4 || bmp_version_minor < 0x1)
		/* don't know if 5.0 exists... */
		bmplength = 62;
		/* guessed - BMP I2C indices added in version 4*/
	else if (bmp_version_minor < 0x6)
		bmplength = 67; /* exact for 5.01 */
	else if (bmp_version_minor < 0x10)
		bmplength = 75; /* exact for 5.06 */
	else if (bmp_version_minor == 0x10)
		bmplength = 89; /* exact for 5.10h */
	else if (bmp_version_minor < 0x14)
		bmplength = 118; /* exact for 5.11h */
	else if (bmp_version_minor < 0x24)
		/*
		 * Not sure of version where pll limits came in;
		 * certainly exist by 0x24 though.
		 */
		/* length not exact: this is long enough to get lvds members */
		bmplength = 123;
	else if (bmp_version_minor < 0x27)
		/*
		 * Length not exact: this is long enough to get pll limit
		 * member
		 */
		bmplength = 144;
	else
		/*
		 * Length not exact: this is long enough to get dual link
		 * transition clock.
		 */
		bmplength = 158;

	/* checksum */
	if (nv_cksum(bmp, 8)) {
		NV_ERROR(dev, "Bad BMP checksum\n");
		return -EINVAL;
	}

	/*
	 * Bit 4 seems to indicate either a mobile bios or a quadro card --
	 * mobile behaviour consistent (nv11+), quadro only seen nv18gl-nv36gl
	 * (not nv10gl), bit 5 that the flat panel tables are present, and
	 * bit 6 a tv bios.
	 */
	bios->feature_byte = bmp[9];

	parse_bios_version(dev, bios, offset + 10);

	if (bmp_version_major < 5 || bmp_version_minor < 0x10)
		bios->old_style_init = true;
	legacy_scripts_offset = 18;
	if (bmp_version_major < 2)
		legacy_scripts_offset -= 4;
	bios->init_script_tbls_ptr = ROM16(bmp[legacy_scripts_offset]);
	bios->extra_init_script_tbl_ptr = ROM16(bmp[legacy_scripts_offset + 2]);

	if (bmp_version_major > 2) {	/* appears in BMP 3 */
		bios->legacy.mem_init_tbl_ptr = ROM16(bmp[24]);
		bios->legacy.sdr_seq_tbl_ptr = ROM16(bmp[26]);
		bios->legacy.ddr_seq_tbl_ptr = ROM16(bmp[28]);
	}

	legacy_i2c_offset = 0x48;	/* BMP version 2 & 3 */
	if (bmplength > 61)
		legacy_i2c_offset = offset + 54;
	bios->legacy.i2c_indices.crt = bios->data[legacy_i2c_offset];
	bios->legacy.i2c_indices.tv = bios->data[legacy_i2c_offset + 1];
	bios->legacy.i2c_indices.panel = bios->data[legacy_i2c_offset + 2];
	bios->dcb.i2c[0].write = bios->data[legacy_i2c_offset + 4];
	bios->dcb.i2c[0].read = bios->data[legacy_i2c_offset + 5];
	bios->dcb.i2c[1].write = bios->data[legacy_i2c_offset + 6];
	bios->dcb.i2c[1].read = bios->data[legacy_i2c_offset + 7];

	if (bmplength > 74) {
		bios->fmaxvco = ROM32(bmp[67]);
		bios->fminvco = ROM32(bmp[71]);
	}
	if (bmplength > 88)
		parse_script_table_pointers(bios, offset + 75);
	if (bmplength > 94) {
		bios->tmds.output0_script_ptr = ROM16(bmp[89]);
		bios->tmds.output1_script_ptr = ROM16(bmp[91]);
		/*
		 * Never observed in use with lvds scripts, but is reused for
		 * 18/24 bit panel interface default for EDID equipped panels
		 * (if_is_24bit not set directly to avoid any oscillation).
		 */
		bios->legacy.lvds_single_a_script_ptr = ROM16(bmp[95]);
	}
	if (bmplength > 108) {
		bios->fp.fptablepointer = ROM16(bmp[105]);
		bios->fp.fpxlatetableptr = ROM16(bmp[107]);
		bios->fp.xlatwidth = 1;
	}
	if (bmplength > 120) {
		bios->fp.lvdsmanufacturerpointer = ROM16(bmp[117]);
		bios->fp.fpxlatemanufacturertableptr = ROM16(bmp[119]);
	}
	if (bmplength > 143)
		bios->pll_limit_tbl_ptr = ROM16(bmp[142]);

	if (bmplength > 157)
		bios->fp.duallink_transition_clk = ROM16(bmp[156]) * 10;

	return 0;
}

static uint16_t findstr(uint8_t *data, int n, const uint8_t *str, int len)
{
	int i, j;

	for (i = 0; i <= (n - len); i++) {
		for (j = 0; j < len; j++)
			if (data[i + j] != str[j])
				break;
		if (j == len)
			return i;
	}

	return 0;
}

static struct dcb_gpio_entry *
new_gpio_entry(struct nvbios *bios)
{
	struct dcb_gpio_table *gpio = &bios->dcb.gpio;

	return &gpio->entry[gpio->entries++];
}

struct dcb_gpio_entry *
nouveau_bios_gpio_entry(struct drm_device *dev, enum dcb_gpio_tag tag)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;
	int i;

	for (i = 0; i < bios->dcb.gpio.entries; i++) {
		if (bios->dcb.gpio.entry[i].tag != tag)
			continue;

		return &bios->dcb.gpio.entry[i];
	}

	return NULL;
}

static void
parse_dcb30_gpio_entry(struct nvbios *bios, uint16_t offset)
{
	struct dcb_gpio_entry *gpio;
	uint16_t ent = ROM16(bios->data[offset]);
	uint8_t line = ent & 0x1f,
		tag = ent >> 5 & 0x3f,
		flags = ent >> 11 & 0x1f;

	if (tag == 0x3f)
		return;

	gpio = new_gpio_entry(bios);

	gpio->tag = tag;
	gpio->line = line;
	gpio->invert = flags != 4;
	gpio->entry = ent;
}

static void
parse_dcb40_gpio_entry(struct nvbios *bios, uint16_t offset)
{
	uint32_t entry = ROM32(bios->data[offset]);
	struct dcb_gpio_entry *gpio;

	if ((entry & 0x0000ff00) == 0x0000ff00)
		return;

	gpio = new_gpio_entry(bios);
	gpio->tag = (entry & 0x0000ff00) >> 8;
	gpio->line = (entry & 0x0000001f) >> 0;
	gpio->state_default = (entry & 0x01000000) >> 24;
	gpio->state[0] = (entry & 0x18000000) >> 27;
	gpio->state[1] = (entry & 0x60000000) >> 29;
	gpio->entry = entry;
}

static void
parse_dcb_gpio_table(struct nvbios *bios)
{
	struct drm_device *dev = bios->dev;
	uint16_t gpio_table_ptr = bios->dcb.gpio_table_ptr;
	uint8_t *gpio_table = &bios->data[gpio_table_ptr];
	int header_len = gpio_table[1],
	    entries = gpio_table[2],
	    entry_len = gpio_table[3];
	void (*parse_entry)(struct nvbios *, uint16_t) = NULL;
	int i;

	if (bios->dcb.version >= 0x40) {
		if (gpio_table_ptr && entry_len != 4) {
			NV_WARN(dev, "Invalid DCB GPIO table entry length.\n");
			return;
		}

		parse_entry = parse_dcb40_gpio_entry;

	} else if (bios->dcb.version >= 0x30) {
		if (gpio_table_ptr && entry_len != 2) {
			NV_WARN(dev, "Invalid DCB GPIO table entry length.\n");
			return;
		}

		parse_entry = parse_dcb30_gpio_entry;

	} else if (bios->dcb.version >= 0x22) {
		/*
		 * DCBs older than v3.0 don't really have a GPIO
		 * table, instead they keep some GPIO info at fixed
		 * locations.
		 */
		uint16_t dcbptr = ROM16(bios->data[0x36]);
		uint8_t *tvdac_gpio = &bios->data[dcbptr - 5];

		if (tvdac_gpio[0] & 1) {
			struct dcb_gpio_entry *gpio = new_gpio_entry(bios);

			gpio->tag = DCB_GPIO_TVDAC0;
			gpio->line = tvdac_gpio[1] >> 4;
			gpio->invert = tvdac_gpio[0] & 2;
		}
	}

	if (!gpio_table_ptr)
		return;

	if (entries > DCB_MAX_NUM_GPIO_ENTRIES) {
		NV_WARN(dev, "Too many entries in the DCB GPIO table.\n");
		entries = DCB_MAX_NUM_GPIO_ENTRIES;
	}

	for (i = 0; i < entries; i++)
		parse_entry(bios, gpio_table_ptr + header_len + entry_len * i);
}

struct dcb_connector_table_entry *
nouveau_bios_connector_entry(struct drm_device *dev, int index)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;
	struct dcb_connector_table_entry *cte;

	if (index >= bios->dcb.connector.entries)
		return NULL;

	cte = &bios->dcb.connector.entry[index];
	if (cte->type == 0xff)
		return NULL;

	return cte;
}

static enum dcb_connector_type
divine_connector_type(struct nvbios *bios, int index)
{
	struct dcb_table *dcb = &bios->dcb;
	unsigned encoders = 0, type = DCB_CONNECTOR_NONE;
	int i;

	for (i = 0; i < dcb->entries; i++) {
		if (dcb->entry[i].connector == index)
			encoders |= (1 << dcb->entry[i].type);
	}

	if (encoders & (1 << OUTPUT_DP)) {
		if (encoders & (1 << OUTPUT_TMDS))
			type = DCB_CONNECTOR_DP;
		else
			type = DCB_CONNECTOR_eDP;
	} else
	if (encoders & (1 << OUTPUT_TMDS)) {
		if (encoders & (1 << OUTPUT_ANALOG))
			type = DCB_CONNECTOR_DVI_I;
		else
			type = DCB_CONNECTOR_DVI_D;
	} else
	if (encoders & (1 << OUTPUT_ANALOG)) {
		type = DCB_CONNECTOR_VGA;
	} else
	if (encoders & (1 << OUTPUT_LVDS)) {
		type = DCB_CONNECTOR_LVDS;
	} else
	if (encoders & (1 << OUTPUT_TV)) {
		type = DCB_CONNECTOR_TV_0;
	}

	return type;
}

static void
apply_dcb_connector_quirks(struct nvbios *bios, int idx)
{
	struct dcb_connector_table_entry *cte = &bios->dcb.connector.entry[idx];
	struct drm_device *dev = bios->dev;

	/* Gigabyte NX85T */
	if ((dev->pdev->device == 0x0421) &&
	    (dev->pdev->subsystem_vendor == 0x1458) &&
	    (dev->pdev->subsystem_device == 0x344c)) {
		if (cte->type == DCB_CONNECTOR_HDMI_1)
			cte->type = DCB_CONNECTOR_DVI_I;
	}
}

static void
parse_dcb_connector_table(struct nvbios *bios)
{
	struct drm_device *dev = bios->dev;
	struct dcb_connector_table *ct = &bios->dcb.connector;
	struct dcb_connector_table_entry *cte;
	uint8_t *conntab = &bios->data[bios->dcb.connector_table_ptr];
	uint8_t *entry;
	int i;

	if (!bios->dcb.connector_table_ptr) {
		NV_DEBUG_KMS(dev, "No DCB connector table present\n");
		return;
	}

	NV_INFO(dev, "DCB connector table: VHER 0x%02x %d %d %d\n",
		conntab[0], conntab[1], conntab[2], conntab[3]);
	if ((conntab[0] != 0x30 && conntab[0] != 0x40) ||
	    (conntab[3] != 2 && conntab[3] != 4)) {
		NV_ERROR(dev, "  Unknown!  Please report.\n");
		return;
	}

	ct->entries = conntab[2];

	entry = conntab + conntab[1];
	cte = &ct->entry[0];
	for (i = 0; i < conntab[2]; i++, entry += conntab[3], cte++) {
		cte->index = i;
		if (conntab[3] == 2)
			cte->entry = ROM16(entry[0]);
		else
			cte->entry = ROM32(entry[0]);

		cte->type  = (cte->entry & 0x000000ff) >> 0;
		cte->index2 = (cte->entry & 0x00000f00) >> 8;
		switch (cte->entry & 0x00033000) {
		case 0x00001000:
			cte->gpio_tag = 0x07;
			break;
		case 0x00002000:
			cte->gpio_tag = 0x08;
			break;
		case 0x00010000:
			cte->gpio_tag = 0x51;
			break;
		case 0x00020000:
			cte->gpio_tag = 0x52;
			break;
		default:
			cte->gpio_tag = 0xff;
			break;
		}

		if (cte->type == 0xff)
			continue;

		apply_dcb_connector_quirks(bios, i);

		NV_INFO(dev, "  %d: 0x%08x: type 0x%02x idx %d tag 0x%02x\n",
			i, cte->entry, cte->type, cte->index, cte->gpio_tag);

		/* check for known types, fallback to guessing the type
		 * from attached encoders if we hit an unknown.
		 */
		switch (cte->type) {
		case DCB_CONNECTOR_VGA:
		case DCB_CONNECTOR_TV_0:
		case DCB_CONNECTOR_TV_1:
		case DCB_CONNECTOR_TV_3:
		case DCB_CONNECTOR_DVI_I:
		case DCB_CONNECTOR_DVI_D:
		case DCB_CONNECTOR_LVDS:
		case DCB_CONNECTOR_DP:
		case DCB_CONNECTOR_eDP:
		case DCB_CONNECTOR_HDMI_0:
		case DCB_CONNECTOR_HDMI_1:
			break;
		default:
			cte->type = divine_connector_type(bios, cte->index);
			NV_WARN(dev, "unknown type, using 0x%02x\n", cte->type);
			break;
		}

		if (nouveau_override_conntype) {
			int type = divine_connector_type(bios, cte->index);
			if (type != cte->type)
				NV_WARN(dev, " -> type 0x%02x\n", cte->type);
		}

	}
}

static struct dcb_entry *new_dcb_entry(struct dcb_table *dcb)
{
	struct dcb_entry *entry = &dcb->entry[dcb->entries];

	memset(entry, 0, sizeof(struct dcb_entry));
	entry->index = dcb->entries++;

	return entry;
}

static void fabricate_vga_output(struct dcb_table *dcb, int i2c, int heads)
{
	struct dcb_entry *entry = new_dcb_entry(dcb);

	entry->type = 0;
	entry->i2c_index = i2c;
	entry->heads = heads;
	entry->location = DCB_LOC_ON_CHIP;
	/* "or" mostly unused in early gen crt modesetting, 0 is fine */
}

static void fabricate_dvi_i_output(struct dcb_table *dcb, bool twoHeads)
{
	struct dcb_entry *entry = new_dcb_entry(dcb);

	entry->type = 2;
	entry->i2c_index = LEGACY_I2C_PANEL;
	entry->heads = twoHeads ? 3 : 1;
	entry->location = !DCB_LOC_ON_CHIP;	/* ie OFF CHIP */
	entry->or = 1;	/* means |0x10 gets set on CRE_LCD__INDEX */
	entry->duallink_possible = false; /* SiI164 and co. are single link */

#if 0
	/*
	 * For dvi-a either crtc probably works, but my card appears to only
	 * support dvi-d.  "nvidia" still attempts to program it for dvi-a,
	 * doing the full fp output setup (program 0x6808.. fp dimension regs,
	 * setting 0x680848 to 0x10000111 to enable, maybe setting 0x680880);
	 * the monitor picks up the mode res ok and lights up, but no pixel
	 * data appears, so the board manufacturer probably connected up the
	 * sync lines, but missed the video traces / components
	 *
	 * with this introduction, dvi-a left as an exercise for the reader.
	 */
	fabricate_vga_output(dcb, LEGACY_I2C_PANEL, entry->heads);
#endif
}

static void fabricate_tv_output(struct dcb_table *dcb, bool twoHeads)
{
	struct dcb_entry *entry = new_dcb_entry(dcb);

	entry->type = 1;
	entry->i2c_index = LEGACY_I2C_TV;
	entry->heads = twoHeads ? 3 : 1;
	entry->location = !DCB_LOC_ON_CHIP;	/* ie OFF CHIP */
}

static bool
parse_dcb20_entry(struct drm_device *dev, struct dcb_table *dcb,
		  uint32_t conn, uint32_t conf, struct dcb_entry *entry)
{
	entry->type = conn & 0xf;
	entry->i2c_index = (conn >> 4) & 0xf;
	entry->heads = (conn >> 8) & 0xf;
	if (dcb->version >= 0x40)
		entry->connector = (conn >> 12) & 0xf;
	entry->bus = (conn >> 16) & 0xf;
	entry->location = (conn >> 20) & 0x3;
	entry->or = (conn >> 24) & 0xf;
	/*
	 * Normal entries consist of a single bit, but dual link has the
	 * next most significant bit set too
	 */
	entry->duallink_possible =
			((1 << (ffs(entry->or) - 1)) * 3 == entry->or);

	switch (entry->type) {
	case OUTPUT_ANALOG:
		/*
		 * Although the rest of a CRT conf dword is usually
		 * zeros, mac biosen have stuff there so we must mask
		 */
		entry->crtconf.maxfreq = (dcb->version < 0x30) ?
					 (conf & 0xffff) * 10 :
					 (conf & 0xff) * 10000;
		break;
	case OUTPUT_LVDS:
		{
		uint32_t mask;
		if (conf & 0x1)
			entry->lvdsconf.use_straps_for_mode = true;
		if (dcb->version < 0x22) {
			mask = ~0xd;
			/*
			 * The laptop in bug 14567 lies and claims to not use
			 * straps when it does, so assume all DCB 2.0 laptops
			 * use straps, until a broken EDID using one is produced
			 */
			entry->lvdsconf.use_straps_for_mode = true;
			/*
			 * Both 0x4 and 0x8 show up in v2.0 tables; assume they
			 * mean the same thing (probably wrong, but might work)
			 */
			if (conf & 0x4 || conf & 0x8)
				entry->lvdsconf.use_power_scripts = true;
		} else {
			mask = ~0x5;
			if (conf & 0x4)
				entry->lvdsconf.use_power_scripts = true;
		}
		if (conf & mask) {
			/*
			 * Until we even try to use these on G8x, it's
			 * useless reporting unknown bits.  They all are.
			 */
			if (dcb->version >= 0x40)
				break;

			NV_ERROR(dev, "Unknown LVDS configuration bits, "
				      "please report\n");
		}
		break;
		}
	case OUTPUT_TV:
	{
		if (dcb->version >= 0x30)
			entry->tvconf.has_component_output = conf & (0x8 << 4);
		else
			entry->tvconf.has_component_output = false;

		break;
	}
	case OUTPUT_DP:
		entry->dpconf.sor.link = (conf & 0x00000030) >> 4;
		entry->dpconf.link_bw = (conf & 0x00e00000) >> 21;
		switch ((conf & 0x0f000000) >> 24) {
		case 0xf:
			entry->dpconf.link_nr = 4;
			break;
		case 0x3:
			entry->dpconf.link_nr = 2;
			break;
		default:
			entry->dpconf.link_nr = 1;
			break;
		}
		break;
	case OUTPUT_TMDS:
		entry->tmdsconf.sor.link = (conf & 0x00000030) >> 4;
		break;
	case 0xe:
		/* weird g80 mobile type that "nv" treats as a terminator */
		dcb->entries--;
		return false;
	default:
		break;
	}

	/* unsure what DCB version introduces this, 3.0? */
	if (conf & 0x100000)
		entry->i2c_upper_default = true;

	return true;
}

static bool
parse_dcb15_entry(struct drm_device *dev, struct dcb_table *dcb,
		  uint32_t conn, uint32_t conf, struct dcb_entry *entry)
{
	switch (conn & 0x0000000f) {
	case 0:
		entry->type = OUTPUT_ANALOG;
		break;
	case 1:
		entry->type = OUTPUT_TV;
		break;
	case 2:
	case 3:
		entry->type = OUTPUT_LVDS;
		break;
	case 4:
		switch ((conn & 0x000000f0) >> 4) {
		case 0:
			entry->type = OUTPUT_TMDS;
			break;
		case 1:
			entry->type = OUTPUT_LVDS;
			break;
		default:
			NV_ERROR(dev, "Unknown DCB subtype 4/%d\n",
				 (conn & 0x000000f0) >> 4);
			return false;
		}
		break;
	default:
		NV_ERROR(dev, "Unknown DCB type %d\n", conn & 0x0000000f);
		return false;
	}

	entry->i2c_index = (conn & 0x0003c000) >> 14;
	entry->heads = ((conn & 0x001c0000) >> 18) + 1;
	entry->or = entry->heads; /* same as heads, hopefully safe enough */
	entry->location = (conn & 0x01e00000) >> 21;
	entry->bus = (conn & 0x0e000000) >> 25;
	entry->duallink_possible = false;

	switch (entry->type) {
	case OUTPUT_ANALOG:
		entry->crtconf.maxfreq = (conf & 0xffff) * 10;
		break;
	case OUTPUT_TV:
		entry->tvconf.has_component_output = false;
		break;
	case OUTPUT_TMDS:
		/*
		 * Invent a DVI-A output, by copying the fields of the DVI-D
		 * output; reported to work by math_b on an NV20(!).
		 */
		fabricate_vga_output(dcb, entry->i2c_index, entry->heads);
		break;
	case OUTPUT_LVDS:
		if ((conn & 0x00003f00) != 0x10)
			entry->lvdsconf.use_straps_for_mode = true;
		entry->lvdsconf.use_power_scripts = true;
		break;
	default:
		break;
	}

	return true;
}

static bool parse_dcb_entry(struct drm_device *dev, struct dcb_table *dcb,
			    uint32_t conn, uint32_t conf)
{
	struct dcb_entry *entry = new_dcb_entry(dcb);
	bool ret;

	if (dcb->version >= 0x20)
		ret = parse_dcb20_entry(dev, dcb, conn, conf, entry);
	else
		ret = parse_dcb15_entry(dev, dcb, conn, conf, entry);
	if (!ret)
		return ret;

	read_dcb_i2c_entry(dev, dcb->version, dcb->i2c_table,
			   entry->i2c_index, &dcb->i2c[entry->i2c_index]);

	return true;
}

static
void merge_like_dcb_entries(struct drm_device *dev, struct dcb_table *dcb)
{
	/*
	 * DCB v2.0 lists each output combination separately.
	 * Here we merge compatible entries to have fewer outputs, with
	 * more options
	 */

	int i, newentries = 0;

	for (i = 0; i < dcb->entries; i++) {
		struct dcb_entry *ient = &dcb->entry[i];
		int j;

		for (j = i + 1; j < dcb->entries; j++) {
			struct dcb_entry *jent = &dcb->entry[j];

			if (jent->type == 100) /* already merged entry */
				continue;

			/* merge heads field when all other fields the same */
			if (jent->i2c_index == ient->i2c_index &&
			    jent->type == ient->type &&
			    jent->location == ient->location &&
			    jent->or == ient->or) {
				NV_TRACE(dev, "Merging DCB entries %d and %d\n",
					 i, j);
				ient->heads |= jent->heads;
				jent->type = 100; /* dummy value */
			}
		}
	}

	/* Compact entries merged into others out of dcb */
	for (i = 0; i < dcb->entries; i++) {
		if (dcb->entry[i].type == 100)
			continue;

		if (newentries != i) {
			dcb->entry[newentries] = dcb->entry[i];
			dcb->entry[newentries].index = newentries;
		}
		newentries++;
	}

	dcb->entries = newentries;
}

static int
parse_dcb_table(struct drm_device *dev, struct nvbios *bios, bool twoHeads)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct dcb_table *dcb = &bios->dcb;
	uint16_t dcbptr = 0, i2ctabptr = 0;
	uint8_t *dcbtable;
	uint8_t headerlen = 0x4, entries = DCB_MAX_NUM_ENTRIES;
	bool configblock = true;
	int recordlength = 8, confofs = 4;
	int i;

	/* get the offset from 0x36 */
	if (dev_priv->card_type > NV_04) {
		dcbptr = ROM16(bios->data[0x36]);
		if (dcbptr == 0x0000)
			NV_WARN(dev, "No output data (DCB) found in BIOS\n");
	}

	/* this situation likely means a really old card, pre DCB */
	if (dcbptr == 0x0) {
		NV_INFO(dev, "Assuming a CRT output exists\n");
		fabricate_vga_output(dcb, LEGACY_I2C_CRT, 1);

		if (nv04_tv_identify(dev, bios->legacy.i2c_indices.tv) >= 0)
			fabricate_tv_output(dcb, twoHeads);

		return 0;
	}

	dcbtable = &bios->data[dcbptr];

	/* get DCB version */
	dcb->version = dcbtable[0];
	NV_TRACE(dev, "Found Display Configuration Block version %d.%d\n",
		 dcb->version >> 4, dcb->version & 0xf);

	if (dcb->version >= 0x20) { /* NV17+ */
		uint32_t sig;

		if (dcb->version >= 0x30) { /* NV40+ */
			headerlen = dcbtable[1];
			entries = dcbtable[2];
			recordlength = dcbtable[3];
			i2ctabptr = ROM16(dcbtable[4]);
			sig = ROM32(dcbtable[6]);
			dcb->gpio_table_ptr = ROM16(dcbtable[10]);
			dcb->connector_table_ptr = ROM16(dcbtable[20]);
		} else {
			i2ctabptr = ROM16(dcbtable[2]);
			sig = ROM32(dcbtable[4]);
			headerlen = 8;
		}

		if (sig != 0x4edcbdcb) {
			NV_ERROR(dev, "Bad Display Configuration Block "
					"signature (%08X)\n", sig);
			return -EINVAL;
		}
	} else if (dcb->version >= 0x15) { /* some NV11 and NV20 */
		char sig[8] = { 0 };

		strncpy(sig, (char *)&dcbtable[-7], 7);
		i2ctabptr = ROM16(dcbtable[2]);
		recordlength = 10;
		confofs = 6;

		if (strcmp(sig, "DEV_REC")) {
			NV_ERROR(dev, "Bad Display Configuration Block "
					"signature (%s)\n", sig);
			return -EINVAL;
		}
	} else {
		/*
		 * v1.4 (some NV15/16, NV11+) seems the same as v1.5, but always
		 * has the same single (crt) entry, even when tv-out present, so
		 * the conclusion is this version cannot really be used.
		 * v1.2 tables (some NV6/10, and NV15+) normally have the same
		 * 5 entries, which are not specific to the card and so no use.
		 * v1.2 does have an I2C table that read_dcb_i2c_table can
		 * handle, but cards exist (nv11 in #14821) with a bad i2c table
		 * pointer, so use the indices parsed in parse_bmp_structure.
		 * v1.1 (NV5+, maybe some NV4) is entirely unhelpful
		 */
		NV_TRACEWARN(dev, "No useful information in BIOS output table; "
				  "adding all possible outputs\n");
		fabricate_vga_output(dcb, LEGACY_I2C_CRT, 1);

		/*
		 * Attempt to detect TV before DVI because the test
		 * for the former is more accurate and it rules the
		 * latter out.
		 */
		if (nv04_tv_identify(dev,
				     bios->legacy.i2c_indices.tv) >= 0)
			fabricate_tv_output(dcb, twoHeads);

		else if (bios->tmds.output0_script_ptr ||
			 bios->tmds.output1_script_ptr)
			fabricate_dvi_i_output(dcb, twoHeads);

		return 0;
	}

	if (!i2ctabptr)
		NV_WARN(dev, "No pointer to DCB I2C port table\n");
	else {
		dcb->i2c_table = &bios->data[i2ctabptr];
		if (dcb->version >= 0x30)
			dcb->i2c_default_indices = dcb->i2c_table[4];
	}

	if (entries > DCB_MAX_NUM_ENTRIES)
		entries = DCB_MAX_NUM_ENTRIES;

	for (i = 0; i < entries; i++) {
		uint32_t connection, config = 0;

		connection = ROM32(dcbtable[headerlen + recordlength * i]);
		if (configblock)
			config = ROM32(dcbtable[headerlen + confofs + recordlength * i]);

		/* seen on an NV11 with DCB v1.5 */
		if (connection == 0x00000000)
			break;

		/* seen on an NV17 with DCB v2.0 */
		if (connection == 0xffffffff)
			break;

		if ((connection & 0x0000000f) == 0x0000000f)
			continue;

		NV_TRACEWARN(dev, "Raw DCB entry %d: %08x %08x\n",
			     dcb->entries, connection, config);

		if (!parse_dcb_entry(dev, dcb, connection, config))
			break;
	}

	/*
	 * apart for v2.1+ not being known for requiring merging, this
	 * guarantees dcbent->index is the index of the entry in the rom image
	 */
	if (dcb->version < 0x21)
		merge_like_dcb_entries(dev, dcb);

	if (!dcb->entries)
		return -ENXIO;

	parse_dcb_gpio_table(bios);
	parse_dcb_connector_table(bios);
	return 0;
}

static void
fixup_legacy_connector(struct nvbios *bios)
{
	struct dcb_table *dcb = &bios->dcb;
	int i, i2c, i2c_conn[DCB_MAX_NUM_I2C_ENTRIES] = { };

	/*
	 * DCB 3.0 also has the table in most cases, but there are some cards
	 * where the table is filled with stub entries, and the DCB entriy
	 * indices are all 0.  We don't need the connector indices on pre-G80
	 * chips (yet?) so limit the use to DCB 4.0 and above.
	 */
	if (dcb->version >= 0x40)
		return;

	dcb->connector.entries = 0;

	/*
	 * No known connector info before v3.0, so make it up.  the rule here
	 * is: anything on the same i2c bus is considered to be on the same
	 * connector.  any output without an associated i2c bus is assigned
	 * its own unique connector index.
	 */
	for (i = 0; i < dcb->entries; i++) {
		/*
		 * Ignore the I2C index for on-chip TV-out, as there
		 * are cards with bogus values (nv31m in bug 23212),
		 * and it's otherwise useless.
		 */
		if (dcb->entry[i].type == OUTPUT_TV &&
		    dcb->entry[i].location == DCB_LOC_ON_CHIP)
			dcb->entry[i].i2c_index = 0xf;
		i2c = dcb->entry[i].i2c_index;

		if (i2c_conn[i2c]) {
			dcb->entry[i].connector = i2c_conn[i2c] - 1;
			continue;
		}

		dcb->entry[i].connector = dcb->connector.entries++;
		if (i2c != 0xf)
			i2c_conn[i2c] = dcb->connector.entries;
	}

	/* Fake the connector table as well as just connector indices */
	for (i = 0; i < dcb->connector.entries; i++) {
		dcb->connector.entry[i].index = i;
		dcb->connector.entry[i].type = divine_connector_type(bios, i);
		dcb->connector.entry[i].gpio_tag = 0xff;
	}
}

static void
fixup_legacy_i2c(struct nvbios *bios)
{
	struct dcb_table *dcb = &bios->dcb;
	int i;

	for (i = 0; i < dcb->entries; i++) {
		if (dcb->entry[i].i2c_index == LEGACY_I2C_CRT)
			dcb->entry[i].i2c_index = bios->legacy.i2c_indices.crt;
		if (dcb->entry[i].i2c_index == LEGACY_I2C_PANEL)
			dcb->entry[i].i2c_index = bios->legacy.i2c_indices.panel;
		if (dcb->entry[i].i2c_index == LEGACY_I2C_TV)
			dcb->entry[i].i2c_index = bios->legacy.i2c_indices.tv;
	}
}

static int load_nv17_hwsq_ucode_entry(struct drm_device *dev, struct nvbios *bios, uint16_t hwsq_offset, int entry)
{
	/*
	 * The header following the "HWSQ" signature has the number of entries,
	 * and the entry size
	 *
	 * An entry consists of a dword to write to the sequencer control reg
	 * (0x00001304), followed by the ucode bytes, written sequentially,
	 * starting at reg 0x00001400
	 */

	uint8_t bytes_to_write;
	uint16_t hwsq_entry_offset;
	int i;

	if (bios->data[hwsq_offset] <= entry) {
		NV_ERROR(dev, "Too few entries in HW sequencer table for "
				"requested entry\n");
		return -ENOENT;
	}

	bytes_to_write = bios->data[hwsq_offset + 1];

	if (bytes_to_write != 36) {
		NV_ERROR(dev, "Unknown HW sequencer entry size\n");
		return -EINVAL;
	}

	NV_TRACE(dev, "Loading NV17 power sequencing microcode\n");

	hwsq_entry_offset = hwsq_offset + 2 + entry * bytes_to_write;

	/* set sequencer control */
	bios_wr32(bios, 0x00001304, ROM32(bios->data[hwsq_entry_offset]));
	bytes_to_write -= 4;

	/* write ucode */
	for (i = 0; i < bytes_to_write; i += 4)
		bios_wr32(bios, 0x00001400 + i, ROM32(bios->data[hwsq_entry_offset + i + 4]));

	/* twiddle NV_PBUS_DEBUG_4 */
	bios_wr32(bios, NV_PBUS_DEBUG_4, bios_rd32(bios, NV_PBUS_DEBUG_4) | 0x18);

	return 0;
}

static int load_nv17_hw_sequencer_ucode(struct drm_device *dev,
					struct nvbios *bios)
{
	/*
	 * BMP based cards, from NV17, need a microcode loading to correctly
	 * control the GPIO etc for LVDS panels
	 *
	 * BIT based cards seem to do this directly in the init scripts
	 *
	 * The microcode entries are found by the "HWSQ" signature.
	 */

	const uint8_t hwsq_signature[] = { 'H', 'W', 'S', 'Q' };
	const int sz = sizeof(hwsq_signature);
	int hwsq_offset;

	hwsq_offset = findstr(bios->data, bios->length, hwsq_signature, sz);
	if (!hwsq_offset)
		return 0;

	/* always use entry 0? */
	return load_nv17_hwsq_ucode_entry(dev, bios, hwsq_offset + sz, 0);
}

uint8_t *nouveau_bios_embedded_edid(struct drm_device *dev)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;
	const uint8_t edid_sig[] = {
			0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 };
	uint16_t offset = 0;
	uint16_t newoffset;
	int searchlen = NV_PROM_SIZE;

	if (bios->fp.edid)
		return bios->fp.edid;

	while (searchlen) {
		newoffset = findstr(&bios->data[offset], searchlen,
								edid_sig, 8);
		if (!newoffset)
			return NULL;
		offset += newoffset;
		if (!nv_cksum(&bios->data[offset], EDID1_LEN))
			break;

		searchlen -= offset;
		offset++;
	}

	NV_TRACE(dev, "Found EDID in BIOS\n");

	return bios->fp.edid = &bios->data[offset];
}

void
nouveau_bios_run_init_table(struct drm_device *dev, uint16_t table,
			    struct dcb_entry *dcbent)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;
	struct init_exec iexec = { true, false };

	mutex_lock(&bios->lock);
	bios->display.output = dcbent;
	parse_init_table(bios, table, &iexec);
	bios->display.output = NULL;
	mutex_unlock(&bios->lock);
}

static bool NVInitVBIOS(struct drm_device *dev)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;

	memset(bios, 0, sizeof(struct nvbios));
	mutex_init(&bios->lock);
	bios->dev = dev;

	if (!NVShadowVBIOS(dev, bios->data))
		return false;

	bios->length = NV_PROM_SIZE;
	return true;
}

static int nouveau_parse_vbios_struct(struct drm_device *dev)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;
	const uint8_t bit_signature[] = { 0xff, 0xb8, 'B', 'I', 'T' };
	const uint8_t bmp_signature[] = { 0xff, 0x7f, 'N', 'V', 0x0 };
	int offset;

	offset = findstr(bios->data, bios->length,
					bit_signature, sizeof(bit_signature));
	if (offset) {
		NV_TRACE(dev, "BIT BIOS found\n");
		return parse_bit_structure(bios, offset + 6);
	}

	offset = findstr(bios->data, bios->length,
					bmp_signature, sizeof(bmp_signature));
	if (offset) {
		NV_TRACE(dev, "BMP BIOS found\n");
		return parse_bmp_structure(dev, bios, offset);
	}

	NV_ERROR(dev, "No known BIOS signature found\n");
	return -ENODEV;
}

int
nouveau_run_vbios_init(struct drm_device *dev)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;
	int i, ret = 0;

	NVLockVgaCrtcs(dev, false);
	if (nv_two_heads(dev))
		NVSetOwner(dev, bios->state.crtchead);

	if (bios->major_version < 5)	/* BMP only */
		load_nv17_hw_sequencer_ucode(dev, bios);

	if (bios->execute) {
		bios->fp.last_script_invoc = 0;
		bios->fp.lvds_init_run = false;
	}

	parse_init_tables(bios);

	/*
	 * Runs some additional script seen on G8x VBIOSen.  The VBIOS'
	 * parser will run this right after the init tables, the binary
	 * driver appears to run it at some point later.
	 */
	if (bios->some_script_ptr) {
		struct init_exec iexec = {true, false};

		NV_INFO(dev, "Parsing VBIOS init table at offset 0x%04X\n",
			bios->some_script_ptr);
		parse_init_table(bios, bios->some_script_ptr, &iexec);
	}

	if (dev_priv->card_type >= NV_50) {
		for (i = 0; i < bios->dcb.entries; i++) {
			nouveau_bios_run_display_table(dev,
						       &bios->dcb.entry[i],
						       0, 0);
		}
	}

	NVLockVgaCrtcs(dev, true);

	return ret;
}

static void
nouveau_bios_i2c_devices_takedown(struct drm_device *dev)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;
	struct dcb_i2c_entry *entry;
	int i;

	entry = &bios->dcb.i2c[0];
	for (i = 0; i < DCB_MAX_NUM_I2C_ENTRIES; i++, entry++)
		nouveau_i2c_fini(dev, entry);
}

int
nouveau_bios_init(struct drm_device *dev)
{
	struct drm_nouveau_private *dev_priv = dev->dev_private;
	struct nvbios *bios = &dev_priv->vbios;
	uint32_t saved_nv_pextdev_boot_0;
	bool was_locked;
	int ret;

	if (!NVInitVBIOS(dev))
		return -ENODEV;

	ret = nouveau_parse_vbios_struct(dev);
	if (ret)
		return ret;

	ret = parse_dcb_table(dev, bios, nv_two_heads(dev));
	if (ret)
		return ret;

	fixup_legacy_i2c(bios);
	fixup_legacy_connector(bios);

	if (!bios->major_version)	/* we don't run version 0 bios */
		return 0;

	/* these will need remembering across a suspend */
	saved_nv_pextdev_boot_0 = bios_rd32(bios, NV_PEXTDEV_BOOT_0);
	bios->state.saved_nv_pfb_cfg0 = bios_rd32(bios, NV_PFB_CFG0);

	/* init script execution disabled */
	bios->execute = false;

	/* ... unless card isn't POSTed already */
	if (dev_priv->card_type >= NV_10 &&
	    NVReadVgaCrtc(dev, 0, 0x00) == 0 &&
	    NVReadVgaCrtc(dev, 0, 0x1a) == 0) {
		NV_INFO(dev, "Adaptor not initialised\n");
		if (dev_priv->card_type < NV_50) {
			NV_ERROR(dev, "Unable to POST this chipset\n");
			return -ENODEV;
		}

		NV_INFO(dev, "Running VBIOS init tables\n");
		bios->execute = true;
	}

	bios_wr32(bios, NV_PEXTDEV_BOOT_0, saved_nv_pextdev_boot_0);

	ret = nouveau_run_vbios_init(dev);
	if (ret)
		return ret;

	/* feature_byte on BMP is poor, but init always sets CR4B */
	was_locked = NVLockVgaCrtcs(dev, false);
	if (bios->major_version < 5)
		bios->is_mobile = NVReadVgaCrtc(dev, 0, NV_CIO_CRE_4B) & 0x40;

	/* all BIT systems need p_f_m_t for digital_min_front_porch */
	if (bios->is_mobile || bios->major_version >= 5)
		ret = parse_fp_mode_table(dev, bios);
	NVLockVgaCrtcs(dev, was_locked);

	/* allow subsequent scripts to execute */
	bios->execute = true;

	return 0;
}

void
nouveau_bios_takedown(struct drm_device *dev)
{
	nouveau_bios_i2c_devices_takedown(dev);
}