1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | #include <linux/types.h> |
3 | #include <linux/ioport.h> |
4 | #include <linux/slab.h> |
5 | #include <linux/export.h> |
6 | #include <linux/io.h> |
7 | #include <linux/mcb.h> |
8 | |
9 | #include "mcb-internal.h" |
10 | |
11 | struct mcb_parse_priv { |
12 | phys_addr_t mapbase; |
13 | void __iomem *base; |
14 | }; |
15 | |
16 | #define for_each_chameleon_cell(dtype, p) \ |
17 | for ((dtype) = get_next_dtype((p)); \ |
18 | (dtype) != CHAMELEON_DTYPE_END; \ |
19 | (dtype) = get_next_dtype((p))) |
20 | |
21 | static inline uint32_t get_next_dtype(void __iomem *p) |
22 | { |
23 | uint32_t dtype; |
24 | |
25 | dtype = readl(addr: p); |
26 | return dtype >> 28; |
27 | } |
28 | |
29 | static int chameleon_parse_bdd(struct mcb_bus *bus, |
30 | struct chameleon_bar *cb, |
31 | void __iomem *base) |
32 | { |
33 | return 0; |
34 | } |
35 | |
36 | static int chameleon_parse_gdd(struct mcb_bus *bus, |
37 | struct chameleon_bar *cb, |
38 | void __iomem *base, int bar_count) |
39 | { |
40 | struct chameleon_gdd __iomem *gdd = |
41 | (struct chameleon_gdd __iomem *) base; |
42 | struct mcb_device *mdev; |
43 | u32 dev_mapbase; |
44 | u32 offset; |
45 | u32 size; |
46 | int ret; |
47 | __le32 reg1; |
48 | __le32 reg2; |
49 | |
50 | mdev = mcb_alloc_dev(bus); |
51 | if (!mdev) |
52 | return -ENOMEM; |
53 | |
54 | reg1 = readl(addr: &gdd->reg1); |
55 | reg2 = readl(addr: &gdd->reg2); |
56 | offset = readl(addr: &gdd->offset); |
57 | size = readl(addr: &gdd->size); |
58 | |
59 | mdev->id = GDD_DEV(reg1); |
60 | mdev->rev = GDD_REV(reg1); |
61 | mdev->var = GDD_VAR(reg1); |
62 | mdev->bar = GDD_BAR(reg2); |
63 | mdev->group = GDD_GRP(reg2); |
64 | mdev->inst = GDD_INS(reg2); |
65 | |
66 | /* |
67 | * If the BAR is missing, dev_mapbase is zero, or if the |
68 | * device is IO mapped we just print a warning and go on with the |
69 | * next device, instead of completely stop the gdd parser |
70 | */ |
71 | if (mdev->bar > bar_count - 1) { |
72 | pr_info("No BAR for 16z%03d\n" , mdev->id); |
73 | ret = 0; |
74 | goto err; |
75 | } |
76 | |
77 | dev_mapbase = cb[mdev->bar].addr; |
78 | if (!dev_mapbase) { |
79 | pr_info("BAR not assigned for 16z%03d\n" , mdev->id); |
80 | ret = 0; |
81 | goto err; |
82 | } |
83 | |
84 | if (dev_mapbase & 0x01) { |
85 | pr_info("IO mapped Device (16z%03d) not yet supported\n" , |
86 | mdev->id); |
87 | ret = 0; |
88 | goto err; |
89 | } |
90 | |
91 | pr_debug("Found a 16z%03d\n" , mdev->id); |
92 | |
93 | mdev->irq.start = GDD_IRQ(reg1); |
94 | mdev->irq.end = GDD_IRQ(reg1); |
95 | mdev->irq.flags = IORESOURCE_IRQ; |
96 | |
97 | mdev->mem.start = dev_mapbase + offset; |
98 | |
99 | mdev->mem.end = mdev->mem.start + size - 1; |
100 | mdev->mem.flags = IORESOURCE_MEM; |
101 | |
102 | ret = mcb_device_register(bus, dev: mdev); |
103 | if (ret < 0) |
104 | goto err; |
105 | |
106 | return 0; |
107 | |
108 | err: |
109 | mcb_free_dev(dev: mdev); |
110 | |
111 | return ret; |
112 | } |
113 | |
114 | static void chameleon_parse_bar(void __iomem *base, |
115 | struct chameleon_bar *cb, int bar_count) |
116 | { |
117 | char __iomem *p = base; |
118 | int i; |
119 | |
120 | /* skip reg1 */ |
121 | p += sizeof(__le32); |
122 | |
123 | for (i = 0; i < bar_count; i++) { |
124 | cb[i].addr = readl(addr: p); |
125 | cb[i].size = readl(addr: p + 4); |
126 | |
127 | p += sizeof(struct chameleon_bar); |
128 | } |
129 | } |
130 | |
131 | static int chameleon_get_bar(void __iomem **base, phys_addr_t mapbase, |
132 | struct chameleon_bar **cb) |
133 | { |
134 | struct chameleon_bar *c; |
135 | int bar_count; |
136 | __le32 reg; |
137 | u32 dtype; |
138 | |
139 | /* |
140 | * For those devices which are not connected |
141 | * to the PCI Bus (e.g. LPC) there is a bar |
142 | * descriptor located directly after the |
143 | * chameleon header. This header is comparable |
144 | * to a PCI header. |
145 | */ |
146 | dtype = get_next_dtype(p: *base); |
147 | if (dtype == CHAMELEON_DTYPE_BAR) { |
148 | reg = readl(addr: *base); |
149 | |
150 | bar_count = BAR_CNT(reg); |
151 | if (bar_count <= 0 || bar_count > CHAMELEON_BAR_MAX) |
152 | return -ENODEV; |
153 | |
154 | c = kcalloc(n: bar_count, size: sizeof(struct chameleon_bar), |
155 | GFP_KERNEL); |
156 | if (!c) |
157 | return -ENOMEM; |
158 | |
159 | chameleon_parse_bar(base: *base, cb: c, bar_count); |
160 | *base += BAR_DESC_SIZE(bar_count); |
161 | } else { |
162 | c = kzalloc(size: sizeof(struct chameleon_bar), GFP_KERNEL); |
163 | if (!c) |
164 | return -ENOMEM; |
165 | |
166 | bar_count = 1; |
167 | c->addr = mapbase; |
168 | } |
169 | |
170 | *cb = c; |
171 | |
172 | return bar_count; |
173 | } |
174 | |
175 | int chameleon_parse_cells(struct mcb_bus *bus, phys_addr_t mapbase, |
176 | void __iomem *base) |
177 | { |
178 | struct chameleon_fpga_header *; |
179 | struct chameleon_bar *cb; |
180 | void __iomem *p = base; |
181 | int num_cells = 0; |
182 | uint32_t dtype; |
183 | int bar_count; |
184 | int ret; |
185 | u32 hsize; |
186 | u32 table_size; |
187 | |
188 | hsize = sizeof(struct chameleon_fpga_header); |
189 | |
190 | header = kzalloc(size: hsize, GFP_KERNEL); |
191 | if (!header) |
192 | return -ENOMEM; |
193 | |
194 | /* Extract header information */ |
195 | memcpy_fromio(header, p, hsize); |
196 | /* We only support chameleon v2 at the moment */ |
197 | header->magic = le16_to_cpu(header->magic); |
198 | if (header->magic != CHAMELEONV2_MAGIC) { |
199 | pr_err("Unsupported chameleon version 0x%x\n" , |
200 | header->magic); |
201 | ret = -ENODEV; |
202 | goto free_header; |
203 | } |
204 | p += hsize; |
205 | |
206 | bus->revision = header->revision; |
207 | bus->model = header->model; |
208 | bus->minor = header->minor; |
209 | snprintf(buf: bus->name, CHAMELEON_FILENAME_LEN + 1, fmt: "%s" , |
210 | header->filename); |
211 | |
212 | bar_count = chameleon_get_bar(base: &p, mapbase, cb: &cb); |
213 | if (bar_count < 0) { |
214 | ret = bar_count; |
215 | goto free_header; |
216 | } |
217 | |
218 | for_each_chameleon_cell(dtype, p) { |
219 | switch (dtype) { |
220 | case CHAMELEON_DTYPE_GENERAL: |
221 | ret = chameleon_parse_gdd(bus, cb, base: p, bar_count); |
222 | if (ret < 0) |
223 | goto free_bar; |
224 | p += sizeof(struct chameleon_gdd); |
225 | break; |
226 | case CHAMELEON_DTYPE_BRIDGE: |
227 | chameleon_parse_bdd(bus, cb, base: p); |
228 | p += sizeof(struct chameleon_bdd); |
229 | break; |
230 | case CHAMELEON_DTYPE_END: |
231 | break; |
232 | default: |
233 | pr_err("Invalid chameleon descriptor type 0x%x\n" , |
234 | dtype); |
235 | ret = -EINVAL; |
236 | goto free_bar; |
237 | } |
238 | num_cells++; |
239 | } |
240 | |
241 | if (num_cells == 0) { |
242 | ret = -EINVAL; |
243 | goto free_bar; |
244 | } |
245 | |
246 | table_size = p - base; |
247 | pr_debug("%d cell(s) found. Chameleon table size: 0x%04x bytes\n" , num_cells, table_size); |
248 | kfree(objp: cb); |
249 | kfree(objp: header); |
250 | return table_size; |
251 | |
252 | free_bar: |
253 | kfree(objp: cb); |
254 | : |
255 | kfree(objp: header); |
256 | |
257 | return ret; |
258 | } |
259 | EXPORT_SYMBOL_NS_GPL(chameleon_parse_cells, MCB); |
260 | |