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