1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Support for indirect PCI bridges.
4 *
5 * Copyright (C) 1998 Gabriel Paubert.
6 */
7
8#include <linux/kernel.h>
9#include <linux/pci.h>
10#include <linux/delay.h>
11#include <linux/string.h>
12#include <linux/init.h>
13
14#include <linux/io.h>
15#include <asm/pci-bridge.h>
16
17static int
18indirect_read_config(struct pci_bus *bus, unsigned int devfn, int offset,
19		     int len, u32 *val)
20{
21	struct pci_controller *hose = pci_bus_to_host(bus);
22	volatile void __iomem *cfg_data;
23	u8 cfg_type = 0;
24	u32 bus_no, reg;
25
26	if (hose->indirect_type & INDIRECT_TYPE_NO_PCIE_LINK) {
27		if (bus->number != hose->first_busno)
28			return PCIBIOS_DEVICE_NOT_FOUND;
29		if (devfn != 0)
30			return PCIBIOS_DEVICE_NOT_FOUND;
31	}
32
33	if (hose->indirect_type & INDIRECT_TYPE_SET_CFG_TYPE)
34		if (bus->number != hose->first_busno)
35			cfg_type = 1;
36
37	bus_no = (bus->number == hose->first_busno) ?
38			hose->self_busno : bus->number;
39
40	if (hose->indirect_type & INDIRECT_TYPE_EXT_REG)
41		reg = ((offset & 0xf00) << 16) | (offset & 0xfc);
42	else
43		reg = offset & 0xfc; /* Only 3 bits for function */
44
45	if (hose->indirect_type & INDIRECT_TYPE_BIG_ENDIAN)
46		out_be32(hose->cfg_addr, (0x80000000 | (bus_no << 16) |
47			 (devfn << 8) | reg | cfg_type));
48	else
49		out_le32(hose->cfg_addr, (0x80000000 | (bus_no << 16) |
50			 (devfn << 8) | reg | cfg_type));
51
52	/*
53	 * Note: the caller has already checked that offset is
54	 * suitably aligned and that len is 1, 2 or 4.
55	 */
56	cfg_data = hose->cfg_data + (offset & 3); /* Only 3 bits for function */
57	switch (len) {
58	case 1:
59		*val = in_8(cfg_data);
60		break;
61	case 2:
62		*val = in_le16(cfg_data);
63		break;
64	default:
65		*val = in_le32(cfg_data);
66		break;
67	}
68	return PCIBIOS_SUCCESSFUL;
69}
70
71static int
72indirect_write_config(struct pci_bus *bus, unsigned int devfn, int offset,
73		      int len, u32 val)
74{
75	struct pci_controller *hose = pci_bus_to_host(bus);
76	volatile void __iomem *cfg_data;
77	u8 cfg_type = 0;
78	u32 bus_no, reg;
79
80	if (hose->indirect_type & INDIRECT_TYPE_NO_PCIE_LINK) {
81		if (bus->number != hose->first_busno)
82			return PCIBIOS_DEVICE_NOT_FOUND;
83		if (devfn != 0)
84			return PCIBIOS_DEVICE_NOT_FOUND;
85	}
86
87	if (hose->indirect_type & INDIRECT_TYPE_SET_CFG_TYPE)
88		if (bus->number != hose->first_busno)
89			cfg_type = 1;
90
91	bus_no = (bus->number == hose->first_busno) ?
92			hose->self_busno : bus->number;
93
94	if (hose->indirect_type & INDIRECT_TYPE_EXT_REG)
95		reg = ((offset & 0xf00) << 16) | (offset & 0xfc);
96	else
97		reg = offset & 0xfc;
98
99	if (hose->indirect_type & INDIRECT_TYPE_BIG_ENDIAN)
100		out_be32(hose->cfg_addr, (0x80000000 | (bus_no << 16) |
101			 (devfn << 8) | reg | cfg_type));
102	else
103		out_le32(hose->cfg_addr, (0x80000000 | (bus_no << 16) |
104			 (devfn << 8) | reg | cfg_type));
105
106	/* suppress setting of PCI_PRIMARY_BUS */
107	if (hose->indirect_type & INDIRECT_TYPE_SURPRESS_PRIMARY_BUS)
108		if ((offset == PCI_PRIMARY_BUS) &&
109			(bus->number == hose->first_busno))
110			val &= 0xffffff00;
111
112	/* Workaround for PCI_28 Errata in 440EPx/GRx */
113	if ((hose->indirect_type & INDIRECT_TYPE_BROKEN_MRM) &&
114			offset == PCI_CACHE_LINE_SIZE) {
115		val = 0;
116	}
117
118	/*
119	 * Note: the caller has already checked that offset is
120	 * suitably aligned and that len is 1, 2 or 4.
121	 */
122	cfg_data = hose->cfg_data + (offset & 3);
123	switch (len) {
124	case 1:
125		out_8(cfg_data, val);
126		break;
127	case 2:
128		out_le16(cfg_data, val);
129		break;
130	default:
131		out_le32(cfg_data, val);
132		break;
133	}
134
135	return PCIBIOS_SUCCESSFUL;
136}
137
138static struct pci_ops indirect_pci_ops = {
139	.read = indirect_read_config,
140	.write = indirect_write_config,
141};
142
143void __init
144setup_indirect_pci(struct pci_controller *hose,
145		   resource_size_t cfg_addr,
146		   resource_size_t cfg_data, u32 flags)
147{
148	resource_size_t base = cfg_addr & PAGE_MASK;
149	void __iomem *mbase;
150
151	mbase = ioremap(base, PAGE_SIZE);
152	hose->cfg_addr = mbase + (cfg_addr & ~PAGE_MASK);
153	if ((cfg_data & PAGE_MASK) != base)
154		mbase = ioremap(cfg_data & PAGE_MASK, PAGE_SIZE);
155	hose->cfg_data = mbase + (cfg_data & ~PAGE_MASK);
156	hose->ops = &indirect_pci_ops;
157	hose->indirect_type = flags;
158}
159