sprom.c revision 4ac887cfdad909f16ee1886fe4fa19b452fc7fd3
1/*
2 * Broadcom specific AMBA
3 * SPROM reading
4 *
5 * Copyright 2011, 2012, Hauke Mehrtens <hauke@hauke-m.de>
6 *
7 * Licensed under the GNU/GPL. See COPYING for details.
8 */
9
10#include "bcma_private.h"
11
12#include <linux/bcma/bcma.h>
13#include <linux/bcma/bcma_regs.h>
14#include <linux/pci.h>
15#include <linux/io.h>
16#include <linux/dma-mapping.h>
17#include <linux/slab.h>
18
19static int(*get_fallback_sprom)(struct bcma_bus *dev, struct ssb_sprom *out);
20
21/**
22 * bcma_arch_register_fallback_sprom - Registers a method providing a
23 * fallback SPROM if no SPROM is found.
24 *
25 * @sprom_callback: The callback function.
26 *
27 * With this function the architecture implementation may register a
28 * callback handler which fills the SPROM data structure. The fallback is
29 * used for PCI based BCMA devices, where no valid SPROM can be found
30 * in the shadow registers and to provide the SPROM for SoCs where BCMA is
31 * to controll the system bus.
32 *
33 * This function is useful for weird architectures that have a half-assed
34 * BCMA device hardwired to their PCI bus.
35 *
36 * This function is available for architecture code, only. So it is not
37 * exported.
38 */
39int bcma_arch_register_fallback_sprom(int (*sprom_callback)(struct bcma_bus *bus,
40				     struct ssb_sprom *out))
41{
42	if (get_fallback_sprom)
43		return -EEXIST;
44	get_fallback_sprom = sprom_callback;
45
46	return 0;
47}
48
49static int bcma_fill_sprom_with_fallback(struct bcma_bus *bus,
50					 struct ssb_sprom *out)
51{
52	int err;
53
54	if (!get_fallback_sprom) {
55		err = -ENOENT;
56		goto fail;
57	}
58
59	err = get_fallback_sprom(bus, out);
60	if (err)
61		goto fail;
62
63	pr_debug("Using SPROM revision %d provided by"
64		 " platform.\n", bus->sprom.revision);
65	return 0;
66fail:
67	pr_warn("Using fallback SPROM failed (err %d)\n", err);
68	return err;
69}
70
71/**************************************************
72 * R/W ops.
73 **************************************************/
74
75static void bcma_sprom_read(struct bcma_bus *bus, u16 offset, u16 *sprom)
76{
77	int i;
78	for (i = 0; i < SSB_SPROMSIZE_WORDS_R4; i++)
79		sprom[i] = bcma_read16(bus->drv_cc.core,
80				       offset + (i * 2));
81}
82
83/**************************************************
84 * Validation.
85 **************************************************/
86
87static inline u8 bcma_crc8(u8 crc, u8 data)
88{
89	/* Polynomial:   x^8 + x^7 + x^6 + x^4 + x^2 + 1   */
90	static const u8 t[] = {
91		0x00, 0xF7, 0xB9, 0x4E, 0x25, 0xD2, 0x9C, 0x6B,
92		0x4A, 0xBD, 0xF3, 0x04, 0x6F, 0x98, 0xD6, 0x21,
93		0x94, 0x63, 0x2D, 0xDA, 0xB1, 0x46, 0x08, 0xFF,
94		0xDE, 0x29, 0x67, 0x90, 0xFB, 0x0C, 0x42, 0xB5,
95		0x7F, 0x88, 0xC6, 0x31, 0x5A, 0xAD, 0xE3, 0x14,
96		0x35, 0xC2, 0x8C, 0x7B, 0x10, 0xE7, 0xA9, 0x5E,
97		0xEB, 0x1C, 0x52, 0xA5, 0xCE, 0x39, 0x77, 0x80,
98		0xA1, 0x56, 0x18, 0xEF, 0x84, 0x73, 0x3D, 0xCA,
99		0xFE, 0x09, 0x47, 0xB0, 0xDB, 0x2C, 0x62, 0x95,
100		0xB4, 0x43, 0x0D, 0xFA, 0x91, 0x66, 0x28, 0xDF,
101		0x6A, 0x9D, 0xD3, 0x24, 0x4F, 0xB8, 0xF6, 0x01,
102		0x20, 0xD7, 0x99, 0x6E, 0x05, 0xF2, 0xBC, 0x4B,
103		0x81, 0x76, 0x38, 0xCF, 0xA4, 0x53, 0x1D, 0xEA,
104		0xCB, 0x3C, 0x72, 0x85, 0xEE, 0x19, 0x57, 0xA0,
105		0x15, 0xE2, 0xAC, 0x5B, 0x30, 0xC7, 0x89, 0x7E,
106		0x5F, 0xA8, 0xE6, 0x11, 0x7A, 0x8D, 0xC3, 0x34,
107		0xAB, 0x5C, 0x12, 0xE5, 0x8E, 0x79, 0x37, 0xC0,
108		0xE1, 0x16, 0x58, 0xAF, 0xC4, 0x33, 0x7D, 0x8A,
109		0x3F, 0xC8, 0x86, 0x71, 0x1A, 0xED, 0xA3, 0x54,
110		0x75, 0x82, 0xCC, 0x3B, 0x50, 0xA7, 0xE9, 0x1E,
111		0xD4, 0x23, 0x6D, 0x9A, 0xF1, 0x06, 0x48, 0xBF,
112		0x9E, 0x69, 0x27, 0xD0, 0xBB, 0x4C, 0x02, 0xF5,
113		0x40, 0xB7, 0xF9, 0x0E, 0x65, 0x92, 0xDC, 0x2B,
114		0x0A, 0xFD, 0xB3, 0x44, 0x2F, 0xD8, 0x96, 0x61,
115		0x55, 0xA2, 0xEC, 0x1B, 0x70, 0x87, 0xC9, 0x3E,
116		0x1F, 0xE8, 0xA6, 0x51, 0x3A, 0xCD, 0x83, 0x74,
117		0xC1, 0x36, 0x78, 0x8F, 0xE4, 0x13, 0x5D, 0xAA,
118		0x8B, 0x7C, 0x32, 0xC5, 0xAE, 0x59, 0x17, 0xE0,
119		0x2A, 0xDD, 0x93, 0x64, 0x0F, 0xF8, 0xB6, 0x41,
120		0x60, 0x97, 0xD9, 0x2E, 0x45, 0xB2, 0xFC, 0x0B,
121		0xBE, 0x49, 0x07, 0xF0, 0x9B, 0x6C, 0x22, 0xD5,
122		0xF4, 0x03, 0x4D, 0xBA, 0xD1, 0x26, 0x68, 0x9F,
123	};
124	return t[crc ^ data];
125}
126
127static u8 bcma_sprom_crc(const u16 *sprom)
128{
129	int word;
130	u8 crc = 0xFF;
131
132	for (word = 0; word < SSB_SPROMSIZE_WORDS_R4 - 1; word++) {
133		crc = bcma_crc8(crc, sprom[word] & 0x00FF);
134		crc = bcma_crc8(crc, (sprom[word] & 0xFF00) >> 8);
135	}
136	crc = bcma_crc8(crc, sprom[SSB_SPROMSIZE_WORDS_R4 - 1] & 0x00FF);
137	crc ^= 0xFF;
138
139	return crc;
140}
141
142static int bcma_sprom_check_crc(const u16 *sprom)
143{
144	u8 crc;
145	u8 expected_crc;
146	u16 tmp;
147
148	crc = bcma_sprom_crc(sprom);
149	tmp = sprom[SSB_SPROMSIZE_WORDS_R4 - 1] & SSB_SPROM_REVISION_CRC;
150	expected_crc = tmp >> SSB_SPROM_REVISION_CRC_SHIFT;
151	if (crc != expected_crc)
152		return -EPROTO;
153
154	return 0;
155}
156
157static int bcma_sprom_valid(const u16 *sprom)
158{
159	u16 revision;
160	int err;
161
162	err = bcma_sprom_check_crc(sprom);
163	if (err)
164		return err;
165
166	revision = sprom[SSB_SPROMSIZE_WORDS_R4 - 1] & SSB_SPROM_REVISION_REV;
167	if (revision != 8 && revision != 9) {
168		pr_err("Unsupported SPROM revision: %d\n", revision);
169		return -ENOENT;
170	}
171
172	return 0;
173}
174
175/**************************************************
176 * SPROM extraction.
177 **************************************************/
178
179#define SPOFF(offset)	((offset) / sizeof(u16))
180
181#define SPEX(_field, _offset, _mask, _shift)	\
182	bus->sprom._field = ((sprom[SPOFF(_offset)] & (_mask)) >> (_shift))
183
184static void bcma_sprom_extract_r8(struct bcma_bus *bus, const u16 *sprom)
185{
186	u16 v, o;
187	int i;
188	u16 pwr_info_offset[] = {
189		SSB_SROM8_PWR_INFO_CORE0, SSB_SROM8_PWR_INFO_CORE1,
190		SSB_SROM8_PWR_INFO_CORE2, SSB_SROM8_PWR_INFO_CORE3
191	};
192	BUILD_BUG_ON(ARRAY_SIZE(pwr_info_offset) !=
193			ARRAY_SIZE(bus->sprom.core_pwr_info));
194
195	bus->sprom.revision = sprom[SSB_SPROMSIZE_WORDS_R4 - 1] &
196		SSB_SPROM_REVISION_REV;
197
198	for (i = 0; i < 3; i++) {
199		v = sprom[SPOFF(SSB_SPROM8_IL0MAC) + i];
200		*(((__be16 *)bus->sprom.il0mac) + i) = cpu_to_be16(v);
201	}
202
203	SPEX(board_rev, SSB_SPROM8_BOARDREV, ~0, 0);
204
205	SPEX(txpid2g[0], SSB_SPROM4_TXPID2G01, SSB_SPROM4_TXPID2G0,
206	     SSB_SPROM4_TXPID2G0_SHIFT);
207	SPEX(txpid2g[1], SSB_SPROM4_TXPID2G01, SSB_SPROM4_TXPID2G1,
208	     SSB_SPROM4_TXPID2G1_SHIFT);
209	SPEX(txpid2g[2], SSB_SPROM4_TXPID2G23, SSB_SPROM4_TXPID2G2,
210	     SSB_SPROM4_TXPID2G2_SHIFT);
211	SPEX(txpid2g[3], SSB_SPROM4_TXPID2G23, SSB_SPROM4_TXPID2G3,
212	     SSB_SPROM4_TXPID2G3_SHIFT);
213
214	SPEX(txpid5gl[0], SSB_SPROM4_TXPID5GL01, SSB_SPROM4_TXPID5GL0,
215	     SSB_SPROM4_TXPID5GL0_SHIFT);
216	SPEX(txpid5gl[1], SSB_SPROM4_TXPID5GL01, SSB_SPROM4_TXPID5GL1,
217	     SSB_SPROM4_TXPID5GL1_SHIFT);
218	SPEX(txpid5gl[2], SSB_SPROM4_TXPID5GL23, SSB_SPROM4_TXPID5GL2,
219	     SSB_SPROM4_TXPID5GL2_SHIFT);
220	SPEX(txpid5gl[3], SSB_SPROM4_TXPID5GL23, SSB_SPROM4_TXPID5GL3,
221	     SSB_SPROM4_TXPID5GL3_SHIFT);
222
223	SPEX(txpid5g[0], SSB_SPROM4_TXPID5G01, SSB_SPROM4_TXPID5G0,
224	     SSB_SPROM4_TXPID5G0_SHIFT);
225	SPEX(txpid5g[1], SSB_SPROM4_TXPID5G01, SSB_SPROM4_TXPID5G1,
226	     SSB_SPROM4_TXPID5G1_SHIFT);
227	SPEX(txpid5g[2], SSB_SPROM4_TXPID5G23, SSB_SPROM4_TXPID5G2,
228	     SSB_SPROM4_TXPID5G2_SHIFT);
229	SPEX(txpid5g[3], SSB_SPROM4_TXPID5G23, SSB_SPROM4_TXPID5G3,
230	     SSB_SPROM4_TXPID5G3_SHIFT);
231
232	SPEX(txpid5gh[0], SSB_SPROM4_TXPID5GH01, SSB_SPROM4_TXPID5GH0,
233	     SSB_SPROM4_TXPID5GH0_SHIFT);
234	SPEX(txpid5gh[1], SSB_SPROM4_TXPID5GH01, SSB_SPROM4_TXPID5GH1,
235	     SSB_SPROM4_TXPID5GH1_SHIFT);
236	SPEX(txpid5gh[2], SSB_SPROM4_TXPID5GH23, SSB_SPROM4_TXPID5GH2,
237	     SSB_SPROM4_TXPID5GH2_SHIFT);
238	SPEX(txpid5gh[3], SSB_SPROM4_TXPID5GH23, SSB_SPROM4_TXPID5GH3,
239	     SSB_SPROM4_TXPID5GH3_SHIFT);
240
241	SPEX(boardflags_lo, SSB_SPROM8_BFLLO, ~0, 0);
242	SPEX(boardflags_hi, SSB_SPROM8_BFLHI, ~0, 0);
243	SPEX(boardflags2_lo, SSB_SPROM8_BFL2LO, ~0, 0);
244	SPEX(boardflags2_hi, SSB_SPROM8_BFL2HI, ~0, 0);
245
246	SPEX(country_code, SSB_SPROM8_CCODE, ~0, 0);
247
248	/* Extract cores power info info */
249	for (i = 0; i < ARRAY_SIZE(pwr_info_offset); i++) {
250		o = pwr_info_offset[i];
251		SPEX(core_pwr_info[i].itssi_2g, o + SSB_SROM8_2G_MAXP_ITSSI,
252			SSB_SPROM8_2G_ITSSI, SSB_SPROM8_2G_ITSSI_SHIFT);
253		SPEX(core_pwr_info[i].maxpwr_2g, o + SSB_SROM8_2G_MAXP_ITSSI,
254			SSB_SPROM8_2G_MAXP, 0);
255
256		SPEX(core_pwr_info[i].pa_2g[0], o + SSB_SROM8_2G_PA_0, ~0, 0);
257		SPEX(core_pwr_info[i].pa_2g[1], o + SSB_SROM8_2G_PA_1, ~0, 0);
258		SPEX(core_pwr_info[i].pa_2g[2], o + SSB_SROM8_2G_PA_2, ~0, 0);
259
260		SPEX(core_pwr_info[i].itssi_5g, o + SSB_SROM8_5G_MAXP_ITSSI,
261			SSB_SPROM8_5G_ITSSI, SSB_SPROM8_5G_ITSSI_SHIFT);
262		SPEX(core_pwr_info[i].maxpwr_5g, o + SSB_SROM8_5G_MAXP_ITSSI,
263			SSB_SPROM8_5G_MAXP, 0);
264		SPEX(core_pwr_info[i].maxpwr_5gh, o + SSB_SPROM8_5GHL_MAXP,
265			SSB_SPROM8_5GH_MAXP, 0);
266		SPEX(core_pwr_info[i].maxpwr_5gl, o + SSB_SPROM8_5GHL_MAXP,
267			SSB_SPROM8_5GL_MAXP, SSB_SPROM8_5GL_MAXP_SHIFT);
268
269		SPEX(core_pwr_info[i].pa_5gl[0], o + SSB_SROM8_5GL_PA_0, ~0, 0);
270		SPEX(core_pwr_info[i].pa_5gl[1], o + SSB_SROM8_5GL_PA_1, ~0, 0);
271		SPEX(core_pwr_info[i].pa_5gl[2], o + SSB_SROM8_5GL_PA_2, ~0, 0);
272		SPEX(core_pwr_info[i].pa_5g[0], o + SSB_SROM8_5G_PA_0, ~0, 0);
273		SPEX(core_pwr_info[i].pa_5g[1], o + SSB_SROM8_5G_PA_1, ~0, 0);
274		SPEX(core_pwr_info[i].pa_5g[2], o + SSB_SROM8_5G_PA_2, ~0, 0);
275		SPEX(core_pwr_info[i].pa_5gh[0], o + SSB_SROM8_5GH_PA_0, ~0, 0);
276		SPEX(core_pwr_info[i].pa_5gh[1], o + SSB_SROM8_5GH_PA_1, ~0, 0);
277		SPEX(core_pwr_info[i].pa_5gh[2], o + SSB_SROM8_5GH_PA_2, ~0, 0);
278	}
279
280	SPEX(fem.ghz2.tssipos, SSB_SPROM8_FEM2G, SSB_SROM8_FEM_TSSIPOS,
281	     SSB_SROM8_FEM_TSSIPOS_SHIFT);
282	SPEX(fem.ghz2.extpa_gain, SSB_SPROM8_FEM2G, SSB_SROM8_FEM_EXTPA_GAIN,
283	     SSB_SROM8_FEM_EXTPA_GAIN_SHIFT);
284	SPEX(fem.ghz2.pdet_range, SSB_SPROM8_FEM2G, SSB_SROM8_FEM_PDET_RANGE,
285	     SSB_SROM8_FEM_PDET_RANGE_SHIFT);
286	SPEX(fem.ghz2.tr_iso, SSB_SPROM8_FEM2G, SSB_SROM8_FEM_TR_ISO,
287	     SSB_SROM8_FEM_TR_ISO_SHIFT);
288	SPEX(fem.ghz2.antswlut, SSB_SPROM8_FEM2G, SSB_SROM8_FEM_ANTSWLUT,
289	     SSB_SROM8_FEM_ANTSWLUT_SHIFT);
290
291	SPEX(fem.ghz5.tssipos, SSB_SPROM8_FEM5G, SSB_SROM8_FEM_TSSIPOS,
292	     SSB_SROM8_FEM_TSSIPOS_SHIFT);
293	SPEX(fem.ghz5.extpa_gain, SSB_SPROM8_FEM5G, SSB_SROM8_FEM_EXTPA_GAIN,
294	     SSB_SROM8_FEM_EXTPA_GAIN_SHIFT);
295	SPEX(fem.ghz5.pdet_range, SSB_SPROM8_FEM5G, SSB_SROM8_FEM_PDET_RANGE,
296	     SSB_SROM8_FEM_PDET_RANGE_SHIFT);
297	SPEX(fem.ghz5.tr_iso, SSB_SPROM8_FEM5G, SSB_SROM8_FEM_TR_ISO,
298	     SSB_SROM8_FEM_TR_ISO_SHIFT);
299	SPEX(fem.ghz5.antswlut, SSB_SPROM8_FEM5G, SSB_SROM8_FEM_ANTSWLUT,
300	     SSB_SROM8_FEM_ANTSWLUT_SHIFT);
301}
302
303static bool bcma_is_sprom_available(struct bcma_bus *bus)
304{
305	u32 sromctrl;
306
307	if (!(bus->drv_cc.capabilities & BCMA_CC_CAP_SPROM))
308		return false;
309
310	if (bus->drv_cc.core->id.rev >= 32) {
311		sromctrl = bcma_read32(bus->drv_cc.core, BCMA_CC_SROM_CONTROL);
312		return sromctrl & BCMA_CC_SROM_CONTROL_PRESENT;
313	}
314	return true;
315}
316
317int bcma_sprom_get(struct bcma_bus *bus)
318{
319	u16 offset;
320	u16 *sprom;
321	int err = 0;
322
323	if (!bus->drv_cc.core)
324		return -EOPNOTSUPP;
325
326	if (!bcma_is_sprom_available(bus)) {
327		/*
328		 * Maybe there is no SPROM on the device?
329		 * Now we ask the arch code if there is some sprom
330		 * available for this device in some other storage.
331		 */
332		err = bcma_fill_sprom_with_fallback(bus, &bus->sprom);
333		return err;
334	}
335
336	sprom = kcalloc(SSB_SPROMSIZE_WORDS_R4, sizeof(u16),
337			GFP_KERNEL);
338	if (!sprom)
339		return -ENOMEM;
340
341	if (bus->chipinfo.id == 0x4331)
342		bcma_chipco_bcm4331_ext_pa_lines_ctl(&bus->drv_cc, false);
343
344	/* Most cards have SPROM moved by additional offset 0x30 (48 dwords).
345	 * According to brcm80211 this applies to cards with PCIe rev >= 6
346	 * TODO: understand this condition and use it */
347	offset = (bus->chipinfo.id == 0x4331) ? BCMA_CC_SPROM :
348		BCMA_CC_SPROM_PCIE6;
349	pr_debug("SPROM offset 0x%x\n", offset);
350	bcma_sprom_read(bus, offset, sprom);
351
352	if (bus->chipinfo.id == 0x4331)
353		bcma_chipco_bcm4331_ext_pa_lines_ctl(&bus->drv_cc, true);
354
355	err = bcma_sprom_valid(sprom);
356	if (err)
357		goto out;
358
359	bcma_sprom_extract_r8(bus, sprom);
360
361out:
362	kfree(sprom);
363	return err;
364}
365