diff options
Diffstat (limited to 'arch/sparc/kernel/leon_pci.c')
-rw-r--r-- | arch/sparc/kernel/leon_pci.c | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/arch/sparc/kernel/leon_pci.c b/arch/sparc/kernel/leon_pci.c new file mode 100644 index 000000000000..a8a9a275037d --- /dev/null +++ b/arch/sparc/kernel/leon_pci.c | |||
@@ -0,0 +1,253 @@ | |||
1 | /* | ||
2 | * leon_pci.c: LEON Host PCI support | ||
3 | * | ||
4 | * Copyright (C) 2011 Aeroflex Gaisler AB, Daniel Hellstrom | ||
5 | * | ||
6 | * Code is partially derived from pcic.c | ||
7 | */ | ||
8 | |||
9 | #include <linux/of_device.h> | ||
10 | #include <linux/kernel.h> | ||
11 | #include <linux/pci.h> | ||
12 | #include <asm/leon.h> | ||
13 | #include <asm/leon_pci.h> | ||
14 | |||
15 | /* The LEON architecture does not rely on a BIOS or bootloader to setup | ||
16 | * PCI for us. The Linux generic routines are used to setup resources, | ||
17 | * reset values of confuration-space registers settings ae preseved. | ||
18 | */ | ||
19 | void leon_pci_init(struct platform_device *ofdev, struct leon_pci_info *info) | ||
20 | { | ||
21 | struct pci_bus *root_bus; | ||
22 | |||
23 | root_bus = pci_scan_bus_parented(&ofdev->dev, 0, info->ops, info); | ||
24 | if (root_bus) { | ||
25 | root_bus->resource[0] = &info->io_space; | ||
26 | root_bus->resource[1] = &info->mem_space; | ||
27 | root_bus->resource[2] = NULL; | ||
28 | |||
29 | /* Init all PCI devices into PCI tree */ | ||
30 | pci_bus_add_devices(root_bus); | ||
31 | |||
32 | /* Setup IRQs of all devices using custom routines */ | ||
33 | pci_fixup_irqs(pci_common_swizzle, info->map_irq); | ||
34 | |||
35 | /* Assign devices with resources */ | ||
36 | pci_assign_unassigned_resources(); | ||
37 | } | ||
38 | } | ||
39 | |||
40 | /* PCI Memory and Prefetchable Memory is direct-mapped. However I/O Space is | ||
41 | * accessed through a Window which is translated to low 64KB in PCI space, the | ||
42 | * first 4KB is not used so 60KB is available. | ||
43 | * | ||
44 | * This function is used by generic code to translate resource addresses into | ||
45 | * PCI addresses. | ||
46 | */ | ||
47 | void pcibios_resource_to_bus(struct pci_dev *dev, struct pci_bus_region *region, | ||
48 | struct resource *res) | ||
49 | { | ||
50 | struct leon_pci_info *info = dev->bus->sysdata; | ||
51 | |||
52 | region->start = res->start; | ||
53 | region->end = res->end; | ||
54 | |||
55 | if (res->flags & IORESOURCE_IO) { | ||
56 | region->start -= (info->io_space.start - 0x1000); | ||
57 | region->end -= (info->io_space.start - 0x1000); | ||
58 | } | ||
59 | } | ||
60 | EXPORT_SYMBOL(pcibios_resource_to_bus); | ||
61 | |||
62 | /* see pcibios_resource_to_bus() comment */ | ||
63 | void pcibios_bus_to_resource(struct pci_dev *dev, struct resource *res, | ||
64 | struct pci_bus_region *region) | ||
65 | { | ||
66 | struct leon_pci_info *info = dev->bus->sysdata; | ||
67 | |||
68 | res->start = region->start; | ||
69 | res->end = region->end; | ||
70 | |||
71 | if (res->flags & IORESOURCE_IO) { | ||
72 | res->start += (info->io_space.start - 0x1000); | ||
73 | res->end += (info->io_space.start - 0x1000); | ||
74 | } | ||
75 | } | ||
76 | EXPORT_SYMBOL(pcibios_bus_to_resource); | ||
77 | |||
78 | void __devinit pcibios_fixup_bus(struct pci_bus *pbus) | ||
79 | { | ||
80 | struct leon_pci_info *info = pbus->sysdata; | ||
81 | struct pci_dev *dev; | ||
82 | int i, has_io, has_mem; | ||
83 | u16 cmd; | ||
84 | |||
85 | /* Generic PCI bus probing sets these to point at | ||
86 | * &io{port,mem}_resouce which is wrong for us. | ||
87 | */ | ||
88 | if (pbus->self == NULL) { | ||
89 | pbus->resource[0] = &info->io_space; | ||
90 | pbus->resource[1] = &info->mem_space; | ||
91 | pbus->resource[2] = NULL; | ||
92 | } | ||
93 | |||
94 | list_for_each_entry(dev, &pbus->devices, bus_list) { | ||
95 | /* | ||
96 | * We can not rely on that the bootloader has enabled I/O | ||
97 | * or memory access to PCI devices. Instead we enable it here | ||
98 | * if the device has BARs of respective type. | ||
99 | */ | ||
100 | has_io = has_mem = 0; | ||
101 | for (i = 0; i < PCI_ROM_RESOURCE; i++) { | ||
102 | unsigned long f = dev->resource[i].flags; | ||
103 | if (f & IORESOURCE_IO) | ||
104 | has_io = 1; | ||
105 | else if (f & IORESOURCE_MEM) | ||
106 | has_mem = 1; | ||
107 | } | ||
108 | /* ROM BARs are mapped into 32-bit memory space */ | ||
109 | if (dev->resource[PCI_ROM_RESOURCE].end != 0) { | ||
110 | dev->resource[PCI_ROM_RESOURCE].flags |= | ||
111 | IORESOURCE_ROM_ENABLE; | ||
112 | has_mem = 1; | ||
113 | } | ||
114 | pci_bus_read_config_word(pbus, dev->devfn, PCI_COMMAND, &cmd); | ||
115 | if (has_io && !(cmd & PCI_COMMAND_IO)) { | ||
116 | #ifdef CONFIG_PCI_DEBUG | ||
117 | printk(KERN_INFO "LEONPCI: Enabling I/O for dev %s\n", | ||
118 | pci_name(dev)); | ||
119 | #endif | ||
120 | cmd |= PCI_COMMAND_IO; | ||
121 | pci_bus_write_config_word(pbus, dev->devfn, PCI_COMMAND, | ||
122 | cmd); | ||
123 | } | ||
124 | if (has_mem && !(cmd & PCI_COMMAND_MEMORY)) { | ||
125 | #ifdef CONFIG_PCI_DEBUG | ||
126 | printk(KERN_INFO "LEONPCI: Enabling MEMORY for dev" | ||
127 | "%s\n", pci_name(dev)); | ||
128 | #endif | ||
129 | cmd |= PCI_COMMAND_MEMORY; | ||
130 | pci_bus_write_config_word(pbus, dev->devfn, PCI_COMMAND, | ||
131 | cmd); | ||
132 | } | ||
133 | } | ||
134 | } | ||
135 | |||
136 | /* | ||
137 | * Other archs parse arguments here. | ||
138 | */ | ||
139 | char * __devinit pcibios_setup(char *str) | ||
140 | { | ||
141 | return str; | ||
142 | } | ||
143 | |||
144 | resource_size_t pcibios_align_resource(void *data, const struct resource *res, | ||
145 | resource_size_t size, resource_size_t align) | ||
146 | { | ||
147 | return res->start; | ||
148 | } | ||
149 | |||
150 | int pcibios_enable_device(struct pci_dev *dev, int mask) | ||
151 | { | ||
152 | return pci_enable_resources(dev, mask); | ||
153 | } | ||
154 | |||
155 | struct device_node *pci_device_to_OF_node(struct pci_dev *pdev) | ||
156 | { | ||
157 | /* | ||
158 | * Currently the OpenBoot nodes are not connected with the PCI device, | ||
159 | * this is because the LEON PROM does not create PCI nodes. Eventually | ||
160 | * this will change and the same approach as pcic.c can be used to | ||
161 | * match PROM nodes with pci devices. | ||
162 | */ | ||
163 | return NULL; | ||
164 | } | ||
165 | EXPORT_SYMBOL(pci_device_to_OF_node); | ||
166 | |||
167 | void __devinit pcibios_update_irq(struct pci_dev *dev, int irq) | ||
168 | { | ||
169 | #ifdef CONFIG_PCI_DEBUG | ||
170 | printk(KERN_DEBUG "LEONPCI: Assigning IRQ %02d to %s\n", irq, | ||
171 | pci_name(dev)); | ||
172 | #endif | ||
173 | pci_write_config_byte(dev, PCI_INTERRUPT_LINE, irq); | ||
174 | } | ||
175 | |||
176 | /* in/out routines taken from pcic.c | ||
177 | * | ||
178 | * This probably belongs here rather than ioport.c because | ||
179 | * we do not want this crud linked into SBus kernels. | ||
180 | * Also, think for a moment about likes of floppy.c that | ||
181 | * include architecture specific parts. They may want to redefine ins/outs. | ||
182 | * | ||
183 | * We do not use horrible macros here because we want to | ||
184 | * advance pointer by sizeof(size). | ||
185 | */ | ||
186 | void outsb(unsigned long addr, const void *src, unsigned long count) | ||
187 | { | ||
188 | while (count) { | ||
189 | count -= 1; | ||
190 | outb(*(const char *)src, addr); | ||
191 | src += 1; | ||
192 | /* addr += 1; */ | ||
193 | } | ||
194 | } | ||
195 | EXPORT_SYMBOL(outsb); | ||
196 | |||
197 | void outsw(unsigned long addr, const void *src, unsigned long count) | ||
198 | { | ||
199 | while (count) { | ||
200 | count -= 2; | ||
201 | outw(*(const short *)src, addr); | ||
202 | src += 2; | ||
203 | /* addr += 2; */ | ||
204 | } | ||
205 | } | ||
206 | EXPORT_SYMBOL(outsw); | ||
207 | |||
208 | void outsl(unsigned long addr, const void *src, unsigned long count) | ||
209 | { | ||
210 | while (count) { | ||
211 | count -= 4; | ||
212 | outl(*(const long *)src, addr); | ||
213 | src += 4; | ||
214 | /* addr += 4; */ | ||
215 | } | ||
216 | } | ||
217 | EXPORT_SYMBOL(outsl); | ||
218 | |||
219 | void insb(unsigned long addr, void *dst, unsigned long count) | ||
220 | { | ||
221 | while (count) { | ||
222 | count -= 1; | ||
223 | *(unsigned char *)dst = inb(addr); | ||
224 | dst += 1; | ||
225 | /* addr += 1; */ | ||
226 | } | ||
227 | } | ||
228 | EXPORT_SYMBOL(insb); | ||
229 | |||
230 | void insw(unsigned long addr, void *dst, unsigned long count) | ||
231 | { | ||
232 | while (count) { | ||
233 | count -= 2; | ||
234 | *(unsigned short *)dst = inw(addr); | ||
235 | dst += 2; | ||
236 | /* addr += 2; */ | ||
237 | } | ||
238 | } | ||
239 | EXPORT_SYMBOL(insw); | ||
240 | |||
241 | void insl(unsigned long addr, void *dst, unsigned long count) | ||
242 | { | ||
243 | while (count) { | ||
244 | count -= 4; | ||
245 | /* | ||
246 | * XXX I am sure we are in for an unaligned trap here. | ||
247 | */ | ||
248 | *(unsigned long *)dst = inl(addr); | ||
249 | dst += 4; | ||
250 | /* addr += 4; */ | ||
251 | } | ||
252 | } | ||
253 | EXPORT_SYMBOL(insl); | ||