aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/firmware
diff options
context:
space:
mode:
authorRafał Miłecki <zajec5@gmail.com>2016-01-25 03:50:29 -0500
committerRalf Baechle <ralf@linux-mips.org>2016-05-13 08:01:43 -0400
commit2ab71a02c56f8244ac611b5c6e6603c6fe83b966 (patch)
treeb741901481c7b40fe0afe0a2dbc36aeedac1e44c /drivers/firmware
parentb11d022767f78357f08bc3d548e99af1c6cf7808 (diff)
MIPS: BCM47xx: Move SPROM driver to drivers/firmware/
Broadcom ARM home routers store SPROM content in NVRAM just like MIPS ones. To share SPROM code we need to move it out of arch/mips/ to some common place. We already have bcm47xx_nvram in firmware path and SPROM should fit there as well. This driver is responsible for parsing SoC configuration data into a struct shared between ssb and bcma buses. This was tested with BCM4706 & BCM5357C0 (BCM47XX) and BCM4708A0 (ARCH_BCM_5301X). Signed-off-by: Rafał Miłecki <zajec5@gmail.com> Cc: Hauke Mehrtens <hauke@hauke-m.de> Cc: linux-mips@linux-mips.org Patchwork: https://patchwork.linux-mips.org/patch/12210/ Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Diffstat (limited to 'drivers/firmware')
-rw-r--r--drivers/firmware/broadcom/Kconfig11
-rw-r--r--drivers/firmware/broadcom/Makefile1
-rw-r--r--drivers/firmware/broadcom/bcm47xx_sprom.c737
3 files changed, 749 insertions, 0 deletions
diff --git a/drivers/firmware/broadcom/Kconfig b/drivers/firmware/broadcom/Kconfig
index 6bed119930dd..3c7e5b741e37 100644
--- a/drivers/firmware/broadcom/Kconfig
+++ b/drivers/firmware/broadcom/Kconfig
@@ -9,3 +9,14 @@ config BCM47XX_NVRAM
9 This driver provides an easy way to get value of requested parameter. 9 This driver provides an easy way to get value of requested parameter.
10 It simply reads content of NVRAM and parses it. It doesn't control any 10 It simply reads content of NVRAM and parses it. It doesn't control any
11 hardware part itself. 11 hardware part itself.
12
13config BCM47XX_SPROM
14 bool "Broadcom SPROM driver"
15 depends on BCM47XX_NVRAM
16 help
17 Broadcom devices store configuration data in SPROM. Accessing it is
18 specific to the bus host type, e.g. PCI(e) devices have it mapped in
19 a PCI BAR.
20 In case of SoC devices SPROM content is stored on a flash used by
21 bootloader firmware CFE. This driver provides method to ssb and bcma
22 drivers to read SPROM on SoC.
diff --git a/drivers/firmware/broadcom/Makefile b/drivers/firmware/broadcom/Makefile
index d0e683583cd6..f93efc479b8b 100644
--- a/drivers/firmware/broadcom/Makefile
+++ b/drivers/firmware/broadcom/Makefile
@@ -1 +1,2 @@
1obj-$(CONFIG_BCM47XX_NVRAM) += bcm47xx_nvram.o 1obj-$(CONFIG_BCM47XX_NVRAM) += bcm47xx_nvram.o
2obj-$(CONFIG_BCM47XX_SPROM) += bcm47xx_sprom.o
diff --git a/drivers/firmware/broadcom/bcm47xx_sprom.c b/drivers/firmware/broadcom/bcm47xx_sprom.c
new file mode 100644
index 000000000000..b6eb875d4af3
--- /dev/null
+++ b/drivers/firmware/broadcom/bcm47xx_sprom.c
@@ -0,0 +1,737 @@
1/*
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>
7 *
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.
12 *
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.
23 *
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.
27 */
28
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>
34
35static void create_key(const char *prefix, const char *postfix,
36 const char *name, char *buf, int len)
37{
38 if (prefix && postfix)
39 snprintf(buf, len, "%s%s%s", prefix, name, postfix);
40 else if (prefix)
41 snprintf(buf, len, "%s%s", prefix, name);
42 else if (postfix)
43 snprintf(buf, len, "%s%s", name, postfix);
44 else
45 snprintf(buf, len, "%s", name);
46}
47
48static int get_nvram_var(const char *prefix, const char *postfix,
49 const char *name, char *buf, int len, bool fallback)
50{
51 char key[40];
52 int err;
53
54 create_key(prefix, postfix, name, key, sizeof(key));
55
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);
60 }
61 return err;
62}
63
64#define NVRAM_READ_VAL(type) \
65static void nvram_read_ ## type(const char *prefix, \
66 const char *postfix, const char *name, \
67 type *val, type allset, bool fallback) \
68{ \
69 char buf[100]; \
70 int err; \
71 type var; \
72 \
73 err = get_nvram_var(prefix, postfix, name, buf, sizeof(buf), \
74 fallback); \
75 if (err < 0) \
76 return; \
77 err = kstrto ## type(strim(buf), 0, &var); \
78 if (err) { \
79 pr_warn("can not parse nvram name %s%s%s with value %s got %i\n", \
80 prefix, name, postfix, buf, err); \
81 return; \
82 } \
83 if (allset && var == allset) \
84 return; \
85 *val = var; \
86}
87
88NVRAM_READ_VAL(u8)
89NVRAM_READ_VAL(s8)
90NVRAM_READ_VAL(u16)
91NVRAM_READ_VAL(u32)
92
93#undef NVRAM_READ_VAL
94
95static void nvram_read_u32_2(const char *prefix, const char *name,
96 u16 *val_lo, u16 *val_hi, bool fallback)
97{
98 char buf[100];
99 int err;
100 u32 val;
101
102 err = get_nvram_var(prefix, NULL, name, buf, sizeof(buf), fallback);
103 if (err < 0)
104 return;
105 err = kstrtou32(strim(buf), 0, &val);
106 if (err) {
107 pr_warn("can not parse nvram name %s%s with value %s got %i\n",
108 prefix, name, buf, err);
109 return;
110 }
111 *val_lo = (val & 0x0000FFFFU);
112 *val_hi = (val & 0xFFFF0000U) >> 16;
113}
114
115static void nvram_read_leddc(const char *prefix, const char *name,
116 u8 *leddc_on_time, u8 *leddc_off_time,
117 bool fallback)
118{
119 char buf[100];
120 int err;
121 u32 val;
122
123 err = get_nvram_var(prefix, NULL, name, buf, sizeof(buf), fallback);
124 if (err < 0)
125 return;
126 err = kstrtou32(strim(buf), 0, &val);
127 if (err) {
128 pr_warn("can not parse nvram name %s%s with value %s got %i\n",
129 prefix, name, buf, err);
130 return;
131 }
132
133 if (val == 0xffff || val == 0xffffffff)
134 return;
135
136 *leddc_on_time = val & 0xff;
137 *leddc_off_time = (val >> 16) & 0xff;
138}
139
140static void bcm47xx_nvram_parse_macaddr(char *buf, u8 macaddr[6])
141{
142 if (strchr(buf, ':'))
143 sscanf(buf, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &macaddr[0],
144 &macaddr[1], &macaddr[2], &macaddr[3], &macaddr[4],
145 &macaddr[5]);
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],
149 &macaddr[5]);
150 else
151 pr_warn("Can not parse mac address: %s\n", buf);
152}
153
154static void nvram_read_macaddr(const char *prefix, const char *name,
155 u8 val[6], bool fallback)
156{
157 char buf[100];
158 int err;
159
160 err = get_nvram_var(prefix, NULL, name, buf, sizeof(buf), fallback);
161 if (err < 0)
162 return;
163
164 bcm47xx_nvram_parse_macaddr(buf, val);
165}
166
167static void nvram_read_alpha2(const char *prefix, const char *name,
168 char val[2], bool fallback)
169{
170 char buf[10];
171 int err;
172
173 err = get_nvram_var(prefix, NULL, name, buf, sizeof(buf), fallback);
174 if (err < 0)
175 return;
176 if (buf[0] == '0')
177 return;
178 if (strlen(buf) > 2) {
179 pr_warn("alpha2 is too long %s\n", buf);
180 return;
181 }
182 memcpy(val, buf, 2);
183}
184
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, \
189 _allset, _fallback)
190/*
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.
198 */
199static void bcm47xx_sprom_fill_auto(struct ssb_sprom *sprom,
200 const char *prefix, bool fallback)
201{
202 const char *pre = prefix;
203 bool fb = fallback;
204
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);
207
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);
217
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);
222
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);
228
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);
236
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);
250
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);
294
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);
312
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);
354
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);
380
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);
397
398 ENTRY(0xfffffe00, u8, pre, "sar2g", sar2g, 0, fb);
399 ENTRY(0xfffffe00, u8, pre, "sar5g", sar5g, 0, fb);
400
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);
417}
418#undef ENTRY /* It's specififc, uses local variable, don't use it (again). */
419
420static void bcm47xx_fill_sprom_path_r4589(struct ssb_sprom *sprom,
421 const char *prefix, bool fallback)
422{
423 char postfix[2];
424 int i;
425
426 for (i = 0; i < ARRAY_SIZE(sprom->core_pwr_info); i++) {
427 struct ssb_sprom_core_pwr_info *pwr_info;
428
429 pwr_info = &sprom->core_pwr_info[i];
430
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);
468 }
469}
470
471static void bcm47xx_fill_sprom_path_r45(struct ssb_sprom *sprom,
472 const char *prefix, bool fallback)
473{
474 char postfix[2];
475 int i;
476
477 for (i = 0; i < ARRAY_SIZE(sprom->core_pwr_info); i++) {
478 struct ssb_sprom_core_pwr_info *pwr_info;
479
480 pwr_info = &sprom->core_pwr_info[i];
481
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);
491 }
492}
493
494static bool bcm47xx_is_valid_mac(u8 *mac)
495{
496 return mac && !(mac[0] == 0x00 && mac[1] == 0x90 && mac[2] == 0x4c);
497}
498
499static int bcm47xx_increase_mac_addr(u8 *mac, u8 num)
500{
501 u8 *oui = mac + ETH_ALEN/2 - 1;
502 u8 *p = mac + ETH_ALEN - 1;
503
504 do {
505 (*p) += num;
506 if (*p > num)
507 break;
508 p--;
509 num = 1;
510 } while (p != oui);
511
512 if (p == oui) {
513 pr_err("unable to fetch mac address\n");
514 return -ENOENT;
515 }
516 return 0;
517}
518
519static int mac_addr_used = 2;
520
521static void bcm47xx_fill_sprom_ethernet(struct ssb_sprom *sprom,
522 const char *prefix, bool fallback)
523{
524 bool fb = fallback;
525
526 nvram_read_macaddr(prefix, "et0macaddr", sprom->et0mac, fallback);
527 nvram_read_u8(prefix, NULL, "et0mdcport", &sprom->et0mdcport, 0,
528 fallback);
529 nvram_read_u8(prefix, NULL, "et0phyaddr", &sprom->et0phyaddr, 0,
530 fallback);
531
532 nvram_read_macaddr(prefix, "et1macaddr", sprom->et1mac, fallback);
533 nvram_read_u8(prefix, NULL, "et1mdcport", &sprom->et1mdcport, 0,
534 fallback);
535 nvram_read_u8(prefix, NULL, "et1phyaddr", &sprom->et1phyaddr, 0,
536 fallback);
537
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);
541
542 nvram_read_macaddr(prefix, "macaddr", sprom->il0mac, fallback);
543 nvram_read_macaddr(prefix, "il0macaddr", sprom->il0mac, fallback);
544
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.
550 */
551 if (!bcm47xx_is_valid_mac(sprom->il0mac)) {
552 u8 mac[6];
553
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);
557
558 if (!err) {
559 ether_addr_copy(sprom->il0mac, mac);
560 mac_addr_used++;
561 }
562 }
563 }
564}
565
566static void bcm47xx_fill_board_data(struct ssb_sprom *sprom, const char *prefix,
567 bool fallback)
568{
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);
573}
574
575void bcm47xx_fill_sprom(struct ssb_sprom *sprom, const char *prefix,
576 bool fallback)
577{
578 bcm47xx_fill_sprom_ethernet(sprom, prefix, fallback);
579 bcm47xx_fill_board_data(sprom, prefix, fallback);
580
581 nvram_read_u8(prefix, NULL, "sromrev", &sprom->revision, 0, fallback);
582
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);
588
589 switch (sprom->revision) {
590 case 4:
591 case 5:
592 bcm47xx_fill_sprom_path_r4589(sprom, prefix, fallback);
593 bcm47xx_fill_sprom_path_r45(sprom, prefix, fallback);
594 break;
595 case 8:
596 case 9:
597 bcm47xx_fill_sprom_path_r4589(sprom, prefix, fallback);
598 break;
599 }
600
601 bcm47xx_sprom_fill_auto(sprom, prefix, fallback);
602}
603
604#if IS_BUILTIN(CONFIG_SSB) && IS_ENABLED(CONFIG_SSB_SPROM)
605static int bcm47xx_get_sprom_ssb(struct ssb_bus *bus, struct ssb_sprom *out)
606{
607 char prefix[10];
608
609 switch (bus->bustype) {
610 case SSB_BUSTYPE_SSB:
611 bcm47xx_fill_sprom(out, NULL, false);
612 return 0;
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);
619 return 0;
620 default:
621 pr_warn("Unable to fill SPROM for given bustype.\n");
622 return -EINVAL;
623 }
624}
625#endif
626
627#if IS_BUILTIN(CONFIG_BCMA)
628/*
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/.
634 */
635static void bcm47xx_sprom_apply_prefix_alias(char *prefix, size_t prefix_size)
636{
637 size_t prefix_len = strlen(prefix);
638 size_t short_len = prefix_len - 1;
639 char nvram_var[10];
640 char buf[20];
641 int i;
642
643 /* Passed prefix has to end with a slash */
644 if (prefix_len <= 0 || prefix[prefix_len - 1] != '/')
645 return;
646
647 for (i = 0; i < 3; i++) {
648 if (snprintf(nvram_var, sizeof(nvram_var), "devpath%d", i) <= 0)
649 continue;
650 if (bcm47xx_nvram_getenv(nvram_var, buf, sizeof(buf)) < 0)
651 continue;
652 if (!strcmp(buf, prefix) ||
653 (short_len && strlen(buf) == short_len && !strncmp(buf, prefix, short_len))) {
654 snprintf(prefix, prefix_size, "%d:", i);
655 return;
656 }
657 }
658}
659
660static int bcm47xx_get_sprom_bcma(struct bcma_bus *bus, struct ssb_sprom *out)
661{
662 struct bcma_boardinfo *binfo = &bus->boardinfo;
663 struct bcma_device *core;
664 char buf[10];
665 char *prefix;
666 bool fallback = false;
667
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));
676 else
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));
681 prefix = buf;
682 break;
683 case BCMA_HOSTTYPE_SOC:
684 memset(out, 0, sizeof(struct ssb_sprom));
685 core = bcma_find_core(bus, BCMA_CORE_80211);
686 if (core) {
687 snprintf(buf, sizeof(buf), "sb/%u/",
688 core->core_index);
689 prefix = buf;
690 fallback = true;
691 } else {
692 prefix = NULL;
693 }
694 break;
695 default:
696 pr_warn("Unable to fill SPROM for given bustype.\n");
697 return -EINVAL;
698 }
699
700 nvram_read_u16(prefix, NULL, "boardvendor", &binfo->vendor, 0, true);
701 if (!binfo->vendor)
702 binfo->vendor = SSB_BOARDVENDOR_BCM;
703 nvram_read_u16(prefix, NULL, "boardtype", &binfo->type, 0, true);
704
705 bcm47xx_fill_sprom(out, prefix, fallback);
706
707 return 0;
708}
709#endif
710
711static unsigned int bcm47xx_sprom_registered;
712
713/*
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.
716 */
717int bcm47xx_sprom_register_fallbacks(void)
718{
719 if (bcm47xx_sprom_registered)
720 return 0;
721
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");
725#endif
726
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");
730#endif
731
732 bcm47xx_sprom_registered = 1;
733
734 return 0;
735}
736
737fs_initcall(bcm47xx_sprom_register_fallbacks);