]>
Commit | Line | Data |
---|---|---|
4319c7f7 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
77864f2e AR |
2 | /* |
3 | * Copyright (C) 2012 CERN (www.cern.ch) | |
4 | * Author: Alessandro Rubini <rubini@gnudd.com> | |
5 | * | |
77864f2e AR |
6 | * This work is part of the White Rabbit project, a research effort led |
7 | * by CERN, the European Institute for Nuclear Research. | |
8 | */ | |
9 | #include <linux/module.h> | |
10 | #include <linux/slab.h> | |
11 | #include <linux/fmc.h> | |
12 | #include <linux/sdb.h> | |
13 | #include <linux/err.h> | |
14 | #include <linux/fmc-sdb.h> | |
15 | #include <asm/byteorder.h> | |
16 | ||
17 | static uint32_t __sdb_rd(struct fmc_device *fmc, unsigned long address, | |
18 | int convert) | |
19 | { | |
20 | uint32_t res = fmc_readl(fmc, address); | |
21 | if (convert) | |
22 | return __be32_to_cpu(res); | |
23 | return res; | |
24 | } | |
25 | ||
26 | static struct sdb_array *__fmc_scan_sdb_tree(struct fmc_device *fmc, | |
27 | unsigned long sdb_addr, | |
28 | unsigned long reg_base, int level) | |
29 | { | |
30 | uint32_t onew; | |
31 | int i, j, n, convert = 0; | |
32 | struct sdb_array *arr, *sub; | |
33 | ||
34 | onew = fmc_readl(fmc, sdb_addr); | |
35 | if (onew == SDB_MAGIC) { | |
36 | /* Uh! If we are little-endian, we must convert */ | |
37 | if (SDB_MAGIC != __be32_to_cpu(SDB_MAGIC)) | |
38 | convert = 1; | |
39 | } else if (onew == __be32_to_cpu(SDB_MAGIC)) { | |
40 | /* ok, don't convert */ | |
41 | } else { | |
42 | return ERR_PTR(-ENOENT); | |
43 | } | |
44 | /* So, the magic was there: get the count from offset 4*/ | |
45 | onew = __sdb_rd(fmc, sdb_addr + 4, convert); | |
46 | n = __be16_to_cpu(*(uint16_t *)&onew); | |
47 | arr = kzalloc(sizeof(*arr), GFP_KERNEL); | |
e42d50ba DC |
48 | if (!arr) |
49 | return ERR_PTR(-ENOMEM); | |
6396bb22 KC |
50 | arr->record = kcalloc(n, sizeof(arr->record[0]), GFP_KERNEL); |
51 | arr->subtree = kcalloc(n, sizeof(arr->subtree[0]), GFP_KERNEL); | |
e42d50ba | 52 | if (!arr->record || !arr->subtree) { |
77864f2e AR |
53 | kfree(arr->record); |
54 | kfree(arr->subtree); | |
55 | kfree(arr); | |
56 | return ERR_PTR(-ENOMEM); | |
57 | } | |
e42d50ba | 58 | |
77864f2e AR |
59 | arr->len = n; |
60 | arr->level = level; | |
61 | arr->fmc = fmc; | |
62 | for (i = 0; i < n; i++) { | |
63 | union sdb_record *r; | |
64 | ||
65 | for (j = 0; j < sizeof(arr->record[0]); j += 4) { | |
66 | *(uint32_t *)((void *)(arr->record + i) + j) = | |
67 | __sdb_rd(fmc, sdb_addr + (i * 64) + j, convert); | |
68 | } | |
69 | r = &arr->record[i]; | |
70 | arr->subtree[i] = ERR_PTR(-ENODEV); | |
71 | if (r->empty.record_type == sdb_type_bridge) { | |
72 | struct sdb_component *c = &r->bridge.sdb_component; | |
73 | uint64_t subaddr = __be64_to_cpu(r->bridge.sdb_child); | |
74 | uint64_t newbase = __be64_to_cpu(c->addr_first); | |
75 | ||
76 | subaddr += reg_base; | |
77 | newbase += reg_base; | |
78 | sub = __fmc_scan_sdb_tree(fmc, subaddr, newbase, | |
79 | level + 1); | |
80 | arr->subtree[i] = sub; /* may be error */ | |
81 | if (IS_ERR(sub)) | |
82 | continue; | |
83 | sub->parent = arr; | |
84 | sub->baseaddr = newbase; | |
85 | } | |
86 | } | |
87 | return arr; | |
88 | } | |
89 | ||
90 | int fmc_scan_sdb_tree(struct fmc_device *fmc, unsigned long address) | |
91 | { | |
92 | struct sdb_array *ret; | |
93 | if (fmc->sdb) | |
94 | return -EBUSY; | |
95 | ret = __fmc_scan_sdb_tree(fmc, address, 0 /* regs */, 0); | |
96 | if (IS_ERR(ret)) | |
97 | return PTR_ERR(ret); | |
98 | fmc->sdb = ret; | |
99 | return 0; | |
100 | } | |
101 | EXPORT_SYMBOL(fmc_scan_sdb_tree); | |
102 | ||
103 | static void __fmc_sdb_free(struct sdb_array *arr) | |
104 | { | |
105 | int i, n; | |
106 | ||
107 | if (!arr) | |
108 | return; | |
109 | n = arr->len; | |
110 | for (i = 0; i < n; i++) { | |
111 | if (IS_ERR(arr->subtree[i])) | |
112 | continue; | |
113 | __fmc_sdb_free(arr->subtree[i]); | |
114 | } | |
115 | kfree(arr->record); | |
116 | kfree(arr->subtree); | |
117 | kfree(arr); | |
118 | } | |
119 | ||
120 | int fmc_free_sdb_tree(struct fmc_device *fmc) | |
121 | { | |
122 | __fmc_sdb_free(fmc->sdb); | |
123 | fmc->sdb = NULL; | |
124 | return 0; | |
125 | } | |
126 | EXPORT_SYMBOL(fmc_free_sdb_tree); | |
127 | ||
9c0dda14 FV |
128 | /* This helper calls reprogram and inizialized sdb as well */ |
129 | int fmc_reprogram_raw(struct fmc_device *fmc, struct fmc_driver *d, | |
130 | void *gw, unsigned long len, int sdb_entry) | |
131 | { | |
132 | int ret; | |
133 | ||
134 | ret = fmc->op->reprogram_raw(fmc, d, gw, len); | |
135 | if (ret < 0) | |
136 | return ret; | |
137 | if (sdb_entry < 0) | |
138 | return ret; | |
139 | ||
140 | /* We are required to find SDB at a given offset */ | |
141 | ret = fmc_scan_sdb_tree(fmc, sdb_entry); | |
142 | if (ret < 0) { | |
143 | dev_err(&fmc->dev, "Can't find SDB at address 0x%x\n", | |
144 | sdb_entry); | |
145 | return -ENODEV; | |
146 | } | |
147 | ||
148 | return 0; | |
149 | } | |
150 | EXPORT_SYMBOL(fmc_reprogram_raw); | |
151 | ||
77864f2e AR |
152 | /* This helper calls reprogram and inizialized sdb as well */ |
153 | int fmc_reprogram(struct fmc_device *fmc, struct fmc_driver *d, char *gw, | |
154 | int sdb_entry) | |
155 | { | |
156 | int ret; | |
157 | ||
158 | ret = fmc->op->reprogram(fmc, d, gw); | |
159 | if (ret < 0) | |
160 | return ret; | |
161 | if (sdb_entry < 0) | |
162 | return ret; | |
163 | ||
164 | /* We are required to find SDB at a given offset */ | |
165 | ret = fmc_scan_sdb_tree(fmc, sdb_entry); | |
166 | if (ret < 0) { | |
167 | dev_err(&fmc->dev, "Can't find SDB at address 0x%x\n", | |
168 | sdb_entry); | |
169 | return -ENODEV; | |
170 | } | |
2071a3e9 | 171 | |
77864f2e AR |
172 | return 0; |
173 | } | |
174 | EXPORT_SYMBOL(fmc_reprogram); | |
175 | ||
77864f2e AR |
176 | void fmc_show_sdb_tree(const struct fmc_device *fmc) |
177 | { | |
2071a3e9 FV |
178 | pr_err("%s: not supported anymore, use debugfs to dump SDB\n", |
179 | __func__); | |
77864f2e AR |
180 | } |
181 | EXPORT_SYMBOL(fmc_show_sdb_tree); | |
182 | ||
183 | signed long fmc_find_sdb_device(struct sdb_array *tree, | |
184 | uint64_t vid, uint32_t did, unsigned long *sz) | |
185 | { | |
186 | signed long res = -ENODEV; | |
187 | union sdb_record *r; | |
188 | struct sdb_product *p; | |
189 | struct sdb_component *c; | |
190 | int i, n = tree->len; | |
191 | uint64_t last, first; | |
192 | ||
193 | /* FIXME: what if the first interconnect is not at zero? */ | |
194 | for (i = 0; i < n; i++) { | |
195 | r = &tree->record[i]; | |
196 | c = &r->dev.sdb_component; | |
197 | p = &c->product; | |
198 | ||
199 | if (!IS_ERR(tree->subtree[i])) | |
200 | res = fmc_find_sdb_device(tree->subtree[i], | |
201 | vid, did, sz); | |
202 | if (res >= 0) | |
203 | return res + tree->baseaddr; | |
204 | if (r->empty.record_type != sdb_type_device) | |
205 | continue; | |
206 | if (__be64_to_cpu(p->vendor_id) != vid) | |
207 | continue; | |
208 | if (__be32_to_cpu(p->device_id) != did) | |
209 | continue; | |
210 | /* found */ | |
211 | last = __be64_to_cpu(c->addr_last); | |
212 | first = __be64_to_cpu(c->addr_first); | |
213 | if (sz) | |
214 | *sz = (typeof(*sz))(last + 1 - first); | |
215 | return first + tree->baseaddr; | |
216 | } | |
217 | return res; | |
218 | } | |
219 | EXPORT_SYMBOL(fmc_find_sdb_device); |