2 * Copyright (C) 2004 Florian Schirmer <jolt@tuxbox.org>
3 * Copyright (C) 2006 Felix Fietkau <nbd@openwrt.org>
4 * Copyright (C) 2006 Michael Buesch <m@bues.ch>
5 * Copyright (C) 2010 Waldemar Brodkorb <wbx@openadk.org>
6 * Copyright (C) 2010-2012 Hauke Mehrtens <hauke@hauke-m.de>
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2 of the License, or (at your
11 * option) any later version.
13 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
14 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
16 * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
19 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 * You should have received a copy of the GNU General Public License along
25 * with this program; if not, write to the Free Software Foundation, Inc.,
26 * 675 Mass Ave, Cambridge, MA 02139, USA.
29 #include <linux/bcm47xx_nvram.h>
30 #include <linux/bcma/bcma.h>
31 #include <linux/etherdevice.h>
32 #include <linux/if_ether.h>
33 #include <linux/ssb/ssb.h>
35 static void create_key(const char *prefix
, const char *postfix
,
36 const char *name
, char *buf
, int len
)
38 if (prefix
&& postfix
)
39 snprintf(buf
, len
, "%s%s%s", prefix
, name
, postfix
);
41 snprintf(buf
, len
, "%s%s", prefix
, name
);
43 snprintf(buf
, len
, "%s%s", name
, postfix
);
45 snprintf(buf
, len
, "%s", name
);
48 static int get_nvram_var(const char *prefix
, const char *postfix
,
49 const char *name
, char *buf
, int len
, bool fallback
)
54 create_key(prefix
, postfix
, name
, key
, sizeof(key
));
56 err
= bcm47xx_nvram_getenv(key
, buf
, len
);
57 if (fallback
&& err
== -ENOENT
&& prefix
) {
58 create_key(NULL
, postfix
, name
, key
, sizeof(key
));
59 err
= bcm47xx_nvram_getenv(key
, buf
, len
);
64 #define NVRAM_READ_VAL(type) \
65 static void nvram_read_ ## type(const char *prefix, \
66 const char *postfix, const char *name, \
67 type *val, type allset, bool fallback) \
73 err = get_nvram_var(prefix, postfix, name, buf, sizeof(buf), \
77 err = kstrto ## type(strim(buf), 0, &var); \
79 pr_warn("can not parse nvram name %s%s%s with value %s got %i\n", \
80 prefix, name, postfix, buf, err); \
83 if (allset && var == allset) \
95 static void nvram_read_u32_2(const char *prefix
, const char *name
,
96 u16
*val_lo
, u16
*val_hi
, bool fallback
)
102 err
= get_nvram_var(prefix
, NULL
, name
, buf
, sizeof(buf
), fallback
);
105 err
= kstrtou32(strim(buf
), 0, &val
);
107 pr_warn("can not parse nvram name %s%s with value %s got %i\n",
108 prefix
, name
, buf
, err
);
111 *val_lo
= (val
& 0x0000FFFFU
);
112 *val_hi
= (val
& 0xFFFF0000U
) >> 16;
115 static void nvram_read_leddc(const char *prefix
, const char *name
,
116 u8
*leddc_on_time
, u8
*leddc_off_time
,
123 err
= get_nvram_var(prefix
, NULL
, name
, buf
, sizeof(buf
), fallback
);
126 err
= kstrtou32(strim(buf
), 0, &val
);
128 pr_warn("can not parse nvram name %s%s with value %s got %i\n",
129 prefix
, name
, buf
, err
);
133 if (val
== 0xffff || val
== 0xffffffff)
136 *leddc_on_time
= val
& 0xff;
137 *leddc_off_time
= (val
>> 16) & 0xff;
140 static void bcm47xx_nvram_parse_macaddr(char *buf
, u8 macaddr
[6])
142 if (strchr(buf
, ':'))
143 sscanf(buf
, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &macaddr
[0],
144 &macaddr
[1], &macaddr
[2], &macaddr
[3], &macaddr
[4],
146 else if (strchr(buf
, '-'))
147 sscanf(buf
, "%hhx-%hhx-%hhx-%hhx-%hhx-%hhx", &macaddr
[0],
148 &macaddr
[1], &macaddr
[2], &macaddr
[3], &macaddr
[4],
151 pr_warn("Can not parse mac address: %s\n", buf
);
154 static void nvram_read_macaddr(const char *prefix
, const char *name
,
155 u8 val
[6], bool fallback
)
160 err
= get_nvram_var(prefix
, NULL
, name
, buf
, sizeof(buf
), fallback
);
164 bcm47xx_nvram_parse_macaddr(buf
, val
);
167 static void nvram_read_alpha2(const char *prefix
, const char *name
,
168 char val
[2], bool fallback
)
173 err
= get_nvram_var(prefix
, NULL
, name
, buf
, sizeof(buf
), fallback
);
178 if (strlen(buf
) > 2) {
179 pr_warn("alpha2 is too long %s\n", buf
);
185 /* This is one-function-only macro, it uses local "sprom" variable! */
186 #define ENTRY(_revmask, _type, _prefix, _name, _val, _allset, _fallback) \
187 if (_revmask & BIT(sprom->revision)) \
188 nvram_read_ ## _type(_prefix, NULL, _name, &sprom->_val, \
191 * Special version of filling function that can be safely called for any SPROM
192 * revision. For every NVRAM to SPROM mapping it contains bitmask of revisions
193 * for which the mapping is valid.
194 * It obviously requires some hexadecimal/bitmasks knowledge, but allows
195 * writing cleaner code (easy revisions handling).
196 * Note that while SPROM revision 0 was never used, we still keep BIT(0)
197 * reserved for it, just to keep numbering sane.
199 static void bcm47xx_sprom_fill_auto(struct ssb_sprom
*sprom
,
200 const char *prefix
, bool fallback
)
202 const char *pre
= prefix
;
205 /* Broadcom extracts it for rev 8+ but it was found on 2 and 4 too */
206 ENTRY(0xfffffffe, u16
, pre
, "devid", dev_id
, 0, fallback
);
208 ENTRY(0xfffffffe, u16
, pre
, "boardrev", board_rev
, 0, true);
209 ENTRY(0xfffffffe, u32
, pre
, "boardflags", boardflags
, 0, fb
);
210 ENTRY(0xfffffff0, u32
, pre
, "boardflags2", boardflags2
, 0, fb
);
211 ENTRY(0xfffff800, u32
, pre
, "boardflags3", boardflags3
, 0, fb
);
212 ENTRY(0x00000002, u16
, pre
, "boardflags", boardflags_lo
, 0, fb
);
213 ENTRY(0xfffffffc, u16
, pre
, "boardtype", board_type
, 0, true);
214 ENTRY(0xfffffffe, u16
, pre
, "boardnum", board_num
, 0, fb
);
215 ENTRY(0x00000002, u8
, pre
, "cc", country_code
, 0, fb
);
216 ENTRY(0xfffffff8, u8
, pre
, "regrev", regrev
, 0, fb
);
218 ENTRY(0xfffffffe, u8
, pre
, "ledbh0", gpio0
, 0xff, fb
);
219 ENTRY(0xfffffffe, u8
, pre
, "ledbh1", gpio1
, 0xff, fb
);
220 ENTRY(0xfffffffe, u8
, pre
, "ledbh2", gpio2
, 0xff, fb
);
221 ENTRY(0xfffffffe, u8
, pre
, "ledbh3", gpio3
, 0xff, fb
);
223 ENTRY(0x0000070e, u16
, pre
, "pa0b0", pa0b0
, 0, fb
);
224 ENTRY(0x0000070e, u16
, pre
, "pa0b1", pa0b1
, 0, fb
);
225 ENTRY(0x0000070e, u16
, pre
, "pa0b2", pa0b2
, 0, fb
);
226 ENTRY(0x0000070e, u8
, pre
, "pa0itssit", itssi_bg
, 0, fb
);
227 ENTRY(0x0000070e, u8
, pre
, "pa0maxpwr", maxpwr_bg
, 0, fb
);
229 ENTRY(0x0000070c, u8
, pre
, "opo", opo
, 0, fb
);
230 ENTRY(0xfffffffe, u8
, pre
, "aa2g", ant_available_bg
, 0, fb
);
231 ENTRY(0xfffffffe, u8
, pre
, "aa5g", ant_available_a
, 0, fb
);
232 ENTRY(0x000007fe, s8
, pre
, "ag0", antenna_gain
.a0
, 0, fb
);
233 ENTRY(0x000007fe, s8
, pre
, "ag1", antenna_gain
.a1
, 0, fb
);
234 ENTRY(0x000007f0, s8
, pre
, "ag2", antenna_gain
.a2
, 0, fb
);
235 ENTRY(0x000007f0, s8
, pre
, "ag3", antenna_gain
.a3
, 0, fb
);
237 ENTRY(0x0000070e, u16
, pre
, "pa1b0", pa1b0
, 0, fb
);
238 ENTRY(0x0000070e, u16
, pre
, "pa1b1", pa1b1
, 0, fb
);
239 ENTRY(0x0000070e, u16
, pre
, "pa1b2", pa1b2
, 0, fb
);
240 ENTRY(0x0000070c, u16
, pre
, "pa1lob0", pa1lob0
, 0, fb
);
241 ENTRY(0x0000070c, u16
, pre
, "pa1lob1", pa1lob1
, 0, fb
);
242 ENTRY(0x0000070c, u16
, pre
, "pa1lob2", pa1lob2
, 0, fb
);
243 ENTRY(0x0000070c, u16
, pre
, "pa1hib0", pa1hib0
, 0, fb
);
244 ENTRY(0x0000070c, u16
, pre
, "pa1hib1", pa1hib1
, 0, fb
);
245 ENTRY(0x0000070c, u16
, pre
, "pa1hib2", pa1hib2
, 0, fb
);
246 ENTRY(0x0000070e, u8
, pre
, "pa1itssit", itssi_a
, 0, fb
);
247 ENTRY(0x0000070e, u8
, pre
, "pa1maxpwr", maxpwr_a
, 0, fb
);
248 ENTRY(0x0000070c, u8
, pre
, "pa1lomaxpwr", maxpwr_al
, 0, fb
);
249 ENTRY(0x0000070c, u8
, pre
, "pa1himaxpwr", maxpwr_ah
, 0, fb
);
251 ENTRY(0x00000708, u8
, pre
, "bxa2g", bxa2g
, 0, fb
);
252 ENTRY(0x00000708, u8
, pre
, "rssisav2g", rssisav2g
, 0, fb
);
253 ENTRY(0x00000708, u8
, pre
, "rssismc2g", rssismc2g
, 0, fb
);
254 ENTRY(0x00000708, u8
, pre
, "rssismf2g", rssismf2g
, 0, fb
);
255 ENTRY(0x00000708, u8
, pre
, "bxa5g", bxa5g
, 0, fb
);
256 ENTRY(0x00000708, u8
, pre
, "rssisav5g", rssisav5g
, 0, fb
);
257 ENTRY(0x00000708, u8
, pre
, "rssismc5g", rssismc5g
, 0, fb
);
258 ENTRY(0x00000708, u8
, pre
, "rssismf5g", rssismf5g
, 0, fb
);
259 ENTRY(0x00000708, u8
, pre
, "tri2g", tri2g
, 0, fb
);
260 ENTRY(0x00000708, u8
, pre
, "tri5g", tri5g
, 0, fb
);
261 ENTRY(0x00000708, u8
, pre
, "tri5gl", tri5gl
, 0, fb
);
262 ENTRY(0x00000708, u8
, pre
, "tri5gh", tri5gh
, 0, fb
);
263 ENTRY(0x00000708, s8
, pre
, "rxpo2g", rxpo2g
, 0, fb
);
264 ENTRY(0x00000708, s8
, pre
, "rxpo5g", rxpo5g
, 0, fb
);
265 ENTRY(0xfffffff0, u8
, pre
, "txchain", txchain
, 0xf, fb
);
266 ENTRY(0xfffffff0, u8
, pre
, "rxchain", rxchain
, 0xf, fb
);
267 ENTRY(0xfffffff0, u8
, pre
, "antswitch", antswitch
, 0xff, fb
);
268 ENTRY(0x00000700, u8
, pre
, "tssipos2g", fem
.ghz2
.tssipos
, 0, fb
);
269 ENTRY(0x00000700, u8
, pre
, "extpagain2g", fem
.ghz2
.extpa_gain
, 0, fb
);
270 ENTRY(0x00000700, u8
, pre
, "pdetrange2g", fem
.ghz2
.pdet_range
, 0, fb
);
271 ENTRY(0x00000700, u8
, pre
, "triso2g", fem
.ghz2
.tr_iso
, 0, fb
);
272 ENTRY(0x00000700, u8
, pre
, "antswctl2g", fem
.ghz2
.antswlut
, 0, fb
);
273 ENTRY(0x00000700, u8
, pre
, "tssipos5g", fem
.ghz5
.tssipos
, 0, fb
);
274 ENTRY(0x00000700, u8
, pre
, "extpagain5g", fem
.ghz5
.extpa_gain
, 0, fb
);
275 ENTRY(0x00000700, u8
, pre
, "pdetrange5g", fem
.ghz5
.pdet_range
, 0, fb
);
276 ENTRY(0x00000700, u8
, pre
, "triso5g", fem
.ghz5
.tr_iso
, 0, fb
);
277 ENTRY(0x00000700, u8
, pre
, "antswctl5g", fem
.ghz5
.antswlut
, 0, fb
);
278 ENTRY(0x000000f0, u8
, pre
, "txpid2ga0", txpid2g
[0], 0, fb
);
279 ENTRY(0x000000f0, u8
, pre
, "txpid2ga1", txpid2g
[1], 0, fb
);
280 ENTRY(0x000000f0, u8
, pre
, "txpid2ga2", txpid2g
[2], 0, fb
);
281 ENTRY(0x000000f0, u8
, pre
, "txpid2ga3", txpid2g
[3], 0, fb
);
282 ENTRY(0x000000f0, u8
, pre
, "txpid5ga0", txpid5g
[0], 0, fb
);
283 ENTRY(0x000000f0, u8
, pre
, "txpid5ga1", txpid5g
[1], 0, fb
);
284 ENTRY(0x000000f0, u8
, pre
, "txpid5ga2", txpid5g
[2], 0, fb
);
285 ENTRY(0x000000f0, u8
, pre
, "txpid5ga3", txpid5g
[3], 0, fb
);
286 ENTRY(0x000000f0, u8
, pre
, "txpid5gla0", txpid5gl
[0], 0, fb
);
287 ENTRY(0x000000f0, u8
, pre
, "txpid5gla1", txpid5gl
[1], 0, fb
);
288 ENTRY(0x000000f0, u8
, pre
, "txpid5gla2", txpid5gl
[2], 0, fb
);
289 ENTRY(0x000000f0, u8
, pre
, "txpid5gla3", txpid5gl
[3], 0, fb
);
290 ENTRY(0x000000f0, u8
, pre
, "txpid5gha0", txpid5gh
[0], 0, fb
);
291 ENTRY(0x000000f0, u8
, pre
, "txpid5gha1", txpid5gh
[1], 0, fb
);
292 ENTRY(0x000000f0, u8
, pre
, "txpid5gha2", txpid5gh
[2], 0, fb
);
293 ENTRY(0x000000f0, u8
, pre
, "txpid5gha3", txpid5gh
[3], 0, fb
);
295 ENTRY(0xffffff00, u8
, pre
, "tempthresh", tempthresh
, 0, fb
);
296 ENTRY(0xffffff00, u8
, pre
, "tempoffset", tempoffset
, 0, fb
);
297 ENTRY(0xffffff00, u16
, pre
, "rawtempsense", rawtempsense
, 0, fb
);
298 ENTRY(0xffffff00, u8
, pre
, "measpower", measpower
, 0, fb
);
299 ENTRY(0xffffff00, u8
, pre
, "tempsense_slope", tempsense_slope
, 0, fb
);
300 ENTRY(0xffffff00, u8
, pre
, "tempcorrx", tempcorrx
, 0, fb
);
301 ENTRY(0xffffff00, u8
, pre
, "tempsense_option", tempsense_option
, 0, fb
);
302 ENTRY(0x00000700, u8
, pre
, "freqoffset_corr", freqoffset_corr
, 0, fb
);
303 ENTRY(0x00000700, u8
, pre
, "iqcal_swp_dis", iqcal_swp_dis
, 0, fb
);
304 ENTRY(0x00000700, u8
, pre
, "hw_iqcal_en", hw_iqcal_en
, 0, fb
);
305 ENTRY(0x00000700, u8
, pre
, "elna2g", elna2g
, 0, fb
);
306 ENTRY(0x00000700, u8
, pre
, "elna5g", elna5g
, 0, fb
);
307 ENTRY(0xffffff00, u8
, pre
, "phycal_tempdelta", phycal_tempdelta
, 0, fb
);
308 ENTRY(0xffffff00, u8
, pre
, "temps_period", temps_period
, 0, fb
);
309 ENTRY(0xffffff00, u8
, pre
, "temps_hysteresis", temps_hysteresis
, 0, fb
);
310 ENTRY(0xffffff00, u8
, pre
, "measpower1", measpower1
, 0, fb
);
311 ENTRY(0xffffff00, u8
, pre
, "measpower2", measpower2
, 0, fb
);
313 ENTRY(0x000001f0, u16
, pre
, "cck2gpo", cck2gpo
, 0, fb
);
314 ENTRY(0x000001f0, u32
, pre
, "ofdm2gpo", ofdm2gpo
, 0, fb
);
315 ENTRY(0x000001f0, u32
, pre
, "ofdm5gpo", ofdm5gpo
, 0, fb
);
316 ENTRY(0x000001f0, u32
, pre
, "ofdm5glpo", ofdm5glpo
, 0, fb
);
317 ENTRY(0x000001f0, u32
, pre
, "ofdm5ghpo", ofdm5ghpo
, 0, fb
);
318 ENTRY(0x000001f0, u16
, pre
, "mcs2gpo0", mcs2gpo
[0], 0, fb
);
319 ENTRY(0x000001f0, u16
, pre
, "mcs2gpo1", mcs2gpo
[1], 0, fb
);
320 ENTRY(0x000001f0, u16
, pre
, "mcs2gpo2", mcs2gpo
[2], 0, fb
);
321 ENTRY(0x000001f0, u16
, pre
, "mcs2gpo3", mcs2gpo
[3], 0, fb
);
322 ENTRY(0x000001f0, u16
, pre
, "mcs2gpo4", mcs2gpo
[4], 0, fb
);
323 ENTRY(0x000001f0, u16
, pre
, "mcs2gpo5", mcs2gpo
[5], 0, fb
);
324 ENTRY(0x000001f0, u16
, pre
, "mcs2gpo6", mcs2gpo
[6], 0, fb
);
325 ENTRY(0x000001f0, u16
, pre
, "mcs2gpo7", mcs2gpo
[7], 0, fb
);
326 ENTRY(0x000001f0, u16
, pre
, "mcs5gpo0", mcs5gpo
[0], 0, fb
);
327 ENTRY(0x000001f0, u16
, pre
, "mcs5gpo1", mcs5gpo
[1], 0, fb
);
328 ENTRY(0x000001f0, u16
, pre
, "mcs5gpo2", mcs5gpo
[2], 0, fb
);
329 ENTRY(0x000001f0, u16
, pre
, "mcs5gpo3", mcs5gpo
[3], 0, fb
);
330 ENTRY(0x000001f0, u16
, pre
, "mcs5gpo4", mcs5gpo
[4], 0, fb
);
331 ENTRY(0x000001f0, u16
, pre
, "mcs5gpo5", mcs5gpo
[5], 0, fb
);
332 ENTRY(0x000001f0, u16
, pre
, "mcs5gpo6", mcs5gpo
[6], 0, fb
);
333 ENTRY(0x000001f0, u16
, pre
, "mcs5gpo7", mcs5gpo
[7], 0, fb
);
334 ENTRY(0x000001f0, u16
, pre
, "mcs5glpo0", mcs5glpo
[0], 0, fb
);
335 ENTRY(0x000001f0, u16
, pre
, "mcs5glpo1", mcs5glpo
[1], 0, fb
);
336 ENTRY(0x000001f0, u16
, pre
, "mcs5glpo2", mcs5glpo
[2], 0, fb
);
337 ENTRY(0x000001f0, u16
, pre
, "mcs5glpo3", mcs5glpo
[3], 0, fb
);
338 ENTRY(0x000001f0, u16
, pre
, "mcs5glpo4", mcs5glpo
[4], 0, fb
);
339 ENTRY(0x000001f0, u16
, pre
, "mcs5glpo5", mcs5glpo
[5], 0, fb
);
340 ENTRY(0x000001f0, u16
, pre
, "mcs5glpo6", mcs5glpo
[6], 0, fb
);
341 ENTRY(0x000001f0, u16
, pre
, "mcs5glpo7", mcs5glpo
[7], 0, fb
);
342 ENTRY(0x000001f0, u16
, pre
, "mcs5ghpo0", mcs5ghpo
[0], 0, fb
);
343 ENTRY(0x000001f0, u16
, pre
, "mcs5ghpo1", mcs5ghpo
[1], 0, fb
);
344 ENTRY(0x000001f0, u16
, pre
, "mcs5ghpo2", mcs5ghpo
[2], 0, fb
);
345 ENTRY(0x000001f0, u16
, pre
, "mcs5ghpo3", mcs5ghpo
[3], 0, fb
);
346 ENTRY(0x000001f0, u16
, pre
, "mcs5ghpo4", mcs5ghpo
[4], 0, fb
);
347 ENTRY(0x000001f0, u16
, pre
, "mcs5ghpo5", mcs5ghpo
[5], 0, fb
);
348 ENTRY(0x000001f0, u16
, pre
, "mcs5ghpo6", mcs5ghpo
[6], 0, fb
);
349 ENTRY(0x000001f0, u16
, pre
, "mcs5ghpo7", mcs5ghpo
[7], 0, fb
);
350 ENTRY(0x000001f0, u16
, pre
, "cddpo", cddpo
, 0, fb
);
351 ENTRY(0x000001f0, u16
, pre
, "stbcpo", stbcpo
, 0, fb
);
352 ENTRY(0x000001f0, u16
, pre
, "bw40po", bw40po
, 0, fb
);
353 ENTRY(0x000001f0, u16
, pre
, "bwduppo", bwduppo
, 0, fb
);
355 ENTRY(0xfffffe00, u16
, pre
, "cckbw202gpo", cckbw202gpo
, 0, fb
);
356 ENTRY(0xfffffe00, u16
, pre
, "cckbw20ul2gpo", cckbw20ul2gpo
, 0, fb
);
357 ENTRY(0x00000600, u32
, pre
, "legofdmbw202gpo", legofdmbw202gpo
, 0, fb
);
358 ENTRY(0x00000600, u32
, pre
, "legofdmbw20ul2gpo", legofdmbw20ul2gpo
, 0, fb
);
359 ENTRY(0x00000600, u32
, pre
, "legofdmbw205glpo", legofdmbw205glpo
, 0, fb
);
360 ENTRY(0x00000600, u32
, pre
, "legofdmbw20ul5glpo", legofdmbw20ul5glpo
, 0, fb
);
361 ENTRY(0x00000600, u32
, pre
, "legofdmbw205gmpo", legofdmbw205gmpo
, 0, fb
);
362 ENTRY(0x00000600, u32
, pre
, "legofdmbw20ul5gmpo", legofdmbw20ul5gmpo
, 0, fb
);
363 ENTRY(0x00000600, u32
, pre
, "legofdmbw205ghpo", legofdmbw205ghpo
, 0, fb
);
364 ENTRY(0x00000600, u32
, pre
, "legofdmbw20ul5ghpo", legofdmbw20ul5ghpo
, 0, fb
);
365 ENTRY(0xfffffe00, u32
, pre
, "mcsbw202gpo", mcsbw202gpo
, 0, fb
);
366 ENTRY(0x00000600, u32
, pre
, "mcsbw20ul2gpo", mcsbw20ul2gpo
, 0, fb
);
367 ENTRY(0xfffffe00, u32
, pre
, "mcsbw402gpo", mcsbw402gpo
, 0, fb
);
368 ENTRY(0xfffffe00, u32
, pre
, "mcsbw205glpo", mcsbw205glpo
, 0, fb
);
369 ENTRY(0x00000600, u32
, pre
, "mcsbw20ul5glpo", mcsbw20ul5glpo
, 0, fb
);
370 ENTRY(0xfffffe00, u32
, pre
, "mcsbw405glpo", mcsbw405glpo
, 0, fb
);
371 ENTRY(0xfffffe00, u32
, pre
, "mcsbw205gmpo", mcsbw205gmpo
, 0, fb
);
372 ENTRY(0x00000600, u32
, pre
, "mcsbw20ul5gmpo", mcsbw20ul5gmpo
, 0, fb
);
373 ENTRY(0xfffffe00, u32
, pre
, "mcsbw405gmpo", mcsbw405gmpo
, 0, fb
);
374 ENTRY(0xfffffe00, u32
, pre
, "mcsbw205ghpo", mcsbw205ghpo
, 0, fb
);
375 ENTRY(0x00000600, u32
, pre
, "mcsbw20ul5ghpo", mcsbw20ul5ghpo
, 0, fb
);
376 ENTRY(0xfffffe00, u32
, pre
, "mcsbw405ghpo", mcsbw405ghpo
, 0, fb
);
377 ENTRY(0x00000600, u16
, pre
, "mcs32po", mcs32po
, 0, fb
);
378 ENTRY(0x00000600, u16
, pre
, "legofdm40duppo", legofdm40duppo
, 0, fb
);
379 ENTRY(0x00000700, u8
, pre
, "pcieingress_war", pcieingress_war
, 0, fb
);
381 /* TODO: rev 11 support */
382 ENTRY(0x00000700, u8
, pre
, "rxgainerr2ga0", rxgainerr2ga
[0], 0, fb
);
383 ENTRY(0x00000700, u8
, pre
, "rxgainerr2ga1", rxgainerr2ga
[1], 0, fb
);
384 ENTRY(0x00000700, u8
, pre
, "rxgainerr2ga2", rxgainerr2ga
[2], 0, fb
);
385 ENTRY(0x00000700, u8
, pre
, "rxgainerr5gla0", rxgainerr5gla
[0], 0, fb
);
386 ENTRY(0x00000700, u8
, pre
, "rxgainerr5gla1", rxgainerr5gla
[1], 0, fb
);
387 ENTRY(0x00000700, u8
, pre
, "rxgainerr5gla2", rxgainerr5gla
[2], 0, fb
);
388 ENTRY(0x00000700, u8
, pre
, "rxgainerr5gma0", rxgainerr5gma
[0], 0, fb
);
389 ENTRY(0x00000700, u8
, pre
, "rxgainerr5gma1", rxgainerr5gma
[1], 0, fb
);
390 ENTRY(0x00000700, u8
, pre
, "rxgainerr5gma2", rxgainerr5gma
[2], 0, fb
);
391 ENTRY(0x00000700, u8
, pre
, "rxgainerr5gha0", rxgainerr5gha
[0], 0, fb
);
392 ENTRY(0x00000700, u8
, pre
, "rxgainerr5gha1", rxgainerr5gha
[1], 0, fb
);
393 ENTRY(0x00000700, u8
, pre
, "rxgainerr5gha2", rxgainerr5gha
[2], 0, fb
);
394 ENTRY(0x00000700, u8
, pre
, "rxgainerr5gua0", rxgainerr5gua
[0], 0, fb
);
395 ENTRY(0x00000700, u8
, pre
, "rxgainerr5gua1", rxgainerr5gua
[1], 0, fb
);
396 ENTRY(0x00000700, u8
, pre
, "rxgainerr5gua2", rxgainerr5gua
[2], 0, fb
);
398 ENTRY(0xfffffe00, u8
, pre
, "sar2g", sar2g
, 0, fb
);
399 ENTRY(0xfffffe00, u8
, pre
, "sar5g", sar5g
, 0, fb
);
401 /* TODO: rev 11 support */
402 ENTRY(0x00000700, u8
, pre
, "noiselvl2ga0", noiselvl2ga
[0], 0, fb
);
403 ENTRY(0x00000700, u8
, pre
, "noiselvl2ga1", noiselvl2ga
[1], 0, fb
);
404 ENTRY(0x00000700, u8
, pre
, "noiselvl2ga2", noiselvl2ga
[2], 0, fb
);
405 ENTRY(0x00000700, u8
, pre
, "noiselvl5gla0", noiselvl5gla
[0], 0, fb
);
406 ENTRY(0x00000700, u8
, pre
, "noiselvl5gla1", noiselvl5gla
[1], 0, fb
);
407 ENTRY(0x00000700, u8
, pre
, "noiselvl5gla2", noiselvl5gla
[2], 0, fb
);
408 ENTRY(0x00000700, u8
, pre
, "noiselvl5gma0", noiselvl5gma
[0], 0, fb
);
409 ENTRY(0x00000700, u8
, pre
, "noiselvl5gma1", noiselvl5gma
[1], 0, fb
);
410 ENTRY(0x00000700, u8
, pre
, "noiselvl5gma2", noiselvl5gma
[2], 0, fb
);
411 ENTRY(0x00000700, u8
, pre
, "noiselvl5gha0", noiselvl5gha
[0], 0, fb
);
412 ENTRY(0x00000700, u8
, pre
, "noiselvl5gha1", noiselvl5gha
[1], 0, fb
);
413 ENTRY(0x00000700, u8
, pre
, "noiselvl5gha2", noiselvl5gha
[2], 0, fb
);
414 ENTRY(0x00000700, u8
, pre
, "noiselvl5gua0", noiselvl5gua
[0], 0, fb
);
415 ENTRY(0x00000700, u8
, pre
, "noiselvl5gua1", noiselvl5gua
[1], 0, fb
);
416 ENTRY(0x00000700, u8
, pre
, "noiselvl5gua2", noiselvl5gua
[2], 0, fb
);
418 #undef ENTRY /* It's specififc, uses local variable, don't use it (again). */
420 static void bcm47xx_fill_sprom_path_r4589(struct ssb_sprom
*sprom
,
421 const char *prefix
, bool fallback
)
426 for (i
= 0; i
< ARRAY_SIZE(sprom
->core_pwr_info
); i
++) {
427 struct ssb_sprom_core_pwr_info
*pwr_info
;
429 pwr_info
= &sprom
->core_pwr_info
[i
];
431 snprintf(postfix
, sizeof(postfix
), "%i", i
);
432 nvram_read_u8(prefix
, postfix
, "maxp2ga",
433 &pwr_info
->maxpwr_2g
, 0, fallback
);
434 nvram_read_u8(prefix
, postfix
, "itt2ga",
435 &pwr_info
->itssi_2g
, 0, fallback
);
436 nvram_read_u8(prefix
, postfix
, "itt5ga",
437 &pwr_info
->itssi_5g
, 0, fallback
);
438 nvram_read_u16(prefix
, postfix
, "pa2gw0a",
439 &pwr_info
->pa_2g
[0], 0, fallback
);
440 nvram_read_u16(prefix
, postfix
, "pa2gw1a",
441 &pwr_info
->pa_2g
[1], 0, fallback
);
442 nvram_read_u16(prefix
, postfix
, "pa2gw2a",
443 &pwr_info
->pa_2g
[2], 0, fallback
);
444 nvram_read_u8(prefix
, postfix
, "maxp5ga",
445 &pwr_info
->maxpwr_5g
, 0, fallback
);
446 nvram_read_u8(prefix
, postfix
, "maxp5gha",
447 &pwr_info
->maxpwr_5gh
, 0, fallback
);
448 nvram_read_u8(prefix
, postfix
, "maxp5gla",
449 &pwr_info
->maxpwr_5gl
, 0, fallback
);
450 nvram_read_u16(prefix
, postfix
, "pa5gw0a",
451 &pwr_info
->pa_5g
[0], 0, fallback
);
452 nvram_read_u16(prefix
, postfix
, "pa5gw1a",
453 &pwr_info
->pa_5g
[1], 0, fallback
);
454 nvram_read_u16(prefix
, postfix
, "pa5gw2a",
455 &pwr_info
->pa_5g
[2], 0, fallback
);
456 nvram_read_u16(prefix
, postfix
, "pa5glw0a",
457 &pwr_info
->pa_5gl
[0], 0, fallback
);
458 nvram_read_u16(prefix
, postfix
, "pa5glw1a",
459 &pwr_info
->pa_5gl
[1], 0, fallback
);
460 nvram_read_u16(prefix
, postfix
, "pa5glw2a",
461 &pwr_info
->pa_5gl
[2], 0, fallback
);
462 nvram_read_u16(prefix
, postfix
, "pa5ghw0a",
463 &pwr_info
->pa_5gh
[0], 0, fallback
);
464 nvram_read_u16(prefix
, postfix
, "pa5ghw1a",
465 &pwr_info
->pa_5gh
[1], 0, fallback
);
466 nvram_read_u16(prefix
, postfix
, "pa5ghw2a",
467 &pwr_info
->pa_5gh
[2], 0, fallback
);
471 static void bcm47xx_fill_sprom_path_r45(struct ssb_sprom
*sprom
,
472 const char *prefix
, bool fallback
)
477 for (i
= 0; i
< ARRAY_SIZE(sprom
->core_pwr_info
); i
++) {
478 struct ssb_sprom_core_pwr_info
*pwr_info
;
480 pwr_info
= &sprom
->core_pwr_info
[i
];
482 snprintf(postfix
, sizeof(postfix
), "%i", i
);
483 nvram_read_u16(prefix
, postfix
, "pa2gw3a",
484 &pwr_info
->pa_2g
[3], 0, fallback
);
485 nvram_read_u16(prefix
, postfix
, "pa5gw3a",
486 &pwr_info
->pa_5g
[3], 0, fallback
);
487 nvram_read_u16(prefix
, postfix
, "pa5glw3a",
488 &pwr_info
->pa_5gl
[3], 0, fallback
);
489 nvram_read_u16(prefix
, postfix
, "pa5ghw3a",
490 &pwr_info
->pa_5gh
[3], 0, fallback
);
494 static bool bcm47xx_is_valid_mac(u8
*mac
)
496 return mac
&& !(mac
[0] == 0x00 && mac
[1] == 0x90 && mac
[2] == 0x4c);
499 static int bcm47xx_increase_mac_addr(u8
*mac
, u8 num
)
501 u8
*oui
= mac
+ ETH_ALEN
/2 - 1;
502 u8
*p
= mac
+ ETH_ALEN
- 1;
513 pr_err("unable to fetch mac address\n");
519 static int mac_addr_used
= 2;
521 static void bcm47xx_fill_sprom_ethernet(struct ssb_sprom
*sprom
,
522 const char *prefix
, bool fallback
)
526 nvram_read_macaddr(prefix
, "et0macaddr", sprom
->et0mac
, fallback
);
527 nvram_read_u8(prefix
, NULL
, "et0mdcport", &sprom
->et0mdcport
, 0,
529 nvram_read_u8(prefix
, NULL
, "et0phyaddr", &sprom
->et0phyaddr
, 0,
532 nvram_read_macaddr(prefix
, "et1macaddr", sprom
->et1mac
, fallback
);
533 nvram_read_u8(prefix
, NULL
, "et1mdcport", &sprom
->et1mdcport
, 0,
535 nvram_read_u8(prefix
, NULL
, "et1phyaddr", &sprom
->et1phyaddr
, 0,
538 nvram_read_macaddr(prefix
, "et2macaddr", sprom
->et2mac
, fb
);
539 nvram_read_u8(prefix
, NULL
, "et2mdcport", &sprom
->et2mdcport
, 0, fb
);
540 nvram_read_u8(prefix
, NULL
, "et2phyaddr", &sprom
->et2phyaddr
, 0, fb
);
542 nvram_read_macaddr(prefix
, "macaddr", sprom
->il0mac
, fallback
);
543 nvram_read_macaddr(prefix
, "il0macaddr", sprom
->il0mac
, fallback
);
545 /* The address prefix 00:90:4C is used by Broadcom in their initial
546 * configuration. When a mac address with the prefix 00:90:4C is used
547 * all devices from the same series are sharing the same mac address.
548 * To prevent mac address collisions we replace them with a mac address
549 * based on the base address.
551 if (!bcm47xx_is_valid_mac(sprom
->il0mac
)) {
554 nvram_read_macaddr(NULL
, "et0macaddr", mac
, false);
555 if (bcm47xx_is_valid_mac(mac
)) {
556 int err
= bcm47xx_increase_mac_addr(mac
, mac_addr_used
);
559 ether_addr_copy(sprom
->il0mac
, mac
);
566 static void bcm47xx_fill_board_data(struct ssb_sprom
*sprom
, const char *prefix
,
569 nvram_read_u32_2(prefix
, "boardflags", &sprom
->boardflags_lo
,
570 &sprom
->boardflags_hi
, fallback
);
571 nvram_read_u32_2(prefix
, "boardflags2", &sprom
->boardflags2_lo
,
572 &sprom
->boardflags2_hi
, fallback
);
575 void bcm47xx_fill_sprom(struct ssb_sprom
*sprom
, const char *prefix
,
578 bcm47xx_fill_sprom_ethernet(sprom
, prefix
, fallback
);
579 bcm47xx_fill_board_data(sprom
, prefix
, fallback
);
581 nvram_read_u8(prefix
, NULL
, "sromrev", &sprom
->revision
, 0, fallback
);
583 /* Entries requiring custom functions */
584 nvram_read_alpha2(prefix
, "ccode", sprom
->alpha2
, fallback
);
585 if (sprom
->revision
>= 3)
586 nvram_read_leddc(prefix
, "leddc", &sprom
->leddc_on_time
,
587 &sprom
->leddc_off_time
, fallback
);
589 switch (sprom
->revision
) {
592 bcm47xx_fill_sprom_path_r4589(sprom
, prefix
, fallback
);
593 bcm47xx_fill_sprom_path_r45(sprom
, prefix
, fallback
);
597 bcm47xx_fill_sprom_path_r4589(sprom
, prefix
, fallback
);
601 bcm47xx_sprom_fill_auto(sprom
, prefix
, fallback
);
604 #if IS_BUILTIN(CONFIG_SSB) && IS_ENABLED(CONFIG_SSB_SPROM)
605 static int bcm47xx_get_sprom_ssb(struct ssb_bus
*bus
, struct ssb_sprom
*out
)
609 switch (bus
->bustype
) {
610 case SSB_BUSTYPE_SSB
:
611 bcm47xx_fill_sprom(out
, NULL
, false);
613 case SSB_BUSTYPE_PCI
:
614 memset(out
, 0, sizeof(struct ssb_sprom
));
615 snprintf(prefix
, sizeof(prefix
), "pci/%u/%u/",
616 bus
->host_pci
->bus
->number
+ 1,
617 PCI_SLOT(bus
->host_pci
->devfn
));
618 bcm47xx_fill_sprom(out
, prefix
, false);
621 pr_warn("Unable to fill SPROM for given bustype.\n");
627 #if IS_BUILTIN(CONFIG_BCMA)
629 * Having many NVRAM entries for PCI devices led to repeating prefixes like
630 * pci/1/1/ all the time and wasting flash space. So at some point Broadcom
631 * decided to introduce prefixes like 0: 1: 2: etc.
632 * If we find e.g. devpath0=pci/2/1 or devpath0=pci/2/1/ we should use 0:
633 * instead of pci/2/1/.
635 static void bcm47xx_sprom_apply_prefix_alias(char *prefix
, size_t prefix_size
)
637 size_t prefix_len
= strlen(prefix
);
638 size_t short_len
= prefix_len
- 1;
643 /* Passed prefix has to end with a slash */
644 if (prefix_len
<= 0 || prefix
[prefix_len
- 1] != '/')
647 for (i
= 0; i
< 3; i
++) {
648 if (snprintf(nvram_var
, sizeof(nvram_var
), "devpath%d", i
) <= 0)
650 if (bcm47xx_nvram_getenv(nvram_var
, buf
, sizeof(buf
)) < 0)
652 if (!strcmp(buf
, prefix
) ||
653 (short_len
&& strlen(buf
) == short_len
&& !strncmp(buf
, prefix
, short_len
))) {
654 snprintf(prefix
, prefix_size
, "%d:", i
);
660 static int bcm47xx_get_sprom_bcma(struct bcma_bus
*bus
, struct ssb_sprom
*out
)
662 struct bcma_boardinfo
*binfo
= &bus
->boardinfo
;
663 struct bcma_device
*core
;
666 bool fallback
= false;
668 switch (bus
->hosttype
) {
669 case BCMA_HOSTTYPE_PCI
:
670 memset(out
, 0, sizeof(struct ssb_sprom
));
671 /* On BCM47XX all PCI buses share the same domain */
672 if (config_enabled(CONFIG_BCM47XX
))
673 snprintf(buf
, sizeof(buf
), "pci/%u/%u/",
674 bus
->host_pci
->bus
->number
+ 1,
675 PCI_SLOT(bus
->host_pci
->devfn
));
677 snprintf(buf
, sizeof(buf
), "pci/%u/%u/",
678 pci_domain_nr(bus
->host_pci
->bus
) + 1,
679 bus
->host_pci
->bus
->number
);
680 bcm47xx_sprom_apply_prefix_alias(buf
, sizeof(buf
));
683 case BCMA_HOSTTYPE_SOC
:
684 memset(out
, 0, sizeof(struct ssb_sprom
));
685 core
= bcma_find_core(bus
, BCMA_CORE_80211
);
687 snprintf(buf
, sizeof(buf
), "sb/%u/",
696 pr_warn("Unable to fill SPROM for given bustype.\n");
700 nvram_read_u16(prefix
, NULL
, "boardvendor", &binfo
->vendor
, 0, true);
702 binfo
->vendor
= SSB_BOARDVENDOR_BCM
;
703 nvram_read_u16(prefix
, NULL
, "boardtype", &binfo
->type
, 0, true);
705 bcm47xx_fill_sprom(out
, prefix
, fallback
);
711 static unsigned int bcm47xx_sprom_registered
;
714 * On bcm47xx we need to register SPROM fallback handler very early, so we can't
715 * use anything like platform device / driver for this.
717 int bcm47xx_sprom_register_fallbacks(void)
719 if (bcm47xx_sprom_registered
)
722 #if IS_BUILTIN(CONFIG_SSB) && IS_ENABLED(CONFIG_SSB_SPROM)
723 if (ssb_arch_register_fallback_sprom(&bcm47xx_get_sprom_ssb
))
724 pr_warn("Failed to register ssb SPROM handler\n");
727 #if IS_BUILTIN(CONFIG_BCMA)
728 if (bcma_arch_register_fallback_sprom(&bcm47xx_get_sprom_bcma
))
729 pr_warn("Failed to register bcma SPROM handler\n");
732 bcm47xx_sprom_registered
= 1;
737 fs_initcall(bcm47xx_sprom_register_fallbacks
);