#include <sys/param.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/endian.h>
#include <sys/firmware.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/rman.h>
#include <sys/sysctl.h>
#include <sys/linker.h>
#include <machine/endian.h>
#include <bus/pci/pcivar.h>
#include <bus/pci/pcireg.h>
#include <net/bpf.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_arp.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_types.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netproto/802_11/ieee80211_var.h>
#include <netproto/802_11/ieee80211_regdomain.h>
#include <netproto/802_11/ieee80211_ratectl.h>
#include <netproto/802_11/ieee80211_radiotap.h>
#include "if_iwmreg.h"
#include "if_iwmvar.h"
#include "if_iwm_config.h"
#include "if_iwm_debug.h"
#include "if_iwm_pcie_trans.h"
uint32_t
iwm_read_prph(struct iwm_softc *sc, uint32_t addr)
{
IWM_WRITE(sc,
IWM_HBUS_TARG_PRPH_RADDR, ((addr & 0x000fffff) | (3 << 24)));
IWM_BARRIER_READ_WRITE(sc);
return IWM_READ(sc, IWM_HBUS_TARG_PRPH_RDAT);
}
void
iwm_write_prph(struct iwm_softc *sc, uint32_t addr, uint32_t val)
{
IWM_WRITE(sc,
IWM_HBUS_TARG_PRPH_WADDR, ((addr & 0x000fffff) | (3 << 24)));
IWM_BARRIER_WRITE(sc);
IWM_WRITE(sc, IWM_HBUS_TARG_PRPH_WDAT, val);
}
void
iwm_write_prph64(struct iwm_softc *sc, uint64_t addr, uint64_t val)
{
iwm_write_prph(sc, (uint32_t)addr, val & 0xffffffff);
iwm_write_prph(sc, (uint32_t)addr + 4, val >> 32);
}
int
iwm_poll_prph(struct iwm_softc *sc, uint32_t addr, uint32_t bits, uint32_t mask,
int timeout)
{
do {
if ((iwm_read_prph(sc, addr) & mask) == (bits & mask))
return (0);
DELAY(10);
timeout -= 10;
} while (timeout > 0);
return (ETIMEDOUT);
}
#ifdef IWM_DEBUG
int
iwm_read_mem(struct iwm_softc *sc, uint32_t addr, void *buf, int dwords)
{
int offs, ret = 0;
uint32_t *vals = buf;
if (iwm_nic_lock(sc)) {
IWM_WRITE(sc, IWM_HBUS_TARG_MEM_RADDR, addr);
for (offs = 0; offs < dwords; offs++)
vals[offs] = IWM_READ(sc, IWM_HBUS_TARG_MEM_RDAT);
iwm_nic_unlock(sc);
} else {
ret = EBUSY;
}
return ret;
}
#endif
int
iwm_write_mem(struct iwm_softc *sc, uint32_t addr, const void *buf, int dwords)
{
int offs;
const uint32_t *vals = buf;
if (iwm_nic_lock(sc)) {
IWM_WRITE(sc, IWM_HBUS_TARG_MEM_WADDR, addr);
for (offs = 0; offs < dwords; offs++) {
uint32_t val = vals ? vals[offs] : 0;
IWM_WRITE(sc, IWM_HBUS_TARG_MEM_WDAT, val);
}
iwm_nic_unlock(sc);
} else {
IWM_DPRINTF(sc, IWM_DEBUG_TRANS,
"%s: write_mem failed\n", __func__);
return EBUSY;
}
return 0;
}
int
iwm_write_mem32(struct iwm_softc *sc, uint32_t addr, uint32_t val)
{
return iwm_write_mem(sc, addr, &val, 1);
}
int
iwm_poll_bit(struct iwm_softc *sc, int reg,
uint32_t bits, uint32_t mask, int timo)
{
for (;;) {
if ((IWM_READ(sc, reg) & mask) == (bits & mask)) {
return 1;
}
if (timo < 10) {
return 0;
}
timo -= 10;
DELAY(10);
}
}
int
iwm_nic_lock(struct iwm_softc *sc)
{
int rv = 0;
if (sc->cmd_hold_nic_awake)
return 1;
IWM_SETBITS(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
if (sc->cfg->device_family >= IWM_DEVICE_FAMILY_8000)
DELAY(2);
if (iwm_poll_bit(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_VAL_MAC_ACCESS_EN,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY
| IWM_CSR_GP_CNTRL_REG_FLAG_GOING_TO_SLEEP, 15000)) {
rv = 1;
} else {
IWM_DPRINTF(sc, IWM_DEBUG_RESET,
"%s: resetting device via NMI\n", __func__);
IWM_WRITE(sc, IWM_CSR_RESET, IWM_CSR_RESET_REG_FLAG_FORCE_NMI);
}
return rv;
}
void
iwm_nic_unlock(struct iwm_softc *sc)
{
if (sc->cmd_hold_nic_awake)
return;
IWM_CLRBITS(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
}
void
iwm_set_bits_mask_prph(struct iwm_softc *sc,
uint32_t reg, uint32_t bits, uint32_t mask)
{
uint32_t val;
if (iwm_nic_lock(sc)) {
val = iwm_read_prph(sc, reg) & mask;
val |= bits;
iwm_write_prph(sc, reg, val);
iwm_nic_unlock(sc);
}
}
void
iwm_set_bits_prph(struct iwm_softc *sc, uint32_t reg, uint32_t bits)
{
iwm_set_bits_mask_prph(sc, reg, bits, ~0);
}
void
iwm_clear_bits_prph(struct iwm_softc *sc, uint32_t reg, uint32_t bits)
{
iwm_set_bits_mask_prph(sc, reg, 0, ~bits);
}
void
iwm_enable_rfkill_int(struct iwm_softc *sc)
{
sc->sc_intmask = IWM_CSR_INT_BIT_RF_KILL;
IWM_WRITE(sc, IWM_CSR_INT_MASK, sc->sc_intmask);
IWM_SETBITS(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_FLAG_RFKILL_WAKE_L1A_EN);
}
int
iwm_check_rfkill(struct iwm_softc *sc)
{
uint32_t v;
int rv;
v = IWM_READ(sc, IWM_CSR_GP_CNTRL);
rv = (v & IWM_CSR_GP_CNTRL_REG_FLAG_HW_RF_KILL_SW) == 0;
if (rv) {
sc->sc_flags |= IWM_FLAG_RFKILL;
} else {
sc->sc_flags &= ~IWM_FLAG_RFKILL;
}
return rv;
}
#define IWM_HW_READY_TIMEOUT 50
int
iwm_set_hw_ready(struct iwm_softc *sc)
{
int ready;
IWM_SETBITS(sc, IWM_CSR_HW_IF_CONFIG_REG,
IWM_CSR_HW_IF_CONFIG_REG_BIT_NIC_READY);
ready = iwm_poll_bit(sc, IWM_CSR_HW_IF_CONFIG_REG,
IWM_CSR_HW_IF_CONFIG_REG_BIT_NIC_READY,
IWM_CSR_HW_IF_CONFIG_REG_BIT_NIC_READY,
IWM_HW_READY_TIMEOUT);
if (ready) {
IWM_SETBITS(sc, IWM_CSR_MBOX_SET_REG,
IWM_CSR_MBOX_SET_REG_OS_ALIVE);
}
return ready;
}
#undef IWM_HW_READY_TIMEOUT
int
iwm_prepare_card_hw(struct iwm_softc *sc)
{
int rv = 0;
int t = 0;
IWM_DPRINTF(sc, IWM_DEBUG_RESET, "->%s\n", __func__);
if (iwm_set_hw_ready(sc))
goto out;
IWM_SETBITS(sc, IWM_CSR_DBG_LINK_PWR_MGMT_REG,
IWM_CSR_RESET_LINK_PWR_MGMT_DISABLED);
DELAY(1000);
IWM_SETBITS(sc, IWM_CSR_HW_IF_CONFIG_REG,
IWM_CSR_HW_IF_CONFIG_REG_PREPARE);
do {
if (iwm_set_hw_ready(sc))
goto out;
DELAY(200);
t += 200;
} while (t < 150000);
rv = ETIMEDOUT;
out:
IWM_DPRINTF(sc, IWM_DEBUG_RESET, "<-%s\n", __func__);
return rv;
}
void
iwm_apm_config(struct iwm_softc *sc)
{
uint16_t lctl, cap;
int pcie_ptr;
#if !defined(__DragonFly__)
int error;
error = pci_find_cap(sc->sc_dev, PCIY_EXPRESS, &pcie_ptr);
if (error != 0)
return;
lctl = pci_read_config(sc->sc_dev, pcie_ptr + PCIER_LINK_CTL,
sizeof(lctl));
if (lctl & PCIEM_LINK_CTL_ASPMC_L1) {
#else
pcie_ptr = pci_get_pciecap_ptr(sc->sc_dev);
if (pcie_ptr == 0)
return;
lctl = pci_read_config(sc->sc_dev, pcie_ptr + PCIER_LINKCTRL,
sizeof(lctl));
if (lctl & PCIEM_LNKCTL_ASPM_L1) {
#endif
IWM_SETBITS(sc, IWM_CSR_GIO_REG,
IWM_CSR_GIO_REG_VAL_L0S_ENABLED);
} else {
IWM_CLRBITS(sc, IWM_CSR_GIO_REG,
IWM_CSR_GIO_REG_VAL_L0S_ENABLED);
}
#if !defined(__DragonFly__)
cap = pci_read_config(sc->sc_dev, pcie_ptr + PCIER_DEVICE_CTL2,
sizeof(cap));
sc->sc_ltr_enabled = (cap & PCIEM_CTL2_LTR_ENABLE) ? 1 : 0;
IWM_DPRINTF(sc, IWM_DEBUG_RESET | IWM_DEBUG_PWRSAVE,
"L1 %sabled - LTR %sabled\n",
(lctl & PCIEM_LINK_CTL_ASPMC_L1) ? "En" : "Dis",
sc->sc_ltr_enabled ? "En" : "Dis");
#else
cap = pci_read_config(sc->sc_dev, pcie_ptr + PCIER_DEVCTRL2,
sizeof(cap));
sc->sc_ltr_enabled = (cap & PCIEM_DEVCTL2_LTR_ENABLE) ? 1 : 0;
IWM_DPRINTF(sc, IWM_DEBUG_RESET | IWM_DEBUG_PWRSAVE,
"L1 %sabled - LTR %sabled\n",
(lctl & PCIEM_LNKCTL_ASPM_L1) ? "En" : "Dis",
sc->sc_ltr_enabled ? "En" : "Dis");
#endif
}
int
iwm_apm_init(struct iwm_softc *sc)
{
int error = 0;
IWM_DPRINTF(sc, IWM_DEBUG_RESET, "iwm apm start\n");
if (sc->cfg->device_family < IWM_DEVICE_FAMILY_8000) {
IWM_SETBITS(sc, IWM_CSR_GIO_CHICKEN_BITS,
IWM_CSR_GIO_CHICKEN_BITS_REG_BIT_DIS_L0S_EXIT_TIMER);
}
IWM_SETBITS(sc, IWM_CSR_GIO_CHICKEN_BITS,
IWM_CSR_GIO_CHICKEN_BITS_REG_BIT_L1A_NO_L0S_RX);
IWM_SETBITS(sc, IWM_CSR_DBG_HPET_MEM_REG, IWM_CSR_DBG_HPET_MEM_REG_VAL);
IWM_SETBITS(sc, IWM_CSR_HW_IF_CONFIG_REG,
IWM_CSR_HW_IF_CONFIG_REG_BIT_HAP_WAKE_L1A);
iwm_apm_config(sc);
#if 0
if (trans->cfg->base_params->pll_cfg_val)
IWM_SETBITS(trans, IWM_CSR_ANA_PLL_CFG,
trans->cfg->base_params->pll_cfg_val);
#endif
IWM_SETBITS(sc, IWM_CSR_GP_CNTRL, IWM_CSR_GP_CNTRL_REG_FLAG_INIT_DONE);
if (!iwm_poll_bit(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY, 25000)) {
device_printf(sc->sc_dev,
"timeout waiting for clock stabilization\n");
error = ETIMEDOUT;
goto out;
}
if (sc->cfg->host_interrupt_operation_mode) {
if (iwm_nic_lock(sc)) {
iwm_read_prph(sc, IWM_OSC_CLK);
iwm_read_prph(sc, IWM_OSC_CLK);
iwm_nic_unlock(sc);
}
iwm_set_bits_prph(sc, IWM_OSC_CLK, IWM_OSC_CLK_FORCE_CONTROL);
if (iwm_nic_lock(sc)) {
iwm_read_prph(sc, IWM_OSC_CLK);
iwm_read_prph(sc, IWM_OSC_CLK);
iwm_nic_unlock(sc);
}
}
if (sc->cfg->device_family == IWM_DEVICE_FAMILY_7000) {
if (iwm_nic_lock(sc)) {
iwm_write_prph(sc, IWM_APMG_CLK_EN_REG,
IWM_APMG_CLK_VAL_DMA_CLK_RQT);
iwm_nic_unlock(sc);
}
DELAY(20);
iwm_set_bits_prph(sc, IWM_APMG_PCIDEV_STT_REG,
IWM_APMG_PCIDEV_STT_VAL_L1_ACT_DIS);
if (iwm_nic_lock(sc)) {
iwm_write_prph(sc, IWM_APMG_RTC_INT_STT_REG,
IWM_APMG_RTC_INT_STT_RFKILL);
iwm_nic_unlock(sc);
}
}
out:
if (error)
device_printf(sc->sc_dev, "apm init error %d\n", error);
return error;
}
void
iwm_apm_stop(struct iwm_softc *sc)
{
IWM_SETBITS(sc, IWM_CSR_DBG_LINK_PWR_MGMT_REG,
IWM_CSR_RESET_LINK_PWR_MGMT_DISABLED);
IWM_SETBITS(sc, IWM_CSR_HW_IF_CONFIG_REG,
IWM_CSR_HW_IF_CONFIG_REG_PREPARE |
IWM_CSR_HW_IF_CONFIG_REG_ENABLE_PME);
DELAY(1000);
IWM_CLRBITS(sc, IWM_CSR_DBG_LINK_PWR_MGMT_REG,
IWM_CSR_RESET_LINK_PWR_MGMT_DISABLED);
DELAY(5000);
IWM_SETBITS(sc, IWM_CSR_RESET, IWM_CSR_RESET_REG_FLAG_STOP_MASTER);
if (!iwm_poll_bit(sc, IWM_CSR_RESET,
IWM_CSR_RESET_REG_FLAG_MASTER_DISABLED,
IWM_CSR_RESET_REG_FLAG_MASTER_DISABLED, 100))
device_printf(sc->sc_dev, "timeout waiting for master\n");
IWM_CLRBITS(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_FLAG_INIT_DONE);
IWM_DPRINTF(sc, IWM_DEBUG_TRANS, "%s: iwm apm stop\n", __func__);
}
int
iwm_start_hw(struct iwm_softc *sc)
{
int error;
if ((error = iwm_prepare_card_hw(sc)) != 0)
return error;
IWM_WRITE(sc, IWM_CSR_RESET, IWM_CSR_RESET_REG_FLAG_SW_RESET);
DELAY(5000);
if ((error = iwm_apm_init(sc)) != 0)
return error;
if (sc->cfg->mqrx_supported)
iwm_write_prph(sc, IWM_UREG_CHICK, IWM_UREG_CHICK_MSI_ENABLE);
iwm_enable_rfkill_int(sc);
iwm_check_rfkill(sc);
return 0;
}
void
iwm_set_pwr(struct iwm_softc *sc)
{
iwm_set_bits_mask_prph(sc, IWM_APMG_PS_CTRL_REG,
IWM_APMG_PS_CTRL_VAL_PWR_SRC_VMAIN, ~IWM_APMG_PS_CTRL_MSK_PWR_SRC);
}
int
iwm_pcie_rx_stop(struct iwm_softc *sc)
{
int ret;
ret = 0;
if (iwm_nic_lock(sc)) {
if (sc->cfg->mqrx_supported) {
iwm_write_prph(sc, IWM_RFH_RXF_DMA_CFG, 0);
ret = iwm_poll_prph(sc, IWM_RFH_GEN_STATUS,
IWM_RXF_DMA_IDLE, IWM_RXF_DMA_IDLE, 1000);
} else {
IWM_WRITE(sc, IWM_FH_MEM_RCSR_CHNL0_CONFIG_REG, 0);
ret = iwm_poll_bit(sc, IWM_FH_MEM_RSSR_RX_STATUS_REG,
IWM_FH_RSSR_CHNL0_RX_STATUS_CHNL_IDLE,
IWM_FH_RSSR_CHNL0_RX_STATUS_CHNL_IDLE,
1000);
}
iwm_nic_unlock(sc);
}
return ret;
}
void
iwm_pcie_clear_cmd_in_flight(struct iwm_softc *sc)
{
if (!sc->cfg->apmg_wake_up_wa)
return;
if (!sc->cmd_hold_nic_awake) {
device_printf(sc->sc_dev,
"%s: cmd_hold_nic_awake not set\n", __func__);
return;
}
sc->cmd_hold_nic_awake = 0;
IWM_CLRBITS(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
}
int
iwm_pcie_set_cmd_in_flight(struct iwm_softc *sc)
{
int ret;
if (sc->cfg->apmg_wake_up_wa &&
!sc->cmd_hold_nic_awake) {
IWM_SETBITS(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
ret = iwm_poll_bit(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_VAL_MAC_ACCESS_EN,
(IWM_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY |
IWM_CSR_GP_CNTRL_REG_FLAG_GOING_TO_SLEEP),
15000);
if (ret == 0) {
IWM_CLRBITS(sc, IWM_CSR_GP_CNTRL,
IWM_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ);
device_printf(sc->sc_dev,
"%s: Failed to wake NIC for hcmd\n", __func__);
return EIO;
}
sc->cmd_hold_nic_awake = 1;
}
return 0;
}