1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Phytium PCIE host driver
4  *
5  * Heavily based on drivers/pci/pcie_xilinx.c
6  *
7  * Copyright (C) 2019
8  */
9 
10 #include <common.h>
11 #include <dm.h>
12 #include <pci.h>
13 #include <asm/global_data.h>
14 #include <asm/io.h>
15 
16 /**
17  * struct phytium_pcie - phytium PCIe controller state
18  * @cfg_base: The base address of memory mapped configuration space
19  */
20 struct phytium_pcie {
21 	void *cfg_base;
22 };
23 
24 /*
25  * phytium_pci_skip_dev()
26  * @parent: Identifies the PCIe device to access
27  *
28  * Checks whether the parent of the PCIe device is bridge
29  *
30  * Return: true if it is bridge, else false.
31  */
phytium_pci_skip_dev(pci_dev_t parent)32 static int phytium_pci_skip_dev(pci_dev_t parent)
33 {
34 	unsigned char pos, id;
35 	unsigned long addr = 0x40000000;
36 	unsigned short capreg;
37 	unsigned char port_type;
38 
39 	addr += PCIE_ECAM_OFFSET(PCI_BUS(parent), PCI_DEV(parent), PCI_FUNC(parent), 0);
40 
41 	pos = 0x34;
42 	while (1) {
43 		pos = readb(addr + pos);
44 		if (pos < 0x40)
45 			break;
46 		pos &= ~3;
47 		id = readb(addr + pos);
48 		if (id == 0xff)
49 			break;
50 		if (id == 0x10) {
51 			capreg = readw(addr + pos + 2);
52 			port_type = (capreg >> 4) & 0xf;
53 			if (port_type == 0x6 || port_type == 0x4)
54 				return 1;
55 			else
56 				return 0;
57 		}
58 		pos += 1;
59 	}
60 	return 0;
61 }
62 
63 /**
64  * pci_phytium_conf_address() - Calculate the address of a config access
65  * @bus: Pointer to the PCI bus
66  * @bdf: Identifies the PCIe device to access
67  * @offset: The offset into the device's configuration space
68  * @paddress: Pointer to the pointer to write the calculates address to
69  *
70  * Calculates the address that should be accessed to perform a PCIe
71  * configuration space access for a given device identified by the PCIe
72  * controller device @pcie and the bus, device & function numbers in @bdf. If
73  * access to the device is not valid then the function will return an error
74  * code. Otherwise the address to access will be written to the pointer pointed
75  * to by @paddress.
76  */
pci_phytium_conf_address(const struct udevice * bus,pci_dev_t bdf,uint offset,void ** paddress)77 static int pci_phytium_conf_address(const struct udevice *bus, pci_dev_t bdf,
78 				    uint offset, void **paddress)
79 {
80 	struct phytium_pcie *pcie = dev_get_priv(bus);
81 	void *addr;
82 	pci_dev_t bdf_parent;
83 
84 	unsigned int bus_no = PCI_BUS(bdf);
85 	unsigned int dev_no = PCI_DEV(bdf);
86 
87 	bdf_parent = PCI_BDF((bus_no - 1), 0, 0);
88 
89 	addr = pcie->cfg_base;
90 	addr += PCIE_ECAM_OFFSET(PCI_BUS(bdf), PCI_DEV(bdf), PCI_FUNC(bdf), 0);
91 
92 	if (bus_no > 0 && dev_no > 0) {
93 		if ((readb(addr + PCI_HEADER_TYPE) & 0x7f) !=
94 				PCI_HEADER_TYPE_BRIDGE)
95 			return -ENODEV;
96 		if (phytium_pci_skip_dev(bdf_parent))
97 			return -ENODEV;
98 	}
99 
100 	addr += offset;
101 	*paddress = addr;
102 
103 	return 0;
104 }
105 
106 /**
107  * pci_phytium_read_config() - Read from configuration space
108  * @bus: Pointer to the PCI bus
109  * @bdf: Identifies the PCIe device to access
110  * @offset: The offset into the device's configuration space
111  * @valuep: A pointer at which to store the read value
112  * @size: Indicates the size of access to perform
113  *
114  * Read a value of size @size from offset @offset within the configuration
115  * space of the device identified by the bus, device & function numbers in @bdf
116  * on the PCI bus @bus.
117  */
pci_phytium_read_config(const struct udevice * bus,pci_dev_t bdf,uint offset,ulong * valuep,enum pci_size_t size)118 static int pci_phytium_read_config(const struct udevice *bus, pci_dev_t bdf,
119 				   uint offset, ulong *valuep,
120 				   enum pci_size_t size)
121 {
122 	return pci_generic_mmap_read_config(bus, pci_phytium_conf_address,
123 					    bdf, offset, valuep, size);
124 }
125 
126 /**
127  * pci_phytium_write_config() - Write to configuration space
128  * @bus: Pointer to the PCI bus
129  * @bdf: Identifies the PCIe device to access
130  * @offset: The offset into the device's configuration space
131  * @value: The value to write
132  * @size: Indicates the size of access to perform
133  *
134  * Write the value @value of size @size from offset @offset within the
135  * configuration space of the device identified by the bus, device & function
136  * numbers in @bdf on the PCI bus @bus.
137  */
pci_phytium_write_config(struct udevice * bus,pci_dev_t bdf,uint offset,ulong value,enum pci_size_t size)138 static int pci_phytium_write_config(struct udevice *bus, pci_dev_t bdf,
139 				    uint offset, ulong value,
140 				    enum pci_size_t size)
141 {
142 	return pci_generic_mmap_write_config(bus, pci_phytium_conf_address,
143 					     bdf, offset, value, size);
144 }
145 
146 /**
147  * pci_phytium_of_to_plat() - Translate from DT to device state
148  * @dev: A pointer to the device being operated on
149  *
150  * Translate relevant data from the device tree pertaining to device @dev into
151  * state that the driver will later make use of. This state is stored in the
152  * device's private data structure.
153  *
154  * Return: 0 on success, else -EINVAL
155  */
pci_phytium_of_to_plat(struct udevice * dev)156 static int pci_phytium_of_to_plat(struct udevice *dev)
157 {
158 	struct phytium_pcie *pcie = dev_get_priv(dev);
159 	struct fdt_resource reg_res;
160 
161 	DECLARE_GLOBAL_DATA_PTR;
162 
163 	int err;
164 
165 	err = fdt_get_resource(gd->fdt_blob, dev_of_offset(dev), "reg",
166 			       0, &reg_res);
167 	if (err < 0) {
168 		pr_err("\"reg\" resource not found\n");
169 		return err;
170 	}
171 
172 	pcie->cfg_base = map_physmem(reg_res.start,
173 				     fdt_resource_size(&reg_res),
174 				     MAP_NOCACHE);
175 
176 	return 0;
177 }
178 
179 static const struct dm_pci_ops pci_phytium_ops = {
180 	.read_config	= pci_phytium_read_config,
181 	.write_config	= pci_phytium_write_config,
182 };
183 
184 static const struct udevice_id pci_phytium_ids[] = {
185 	{ .compatible = "phytium,pcie-host-1.0" },
186 	{ }
187 };
188 
189 U_BOOT_DRIVER(pci_phytium) = {
190 	.name			= "pci_phytium",
191 	.id			= UCLASS_PCI,
192 	.of_match		= pci_phytium_ids,
193 	.ops			= &pci_phytium_ops,
194 	.of_to_plat	= pci_phytium_of_to_plat,
195 	.priv_auto	= sizeof(struct phytium_pcie),
196 };
197