1. 程式人生 > >Linux 下wifi 驅動開發(三)—— SDIO介面WiFi驅動淺析

Linux 下wifi 驅動開發(三)—— SDIO介面WiFi驅動淺析

      SDIO-Wifi模組是基於SDIO介面的符合wifi無線網路標準的嵌入式模組,內建無線網路協議IEEE802.11協議棧以及TCP/IP協議棧,能夠實現使用者主平臺數據通過SDIO口到無線網路之間的轉換。SDIO具有傳輸資料快,相容SD、MMC介面等特點。

     對於SDIO介面的wifi,首先,它是一個sdio的卡的裝置,然後具備了wifi的功能,所以,註冊的時候還是先以sdio的卡的裝置去註冊的。然後檢測到卡之後就要驅動他的wifi功能了,顯然,他是用sdio的協議,通過發命令和資料來控制的。下面先簡單回顧一下SDIO的相關知識:

一、SDIO相關基礎知識解析

1、SDIO介面

       SDIO 故名思義,就是 SD 的 I/O 介面(interface)的意思,不過這樣解釋可能還有點抽像。更具體的說明,SD 本來是記憶卡的標準,但是現在也可以把 SD 拿來插上一些外圍介面使用,這樣的技術便是 SDIO。

       所以 SDIO 本身是一種相當單純的技術,透過 SD 的 I/O 接腳來連線外部外圍,並且透過 SD 上的 I/O 資料接位與這些外圍傳輸資料,而且 SD 協會會員也推出很完整的 SDIO stack 驅動程式,使得 SDIO 外圍(我們稱為SDIO 卡)的開發與應用變得相當熱門。

       現在已經有非常多的手機或是手持裝置都支援 SDIO 的功能(SD 標準原本就是針對 mobile device 而制定),而且許多 SDIO 外圍也都被開發出來,讓手機外接外圍更加容易,並且開發上更有彈性(不需要內建外圍)。目前常見的 SDIO 外圍(SDIO 卡)有:

· Wi-Fi card(無線網路卡) 

· CMOS sensor card(照相模組) 

· GPS card 

· GSM/GPRS modem card 

· Bluetooth card 

        SDIO 的應用將是未來嵌入式系統最重要的介面技術之一,並且也會取代目前 GPIO 式的 SPI 介面。

2、SDIO匯流排

      SDIO匯流排 和 USB匯流排 類似,SDIO也有兩端,其中一端是HOST端,另一端是device端。所有的通訊都是由HOST端 傳送 命令 開始的,Device端只要能解析命令,就可以相互通訊

CLK訊號:HOST給DEVICE的 時鐘訊號,每個時鐘週期傳輸一個命令。

CMD訊號:雙向 的訊號,用於傳送 命令 和 反應。

DAT0-DAT3 訊號:四條用於傳送的資料線。

VDD訊號:電源訊號。

VSS1,VSS2:電源地訊號。

3、SDIO熱插拔原理

方法:設定一個 定時器檢查插拔中斷檢測

硬體:假如GPG10(EINT18)用於SD卡檢測

GPG10 為高電平 即沒有插入SD卡

GPG10為低電平  即插入了SD卡

4、SDIO命令

      SDIO總線上都是HOST端發起請求,然後DEVICE端迴應請求。sdio命令由6個位元組組成。

a -- Command:用於開始傳輸的命令,是由HOST端發往DEVICE端的。其中命令是通過CMD訊號線傳送的。

b -- Response:迴應是DEVICE返回的HOST的命令,作為Command的迴應。也是通過CMD線傳送的。

c -- Data:資料是雙向的傳送的。可以設定為1線模式,也可以設定為4線模式。資料是通過DAT0-DAT3訊號線傳輸的。

      SDIO的每次操作都是由HOST在CMD線上發起一個CMD,對於有的CMD,DEVICE需要返回Response,有的則不需要。

     對於讀命令,首先HOST會向DEVICE傳送命令,緊接著DEVICE會返回一個握手訊號,此時,當HOST收到迴應的握手訊號後,會將資料放在4位的資料線上,在傳送資料的同時會跟隨著CRC校驗碼。當整個讀傳送完畢後,HOST會再次傳送一個命令,通知DEVICE操作完畢,DEVICE同時會返回一個響應。

    對於寫命令,首先HOST會向DEVICE傳送命令,緊接著DEVICE會返回一個握手訊號,此時,當HOST收到迴應的握手訊號後,會將資料放在4位的資料線上,在傳送資料的同時會跟隨著CRC校驗碼。當整個寫傳送完畢後,HOST會再次傳送一個命令,通知DEVICE操作完畢,DEVICE同時會返回一個響應。

二、SDIO介面驅動

        前面講到,SDIO介面的wifi,首先,它是一個sdio的卡的裝置,然後具備了wifi的功能,所以SDIO介面的WiFi驅動就是在wifi驅動外面套上了一個SDIO驅動的外殼,SDIO驅動仍然符合裝置驅動的分層與分離思想

     裝置驅動層(wifi 裝置)

                      |

核心層(向上向下提供介面)

                      |

主機驅動層 (實現SDIO驅動)

        下面先分析SDIO介面驅動的實現,看幾個重要的資料結構(用於核心層與主機驅動層 的資料交換處理)。

[ /include/linux/mmc/host.h ]

struct mmc_host     用來描述卡控制器

struct mmc_card     用來描述卡

struct mmc_driver  用來描述 mmc 卡驅動

struct sdio_func      用來描述 功能裝置

struct mmc_host_ops   用來描述卡控制器操作介面函式功能,用於從 主機控制器層向 core 層註冊操作函式,從而將core 層與具體的主機控制器隔離。也就是說 core 要操作主機控制器,就用這個 ops 當中給的函式指標操作,不能直接呼叫具體主控制器的函式。

1、編寫Host層驅動

     這裡參考的是S3C24XX的HOST驅動程式   /drivers/mmc/host/s3cmci.c 

static struct platform_driver s3cmci_driver = {
     .driver  = {
         .name    = "s3c-sdi",  //名稱和平臺裝置定義中的對應
         .owner   = THIS_MODULE,
         .pm  = s3cmci_pm_ops,
     },
     .id_table = s3cmci_driver_ids,
     .probe        = s3cmci_probe,  //平臺裝置探測介面函式
     .remove       = __devexit_p(s3cmci_remove),
     .shutdown = s3cmci_shutdown,
};

s3cmci_probe(struct platform_device *pdev)
{
	//....
	struct mmc_host *mmc;
	mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);  //分配mmc_host結構體

	//.....
}

/*註冊中斷處理函式s3cmci_irq,來處理資料收發過程引起的各種中斷*/
request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host) //註冊中斷處理函式s3cmci_irq

/*註冊中斷處理s3cmci_irq_cd函式,來處理熱撥插引起的中斷,中斷觸發的形式為上升沿、下降沿觸發*/
request_irq(host->irq_cd, s3cmci_irq_cd,IRQF_TRIGGER_RISING |IRQF_TRIGGER_FALLING, DRIVER_NAME, host)

mmc_add_host(mmc);  //initialise host hardware //向MMC core註冊host驅動
----> device_add(&host->class_dev); //新增裝置到mmc_bus_type總線上的裝置連結串列中
----> mmc_start_host(host); //啟動mmc host

/*MMC drivers should call this when they detect a card has been inserted or removed.檢測sd卡是否插上或移除*/
 ---->mmc_detect_change(host, 0);

/*Schedule delayed work in the MMC work queue.排程延時工作佇列*/
 mmc_schedule_delayed_work(&host->detect, delay);

搜尋host->detected得到以下資訊:

[/drivers/mmc/core/host.c]

NIT_DELAYED_WORK(&host->detect, mmc_rescan);

mmc_rescan(struct work_struct *work)
---->mmc_bus_put(host);//card 從bus上移除時,釋放它佔有的匯流排空間

/*判斷當前mmc host控制器是否被佔用,當前mmc控制器如果被佔用,那麼  host->claimed = 1;否則為0
 *如果為1,那麼會在while(1)迴圈中呼叫schedule切換出自己,當佔用mmc控制器的操作完成之後,執行 *mmc_release_host()的時候,會啟用登記到等待佇列&host->wq中的其他 程式獲得mmc主控制器的使用權
 */
mmc_claim_host(host);
     mmc_rescan_try_freq(host, max(freqs[i], host->f_min);

static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
     …
     /* Order's important: probe SDIO, then SD, then MMC */
     if (!mmc_attach_sdio(host))
          return 0;
     if (!mmc_attach_sd(host))
         return 0;
     if (!mmc_attach_mmc(host))
         return 0;
        ….
}

mmc_attach_sdio(struct mmc_host *host)  //匹配sdio介面卡
     --->mmc_attach_bus(host, &mmc_sdio_ops);

/*當card與總線上的驅動匹配,就初始化card*/
mmc_sdio_init_card(host, host->ocr, NULL, 0); 
    --->card = mmc_alloc_card(host, NULL);//分配一個card結構體
          mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); //設定mmc_bus的工作模式

struct sdio_func *sdio_func[SDIO_MAX_FUNCS]; //SDIO functions (devices)

sdio_init_func(host->card, i + 1);
    --->func = sdio_alloc_func(card); //分配struct sdio_fun(sdio功能裝置)結構體
          mmc_io_rw_direct();
          card->sdio_func[fn - 1] = func;

mmc_add_card(host->card);  //將具體的sdio裝置掛載到mmc_bus_types 匯流排
sdio_add_func(host->card->sdio_func[i]); //將sdio功能裝置掛載到sdio_bus_types匯流排

這裡一系列函式呼叫在前面的SD驅動蚊帳中已經闡述過了,不再詳細闡述

2、SDIO裝置的熱插拔

      當插拔SDIO裝置,會觸發中斷通知到CPU,然後執行卡檢測中斷處理函式在這個中斷服務函式中,mmc_detect_change->mmc_schedule_delayed_work(&host->detect,delay), INIT_DELAYED_WORK(&host->detect, mmc_rescan)會排程mmc_rescan函式延時排程工作佇列,這樣也會觸發SDIO裝置的初始化流程,檢測到有效的SDIO裝置後,會將它註冊到系統中去。

static irqreturn_t s3cmci_irq_cd(int irq, void *dev_id)
{
     struct s3cmci_host *host = (struct s3cmci_host *)dev_id;
     ........
     mmc_detect_change(host->mmc, msecs_to_jiffies(500));

     return IRQ_HANDLED;
}

三、wifi 驅動部分解析

wifi驅動的通用的軟體架構

1. 分為兩部分,上面為主機端驅動,下面是我們之前所說的firmware

2. 其中韌體部分的主要工作是:因為天線接受和傳送回來的都是802.11幀的幀,而主機接受和傳送出來的資料都必須是802.3的幀,所以必須由firmware來負責802.3的幀和802.11幀之間的轉換

3. 當天線收到資料,並被firmware處理好後會放在一個buffer裡,併產生一箇中斷,主機在收到中斷後就去讀這個buffer。

     SDIO裝置的驅動由sdio_driver結構體定義,sdio_driver其實是driver的封裝。通過sdio_register_driver函式將SDIO裝置驅動載入進核心,其實就是掛載到sdio_bus_type總線上去。

1、裝置驅動的註冊與匹配

[Drivers/net/wireless/libertas/if_sdio.c]

/* SDIO function device driver*/

struct sdio_driver {
     char *name;  //裝置名
     const struct sdio_device_id *id_table; //裝置驅動ID
     int (*probe)(struct sdio_func *, const struct sdio_device_id *);//匹配函式
     void (*remove)(struct sdio_func *);
     struct device_driver drv;
};

下面是具體函式的填充:

/*if_sdio.c*/

static struct sdio_driver if_sdio_driver = {
     .name         = "libertas_sdio",
     .id_table = if_sdio_ids,  //用於裝置與驅動的匹配
     .probe        = if_sdio_probe,
     .remove       = if_sdio_remove,
     .drv = {
         .pm = &if_sdio_pm_ops,
         }
};

設備註冊函式

/**
 *   sdio_register_driver - register a function driver
 *   @drv: SDIO function driver
 */

int sdio_register_driver(struct sdio_driver *drv)
{
     drv->drv.name = drv->name;
     drv->drv.bus = &sdio_bus_type;  //設定driver的bus為sdio_bus_type
     return driver_register(&drv->drv);
}

匯流排函式

static struct bus_type sdio_bus_type = {
     .name         = "sdio",
     .dev_attrs    = sdio_dev_attrs,
     .match        = sdio_bus_match,
     .uevent       = sdio_bus_uevent,
     .probe        = sdio_bus_probe,
     .remove       = sdio_bus_remove,
     .pm      = SDIO_PM_OPS_PTR,
};

注意:裝置或者驅動註冊到系統中的過程中,都會呼叫相應bus上的匹配函式來進行匹配合適的驅動或者裝置,對於sdio裝置的匹配是由sdio_bus_matchsdio_bus_probe函式來完成。

static int sdio_bus_match(struct device *dev, struct device_driver *drv)
{
     struct sdio_func *func = dev_to_sdio_func(dev);
     struct sdio_driver *sdrv = to_sdio_driver(drv); 
     if (sdio_match_device(func, sdrv))
         return 1; 

     return 0;
}

static const struct sdio_device_id *sdio_match_device(struct sdio_func *func,
     struct sdio_driver *sdrv)
{
     const struct sdio_device_id *ids;
     ids = sdrv->id_table;           

    if (sdio_match_one(func, ids))
                   return ids;
}

由以上匹配過程來看,通過匹配id_table 和 sdio_driver裝置驅動中id,來匹配合適的驅動或裝置。最終會呼叫.probe函式,來完成相關操作。

2、If_sdio_probe函式

    當檢測到sdio卡插入了之後就會呼叫If_sdio_probe,而當卡被移除後就會呼叫If_sdio_remove


下面先看下If_sdio_probet函式,if_sdio_prob 函式 主要做了兩件事  

static struct sdio_driver if_sdio_driver = {
 .name  = "libertas_sdio",
 .id_table = if_sdio_ids,   //用於裝置和驅動的匹配
 .probe  = if_sdio_probe,
 .remove  = if_sdio_remove,
 .drv = {
  .pm = &if_sdio_pm_ops,
 },
};
 

1 //定義一個 if_sdio  card的結構體
 struct if_sdio_card *card;
 struct if_sdio_packet *packet;  //sdio 包的結構體 
 struct mmc_host *host = func->card->host;

 // 查詢是否有指定的功能暫存器在mmc
   //_sdio_card中
 for (i = 0;i < func->card->num_info;i++) {
  if (sscanf(func->card->info[i],
    "802.11 SDIO ID: %x", &model) == 1)
 
//在這裡進行片選  選擇到我們使用的marvell 8686 的裝置
case MODEL_8686:
  card->scratch_reg = IF_SDIO_SCRATCH;
 
 
//建立sdio 的工作佇列 
card->workqueue = create_workqueue("libertas_sdio");
//呼叫下面的函式
INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker);


//主機到卡的工作佇列
static void if_sdio_host_to_card_worker(struct work_struct *work)

 /* Check if we support this card  選擇我們所支援的卡的型別*/
  //賦值為sd8686_helper.bin   sd8686.bin
/*fw_table 中的  MODEL_8686, "sd8686_helper.bin", "sd8686.bin" },?/
 for (i = 0; i < ARRAY_SIZE(fw_table); i++) {
      if (card->model == fw_table[i].model)
           break;
 }
 { MODEL_8688, "libertas/sd8688_helper.bin", "libertas/sd8688.bin" },
 

//申請一個host
sdio_claim_host(func);
//使能sdio 的功能 暫存器
ret = sdio_enable_func(func);
if (ret)
  goto release;

2//申請 sdio 的中斷  當有資料  ,命令 或者是事件 的時間執行中斷
ret = sdio_claim_irq(func, if_sdio_interrupt);
ret = if_sdio_card_to_host(card);  //從無線網絡卡接收到資料 或者說是上報資料
ret = if_sdio_handle_data(card, card->buffer + 4, chunk - 4);   //接收資料的處理 
ret = if_sdio_handle_cmd(card, card->buffer + 4, chunk - 4);   //處理申請的命令中斷
ret = if_sdio_handle_event(card, card->buffer + 4, chunk - 4);//處理申請的事件中斷


//新增網路結構體  分配裝置並註冊
priv = lbs_add_card(card, &func->dev);

//分配Ethernet裝置並註冊 
 wdev = lbs_cfg_alloc(dmdev);
//802無線網的具體的操作函式 
wdev->wiphy = wiphy_new(&lbs_cfg80211_ops, sizeof(struct lbs_private));

 
//分配網路裝置是整個網路部分操作的
//的核心結構體
dev = alloc_netdev(0, "wlan%d", ether_setup);  //例項化wlan0的屬性
dev->ieee80211_ptr = wdev;
 dev->ml_priv = priv;
 //設定裝置的實體地址 
 SET_NETDEV_DEV(dev, dmdev);
 wdev->netdev = dev;
 priv->dev = dev;
   //初始化網路裝置 ops.  看門狗  
  dev->netdev_ops = &lbs_netdev_ops;    //網路裝置的具體的操作函式 
 dev->watchdog_timeo = 5 * HZ;
 dev->ethtool_ops = &lbs_ethtool_ops;   
 dev->flags |= IFF_BROADCAST | IFF_MULTICAST;  //廣播或者多播
 
 
 
 //啟動一個核心執行緒來管理這個網路裝置的資料傳送,事件的處理(卡的拔出)和一些命令的處理 
 priv->main_thread = kthread_run(lbs_thread, dev, "lbs_main");
//初始化相關的工作佇列
 priv->work_thread = create_singlethread_workqueue("lbs_worker");
 INIT_WORK(&priv->mcast_work, lbs_set_mcast_worker);
 priv->wol_criteria = EHS_REMOVE_WAKEUP;
 priv->wol_gpio = 0xff;
 priv->wol_gap = 20;
 priv->ehs_remove_supported = true;
 
 
 //設定私有變數 
//設定主機發送資料到卡
 priv->hw_host_to_card = if_sdio_host_to_card;
 priv->enter_deep_sleep = if_sdio_enter_deep_sleep;
 priv->exit_deep_sleep = if_sdio_exit_deep_sleep;
 priv->reset_deep_sleep_wakeup = if_sdio_reset_deep_sleep_wakeup;
 sdio_claim_host(func);  

  //啟動卡裝置 
 ret = lbs_start_card(priv);
 if (lbs_cfg_register(priv)) 

 ret = register_netdev(priv->dev);
 err = register_netdevice(dev);

 
//具體的wifi裝置驅動功能 
//網路裝置操作的具體函式 
static const struct net_device_ops lbs_netdev_ops = {
 .ndo_open   = lbs_dev_open,   //開啟
 .ndo_stop  = lbs_eth_stop,  //停止
 .ndo_start_xmit  = lbs_hard_start_xmit,   //開始傳送資料
 .ndo_set_mac_address = lbs_set_mac_address,   //設定mac地址 
 .ndo_tx_timeout  = lbs_tx_timeout,    //傳送超時
 .ndo_set_multicast_list = lbs_set_multicast_list,   //多播地址
 .ndo_change_mtu  = eth_change_mtu,  //最大傳輸單元
 .ndo_validate_addr = eth_validate_addr,  //判斷地址的有效性 

3、資料的接收,通過中斷的方式來解決

   網路裝置接收資料的主要方法是由中斷引發裝置的中斷處理函式,中斷處理函式判斷中斷的型別,如果為接收中斷,則讀取接收到的資料,分配sk_buff資料結構和資料緩衝區,並將接收的資料複製到資料快取區,並呼叫netif_rx()函式將sk_buff傳遞給上層協議。

    搜尋if_sdio_interrupt,可知道它是在if_sdio.c檔案中if_sdio_probe()函式中sdio_claim_irq(func, if_sdio_interrupt) ,func->irq_handler = if_sdio_interrupt。當s3cmci_irq中斷處理函式的S3C2410_SDIIMSK_SDIOIRQ 中斷被觸發時將呼叫if_sdio_interrupt()函式,進行接收資料。

static void if_sdio_interrupt(struct sdio_func *func)

ret = if_sdio_card_to_host(card);  //從無線網絡卡接收到資料 或者說是上報資料
//讀取埠上的資料 ,放到card的buffer中 
 ret = sdio_readsb(card->func, card->buffer, card->ioport, chunk);
1.在這裡一方面處理中斷  還有2 
 switch (type) {   //處理cmd   data  event的請求 
 case MVMS_CMD:
  ret = if_sdio_handle_cmd(card, card->buffer + 4, chunk - 4);   //處理申請的命令中斷
  if (ret)
   goto out;
  break;
 case MVMS_DAT:
  ret = if_sdio_handle_data(card, card->buffer + 4, chunk - 4);//處理申請的資料中斷 
  if (ret)
   goto out;
  break;
 case MVMS_EVENT:
  ret = if_sdio_handle_event(card, card->buffer + 4, chunk - 4);//處理申請的事件中斷
 
//讀取包的過程 
 lbs_process_rxed_packet(card->priv, skb);
 
 //如果是中斷 ,就把skb這個包提交給協議層,這個函式是
 //協議層提供的  netif_rx(skb)
 if (in_interrupt())
  netif_rx(skb);    //提交給協議層 
 
 
2//讀取埠上的資料 ,放到card的buffer中 
 ret = sdio_readsb(card->func, card->buffer, card->ioport, chunk);
//讀取地址,目的地址,數量 等
int sdio_readsb(struct sdio_func *func, void *dst, unsigned int addr, int count)

         return sdio_io_rw_ext_helper(func, 0, addr, 0, dst, count);

                ret = mmc_io_rw_extended(func->card, write,func->num, addr, incr_addr, buf,blocks, func->cur_blksize);
                         cmd.arg = write ? 0x80000000 : 0x00000000;
                                
                    //wait for  request  
                     mmc_wait_for_req(card->host, &mrq);
                        開始應答 
                         mmc_start_request(host, mrq);
                         wait_for_completion(&complete);
                                    
                             host->ops->request(host, mrq);

4、資料傳送
//IP層通過dev_queue_xmit()將資料交給網路裝置協議介面層,網路介面層通過netdevice中的註冊函式的資料傳送函式
int dev_queue_xmit(struct sk_buff *skb)

    if (!netif_tx_queue_stopped(txq)) {
    __this_cpu_inc(xmit_recursion);
   //裝置硬體開始傳送  
    rc = dev_hard_start_xmit(skb, dev, txq);
  //呼叫wifi網路中的ops 

  rc = ops->ndo_start_xmit(skb, dev);

  dev->netdev_ops = &lbs_netdev_ops;    //裝置的操作函式 

 //處理sdio firware資料和核心的資料main_thread 主執行緒  
 priv->main_thread = kthread_run(lbs_thread, dev, "lbs_main");

   //呼叫host_to_card   即if_sdio_card_to_host函式。 
   int ret = priv->hw_host_to_card(priv, MVMS_DAT,priv->tx_pending_buf,priv->tx_pending_len);
為什麼是if_sdio_to_host呢 ?因為在prob函式中定義了這一個
//設定主機發送資料到卡
 priv->hw_host_to_card = if_sdio_host_to_card;
   
static int if_sdio_host_to_card(struct lbs_private *priv,u8 type, u8 *buf, u16 nb)
      //把buf中的資料 copy到sdio 包中,在對sdio 的包進行處理
         memcpy(packet->buffer + 4, buf, nb);
//建立工作佇列  
         queue_work(card->workqueue, &card->packet_worker);
 //初始化佇列  
 INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker);

 //sdio的寫資料   
   ret = sdio_writesb(card->func, card->ioport, packet->buffer, packet->nb);
         //mmc寫擴充套件口 
               ret = mmc_io_rw_extended(func->card, write,func->num, addr, incr_addr, buf,blocks, func->cur_blksize);

                    //wait for  request  
                                 mmc_wait_for_req(card->host, &mrq);
                                  
                             mrq->done_data = &complete;
                             mrq->done = mmc_wait_done;
                             mmc_start_request(host, mrq);
                                //完成等待 寫資料結束 
                             wait_for_completion(&complete);
 
 
                             host->ops->request(host, mrq);
   //到底結束  傳送資料  

5、移除函式

       當sdio卡拔除時,驅動會呼叫該函式,完成相應操作。如釋放佔有的資源,禁止func功能函式,釋放host。

if_sdio_remove(struct sdio_func *func)
---->lbs_stop_card(card->priv);
     lbs_remove_card(card->priv);
     ---->kthread_stop(priv->main_thread);  //終止核心執行緒

         lbs_free_adapter(priv);
         lbs_cfg_free(priv);
          free_netdev(dev);

     flush_workqueue(card->workqueue);  //重新整理工作佇列
     destroy_workqueue(card->workqueue);
     sdio_claim_host(func);
     sdio_release_irq(func);
     sdio_disable_func(func);
      sdio_release_host(func);