Commit f3c603d4 authored by Carl Huang's avatar Carl Huang Committed by Kalle Valo
Browse files

ath11k: reset MHI during power down and power up



For QCA6390, normal power up and power down can't bring MHI
to a workable state. This happens especially in warm reboot
and rmmod and insmod. Host needs to write a few registers to
bring MHI to normal state.

Tested-on: QCA6390 hw2.0 PCI WLAN.HST.1.0.1-01740-QCAHSTSWPLZ_V2_TO_X86-1
Tested-on: IPQ8074 hw2.0 AHB WLAN.HK.2.1.0.1-01238-QCAHKSWPL_SILICONZ-2

Signed-off-by: default avatarCarl Huang <cjhuang@codeauroro.org>
Signed-off-by: default avatarKalle Valo <kvalo@codeaurora.org>
Link: https://lore.kernel.org/r/1597555891-26112-10-git-send-email-kvalo@codeaurora.org
parent 065f5f68
Loading
Loading
Loading
Loading
+45 −1
Original line number Diff line number Diff line
@@ -107,6 +107,51 @@ static struct mhi_controller_config ath11k_mhi_config = {
	.event_cfg = ath11k_mhi_events,
};

void ath11k_mhi_set_mhictrl_reset(struct ath11k_base *ab)
{
	u32 val;

	val = ath11k_pci_read32(ab, MHISTATUS);

	ath11k_dbg(ab, ATH11K_DBG_PCI, "MHISTATUS 0x%x\n", val);

	/* Observed on QCA6390 that after SOC_GLOBAL_RESET, MHISTATUS
	 * has SYSERR bit set and thus need to set MHICTRL_RESET
	 * to clear SYSERR.
	 */
	ath11k_pci_write32(ab, MHICTRL, MHICTRL_RESET_MASK);

	mdelay(10);
}

static void ath11k_mhi_reset_txvecdb(struct ath11k_base *ab)
{
	ath11k_pci_write32(ab, PCIE_TXVECDB, 0);
}

static void ath11k_mhi_reset_txvecstatus(struct ath11k_base *ab)
{
	ath11k_pci_write32(ab, PCIE_TXVECSTATUS, 0);
}

static void ath11k_mhi_reset_rxvecdb(struct ath11k_base *ab)
{
	ath11k_pci_write32(ab, PCIE_RXVECDB, 0);
}

static void ath11k_mhi_reset_rxvecstatus(struct ath11k_base *ab)
{
	ath11k_pci_write32(ab, PCIE_RXVECSTATUS, 0);
}

void ath11k_mhi_clear_vector(struct ath11k_base *ab)
{
	ath11k_mhi_reset_txvecdb(ab);
	ath11k_mhi_reset_txvecstatus(ab);
	ath11k_mhi_reset_rxvecdb(ab);
	ath11k_mhi_reset_rxvecstatus(ab);
}

static int ath11k_mhi_get_msi(struct ath11k_pci *ab_pci)
{
	struct ath11k_base *ab = ab_pci->ab;
@@ -416,7 +461,6 @@ int ath11k_mhi_start(struct ath11k_pci *ab_pci)

void ath11k_mhi_stop(struct ath11k_pci *ab_pci)
{
	ath11k_mhi_set_state(ab_pci, ATH11K_MHI_RESUME);
	ath11k_mhi_set_state(ab_pci, ATH11K_MHI_POWER_OFF);
	ath11k_mhi_set_state(ab_pci, ATH11K_MHI_DEINIT);
}
+11 −0
Original line number Diff line number Diff line
@@ -7,6 +7,15 @@

#include "pci.h"

#define PCIE_TXVECDB				0x360
#define PCIE_TXVECSTATUS			0x368
#define PCIE_RXVECDB				0x394
#define PCIE_RXVECSTATUS			0x39C

#define MHISTATUS				0x48
#define MHICTRL					0x38
#define MHICTRL_RESET_MASK			0x2

enum ath11k_mhi_state {
	ATH11K_MHI_INIT,
	ATH11K_MHI_DEINIT,
@@ -24,5 +33,7 @@ int ath11k_mhi_start(struct ath11k_pci *ar_pci);
void ath11k_mhi_stop(struct ath11k_pci *ar_pci);
int ath11k_mhi_register(struct ath11k_pci *ar_pci);
void ath11k_mhi_unregister(struct ath11k_pci *ar_pci);
void ath11k_mhi_set_mhictrl_reset(struct ath11k_base *ab);
void ath11k_mhi_clear_vector(struct ath11k_base *ab);

#endif
+77 −2
Original line number Diff line number Diff line
@@ -301,7 +301,7 @@ static inline void ath11k_pci_select_window(struct ath11k_pci *ab_pci, u32 offse
	}
}

static void ath11k_pci_write32(struct ath11k_base *ab, u32 offset, u32 value)
void ath11k_pci_write32(struct ath11k_base *ab, u32 offset, u32 value)
{
	struct ath11k_pci *ab_pci = ath11k_pci_priv(ab);

@@ -315,7 +315,7 @@ static void ath11k_pci_write32(struct ath11k_base *ab, u32 offset, u32 value)
	}
}

static u32 ath11k_pci_read32(struct ath11k_base *ab, u32 offset)
u32 ath11k_pci_read32(struct ath11k_base *ab, u32 offset)
{
	struct ath11k_pci *ab_pci = ath11k_pci_priv(ab);
	u32 val;
@@ -332,6 +332,77 @@ static u32 ath11k_pci_read32(struct ath11k_base *ab, u32 offset)
	return val;
}

static void ath11k_pci_soc_global_reset(struct ath11k_base *ab)
{
	u32 val, delay;

	val = ath11k_pci_read32(ab, PCIE_SOC_GLOBAL_RESET);

	val |= PCIE_SOC_GLOBAL_RESET_V;

	ath11k_pci_write32(ab, PCIE_SOC_GLOBAL_RESET, val);

	/* TODO: exact time to sleep is uncertain */
	delay = 10;
	mdelay(delay);

	/* Need to toggle V bit back otherwise stuck in reset status */
	val &= ~PCIE_SOC_GLOBAL_RESET_V;

	ath11k_pci_write32(ab, PCIE_SOC_GLOBAL_RESET, val);

	mdelay(delay);

	val = ath11k_pci_read32(ab, PCIE_SOC_GLOBAL_RESET);
	if (val == 0xffffffff)
		ath11k_warn(ab, "link down error during global reset\n");
}

static void ath11k_pci_clear_dbg_registers(struct ath11k_base *ab)
{
	u32 val;

	/* read cookie */
	val = ath11k_pci_read32(ab, PCIE_Q6_COOKIE_ADDR);
	ath11k_dbg(ab, ATH11K_DBG_PCI, "cookie:0x%x\n", val);

	val = ath11k_pci_read32(ab, WLAON_WARM_SW_ENTRY);
	ath11k_dbg(ab, ATH11K_DBG_PCI, "WLAON_WARM_SW_ENTRY 0x%x\n", val);

	/* TODO: exact time to sleep is uncertain */
	mdelay(10);

	/* write 0 to WLAON_WARM_SW_ENTRY to prevent Q6 from
	 * continuing warm path and entering dead loop.
	 */
	ath11k_pci_write32(ab, WLAON_WARM_SW_ENTRY, 0);
	mdelay(10);

	val = ath11k_pci_read32(ab, WLAON_WARM_SW_ENTRY);
	ath11k_dbg(ab, ATH11K_DBG_PCI, "WLAON_WARM_SW_ENTRY 0x%x\n", val);

	/* A read clear register. clear the register to prevent
	 * Q6 from entering wrong code path.
	 */
	val = ath11k_pci_read32(ab, WLAON_SOC_RESET_CAUSE_REG);
	ath11k_dbg(ab, ATH11K_DBG_PCI, "soc reset cause:%d\n", val);
}

static void ath11k_pci_force_wake(struct ath11k_base *ab)
{
	ath11k_pci_write32(ab, PCIE_SOC_WAKE_PCIE_LOCAL_REG, 1);
	mdelay(5);
}

static void ath11k_pci_sw_reset(struct ath11k_base *ab)
{
	ath11k_pci_soc_global_reset(ab);
	ath11k_mhi_clear_vector(ab);
	ath11k_pci_soc_global_reset(ab);
	ath11k_mhi_set_mhictrl_reset(ab);
	ath11k_pci_clear_dbg_registers(ab);
}

int ath11k_pci_get_msi_irq(struct device *dev, unsigned int vector)
{
	struct pci_dev *pci_dev = to_pci_dev(dev);
@@ -834,6 +905,8 @@ static int ath11k_pci_power_up(struct ath11k_base *ab)
	struct ath11k_pci *ab_pci = ath11k_pci_priv(ab);
	int ret;

	ath11k_pci_sw_reset(ab_pci->ab);

	ret = ath11k_mhi_start(ab_pci);
	if (ret) {
		ath11k_err(ab, "failed to start mhi: %d\n", ret);
@@ -848,6 +921,8 @@ static void ath11k_pci_power_down(struct ath11k_base *ab)
	struct ath11k_pci *ab_pci = ath11k_pci_priv(ab);

	ath11k_mhi_stop(ab_pci);
	ath11k_pci_force_wake(ab_pci->ab);
	ath11k_pci_sw_reset(ab_pci->ab);
}

static void ath11k_pci_kill_tasklets(struct ath11k_base *ab)
+17 −0
Original line number Diff line number Diff line
@@ -9,6 +9,21 @@

#include "core.h"

#define PCIE_SOC_GLOBAL_RESET			0x3008
#define PCIE_SOC_GLOBAL_RESET_V			1

#define WLAON_WARM_SW_ENTRY			0x1f80504
#define WLAON_SOC_RESET_CAUSE_REG		0x01f8060c

#define PCIE_Q6_COOKIE_ADDR			0x01f80500
#define PCIE_Q6_COOKIE_DATA			0xc0000000

/* register to wake the UMAC from power collapse */
#define PCIE_SCRATCH_0_SOC_PCIE_REG		0x4040

/* register used for handshake mechanism to validate UMAC is awake */
#define PCIE_SOC_WAKE_PCIE_LOCAL_REG		0x3004

struct ath11k_msi_user {
	char *name;
	int num_vectors;
@@ -44,5 +59,7 @@ int ath11k_pci_get_user_msi_assignment(struct ath11k_pci *ar_pci, char *user_nam
				       int *num_vectors, u32 *user_base_data,
				       u32 *base_vector);
int ath11k_pci_get_msi_irq(struct device *dev, unsigned int vector);
void ath11k_pci_write32(struct ath11k_base *ab, u32 offset, u32 value);
u32 ath11k_pci_read32(struct ath11k_base *ab, u32 offset);

#endif