Commit 70582b82 authored by Johannes Berg's avatar Johannes Berg

wifi: iwlwifi: pcie: work around ROM bug on AX210 integrated

On 22000 and AX210 devices, there's a ROM bug that causes it to
set invalid LTR settings. On 22000 and AX210 non-integrated we
can fix up these settings from the driver (as done in the code
here), but on AX210 integrated these registers are not available
to the driver.

Attempt to work around the issue by spinning while the IML is
being loaded, the IML will then reprogram the LTR values itself
after it's loaded, so only the brief IML load (which the ROM is
doing) is affected.
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
Signed-off-by: default avatarGregory Greenman <gregory.greenman@intel.com>
Link: https://lore.kernel.org/r/20230413213309.aaa0a4339984.If08da23e960b6236f8c05c06fc8b26041ac89f1e@changeidSigned-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
parent d4830432
......@@ -102,6 +102,8 @@
#define CSR_LTR_LONG_VAL_AD_SNOOP_VAL 0x000003ff
#define CSR_LTR_LONG_VAL_AD_SCALE_USEC 2
#define CSR_LTR_LAST_MSG (CSR_BASE + 0x0DC)
/* GIO Chicken Bits (PCI Express bus link power management) */
#define CSR_GIO_CHICKEN_BITS (CSR_BASE+0x100)
......
......@@ -350,7 +350,7 @@ void iwl_trans_pcie_gen2_fw_alive(struct iwl_trans *trans, u32 scd_addr)
mutex_unlock(&trans_pcie->mutex);
}
static void iwl_pcie_set_ltr(struct iwl_trans *trans)
static bool iwl_pcie_set_ltr(struct iwl_trans *trans)
{
u32 ltr_val = CSR_LTR_LONG_VAL_AD_NO_SNOOP_REQ |
u32_encode_bits(CSR_LTR_LONG_VAL_AD_SCALE_USEC,
......@@ -371,18 +371,77 @@ static void iwl_pcie_set_ltr(struct iwl_trans *trans)
trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_22000) &&
!trans->trans_cfg->integrated) {
iwl_write32(trans, CSR_LTR_LONG_VAL_AD, ltr_val);
} else if (trans->trans_cfg->integrated &&
trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_22000) {
return true;
}
if (trans->trans_cfg->integrated &&
trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_22000) {
iwl_write_prph(trans, HPM_MAC_LTR_CSR, HPM_MAC_LRT_ENABLE_ALL);
iwl_write_prph(trans, HPM_UMAC_LTR, ltr_val);
return true;
}
if (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_AX210) {
/* First clear the interrupt, just in case */
iwl_write32(trans, CSR_MSIX_HW_INT_CAUSES_AD,
MSIX_HW_INT_CAUSES_REG_IML);
/* In this case, unfortunately the same ROM bug exists in the
* device (not setting LTR correctly), but we don't have control
* over the settings from the host due to some hardware security
* features. The only workaround we've been able to come up with
* so far is to try to keep the CPU and device busy by polling
* it and the IML (image loader) completed interrupt.
*/
return false;
}
/* nothing needs to be done on other devices */
return true;
}
static void iwl_pcie_spin_for_iml(struct iwl_trans *trans)
{
/* in practice, this seems to complete in around 20-30ms at most, wait 100 */
#define IML_WAIT_TIMEOUT (HZ / 10)
struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
unsigned long end_time = jiffies + IML_WAIT_TIMEOUT;
u32 value, loops = 0;
bool irq = false;
if (WARN_ON(!trans_pcie->iml))
return;
value = iwl_read32(trans, CSR_LTR_LAST_MSG);
IWL_DEBUG_INFO(trans, "Polling for IML load - CSR_LTR_LAST_MSG=0x%x\n",
value);
while (time_before(jiffies, end_time)) {
if (iwl_read32(trans, CSR_MSIX_HW_INT_CAUSES_AD) &
MSIX_HW_INT_CAUSES_REG_IML) {
irq = true;
break;
}
/* Keep the CPU and device busy. */
value = iwl_read32(trans, CSR_LTR_LAST_MSG);
loops++;
}
IWL_DEBUG_INFO(trans,
"Polled for IML load: irq=%d, loops=%d, CSR_LTR_LAST_MSG=0x%x\n",
irq, loops, value);
/* We don't fail here even if we timed out - maybe we get lucky and the
* interrupt comes in later (and we get alive from firmware) and then
* we're all happy - but if not we'll fail on alive timeout or get some
* other error out.
*/
}
int iwl_trans_pcie_gen2_start_fw(struct iwl_trans *trans,
const struct fw_img *fw, bool run_in_rfkill)
{
struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
bool hw_rfkill;
bool hw_rfkill, keep_ram_busy;
int ret;
/* This may fail if AMT took ownership of the device */
......@@ -443,7 +502,7 @@ int iwl_trans_pcie_gen2_start_fw(struct iwl_trans *trans,
if (ret)
goto out;
iwl_pcie_set_ltr(trans);
keep_ram_busy = !iwl_pcie_set_ltr(trans);
if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_BZ) {
iwl_write32(trans, CSR_FUNC_SCRATCH, CSR_FUNC_SCRATCH_INIT_VALUE);
......@@ -455,6 +514,9 @@ int iwl_trans_pcie_gen2_start_fw(struct iwl_trans *trans,
iwl_write_prph(trans, UREG_CPU_INIT_RUN, 1);
}
if (keep_ram_busy)
iwl_pcie_spin_for_iml(trans);
/* re-check RF-Kill state since we may have missed the interrupt */
hw_rfkill = iwl_pcie_check_hw_rf_kill(trans);
if (hw_rfkill && !run_in_rfkill)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment