|
@@ -68,6 +68,8 @@
|
|
|
#define CNSS_256KB_SIZE 0x40000
|
|
|
#define DEVICE_RDDM_COOKIE 0xCAFECACE
|
|
|
|
|
|
+static bool cnss_driver_registered;
|
|
|
+
|
|
|
static DEFINE_SPINLOCK(pci_link_down_lock);
|
|
|
static DEFINE_SPINLOCK(pci_reg_window_lock);
|
|
|
static DEFINE_SPINLOCK(time_sync_lock);
|
|
@@ -82,6 +84,8 @@ static DEFINE_SPINLOCK(time_sync_lock);
|
|
|
#define FORCE_WAKE_DELAY_MAX_US 6000
|
|
|
#define FORCE_WAKE_DELAY_TIMEOUT_US 60000
|
|
|
|
|
|
+#define REG_RETRY_MAX_TIMES 3
|
|
|
+
|
|
|
#define MHI_SUSPEND_RETRY_MAX_TIMES 3
|
|
|
#define MHI_SUSPEND_RETRY_DELAY_US 5000
|
|
|
|
|
@@ -667,6 +671,8 @@ static struct cnss_print_optimize print_optimize;
|
|
|
#define SYSPM_REG_SIZE ARRAY_SIZE(syspm_reg_access_seq)
|
|
|
|
|
|
static int cnss_pci_update_fw_name(struct cnss_pci_data *pci_priv);
|
|
|
+static void cnss_pci_suspend_pwroff(struct pci_dev *pci_dev);
|
|
|
+
|
|
|
|
|
|
#if IS_ENABLED(CONFIG_MHI_BUS_MISC)
|
|
|
static void cnss_mhi_debug_reg_dump(struct cnss_pci_data *pci_priv)
|
|
@@ -1933,11 +1939,158 @@ static int cnss_pci_config_msi_data(struct cnss_pci_data *pci_priv)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+#ifdef CONFIG_CNSS_SUPPORT_DUAL_DEV
|
|
|
+#define PLC_PCIE_NAME_LEN 14
|
|
|
+
|
|
|
+static struct cnss_plat_data *
|
|
|
+cnss_get_plat_priv_by_driver_ops(struct cnss_wlan_driver *driver_ops)
|
|
|
+{
|
|
|
+ int plat_env_count = cnss_get_plat_env_count();
|
|
|
+ struct cnss_plat_data *plat_env;
|
|
|
+ struct cnss_pci_data *pci_priv;
|
|
|
+ int i = 0;
|
|
|
+
|
|
|
+ if (!driver_ops) {
|
|
|
+ cnss_pr_err("No cnss driver\n");
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < plat_env_count; i++) {
|
|
|
+ plat_env = cnss_get_plat_env(i);
|
|
|
+ if (!plat_env)
|
|
|
+ continue;
|
|
|
+ if (driver_ops->name && plat_env->pld_bus_ops_name) {
|
|
|
+ /* driver_ops->name = PLD_PCIE_OPS_NAME
|
|
|
+ * #ifdef MULTI_IF_NAME
|
|
|
+ * #define PLD_PCIE_OPS_NAME "pld_pcie_" MULTI_IF_NAME
|
|
|
+ * #else
|
|
|
+ * #define PLD_PCIE_OPS_NAME "pld_pcie"
|
|
|
+ * #endif
|
|
|
+ */
|
|
|
+ if (memcmp(driver_ops->name,
|
|
|
+ plat_env->pld_bus_ops_name,
|
|
|
+ PLC_PCIE_NAME_LEN) == 0)
|
|
|
+ return plat_env;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ cnss_pr_err("Invalid cnss driver name from ko %s\n", driver_ops->name);
|
|
|
+ /* in the dual wlan card case, the pld_bus_ops_name from dts
|
|
|
+ * and driver_ops-> name from ko should match, otherwise
|
|
|
+ * wlanhost driver don't know which plat_env it can use;
|
|
|
+ * if doesn't find the match one, then get first available
|
|
|
+ * instance insteadly.
|
|
|
+ */
|
|
|
+
|
|
|
+ for (i = 0; i < plat_env_count; i++) {
|
|
|
+ plat_env = cnss_get_plat_env(i);
|
|
|
+
|
|
|
+ if (!plat_env)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ pci_priv = plat_env->bus_priv;
|
|
|
+ if (!pci_priv) {
|
|
|
+ cnss_pr_err("pci_priv is NULL\n");
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (driver_ops == pci_priv->driver_ops)
|
|
|
+ return plat_env;
|
|
|
+ }
|
|
|
+ /* Doesn't find the existing instance,
|
|
|
+ * so return the fist empty instance
|
|
|
+ */
|
|
|
+ for (i = 0; i < plat_env_count; i++) {
|
|
|
+ plat_env = cnss_get_plat_env(i);
|
|
|
+
|
|
|
+ if (!plat_env)
|
|
|
+ continue;
|
|
|
+ pci_priv = plat_env->bus_priv;
|
|
|
+ if (!pci_priv) {
|
|
|
+ cnss_pr_err("pci_priv is NULL\n");
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!pci_priv->driver_ops)
|
|
|
+ return plat_env;
|
|
|
+ }
|
|
|
+
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+static int cnss_pci_store_qrtr_node_id(struct cnss_pci_data *pci_priv)
|
|
|
+{
|
|
|
+ int ret = 0;
|
|
|
+ u32 scratch = QCA6390_PCIE_SOC_PCIE_REG_PCIE_SCRATCH_2_SOC_PCIE_REG;
|
|
|
+ struct cnss_plat_data *plat_priv;
|
|
|
+
|
|
|
+ if (!pci_priv) {
|
|
|
+ cnss_pr_err("pci_priv is NULL\n");
|
|
|
+ return -ENODEV;
|
|
|
+ }
|
|
|
+
|
|
|
+ plat_priv = pci_priv->plat_priv;
|
|
|
+ /**
|
|
|
+ * in the single wlan chipset case, plat_priv->qrtr_node_id always is 0,
|
|
|
+ * wlan fw will use the hardcode 7 as the qrtr node id.
|
|
|
+ * in the dual Hastings case, we will read qrtr node id
|
|
|
+ * from device tree and pass to get plat_priv->qrtr_node_id,
|
|
|
+ * which always is not zero. And then store this new value
|
|
|
+ * to pcie register, wlan fw will read out this qrtr node id
|
|
|
+ * from this register and overwrite to the hardcode one
|
|
|
+ * while do initialization for ipc router.
|
|
|
+ * without this change, two Hastings will use the same
|
|
|
+ * qrtr node instance id, which will mess up qmi message
|
|
|
+ * exchange. According to qrtr spec, every node should
|
|
|
+ * have unique qrtr node id
|
|
|
+ */
|
|
|
+ if (plat_priv->device_id == QCA6390_DEVICE_ID &&
|
|
|
+ plat_priv->qrtr_node_id) {
|
|
|
+ u32 val;
|
|
|
+
|
|
|
+ cnss_pr_dbg("write 0x%x to SCRATCH REG\n",
|
|
|
+ plat_priv->qrtr_node_id);
|
|
|
+ ret = cnss_pci_reg_write(pci_priv, scratch,
|
|
|
+ plat_priv->qrtr_node_id);
|
|
|
+ if (ret) {
|
|
|
+ cnss_pr_err("Failed to write register offset 0x%x, err = %d\n",
|
|
|
+ scratch, ret);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = cnss_pci_reg_read(pci_priv, scratch, &val);
|
|
|
+ if (ret) {
|
|
|
+ cnss_pr_err("Failed to read SCRATCH REG");
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (val != plat_priv->qrtr_node_id) {
|
|
|
+ cnss_pr_err("qrtr node id write to register doesn't match with readout value");
|
|
|
+ return -ERANGE;
|
|
|
+ }
|
|
|
+ }
|
|
|
+out:
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+#else
|
|
|
+static struct cnss_plat_data *
|
|
|
+cnss_get_plat_priv_by_driver_ops(struct cnss_wlan_driver *driver_ops)
|
|
|
+{
|
|
|
+ return cnss_bus_dev_to_plat_priv(NULL);
|
|
|
+}
|
|
|
+
|
|
|
+static int cnss_pci_store_qrtr_node_id(struct cnss_pci_data *pci_priv)
|
|
|
+{
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
int cnss_pci_start_mhi(struct cnss_pci_data *pci_priv)
|
|
|
{
|
|
|
int ret = 0;
|
|
|
struct cnss_plat_data *plat_priv;
|
|
|
unsigned int timeout = 0;
|
|
|
+ int retry = 0;
|
|
|
|
|
|
if (!pci_priv) {
|
|
|
cnss_pr_err("pci_priv is NULL\n");
|
|
@@ -1963,6 +2116,15 @@ int cnss_pci_start_mhi(struct cnss_pci_data *pci_priv)
|
|
|
else /* For perf builds the timeout is 10 (default) * 3 seconds */
|
|
|
pci_priv->mhi_ctrl->timeout_ms *= 3;
|
|
|
|
|
|
+retry:
|
|
|
+ ret = cnss_pci_store_qrtr_node_id(pci_priv);
|
|
|
+ if (ret) {
|
|
|
+ if (retry++ < REG_RETRY_MAX_TIMES)
|
|
|
+ goto retry;
|
|
|
+ else
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
/* Start the timer to dump MHI/PBL/SBL debug data periodically */
|
|
|
mod_timer(&pci_priv->boot_debug_timer,
|
|
|
jiffies + msecs_to_jiffies(BOOT_DEBUG_TIMEOUT_MS));
|
|
@@ -3111,7 +3273,7 @@ reg_driver:
|
|
|
int cnss_wlan_register_driver(struct cnss_wlan_driver *driver_ops)
|
|
|
{
|
|
|
int ret = 0;
|
|
|
- struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(NULL);
|
|
|
+ struct cnss_plat_data *plat_priv;
|
|
|
struct cnss_pci_data *pci_priv;
|
|
|
const struct pci_device_id *id_table = driver_ops->id_table;
|
|
|
unsigned int timeout;
|
|
@@ -3121,6 +3283,8 @@ int cnss_wlan_register_driver(struct cnss_wlan_driver *driver_ops)
|
|
|
return -ENODEV;
|
|
|
}
|
|
|
|
|
|
+ plat_priv = cnss_get_plat_priv_by_driver_ops(driver_ops);
|
|
|
+
|
|
|
if (!plat_priv) {
|
|
|
cnss_pr_buf("plat_priv is not ready for register driver\n");
|
|
|
return -EAGAIN;
|
|
@@ -3216,10 +3380,11 @@ EXPORT_SYMBOL(cnss_wlan_register_driver);
|
|
|
|
|
|
void cnss_wlan_unregister_driver(struct cnss_wlan_driver *driver_ops)
|
|
|
{
|
|
|
- struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(NULL);
|
|
|
+ struct cnss_plat_data *plat_priv;
|
|
|
int ret = 0;
|
|
|
unsigned int timeout;
|
|
|
|
|
|
+ plat_priv = cnss_get_plat_priv_by_driver_ops(driver_ops);
|
|
|
if (!plat_priv) {
|
|
|
cnss_pr_err("plat_priv is NULL\n");
|
|
|
return;
|
|
@@ -5907,6 +6072,10 @@ static int cnss_pci_register_mhi(struct cnss_pci_data *pci_priv)
|
|
|
const struct mhi_controller_config *cnss_mhi_config =
|
|
|
&cnss_mhi_config_default;
|
|
|
|
|
|
+ ret = cnss_qmi_init(plat_priv);
|
|
|
+ if (ret)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
if (pci_priv->device_id == QCA6174_DEVICE_ID)
|
|
|
return 0;
|
|
|
|
|
@@ -6150,6 +6319,57 @@ static void cnss_pci_wake_gpio_deinit(struct cnss_pci_data *pci_priv)
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
+#ifdef CONFIG_CNSS_SUPPORT_DUAL_DEV
|
|
|
+static int cnss_try_suspend(struct cnss_plat_data *plat_priv)
|
|
|
+{
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ /* in the dual wlan card case, if call pci_register_driver after
|
|
|
+ * finishing the first pcie device enumeration, it will cause
|
|
|
+ * the cnss_pci_probe called in advance with the second wlan card,
|
|
|
+ * and the sequence like this:
|
|
|
+ * enter msm_pcie_enumerate -> pci_bus_add_devices -> cnss_pci_probe
|
|
|
+ * -> exit msm_pcie_enumerate.
|
|
|
+ * But the correct sequence we expected is like this:
|
|
|
+ * enter msm_pcie_enumerate -> pci_bus_add_devices ->
|
|
|
+ * exit msm_pcie_enumerate -> cnss_pci_probe.
|
|
|
+ * And this unexpected sequence will make the second wlan card do
|
|
|
+ * pcie link suspend while the pcie enumeration not finished.
|
|
|
+ * So need to add below logical to avoid doing pcie link suspend
|
|
|
+ * if the enumeration has not finish.
|
|
|
+ */
|
|
|
+ plat_priv->enumerate_done = true;
|
|
|
+
|
|
|
+ /* Now enumeration is finished, try to suspend PCIe link */
|
|
|
+ if (plat_priv->bus_priv) {
|
|
|
+ struct cnss_pci_data *pci_priv = plat_priv->bus_priv;
|
|
|
+ struct pci_dev *pci_dev = pci_priv->pci_dev;
|
|
|
+
|
|
|
+ switch (pci_dev->device) {
|
|
|
+ case QCA6390_DEVICE_ID:
|
|
|
+ cnss_pci_set_wlaon_pwr_ctrl(pci_priv,
|
|
|
+ false,
|
|
|
+ true,
|
|
|
+ false);
|
|
|
+
|
|
|
+ cnss_pci_suspend_pwroff(pci_dev);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ cnss_pr_err("Unknown PCI device found: 0x%x\n",
|
|
|
+ pci_dev->device);
|
|
|
+ ret = -ENODEV;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+#else
|
|
|
+static int cnss_try_suspend(struct cnss_plat_data *plat_priv)
|
|
|
+{
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
/* Setting to use this cnss_pm_domain ops will let PM framework override the
|
|
|
* ops from dev->bus->pm which is pci_dev_pm_ops from pci-driver.c. This ops
|
|
|
* has to take care everything device driver needed which is currently done
|
|
@@ -6238,10 +6458,13 @@ static bool cnss_should_suspend_pwroff(struct pci_dev *pci_dev)
|
|
|
static void cnss_pci_suspend_pwroff(struct pci_dev *pci_dev)
|
|
|
{
|
|
|
struct cnss_pci_data *pci_priv = cnss_get_pci_priv(pci_dev);
|
|
|
- struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(NULL);
|
|
|
+ int rc_num = pci_dev->bus->domain_nr;
|
|
|
+ struct cnss_plat_data *plat_priv;
|
|
|
int ret = 0;
|
|
|
bool suspend_pwroff = cnss_should_suspend_pwroff(pci_dev);
|
|
|
|
|
|
+ plat_priv = cnss_get_plat_priv_by_rc_num(rc_num);
|
|
|
+
|
|
|
if (suspend_pwroff) {
|
|
|
ret = cnss_suspend_pci_link(pci_priv);
|
|
|
if (ret)
|
|
@@ -6259,11 +6482,17 @@ static int cnss_pci_probe(struct pci_dev *pci_dev,
|
|
|
{
|
|
|
int ret = 0;
|
|
|
struct cnss_pci_data *pci_priv;
|
|
|
- struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(NULL);
|
|
|
struct device *dev = &pci_dev->dev;
|
|
|
+ int rc_num = pci_dev->bus->domain_nr;
|
|
|
+ struct cnss_plat_data *plat_priv = cnss_get_plat_priv_by_rc_num(rc_num);
|
|
|
|
|
|
- cnss_pr_dbg("PCI is probing, vendor ID: 0x%x, device ID: 0x%x\n",
|
|
|
- id->vendor, pci_dev->device);
|
|
|
+ cnss_pr_dbg("PCI is probing, vendor ID: 0x%x, device ID: 0x%x rc_num %d\n",
|
|
|
+ id->vendor, pci_dev->device, rc_num);
|
|
|
+ if (!plat_priv) {
|
|
|
+ cnss_pr_err("Find match plat_priv with rc number failure\n");
|
|
|
+ ret = -ENODEV;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
|
|
|
pci_priv = devm_kzalloc(dev, sizeof(*pci_priv), GFP_KERNEL);
|
|
|
if (!pci_priv) {
|
|
@@ -6336,7 +6565,11 @@ static int cnss_pci_probe(struct pci_dev *pci_dev,
|
|
|
case KIWI_DEVICE_ID:
|
|
|
case MANGO_DEVICE_ID:
|
|
|
case PEACH_DEVICE_ID:
|
|
|
- cnss_pci_set_wlaon_pwr_ctrl(pci_priv, false, false, false);
|
|
|
+ if ((cnss_is_dual_wlan_enabled() &&
|
|
|
+ plat_priv->enumerate_done) || !cnss_is_dual_wlan_enabled())
|
|
|
+ cnss_pci_set_wlaon_pwr_ctrl(pci_priv, false, false,
|
|
|
+ false);
|
|
|
+
|
|
|
timer_setup(&pci_priv->dev_rddm_timer,
|
|
|
cnss_dev_rddm_timeout_hdlr, 0);
|
|
|
timer_setup(&pci_priv->boot_debug_timer,
|
|
@@ -6357,7 +6590,11 @@ static int cnss_pci_probe(struct pci_dev *pci_dev,
|
|
|
cnss_pci_config_regs(pci_priv);
|
|
|
if (EMULATION_HW)
|
|
|
goto out;
|
|
|
+ if (cnss_is_dual_wlan_enabled() && !plat_priv->enumerate_done)
|
|
|
+ goto probe_done;
|
|
|
cnss_pci_suspend_pwroff(pci_dev);
|
|
|
+
|
|
|
+probe_done:
|
|
|
set_bit(CNSS_PCI_PROBE_DONE, &plat_priv->driver_state);
|
|
|
|
|
|
return 0;
|
|
@@ -6443,7 +6680,7 @@ static const struct dev_pm_ops cnss_pm_ops = {
|
|
|
cnss_pci_runtime_idle)
|
|
|
};
|
|
|
|
|
|
-struct pci_driver cnss_pci_driver = {
|
|
|
+static struct pci_driver cnss_pci_driver = {
|
|
|
.name = "cnss_pci",
|
|
|
.id_table = cnss_pci_id_table,
|
|
|
.probe = cnss_pci_probe,
|
|
@@ -6515,17 +6752,25 @@ int cnss_pci_init(struct cnss_plat_data *plat_priv)
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
|
- ret = pci_register_driver(&cnss_pci_driver);
|
|
|
+ ret = cnss_try_suspend(plat_priv);
|
|
|
if (ret) {
|
|
|
- cnss_pr_err("Failed to register to PCI framework, err = %d\n",
|
|
|
- ret);
|
|
|
+ cnss_pr_err("Failed to suspend, ret: %d\n", ret);
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
|
- if (!plat_priv->bus_priv) {
|
|
|
- cnss_pr_err("Failed to probe PCI driver\n");
|
|
|
- ret = -ENODEV;
|
|
|
- goto unreg_pci;
|
|
|
+ if (!cnss_driver_registered) {
|
|
|
+ ret = pci_register_driver(&cnss_pci_driver);
|
|
|
+ if (ret) {
|
|
|
+ cnss_pr_err("Failed to register to PCI framework, err = %d\n",
|
|
|
+ ret);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+ if (!plat_priv->bus_priv) {
|
|
|
+ cnss_pr_err("Failed to probe PCI driver\n");
|
|
|
+ ret = -ENODEV;
|
|
|
+ goto unreg_pci;
|
|
|
+ }
|
|
|
+ cnss_driver_registered = true;
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
@@ -6538,5 +6783,8 @@ out:
|
|
|
|
|
|
void cnss_pci_deinit(struct cnss_plat_data *plat_priv)
|
|
|
{
|
|
|
- pci_unregister_driver(&cnss_pci_driver);
|
|
|
+ if (cnss_driver_registered) {
|
|
|
+ pci_unregister_driver(&cnss_pci_driver);
|
|
|
+ cnss_driver_registered = false;
|
|
|
+ }
|
|
|
}
|