blob: 916ae255ff641b590d7835adcb94b56d00cf7ebb [file] [log] [blame]
Rafał Miłecki27f18dc2011-06-02 02:08:51 +02001/*
2 * Broadcom specific AMBA
3 * SPROM reading
4 *
Hauke Mehrtensa0272372012-02-28 00:56:10 +01005 * Copyright 2011, 2012, Hauke Mehrtens <hauke@hauke-m.de>
6 *
Rafał Miłecki27f18dc2011-06-02 02:08:51 +02007 * 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
Hauke Mehrtensa0272372012-02-28 00:56:10 +010019static 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 if (!get_fallback_sprom)
53 return -ENOENT;
54
55 return get_fallback_sprom(bus, out);
56}
57
Rafał Miłecki27f18dc2011-06-02 02:08:51 +020058/**************************************************
59 * R/W ops.
60 **************************************************/
61
Rafał Miłeckieb1577b2011-07-17 11:00:59 +020062static void bcma_sprom_read(struct bcma_bus *bus, u16 offset, u16 *sprom)
Rafał Miłecki27f18dc2011-06-02 02:08:51 +020063{
64 int i;
65 for (i = 0; i < SSB_SPROMSIZE_WORDS_R4; i++)
66 sprom[i] = bcma_read16(bus->drv_cc.core,
Rafał Miłeckieb1577b2011-07-17 11:00:59 +020067 offset + (i * 2));
Rafał Miłecki27f18dc2011-06-02 02:08:51 +020068}
69
70/**************************************************
71 * Validation.
72 **************************************************/
73
74static inline u8 bcma_crc8(u8 crc, u8 data)
75{
76 /* Polynomial: x^8 + x^7 + x^6 + x^4 + x^2 + 1 */
77 static const u8 t[] = {
78 0x00, 0xF7, 0xB9, 0x4E, 0x25, 0xD2, 0x9C, 0x6B,
79 0x4A, 0xBD, 0xF3, 0x04, 0x6F, 0x98, 0xD6, 0x21,
80 0x94, 0x63, 0x2D, 0xDA, 0xB1, 0x46, 0x08, 0xFF,
81 0xDE, 0x29, 0x67, 0x90, 0xFB, 0x0C, 0x42, 0xB5,
82 0x7F, 0x88, 0xC6, 0x31, 0x5A, 0xAD, 0xE3, 0x14,
83 0x35, 0xC2, 0x8C, 0x7B, 0x10, 0xE7, 0xA9, 0x5E,
84 0xEB, 0x1C, 0x52, 0xA5, 0xCE, 0x39, 0x77, 0x80,
85 0xA1, 0x56, 0x18, 0xEF, 0x84, 0x73, 0x3D, 0xCA,
86 0xFE, 0x09, 0x47, 0xB0, 0xDB, 0x2C, 0x62, 0x95,
87 0xB4, 0x43, 0x0D, 0xFA, 0x91, 0x66, 0x28, 0xDF,
88 0x6A, 0x9D, 0xD3, 0x24, 0x4F, 0xB8, 0xF6, 0x01,
89 0x20, 0xD7, 0x99, 0x6E, 0x05, 0xF2, 0xBC, 0x4B,
90 0x81, 0x76, 0x38, 0xCF, 0xA4, 0x53, 0x1D, 0xEA,
91 0xCB, 0x3C, 0x72, 0x85, 0xEE, 0x19, 0x57, 0xA0,
92 0x15, 0xE2, 0xAC, 0x5B, 0x30, 0xC7, 0x89, 0x7E,
93 0x5F, 0xA8, 0xE6, 0x11, 0x7A, 0x8D, 0xC3, 0x34,
94 0xAB, 0x5C, 0x12, 0xE5, 0x8E, 0x79, 0x37, 0xC0,
95 0xE1, 0x16, 0x58, 0xAF, 0xC4, 0x33, 0x7D, 0x8A,
96 0x3F, 0xC8, 0x86, 0x71, 0x1A, 0xED, 0xA3, 0x54,
97 0x75, 0x82, 0xCC, 0x3B, 0x50, 0xA7, 0xE9, 0x1E,
98 0xD4, 0x23, 0x6D, 0x9A, 0xF1, 0x06, 0x48, 0xBF,
99 0x9E, 0x69, 0x27, 0xD0, 0xBB, 0x4C, 0x02, 0xF5,
100 0x40, 0xB7, 0xF9, 0x0E, 0x65, 0x92, 0xDC, 0x2B,
101 0x0A, 0xFD, 0xB3, 0x44, 0x2F, 0xD8, 0x96, 0x61,
102 0x55, 0xA2, 0xEC, 0x1B, 0x70, 0x87, 0xC9, 0x3E,
103 0x1F, 0xE8, 0xA6, 0x51, 0x3A, 0xCD, 0x83, 0x74,
104 0xC1, 0x36, 0x78, 0x8F, 0xE4, 0x13, 0x5D, 0xAA,
105 0x8B, 0x7C, 0x32, 0xC5, 0xAE, 0x59, 0x17, 0xE0,
106 0x2A, 0xDD, 0x93, 0x64, 0x0F, 0xF8, 0xB6, 0x41,
107 0x60, 0x97, 0xD9, 0x2E, 0x45, 0xB2, 0xFC, 0x0B,
108 0xBE, 0x49, 0x07, 0xF0, 0x9B, 0x6C, 0x22, 0xD5,
109 0xF4, 0x03, 0x4D, 0xBA, 0xD1, 0x26, 0x68, 0x9F,
110 };
111 return t[crc ^ data];
112}
113
114static u8 bcma_sprom_crc(const u16 *sprom)
115{
116 int word;
117 u8 crc = 0xFF;
118
119 for (word = 0; word < SSB_SPROMSIZE_WORDS_R4 - 1; word++) {
120 crc = bcma_crc8(crc, sprom[word] & 0x00FF);
121 crc = bcma_crc8(crc, (sprom[word] & 0xFF00) >> 8);
122 }
123 crc = bcma_crc8(crc, sprom[SSB_SPROMSIZE_WORDS_R4 - 1] & 0x00FF);
124 crc ^= 0xFF;
125
126 return crc;
127}
128
129static int bcma_sprom_check_crc(const u16 *sprom)
130{
131 u8 crc;
132 u8 expected_crc;
133 u16 tmp;
134
135 crc = bcma_sprom_crc(sprom);
136 tmp = sprom[SSB_SPROMSIZE_WORDS_R4 - 1] & SSB_SPROM_REVISION_CRC;
137 expected_crc = tmp >> SSB_SPROM_REVISION_CRC_SHIFT;
138 if (crc != expected_crc)
139 return -EPROTO;
140
141 return 0;
142}
143
144static int bcma_sprom_valid(const u16 *sprom)
145{
146 u16 revision;
147 int err;
148
149 err = bcma_sprom_check_crc(sprom);
150 if (err)
151 return err;
152
153 revision = sprom[SSB_SPROMSIZE_WORDS_R4 - 1] & SSB_SPROM_REVISION_REV;
Rafał Miłeckic54dcd12011-07-14 21:49:21 +0200154 if (revision != 8 && revision != 9) {
Rafał Miłecki27f18dc2011-06-02 02:08:51 +0200155 pr_err("Unsupported SPROM revision: %d\n", revision);
156 return -ENOENT;
157 }
158
159 return 0;
160}
161
162/**************************************************
163 * SPROM extraction.
164 **************************************************/
165
Rafał Miłeckib35a9ac2012-01-02 08:41:24 +0100166#define SPOFF(offset) ((offset) / sizeof(u16))
167
168#define SPEX(_field, _offset, _mask, _shift) \
169 bus->sprom._field = ((sprom[SPOFF(_offset)] & (_mask)) >> (_shift))
170
Rafał Miłecki27f18dc2011-06-02 02:08:51 +0200171static void bcma_sprom_extract_r8(struct bcma_bus *bus, const u16 *sprom)
172{
Rafał Miłecki507f9a72012-01-02 08:41:25 +0100173 u16 v, o;
Rafał Miłecki27f18dc2011-06-02 02:08:51 +0200174 int i;
Rafał Miłecki507f9a72012-01-02 08:41:25 +0100175 u16 pwr_info_offset[] = {
176 SSB_SROM8_PWR_INFO_CORE0, SSB_SROM8_PWR_INFO_CORE1,
177 SSB_SROM8_PWR_INFO_CORE2, SSB_SROM8_PWR_INFO_CORE3
178 };
179 BUILD_BUG_ON(ARRAY_SIZE(pwr_info_offset) !=
180 ARRAY_SIZE(bus->sprom.core_pwr_info));
Rafał Miłecki27f18dc2011-06-02 02:08:51 +0200181
Rafał Miłeckidaadc6b2011-12-12 21:33:12 +0100182 bus->sprom.revision = sprom[SSB_SPROMSIZE_WORDS_R4 - 1] &
183 SSB_SPROM_REVISION_REV;
184
Rafał Miłecki27f18dc2011-06-02 02:08:51 +0200185 for (i = 0; i < 3; i++) {
186 v = sprom[SPOFF(SSB_SPROM8_IL0MAC) + i];
187 *(((__be16 *)bus->sprom.il0mac) + i) = cpu_to_be16(v);
188 }
Rafał Miłeckid703a5a2011-08-28 18:47:23 +0200189
Rafał Miłeckib35a9ac2012-01-02 08:41:24 +0100190 SPEX(board_rev, SSB_SPROM8_BOARDREV, ~0, 0);
Rafał Miłeckid703a5a2011-08-28 18:47:23 +0200191
Rafał Miłeckib35a9ac2012-01-02 08:41:24 +0100192 SPEX(txpid2g[0], SSB_SPROM4_TXPID2G01, SSB_SPROM4_TXPID2G0,
193 SSB_SPROM4_TXPID2G0_SHIFT);
194 SPEX(txpid2g[1], SSB_SPROM4_TXPID2G01, SSB_SPROM4_TXPID2G1,
195 SSB_SPROM4_TXPID2G1_SHIFT);
196 SPEX(txpid2g[2], SSB_SPROM4_TXPID2G23, SSB_SPROM4_TXPID2G2,
197 SSB_SPROM4_TXPID2G2_SHIFT);
198 SPEX(txpid2g[3], SSB_SPROM4_TXPID2G23, SSB_SPROM4_TXPID2G3,
199 SSB_SPROM4_TXPID2G3_SHIFT);
Rafał Miłeckidaadc6b2011-12-12 21:33:12 +0100200
Rafał Miłeckib35a9ac2012-01-02 08:41:24 +0100201 SPEX(txpid5gl[0], SSB_SPROM4_TXPID5GL01, SSB_SPROM4_TXPID5GL0,
202 SSB_SPROM4_TXPID5GL0_SHIFT);
203 SPEX(txpid5gl[1], SSB_SPROM4_TXPID5GL01, SSB_SPROM4_TXPID5GL1,
204 SSB_SPROM4_TXPID5GL1_SHIFT);
205 SPEX(txpid5gl[2], SSB_SPROM4_TXPID5GL23, SSB_SPROM4_TXPID5GL2,
206 SSB_SPROM4_TXPID5GL2_SHIFT);
207 SPEX(txpid5gl[3], SSB_SPROM4_TXPID5GL23, SSB_SPROM4_TXPID5GL3,
208 SSB_SPROM4_TXPID5GL3_SHIFT);
Rafał Miłeckidaadc6b2011-12-12 21:33:12 +0100209
Rafał Miłeckib35a9ac2012-01-02 08:41:24 +0100210 SPEX(txpid5g[0], SSB_SPROM4_TXPID5G01, SSB_SPROM4_TXPID5G0,
211 SSB_SPROM4_TXPID5G0_SHIFT);
212 SPEX(txpid5g[1], SSB_SPROM4_TXPID5G01, SSB_SPROM4_TXPID5G1,
213 SSB_SPROM4_TXPID5G1_SHIFT);
214 SPEX(txpid5g[2], SSB_SPROM4_TXPID5G23, SSB_SPROM4_TXPID5G2,
215 SSB_SPROM4_TXPID5G2_SHIFT);
216 SPEX(txpid5g[3], SSB_SPROM4_TXPID5G23, SSB_SPROM4_TXPID5G3,
217 SSB_SPROM4_TXPID5G3_SHIFT);
Rafał Miłeckidaadc6b2011-12-12 21:33:12 +0100218
Rafał Miłeckib35a9ac2012-01-02 08:41:24 +0100219 SPEX(txpid5gh[0], SSB_SPROM4_TXPID5GH01, SSB_SPROM4_TXPID5GH0,
220 SSB_SPROM4_TXPID5GH0_SHIFT);
221 SPEX(txpid5gh[1], SSB_SPROM4_TXPID5GH01, SSB_SPROM4_TXPID5GH1,
222 SSB_SPROM4_TXPID5GH1_SHIFT);
223 SPEX(txpid5gh[2], SSB_SPROM4_TXPID5GH23, SSB_SPROM4_TXPID5GH2,
224 SSB_SPROM4_TXPID5GH2_SHIFT);
225 SPEX(txpid5gh[3], SSB_SPROM4_TXPID5GH23, SSB_SPROM4_TXPID5GH3,
226 SSB_SPROM4_TXPID5GH3_SHIFT);
Rafał Miłeckidaadc6b2011-12-12 21:33:12 +0100227
Rafał Miłeckib35a9ac2012-01-02 08:41:24 +0100228 SPEX(boardflags_lo, SSB_SPROM8_BFLLO, ~0, 0);
229 SPEX(boardflags_hi, SSB_SPROM8_BFLHI, ~0, 0);
230 SPEX(boardflags2_lo, SSB_SPROM8_BFL2LO, ~0, 0);
231 SPEX(boardflags2_hi, SSB_SPROM8_BFL2HI, ~0, 0);
Rafał Miłeckid703a5a2011-08-28 18:47:23 +0200232
Rafał Miłeckib35a9ac2012-01-02 08:41:24 +0100233 SPEX(country_code, SSB_SPROM8_CCODE, ~0, 0);
Rafał Miłeckiaee5ed52011-12-08 18:02:22 +0100234
Rafał Miłecki507f9a72012-01-02 08:41:25 +0100235 /* Extract cores power info info */
236 for (i = 0; i < ARRAY_SIZE(pwr_info_offset); i++) {
237 o = pwr_info_offset[i];
238 SPEX(core_pwr_info[i].itssi_2g, o + SSB_SROM8_2G_MAXP_ITSSI,
239 SSB_SPROM8_2G_ITSSI, SSB_SPROM8_2G_ITSSI_SHIFT);
240 SPEX(core_pwr_info[i].maxpwr_2g, o + SSB_SROM8_2G_MAXP_ITSSI,
241 SSB_SPROM8_2G_MAXP, 0);
242
243 SPEX(core_pwr_info[i].pa_2g[0], o + SSB_SROM8_2G_PA_0, ~0, 0);
244 SPEX(core_pwr_info[i].pa_2g[1], o + SSB_SROM8_2G_PA_1, ~0, 0);
245 SPEX(core_pwr_info[i].pa_2g[2], o + SSB_SROM8_2G_PA_2, ~0, 0);
246
247 SPEX(core_pwr_info[i].itssi_5g, o + SSB_SROM8_5G_MAXP_ITSSI,
248 SSB_SPROM8_5G_ITSSI, SSB_SPROM8_5G_ITSSI_SHIFT);
249 SPEX(core_pwr_info[i].maxpwr_5g, o + SSB_SROM8_5G_MAXP_ITSSI,
250 SSB_SPROM8_5G_MAXP, 0);
251 SPEX(core_pwr_info[i].maxpwr_5gh, o + SSB_SPROM8_5GHL_MAXP,
252 SSB_SPROM8_5GH_MAXP, 0);
253 SPEX(core_pwr_info[i].maxpwr_5gl, o + SSB_SPROM8_5GHL_MAXP,
254 SSB_SPROM8_5GL_MAXP, SSB_SPROM8_5GL_MAXP_SHIFT);
255
256 SPEX(core_pwr_info[i].pa_5gl[0], o + SSB_SROM8_5GL_PA_0, ~0, 0);
257 SPEX(core_pwr_info[i].pa_5gl[1], o + SSB_SROM8_5GL_PA_1, ~0, 0);
258 SPEX(core_pwr_info[i].pa_5gl[2], o + SSB_SROM8_5GL_PA_2, ~0, 0);
259 SPEX(core_pwr_info[i].pa_5g[0], o + SSB_SROM8_5G_PA_0, ~0, 0);
260 SPEX(core_pwr_info[i].pa_5g[1], o + SSB_SROM8_5G_PA_1, ~0, 0);
261 SPEX(core_pwr_info[i].pa_5g[2], o + SSB_SROM8_5G_PA_2, ~0, 0);
262 SPEX(core_pwr_info[i].pa_5gh[0], o + SSB_SROM8_5GH_PA_0, ~0, 0);
263 SPEX(core_pwr_info[i].pa_5gh[1], o + SSB_SROM8_5GH_PA_1, ~0, 0);
264 SPEX(core_pwr_info[i].pa_5gh[2], o + SSB_SROM8_5GH_PA_2, ~0, 0);
265 }
266
Rafał Miłeckib35a9ac2012-01-02 08:41:24 +0100267 SPEX(fem.ghz2.tssipos, SSB_SPROM8_FEM2G, SSB_SROM8_FEM_TSSIPOS,
268 SSB_SROM8_FEM_TSSIPOS_SHIFT);
269 SPEX(fem.ghz2.extpa_gain, SSB_SPROM8_FEM2G, SSB_SROM8_FEM_EXTPA_GAIN,
270 SSB_SROM8_FEM_EXTPA_GAIN_SHIFT);
271 SPEX(fem.ghz2.pdet_range, SSB_SPROM8_FEM2G, SSB_SROM8_FEM_PDET_RANGE,
272 SSB_SROM8_FEM_PDET_RANGE_SHIFT);
273 SPEX(fem.ghz2.tr_iso, SSB_SPROM8_FEM2G, SSB_SROM8_FEM_TR_ISO,
274 SSB_SROM8_FEM_TR_ISO_SHIFT);
275 SPEX(fem.ghz2.antswlut, SSB_SPROM8_FEM2G, SSB_SROM8_FEM_ANTSWLUT,
276 SSB_SROM8_FEM_ANTSWLUT_SHIFT);
Rafał Miłeckiaee5ed52011-12-08 18:02:22 +0100277
Rafał Miłeckib35a9ac2012-01-02 08:41:24 +0100278 SPEX(fem.ghz5.tssipos, SSB_SPROM8_FEM5G, SSB_SROM8_FEM_TSSIPOS,
279 SSB_SROM8_FEM_TSSIPOS_SHIFT);
280 SPEX(fem.ghz5.extpa_gain, SSB_SPROM8_FEM5G, SSB_SROM8_FEM_EXTPA_GAIN,
281 SSB_SROM8_FEM_EXTPA_GAIN_SHIFT);
282 SPEX(fem.ghz5.pdet_range, SSB_SPROM8_FEM5G, SSB_SROM8_FEM_PDET_RANGE,
283 SSB_SROM8_FEM_PDET_RANGE_SHIFT);
284 SPEX(fem.ghz5.tr_iso, SSB_SPROM8_FEM5G, SSB_SROM8_FEM_TR_ISO,
285 SSB_SROM8_FEM_TR_ISO_SHIFT);
286 SPEX(fem.ghz5.antswlut, SSB_SPROM8_FEM5G, SSB_SROM8_FEM_ANTSWLUT,
287 SSB_SROM8_FEM_ANTSWLUT_SHIFT);
Rafał Miłecki27f18dc2011-06-02 02:08:51 +0200288}
289
Hauke Mehrtensa0272372012-02-28 00:56:10 +0100290static bool bcma_is_sprom_available(struct bcma_bus *bus)
291{
292 u32 sromctrl;
293
294 if (!(bus->drv_cc.capabilities & BCMA_CC_CAP_SPROM))
295 return false;
296
297 if (bus->drv_cc.core->id.rev >= 32) {
298 sromctrl = bcma_read32(bus->drv_cc.core, BCMA_CC_SROM_CONTROL);
299 return sromctrl & BCMA_CC_SROM_CONTROL_PRESENT;
300 }
301 return true;
302}
303
Rafał Miłecki27f18dc2011-06-02 02:08:51 +0200304int bcma_sprom_get(struct bcma_bus *bus)
305{
Rafał Miłeckieb1577b2011-07-17 11:00:59 +0200306 u16 offset;
Rafał Miłecki27f18dc2011-06-02 02:08:51 +0200307 u16 *sprom;
308 int err = 0;
309
310 if (!bus->drv_cc.core)
311 return -EOPNOTSUPP;
312
Hauke Mehrtensa0272372012-02-28 00:56:10 +0100313 if (!bcma_is_sprom_available(bus)) {
314 /*
315 * Maybe there is no SPROM on the device?
316 * Now we ask the arch code if there is some sprom
317 * available for this device in some other storage.
318 */
319 err = bcma_fill_sprom_with_fallback(bus, &bus->sprom);
320 if (err) {
321 pr_warn("Using fallback SPROM failed (err %d)\n", err);
322 } else {
323 pr_debug("Using SPROM revision %d provided by"
324 " platform.\n", bus->sprom.revision);
325 return 0;
326 }
Hauke Mehrtensd6865dcc2012-01-31 00:03:37 +0100327 }
328
Rafał Miłecki27f18dc2011-06-02 02:08:51 +0200329 sprom = kcalloc(SSB_SPROMSIZE_WORDS_R4, sizeof(u16),
330 GFP_KERNEL);
331 if (!sprom)
332 return -ENOMEM;
333
Rafał Miłecki984e5be2011-08-11 23:46:44 +0200334 if (bus->chipinfo.id == 0x4331)
335 bcma_chipco_bcm4331_ext_pa_lines_ctl(&bus->drv_cc, false);
336
Rafał Miłeckieb1577b2011-07-17 11:00:59 +0200337 /* Most cards have SPROM moved by additional offset 0x30 (48 dwords).
338 * According to brcm80211 this applies to cards with PCIe rev >= 6
339 * TODO: understand this condition and use it */
340 offset = (bus->chipinfo.id == 0x4331) ? BCMA_CC_SPROM :
341 BCMA_CC_SPROM_PCIE6;
Larry Fingercebcab92012-01-11 14:39:32 -0600342 pr_debug("SPROM offset 0x%x\n", offset);
Rafał Miłeckieb1577b2011-07-17 11:00:59 +0200343 bcma_sprom_read(bus, offset, sprom);
Rafał Miłecki27f18dc2011-06-02 02:08:51 +0200344
Rafał Miłecki984e5be2011-08-11 23:46:44 +0200345 if (bus->chipinfo.id == 0x4331)
346 bcma_chipco_bcm4331_ext_pa_lines_ctl(&bus->drv_cc, true);
347
Rafał Miłecki27f18dc2011-06-02 02:08:51 +0200348 err = bcma_sprom_valid(sprom);
349 if (err)
350 goto out;
351
352 bcma_sprom_extract_r8(bus, sprom);
353
354out:
355 kfree(sprom);
356 return err;
357}