#include "debug.h"
#include "pci.h"
#include "usb.h"
+#include "sdio.h"
enum cnss_dev_bus_type cnss_get_dev_bus_type(struct device *dev)
{
return CNSS_BUS_PCI;
else if (memcmp(dev->bus->name, "usb", 3) == 0)
return CNSS_BUS_USB;
+ else if (memcmp(dev->bus->name, "sdio", 4) == 0)
+ return CNSS_BUS_SDIO;
else
return CNSS_BUS_NONE;
}
case QCN7605_VER20_STANDALONE_DEVICE_ID:
case QCN7605_VER20_COMPOSITE_DEVICE_ID:
return CNSS_BUS_USB;
+ case QCN7605_SDIO_DEVICE_ID:
+ return CNSS_BUS_SDIO;
default:
cnss_pr_err("Unknown device_id: 0x%lx\n", device_id);
return CNSS_BUS_NONE;
}
}
-bool cnss_bus_req_mem_ind_valid(struct cnss_plat_data *plat_priv)
-{
- if (cnss_get_bus_type(plat_priv->device_id) == CNSS_BUS_USB)
- return false;
- else
- return true;
-}
-
void *cnss_bus_dev_to_bus_priv(struct device *dev)
{
if (!dev)
return cnss_pci_priv_to_plat_priv(bus_priv);
case CNSS_BUS_USB:
return cnss_usb_priv_to_plat_priv(bus_priv);
+ case CNSS_BUS_SDIO:
+ return cnss_get_plat_priv(NULL);
default:
return NULL;
}
return cnss_pci_init(plat_priv);
case CNSS_BUS_USB:
return cnss_usb_init(plat_priv);
+ case CNSS_BUS_SDIO:
+ return cnss_sdio_init(plat_priv);
default:
cnss_pr_err("Unsupported bus type: %d\n",
plat_priv->bus_type);
switch (plat_priv->bus_type) {
case CNSS_BUS_PCI:
cnss_pci_deinit(plat_priv);
+ break;
case CNSS_BUS_USB:
cnss_usb_deinit(plat_priv);
+ break;
+ case CNSS_BUS_SDIO:
+ cnss_sdio_deinit(plat_priv);
+ break;
default:
cnss_pr_err("Unsupported bus type: %d\n",
plat_priv->bus_type);
- return;
}
+ return;
}
int cnss_bus_load_m3(struct cnss_plat_data *plat_priv)
return cnss_pci_fw_boot_timeout_hdlr(plat_priv->bus_priv);
case CNSS_BUS_USB:
return cnss_usb_fw_boot_timeout_hdlr(plat_priv->bus_priv);
+ case CNSS_BUS_SDIO:
+ return cnss_sdio_fw_boot_timeout_hdlr(plat_priv->bus_priv);
default:
cnss_pr_err("Unsupported bus type: %d\n",
plat_priv->bus_type);
return cnss_pci_call_driver_probe(plat_priv->bus_priv);
case CNSS_BUS_USB:
return cnss_usb_call_driver_probe(plat_priv->bus_priv);
+ case CNSS_BUS_SDIO:
+ return cnss_sdio_call_driver_probe(plat_priv->bus_priv);
default:
cnss_pr_err("Unsupported bus type: %d\n",
plat_priv->bus_type);
return cnss_pci_call_driver_remove(plat_priv->bus_priv);
case CNSS_BUS_USB:
return cnss_usb_call_driver_remove(plat_priv->bus_priv);
+ case CNSS_BUS_SDIO:
+ return cnss_sdio_call_driver_remove(plat_priv->bus_priv);
default:
cnss_pr_err("Unsupported bus type: %d\n",
plat_priv->bus_type);
return cnss_pci_dev_powerup(plat_priv->bus_priv);
case CNSS_BUS_USB:
return cnss_usb_dev_powerup(plat_priv);
+ case CNSS_BUS_SDIO:
+ return cnss_sdio_dev_powerup(plat_priv->bus_priv);
default:
cnss_pr_err("Unsupported bus type: %d\n",
plat_priv->bus_type);
return cnss_pci_dev_shutdown(plat_priv->bus_priv);
case CNSS_BUS_USB:
return 0;
+ case CNSS_BUS_SDIO:
+ return cnss_sdio_dev_shutdown(plat_priv->bus_priv);
default:
cnss_pr_err("Unsupported bus type: %d\n",
plat_priv->bus_type);
return cnss_pci_register_driver_hdlr(plat_priv->bus_priv, data);
case CNSS_BUS_USB:
return cnss_usb_register_driver_hdlr(plat_priv->bus_priv, data);
+ case CNSS_BUS_SDIO:
+ return cnss_sdio_register_driver_hdlr(plat_priv->bus_priv,
+ data);
default:
cnss_pr_err("Unsupported bus type: %d\n",
plat_priv->bus_type);
return cnss_pci_unregister_driver_hdlr(plat_priv->bus_priv);
case CNSS_BUS_USB:
return cnss_usb_unregister_driver_hdlr(plat_priv->bus_priv);
+ case CNSS_BUS_SDIO:
+ return cnss_sdio_unregister_driver_hdlr(plat_priv->bus_priv);
default:
cnss_pr_err("Unsupported bus type: %d\n",
plat_priv->bus_type);
enum cnss_driver_mode mode,
const char *host_version)
{
- struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);
+ struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(NULL);
struct wlfw_wlan_cfg_req_msg_v01 req;
u32 i, ce_id, num_vectors, user_base_data, base_vector;
int ret = 0;
if (qmi_bypass)
return 0;
- if (cnss_get_bus_type(plat_priv->device_id) == CNSS_BUS_USB)
+ if (plat_priv->bus_type == CNSS_BUS_USB ||
+ plat_priv->bus_type == CNSS_BUS_SDIO)
goto skip_cfg;
if (!config || !host_version) {
int cnss_wlan_disable(struct device *dev, enum cnss_driver_mode mode)
{
- struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(dev);
+ struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(NULL);
if (plat_priv->device_id == QCA6174_DEVICE_ID)
return 0;
if (ret)
goto out;
- if (!cnss_bus_req_mem_ind_valid(plat_priv)) {
+ if (plat_priv->bus_type == CNSS_BUS_USB ||
+ plat_priv->bus_type == CNSS_BUS_SDIO) {
ret = cnss_wlfw_tgt_cap_send_sync(plat_priv);
if (ret)
goto out;
if (test_bit(CNSS_DEV_REMOVED, &plat_priv->driver_state))
pwr_up_reqd = true;
- if (pwr_up_reqd || plat_priv->bus_type != CNSS_BUS_USB)
+ if (pwr_up_reqd || plat_priv->bus_type == CNSS_BUS_PCI)
ret = cnss_bus_dev_powerup(plat_priv);
if (ret)
case QCN7605_COMPOSITE_DEVICE_ID:
case QCN7605_VER20_STANDALONE_DEVICE_ID:
case QCN7605_VER20_COMPOSITE_DEVICE_ID:
+ case QCN7605_SDIO_DEVICE_ID:
subsys_info->subsys_desc.name = "QCN7605";
break;
default:
return count;
}
+#ifdef CONFIG_SDIO_QCN
+static void cnss_set_card_state(bool state)
+{
+ qcn_sdio_card_state(state);
+}
+#else
+static void cnss_set_card_state(bool state)
+{
+ /* no op */
+}
+#endif
+
static DEVICE_ATTR(fs_ready, 0220, NULL, cnss_fs_ready_store);
static ssize_t cnss_wl_pwr_on(struct device *dev,
timeout = cnss_get_qmi_timeout();
if (pwr_state) {
cnss_power_on_device(plat_priv);
+ cnss_set_card_state(true);
if (timeout) {
mod_timer(&plat_priv->fw_boot_timer,
jiffies + msecs_to_jiffies(timeout));
}
} else {
cnss_power_off_device(plat_priv);
+ cnss_set_card_state(false);
del_timer(&plat_priv->fw_boot_timer);
}
return count;
{ .name = "qca6174", .driver_data = QCA6174_DEVICE_ID, },
{ .name = "qca6290", .driver_data = QCA6290_DEVICE_ID, },
{ .name = "qcn7605", .driver_data = QCN7605_DEVICE_ID, },
+ { .name = "qcn7605_sdio", .driver_data = QCN7605_SDIO_DEVICE_ID, },
};
static const struct of_device_id cnss_of_match_table[] = {
{
.compatible = "qcom,cnss-qca6290",
.data = (void *)&cnss_platform_id_table[1]},
+ {
+ .compatible = "qcom,cnss",
+ .data = (void *)&cnss_platform_id_table[2]},
+ {
+ .compatible = "qcom,cnss-sdio",
+ .data = (void *)&cnss_platform_id_table[3]},
{ },
};
MODULE_DEVICE_TABLE(of, cnss_of_match_table);
goto free_res;
ret = cnss_bus_init(plat_priv);
- if (ret)
+ if (ret == -EPROBE_DEFER)
+ goto free_res;
+ else if (ret)
goto power_off;
}
--- /dev/null
+/* Copyright (c) 2015-2019, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <soc/qcom/subsystem_restart.h>
+#include <soc/qcom/subsystem_notif.h>
+#include <net/cnss2.h>
+#include <linux/qcn_sdio_al.h>
+#include "main.h"
+#include "sdio.h"
+#include "debug.h"
+#include "bus.h"
+
+int cnss_sdio_call_driver_probe(struct cnss_sdio_data *sdio_priv)
+{
+ int ret = 0;
+ struct cnss_plat_data *plat_priv = sdio_priv->plat_priv;
+
+ if ((!sdio_priv->al_client_handle) ||
+ (!sdio_priv->al_client_handle->func)) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ if (!sdio_priv->ops) {
+ cnss_pr_err("driver_ops is NULL\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state) &&
+ test_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state)) {
+ ret = sdio_priv->ops->reinit(sdio_priv->al_client_handle->func,
+ sdio_priv->device_id);
+ if (ret) {
+ cnss_pr_err("Failed to reinit host driver, err = %d\n",
+ ret);
+ goto out;
+ }
+ clear_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state);
+ } else if (test_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state)) {
+ ret = sdio_priv->ops->probe(sdio_priv->al_client_handle->func,
+ sdio_priv->device_id);
+ if (ret) {
+ cnss_pr_err("Failed to probe host driver, err = %d\n",
+ ret);
+ goto out;
+ }
+ clear_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state);
+ clear_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state);
+ set_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state);
+ }
+
+ return 0;
+
+out:
+ return ret;
+}
+
+int cnss_sdio_call_driver_remove(struct cnss_sdio_data *sdio_priv)
+{
+ struct cnss_plat_data *plat_priv = sdio_priv->plat_priv;
+
+ if (test_bit(CNSS_COLD_BOOT_CAL, &plat_priv->driver_state) ||
+ test_bit(CNSS_FW_BOOT_RECOVERY, &plat_priv->driver_state) ||
+ test_bit(CNSS_DRIVER_DEBUG, &plat_priv->driver_state)) {
+ cnss_pr_dbg("Skip driver remove\n");
+ return 0;
+ }
+
+ if (!sdio_priv->ops) {
+ cnss_pr_err("driver_ops is NULL\n");
+ return -EINVAL;
+ }
+
+ if (test_bit(CNSS_DRIVER_RECOVERY, &plat_priv->driver_state) &&
+ test_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state)) {
+ cnss_pr_dbg("Recovery set after driver probed.Call shutdown\n");
+ sdio_priv->ops->shutdown(sdio_priv->al_client_handle->func);
+ } else if (test_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state)) {
+ cnss_pr_dbg("driver_ops->remove\n");
+ sdio_priv->ops->remove(sdio_priv->al_client_handle->func);
+ clear_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state);
+ }
+ return 0;
+}
+
+/**
+ * cnss_sdio_wlan_register_driver() - cnss wlan register API
+ * @driver: sdio wlan driver interface from wlan driver.
+ *
+ * wlan sdio function driver uses this API to register callback
+ * functions to cnss_sido platform driver. The callback will
+ * be invoked by corresponding wrapper function of this cnss
+ * platform driver.
+ */
+int cnss_sdio_wlan_register_driver(struct cnss_sdio_wlan_driver *driver_ops)
+{
+ struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(NULL);
+ struct cnss_sdio_data *cnss_info;
+ int ret = 0;
+
+ if (!plat_priv) {
+ cnss_pr_err("plat_priv is NULL\n");
+ return -ENODEV;
+ }
+
+ cnss_info = plat_priv->bus_priv;
+ if ((!cnss_info) ||
+ (!cnss_info->al_client_handle) ||
+ (!cnss_info->al_client_handle->func)) {
+ cnss_pr_err("cnss_info is NULL\n");
+ return -ENODEV;
+ }
+
+ if (cnss_info->ops) {
+ cnss_pr_err("Driver has already registered\n");
+ return -EEXIST;
+ }
+ cnss_info->ops = driver_ops;
+
+ ret = cnss_driver_event_post(plat_priv,
+ CNSS_DRIVER_EVENT_REGISTER_DRIVER,
+ CNSS_EVENT_SYNC_UNINTERRUPTIBLE,
+ driver_ops);
+ return ret;
+}
+EXPORT_SYMBOL(cnss_sdio_wlan_register_driver);
+
+/**
+ * cnss_sdio_wlan_unregister_driver() - cnss wlan unregister API
+ * @driver: sdio wlan driver interface from wlan driver.
+ *
+ * wlan sdio function driver uses this API to detach it from cnss_sido
+ * platform driver.
+ */
+void cnss_sdio_wlan_unregister_driver(struct cnss_sdio_wlan_driver *driver_ops)
+{
+ struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(NULL);
+
+ if (!plat_priv) {
+ cnss_pr_err("plat_priv is NULL\n");
+ return;
+ }
+
+ cnss_driver_event_post(plat_priv, CNSS_DRIVER_EVENT_UNREGISTER_DRIVER,
+ CNSS_EVENT_SYNC_UNINTERRUPTIBLE, NULL);
+}
+EXPORT_SYMBOL(cnss_sdio_wlan_unregister_driver);
+
+struct sdio_al_client_handle *cnss_sdio_wlan_get_sdio_al_client_handle(
+ struct sdio_func *func)
+{
+ struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(NULL);
+ struct cnss_sdio_data *cnss_info = plat_priv->bus_priv;
+
+ return cnss_info->al_client_handle;
+}
+EXPORT_SYMBOL(cnss_sdio_wlan_get_sdio_al_client_handle);
+
+struct sdio_al_channel_handle *cnss_sdio_wlan_register_sdio_al_channel(
+ struct sdio_al_channel_data *channel_data)
+{
+ struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(NULL);
+ struct cnss_sdio_data *cnss_info = plat_priv->bus_priv;
+
+ return sdio_al_register_channel(cnss_info->al_client_handle,
+ channel_data);
+}
+EXPORT_SYMBOL(cnss_sdio_wlan_register_sdio_al_channel);
+
+void cnss_sdio_wlan_unregister_sdio_al_channel(
+ struct sdio_al_channel_handle *ch_handle)
+{
+ sdio_al_deregister_channel(ch_handle);
+}
+EXPORT_SYMBOL(cnss_sdio_wlan_unregister_sdio_al_channel);
+
+int cnss_sdio_register_driver_hdlr(struct cnss_sdio_data *cnss_info,
+ void *data)
+{
+ struct cnss_plat_data *plat_priv = cnss_info->plat_priv;
+ int ret = 0;
+ unsigned int timeout;
+
+ set_bit(CNSS_DRIVER_LOADING, &plat_priv->driver_state);
+ if (test_bit(CNSS_FW_READY, &plat_priv->driver_state)) {
+ cnss_pr_info("CNSS SDIO driver register in FW_Ready state");
+ cnss_sdio_call_driver_probe(cnss_info);
+ } else if ((*cnss_get_qmi_bypass()) &&
+ (cnss_info->al_client_handle->func)) {
+ cnss_pr_info("qmi bypass enabled");
+ cnss_sdio_call_driver_probe(cnss_info);
+ } else {
+ cnss_pr_info("Wait for FW_Ready");
+ ret = cnss_power_on_device(plat_priv);
+ if (ret) {
+ cnss_pr_err("Failed to power on device, err = %d\n",
+ ret);
+ return ret;
+ }
+
+ qcn_sdio_card_state(true);
+ timeout = cnss_get_qmi_timeout();
+ if (timeout) {
+ mod_timer(&plat_priv->fw_boot_timer,
+ jiffies + msecs_to_jiffies(timeout << 1));
+ }
+ }
+ return 0;
+}
+
+int cnss_sdio_unregister_driver_hdlr(struct cnss_sdio_data *cnss_info)
+{
+ struct cnss_plat_data *plat_priv = cnss_info->plat_priv;
+
+ set_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state);
+ cnss_sdio_call_driver_remove(cnss_info);
+ cnss_request_bus_bandwidth(&plat_priv->plat_dev->dev,
+ CNSS_BUS_WIDTH_NONE);
+ qcn_sdio_card_state(false);
+ cnss_power_off_device(plat_priv);
+ clear_bit(CNSS_FW_READY, &plat_priv->driver_state);
+ clear_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state);
+
+ cnss_info->ops = NULL;
+ return 0;
+}
+
+int cnss_sdio_dev_powerup(struct cnss_sdio_data *cnss_info)
+{
+ struct cnss_plat_data *plat_priv = cnss_info->plat_priv;
+ int ret = 0;
+ unsigned int timeout;
+
+ switch (plat_priv->device_id) {
+ case QCN7605_SDIO_DEVICE_ID:
+ ret = cnss_power_on_device(plat_priv);
+ if (ret) {
+ cnss_pr_err("Failed to power on device, err = %d\n",
+ ret);
+ goto out;
+ }
+
+ qcn_sdio_card_state(true);
+ timeout = cnss_get_qmi_timeout();
+ mod_timer(&plat_priv->fw_boot_timer,
+ jiffies + msecs_to_jiffies(timeout >> 1));
+ cnss_set_pin_connect_status(plat_priv);
+ break;
+ default:
+ cnss_pr_err("Unknown device_id found: 0x%lx\n",
+ plat_priv->device_id);
+ ret = -ENODEV;
+ }
+out:
+ return ret;
+}
+
+int cnss_sdio_dev_shutdown(struct cnss_sdio_data *cnss_info)
+{
+ struct cnss_plat_data *plat_priv = cnss_info->plat_priv;
+
+ cnss_sdio_call_driver_remove(cnss_info);
+ cnss_request_bus_bandwidth(&plat_priv->plat_dev->dev,
+ CNSS_BUS_WIDTH_NONE);
+ /*qcn_sdio_card_state(false);*/
+ cnss_power_off_device(plat_priv);
+ clear_bit(CNSS_FW_READY, &plat_priv->driver_state);
+ clear_bit(CNSS_DRIVER_UNLOADING, &plat_priv->driver_state);
+
+ return 0;
+}
+
+void cnss_sdio_fw_boot_timeout_hdlr(void *bus_priv)
+{
+ cnss_pr_err("Timeout waiting for FW ready indication\n");
+}
+
+static int cnss_sdio_probe(struct sdio_al_client_handle *pal_cli_handle)
+{
+ struct cnss_sdio_data *sdio_info = pal_cli_handle->client_priv;
+ struct cnss_plat_data *plat_priv = cnss_bus_dev_to_plat_priv(NULL);
+ struct sdio_device_id *device_id;
+
+ device_id = devm_kzalloc(&plat_priv->plat_dev->dev,
+ sizeof(struct sdio_device_id),
+ GFP_KERNEL);
+ device_id->class = pal_cli_handle->func->class;
+ device_id->vendor = pal_cli_handle->func->vendor;
+ device_id->device = pal_cli_handle->func->device;
+ sdio_info->device_id = device_id;
+
+ if (pal_cli_handle->func)
+ cnss_pr_info("CNSS SDIO AL Probe for device Id: 0x%x\n",
+ pal_cli_handle->func->device);
+ clear_bit(CNSS_DEV_REMOVED, &plat_priv->driver_state);
+ plat_priv->device_id = pal_cli_handle->func->device;
+ cnss_register_subsys(sdio_info->plat_priv);
+ return 0;
+}
+
+static int cnss_sdio_remove(struct sdio_al_client_handle *pal_cli_handle)
+{
+ struct cnss_sdio_data *sdio_info = pal_cli_handle->client_priv;
+ struct cnss_plat_data *plat_priv = sdio_info->plat_priv;
+
+ if (pal_cli_handle->func)
+ cnss_pr_err(
+ "SDIO AL remove for device Id: 0x%x in driver state %lu\n",
+ pal_cli_handle->func->device,
+ plat_priv->driver_state);
+
+ clear_bit(CNSS_FW_READY, &plat_priv->driver_state);
+ set_bit(CNSS_DEV_REMOVED, &plat_priv->driver_state);
+ if (sdio_info->ops &&
+ test_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state)) {
+ cnss_pr_err("Triggering driver_ops remove\n");
+ sdio_info->ops->update_status(
+ sdio_info->al_client_handle->func,
+ CNSS_FW_DOWN);
+ sdio_info->ops->remove(sdio_info->al_client_handle->func);
+ clear_bit(CNSS_DRIVER_PROBED, &plat_priv->driver_state);
+ }
+
+ cnss_unregister_subsys(plat_priv);
+ devm_kfree(&plat_priv->plat_dev->dev, (void *)sdio_info->device_id);
+
+ return 0;
+}
+
+static void cnss_sdio_pm(struct sdio_al_client_handle *pal_cli_handle,
+ enum sdio_al_lpm_event event)
+{
+ struct cnss_sdio_data *sdio_info = pal_cli_handle->client_priv;
+ struct sdio_func *func = sdio_info->al_client_handle->func;
+
+ if (!sdio_info->ops) {
+ cnss_pr_err("Ignore LPM event\n");
+ return;
+ }
+
+ if (event == LPM_ENTER) {
+ cnss_pr_info("Entering LPM\n");
+ sdio_info->ops->suspend(&func->dev);
+ } else {
+ cnss_pr_info("Exiting LPM\n");
+ sdio_info->ops->resume(&func->dev);
+ }
+}
+
+struct sdio_al_client_data al_cli_data = {
+ .name = "SDIO_AL_CLIENT_WLAN",
+ .probe = cnss_sdio_probe,
+ .remove = cnss_sdio_remove,
+ .lpm_notify_cb = cnss_sdio_pm,
+};
+
+int cnss_sdio_init(struct cnss_plat_data *plat_priv)
+{
+ struct cnss_sdio_data *sdio_info;
+ struct sdio_al_client_handle *al_client_handle;
+ int ret = 0;
+
+ if (sdio_al_is_ready()) {
+ cnss_pr_err("sdio_al not ready, defer probe\n");
+ ret = -EPROBE_DEFER;
+ goto out;
+ }
+
+ al_client_handle = sdio_al_register_client(&al_cli_data);
+ if (!al_client_handle) {
+ cnss_pr_err("sdio al registration failed!\n");
+ ret = -ENODEV;
+ goto out;
+ }
+ sdio_info = devm_kzalloc(&plat_priv->plat_dev->dev, sizeof(*sdio_info),
+ GFP_KERNEL);
+ if (!sdio_info) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ al_client_handle->client_priv = sdio_info;
+ sdio_info->al_client_handle = al_client_handle;
+ sdio_info->plat_priv = plat_priv;
+ plat_priv->bus_priv = sdio_info;
+
+out:
+ return ret;
+}
+
+int cnss_sdio_deinit(struct cnss_plat_data *plat_priv)
+{
+ struct cnss_sdio_data *sdio_info = plat_priv->bus_priv;
+
+ sdio_al_deregister_client(sdio_info->al_client_handle);
+ return 0;
+}