summaryrefslogtreecommitdiff
path: root/arch/mips/pci/ops-pmcmsp.c
blob: dd2d9f7e9412aaf283d5e31f97609a07b513a520 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
/*
 * PMC-Sierra MSP board specific pci_ops
 *
 * Copyright 2001 MontaVista Software Inc.
 * Copyright 2005-2007 PMC-Sierra, Inc
 *
 * Author: Jun Sun, jsun@mvista.com or jsun@junsun.net
 *
 * Much of the code is derived from the original DDB5074 port by
 * Geert Uytterhoeven <geert@linux-m68k.org>
 *
 * This program is free software; you can redistribute	it and/or modify it
 * under  the terms of	the GNU General	 Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 *
 */

#define PCI_COUNTERS	1

#include <linux/types.h>
#include <linux/pci.h>
#include <linux/interrupt.h>

#if defined(CONFIG_PROC_FS) && defined(PCI_COUNTERS)
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#endif /* CONFIG_PROC_FS && PCI_COUNTERS */

#include <linux/kernel.h>
#include <linux/init.h>

#include <asm/byteorder.h>
#if defined(CONFIG_PMC_MSP7120_GW) || defined(CONFIG_PMC_MSP7120_EVAL)
#include <asm/mipsmtregs.h>
#endif

#include <msp_prom.h>
#include <msp_cic_int.h>
#include <msp_pci.h>
#include <msp_regs.h>
#include <msp_regops.h>

#define PCI_ACCESS_READ		0
#define PCI_ACCESS_WRITE	1

#if defined(CONFIG_PROC_FS) && defined(PCI_COUNTERS)
static char proc_init;
extern struct proc_dir_entry *proc_bus_pci_dir;
unsigned int pci_int_count[32];

static void pci_proc_init(void);

/*****************************************************************************
 *
 *  FUNCTION: show_msp_pci_counts
 *  _________________________________________________________________________
 *
 *  DESCRIPTION: Prints the count of how many times each PCI
 *		 interrupt has asserted. Can be invoked by the
 *		 /proc filesystem.
 *
 *  INPUTS:	 m	 - synthetic file construction data
 *		 v	 - iterator
 *
 *  RETURNS:	 0 or error
 *
 ****************************************************************************/
static int show_msp_pci_counts(struct seq_file *m, void *v)
{
	int i;
	unsigned int intcount, total = 0;

	for (i = 0; i < 32; ++i) {
		intcount = pci_int_count[i];
		if (intcount != 0) {
			seq_printf(m, "[%d] = %u\n", i, intcount);
			total += intcount;
		}
	}

	seq_printf(m, "total = %u\n", total);
	return 0;
}

static int msp_pci_rd_cnt_open(struct inode *inode, struct file *file)
{
	return single_open(file, show_msp_pci_counts, NULL);
}

static const struct file_operations msp_pci_rd_cnt_fops = {
	.open		= msp_pci_rd_cnt_open,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.release	= single_release,
};

/*****************************************************************************
 *
 *  FUNCTION: gen_pci_cfg_wr_show
 *  _________________________________________________________________________
 *
 *  DESCRIPTION: Generates a configuration write cycle for debug purposes.
 *		 The IDSEL line asserted and location and data written are
 *		 immaterial. Just want to be able to prove that a
 *		 configuration write can be correctly generated on the
 *		 PCI bus.  Intent is that this function by invocable from
 *		 the /proc filesystem.
 *
 *  INPUTS:	 m	 - synthetic file construction data
 *		 v	 - iterator
 *
 *  RETURNS:	 0 or error
 *
 ****************************************************************************/
static int gen_pci_cfg_wr_show(struct seq_file *m, void *v)
{
	unsigned char where = 0; /* Write to static Device/Vendor ID */
	unsigned char bus_num = 0; /* Bus 0 */
	unsigned char dev_fn = 0xF; /* Arbitrary device number */
	u32 wr_data = 0xFF00AA00; /* Arbitrary data */
	struct msp_pci_regs *preg = (void *)PCI_BASE_REG;
	unsigned long value;
	int intr;

	seq_puts(m, "PMC MSP PCI: Beginning\n");

	if (proc_init == 0) {
		pci_proc_init();
		proc_init = ~0;
	}

	seq_puts(m, "PMC MSP PCI: Before Cfg Wr\n");

	/*
	 * Generate PCI Configuration Write Cycle
	 */

	/* Clear cause register bits */
	preg->if_status = ~(BPCI_IFSTATUS_BC0F | BPCI_IFSTATUS_BC1F);

	/* Setup address that is to appear on PCI bus */
	preg->config_addr = BPCI_CFGADDR_ENABLE |
		(bus_num << BPCI_CFGADDR_BUSNUM_SHF) |
		(dev_fn << BPCI_CFGADDR_FUNCTNUM_SHF) |
		(where & 0xFC);

	value = cpu_to_le32(wr_data);

	/* Launch the PCI configuration write cycle */
	*PCI_CONFIG_SPACE_REG = value;

	/*
	 * Check if the PCI configuration cycle (rd or wr) succeeded, by
	 * checking the status bits for errors like master or target abort.
	 */
	intr = preg->if_status;

	seq_puts(m, "PMC MSP PCI: After Cfg Wr\n");
	return 0;
}

static int gen_pci_cfg_wr_open(struct inode *inode, struct file *file)
{
	return single_open(file, gen_pci_cfg_wr_show, NULL);
}

static const struct file_operations gen_pci_cfg_wr_fops = {
	.open		= gen_pci_cfg_wr_open,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.release	= single_release,
};

/*****************************************************************************
 *
 *  FUNCTION: pci_proc_init
 *  _________________________________________________________________________
 *
 *  DESCRIPTION: Create entries in the /proc filesystem for debug access.
 *
 *  INPUTS:	 none
 *
 *  OUTPUTS:	 none
 *
 *  RETURNS:	 none
 *
 ****************************************************************************/
static void pci_proc_init(void)
{
	proc_create("pmc_msp_pci_rd_cnt", 0, NULL, &msp_pci_rd_cnt_fops);
	proc_create("pmc_msp_pci_cfg_wr", 0, NULL, &gen_pci_cfg_wr_fops);
}
#endif /* CONFIG_PROC_FS && PCI_COUNTERS */

/*****************************************************************************
 *
 *  STRUCT: pci_io_resource
 *  _________________________________________________________________________
 *
 *  DESCRIPTION: Defines the address range that pciauto() will use to
 *		 assign to the I/O BARs of PCI devices.
 *
 *		 Use the start and end addresses of the MSP7120 PCI Host
 *		 Controller I/O space, in the form that they appear on the
 *		 PCI bus AFTER MSP7120 has performed address translation.
 *
 *		 For I/O accesses, MSP7120 ignores OATRAN and maps I/O
 *		 accesses into the bottom 0xFFF region of address space,
 *		 so that is the range to put into the pci_io_resource
 *		 struct.
 *
 *		 In MSP4200, the start address was 0x04 instead of the
 *		 expected 0x00. Will just assume there was a good reason
 *		 for this!
 *
 *  NOTES:	 Linux, by default, will assign I/O space to the lowest
 *		 region of address space. Since MSP7120 and Linux,
 *		 by default, have no offset in between how they map, the
 *		 io_offset element of pci_controller struct should be set
 *		 to zero.
 *  ELEMENTS:
 *    name	 - String used for a meaningful name.
 *
 *    start	 - Start address of MSP7120's I/O space, as MSP7120 presents
 *		   the address on the PCI bus.
 *
 *    end	 - End address of MSP7120's I/O space, as MSP7120 presents
 *		   the address on the PCI bus.
 *
 *    flags	 - Attributes indicating the type of resource. In this case,
 *		   indicate I/O space.
 *
 ****************************************************************************/
static struct resource pci_io_resource = {
	.name	= "pci IO space",
	.start	= 0x04,
	.end	= 0x0FFF,
	.flags	= IORESOURCE_IO /* I/O space */
};

/*****************************************************************************
 *
 *  STRUCT: pci_mem_resource
 *  _________________________________________________________________________
 *
 *  DESCRIPTION: Defines the address range that pciauto() will use to
 *		 assign to the memory BARs of PCI devices.
 *
 *		 The .start and .end values are dependent upon how address
 *		 translation is performed by the OATRAN regiser.
 *
 *		 The values to use for .start and .end are the values
 *		 in the form they appear on the PCI bus AFTER MSP7120 has
 *		 performed OATRAN address translation.
 *
 *  ELEMENTS:
 *    name	 - String used for a meaningful name.
 *
 *    start	 - Start address of MSP7120's memory space, as MSP7120 presents
 *		   the address on the PCI bus.
 *
 *    end	 - End address of MSP7120's memory space, as MSP7120 presents
 *		   the address on the PCI bus.
 *
 *    flags	 - Attributes indicating the type of resource. In this case,
 *		   indicate memory space.
 *
 ****************************************************************************/
static struct resource pci_mem_resource = {
	.name	= "pci memory space",
	.start	= MSP_PCI_SPACE_BASE,
	.end	= MSP_PCI_SPACE_END,
	.flags	= IORESOURCE_MEM	 /* memory space */
};

/*****************************************************************************
 *
 *  FUNCTION: bpci_interrupt
 *  _________________________________________________________________________
 *
 *  DESCRIPTION: PCI status interrupt handler. Updates the count of how
 *		 many times each status bit has been set, then clears
 *		 the status bits. If the appropriate macros are defined,
 *		 these counts can be viewed via the /proc filesystem.
 *
 *  INPUTS:	 irq	 - unused
 *		 dev_id	 - unused
 *		 pt_regs - unused
 *
 *  OUTPUTS:	 none
 *
 *  RETURNS:	 PCIBIOS_SUCCESSFUL  - success
 *
 ****************************************************************************/
static irqreturn_t bpci_interrupt(int irq, void *dev_id)
{
	struct msp_pci_regs *preg = (void *)PCI_BASE_REG;
	unsigned int stat = preg->if_status;

#if defined(CONFIG_PROC_FS) && defined(PCI_COUNTERS)
	int i;
	for (i = 0; i < 32; ++i) {
		if ((1 << i) & stat)
			++pci_int_count[i];
	}
#endif /* PROC_FS && PCI_COUNTERS */

	/* printk("PCI ISR: Status=%08X\n", stat); */

	/* write to clear all asserted interrupts */
	preg->if_status = stat;

	return IRQ_HANDLED;
}

/*****************************************************************************
 *
 *  FUNCTION: msp_pcibios_config_access
 *  _________________________________________________________________________
 *
 *  DESCRIPTION: Performs a PCI configuration access (rd or wr), then
 *		 checks that the access succeeded by querying MSP7120's
 *		 PCI status bits.
 *
 *  INPUTS:
 *		 access_type  - kind of PCI configuration cycle to perform
 *				(read or write). Legal values are
 *				PCI_ACCESS_WRITE and PCI_ACCESS_READ.
 *
 *		 bus	      - pointer to the bus number of the device to
 *				be targeted for the configuration cycle.
 *				The only element of the pci_bus structure
 *				used is bus->number. This argument determines
 *				if the configuration access will be Type 0 or
 *				Type 1. Since MSP7120 assumes itself to be the
 *				PCI Host, any non-zero bus->number generates
 *				a Type 1 access.
 *
 *		 devfn	      - this is an 8-bit field. The lower three bits
 *				specify the function number of the device to
 *				be targeted for the configuration cycle, with
 *				all three-bit combinations being legal. The
 *				upper five bits specify the device number,
 *				with legal values being 10 to 31.
 *
 *		 where	      - address within the Configuration Header
 *				space to access.
 *
 *		 data	      - for write accesses, contains the data to
 *				write.
 *
 *  OUTPUTS:
 *		 data	      - for read accesses, contains the value read.
 *
 *  RETURNS:	 PCIBIOS_SUCCESSFUL  - success
 *		 -1		     - access failure
 *
 ****************************************************************************/
int msp_pcibios_config_access(unsigned char access_type,
				struct pci_bus *bus,
				unsigned int devfn,
				unsigned char where,
				u32 *data)
{
	struct msp_pci_regs *preg = (void *)PCI_BASE_REG;
	unsigned char bus_num = bus->number;
	unsigned char dev_fn = (unsigned char)devfn;
	unsigned long intr;
	unsigned long value;
	static char pciirqflag;
	int ret;
#if defined(CONFIG_PMC_MSP7120_GW) || defined(CONFIG_PMC_MSP7120_EVAL)
	unsigned int	vpe_status;
#endif

#if defined(CONFIG_PROC_FS) && defined(PCI_COUNTERS)
	if (proc_init == 0) {
		pci_proc_init();
		proc_init = ~0;
	}
#endif /* CONFIG_PROC_FS && PCI_COUNTERS */

	/*
	 * Just the first time this function invokes, allocate
	 * an interrupt line for PCI host status interrupts. The
	 * allocation assigns an interrupt handler to the interrupt.
	 */
	if (pciirqflag == 0) {
		ret = request_irq(MSP_INT_PCI,/* Hardcoded internal MSP7120 wiring */
				bpci_interrupt,
				IRQF_SHARED,
				"PMC MSP PCI Host",
				preg);
		if (ret != 0)
			return ret;
		pciirqflag = ~0;
	}

#if defined(CONFIG_PMC_MSP7120_GW) || defined(CONFIG_PMC_MSP7120_EVAL)
	vpe_status = dvpe();
#endif

	/*
	 * Clear PCI cause register bits.
	 *
	 * In Polo, the PCI Host had a dedicated DMA called the
	 * Block Copy (not to be confused with the general purpose Block
	 * Copy Engine block). There appear to have been special interrupts
	 * for this Block Copy, called Block Copy 0 Fault (BC0F) and
	 * Block Copy 1 Fault (BC1F). MSP4200 and MSP7120 don't have this
	 * dedicated Block Copy block, so these two interrupts are now
	 * marked reserved. In case the	 Block Copy is resurrected in a
	 * future design, maintain the code that treats these two interrupts
	 * specially.
	 *
	 * Write to clear all interrupts in the PCI status register, aside
	 * from BC0F and BC1F.
	 */
	preg->if_status = ~(BPCI_IFSTATUS_BC0F | BPCI_IFSTATUS_BC1F);

	/* Setup address that is to appear on PCI bus */
	preg->config_addr = BPCI_CFGADDR_ENABLE |
		(bus_num << BPCI_CFGADDR_BUSNUM_SHF) |
		(dev_fn << BPCI_CFGADDR_FUNCTNUM_SHF) |
		(where & 0xFC);

	/* IF access is a PCI configuration write */
	if (access_type == PCI_ACCESS_WRITE) {
		value = cpu_to_le32(*data);
		*PCI_CONFIG_SPACE_REG = value;
	} else {
		/* ELSE access is a PCI configuration read */
		value = le32_to_cpu(*PCI_CONFIG_SPACE_REG);
		*data = value;
	}

	/*
	 * Check if the PCI configuration cycle (rd or wr) succeeded, by
	 * checking the status bits for errors like master or target abort.
	 */
	intr = preg->if_status;

	/* Clear config access */
	preg->config_addr = 0;

	/* IF error occurred */
	if (intr & ~(BPCI_IFSTATUS_BC0F | BPCI_IFSTATUS_BC1F)) {
		/* Clear status bits */
		preg->if_status = ~(BPCI_IFSTATUS_BC0F | BPCI_IFSTATUS_BC1F);

#if defined(CONFIG_PMC_MSP7120_GW) || defined(CONFIG_PMC_MSP7120_EVAL)
		evpe(vpe_status);
#endif

		return -1;
	}

#if defined(CONFIG_PMC_MSP7120_GW) || defined(CONFIG_PMC_MSP7120_EVAL)
	evpe(vpe_status);
#endif

	return PCIBIOS_SUCCESSFUL;
}

/*****************************************************************************
 *
 *  FUNCTION: msp_pcibios_read_config_byte
 *  _________________________________________________________________________
 *
 *  DESCRIPTION: Read a byte from PCI configuration address spac
 *		 Since the hardware can't address 8 bit chunks
 *		 directly, read a 32-bit chunk, then mask off extraneous
 *		 bits.
 *
 *  INPUTS	 bus	- structure containing attributes for the PCI bus
 *			  that the read is destined for.
 *		 devfn	- device/function combination that the read is
 *			  destined for.
 *		 where	- register within the Configuration Header space
 *			  to access.
 *
 *  OUTPUTS	 val	- read data
 *
 *  RETURNS:	 PCIBIOS_SUCCESSFUL  - success
 *		 -1		     - read access failure
 *
 ****************************************************************************/
static int
msp_pcibios_read_config_byte(struct pci_bus *bus,
				unsigned int devfn,
				int where,
				u32 *val)
{
	u32 data = 0;

	/*
	 * If the config access did not complete normally (e.g., underwent
	 * master abort) do the PCI compliant thing, which is to supply an
	 * all ones value.
	 */
	if (msp_pcibios_config_access(PCI_ACCESS_READ, bus, devfn,
					where, &data)) {
		*val = 0xFFFFFFFF;
		return -1;
	}

	*val = (data >> ((where & 3) << 3)) & 0x0ff;

	return PCIBIOS_SUCCESSFUL;
}

/*****************************************************************************
 *
 *  FUNCTION: msp_pcibios_read_config_word
 *  _________________________________________________________________________
 *
 *  DESCRIPTION: Read a word (16 bits) from PCI configuration address space.
 *		 Since the hardware can't address 16 bit chunks
 *		 directly, read a 32-bit chunk, then mask off extraneous
 *		 bits.
 *
 *  INPUTS	 bus	- structure containing attributes for the PCI bus
 *			  that the read is destined for.
 *		 devfn	- device/function combination that the read is
 *			  destined for.
 *		 where	- register within the Configuration Header space
 *			  to access.
 *
 *  OUTPUTS	 val	- read data
 *
 *  RETURNS:	 PCIBIOS_SUCCESSFUL	      - success
 *		 PCIBIOS_BAD_REGISTER_NUMBER  - bad register address
 *		 -1			      - read access failure
 *
 ****************************************************************************/
static int
msp_pcibios_read_config_word(struct pci_bus *bus,
				unsigned int devfn,
				int where,
				u32 *val)
{
	u32 data = 0;

	/* if (where & 1) */	/* Commented out non-compliant code.
				 * Should allow word access to configuration
				 * registers, with only exception being when
				 * the word access would wrap around into
				 * the next dword.
				 */
	if ((where & 3) == 3) {
		*val = 0xFFFFFFFF;
		return PCIBIOS_BAD_REGISTER_NUMBER;
	}

	/*
	 * If the config access did not complete normally (e.g., underwent
	 * master abort) do the PCI compliant thing, which is to supply an
	 * all ones value.
	 */
	if (msp_pcibios_config_access(PCI_ACCESS_READ, bus, devfn,
					where, &data)) {
		*val = 0xFFFFFFFF;
		return -1;
	}

	*val = (data >> ((where & 3) << 3)) & 0x0ffff;

	return PCIBIOS_SUCCESSFUL;
}

/*****************************************************************************
 *
 *  FUNCTION: msp_pcibios_read_config_dword
 *  _________________________________________________________________________
 *
 *  DESCRIPTION: Read a double word (32 bits) from PCI configuration
 *		 address space.
 *
 *  INPUTS	 bus	- structure containing attributes for the PCI bus
 *			  that the read is destined for.
 *		 devfn	- device/function combination that the read is
 *			  destined for.
 *		 where	- register within the Configuration Header space
 *			  to access.
 *
 *  OUTPUTS	 val	- read data
 *
 *  RETURNS:	 PCIBIOS_SUCCESSFUL	      - success
 *		 PCIBIOS_BAD_REGISTER_NUMBER  - bad register address
 *		 -1			      - read access failure
 *
 ****************************************************************************/
static int
msp_pcibios_read_config_dword(struct pci_bus *bus,
				unsigned int devfn,
				int where,
				u32 *val)
{
	u32 data = 0;

	/* Address must be dword aligned. */
	if (where & 3) {
		*val = 0xFFFFFFFF;
		return PCIBIOS_BAD_REGISTER_NUMBER;
	}

	/*
	 * If the config access did not complete normally (e.g., underwent
	 * master abort) do the PCI compliant thing, which is to supply an
	 * all ones value.
	 */
	if (msp_pcibios_config_access(PCI_ACCESS_READ, bus, devfn,
					where, &data)) {
		*val = 0xFFFFFFFF;
		return -1;
	}

	*val = data;

	return PCIBIOS_SUCCESSFUL;
}

/*****************************************************************************
 *
 *  FUNCTION: msp_pcibios_write_config_byte
 *  _________________________________________________________________________
 *
 *  DESCRIPTION: Write a byte to PCI configuration address space.
 *		 Since the hardware can't address 8 bit chunks
 *		 directly, a read-modify-write is performed.
 *
 *  INPUTS	 bus	- structure containing attributes for the PCI bus
 *			  that the write is destined for.
 *		 devfn	- device/function combination that the write is
 *			  destined for.
 *		 where	- register within the Configuration Header space
 *			  to access.
 *		 val	- value to write
 *
 *  OUTPUTS	 none
 *
 *  RETURNS:	 PCIBIOS_SUCCESSFUL  - success
 *		 -1		     - write access failure
 *
 ****************************************************************************/
static int
msp_pcibios_write_config_byte(struct pci_bus *bus,
				unsigned int devfn,
				int where,
				u8 val)
{
	u32 data = 0;

	/* read config space */
	if (msp_pcibios_config_access(PCI_ACCESS_READ, bus, devfn,
					where, &data))
		return -1;

	/* modify the byte within the dword */
	data = (data & ~(0xff << ((where & 3) << 3))) |
			(val << ((where & 3) << 3));

	/* write back the full dword */
	if (msp_pcibios_config_access(PCI_ACCESS_WRITE, bus, devfn,
					where, &data))
		return -1;

	return PCIBIOS_SUCCESSFUL;
}

/*****************************************************************************
 *
 *  FUNCTION: msp_pcibios_write_config_word
 *  _________________________________________________________________________
 *
 *  DESCRIPTION: Write a word (16-bits) to PCI configuration address space.
 *		 Since the hardware can't address 16 bit chunks
 *		 directly, a read-modify-write is performed.
 *
 *  INPUTS	 bus	- structure containing attributes for the PCI bus
 *			  that the write is destined for.
 *		 devfn	- device/function combination that the write is
 *			  destined for.
 *		 where	- register within the Configuration Header space
 *			  to access.
 *		 val	- value to write
 *
 *  OUTPUTS	 none
 *
 *  RETURNS:	 PCIBIOS_SUCCESSFUL	      - success
 *		 PCIBIOS_BAD_REGISTER_NUMBER  - bad register address
 *		 -1			      - write access failure
 *
 ****************************************************************************/
static int
msp_pcibios_write_config_word(struct pci_bus *bus,
				unsigned int devfn,
				int where,
				u16 val)
{
	u32 data = 0;

	/* Fixed non-compliance: if (where & 1) */
	if ((where & 3) == 3)
		return PCIBIOS_BAD_REGISTER_NUMBER;

	/* read config space */
	if (msp_pcibios_config_access(PCI_ACCESS_READ, bus, devfn,
					where, &data))
		return -1;

	/* modify the word within the dword */
	data = (data & ~(0xffff << ((where & 3) << 3))) |
			(val << ((where & 3) << 3));

	/* write back the full dword */
	if (msp_pcibios_config_access(PCI_ACCESS_WRITE, bus, devfn,
					where, &data))
		return -1;

	return PCIBIOS_SUCCESSFUL;
}

/*****************************************************************************
 *
 *  FUNCTION: msp_pcibios_write_config_dword
 *  _________________________________________________________________________
 *
 *  DESCRIPTION: Write a double word (32-bits) to PCI configuration address
 *		 space.
 *
 *  INPUTS	 bus	- structure containing attributes for the PCI bus
 *			  that the write is destined for.
 *		 devfn	- device/function combination that the write is
 *			  destined for.
 *		 where	- register within the Configuration Header space
 *			  to access.
 *		 val	- value to write
 *
 *  OUTPUTS	 none
 *
 *  RETURNS:	 PCIBIOS_SUCCESSFUL	      - success
 *		 PCIBIOS_BAD_REGISTER_NUMBER  - bad register address
 *		 -1			      - write access failure
 *
 ****************************************************************************/
static int
msp_pcibios_write_config_dword(struct pci_bus *bus,
				unsigned int devfn,
				int where,
				u32 val)
{
	/* check that address is dword aligned */
	if (where & 3)
		return PCIBIOS_BAD_REGISTER_NUMBER;

	/* perform write */
	if (msp_pcibios_config_access(PCI_ACCESS_WRITE, bus, devfn,
					where, &val))
		return -1;

	return PCIBIOS_SUCCESSFUL;
}

/*****************************************************************************
 *
 *  FUNCTION: msp_pcibios_read_config
 *  _________________________________________________________________________
 *
 *  DESCRIPTION: Interface the PCI configuration read request with
 *		 the appropriate function, based on how many bytes
 *		 the read request is.
 *
 *  INPUTS	 bus	- structure containing attributes for the PCI bus
 *			  that the write is destined for.
 *		 devfn	- device/function combination that the write is
 *			  destined for.
 *		 where	- register within the Configuration Header space
 *			  to access.
 *		 size	- in units of bytes, should be 1, 2, or 4.
 *
 *  OUTPUTS	 val	- value read, with any extraneous bytes masked
 *			  to zero.
 *
 *  RETURNS:	 PCIBIOS_SUCCESSFUL   - success
 *		 -1		      - failure
 *
 ****************************************************************************/
int
msp_pcibios_read_config(struct pci_bus *bus,
			unsigned int	devfn,
			int where,
			int size,
			u32 *val)
{
	if (size == 1) {
		if (msp_pcibios_read_config_byte(bus, devfn, where, val)) {
			return -1;
		}
	} else if (size == 2) {
		if (msp_pcibios_read_config_word(bus, devfn, where, val)) {
			return -1;
		}
	} else if (size == 4) {
		if (msp_pcibios_read_config_dword(bus, devfn, where, val)) {
			return -1;
		}
	} else {
		*val = 0xFFFFFFFF;
		return -1;
	}

	return PCIBIOS_SUCCESSFUL;
}

/*****************************************************************************
 *
 *  FUNCTION: msp_pcibios_write_config
 *  _________________________________________________________________________
 *
 *  DESCRIPTION: Interface the PCI configuration write request with
 *		 the appropriate function, based on how many bytes
 *		 the read request is.
 *
 *  INPUTS	 bus	- structure containing attributes for the PCI bus
 *			  that the write is destined for.
 *		 devfn	- device/function combination that the write is
 *			  destined for.
 *		 where	- register within the Configuration Header space
 *			  to access.
 *		 size	- in units of bytes, should be 1, 2, or 4.
 *		 val	- value to write
 *
 *  OUTPUTS:	 none
 *
 *  RETURNS:	 PCIBIOS_SUCCESSFUL   - success
 *		 -1		      - failure
 *
 ****************************************************************************/
int
msp_pcibios_write_config(struct pci_bus *bus,
			unsigned int devfn,
			int where,
			int size,
			u32 val)
{
	if (size == 1) {
		if (msp_pcibios_write_config_byte(bus, devfn,
						where, (u8)(0xFF & val))) {
			return -1;
		}
	} else if (size == 2) {
		if (msp_pcibios_write_config_word(bus, devfn,
						where, (u16)(0xFFFF & val))) {
			return -1;
		}
	} else if (size == 4) {
		if (msp_pcibios_write_config_dword(bus, devfn, where, val)) {
			return -1;
		}
	} else {
		return -1;
	}

	return PCIBIOS_SUCCESSFUL;
}

/*****************************************************************************
 *
 *  STRUCTURE: msp_pci_ops
 *  _________________________________________________________________________
 *
 *  DESCRIPTION: structure to abstract the hardware specific PCI
 *		 configuration accesses.
 *
 *  ELEMENTS:
 *    read	- function for Linux to generate PCI Configuration reads.
 *    write	- function for Linux to generate PCI Configuration writes.
 *
 ****************************************************************************/
struct pci_ops msp_pci_ops = {
	.read = msp_pcibios_read_config,
	.write = msp_pcibios_write_config
};

/*****************************************************************************
 *
 *  STRUCTURE: msp_pci_controller
 *  _________________________________________________________________________
 *
 *  Describes the attributes of the MSP7120 PCI Host Controller
 *
 *  ELEMENTS:
 *    pci_ops	   - abstracts the hardware specific PCI configuration
 *		     accesses.
 *
 *    mem_resource - address range pciauto() uses to assign to PCI device
 *		     memory BARs.
 *
 *    mem_offset   - offset between how MSP7120 outbound PCI memory
 *		     transaction addresses appear on the PCI bus and how Linux
 *		     wants to configure memory BARs of the PCI devices.
 *		     MSP7120 does nothing funky, so just set to zero.
 *
 *    io_resource  - address range pciauto() uses to assign to PCI device
 *		     I/O BARs.
 *
 *    io_offset	   - offset between how MSP7120 outbound PCI I/O
 *		     transaction addresses appear on the PCI bus and how
 *		     Linux defaults to configure I/O BARs of the PCI devices.
 *		     MSP7120 maps outbound I/O accesses into the bottom
 *		     bottom 4K of PCI address space (and ignores OATRAN).
 *		     Since the Linux default is to configure I/O BARs to the
 *		     bottom 4K, no special offset is needed. Just set to zero.
 *
 ****************************************************************************/
static struct pci_controller msp_pci_controller = {
	.pci_ops	= &msp_pci_ops,
	.mem_resource	= &pci_mem_resource,
	.mem_offset	= 0,
	.io_map_base	= MSP_PCI_IOSPACE_BASE,
	.io_resource	= &pci_io_resource,
	.io_offset	= 0
};

/*****************************************************************************
 *
 *  FUNCTION: msp_pci_init
 *  _________________________________________________________________________
 *
 *  DESCRIPTION: Initialize the PCI Host Controller and register it with
 *		 Linux so Linux can seize control of the PCI bus.
 *
 ****************************************************************************/
void __init msp_pci_init(void)
{
	struct msp_pci_regs *preg = (void *)PCI_BASE_REG;
	u32 id;

	/* Extract Device ID */
	id = read_reg32(PCI_JTAG_DEVID_REG, 0xFFFF) >> 12;

	/* Check if JTAG ID identifies MSP7120 */
	if (!MSP_HAS_PCI(id)) {
		printk(KERN_WARNING "PCI: No PCI; id reads as %x\n", id);
		goto no_pci;
	}

	/*
	 * Enable flushing of the PCI-SDRAM queue upon a read
	 * of the SDRAM's Memory Configuration Register.
	 */
	*(unsigned long *)QFLUSH_REG_1 = 3;

	/* Configure PCI Host Controller. */
	preg->if_status = ~0;		/* Clear cause register bits */
	preg->config_addr = 0;		/* Clear config access */
	preg->oatran	= MSP_PCI_OATRAN; /* PCI outbound addr translation */
	preg->if_mask	= 0xF8BF87C0;	/* Enable all PCI status interrupts */

	/* configure so inb(), outb(), and family are functional */
	set_io_port_base(MSP_PCI_IOSPACE_BASE);

	/* Tell Linux the details of the MSP7120 PCI Host Controller */
	register_pci_controller(&msp_pci_controller);

	return;

no_pci:
	/* Disable PCI channel */
	printk(KERN_WARNING "PCI: no host PCI bus detected\n");
}