Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * Copyright (C) 2004 Matthew Wilcox <matthew@wil.cx> | |
3 | * Copyright (C) 2004 Intel Corp. | |
4 | * | |
5 | * This code is released under the GNU General Public License version 2. | |
6 | */ | |
7 | ||
8 | /* | |
9 | * mmconfig.c - Low-level direct PCI config space access via MMCONFIG | |
10 | */ | |
11 | ||
12 | #include <linux/pci.h> | |
13 | #include <linux/init.h> | |
14 | #include "pci.h" | |
15 | ||
16 | /* The physical address of the MMCONFIG aperture. Set from ACPI tables. */ | |
17 | u32 pci_mmcfg_base_addr; | |
18 | ||
19 | #define mmcfg_virt_addr ((void __iomem *) fix_to_virt(FIX_PCIE_MCFG)) | |
20 | ||
21 | /* The base address of the last MMCONFIG device accessed */ | |
22 | static u32 mmcfg_last_accessed_device; | |
23 | ||
24 | /* | |
25 | * Functions for accessing PCI configuration space with MMCONFIG accesses | |
26 | */ | |
27 | ||
28 | static inline void pci_exp_set_dev_base(int bus, int devfn) | |
29 | { | |
30 | u32 dev_base = pci_mmcfg_base_addr | (bus << 20) | (devfn << 12); | |
31 | if (dev_base != mmcfg_last_accessed_device) { | |
32 | mmcfg_last_accessed_device = dev_base; | |
33 | set_fixmap_nocache(FIX_PCIE_MCFG, dev_base); | |
34 | } | |
35 | } | |
36 | ||
37 | static int pci_mmcfg_read(unsigned int seg, unsigned int bus, | |
38 | unsigned int devfn, int reg, int len, u32 *value) | |
39 | { | |
40 | unsigned long flags; | |
41 | ||
42 | if (!value || (bus > 255) || (devfn > 255) || (reg > 4095)) | |
43 | return -EINVAL; | |
44 | ||
45 | spin_lock_irqsave(&pci_config_lock, flags); | |
46 | ||
47 | pci_exp_set_dev_base(bus, devfn); | |
48 | ||
49 | switch (len) { | |
50 | case 1: | |
51 | *value = readb(mmcfg_virt_addr + reg); | |
52 | break; | |
53 | case 2: | |
54 | *value = readw(mmcfg_virt_addr + reg); | |
55 | break; | |
56 | case 4: | |
57 | *value = readl(mmcfg_virt_addr + reg); | |
58 | break; | |
59 | } | |
60 | ||
61 | spin_unlock_irqrestore(&pci_config_lock, flags); | |
62 | ||
63 | return 0; | |
64 | } | |
65 | ||
66 | static int pci_mmcfg_write(unsigned int seg, unsigned int bus, | |
67 | unsigned int devfn, int reg, int len, u32 value) | |
68 | { | |
69 | unsigned long flags; | |
70 | ||
71 | if ((bus > 255) || (devfn > 255) || (reg > 4095)) | |
72 | return -EINVAL; | |
73 | ||
74 | spin_lock_irqsave(&pci_config_lock, flags); | |
75 | ||
76 | pci_exp_set_dev_base(bus, devfn); | |
77 | ||
78 | switch (len) { | |
79 | case 1: | |
80 | writeb(value, mmcfg_virt_addr + reg); | |
81 | break; | |
82 | case 2: | |
83 | writew(value, mmcfg_virt_addr + reg); | |
84 | break; | |
85 | case 4: | |
86 | writel(value, mmcfg_virt_addr + reg); | |
87 | break; | |
88 | } | |
89 | ||
90 | spin_unlock_irqrestore(&pci_config_lock, flags); | |
91 | ||
92 | return 0; | |
93 | } | |
94 | ||
95 | static struct pci_raw_ops pci_mmcfg = { | |
96 | .read = pci_mmcfg_read, | |
97 | .write = pci_mmcfg_write, | |
98 | }; | |
99 | ||
100 | static int __init pci_mmcfg_init(void) | |
101 | { | |
102 | if ((pci_probe & PCI_PROBE_MMCONF) == 0) | |
103 | goto out; | |
104 | if (!pci_mmcfg_base_addr) | |
105 | goto out; | |
106 | ||
107 | /* Kludge for now. Don't use mmconfig on AMD systems because | |
108 | those have some busses where mmconfig doesn't work, | |
109 | and we don't parse ACPI MCFG well enough to handle that. | |
110 | Remove when proper handling is added. */ | |
111 | if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD) | |
112 | goto out; | |
113 | ||
114 | printk(KERN_INFO "PCI: Using MMCONFIG\n"); | |
115 | raw_pci_ops = &pci_mmcfg; | |
116 | pci_probe = (pci_probe & ~PCI_PROBE_MASK) | PCI_PROBE_MMCONF; | |
117 | ||
118 | out: | |
119 | return 0; | |
120 | } | |
121 | ||
122 | arch_initcall(pci_mmcfg_init); |