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); |
49 | if (!arr) |
50 | return ERR_PTR(-ENOMEM); |
51 | arr->record = kcalloc(n, sizeof(arr->record[0]), GFP_KERNEL); |
52 | arr->subtree = kcalloc(n, sizeof(arr->subtree[0]), GFP_KERNEL); |
53 | if (!arr->record || !arr->subtree) { |
54 | kfree(arr->record); |
55 | kfree(arr->subtree); |
56 | kfree(arr); |
57 | return ERR_PTR(-ENOMEM); |
58 | } |
59 | |
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 | |
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 | |
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 | } |
172 | |
173 | return 0; |
174 | } |
175 | EXPORT_SYMBOL(fmc_reprogram); |
176 | |
177 | void fmc_show_sdb_tree(const struct fmc_device *fmc) |
178 | { |
179 | pr_err("%s: not supported anymore, use debugfs to dump SDB\n" , |
180 | __func__); |
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); |
221 | |