112e36acbSWarner Losh /* 212e36acbSWarner Losh * Copyright (c) 2007 The DragonFly Project. All rights reserved. 312e36acbSWarner Losh * 412e36acbSWarner Losh * This code is derived from software contributed to The DragonFly Project 512e36acbSWarner Losh * by Sepherosa Ziehau <sepherosa@gmail.com> 612e36acbSWarner Losh * 712e36acbSWarner Losh * Redistribution and use in source and binary forms, with or without 812e36acbSWarner Losh * modification, are permitted provided that the following conditions 912e36acbSWarner Losh * are met: 1012e36acbSWarner Losh * 1112e36acbSWarner Losh * 1. Redistributions of source code must retain the above copyright 1212e36acbSWarner Losh * notice, this list of conditions and the following disclaimer. 1312e36acbSWarner Losh * 2. Redistributions in binary form must reproduce the above copyright 1412e36acbSWarner Losh * notice, this list of conditions and the following disclaimer in 1512e36acbSWarner Losh * the documentation and/or other materials provided with the 1612e36acbSWarner Losh * distribution. 1712e36acbSWarner Losh * 3. Neither the name of The DragonFly Project nor the names of its 1812e36acbSWarner Losh * contributors may be used to endorse or promote products derived 1912e36acbSWarner Losh * from this software without specific, prior written permission. 2012e36acbSWarner Losh * 2112e36acbSWarner Losh * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 2212e36acbSWarner Losh * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 2312e36acbSWarner Losh * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 2412e36acbSWarner Losh * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 2512e36acbSWarner Losh * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 2612e36acbSWarner Losh * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, 2712e36acbSWarner Losh * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 2812e36acbSWarner Losh * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 2912e36acbSWarner Losh * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 3012e36acbSWarner Losh * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 3112e36acbSWarner Losh * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 3212e36acbSWarner Losh * SUCH DAMAGE. 3312e36acbSWarner Losh * 3412e36acbSWarner Losh * $DragonFly: src/sys/dev/netif/bwi/bwiphy.c,v 1.5 2008/01/15 09:01:13 sephe Exp $ 3512e36acbSWarner Losh */ 3612e36acbSWarner Losh 3712e36acbSWarner Losh #include <sys/cdefs.h> 3812e36acbSWarner Losh __FBSDID("$FreeBSD$"); 3912e36acbSWarner Losh 4012e36acbSWarner Losh #include "opt_inet.h" 4160807f5bSAdrian Chadd #include "opt_wlan.h" 4212e36acbSWarner Losh 4312e36acbSWarner Losh #include <sys/param.h> 4412e36acbSWarner Losh #include <sys/endian.h> 4512e36acbSWarner Losh #include <sys/kernel.h> 4612e36acbSWarner Losh #include <sys/bus.h> 4712e36acbSWarner Losh #include <sys/malloc.h> 4812e36acbSWarner Losh #include <sys/proc.h> 4912e36acbSWarner Losh #include <sys/rman.h> 5012e36acbSWarner Losh #include <sys/socket.h> 5112e36acbSWarner Losh #include <sys/sockio.h> 5212e36acbSWarner Losh #include <sys/sysctl.h> 5312e36acbSWarner Losh #include <sys/systm.h> 5412e36acbSWarner Losh 5512e36acbSWarner Losh #include <net/if.h> 5676039bc8SGleb Smirnoff #include <net/if_var.h> 5712e36acbSWarner Losh #include <net/if_dl.h> 5812e36acbSWarner Losh #include <net/if_media.h> 5912e36acbSWarner Losh #include <net/if_types.h> 6012e36acbSWarner Losh #include <net/if_arp.h> 6112e36acbSWarner Losh #include <net/ethernet.h> 6212e36acbSWarner Losh #include <net/if_llc.h> 6312e36acbSWarner Losh 6412e36acbSWarner Losh #include <net80211/ieee80211_var.h> 6512e36acbSWarner Losh #include <net80211/ieee80211_radiotap.h> 6612e36acbSWarner Losh #include <net80211/ieee80211_amrr.h> 6712e36acbSWarner Losh 6812e36acbSWarner Losh #include <machine/bus.h> 6912e36acbSWarner Losh 7012e36acbSWarner Losh #include <dev/bwi/bitops.h> 7112e36acbSWarner Losh #include <dev/bwi/if_bwireg.h> 7212e36acbSWarner Losh #include <dev/bwi/if_bwivar.h> 7312e36acbSWarner Losh #include <dev/bwi/bwimac.h> 7412e36acbSWarner Losh #include <dev/bwi/bwirf.h> 7512e36acbSWarner Losh #include <dev/bwi/bwiphy.h> 7612e36acbSWarner Losh 7712e36acbSWarner Losh static void bwi_phy_init_11a(struct bwi_mac *); 7812e36acbSWarner Losh static void bwi_phy_init_11g(struct bwi_mac *); 7912e36acbSWarner Losh static void bwi_phy_init_11b_rev2(struct bwi_mac *); 8012e36acbSWarner Losh static void bwi_phy_init_11b_rev4(struct bwi_mac *); 8112e36acbSWarner Losh static void bwi_phy_init_11b_rev5(struct bwi_mac *); 8212e36acbSWarner Losh static void bwi_phy_init_11b_rev6(struct bwi_mac *); 8312e36acbSWarner Losh 8412e36acbSWarner Losh static void bwi_phy_config_11g(struct bwi_mac *); 8512e36acbSWarner Losh static void bwi_phy_config_agc(struct bwi_mac *); 8612e36acbSWarner Losh 8712e36acbSWarner Losh static void bwi_tbl_write_2(struct bwi_mac *mac, uint16_t, uint16_t); 8812e36acbSWarner Losh static void bwi_tbl_write_4(struct bwi_mac *mac, uint16_t, uint32_t); 8912e36acbSWarner Losh 9012e36acbSWarner Losh #define SUP_BPHY(num) { .rev = num, .init = bwi_phy_init_11b_rev##num } 9112e36acbSWarner Losh 9212e36acbSWarner Losh static const struct { 9312e36acbSWarner Losh uint8_t rev; 9412e36acbSWarner Losh void (*init)(struct bwi_mac *); 9512e36acbSWarner Losh } bwi_sup_bphy[] = { 9612e36acbSWarner Losh SUP_BPHY(2), 9712e36acbSWarner Losh SUP_BPHY(4), 9812e36acbSWarner Losh SUP_BPHY(5), 9912e36acbSWarner Losh SUP_BPHY(6) 10012e36acbSWarner Losh }; 10112e36acbSWarner Losh 10212e36acbSWarner Losh #undef SUP_BPHY 10312e36acbSWarner Losh 10412e36acbSWarner Losh #define BWI_PHYTBL_WRSSI 0x1000 10512e36acbSWarner Losh #define BWI_PHYTBL_NOISE_SCALE 0x1400 10612e36acbSWarner Losh #define BWI_PHYTBL_NOISE 0x1800 10712e36acbSWarner Losh #define BWI_PHYTBL_ROTOR 0x2000 10812e36acbSWarner Losh #define BWI_PHYTBL_DELAY 0x2400 10912e36acbSWarner Losh #define BWI_PHYTBL_RSSI 0x4000 11012e36acbSWarner Losh #define BWI_PHYTBL_SIGMA_SQ 0x5000 11112e36acbSWarner Losh #define BWI_PHYTBL_WRSSI_REV1 0x5400 11212e36acbSWarner Losh #define BWI_PHYTBL_FREQ 0x5800 11312e36acbSWarner Losh 11412e36acbSWarner Losh static const uint16_t bwi_phy_freq_11g_rev1[] = 11512e36acbSWarner Losh { BWI_PHY_FREQ_11G_REV1 }; 11612e36acbSWarner Losh static const uint16_t bwi_phy_noise_11g_rev1[] = 11712e36acbSWarner Losh { BWI_PHY_NOISE_11G_REV1 }; 11812e36acbSWarner Losh static const uint16_t bwi_phy_noise_11g[] = 11912e36acbSWarner Losh { BWI_PHY_NOISE_11G }; 12012e36acbSWarner Losh static const uint32_t bwi_phy_rotor_11g_rev1[] = 12112e36acbSWarner Losh { BWI_PHY_ROTOR_11G_REV1 }; 12212e36acbSWarner Losh static const uint16_t bwi_phy_noise_scale_11g_rev2[] = 12312e36acbSWarner Losh { BWI_PHY_NOISE_SCALE_11G_REV2 }; 12412e36acbSWarner Losh static const uint16_t bwi_phy_noise_scale_11g_rev7[] = 12512e36acbSWarner Losh { BWI_PHY_NOISE_SCALE_11G_REV7 }; 12612e36acbSWarner Losh static const uint16_t bwi_phy_noise_scale_11g[] = 12712e36acbSWarner Losh { BWI_PHY_NOISE_SCALE_11G }; 12812e36acbSWarner Losh static const uint16_t bwi_phy_sigma_sq_11g_rev2[] = 12912e36acbSWarner Losh { BWI_PHY_SIGMA_SQ_11G_REV2 }; 13012e36acbSWarner Losh static const uint16_t bwi_phy_sigma_sq_11g_rev7[] = 13112e36acbSWarner Losh { BWI_PHY_SIGMA_SQ_11G_REV7 }; 13212e36acbSWarner Losh static const uint32_t bwi_phy_delay_11g_rev1[] = 13312e36acbSWarner Losh { BWI_PHY_DELAY_11G_REV1 }; 13412e36acbSWarner Losh 13512e36acbSWarner Losh void 13612e36acbSWarner Losh bwi_phy_write(struct bwi_mac *mac, uint16_t ctrl, uint16_t data) 13712e36acbSWarner Losh { 13812e36acbSWarner Losh struct bwi_softc *sc = mac->mac_sc; 13912e36acbSWarner Losh 14012e36acbSWarner Losh CSR_WRITE_2(sc, BWI_PHY_CTRL, ctrl); 14112e36acbSWarner Losh CSR_WRITE_2(sc, BWI_PHY_DATA, data); 14212e36acbSWarner Losh } 14312e36acbSWarner Losh 14412e36acbSWarner Losh uint16_t 14512e36acbSWarner Losh bwi_phy_read(struct bwi_mac *mac, uint16_t ctrl) 14612e36acbSWarner Losh { 14712e36acbSWarner Losh struct bwi_softc *sc = mac->mac_sc; 14812e36acbSWarner Losh 14912e36acbSWarner Losh CSR_WRITE_2(sc, BWI_PHY_CTRL, ctrl); 15012e36acbSWarner Losh return CSR_READ_2(sc, BWI_PHY_DATA); 15112e36acbSWarner Losh } 15212e36acbSWarner Losh 15312e36acbSWarner Losh int 15412e36acbSWarner Losh bwi_phy_attach(struct bwi_mac *mac) 15512e36acbSWarner Losh { 15612e36acbSWarner Losh struct bwi_softc *sc = mac->mac_sc; 15712e36acbSWarner Losh struct bwi_phy *phy = &mac->mac_phy; 15812e36acbSWarner Losh uint8_t phyrev, phytype, phyver; 15912e36acbSWarner Losh uint16_t val; 16012e36acbSWarner Losh int i; 16112e36acbSWarner Losh 16212e36acbSWarner Losh /* Get PHY type/revision/version */ 16312e36acbSWarner Losh val = CSR_READ_2(sc, BWI_PHYINFO); 16412e36acbSWarner Losh phyrev = __SHIFTOUT(val, BWI_PHYINFO_REV_MASK); 16512e36acbSWarner Losh phytype = __SHIFTOUT(val, BWI_PHYINFO_TYPE_MASK); 16612e36acbSWarner Losh phyver = __SHIFTOUT(val, BWI_PHYINFO_VER_MASK); 16712e36acbSWarner Losh device_printf(sc->sc_dev, "PHY: type %d, rev %d, ver %d\n", 16812e36acbSWarner Losh phytype, phyrev, phyver); 16912e36acbSWarner Losh 17012e36acbSWarner Losh /* 17112e36acbSWarner Losh * Verify whether the revision of the PHY type is supported 17212e36acbSWarner Losh * Convert PHY type to ieee80211_phymode 17312e36acbSWarner Losh */ 17412e36acbSWarner Losh switch (phytype) { 17512e36acbSWarner Losh case BWI_PHYINFO_TYPE_11A: 17612e36acbSWarner Losh if (phyrev >= 4) { 17712e36acbSWarner Losh device_printf(sc->sc_dev, "unsupported 11A PHY, " 17812e36acbSWarner Losh "rev %u\n", phyrev); 17912e36acbSWarner Losh return ENXIO; 18012e36acbSWarner Losh } 18112e36acbSWarner Losh phy->phy_init = bwi_phy_init_11a; 18212e36acbSWarner Losh phy->phy_mode = IEEE80211_MODE_11A; 18312e36acbSWarner Losh phy->phy_tbl_ctrl = BWI_PHYR_TBL_CTRL_11A; 18412e36acbSWarner Losh phy->phy_tbl_data_lo = BWI_PHYR_TBL_DATA_LO_11A; 18512e36acbSWarner Losh phy->phy_tbl_data_hi = BWI_PHYR_TBL_DATA_HI_11A; 18612e36acbSWarner Losh break; 18712e36acbSWarner Losh case BWI_PHYINFO_TYPE_11B: 188*d6166defSAdrian Chadd for (i = 0; i < nitems(bwi_sup_bphy); ++i) { 18912e36acbSWarner Losh if (phyrev == bwi_sup_bphy[i].rev) { 19012e36acbSWarner Losh phy->phy_init = bwi_sup_bphy[i].init; 19112e36acbSWarner Losh break; 19212e36acbSWarner Losh } 19312e36acbSWarner Losh } 194*d6166defSAdrian Chadd if (i == nitems(bwi_sup_bphy)) { 19512e36acbSWarner Losh device_printf(sc->sc_dev, "unsupported 11B PHY, " 19612e36acbSWarner Losh "rev %u\n", phyrev); 19712e36acbSWarner Losh return ENXIO; 19812e36acbSWarner Losh } 19912e36acbSWarner Losh phy->phy_mode = IEEE80211_MODE_11B; 20012e36acbSWarner Losh break; 20112e36acbSWarner Losh case BWI_PHYINFO_TYPE_11G: 20212e36acbSWarner Losh if (phyrev > 8) { 20312e36acbSWarner Losh device_printf(sc->sc_dev, "unsupported 11G PHY, " 20412e36acbSWarner Losh "rev %u\n", phyrev); 20512e36acbSWarner Losh return ENXIO; 20612e36acbSWarner Losh } 20712e36acbSWarner Losh phy->phy_init = bwi_phy_init_11g; 20812e36acbSWarner Losh phy->phy_mode = IEEE80211_MODE_11G; 20912e36acbSWarner Losh phy->phy_tbl_ctrl = BWI_PHYR_TBL_CTRL_11G; 21012e36acbSWarner Losh phy->phy_tbl_data_lo = BWI_PHYR_TBL_DATA_LO_11G; 21112e36acbSWarner Losh phy->phy_tbl_data_hi = BWI_PHYR_TBL_DATA_HI_11G; 21212e36acbSWarner Losh break; 21312e36acbSWarner Losh default: 21412e36acbSWarner Losh device_printf(sc->sc_dev, "unsupported PHY type %d\n", 21512e36acbSWarner Losh phytype); 21612e36acbSWarner Losh return ENXIO; 21712e36acbSWarner Losh } 21812e36acbSWarner Losh phy->phy_rev = phyrev; 21912e36acbSWarner Losh phy->phy_version = phyver; 22012e36acbSWarner Losh return 0; 22112e36acbSWarner Losh } 22212e36acbSWarner Losh 22312e36acbSWarner Losh void 22412e36acbSWarner Losh bwi_phy_set_bbp_atten(struct bwi_mac *mac, uint16_t bbp_atten) 22512e36acbSWarner Losh { 22612e36acbSWarner Losh struct bwi_phy *phy = &mac->mac_phy; 22712e36acbSWarner Losh uint16_t mask = __BITS(3, 0); 22812e36acbSWarner Losh 22912e36acbSWarner Losh if (phy->phy_version == 0) { 23012e36acbSWarner Losh CSR_FILT_SETBITS_2(mac->mac_sc, BWI_BBP_ATTEN, ~mask, 23112e36acbSWarner Losh __SHIFTIN(bbp_atten, mask)); 23212e36acbSWarner Losh } else { 23312e36acbSWarner Losh if (phy->phy_version > 1) 23412e36acbSWarner Losh mask <<= 2; 23512e36acbSWarner Losh else 23612e36acbSWarner Losh mask <<= 3; 23712e36acbSWarner Losh PHY_FILT_SETBITS(mac, BWI_PHYR_BBP_ATTEN, ~mask, 23812e36acbSWarner Losh __SHIFTIN(bbp_atten, mask)); 23912e36acbSWarner Losh } 24012e36acbSWarner Losh } 24112e36acbSWarner Losh 24212e36acbSWarner Losh int 24312e36acbSWarner Losh bwi_phy_calibrate(struct bwi_mac *mac) 24412e36acbSWarner Losh { 24512e36acbSWarner Losh struct bwi_phy *phy = &mac->mac_phy; 24612e36acbSWarner Losh 24712e36acbSWarner Losh /* Dummy read */ 24812e36acbSWarner Losh CSR_READ_4(mac->mac_sc, BWI_MAC_STATUS); 24912e36acbSWarner Losh 25012e36acbSWarner Losh /* Don't re-init */ 25112e36acbSWarner Losh if (phy->phy_flags & BWI_PHY_F_CALIBRATED) 25212e36acbSWarner Losh return 0; 25312e36acbSWarner Losh 25412e36acbSWarner Losh if (phy->phy_mode == IEEE80211_MODE_11G && phy->phy_rev == 1) { 25512e36acbSWarner Losh bwi_mac_reset(mac, 0); 25612e36acbSWarner Losh bwi_phy_init_11g(mac); 25712e36acbSWarner Losh bwi_mac_reset(mac, 1); 25812e36acbSWarner Losh } 25912e36acbSWarner Losh 26012e36acbSWarner Losh phy->phy_flags |= BWI_PHY_F_CALIBRATED; 26112e36acbSWarner Losh return 0; 26212e36acbSWarner Losh } 26312e36acbSWarner Losh 26412e36acbSWarner Losh static void 26512e36acbSWarner Losh bwi_tbl_write_2(struct bwi_mac *mac, uint16_t ofs, uint16_t data) 26612e36acbSWarner Losh { 26712e36acbSWarner Losh struct bwi_phy *phy = &mac->mac_phy; 26812e36acbSWarner Losh 26912e36acbSWarner Losh KASSERT(phy->phy_tbl_ctrl != 0 && phy->phy_tbl_data_lo != 0, 27012e36acbSWarner Losh ("phy_tbl_ctrl %d phy_tbl_data_lo %d", 27112e36acbSWarner Losh phy->phy_tbl_ctrl, phy->phy_tbl_data_lo)); 27212e36acbSWarner Losh PHY_WRITE(mac, phy->phy_tbl_ctrl, ofs); 27312e36acbSWarner Losh PHY_WRITE(mac, phy->phy_tbl_data_lo, data); 27412e36acbSWarner Losh } 27512e36acbSWarner Losh 27612e36acbSWarner Losh static void 27712e36acbSWarner Losh bwi_tbl_write_4(struct bwi_mac *mac, uint16_t ofs, uint32_t data) 27812e36acbSWarner Losh { 27912e36acbSWarner Losh struct bwi_phy *phy = &mac->mac_phy; 28012e36acbSWarner Losh 28112e36acbSWarner Losh KASSERT(phy->phy_tbl_data_lo != 0 && phy->phy_tbl_data_hi != 0 && 28212e36acbSWarner Losh phy->phy_tbl_ctrl != 0, 28312e36acbSWarner Losh ("phy_tbl_data_lo %d phy_tbl_data_hi %d phy_tbl_ctrl %d", 28412e36acbSWarner Losh phy->phy_tbl_data_lo, phy->phy_tbl_data_hi, phy->phy_tbl_ctrl)); 28512e36acbSWarner Losh 28612e36acbSWarner Losh PHY_WRITE(mac, phy->phy_tbl_ctrl, ofs); 28712e36acbSWarner Losh PHY_WRITE(mac, phy->phy_tbl_data_hi, data >> 16); 28812e36acbSWarner Losh PHY_WRITE(mac, phy->phy_tbl_data_lo, data & 0xffff); 28912e36acbSWarner Losh } 29012e36acbSWarner Losh 29112e36acbSWarner Losh void 29212e36acbSWarner Losh bwi_nrssi_write(struct bwi_mac *mac, uint16_t ofs, int16_t data) 29312e36acbSWarner Losh { 29412e36acbSWarner Losh PHY_WRITE(mac, BWI_PHYR_NRSSI_CTRL, ofs); 29512e36acbSWarner Losh PHY_WRITE(mac, BWI_PHYR_NRSSI_DATA, (uint16_t)data); 29612e36acbSWarner Losh } 29712e36acbSWarner Losh 29812e36acbSWarner Losh int16_t 29912e36acbSWarner Losh bwi_nrssi_read(struct bwi_mac *mac, uint16_t ofs) 30012e36acbSWarner Losh { 30112e36acbSWarner Losh PHY_WRITE(mac, BWI_PHYR_NRSSI_CTRL, ofs); 30212e36acbSWarner Losh return (int16_t)PHY_READ(mac, BWI_PHYR_NRSSI_DATA); 30312e36acbSWarner Losh } 30412e36acbSWarner Losh 30512e36acbSWarner Losh static void 30612e36acbSWarner Losh bwi_phy_init_11a(struct bwi_mac *mac) 30712e36acbSWarner Losh { 30812e36acbSWarner Losh /* TODO:11A */ 30912e36acbSWarner Losh } 31012e36acbSWarner Losh 31112e36acbSWarner Losh static void 31212e36acbSWarner Losh bwi_phy_init_11g(struct bwi_mac *mac) 31312e36acbSWarner Losh { 31412e36acbSWarner Losh struct bwi_softc *sc = mac->mac_sc; 31512e36acbSWarner Losh struct bwi_phy *phy = &mac->mac_phy; 31612e36acbSWarner Losh struct bwi_rf *rf = &mac->mac_rf; 31712e36acbSWarner Losh const struct bwi_tpctl *tpctl = &mac->mac_tpctl; 31812e36acbSWarner Losh 31912e36acbSWarner Losh if (phy->phy_rev == 1) 32012e36acbSWarner Losh bwi_phy_init_11b_rev5(mac); 32112e36acbSWarner Losh else 32212e36acbSWarner Losh bwi_phy_init_11b_rev6(mac); 32312e36acbSWarner Losh 32412e36acbSWarner Losh if (phy->phy_rev >= 2 || (phy->phy_flags & BWI_PHY_F_LINKED)) 32512e36acbSWarner Losh bwi_phy_config_11g(mac); 32612e36acbSWarner Losh 32712e36acbSWarner Losh if (phy->phy_rev >= 2) { 32812e36acbSWarner Losh PHY_WRITE(mac, 0x814, 0); 32912e36acbSWarner Losh PHY_WRITE(mac, 0x815, 0); 33012e36acbSWarner Losh 33112e36acbSWarner Losh if (phy->phy_rev == 2) { 33212e36acbSWarner Losh PHY_WRITE(mac, 0x811, 0); 33312e36acbSWarner Losh PHY_WRITE(mac, 0x15, 0xc0); 33412e36acbSWarner Losh } else if (phy->phy_rev > 5) { 33512e36acbSWarner Losh PHY_WRITE(mac, 0x811, 0x400); 33612e36acbSWarner Losh PHY_WRITE(mac, 0x15, 0xc0); 33712e36acbSWarner Losh } 33812e36acbSWarner Losh } 33912e36acbSWarner Losh 34012e36acbSWarner Losh if (phy->phy_rev >= 2 || (phy->phy_flags & BWI_PHY_F_LINKED)) { 34112e36acbSWarner Losh uint16_t val; 34212e36acbSWarner Losh 34312e36acbSWarner Losh val = PHY_READ(mac, 0x400) & 0xff; 34412e36acbSWarner Losh if (val == 3 || val == 5) { 34512e36acbSWarner Losh PHY_WRITE(mac, 0x4c2, 0x1816); 34612e36acbSWarner Losh PHY_WRITE(mac, 0x4c3, 0x8006); 34712e36acbSWarner Losh if (val == 5) { 34812e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x4cc, 34912e36acbSWarner Losh 0xff, 0x1f00); 35012e36acbSWarner Losh } 35112e36acbSWarner Losh } 35212e36acbSWarner Losh } 35312e36acbSWarner Losh 35412e36acbSWarner Losh if ((phy->phy_rev <= 2 && (phy->phy_flags & BWI_PHY_F_LINKED)) || 35512e36acbSWarner Losh phy->phy_rev >= 2) 35612e36acbSWarner Losh PHY_WRITE(mac, 0x47e, 0x78); 35712e36acbSWarner Losh 35812e36acbSWarner Losh if (rf->rf_rev == 8) { 35912e36acbSWarner Losh PHY_SETBITS(mac, 0x801, 0x80); 36012e36acbSWarner Losh PHY_SETBITS(mac, 0x43e, 0x4); 36112e36acbSWarner Losh } 36212e36acbSWarner Losh 36312e36acbSWarner Losh if (phy->phy_rev >= 2 && (phy->phy_flags & BWI_PHY_F_LINKED)) 36412e36acbSWarner Losh bwi_rf_get_gains(mac); 36512e36acbSWarner Losh 36612e36acbSWarner Losh if (rf->rf_rev != 8) 36712e36acbSWarner Losh bwi_rf_init(mac); 36812e36acbSWarner Losh 36912e36acbSWarner Losh if (tpctl->tp_ctrl2 == 0xffff) { 37012e36acbSWarner Losh bwi_rf_lo_update(mac); 37112e36acbSWarner Losh } else { 37212e36acbSWarner Losh if (rf->rf_type == BWI_RF_T_BCM2050 && rf->rf_rev == 8) { 37312e36acbSWarner Losh RF_WRITE(mac, 0x52, 37412e36acbSWarner Losh (tpctl->tp_ctrl1 << 4) | tpctl->tp_ctrl2); 37512e36acbSWarner Losh } else { 376b16a8a58SWarner Losh RF_FILT_SETBITS(mac, 0x52, 0xfff0, tpctl->tp_ctrl2); 37712e36acbSWarner Losh } 37812e36acbSWarner Losh 37912e36acbSWarner Losh if (phy->phy_rev >= 6) { 38012e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x36, 0xfff, 38112e36acbSWarner Losh tpctl->tp_ctrl2 << 12); 38212e36acbSWarner Losh } 38312e36acbSWarner Losh 38412e36acbSWarner Losh if (sc->sc_card_flags & BWI_CARD_F_PA_GPIO9) 38512e36acbSWarner Losh PHY_WRITE(mac, 0x2e, 0x8075); 38612e36acbSWarner Losh else 38712e36acbSWarner Losh PHY_WRITE(mac, 0x2e, 0x807f); 38812e36acbSWarner Losh 38912e36acbSWarner Losh if (phy->phy_rev < 2) 39012e36acbSWarner Losh PHY_WRITE(mac, 0x2f, 0x101); 39112e36acbSWarner Losh else 39212e36acbSWarner Losh PHY_WRITE(mac, 0x2f, 0x202); 39312e36acbSWarner Losh } 39412e36acbSWarner Losh 39512e36acbSWarner Losh if ((phy->phy_flags & BWI_PHY_F_LINKED) || phy->phy_rev >= 2) { 39612e36acbSWarner Losh bwi_rf_lo_adjust(mac, tpctl); 39712e36acbSWarner Losh PHY_WRITE(mac, 0x80f, 0x8078); 39812e36acbSWarner Losh } 39912e36acbSWarner Losh 40012e36acbSWarner Losh if ((sc->sc_card_flags & BWI_CARD_F_SW_NRSSI) == 0) { 40112e36acbSWarner Losh bwi_rf_init_hw_nrssi_table(mac, 0xffff /* XXX */); 40212e36acbSWarner Losh bwi_rf_set_nrssi_thr(mac); 40312e36acbSWarner Losh } else if ((phy->phy_flags & BWI_PHY_F_LINKED) || phy->phy_rev >= 2) { 40412e36acbSWarner Losh if (rf->rf_nrssi[0] == BWI_INVALID_NRSSI) { 40512e36acbSWarner Losh KASSERT(rf->rf_nrssi[1] == BWI_INVALID_NRSSI, 40612e36acbSWarner Losh ("rf_nrssi[1] %d", rf->rf_nrssi[1])); 40712e36acbSWarner Losh bwi_rf_calc_nrssi_slope(mac); 40812e36acbSWarner Losh } else { 40912e36acbSWarner Losh KASSERT(rf->rf_nrssi[1] != BWI_INVALID_NRSSI, 41012e36acbSWarner Losh ("rf_nrssi[1] %d", rf->rf_nrssi[1])); 41112e36acbSWarner Losh bwi_rf_set_nrssi_thr(mac); 41212e36acbSWarner Losh } 41312e36acbSWarner Losh } 41412e36acbSWarner Losh 41512e36acbSWarner Losh if (rf->rf_rev == 8) 41612e36acbSWarner Losh PHY_WRITE(mac, 0x805, 0x3230); 41712e36acbSWarner Losh 41812e36acbSWarner Losh bwi_mac_init_tpctl_11bg(mac); 41912e36acbSWarner Losh 42012e36acbSWarner Losh if (sc->sc_bbp_id == BWI_BBPID_BCM4306 && sc->sc_bbp_pkg == 2) { 42112e36acbSWarner Losh PHY_CLRBITS(mac, 0x429, 0x4000); 42212e36acbSWarner Losh PHY_CLRBITS(mac, 0x4c3, 0x8000); 42312e36acbSWarner Losh } 42412e36acbSWarner Losh } 42512e36acbSWarner Losh 42612e36acbSWarner Losh static void 42712e36acbSWarner Losh bwi_phy_init_11b_rev2(struct bwi_mac *mac) 42812e36acbSWarner Losh { 42912e36acbSWarner Losh /* TODO:11B */ 4307a79cebfSGleb Smirnoff device_printf(mac->mac_sc->sc_dev, 43112e36acbSWarner Losh "%s is not implemented yet\n", __func__); 43212e36acbSWarner Losh } 43312e36acbSWarner Losh 43412e36acbSWarner Losh static void 43512e36acbSWarner Losh bwi_phy_init_11b_rev4(struct bwi_mac *mac) 43612e36acbSWarner Losh { 43712e36acbSWarner Losh struct bwi_softc *sc = mac->mac_sc; 43812e36acbSWarner Losh struct bwi_rf *rf = &mac->mac_rf; 43912e36acbSWarner Losh uint16_t val, ofs; 44012e36acbSWarner Losh u_int chan; 44112e36acbSWarner Losh 44212e36acbSWarner Losh CSR_WRITE_2(sc, BWI_BPHY_CTRL, BWI_BPHY_CTRL_INIT); 44312e36acbSWarner Losh 44412e36acbSWarner Losh PHY_WRITE(mac, 0x20, 0x301c); 44512e36acbSWarner Losh PHY_WRITE(mac, 0x26, 0); 44612e36acbSWarner Losh PHY_WRITE(mac, 0x30, 0xc6); 44712e36acbSWarner Losh PHY_WRITE(mac, 0x88, 0x3e00); 44812e36acbSWarner Losh 44912e36acbSWarner Losh for (ofs = 0, val = 0x3c3d; ofs < 30; ++ofs, val -= 0x202) 45012e36acbSWarner Losh PHY_WRITE(mac, 0x89 + ofs, val); 45112e36acbSWarner Losh 45212e36acbSWarner Losh CSR_WRITE_2(sc, BWI_PHY_MAGIC_REG1, BWI_PHY_MAGIC_REG1_VAL1); 45312e36acbSWarner Losh 45412e36acbSWarner Losh chan = rf->rf_curchan; 45512e36acbSWarner Losh if (chan == IEEE80211_CHAN_ANY) 45612e36acbSWarner Losh chan = 6; /* Force to channel 6 */ 45712e36acbSWarner Losh bwi_rf_set_chan(mac, chan, 0); 45812e36acbSWarner Losh 45912e36acbSWarner Losh if (rf->rf_type != BWI_RF_T_BCM2050) { 46012e36acbSWarner Losh RF_WRITE(mac, 0x75, 0x80); 46112e36acbSWarner Losh RF_WRITE(mac, 0x79, 0x81); 46212e36acbSWarner Losh } 46312e36acbSWarner Losh 46412e36acbSWarner Losh RF_WRITE(mac, 0x50, 0x20); 46512e36acbSWarner Losh RF_WRITE(mac, 0x50, 0x23); 46612e36acbSWarner Losh 46712e36acbSWarner Losh if (rf->rf_type == BWI_RF_T_BCM2050) { 46812e36acbSWarner Losh RF_WRITE(mac, 0x50, 0x20); 46912e36acbSWarner Losh RF_WRITE(mac, 0x5a, 0x70); 47012e36acbSWarner Losh RF_WRITE(mac, 0x5b, 0x7b); 47112e36acbSWarner Losh RF_WRITE(mac, 0x5c, 0xb0); 47212e36acbSWarner Losh RF_WRITE(mac, 0x7a, 0xf); 47312e36acbSWarner Losh PHY_WRITE(mac, 0x38, 0x677); 47412e36acbSWarner Losh bwi_rf_init_bcm2050(mac); 47512e36acbSWarner Losh } 47612e36acbSWarner Losh 47712e36acbSWarner Losh PHY_WRITE(mac, 0x14, 0x80); 47812e36acbSWarner Losh PHY_WRITE(mac, 0x32, 0xca); 47912e36acbSWarner Losh if (rf->rf_type == BWI_RF_T_BCM2050) 48012e36acbSWarner Losh PHY_WRITE(mac, 0x32, 0xe0); 48112e36acbSWarner Losh PHY_WRITE(mac, 0x35, 0x7c2); 48212e36acbSWarner Losh 48312e36acbSWarner Losh bwi_rf_lo_update(mac); 48412e36acbSWarner Losh 48512e36acbSWarner Losh PHY_WRITE(mac, 0x26, 0xcc00); 48612e36acbSWarner Losh if (rf->rf_type == BWI_RF_T_BCM2050) 48712e36acbSWarner Losh PHY_WRITE(mac, 0x26, 0xce00); 48812e36acbSWarner Losh 48912e36acbSWarner Losh CSR_WRITE_2(sc, BWI_RF_CHAN_EX, 0x1100); 49012e36acbSWarner Losh 49112e36acbSWarner Losh PHY_WRITE(mac, 0x2a, 0x88a3); 49212e36acbSWarner Losh if (rf->rf_type == BWI_RF_T_BCM2050) 49312e36acbSWarner Losh PHY_WRITE(mac, 0x2a, 0x88c2); 49412e36acbSWarner Losh 49512e36acbSWarner Losh bwi_mac_set_tpctl_11bg(mac, NULL); 49612e36acbSWarner Losh if (sc->sc_card_flags & BWI_CARD_F_SW_NRSSI) { 49712e36acbSWarner Losh bwi_rf_calc_nrssi_slope(mac); 49812e36acbSWarner Losh bwi_rf_set_nrssi_thr(mac); 49912e36acbSWarner Losh } 50012e36acbSWarner Losh bwi_mac_init_tpctl_11bg(mac); 50112e36acbSWarner Losh } 50212e36acbSWarner Losh 50312e36acbSWarner Losh static void 50412e36acbSWarner Losh bwi_phy_init_11b_rev5(struct bwi_mac *mac) 50512e36acbSWarner Losh { 50612e36acbSWarner Losh struct bwi_softc *sc = mac->mac_sc; 50712e36acbSWarner Losh struct bwi_rf *rf = &mac->mac_rf; 50812e36acbSWarner Losh struct bwi_phy *phy = &mac->mac_phy; 50912e36acbSWarner Losh u_int orig_chan; 51012e36acbSWarner Losh 51112e36acbSWarner Losh if (phy->phy_version == 1) 51212e36acbSWarner Losh RF_SETBITS(mac, 0x7a, 0x50); 51312e36acbSWarner Losh 51412e36acbSWarner Losh if (sc->sc_pci_subvid != PCI_VENDOR_BROADCOM && 51512e36acbSWarner Losh sc->sc_pci_subdid != BWI_PCI_SUBDEVICE_BU4306) { 51612e36acbSWarner Losh uint16_t ofs, val; 51712e36acbSWarner Losh 51812e36acbSWarner Losh val = 0x2120; 51912e36acbSWarner Losh for (ofs = 0xa8; ofs < 0xc7; ++ofs) { 52012e36acbSWarner Losh PHY_WRITE(mac, ofs, val); 52112e36acbSWarner Losh val += 0x202; 52212e36acbSWarner Losh } 52312e36acbSWarner Losh } 52412e36acbSWarner Losh 52512e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x35, 0xf0ff, 0x700); 52612e36acbSWarner Losh 52712e36acbSWarner Losh if (rf->rf_type == BWI_RF_T_BCM2050) 52812e36acbSWarner Losh PHY_WRITE(mac, 0x38, 0x667); 52912e36acbSWarner Losh 53012e36acbSWarner Losh if ((phy->phy_flags & BWI_PHY_F_LINKED) || phy->phy_rev >= 2) { 53112e36acbSWarner Losh if (rf->rf_type == BWI_RF_T_BCM2050) { 53212e36acbSWarner Losh RF_SETBITS(mac, 0x7a, 0x20); 53312e36acbSWarner Losh RF_SETBITS(mac, 0x51, 0x4); 53412e36acbSWarner Losh } 53512e36acbSWarner Losh 53612e36acbSWarner Losh CSR_WRITE_2(sc, BWI_RF_ANTDIV, 0); 53712e36acbSWarner Losh 53812e36acbSWarner Losh PHY_SETBITS(mac, 0x802, 0x100); 53912e36acbSWarner Losh PHY_SETBITS(mac, 0x42b, 0x2000); 54012e36acbSWarner Losh PHY_WRITE(mac, 0x1c, 0x186a); 54112e36acbSWarner Losh 54212e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x13, 0xff, 0x1900); 54312e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x35, 0xffc0, 0x64); 54412e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x5d, 0xff80, 0xa); 54512e36acbSWarner Losh } 54612e36acbSWarner Losh 54712e36acbSWarner Losh /* TODO: bad_frame_preempt? */ 54812e36acbSWarner Losh 54912e36acbSWarner Losh if (phy->phy_version == 1) { 55012e36acbSWarner Losh PHY_WRITE(mac, 0x26, 0xce00); 55112e36acbSWarner Losh PHY_WRITE(mac, 0x21, 0x3763); 55212e36acbSWarner Losh PHY_WRITE(mac, 0x22, 0x1bc3); 55312e36acbSWarner Losh PHY_WRITE(mac, 0x23, 0x6f9); 55412e36acbSWarner Losh PHY_WRITE(mac, 0x24, 0x37e); 55512e36acbSWarner Losh } else { 55612e36acbSWarner Losh PHY_WRITE(mac, 0x26, 0xcc00); 55712e36acbSWarner Losh } 55812e36acbSWarner Losh PHY_WRITE(mac, 0x30, 0xc6); 55912e36acbSWarner Losh 56012e36acbSWarner Losh CSR_WRITE_2(sc, BWI_BPHY_CTRL, BWI_BPHY_CTRL_INIT); 56112e36acbSWarner Losh 56212e36acbSWarner Losh if (phy->phy_version == 1) 56312e36acbSWarner Losh PHY_WRITE(mac, 0x20, 0x3e1c); 56412e36acbSWarner Losh else 56512e36acbSWarner Losh PHY_WRITE(mac, 0x20, 0x301c); 56612e36acbSWarner Losh 56712e36acbSWarner Losh if (phy->phy_version == 0) 56812e36acbSWarner Losh CSR_WRITE_2(sc, BWI_PHY_MAGIC_REG1, BWI_PHY_MAGIC_REG1_VAL1); 56912e36acbSWarner Losh 57012e36acbSWarner Losh /* Force to channel 7 */ 57112e36acbSWarner Losh orig_chan = rf->rf_curchan; 57212e36acbSWarner Losh bwi_rf_set_chan(mac, 7, 0); 57312e36acbSWarner Losh 57412e36acbSWarner Losh if (rf->rf_type != BWI_RF_T_BCM2050) { 57512e36acbSWarner Losh RF_WRITE(mac, 0x75, 0x80); 57612e36acbSWarner Losh RF_WRITE(mac, 0x79, 0x81); 57712e36acbSWarner Losh } 57812e36acbSWarner Losh 57912e36acbSWarner Losh RF_WRITE(mac, 0x50, 0x20); 58012e36acbSWarner Losh RF_WRITE(mac, 0x50, 0x23); 58112e36acbSWarner Losh 58212e36acbSWarner Losh if (rf->rf_type == BWI_RF_T_BCM2050) { 58312e36acbSWarner Losh RF_WRITE(mac, 0x50, 0x20); 58412e36acbSWarner Losh RF_WRITE(mac, 0x5a, 0x70); 58512e36acbSWarner Losh } 58612e36acbSWarner Losh 58712e36acbSWarner Losh RF_WRITE(mac, 0x5b, 0x7b); 58812e36acbSWarner Losh RF_WRITE(mac, 0x5c, 0xb0); 58912e36acbSWarner Losh RF_SETBITS(mac, 0x7a, 0x7); 59012e36acbSWarner Losh 59112e36acbSWarner Losh bwi_rf_set_chan(mac, orig_chan, 0); 59212e36acbSWarner Losh 59312e36acbSWarner Losh PHY_WRITE(mac, 0x14, 0x80); 59412e36acbSWarner Losh PHY_WRITE(mac, 0x32, 0xca); 59512e36acbSWarner Losh PHY_WRITE(mac, 0x2a, 0x88a3); 59612e36acbSWarner Losh 59712e36acbSWarner Losh bwi_mac_set_tpctl_11bg(mac, NULL); 59812e36acbSWarner Losh 59912e36acbSWarner Losh if (rf->rf_type == BWI_RF_T_BCM2050) 60012e36acbSWarner Losh RF_WRITE(mac, 0x5d, 0xd); 60112e36acbSWarner Losh 60212e36acbSWarner Losh CSR_FILT_SETBITS_2(sc, BWI_PHY_MAGIC_REG1, 0xffc0, 0x4); 60312e36acbSWarner Losh } 60412e36acbSWarner Losh 60512e36acbSWarner Losh static void 60612e36acbSWarner Losh bwi_phy_init_11b_rev6(struct bwi_mac *mac) 60712e36acbSWarner Losh { 60812e36acbSWarner Losh struct bwi_softc *sc = mac->mac_sc; 60912e36acbSWarner Losh struct bwi_rf *rf = &mac->mac_rf; 61012e36acbSWarner Losh struct bwi_phy *phy = &mac->mac_phy; 61112e36acbSWarner Losh uint16_t val, ofs; 61212e36acbSWarner Losh u_int orig_chan; 61312e36acbSWarner Losh 61412e36acbSWarner Losh PHY_WRITE(mac, 0x3e, 0x817a); 61512e36acbSWarner Losh RF_SETBITS(mac, 0x7a, 0x58); 61612e36acbSWarner Losh 61712e36acbSWarner Losh if (rf->rf_rev == 4 || rf->rf_rev == 5) { 61812e36acbSWarner Losh RF_WRITE(mac, 0x51, 0x37); 61912e36acbSWarner Losh RF_WRITE(mac, 0x52, 0x70); 62012e36acbSWarner Losh RF_WRITE(mac, 0x53, 0xb3); 62112e36acbSWarner Losh RF_WRITE(mac, 0x54, 0x9b); 62212e36acbSWarner Losh RF_WRITE(mac, 0x5a, 0x88); 62312e36acbSWarner Losh RF_WRITE(mac, 0x5b, 0x88); 62412e36acbSWarner Losh RF_WRITE(mac, 0x5d, 0x88); 62512e36acbSWarner Losh RF_WRITE(mac, 0x5e, 0x88); 62612e36acbSWarner Losh RF_WRITE(mac, 0x7d, 0x88); 62712e36acbSWarner Losh HFLAGS_SETBITS(mac, BWI_HFLAG_MAGIC1); 62812e36acbSWarner Losh } else if (rf->rf_rev == 8) { 62912e36acbSWarner Losh RF_WRITE(mac, 0x51, 0); 63012e36acbSWarner Losh RF_WRITE(mac, 0x52, 0x40); 63112e36acbSWarner Losh RF_WRITE(mac, 0x53, 0xb7); 63212e36acbSWarner Losh RF_WRITE(mac, 0x54, 0x98); 63312e36acbSWarner Losh RF_WRITE(mac, 0x5a, 0x88); 63412e36acbSWarner Losh RF_WRITE(mac, 0x5b, 0x6b); 63512e36acbSWarner Losh RF_WRITE(mac, 0x5c, 0xf); 63612e36acbSWarner Losh if (sc->sc_card_flags & BWI_CARD_F_ALT_IQ) { 63712e36acbSWarner Losh RF_WRITE(mac, 0x5d, 0xfa); 63812e36acbSWarner Losh RF_WRITE(mac, 0x5e, 0xd8); 63912e36acbSWarner Losh } else { 64012e36acbSWarner Losh RF_WRITE(mac, 0x5d, 0xf5); 64112e36acbSWarner Losh RF_WRITE(mac, 0x5e, 0xb8); 64212e36acbSWarner Losh } 64312e36acbSWarner Losh RF_WRITE(mac, 0x73, 0x3); 64412e36acbSWarner Losh RF_WRITE(mac, 0x7d, 0xa8); 64512e36acbSWarner Losh RF_WRITE(mac, 0x7c, 0x1); 64612e36acbSWarner Losh RF_WRITE(mac, 0x7e, 0x8); 64712e36acbSWarner Losh } 64812e36acbSWarner Losh 64912e36acbSWarner Losh val = 0x1e1f; 65012e36acbSWarner Losh for (ofs = 0x88; ofs < 0x98; ++ofs) { 65112e36acbSWarner Losh PHY_WRITE(mac, ofs, val); 65212e36acbSWarner Losh val -= 0x202; 65312e36acbSWarner Losh } 65412e36acbSWarner Losh 65512e36acbSWarner Losh val = 0x3e3f; 65612e36acbSWarner Losh for (ofs = 0x98; ofs < 0xa8; ++ofs) { 65712e36acbSWarner Losh PHY_WRITE(mac, ofs, val); 65812e36acbSWarner Losh val -= 0x202; 65912e36acbSWarner Losh } 66012e36acbSWarner Losh 66112e36acbSWarner Losh val = 0x2120; 66212e36acbSWarner Losh for (ofs = 0xa8; ofs < 0xc8; ++ofs) { 66312e36acbSWarner Losh PHY_WRITE(mac, ofs, (val & 0x3f3f)); 66412e36acbSWarner Losh val += 0x202; 6658553eea5SNathan Whitehorn 6668553eea5SNathan Whitehorn /* XXX: delay 10 us to avoid PCI parity errors with BCM4318 */ 6670a594d9eSNathan Whitehorn DELAY(10); 66812e36acbSWarner Losh } 66912e36acbSWarner Losh 67012e36acbSWarner Losh if (phy->phy_mode == IEEE80211_MODE_11G) { 67112e36acbSWarner Losh RF_SETBITS(mac, 0x7a, 0x20); 67212e36acbSWarner Losh RF_SETBITS(mac, 0x51, 0x4); 67312e36acbSWarner Losh PHY_SETBITS(mac, 0x802, 0x100); 67412e36acbSWarner Losh PHY_SETBITS(mac, 0x42b, 0x2000); 67512e36acbSWarner Losh PHY_WRITE(mac, 0x5b, 0); 67612e36acbSWarner Losh PHY_WRITE(mac, 0x5c, 0); 67712e36acbSWarner Losh } 67812e36acbSWarner Losh 67912e36acbSWarner Losh /* Force to channel 7 */ 68012e36acbSWarner Losh orig_chan = rf->rf_curchan; 68112e36acbSWarner Losh if (orig_chan >= 8) 68212e36acbSWarner Losh bwi_rf_set_chan(mac, 1, 0); 68312e36acbSWarner Losh else 68412e36acbSWarner Losh bwi_rf_set_chan(mac, 13, 0); 68512e36acbSWarner Losh 68612e36acbSWarner Losh RF_WRITE(mac, 0x50, 0x20); 68712e36acbSWarner Losh RF_WRITE(mac, 0x50, 0x23); 68812e36acbSWarner Losh 68912e36acbSWarner Losh DELAY(40); 69012e36acbSWarner Losh 69112e36acbSWarner Losh if (rf->rf_rev < 6 || rf->rf_rev == 8) { 69212e36acbSWarner Losh RF_SETBITS(mac, 0x7c, 0x2); 69312e36acbSWarner Losh RF_WRITE(mac, 0x50, 0x20); 69412e36acbSWarner Losh } 69512e36acbSWarner Losh if (rf->rf_rev <= 2) { 69612e36acbSWarner Losh RF_WRITE(mac, 0x7c, 0x20); 69712e36acbSWarner Losh RF_WRITE(mac, 0x5a, 0x70); 69812e36acbSWarner Losh RF_WRITE(mac, 0x5b, 0x7b); 69912e36acbSWarner Losh RF_WRITE(mac, 0x5c, 0xb0); 70012e36acbSWarner Losh } 70112e36acbSWarner Losh 70212e36acbSWarner Losh RF_FILT_SETBITS(mac, 0x7a, 0xf8, 0x7); 70312e36acbSWarner Losh 70412e36acbSWarner Losh bwi_rf_set_chan(mac, orig_chan, 0); 70512e36acbSWarner Losh 70612e36acbSWarner Losh PHY_WRITE(mac, 0x14, 0x200); 70712e36acbSWarner Losh if (rf->rf_rev >= 6) 70812e36acbSWarner Losh PHY_WRITE(mac, 0x2a, 0x88c2); 70912e36acbSWarner Losh else 71012e36acbSWarner Losh PHY_WRITE(mac, 0x2a, 0x8ac0); 71112e36acbSWarner Losh PHY_WRITE(mac, 0x38, 0x668); 71212e36acbSWarner Losh 71312e36acbSWarner Losh bwi_mac_set_tpctl_11bg(mac, NULL); 71412e36acbSWarner Losh 71512e36acbSWarner Losh if (rf->rf_rev <= 5) { 71612e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x5d, 0xff80, 0x3); 71712e36acbSWarner Losh if (rf->rf_rev <= 2) 71812e36acbSWarner Losh RF_WRITE(mac, 0x5d, 0xd); 71912e36acbSWarner Losh } 72012e36acbSWarner Losh 72112e36acbSWarner Losh if (phy->phy_version == 4) { 72212e36acbSWarner Losh CSR_WRITE_2(sc, BWI_PHY_MAGIC_REG1, BWI_PHY_MAGIC_REG1_VAL2); 72312e36acbSWarner Losh PHY_CLRBITS(mac, 0x61, 0xf000); 72412e36acbSWarner Losh } else { 72512e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x2, 0xffc0, 0x4); 72612e36acbSWarner Losh } 72712e36acbSWarner Losh 72812e36acbSWarner Losh if (phy->phy_mode == IEEE80211_MODE_11B) { 72912e36acbSWarner Losh CSR_WRITE_2(sc, BWI_BBP_ATTEN, BWI_BBP_ATTEN_MAGIC2); 73012e36acbSWarner Losh PHY_WRITE(mac, 0x16, 0x410); 73112e36acbSWarner Losh PHY_WRITE(mac, 0x17, 0x820); 73212e36acbSWarner Losh PHY_WRITE(mac, 0x62, 0x7); 73312e36acbSWarner Losh 73412e36acbSWarner Losh bwi_rf_init_bcm2050(mac); 73512e36acbSWarner Losh bwi_rf_lo_update(mac); 73612e36acbSWarner Losh if (sc->sc_card_flags & BWI_CARD_F_SW_NRSSI) { 73712e36acbSWarner Losh bwi_rf_calc_nrssi_slope(mac); 73812e36acbSWarner Losh bwi_rf_set_nrssi_thr(mac); 73912e36acbSWarner Losh } 74012e36acbSWarner Losh bwi_mac_init_tpctl_11bg(mac); 74112e36acbSWarner Losh } else { 74212e36acbSWarner Losh CSR_WRITE_2(sc, BWI_BBP_ATTEN, 0); 74312e36acbSWarner Losh } 74412e36acbSWarner Losh } 74512e36acbSWarner Losh 74612e36acbSWarner Losh static void 74712e36acbSWarner Losh bwi_phy_config_11g(struct bwi_mac *mac) 74812e36acbSWarner Losh { 74912e36acbSWarner Losh struct bwi_softc *sc = mac->mac_sc; 75012e36acbSWarner Losh struct bwi_phy *phy = &mac->mac_phy; 75112e36acbSWarner Losh const uint16_t *tbl; 75212e36acbSWarner Losh uint16_t wrd_ofs1, wrd_ofs2; 75312e36acbSWarner Losh int i, n; 75412e36acbSWarner Losh 75512e36acbSWarner Losh if (phy->phy_rev == 1) { 75612e36acbSWarner Losh PHY_WRITE(mac, 0x406, 0x4f19); 75712e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x429, 0xfc3f, 0x340); 75812e36acbSWarner Losh PHY_WRITE(mac, 0x42c, 0x5a); 75912e36acbSWarner Losh PHY_WRITE(mac, 0x427, 0x1a); 76012e36acbSWarner Losh 76112e36acbSWarner Losh /* Fill frequency table */ 762*d6166defSAdrian Chadd for (i = 0; i < nitems(bwi_phy_freq_11g_rev1); ++i) { 76312e36acbSWarner Losh bwi_tbl_write_2(mac, BWI_PHYTBL_FREQ + i, 76412e36acbSWarner Losh bwi_phy_freq_11g_rev1[i]); 76512e36acbSWarner Losh } 76612e36acbSWarner Losh 76712e36acbSWarner Losh /* Fill noise table */ 768*d6166defSAdrian Chadd for (i = 0; i < nitems(bwi_phy_noise_11g_rev1); ++i) { 76912e36acbSWarner Losh bwi_tbl_write_2(mac, BWI_PHYTBL_NOISE + i, 77012e36acbSWarner Losh bwi_phy_noise_11g_rev1[i]); 77112e36acbSWarner Losh } 77212e36acbSWarner Losh 77312e36acbSWarner Losh /* Fill rotor table */ 774*d6166defSAdrian Chadd for (i = 0; i < nitems(bwi_phy_rotor_11g_rev1); ++i) { 77512e36acbSWarner Losh /* NB: data length is 4 bytes */ 77612e36acbSWarner Losh bwi_tbl_write_4(mac, BWI_PHYTBL_ROTOR + i, 77712e36acbSWarner Losh bwi_phy_rotor_11g_rev1[i]); 77812e36acbSWarner Losh } 77912e36acbSWarner Losh } else { 78012e36acbSWarner Losh bwi_nrssi_write(mac, 0xba98, (int16_t)0x7654); /* XXX */ 78112e36acbSWarner Losh 78212e36acbSWarner Losh if (phy->phy_rev == 2) { 78312e36acbSWarner Losh PHY_WRITE(mac, 0x4c0, 0x1861); 78412e36acbSWarner Losh PHY_WRITE(mac, 0x4c1, 0x271); 78512e36acbSWarner Losh } else if (phy->phy_rev > 2) { 78612e36acbSWarner Losh PHY_WRITE(mac, 0x4c0, 0x98); 78712e36acbSWarner Losh PHY_WRITE(mac, 0x4c1, 0x70); 78812e36acbSWarner Losh PHY_WRITE(mac, 0x4c9, 0x80); 78912e36acbSWarner Losh } 79012e36acbSWarner Losh PHY_SETBITS(mac, 0x42b, 0x800); 79112e36acbSWarner Losh 79212e36acbSWarner Losh /* Fill RSSI table */ 79312e36acbSWarner Losh for (i = 0; i < 64; ++i) 79412e36acbSWarner Losh bwi_tbl_write_2(mac, BWI_PHYTBL_RSSI + i, i); 79512e36acbSWarner Losh 79612e36acbSWarner Losh /* Fill noise table */ 797*d6166defSAdrian Chadd for (i = 0; i < nitems(bwi_phy_noise_11g); ++i) { 79812e36acbSWarner Losh bwi_tbl_write_2(mac, BWI_PHYTBL_NOISE + i, 79912e36acbSWarner Losh bwi_phy_noise_11g[i]); 80012e36acbSWarner Losh } 80112e36acbSWarner Losh } 80212e36acbSWarner Losh 80312e36acbSWarner Losh /* 80412e36acbSWarner Losh * Fill noise scale table 80512e36acbSWarner Losh */ 80612e36acbSWarner Losh if (phy->phy_rev <= 2) { 80712e36acbSWarner Losh tbl = bwi_phy_noise_scale_11g_rev2; 808*d6166defSAdrian Chadd n = nitems(bwi_phy_noise_scale_11g_rev2); 80912e36acbSWarner Losh } else if (phy->phy_rev >= 7 && (PHY_READ(mac, 0x449) & 0x200)) { 81012e36acbSWarner Losh tbl = bwi_phy_noise_scale_11g_rev7; 811*d6166defSAdrian Chadd n = nitems(bwi_phy_noise_scale_11g_rev7); 81212e36acbSWarner Losh } else { 81312e36acbSWarner Losh tbl = bwi_phy_noise_scale_11g; 814*d6166defSAdrian Chadd n = nitems(bwi_phy_noise_scale_11g); 81512e36acbSWarner Losh } 81612e36acbSWarner Losh for (i = 0; i < n; ++i) 81712e36acbSWarner Losh bwi_tbl_write_2(mac, BWI_PHYTBL_NOISE_SCALE + i, tbl[i]); 81812e36acbSWarner Losh 81912e36acbSWarner Losh /* 82012e36acbSWarner Losh * Fill sigma square table 82112e36acbSWarner Losh */ 82212e36acbSWarner Losh if (phy->phy_rev == 2) { 82312e36acbSWarner Losh tbl = bwi_phy_sigma_sq_11g_rev2; 824*d6166defSAdrian Chadd n = nitems(bwi_phy_sigma_sq_11g_rev2); 82512e36acbSWarner Losh } else if (phy->phy_rev > 2 && phy->phy_rev <= 8) { 82612e36acbSWarner Losh tbl = bwi_phy_sigma_sq_11g_rev7; 827*d6166defSAdrian Chadd n = nitems(bwi_phy_sigma_sq_11g_rev7); 82812e36acbSWarner Losh } else { 82912e36acbSWarner Losh tbl = NULL; 83012e36acbSWarner Losh n = 0; 83112e36acbSWarner Losh } 83212e36acbSWarner Losh for (i = 0; i < n; ++i) 83312e36acbSWarner Losh bwi_tbl_write_2(mac, BWI_PHYTBL_SIGMA_SQ + i, tbl[i]); 83412e36acbSWarner Losh 83512e36acbSWarner Losh if (phy->phy_rev == 1) { 83612e36acbSWarner Losh /* Fill delay table */ 837*d6166defSAdrian Chadd for (i = 0; i < nitems(bwi_phy_delay_11g_rev1); ++i) { 83812e36acbSWarner Losh bwi_tbl_write_4(mac, BWI_PHYTBL_DELAY + i, 83912e36acbSWarner Losh bwi_phy_delay_11g_rev1[i]); 84012e36acbSWarner Losh } 84112e36acbSWarner Losh 84212e36acbSWarner Losh /* Fill WRSSI (Wide-Band RSSI) table */ 84312e36acbSWarner Losh for (i = 4; i < 20; ++i) 84412e36acbSWarner Losh bwi_tbl_write_2(mac, BWI_PHYTBL_WRSSI_REV1 + i, 0x20); 84512e36acbSWarner Losh 84612e36acbSWarner Losh bwi_phy_config_agc(mac); 84712e36acbSWarner Losh 84812e36acbSWarner Losh wrd_ofs1 = 0x5001; 84912e36acbSWarner Losh wrd_ofs2 = 0x5002; 85012e36acbSWarner Losh } else { 85112e36acbSWarner Losh /* Fill WRSSI (Wide-Band RSSI) table */ 85212e36acbSWarner Losh for (i = 0; i < 0x20; ++i) 85312e36acbSWarner Losh bwi_tbl_write_2(mac, BWI_PHYTBL_WRSSI + i, 0x820); 85412e36acbSWarner Losh 85512e36acbSWarner Losh bwi_phy_config_agc(mac); 85612e36acbSWarner Losh 85712e36acbSWarner Losh PHY_READ(mac, 0x400); /* Dummy read */ 85812e36acbSWarner Losh PHY_WRITE(mac, 0x403, 0x1000); 85912e36acbSWarner Losh bwi_tbl_write_2(mac, 0x3c02, 0xf); 86012e36acbSWarner Losh bwi_tbl_write_2(mac, 0x3c03, 0x14); 86112e36acbSWarner Losh 86212e36acbSWarner Losh wrd_ofs1 = 0x401; 86312e36acbSWarner Losh wrd_ofs2 = 0x402; 86412e36acbSWarner Losh } 86512e36acbSWarner Losh 86612e36acbSWarner Losh if (!(BWI_IS_BRCM_BU4306(sc) && sc->sc_pci_revid == 0x17)) { 86712e36acbSWarner Losh bwi_tbl_write_2(mac, wrd_ofs1, 0x2); 86812e36acbSWarner Losh bwi_tbl_write_2(mac, wrd_ofs2, 0x1); 86912e36acbSWarner Losh } 87012e36acbSWarner Losh 87112e36acbSWarner Losh /* phy->phy_flags & BWI_PHY_F_LINKED ? */ 87212e36acbSWarner Losh if (sc->sc_card_flags & BWI_CARD_F_PA_GPIO9) 87312e36acbSWarner Losh PHY_WRITE(mac, 0x46e, 0x3cf); 87412e36acbSWarner Losh } 87512e36acbSWarner Losh 87612e36acbSWarner Losh /* 87712e36acbSWarner Losh * Configure Automatic Gain Controller 87812e36acbSWarner Losh */ 87912e36acbSWarner Losh static void 88012e36acbSWarner Losh bwi_phy_config_agc(struct bwi_mac *mac) 88112e36acbSWarner Losh { 88212e36acbSWarner Losh struct bwi_phy *phy = &mac->mac_phy; 88312e36acbSWarner Losh uint16_t ofs; 88412e36acbSWarner Losh 88512e36acbSWarner Losh ofs = phy->phy_rev == 1 ? 0x4c00 : 0; 88612e36acbSWarner Losh 88712e36acbSWarner Losh bwi_tbl_write_2(mac, ofs, 0xfe); 88812e36acbSWarner Losh bwi_tbl_write_2(mac, ofs + 1, 0xd); 88912e36acbSWarner Losh bwi_tbl_write_2(mac, ofs + 2, 0x13); 89012e36acbSWarner Losh bwi_tbl_write_2(mac, ofs + 3, 0x19); 89112e36acbSWarner Losh 89212e36acbSWarner Losh if (phy->phy_rev == 1) { 89312e36acbSWarner Losh bwi_tbl_write_2(mac, 0x1800, 0x2710); 89412e36acbSWarner Losh bwi_tbl_write_2(mac, 0x1801, 0x9b83); 89512e36acbSWarner Losh bwi_tbl_write_2(mac, 0x1802, 0x9b83); 89612e36acbSWarner Losh bwi_tbl_write_2(mac, 0x1803, 0xf8d); 89712e36acbSWarner Losh PHY_WRITE(mac, 0x455, 0x4); 89812e36acbSWarner Losh } 89912e36acbSWarner Losh 90012e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x4a5, 0xff, 0x5700); 90112e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x41a, 0xff80, 0xf); 90212e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x41a, 0xc07f, 0x2b80); 90312e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x48c, 0xf0ff, 0x300); 90412e36acbSWarner Losh 90512e36acbSWarner Losh RF_SETBITS(mac, 0x7a, 0x8); 90612e36acbSWarner Losh 90712e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x4a0, 0xfff0, 0x8); 90812e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x4a1, 0xf0ff, 0x600); 90912e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x4a2, 0xf0ff, 0x700); 91012e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x4a0, 0xf0ff, 0x100); 91112e36acbSWarner Losh 91212e36acbSWarner Losh if (phy->phy_rev == 1) 91312e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x4a2, 0xfff0, 0x7); 91412e36acbSWarner Losh 91512e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x488, 0xff00, 0x1c); 91612e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x488, 0xc0ff, 0x200); 91712e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x496, 0xff00, 0x1c); 91812e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x489, 0xff00, 0x20); 91912e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x489, 0xc0ff, 0x200); 92012e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x482, 0xff00, 0x2e); 92112e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x496, 0xff, 0x1a00); 92212e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x481, 0xff00, 0x28); 92312e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x481, 0xff, 0x2c00); 92412e36acbSWarner Losh 92512e36acbSWarner Losh if (phy->phy_rev == 1) { 92612e36acbSWarner Losh PHY_WRITE(mac, 0x430, 0x92b); 92712e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x41b, 0xffe1, 0x2); 92812e36acbSWarner Losh } else { 92912e36acbSWarner Losh PHY_CLRBITS(mac, 0x41b, 0x1e); 93012e36acbSWarner Losh PHY_WRITE(mac, 0x41f, 0x287a); 93112e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x420, 0xfff0, 0x4); 93212e36acbSWarner Losh 93312e36acbSWarner Losh if (phy->phy_rev >= 6) { 93412e36acbSWarner Losh PHY_WRITE(mac, 0x422, 0x287a); 93512e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x420, 0xfff, 0x3000); 93612e36acbSWarner Losh } 93712e36acbSWarner Losh } 93812e36acbSWarner Losh 93912e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x4a8, 0x8080, 0x7874); 94012e36acbSWarner Losh PHY_WRITE(mac, 0x48e, 0x1c00); 94112e36acbSWarner Losh 94212e36acbSWarner Losh if (phy->phy_rev == 1) { 94312e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x4ab, 0xf0ff, 0x600); 94412e36acbSWarner Losh PHY_WRITE(mac, 0x48b, 0x5e); 94512e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x48c, 0xff00, 0x1e); 94612e36acbSWarner Losh PHY_WRITE(mac, 0x48d, 0x2); 94712e36acbSWarner Losh } 94812e36acbSWarner Losh 94912e36acbSWarner Losh bwi_tbl_write_2(mac, ofs + 0x800, 0); 95012e36acbSWarner Losh bwi_tbl_write_2(mac, ofs + 0x801, 7); 95112e36acbSWarner Losh bwi_tbl_write_2(mac, ofs + 0x802, 16); 95212e36acbSWarner Losh bwi_tbl_write_2(mac, ofs + 0x803, 28); 95312e36acbSWarner Losh 95412e36acbSWarner Losh if (phy->phy_rev >= 6) { 95512e36acbSWarner Losh PHY_CLRBITS(mac, 0x426, 0x3); 95612e36acbSWarner Losh PHY_CLRBITS(mac, 0x426, 0x1000); 95712e36acbSWarner Losh } 95812e36acbSWarner Losh } 95912e36acbSWarner Losh 96012e36acbSWarner Losh void 96112e36acbSWarner Losh bwi_set_gains(struct bwi_mac *mac, const struct bwi_gains *gains) 96212e36acbSWarner Losh { 96312e36acbSWarner Losh struct bwi_phy *phy = &mac->mac_phy; 96412e36acbSWarner Losh uint16_t tbl_gain_ofs1, tbl_gain_ofs2, tbl_gain; 96512e36acbSWarner Losh int i; 96612e36acbSWarner Losh 96712e36acbSWarner Losh if (phy->phy_rev <= 1) { 96812e36acbSWarner Losh tbl_gain_ofs1 = 0x5000; 96912e36acbSWarner Losh tbl_gain_ofs2 = tbl_gain_ofs1 + 16; 97012e36acbSWarner Losh } else { 97112e36acbSWarner Losh tbl_gain_ofs1 = 0x400; 97212e36acbSWarner Losh tbl_gain_ofs2 = tbl_gain_ofs1 + 8; 97312e36acbSWarner Losh } 97412e36acbSWarner Losh 97512e36acbSWarner Losh for (i = 0; i < 4; ++i) { 97612e36acbSWarner Losh if (gains != NULL) { 97712e36acbSWarner Losh tbl_gain = gains->tbl_gain1; 97812e36acbSWarner Losh } else { 97912e36acbSWarner Losh /* Bit swap */ 98012e36acbSWarner Losh tbl_gain = (i & 0x1) << 1; 98112e36acbSWarner Losh tbl_gain |= (i & 0x2) >> 1; 98212e36acbSWarner Losh } 98312e36acbSWarner Losh bwi_tbl_write_2(mac, tbl_gain_ofs1 + i, tbl_gain); 98412e36acbSWarner Losh } 98512e36acbSWarner Losh 98612e36acbSWarner Losh for (i = 0; i < 16; ++i) { 98712e36acbSWarner Losh if (gains != NULL) 98812e36acbSWarner Losh tbl_gain = gains->tbl_gain2; 98912e36acbSWarner Losh else 99012e36acbSWarner Losh tbl_gain = i; 99112e36acbSWarner Losh bwi_tbl_write_2(mac, tbl_gain_ofs2 + i, tbl_gain); 99212e36acbSWarner Losh } 99312e36acbSWarner Losh 99412e36acbSWarner Losh if (gains == NULL || (gains != NULL && gains->phy_gain != -1)) { 99512e36acbSWarner Losh uint16_t phy_gain1, phy_gain2; 99612e36acbSWarner Losh 99712e36acbSWarner Losh if (gains != NULL) { 99812e36acbSWarner Losh phy_gain1 = 99912e36acbSWarner Losh ((uint16_t)gains->phy_gain << 14) | 100012e36acbSWarner Losh ((uint16_t)gains->phy_gain << 6); 100112e36acbSWarner Losh phy_gain2 = phy_gain1; 100212e36acbSWarner Losh } else { 100312e36acbSWarner Losh phy_gain1 = 0x4040; 100412e36acbSWarner Losh phy_gain2 = 0x4000; 100512e36acbSWarner Losh } 100612e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x4a0, 0xbfbf, phy_gain1); 100712e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x4a1, 0xbfbf, phy_gain1); 100812e36acbSWarner Losh PHY_FILT_SETBITS(mac, 0x4a2, 0xbfbf, phy_gain2); 100912e36acbSWarner Losh } 101012e36acbSWarner Losh bwi_mac_dummy_xmit(mac); 101112e36acbSWarner Losh } 101212e36acbSWarner Losh 101312e36acbSWarner Losh void 101412e36acbSWarner Losh bwi_phy_clear_state(struct bwi_phy *phy) 101512e36acbSWarner Losh { 101612e36acbSWarner Losh phy->phy_flags &= ~BWI_CLEAR_PHY_FLAGS; 101712e36acbSWarner Losh } 1018