1197948Srpaulo/* 2197948Srpaulo * Copyright (c) 2009 Rui Paulo <rpaulo@FreeBSD.org> 3197948Srpaulo * Copyright (c) 2008 Sam Leffler, Errno Consulting 4197948Srpaulo * Copyright (c) 2008 Atheros Communications, Inc. 5197948Srpaulo * 6197948Srpaulo * Permission to use, copy, modify, and/or distribute this software for any 7197948Srpaulo * purpose with or without fee is hereby granted, provided that the above 8197948Srpaulo * copyright notice and this permission notice appear in all copies. 9197948Srpaulo * 10197948Srpaulo * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11197948Srpaulo * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12197948Srpaulo * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13197948Srpaulo * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14197948Srpaulo * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15197948Srpaulo * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16197948Srpaulo * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17197948Srpaulo * 18197948Srpaulo * $FreeBSD: releng/11.0/sys/dev/ath/ath_hal/ah_eeprom_v4k.c 234508 2012-04-20 21:56:13Z adrian $ 19197948Srpaulo */ 20197948Srpaulo#include "opt_ah.h" 21197948Srpaulo 22197948Srpaulo#include "ah.h" 23197948Srpaulo#include "ah_internal.h" 24197948Srpaulo#include "ah_eeprom_v14.h" 25197948Srpaulo#include "ah_eeprom_v4k.h" 26197948Srpaulo 27197948Srpaulostatic HAL_STATUS 28197948Srpaulov4kEepromGet(struct ath_hal *ah, int param, void *val) 29197948Srpaulo{ 30197948Srpaulo#define CHAN_A_IDX 0 31197948Srpaulo#define CHAN_B_IDX 1 32197948Srpaulo#define IS_VERS(op, v) ((pBase->version & AR5416_EEP_VER_MINOR_MASK) op (v)) 33197948Srpaulo HAL_EEPROM_v4k *ee = AH_PRIVATE(ah)->ah_eeprom; 34197948Srpaulo const MODAL_EEP4K_HEADER *pModal = &ee->ee_base.modalHeader; 35197948Srpaulo const BASE_EEP4K_HEADER *pBase = &ee->ee_base.baseEepHeader; 36197948Srpaulo uint32_t sum; 37197948Srpaulo uint8_t *macaddr; 38197948Srpaulo int i; 39197948Srpaulo 40197948Srpaulo switch (param) { 41197948Srpaulo case AR_EEP_NFTHRESH_2: 42208711Srpaulo *(int16_t *)val = pModal->noiseFloorThreshCh[0]; 43197948Srpaulo return HAL_OK; 44197948Srpaulo case AR_EEP_MACADDR: /* Get MAC Address */ 45197948Srpaulo sum = 0; 46197948Srpaulo macaddr = val; 47197948Srpaulo for (i = 0; i < 6; i++) { 48197948Srpaulo macaddr[i] = pBase->macAddr[i]; 49197948Srpaulo sum += pBase->macAddr[i]; 50197948Srpaulo } 51197948Srpaulo if (sum == 0 || sum == 0xffff*3) { 52197948Srpaulo HALDEBUG(ah, HAL_DEBUG_ANY, "%s: bad mac address %s\n", 53197948Srpaulo __func__, ath_hal_ether_sprintf(macaddr)); 54197948Srpaulo return HAL_EEBADMAC; 55197948Srpaulo } 56197948Srpaulo return HAL_OK; 57197948Srpaulo case AR_EEP_REGDMN_0: 58197948Srpaulo return pBase->regDmn[0]; 59197948Srpaulo case AR_EEP_REGDMN_1: 60197948Srpaulo return pBase->regDmn[1]; 61197948Srpaulo case AR_EEP_OPCAP: 62197948Srpaulo return pBase->deviceCap; 63197948Srpaulo case AR_EEP_OPMODE: 64197948Srpaulo return pBase->opCapFlags; 65197948Srpaulo case AR_EEP_RFSILENT: 66197948Srpaulo return pBase->rfSilent; 67197948Srpaulo case AR_EEP_OB_2: 68217809Sadrian return pModal->ob_0; 69197948Srpaulo case AR_EEP_DB_2: 70217809Sadrian return pModal->db1_1; 71197948Srpaulo case AR_EEP_TXMASK: 72197948Srpaulo return pBase->txMask; 73197948Srpaulo case AR_EEP_RXMASK: 74197948Srpaulo return pBase->rxMask; 75197948Srpaulo case AR_EEP_RXGAIN_TYPE: 76197948Srpaulo return AR5416_EEP_RXGAIN_ORIG; 77197948Srpaulo case AR_EEP_TXGAIN_TYPE: 78224624Sadrian return pBase->txGainType; 79197948Srpaulo case AR_EEP_OL_PWRCTRL: 80197948Srpaulo HALASSERT(val == AH_NULL); 81208711Srpaulo return HAL_EIO; 82197948Srpaulo case AR_EEP_AMODE: 83197948Srpaulo HALASSERT(val == AH_NULL); 84197948Srpaulo return pBase->opCapFlags & AR5416_OPFLAGS_11A ? 85197948Srpaulo HAL_OK : HAL_EIO; 86197948Srpaulo case AR_EEP_BMODE: 87197948Srpaulo case AR_EEP_GMODE: 88197948Srpaulo HALASSERT(val == AH_NULL); 89197948Srpaulo return pBase->opCapFlags & AR5416_OPFLAGS_11G ? 90197948Srpaulo HAL_OK : HAL_EIO; 91197948Srpaulo case AR_EEP_32KHZCRYSTAL: 92197948Srpaulo case AR_EEP_COMPRESS: 93197948Srpaulo case AR_EEP_FASTFRAME: /* XXX policy decision, h/w can do it */ 94197948Srpaulo case AR_EEP_WRITEPROTECT: /* NB: no write protect bit */ 95197948Srpaulo HALASSERT(val == AH_NULL); 96197948Srpaulo /* fall thru... */ 97197948Srpaulo case AR_EEP_MAXQCU: /* NB: not in opCapFlags */ 98197948Srpaulo case AR_EEP_KCENTRIES: /* NB: not in opCapFlags */ 99197948Srpaulo return HAL_EIO; 100197948Srpaulo case AR_EEP_AES: 101197948Srpaulo case AR_EEP_BURST: 102197948Srpaulo case AR_EEP_RFKILL: 103197948Srpaulo case AR_EEP_TURBO2DISABLE: 104197948Srpaulo HALASSERT(val == AH_NULL); 105197948Srpaulo return HAL_OK; 106197948Srpaulo case AR_EEP_ANTGAINMAX_2: 107208711Srpaulo *(int8_t *) val = ee->ee_antennaGainMax; 108197948Srpaulo return HAL_OK; 109197948Srpaulo default: 110197948Srpaulo HALASSERT(0); 111197948Srpaulo return HAL_EINVAL; 112197948Srpaulo } 113197948Srpaulo#undef IS_VERS 114197948Srpaulo#undef CHAN_A_IDX 115197948Srpaulo#undef CHAN_B_IDX 116197948Srpaulo} 117197948Srpaulo 118221896Sadrianstatic HAL_STATUS 119197948Srpaulov4kEepromSet(struct ath_hal *ah, int param, int v) 120197948Srpaulo{ 121197948Srpaulo HAL_EEPROM_v4k *ee = AH_PRIVATE(ah)->ah_eeprom; 122197948Srpaulo 123197948Srpaulo switch (param) { 124197948Srpaulo case AR_EEP_ANTGAINMAX_2: 125208711Srpaulo ee->ee_antennaGainMax = (int8_t) v; 126197948Srpaulo return HAL_OK; 127197948Srpaulo } 128197948Srpaulo return HAL_EINVAL; 129197948Srpaulo} 130197948Srpaulo 131197948Srpaulostatic HAL_BOOL 132197948Srpaulov4kEepromDiag(struct ath_hal *ah, int request, 133197948Srpaulo const void *args, uint32_t argsize, void **result, uint32_t *resultsize) 134197948Srpaulo{ 135197948Srpaulo HAL_EEPROM_v4k *ee = AH_PRIVATE(ah)->ah_eeprom; 136197948Srpaulo 137197948Srpaulo switch (request) { 138197948Srpaulo case HAL_DIAG_EEPROM: 139217685Sadrian *result = ee; 140217685Sadrian *resultsize = sizeof(HAL_EEPROM_v4k); 141197948Srpaulo return AH_TRUE; 142197948Srpaulo } 143197948Srpaulo return AH_FALSE; 144197948Srpaulo} 145197948Srpaulo 146197948Srpaulo/* Do structure specific swaps if Eeprom format is non native to host */ 147197948Srpaulostatic void 148197948SrpauloeepromSwap(struct ar5416eeprom_4k *ee) 149197948Srpaulo{ 150197948Srpaulo uint32_t integer, i; 151197948Srpaulo uint16_t word; 152197948Srpaulo MODAL_EEP4K_HEADER *pModal; 153197948Srpaulo 154197948Srpaulo /* convert Base Eep header */ 155197948Srpaulo word = __bswap16(ee->baseEepHeader.length); 156197948Srpaulo ee->baseEepHeader.length = word; 157197948Srpaulo 158197948Srpaulo word = __bswap16(ee->baseEepHeader.checksum); 159197948Srpaulo ee->baseEepHeader.checksum = word; 160197948Srpaulo 161197948Srpaulo word = __bswap16(ee->baseEepHeader.version); 162197948Srpaulo ee->baseEepHeader.version = word; 163197948Srpaulo 164197948Srpaulo word = __bswap16(ee->baseEepHeader.regDmn[0]); 165197948Srpaulo ee->baseEepHeader.regDmn[0] = word; 166197948Srpaulo 167197948Srpaulo word = __bswap16(ee->baseEepHeader.regDmn[1]); 168197948Srpaulo ee->baseEepHeader.regDmn[1] = word; 169197948Srpaulo 170197948Srpaulo word = __bswap16(ee->baseEepHeader.rfSilent); 171197948Srpaulo ee->baseEepHeader.rfSilent = word; 172197948Srpaulo 173197948Srpaulo word = __bswap16(ee->baseEepHeader.blueToothOptions); 174197948Srpaulo ee->baseEepHeader.blueToothOptions = word; 175197948Srpaulo 176197948Srpaulo word = __bswap16(ee->baseEepHeader.deviceCap); 177197948Srpaulo ee->baseEepHeader.deviceCap = word; 178197948Srpaulo 179197948Srpaulo /* convert Modal Eep header */ 180197948Srpaulo pModal = &ee->modalHeader; 181197948Srpaulo 182197948Srpaulo /* XXX linux/ah_osdep.h only defines __bswap32 for BE */ 183197948Srpaulo integer = __bswap32(pModal->antCtrlCommon); 184197948Srpaulo pModal->antCtrlCommon = integer; 185197948Srpaulo 186197948Srpaulo for (i = 0; i < AR5416_4K_MAX_CHAINS; i++) { 187197948Srpaulo integer = __bswap32(pModal->antCtrlChain[i]); 188197948Srpaulo pModal->antCtrlChain[i] = integer; 189197948Srpaulo } 190197948Srpaulo 191197948Srpaulo for (i = 0; i < AR5416_EEPROM_MODAL_SPURS; i++) { 192197948Srpaulo word = __bswap16(pModal->spurChans[i].spurChan); 193197948Srpaulo pModal->spurChans[i].spurChan = word; 194197948Srpaulo } 195197948Srpaulo} 196197948Srpaulo 197197948Srpaulostatic uint16_t 198197948Srpaulov4kEepromGetSpurChan(struct ath_hal *ah, int ix, HAL_BOOL is2GHz) 199197948Srpaulo{ 200197948Srpaulo HAL_EEPROM_v4k *ee = AH_PRIVATE(ah)->ah_eeprom; 201197948Srpaulo 202197948Srpaulo HALASSERT(0 <= ix && ix < AR5416_EEPROM_MODAL_SPURS); 203197948Srpaulo HALASSERT(is2GHz); 204197948Srpaulo return ee->ee_base.modalHeader.spurChans[ix].spurChan; 205197948Srpaulo} 206197948Srpaulo 207197948Srpaulo/************************************************************************** 208197948Srpaulo * fbin2freq 209197948Srpaulo * 210197948Srpaulo * Get channel value from binary representation held in eeprom 211197948Srpaulo * RETURNS: the frequency in MHz 212197948Srpaulo */ 213197948Srpaulostatic uint16_t 214197948Srpaulofbin2freq(uint8_t fbin, HAL_BOOL is2GHz) 215197948Srpaulo{ 216197948Srpaulo /* 217197948Srpaulo * Reserved value 0xFF provides an empty definition both as 218197948Srpaulo * an fbin and as a frequency - do not convert 219197948Srpaulo */ 220197948Srpaulo if (fbin == AR5416_BCHAN_UNUSED) 221197948Srpaulo return fbin; 222197948Srpaulo return (uint16_t)((is2GHz) ? (2300 + fbin) : (4800 + 5 * fbin)); 223197948Srpaulo} 224197948Srpaulo 225197948Srpaulo/* 226197948Srpaulo * Copy EEPROM Conformance Testing Limits contents 227197948Srpaulo * into the allocated space 228197948Srpaulo */ 229197948Srpaulo/* USE CTLS from chain zero */ 230197948Srpaulo#define CTL_CHAIN 0 231197948Srpaulo 232197948Srpaulostatic void 233197948Srpaulov4kEepromReadCTLInfo(struct ath_hal *ah, HAL_EEPROM_v4k *ee) 234197948Srpaulo{ 235197948Srpaulo RD_EDGES_POWER *rep = ee->ee_rdEdgesPower; 236197948Srpaulo int i, j; 237197948Srpaulo 238208711Srpaulo HALASSERT(AR5416_4K_NUM_CTLS <= sizeof(ee->ee_rdEdgesPower)/NUM_EDGES); 239197948Srpaulo 240197948Srpaulo for (i = 0; ee->ee_base.ctlIndex[i] != 0 && i < AR5416_4K_NUM_CTLS; i++) { 241197948Srpaulo for (j = 0; j < NUM_EDGES; j ++) { 242197948Srpaulo /* XXX Confirm this is the right thing to do when an invalid channel is stored */ 243197948Srpaulo if (ee->ee_base.ctlData[i].ctlEdges[CTL_CHAIN][j].bChannel == AR5416_BCHAN_UNUSED) { 244197948Srpaulo rep[j].rdEdge = 0; 245197948Srpaulo rep[j].twice_rdEdgePower = 0; 246197948Srpaulo rep[j].flag = 0; 247197948Srpaulo } else { 248197948Srpaulo rep[j].rdEdge = fbin2freq( 249197948Srpaulo ee->ee_base.ctlData[i].ctlEdges[CTL_CHAIN][j].bChannel, 250197948Srpaulo (ee->ee_base.ctlIndex[i] & CTL_MODE_M) != CTL_11A); 251197948Srpaulo rep[j].twice_rdEdgePower = MS(ee->ee_base.ctlData[i].ctlEdges[CTL_CHAIN][j].tPowerFlag, CAL_CTL_EDGES_POWER); 252197948Srpaulo rep[j].flag = MS(ee->ee_base.ctlData[i].ctlEdges[CTL_CHAIN][j].tPowerFlag, CAL_CTL_EDGES_FLAG) != 0; 253197948Srpaulo } 254197948Srpaulo } 255197948Srpaulo rep += NUM_EDGES; 256197948Srpaulo } 257197948Srpaulo ee->ee_numCtls = i; 258197948Srpaulo HALDEBUG(ah, HAL_DEBUG_ATTACH | HAL_DEBUG_EEPROM, 259197948Srpaulo "%s Numctls = %u\n",__func__,i); 260197948Srpaulo} 261197948Srpaulo 262197948Srpaulo/* 263197948Srpaulo * Reclaim any EEPROM-related storage. 264197948Srpaulo */ 265197948Srpaulostatic void 266197948Srpaulov4kEepromDetach(struct ath_hal *ah) 267197948Srpaulo{ 268197948Srpaulo HAL_EEPROM_v4k *ee = AH_PRIVATE(ah)->ah_eeprom; 269197948Srpaulo 270197948Srpaulo ath_hal_free(ee); 271197948Srpaulo AH_PRIVATE(ah)->ah_eeprom = AH_NULL; 272197948Srpaulo} 273197948Srpaulo 274197948Srpaulo#define owl_get_eep_ver(_ee) \ 275197948Srpaulo (((_ee)->ee_base.baseEepHeader.version >> 12) & 0xF) 276197948Srpaulo#define owl_get_eep_rev(_ee) \ 277197948Srpaulo (((_ee)->ee_base.baseEepHeader.version) & 0xFFF) 278197948Srpaulo 279197948SrpauloHAL_STATUS 280197948Srpauloath_hal_v4kEepromAttach(struct ath_hal *ah) 281197948Srpaulo{ 282197948Srpaulo#define NW(a) (sizeof(a) / sizeof(uint16_t)) 283197948Srpaulo HAL_EEPROM_v4k *ee = AH_PRIVATE(ah)->ah_eeprom; 284197948Srpaulo uint16_t *eep_data, magic; 285197948Srpaulo HAL_BOOL need_swap; 286197948Srpaulo u_int w, off, len; 287197948Srpaulo uint32_t sum; 288197948Srpaulo 289197948Srpaulo HALASSERT(ee == AH_NULL); 290224518Sadrian /* 291224518Sadrian * Don't check magic if we're supplied with an EEPROM block, 292224518Sadrian * typically this is from Howl but it may also be from later 293224518Sadrian * boards w/ an embedded WMAC. 294224518Sadrian */ 295224518Sadrian if (ah->ah_eepromdata == NULL) { 296224518Sadrian if (!ath_hal_eepromRead(ah, AR5416_EEPROM_MAGIC_OFFSET, &magic)) { 297224518Sadrian HALDEBUG(ah, HAL_DEBUG_ANY, 298224518Sadrian "%s Error reading Eeprom MAGIC\n", __func__); 299224518Sadrian return HAL_EEREAD; 300224518Sadrian } 301234508Sadrian HALDEBUG(ah, HAL_DEBUG_ATTACH, "%s Eeprom Magic = 0x%x\n", 302234508Sadrian __func__, magic); 303234508Sadrian if (magic != AR5416_EEPROM_MAGIC) { 304234508Sadrian HALDEBUG(ah, HAL_DEBUG_ANY, "Bad magic number\n"); 305234508Sadrian return HAL_EEMAGIC; 306234508Sadrian } 307197948Srpaulo } 308197948Srpaulo 309197948Srpaulo ee = ath_hal_malloc(sizeof(HAL_EEPROM_v4k)); 310197948Srpaulo if (ee == AH_NULL) { 311197948Srpaulo /* XXX message */ 312197948Srpaulo return HAL_ENOMEM; 313197948Srpaulo } 314197948Srpaulo 315197948Srpaulo eep_data = (uint16_t *)&ee->ee_base; 316197948Srpaulo for (w = 0; w < NW(struct ar5416eeprom_4k); w++) { 317197948Srpaulo off = owl_eep_start_loc + w; /* NB: AP71 starts at 0 */ 318197948Srpaulo if (!ath_hal_eepromRead(ah, off, &eep_data[w])) { 319197948Srpaulo HALDEBUG(ah, HAL_DEBUG_ANY, 320197948Srpaulo "%s eeprom read error at offset 0x%x\n", 321197948Srpaulo __func__, off); 322197948Srpaulo return HAL_EEREAD; 323197948Srpaulo } 324197948Srpaulo } 325197948Srpaulo /* Convert to eeprom native eeprom endian format */ 326224518Sadrian /* 327224518Sadrian * XXX this is likely incorrect but will do for now 328224518Sadrian * XXX to get embedded boards working. 329224518Sadrian */ 330224518Sadrian if (ah->ah_eepromdata == NULL && isBigEndian()) { 331197948Srpaulo for (w = 0; w < NW(struct ar5416eeprom_4k); w++) 332197948Srpaulo eep_data[w] = __bswap16(eep_data[w]); 333197948Srpaulo } 334197948Srpaulo 335197948Srpaulo /* 336197948Srpaulo * At this point, we're in the native eeprom endian format 337197948Srpaulo * Now, determine the eeprom endian by looking at byte 26?? 338197948Srpaulo */ 339197948Srpaulo need_swap = ((ee->ee_base.baseEepHeader.eepMisc & AR5416_EEPMISC_BIG_ENDIAN) != 0) ^ isBigEndian(); 340197948Srpaulo if (need_swap) { 341197948Srpaulo HALDEBUG(ah, HAL_DEBUG_ATTACH | HAL_DEBUG_EEPROM, 342197948Srpaulo "Byte swap EEPROM contents.\n"); 343197948Srpaulo len = __bswap16(ee->ee_base.baseEepHeader.length); 344197948Srpaulo } else { 345197948Srpaulo len = ee->ee_base.baseEepHeader.length; 346197948Srpaulo } 347197948Srpaulo len = AH_MIN(len, sizeof(struct ar5416eeprom_4k)) / sizeof(uint16_t); 348197948Srpaulo 349197948Srpaulo /* Apply the checksum, done in native eeprom format */ 350197948Srpaulo /* XXX - Need to check to make sure checksum calculation is done 351197948Srpaulo * in the correct endian format. Right now, it seems it would 352197948Srpaulo * cast the raw data to host format and do the calculation, which may 353197948Srpaulo * not be correct as the calculation may need to be done in the native 354197948Srpaulo * eeprom format 355197948Srpaulo */ 356197948Srpaulo sum = 0; 357197948Srpaulo for (w = 0; w < len; w++) { 358197948Srpaulo sum ^= eep_data[w]; 359197948Srpaulo } 360197948Srpaulo /* Check CRC - Attach should fail on a bad checksum */ 361197948Srpaulo if (sum != 0xffff) { 362197948Srpaulo HALDEBUG(ah, HAL_DEBUG_ANY, 363197948Srpaulo "Bad EEPROM checksum 0x%x (Len=%u)\n", sum, len); 364197948Srpaulo return HAL_EEBADSUM; 365197948Srpaulo } 366197948Srpaulo 367197948Srpaulo if (need_swap) 368197948Srpaulo eepromSwap(&ee->ee_base); /* byte swap multi-byte data */ 369197948Srpaulo 370197948Srpaulo /* swap words 0+2 so version is at the front */ 371197948Srpaulo magic = eep_data[0]; 372197948Srpaulo eep_data[0] = eep_data[2]; 373197948Srpaulo eep_data[2] = magic; 374197948Srpaulo 375197948Srpaulo HALDEBUG(ah, HAL_DEBUG_ATTACH | HAL_DEBUG_EEPROM, 376197948Srpaulo "%s Eeprom Version %u.%u\n", __func__, 377197948Srpaulo owl_get_eep_ver(ee), owl_get_eep_rev(ee)); 378197948Srpaulo 379197948Srpaulo /* NB: must be after all byte swapping */ 380197948Srpaulo if (owl_get_eep_ver(ee) != AR5416_EEP_VER) { 381197948Srpaulo HALDEBUG(ah, HAL_DEBUG_ANY, 382197948Srpaulo "Bad EEPROM version 0x%x\n", owl_get_eep_ver(ee)); 383197948Srpaulo return HAL_EEBADSUM; 384197948Srpaulo } 385197948Srpaulo 386197948Srpaulo v4kEepromReadCTLInfo(ah, ee); /* Get CTLs */ 387197948Srpaulo 388197948Srpaulo AH_PRIVATE(ah)->ah_eeprom = ee; 389197948Srpaulo AH_PRIVATE(ah)->ah_eeversion = ee->ee_base.baseEepHeader.version; 390197948Srpaulo AH_PRIVATE(ah)->ah_eepromDetach = v4kEepromDetach; 391197948Srpaulo AH_PRIVATE(ah)->ah_eepromGet = v4kEepromGet; 392197948Srpaulo AH_PRIVATE(ah)->ah_eepromSet = v4kEepromSet; 393197948Srpaulo AH_PRIVATE(ah)->ah_getSpurChan = v4kEepromGetSpurChan; 394197948Srpaulo AH_PRIVATE(ah)->ah_eepromDiag = v4kEepromDiag; 395197948Srpaulo return HAL_OK; 396197948Srpaulo#undef NW 397197948Srpaulo} 398