/* * This is part of the Sequans SQN1130 driver. * Copyright 2008 SEQUANS Communications * Written by Dmitriy Chumak , * Andy Shevchenko * * Inspired by if_sdio.c, Copyright 2007 Pierre Ossman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // GPIO_WAKEUP #include #include #include #include "version.h" #include "msg.h" #include "sdio-netdev.h" #include "sdio-sqn.h" #include "sdio.h" #include "thp.h" #include "sdio-driver.h" #include "sdio-fw.h" #include "sdio-pm.h" #define SKB_DEBUG 0 #define SDIO_CLK_DEBUG 0 #define DUMP_NET_PKT 0 static const struct sdio_device_id sqn_sdio_ids[] = { { SDIO_DEVICE(SDIO_VENDOR_ID_SEQUANS, SDIO_DEVICE_ID_SEQUANS_SQN1130) }, { SDIO_DEVICE(SDIO_VENDOR_ID_SEQUANS, SDIO_DEVICE_ID_SEQUANS_SQN1210) }, /* { SDIO_DEVICE(SDIO_ANY_ID, SDIO_ANY_ID) }, */ { 0 }, }; MODULE_DEVICE_TABLE(sdio, sqn_sdio_ids); //HTC:WiMax power ON_OFF function and Card detect function extern int mmc_wimax_power(int on); extern void mmc_wimax_set_carddetect(int val); extern int mmc_wimax_uart_switch(int uart); extern int mmc_wimax_set_status(int on); /*******************************************************************/ /* TX handlers */ /*******************************************************************/ static void sqn_sdio_add_skb_to_tx_queue(struct sqn_private *priv , struct sk_buff *skb, u8 tail) { struct sqn_sdio_card *card = priv->card; sqn_pr_enter(); if (tail) skb_queue_tail(&card->tx_queue, skb); else skb_queue_head(&card->tx_queue, skb); if (skb_queue_len(&card->tx_queue) > TX_QUEUE_MAX_LEN && !netif_queue_stopped(priv->dev)) { sqn_pr_info("tx_queue len %d, disabling netif_queue\n" , skb_queue_len(&card->tx_queue)); netif_stop_queue(priv->dev); } if (!card->waiting_pm_notification && !wake_lock_active(&card->wakelock)) { sqn_pr_dbg("lock wake_lock\n"); wake_lock(&card->wakelock); } sqn_pr_leave(); } static int sqn_sdio_is_tx_queue_empty(struct sqn_private *priv) { int rv = 0; struct sqn_sdio_card *card = priv->card; sqn_pr_enter(); rv = skb_queue_empty(&card->tx_queue); sqn_pr_leave(); return rv; } static int sqn_sdio_get_rstn_wr_fifo_flag(struct sqn_private *priv) { struct sqn_sdio_card *card = priv->card; sqn_pr_enter(); if (0 == card->rstn_wr_fifo_flag) { int rv = 0; sdio_claim_host(card->func); // by daniel card->rstn_wr_fifo_flag = sdio_readb(card->func, SQN_SDIO_RSTN_WR_FIFO(2), &rv); sdio_release_host(card->func); // by daniel sqn_pr_dbg("RSTN_WR_FIFO2 = %d\n", card->rstn_wr_fifo_flag); if (rv) { sqn_pr_err("sdio_readb(RSTN_WR_FIFO2) - return error\n"); card->rstn_wr_fifo_flag = 0; goto out; } } sqn_pr_leave(); out: return card->rstn_wr_fifo_flag; } static int sqn_sdio_recover_after_cmd53_timeout(struct sqn_sdio_card *card) { int rv = 0; sqn_pr_enter(); sqn_pr_info("Try to recovery after SDIO timeout error\n"); sdio_claim_host(card->func); sdio_writeb(card->func, 1 << card->func->num, SDIO_CCCR_IO_ABORT, &rv); sdio_release_host(card->func); if (rv) { sqn_pr_err("sdio_writeb(SDIO_CCCR_IO_ABORT) - return error %d\n" , rv); } sqn_pr_leave(); return rv; } /** * sqn_sdio_cmd52_read_buf - read @size bytes into @buf buffer from * address @addr using CMD52 * @card: sqn sdio card structure * @buf: buffer to return value, should be and address of u16, u32 variable * @size: size of the @buf / count of bytes to read from @addr * * @return error status - 0 if success, !0 otherwise */ static int sqn_sdio_cmd52_read_buf(struct sqn_sdio_card *card, void* buf, int size, int addr) { u8 tmpbuf[4] = { 0xa7, 0xa7, 0xa7, 0xa7 }; int i = 0; int rv = 0; sqn_pr_enter(); sqn_pr_info("Trying to read %d bytes from 0x%x address using CMD52\n", size, addr); sdio_claim_host(card->func); for (i = 0; i < size; i++) { tmpbuf[i] = sdio_readb(card->func, addr + i, &rv); if (rv) { sqn_pr_err("sdio_readb(%x) - return error %d\n", addr + i, rv); break; } } sdio_release_host(card->func); switch (size) { case sizeof(u16): *((u16*)buf) = le16_to_cpup((__le16 *)tmpbuf); break; case sizeof(u32): *((u32*)buf) = le32_to_cpup((__le32 *)tmpbuf); break; default: sqn_pr_err("unsupported buffer size: %d\n", size); } sqn_pr_leave(); return rv; } static int sqn_sdio_dump_registers(struct sqn_sdio_card *card) { u8 b8 = 0; u16 b16 = 0; int rv = 0; sqn_pr_enter(); sqn_pr_info("------------------ REG DUMP BEGIN ------------------\n"); sdio_claim_host(card->func); b8 = sdio_readb(card->func, SQN_SDIO_IT_STATUS_LSBS, &rv); if (rv) sqn_pr_err("can't read SDIO_IT_STATUS_LSBS: %d\n", rv); else sqn_pr_info("SDIO_IT_STATUS_LSBS: 0x%x\n", b8); b8 = sdio_readb(card->func, SQN_SDIO_IT_STATUS_MSBS, &rv); if (rv) sqn_pr_err("can't read SDIO_IT_STATUS_MSBS: %d\n", rv); else sqn_pr_info("SDIO_IT_STATUS_MSBS: 0x%x\n", b8); b8 = sdio_readb(card->func, SQN_SDIO_RSTN_WR_FIFO(2), &rv); if (rv) sqn_pr_err("can't read SQN_SDIO_RSTN_WR_FIFO2: %d\n", rv); else sqn_pr_info("SQN_SDIO_RSTN_WR_FIFO: 0x%x\n", b8); b8 = sdio_readb(card->func, SQN_SOC_SIGS_LSBS, &rv); if (rv) sqn_pr_err("can't read SQN_SOC_SIGS_LSBS: %d\n", rv); else sqn_pr_info("SQN_SOC_SIGS_LSBS: 0x%x\n", b8); b8 = sdio_readb(card->func, SQN_HTS_SIGS, &rv); if (rv) sqn_pr_err("can't read SQN_HTS_SIGS: %d\n", rv); else sqn_pr_info("SQN_HTS_SIGS: 0x%x\n", b8); sdio_release_host(card->func); rv = sqn_sdio_cmd52_read_buf(card, &b16, sizeof(b16), SQN_SDIO_WR_FIFO_BYTESLEFT(2)); if (rv) sqn_pr_err("can't read SDIO_WR_FIFO_BYTESLEFT2: %d\n", rv); else sqn_pr_info("SDIO_WR_FIFO_BYTESLEFT2: 0x%x\n", b16); rv = sqn_sdio_cmd52_read_buf(card, &b16, sizeof(b16), SQN_SDIO_WR_FIFO_LEVEL(2)); if (rv) sqn_pr_err("can't read SQN_SDIO_WR_FIFO_LEVEL2: %d\n", rv); else sqn_pr_info("SQN_SDIO_WR_FIFO_LEVEL2: 0x%x\n", b16); rv = sqn_sdio_cmd52_read_buf(card, &b16, sizeof(b16), SQN_SDIO_RD_FIFO_LEVEL(2)); if (rv) sqn_pr_err("can't read SQN_SDIO_RD_FIFO_LEVEL2: %d\n", rv); else sqn_pr_info("SQN_SDIO_RD_FIFO_LEVEL2: 0x%x\n", b16); rv = sqn_sdio_cmd52_read_buf(card, &b16, sizeof(b16), SDIO_CMN_CISTPLMID_MANF); if (rv) sqn_pr_err("can't read SDIO_CMN_CISTPLMID_MANF: %d\n", rv); else sqn_pr_info("SDIO_CMN_CISTPLMID_MANF: 0x%x\n", b16); rv = sqn_sdio_cmd52_read_buf(card, &b16, sizeof(b16), SDIO_CMN_CISTPLMID_CARD); if (rv) sqn_pr_err("can't read SDIO_CMN_CISTPLMID_CARD: %d\n", rv); else sqn_pr_info("SDIO_CMN_CISTPLMID_CARD: 0x%x\n", b16); sqn_pr_info("------------------ REG DUMP END ------------------\n"); sqn_pr_leave(); return rv; } static int sqn_sdio_get_wr_fifo_level(struct sqn_private *priv) { int level = 0; int rv = 0; struct sqn_sdio_card *card = priv->card; sqn_pr_enter(); sdio_claim_host(card->func); // by daniel /* level = sdio_readw(card->func, SQN_SDIO_WR_FIFO_LEVEL(2), &rv); */ level = sdio_readl(card->func, 0x2050, &rv); level = (u32)level >> sizeof(u16); sdio_release_host(card->func); // by daniel sqn_pr_dbg("SQN_SDIO_WR_FIFO_LEVEL2 = %d\n", level); if (rv) { sqn_pr_err("sdio_readw(WR_FIFO_LEVEL2) error %d\r", rv); level = -1; if (-ETIMEDOUT == rv) sqn_pr_info("SDIO CMD53 timeout error\n"); /* sqn_sdio_recover_after_cmd53_timeout(card); */ /* sqn_sdio_dump_registers(card); */ goto out; } sqn_pr_leave(); out: return level; } #if DUMP_NET_PKT uint8_t is_thp_packet(uint8_t *dest_addr); int is_lsp_packet(const struct sk_buff *skb); #endif struct sk_buff* sqn_sdio_prepare_skb_for_tx(struct sk_buff *skb) { #define PDU_LEN_SIZE 2 #define CRC_SIZE 4 #define PAD_TO_VALUE 512 #if DUMP_NET_PKT struct ethhdr *eth = (struct ethhdr *)skb->data; #endif /* * Calculate padding, to workaround some SDIO controllers we need to pad * each TX buffer so it size will be a multiple of PAD_TO_VALUE */ u32 padding = (skb->len + PDU_LEN_SIZE + CRC_SIZE) % PAD_TO_VALUE ? PAD_TO_VALUE - (skb->len + PDU_LEN_SIZE + CRC_SIZE) % PAD_TO_VALUE : 0; sqn_pr_enter(); sqn_pr_dbg("length %d, padding %d\n", skb->len, padding); if (skb->len > (SQN_MAX_PDU_LEN - (PDU_LEN_SIZE + CRC_SIZE + padding))) return 0; #if DUMP_NET_PKT if (!is_thp_packet(eth->h_source) && !is_lsp_packet(skb)) { sqn_pr_info("----------------------------------------------------------------------\n"); sqn_pr_info("TX PDU length %d\n", skb->len); sqn_pr_info_dump("TX PDU", skb->data, skb->len); } #endif /* * Real size of the PDU is data_len + 2 bytes at begining of PDU * for pdu_size + 4 bytes at the end of PDU for CRC of data */ if (skb_headroom(skb) < PDU_LEN_SIZE || skb_tailroom(skb) < CRC_SIZE + padding) { struct sk_buff *origin_skb = skb; gfp_t gfp_mask = GFP_DMA; if (in_interrupt() || irqs_disabled()) gfp_mask |= GFP_ATOMIC; else gfp_mask |= GFP_KERNEL; sqn_pr_dbg("relocating TX skb, GFP mask %x\n", gfp_mask); #if SKB_DEBUG sqn_pr_info("%s: [0x%p] old before reloc, users %d\n", __func__, origin_skb, atomic_read(&origin_skb->users)); #endif skb = skb_copy_expand(skb, PDU_LEN_SIZE, CRC_SIZE + padding , gfp_mask); #if SKB_DEBUG sqn_pr_info("%s: [0x%p] old after reloc, users %d\n", __func__, origin_skb, atomic_read(&origin_skb->users)); #endif dev_kfree_skb_any(origin_skb); if (0 == skb) { /* An error occured, likely there is no memory to * expand skb, so we drop it. */ return 0; } #if SKB_DEBUG sqn_pr_info("%s: [0x%p] new relocated, users %d\n", __func__, skb, atomic_read(&skb->users)); #endif } else { sqn_pr_dbg("TX skb: headroom = %d tailroom = %d\n" , skb_headroom(skb), skb_tailroom(skb)); } /* * Add size of PDU before ethernet frame * It should be in little endian byte order */ *((u8*)skb->data -2) = (skb->len + CRC_SIZE) & 0xff; *((u8*)skb->data -1) = ((skb->len + CRC_SIZE) >> 8) & 0xff; skb_push(skb, PDU_LEN_SIZE); /* * Add CRC to the end of ethernet frame * Now it simply set to 0 */ memset(skb->tail, 0, CRC_SIZE); skb_put(skb, CRC_SIZE + padding); sqn_pr_leave(); return skb; } int sqn_sdio_tx_skb(struct sqn_sdio_card *card, struct sk_buff *skb , u8 claim_host) { int rv = 0; sqn_pr_enter(); if (claim_host) sdio_claim_host(card->func); rv = sdio_writesb(card->func, SQN_SDIO_RDWR_FIFO(2), skb->data, skb->len); if (rv) { sqn_pr_err("call to sdio_writesb(RDWR_FIFO2) - return error %d\n", rv); if (-ETIMEDOUT == rv) { if (claim_host) { sdio_release_host(card->func); claim_host = 0; } sqn_pr_info("SDIO CMD53 timeout error: TX PDU length %d, PDU[0] 0x%x, PDU[1] 0x%x\n" , skb->len, *((u8*)skb->data), *((u8*)skb->data + 1)); /* sqn_sdio_dump_registers(card); */ /* sqn_sdio_recover_after_cmd53_timeout(card); */ } goto release; } release: if (claim_host) sdio_release_host(card->func); #if SKB_DEBUG sqn_pr_info("%s: free skb [0x%p] after tx, users %d\n", __func__, skb, atomic_read(&skb->users)); #endif dev_kfree_skb_any(skb); sqn_pr_leave(); return rv; } static void sqn_sdio_wake_lock_release_timer_fn(unsigned long data) { struct sqn_sdio_card *card = (struct sqn_sdio_card*) data; sqn_pr_enter(); /* if TX and RX queues are empty, we can releas a wake_lock */ if (wake_lock_active(&card->wakelock) && skb_queue_empty(&card->tx_queue) && skb_queue_empty(&card->rx_queue)) { sqn_pr_dbg("wake_lock is active, release it\n"); wake_unlock(&card->wakelock); } sqn_pr_leave(); } static void sqn_sdio_release_wake_lock(struct sqn_sdio_card *card) { u32 delay = 0; sqn_pr_enter(); // #define SQN_WAKE_LOCK_RELEASE_DELAY_SECONDS 5 #define SQN_WAKE_LOCK_RELEASE_DELAY_SECONDS 1 /* if TX and RX queues are empty, we will wait some time before * doing actual wake_lock release */ if (wake_lock_active(&card->wakelock) && skb_queue_empty(&card->tx_queue) && skb_queue_empty(&card->rx_queue)) { sqn_pr_dbg("shedule wake_lock release in %d sec\n" , SQN_WAKE_LOCK_RELEASE_DELAY_SECONDS); delay = jiffies + msecs_to_jiffies( SQN_WAKE_LOCK_RELEASE_DELAY_SECONDS * MSEC_PER_SEC); mod_timer(&card->wakelock_timer, delay); } #undef SQN_WAKE_LOCK_RELEASE_DELAY_SECONDS sqn_pr_leave(); } static int sqn_sdio_host_to_card(struct sqn_private *priv) { struct sqn_sdio_card *card = priv->card; struct sk_buff *skb = 0; unsigned long irq_flags = 0; int level = 0; int rv = 0; u8 need_to_ulock_mutex = 0; sqn_pr_enter(); if (priv->removed) { // sqn_pr_warn("%s: card/driver is removed, do nothing\n", __func__); // Andrew 0524 goto drv_removed; } spin_lock_irqsave(&priv->drv_lock, irq_flags); if (card->is_card_sleeps) { spin_unlock_irqrestore(&priv->drv_lock, irq_flags); /* * Ignore return value of sqn_wakeup_fw() and try * to send PDU even if wake up failed */ sqn_wakeup_fw(card->func); } else { spin_unlock_irqrestore(&priv->drv_lock, irq_flags); } if (0 == sqn_sdio_get_rstn_wr_fifo_flag(priv)) { rv = -1; goto dequeue_skb; } sqn_pr_dbg("acquire TX mutex\n"); if (!mutex_trylock(&card->tx_mutex)) { sqn_pr_dbg("failed to acquire TX mutex, it means we are going" " to remove a network interface\n"); need_to_ulock_mutex = 0; goto out; } need_to_ulock_mutex = 1; while (!priv->removed && !sqn_sdio_is_tx_queue_empty(priv)) { skb = skb_dequeue(&card->tx_queue); if (0 != (skb = sqn_sdio_prepare_skb_for_tx(skb))) { if (0 == level) { int count = 20; while (0 == (level = sqn_sdio_get_wr_fifo_level(priv))) { if (0 == count--) { sqn_pr_err("WR_FIFO_LEVEL2 timeout\n"); rv = -1; goto free_skb; } mdelay(1); } if (level < 0) { rv = -1; goto free_skb; } } sqn_sdio_tx_skb(card, skb, 1); --level; if (!card->waiting_pm_notification && netif_queue_stopped(priv->dev) && skb_queue_len(&card->tx_queue) < TX_QUEUE_WM_LEN) { sqn_pr_info("tx_queue len %d, enabling netif_queue\n" , skb_queue_len(&card->tx_queue)); netif_wake_queue(priv->dev); } else { sqn_pr_dbg("tx_queue len %d\n" , skb_queue_len(&card->tx_queue)); } } else { priv->stats.tx_dropped++; priv->stats.tx_errors++; } } out: if (need_to_ulock_mutex && mutex_is_locked(&card->tx_mutex)) { mutex_unlock(&card->tx_mutex); sqn_pr_dbg("release TX mutex\n"); } sqn_sdio_release_wake_lock(card); if (0 != rv) { /* * Failed to send PDU - assume that card was removed or * crashed/reset so initiate card detection. */ // Andrew 0424 // Reset chip will cause WiMAX status to OFF and then SCAN, OPERATION // Reset WiMAX chip // It could avoid we hang in SDIO CMD53 timeout and recovery wimax again. sqn_pr_info("reset WiMAX chip\n"); mmc_wimax_power(0); mdelay(5); mmc_wimax_power(1); sqn_pr_err("card seems to be dead/removed - initiate reinitialization\n"); mmc_detect_change(card->func->card->host, 1); } drv_removed: sqn_pr_leave(); return rv; dequeue_skb: if (!sqn_sdio_is_tx_queue_empty(priv)) { sqn_pr_dbg("remove skb from TX queue because of error\n"); skb = skb_dequeue(&card->tx_queue); } free_skb: sqn_pr_dbg("free TX skb because of error\n"); dev_kfree_skb_any(skb); priv->stats.tx_dropped++; priv->stats.tx_errors++; goto out; } /*******************************************************************/ /* RX handlers */ /*******************************************************************/ static void sqn_sdio_process_rx_queue(struct work_struct *work) { struct sqn_private *priv = container_of(work, struct sqn_private , rx_work_struct); struct sqn_sdio_card *card = (struct sqn_sdio_card*) priv->card; struct sk_buff *skb = 0; u8 need_to_ulock_mutex = 0; sqn_pr_enter(); sqn_pr_dbg("acquire RXQ mutex\n"); if (!mutex_trylock(&card->rxq_mutex)) { sqn_pr_dbg("failed to acquire RXQ mutex, it means we are going" " to remove a network interface\n"); need_to_ulock_mutex = 0; goto out; } need_to_ulock_mutex = 1; while (!priv->removed && 0 != (skb = skb_dequeue(&card->rx_queue))) { sqn_rx_process(card->priv->dev, skb); if (waitqueue_active(&priv->rx_waitq) && skb_queue_len(&card->rx_queue) < RX_QUEUE_WM_LEN) { sqn_pr_info("rx_queue len %d, enabling rx\n" , skb_queue_len(&card->rx_queue)); wake_up_interruptible(&priv->rx_waitq); } } out: if (need_to_ulock_mutex && mutex_is_locked(&card->rxq_mutex)) { mutex_unlock(&card->rxq_mutex); sqn_pr_dbg("release RXQ mutex\n"); } sqn_sdio_release_wake_lock(card); sqn_pr_leave(); } static int sqn_sdio_card_to_host(struct sqn_sdio_card *card) { u16 level = 0; int rv = 0; u8 need_to_ulock_mutex = 0; sqn_pr_enter(); if (card->priv->removed) { // sqn_pr_warn("%s: card/driver is removed, do nothing\n", __func__); // Andrew 0524 goto drv_removed; } sqn_pr_dbg("acquire RX mutex\n"); if (!mutex_trylock(&card->rx_mutex)) { sqn_pr_dbg("failed to acquire RX mutex, it means we are going" " to remove a network interface\n"); need_to_ulock_mutex = 0; goto out; } need_to_ulock_mutex = 1; /* * NOTE: call to sdio_claim_host() is already done * in sqn_sdio_it_lsb() - our caller */ check_level: /* Find out how many PDUs we have to read */ level = sdio_readw(card->func, SQN_SDIO_RD_FIFO_LEVEL(2), &rv); if (rv) { sqn_pr_err("ERROR reading SDIO_RD_FIFO_LEVEL\n"); goto out; } if (level == 0) { sqn_pr_dbg("no more PDUs to read\n"); if (rv < 0) sqn_pr_warn("%s: no more PDUs left but status = %d\n", __func__, rv); goto out; } sqn_pr_dbg("PDUs to read %d\n", level); while (!card->priv->removed && level--) { struct sk_buff *skb = 0; #if DUMP_NET_PKT struct ethhdr *eth = 0; #endif u16 size = 0; /* Get the size of PDU */ size = sdio_readw(card->func, SQN_SDIO_RDLEN_FIFO(2), &rv); if (rv) { sqn_pr_err("can't get FIFO read length, status = %d\n", rv); goto out; } sqn_pr_dbg("PDU #%u length %u\n", (u32)level, (u32)size); if (size > SQN_SDIO_PDU_MAXLEN || size < 1) { sqn_pr_err("RX PDU length %u is not correct\n", (u32)size); card->priv->stats.rx_length_errors++; card->priv->stats.rx_errors++; continue; } skb = __netdev_alloc_skb(card->priv->dev, SQN_SDIO_PDU_MAXLEN , GFP_ATOMIC | GFP_DMA); if (0 == skb) { sqn_pr_err("failed to alloc RX buffer\n"); rv = -ENOMEM; goto out; } #if SKB_DEBUG sqn_pr_info("%s: alloc skb [0x%p], users %d\n", __func__, skb, atomic_read(&skb->users)); #endif rv = sdio_readsb(card->func, skb->data, SQN_SDIO_RDWR_FIFO(2), (int)size); if (rv) { sqn_pr_err("RX PDU read failed: %d\n", rv); continue; } skb_put(skb, size); #if DUMP_NET_PKT eth = (struct ethhdr *)skb->data; if (!is_thp_packet(eth->h_dest) && !is_lsp_packet(skb)) { sqn_pr_info("----------------------------------------------------------------------\n"); sqn_pr_info("RX PDU length %d\n", skb->len); sqn_pr_info_dump("RX PDU", skb->data, skb->len); } #endif if (sqn_handle_lsp_packet(card->priv, skb)) continue; /* * If we have some not LSP PDUs to read, then card is not * asleep any more, so we should notify waiters about this */ if (card->is_card_sleeps) { sqn_pr_info("got RX data, card is not asleep\n"); signal_card_sleep_completion(card->priv); } if (!card->waiting_pm_notification && !wake_lock_active(&card->wakelock)) { sqn_pr_dbg("lock wake_lock\n"); wake_lock(&card->wakelock); } /* * Don't use internal RX queue, because kernel has its own. * Just push RX packet directly to kernel */ sqn_rx_process(card->priv->dev, skb); /* skb_queue_tail(&card->rx_queue, skb); */ /* if (skb_queue_len(&card->rx_queue) > RX_QUEUE_MAX_LEN) { */ /* int rv = 0; */ /* sqn_pr_info("rx_queue len %d, wait untill it'll be processed\n" */ /* , skb_queue_len(&card->rx_queue)); */ /* schedule_work(&card->priv->rx_work_struct); */ /* rv = wait_event_interruptible(card->priv->rx_waitq */ /* , skb_queue_len(&card->rx_queue) <= RX_QUEUE_WM_LEN); */ /* |+ */ /* * If we've been interrupted by a signal, then we */ /* * should stop and return */ /* +| */ /* if (0 != rv) { */ /* sqn_pr_warn("got a signal from kernel %d\n", rv); */ /* goto out; */ /* } */ /* sqn_pr_info("rx_queue len %d, continue RX PDUs processing\n" */ /* , skb_queue_len(&card->rx_queue)); */ /* } */ } /* sqn_pr_dbg("rx_queue len %d\n" */ /* , skb_queue_len(&card->rx_queue)); */ /* schedule_work(&card->priv->rx_work_struct); */ sqn_pr_dbg("check is there more PDU to read\n"); goto check_level; out: sqn_sdio_release_wake_lock(card); if (need_to_ulock_mutex && mutex_is_locked(&card->rx_mutex)) { mutex_unlock(&card->rx_mutex); sqn_pr_dbg("release RX mutex\n"); } drv_removed: sqn_pr_leave(); return rv; } /*******************************************************************/ /* Interrupt handling */ /*******************************************************************/ static int sqn_sdio_it_lsb(struct sdio_func *func) { struct sqn_sdio_card *card = sdio_get_drvdata(func); int rc = 0; u8 status = 0; unsigned long irq_flags = 0; u8 is_card_sleeps = 0; sqn_pr_enter(); /* NOTE: call of sdio_claim_host() is already done */ /* Read the interrupt status */ status = sdio_readb(func, SQN_SDIO_IT_STATUS_LSBS, &rc); if (rc) goto out; sqn_pr_dbg("interrupt(LSB): 0x%02X\n", (unsigned char) status); spin_lock_irqsave(&card->priv->drv_lock, irq_flags); is_card_sleeps = card->is_card_sleeps; spin_unlock_irqrestore(&card->priv->drv_lock, irq_flags); /* Handle interrupt */ if (status & SQN_SDIO_IT_WR_FIFO2_WM) { sqn_pr_dbg("skipping FIFO2 write watermark interrupt...\n"); /* Clear interrupt flag */ sdio_writeb(func, SQN_SDIO_IT_WR_FIFO2_WM, SQN_SDIO_IT_STATUS_LSBS, &rc); } if (status & SQN_SDIO_IT_RD_FIFO2_WM) { rc = sqn_sdio_card_to_host(card); if (rc) sqn_pr_err("can't read data from card, error %d\n", rc); /* Clear interrupt flag */ sdio_writeb(func, SQN_SDIO_IT_RD_FIFO2_WM, SQN_SDIO_IT_STATUS_LSBS, &rc); } out: sqn_pr_dbg("returned code: %d\n", rc); sqn_pr_leave(); return rc; } static int sqn_sdio_it_msb(struct sdio_func *func) { int rc = 0; u8 status = 0; sqn_pr_enter(); /* Read the interrupt status */ status = sdio_readb(func, SQN_SDIO_IT_STATUS_MSBS, &rc); if (rc) goto out; sqn_pr_dbg("interrupt(MSB): 0x%02X\n", (unsigned char) status); /* TODO: Handle interrupt */ sqn_pr_dbg("skipping any interrupt...\n"); /* Clear interrupt flag */ sdio_writeb(func, 0xff, SQN_SDIO_IT_STATUS_MSBS, &rc); out: sqn_pr_dbg("returned code: %d\n", rc); sqn_pr_leave(); return rc; } /* * defined in "drivers/mmc/omap2430_hsmmc.c" * in linux kernel from TI */ int sdio_int_enable(int enable, int slot); void sqn_sdio_interrupt(struct sdio_func *func) { unsigned long irq_flags = 0; u8 is_card_sleeps = 0; struct sqn_sdio_card *card = sdio_get_drvdata(func); sqn_pr_enter(); sqn_sdio_it_lsb(func); spin_lock_irqsave(&card->priv->drv_lock, irq_flags); is_card_sleeps = card->is_card_sleeps; spin_unlock_irqrestore(&card->priv->drv_lock, irq_flags); if (!is_card_sleeps) sqn_sdio_it_msb(func); sqn_pr_leave(); } static int sqn_sdio_it_enable(struct sdio_func *func) { u8 enable = 0; int rv = 0; sqn_pr_enter(); sdio_claim_host(func); /* enable LSB */ enable = SQN_SDIO_IT_WR_FIFO2_WM | SQN_SDIO_IT_RD_FIFO2_WM | SQN_SDIO_IT_SW_SIGN; sdio_writeb(func, enable, SQN_SDIO_IT_EN_LSBS, &rv); sqn_pr_dbg("enabled LSBS interrupt: rv=0x%02X\n", rv); if (rv) goto out; sqn_pr_dbg("enabled interrupt(LSB): 0x%02X\n", (unsigned char) enable); /* Set RD watermark to enable interrups for RX packets */ sdio_writew(func, 1, SQN_SDIO_WM_RD_FIFO(2), &rv); sqn_pr_dbg("enabled rd watermark: rv=%d\n", rv); if (rv) { sqn_pr_err("can't enable rd watermark: rv=%d\n", rv); goto out; } out: sdio_release_host(func); sqn_pr_dbg("returned code: %d\n", rv); sqn_pr_leave(); return rv; } static int sqn_sdio_it_disable(struct sdio_func *func) { int rc = 0; sqn_pr_enter(); sdio_claim_host(func); /* disable LSB */ sdio_writeb(func, 0, SQN_SDIO_IT_EN_LSBS, &rc); if (rc) goto out; sqn_pr_dbg("disabled interrupt(LSB)\n"); /* disable MSB */ sdio_writeb(func, 0, SQN_SDIO_IT_EN_MSBS, &rc); if (rc) goto out; sqn_pr_dbg("disabled interrupt(MSB)\n"); out: sqn_pr_dbg("returned code: %d\n", rc); sdio_release_host(func); sqn_pr_leave(); return rc; } void sqn_sdio_stop_it_thread_from_itself(struct sqn_private *priv) { struct sqn_sdio_card *card = priv->card; unsigned long irq_flags = 0; sqn_pr_enter(); spin_lock_irqsave(&priv->drv_lock, irq_flags); card->it_thread_should_stop = 1; spin_unlock_irqrestore(&priv->drv_lock, irq_flags); sqn_pr_leave(); } /*******************************************************************/ /* Driver registration */ /*******************************************************************/ #ifdef DEBUG static void sqn_sdio_debug_test(struct sdio_func *func) { /* int rc = 0; */ /* int val = 0; */ sqn_pr_enter(); sdio_claim_host(func); /* sqn_pr_dbg("write SQN_SOC_SIGS_LSBS\n"); */ /* sdio_writeb(func, 1, SQN_SOC_SIGS_LSBS, &rc); */ /* if (rc) */ /* sqn_pr_dbg("error when writing to SQN_SOC_SIGS_LSBS: %d\n", rc); */ #if 0 sqn_pr_dbg("readb 0x04\n"); val = sdio_readb(func, 0x04, &rc); if (rc) sqn_pr_dbg("readb 0x04 failed %x\n", rc); else sqn_pr_dbg("readb 0x04 = %x\n", val); sqn_pr_dbg("readb 0x2028\n"); val = sdio_readb(func, 0x2028, &rc); if (rc) sqn_pr_dbg("readb 0x2028 failed %x\n", rc); else sqn_pr_dbg("readb 0x2028 = %x\n", val); sqn_pr_dbg("readw 0x2028\n"); val = sdio_readw(func, 0x2028, &rc); if (rc) sqn_pr_dbg("readw 0x2028 failed %x\n", rc); else sqn_pr_dbg("readw 0x2028 = %x\n", val); sqn_pr_dbg("readb RSTN\n"); val = sdio_readb(func, SQN_SDIO_RSTN_WR_FIFO(2), &rc); if (rc) sqn_pr_dbg("readb RSTN failed %x\n", rc); else sqn_pr_dbg("readb RSTN = %x\n", val); sqn_pr_dbg("readl LEVEL\n"); val = sdio_readw(func, SQN_SDIO_WR_FIFO_LEVEL(2), &rc); if (rc) sqn_pr_dbg("readl LEVEL failed %x\n", rc); else sqn_pr_dbg("readl LEVEL = %x\n", val); sqn_pr_dbg("readl 0x2060\n"); val = sdio_readl(func, 0x2060, &rc); if (rc) sqn_pr_dbg("readl 0x2060 failed %x\n", rc); else sqn_pr_dbg("readl 0x2060 = %x\n", val); sqn_pr_dbg("writew SQN_SDIO_WM_RD_FIFO(2)\n"); sdio_writel(func, 1, SQN_SDIO_WM_RD_FIFO(2), &rc); if (rc) sqn_pr_dbg("writel SQN_SDIO_WM_RD_FIFO(2) failed %x\n", rc); else sqn_pr_dbg("writel SQN_SDIO_WM_RD_FIFO(2) = %x\n", rc); #endif sdio_release_host(func); sqn_pr_leave(); } static void sqn_sdio_print_debug_info(struct sdio_func *func) { sqn_pr_enter(); sqn_pr_info("sdio_func: device[%02x]: %04x:%04x\n", func->class, func->vendor, func->device); sqn_pr_info("sdio_func: block size: %d (maximum %d)\n", func->cur_blksize, func->max_blksize); sqn_pr_info("sdio_func: func->state: 0x%04x, card->state: 0x%04x\n" , func->state, func->card->state); sqn_pr_info("mmc_bus: clock=%u, width=%u, mode=%u, vdd=%u\n" , func->card->host->ios.clock , func->card->host->ios.bus_width , func->card->host->ios.bus_mode , func->card->host->ios.vdd); sqn_pr_dbg("host->caps=%x\n", (u32) func->card->host->caps); sqn_pr_leave(); } #endif /* DEBUG */ static void sqn_sdio_free_tx_queue(struct sqn_sdio_card *card) { struct sk_buff *skb = 0; while (0 != (skb = skb_dequeue(&card->tx_queue))) dev_kfree_skb_any(skb); } static void sqn_sdio_free_rx_queue(struct sqn_sdio_card *card) { struct sk_buff *skb = 0; while (0 != (skb = skb_dequeue(&card->rx_queue))) dev_kfree_skb_any(skb); } static int check_boot_from_host_mode(struct sdio_func *func) { int rv = 0; int status = 0; sdio_claim_host(func); status = sdio_readb(func, SQN_H_BOOT_FROM_SPI, &rv); sdio_release_host(func); if (rv) { sqn_pr_err("can't read boot flags from device"); return 0; } return !status; } static int sqn_check_card_id(struct sdio_func *func) { int rv = 0; unsigned short manf_id = 0; unsigned short card_id = 0; sqn_pr_enter(); sqn_pr_info("Checking card IDs...\n"); manf_id = sdio_readw(func, SDIO_CMN_CISTPLMID_MANF, &rv); if (rv) { sqn_pr_err("can't read card manufacturer id\n"); rv = 0; goto out; } card_id = sdio_readw(func, SDIO_CMN_CISTPLMID_CARD, &rv); if (rv) { sqn_pr_err("can't read card id\n"); rv = 0; goto out; } if (manf_id != SDIO_VENDOR_ID_SEQUANS || card_id != SDIO_DEVICE_ID_SEQUANS_SQN1130) { sqn_pr_info("found card with UNSUPPORTED manf_id=%x card_id=%x\n" , manf_id, card_id); rv = 0; } else { sqn_pr_info("found card with SUPPORTED manf_id=%x card_id=%x\n" , manf_id, card_id); rv = 1; } out: sqn_pr_leave(); return rv; } static u8 sqn_get_card_version(struct sdio_func *func) { int rv = 0; u32 version = 0; sqn_pr_enter(); switch (func->device) { case SDIO_DEVICE_ID_SEQUANS_SQN1130: sqn_pr_info("found SQN1130 card\n"); /* * Let bootrom/firmware name to be overridden from userspace as * a module parameter, so we change it only if it was not * changed from its default value */ if (0 == strcmp(firmware_name, SQN_DEFAULT_FW_NAME)) firmware_name = fw1130_name; rv = SQN_1130; break; case SDIO_DEVICE_ID_SEQUANS_SQN1210: sqn_pr_info("found SQN1210 card\n"); /* * Let firmware_name to be overridden from userspace as a module * parameter, so we change firmware_name only if it was not * changed from its default value */ if (0 == strcmp(firmware_name, SQN_DEFAULT_FW_NAME)) firmware_name = fw1210_name; rv = SQN_1210; break; default: sqn_pr_info("found UNKNOWN card with vendor_id 0x%x" " dev_id 0x%x\n", func->vendor, func->device); rv = 0; } /* Maintain in compilable state but don't use it for now */ #if 0 /* * For production devices this is not needed, we can get a device id * from sdio_func */ sqn_pr_info("Checking card version...\n"); sdio_claim_host(func); version = sdio_readl(func, SQN_H_VERSION, &rv); sdio_release_host(func); if (rv) { sqn_pr_err("failed to read card version\n"); rv = 0; goto out; } #define SQN1130_MAJOR_VERSION 0x06 #define SQN12x0_MAJOR_VERSION 0x0a if (SQN1130_MAJOR_VERSION == (version & 0xff)) { sqn_pr_info("found SQN_1130 card with version id 0x%x\n" , version); rv = SQN_1130; } else if (SQN12x0_MAJOR_VERSION == (version & 0xff)) { sqn_pr_info("found SQN_1210 card with version id 0x%x\n" , version); rv = SQN_1210; } else { sqn_pr_info("found UNKNOWN card with version id 0x%x\n" , version); rv = 0; } #endif out: sqn_pr_leave(); return rv; } extern u8 _g_card_sleeps; extern struct sqn_private *g_priv; struct msmsdcc_host; int msmsdcc_enable_clocks(struct msmsdcc_host *host); static irqreturn_t wimax_wakeup_gpio_irq_handler(int irq, void *dev_id) { struct sqn_sdio_card *card = g_priv->card; struct msmsdcc_host *msm_host = mmc_priv(card->func->card->host); sqn_pr_enter(); #if SDIO_CLK_DEBUG /* Please, don't disable this log, it will be printed not often, only * once when host is in sleep mode */ // sqn_pr_info("WiMAX GPIO interrupt\n"); #endif msmsdcc_enable_clocks(msm_host); sqn_pr_leave(); return IRQ_HANDLED; } int init_thp_handler(struct net_device *dev); void cleanup_thp_handler(void); static int sqn_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id) { int rv = 0; struct sqn_sdio_card *sqn_card = 0; struct sqn_private *priv = 0; int counter = 0; int delay = 0; int err; u32 irq; u32 req_flags = IRQF_TRIGGER_RISING; sqn_pr_enter(); sqn_pr_info("module parameters: firmware_name='%s' load_firmware=%d\n" , firmware_name, load_firmware); #ifdef DEBUG sqn_sdio_print_debug_info(func); /* sqn_sdio_debug_test(func); */ #endif /* Allocate card's private data storage */ sqn_card = kzalloc(sizeof(struct sqn_sdio_card), GFP_KERNEL); if (!sqn_card) { rv = -ENOMEM; goto out; } sqn_card->version = sqn_get_card_version(func); if (0 == sqn_card->version) { rv = -EPROTO; goto free_card; } skb_queue_head_init(&sqn_card->tx_queue); skb_queue_head_init(&sqn_card->rx_queue); init_waitqueue_head(&sqn_card->pm_waitq); mutex_init(&sqn_card->tx_mutex); mutex_init(&sqn_card->rx_mutex); mutex_init(&sqn_card->rxq_mutex); wake_lock_init(&sqn_card->wakelock, WAKE_LOCK_SUSPEND, "sqnsdio"); setup_timer(&sqn_card->wakelock_timer , sqn_sdio_wake_lock_release_timer_fn , (unsigned long) sqn_card); sqn_card->func = func; /* Activate SDIO function and register interrupt handler */ sdio_claim_host(func); rv = sdio_enable_func(func); if (rv) goto release; rv = sdio_claim_irq(func, sqn_sdio_interrupt); if (rv) goto disable; sdio_release_host(func); sdio_set_drvdata(func, sqn_card); priv = sqn_add_card(sqn_card, &func->dev); if (!priv) { rv = -ENOMEM; goto reclaim; } sqn_card->priv = priv; INIT_WORK(&priv->rx_work_struct, sqn_sdio_process_rx_queue); priv->card = sqn_card; priv->hw_host_to_card = sqn_sdio_host_to_card; priv->add_skb_to_tx_queue = sqn_sdio_add_skb_to_tx_queue; priv->is_tx_queue_empty = sqn_sdio_is_tx_queue_empty; /* Load firmware if card needs it */ if (check_boot_from_host_mode(sqn_card->func)) { rv = sqn_load_firmware(sqn_card->func); if (rv) goto err_activate_card; } memcpy(priv->dev->dev_addr, priv->mac_addr, ETH_ALEN); rv = sqn_start_card(priv); if (rv) goto err_activate_card; /* We need to setup thp_handler now, to catch all THP packets * as soon as they appear after interrupts are enabled */ rv = init_thp_handler(priv->dev); if (rv) goto unreg_netdev; /* Enable interrupts, now everything is set up */ rv = sqn_sdio_it_enable(sqn_card->func); if (rv) goto clean_thp_handler; sqn_pr_info("wait until FW is started...\n"); counter = 20; delay = 500; while (0 == sqn_sdio_get_rstn_wr_fifo_flag(priv) && --counter > 0) { sqn_pr_dbg("FW is not started yet, sleep for %d msecs," " %d retries left\n" , delay , counter); msleep(delay); } if (0 == sqn_card->rstn_wr_fifo_flag) sqn_pr_warn("FW is still not started, anyway continue as is...\n"); sqn_pr_info("setup GPIO40 for wakeup form SQN1210\n"); rv = irq = MSM_GPIO_TO_INT(40); //GPIO_40 as wakeup if (rv < 0) { sqn_pr_warn("wimax-gpio to irq failed\n"); goto disable; } rv = request_irq(irq, wimax_wakeup_gpio_irq_handler, req_flags, "WiMAX0", sqn_card->priv->dev); if (rv) { sqn_pr_warn("wimax-gpio request_irq failed=%d\n", rv); goto disable; } sqn_pr_dbg("disable GPIO40 interrupt\n"); disable_irq(MSM_GPIO_TO_INT(40)); rv = init_thp(priv->dev); if (rv) goto clean_thp_handler; #ifdef DEBUG /* sqn_sdio_debug_test(sqn_card->func); */ #endif out: sqn_pr_dbg("returned code: %d\n", rv); if (0 == rv) sqn_pr_info("card initialized successfuly\n"); sqn_pr_leave(); return rv; clean_thp: cleanup_thp(); clean_thp_handler: cleanup_thp_handler(); unreg_netdev: unregister_netdev(priv->dev); err_activate_card: flush_scheduled_work(); free_netdev(priv->dev); reclaim: sdio_claim_host(func); sdio_release_irq(func); disable: sdio_disable_func(func); release: sdio_release_host(func); sqn_sdio_free_tx_queue(sqn_card); sqn_sdio_free_rx_queue(sqn_card); /* release a wake_lock if it was not done for a some reason */ if (wake_lock_active(&sqn_card->wakelock)) { sqn_pr_dbg("wake_lock is active, release it\n"); wake_unlock(&sqn_card->wakelock); } wake_lock_destroy(&sqn_card->wakelock); free_card: kfree(sqn_card); goto out; } static void sqn_sdio_remove(struct sdio_func *func) { struct sqn_sdio_card *sqn_card = sdio_get_drvdata(func); u8 count = 0; u32 delay = 0; int rv = 0; sqn_pr_enter(); sqn_pr_info("free GPIO40 interrupt\n"); free_irq(MSM_GPIO_TO_INT(40),sqn_card->priv->dev); #if defined(DEBUG) sqn_sdio_print_debug_info(func); #endif cleanup_thp(); /* * Let all running threads know that we are starting * a remove procedure */ sqn_card->priv->removed = 1; delay = 1000; sqn_sdio_it_disable(sqn_card->func); sqn_pr_info("wait until RX is finished\n"); count = 5; while (--count && !(rv = mutex_trylock(&sqn_card->rx_mutex))) mdelay(delay); if (!rv) sqn_pr_warn("%s: failed to acquire RX mutex\n", __func__); sqn_stop_card(sqn_card->priv); kthread_stop(sqn_card->priv->tx_thread); wake_up_interruptible(&sqn_card->priv->tx_waitq); sqn_pr_info("wait until TX is finished\n"); count = 5; while (--count && !(rv = mutex_trylock(&sqn_card->tx_mutex))) mdelay(delay); if (!rv) sqn_pr_warn("%s: failed to acquire TX mutex\n", __func__); sdio_claim_host(func); sdio_release_irq(func); sdio_disable_func(func); sdio_release_host(func); sqn_remove_card(sqn_card->priv); sqn_sdio_free_tx_queue(sqn_card); sqn_sdio_free_rx_queue(sqn_card); del_timer_sync(&sqn_card->wakelock_timer); /* release a wake_lock if it was not done for a some reason */ if (wake_lock_active(&sqn_card->wakelock)) { sqn_pr_dbg("wake_lock is active, release it\n"); wake_unlock(&sqn_card->wakelock); } wake_lock_destroy(&sqn_card->wakelock); kfree(sqn_card); sdio_set_drvdata(func, 0); sqn_pr_info("card removed successfuly\n"); mmc_detect_change(func->card->host, msecs_to_jiffies(500)); sqn_pr_leave(); } u8 sqn_is_gpio_irq_enabled = 0; int sqn_sdio_suspend(struct sdio_func *func, pm_message_t msg) { int rv = 0; /* unsigned long irq_flags = 0; */ struct sqn_sdio_card *sqn_card = sdio_get_drvdata(func); sqn_pr_enter(); sqn_pr_info("%s: enter\n", __func__); sqn_pr_dbg("pm_message = %x\n", msg.event); WARN(!skb_queue_empty(&sqn_card->tx_queue) , "BANG!!! TX queue is not empty in suspend(): %d" , skb_queue_len(&sqn_card->tx_queue)); WARN(!skb_queue_empty(&sqn_card->rx_queue) , "BANG!!! RX queue is not empty in suspend(): %d" , skb_queue_len(&sqn_card->rx_queue)); if (sqn_card->is_card_sleeps) { sqn_pr_info("card already asleep (pm_message = 0x%x)\n" , msg.event); goto out; } /* Do nothing when system goes to power off */ if (PM_EVENT_SUSPEND != msg.event) { sqn_pr_warn("Not supported pm_message = %x\n", msg.event); goto out; } if (sqn_notify_host_sleep(func)) { sqn_pr_warn("Failed to suspend\n"); goto out; } out: if (!sqn_is_gpio_irq_enabled) { sqn_pr_info("enable GPIO40 interrupt\n"); enable_irq(MSM_GPIO_TO_INT(40)); enable_irq_wake(MSM_GPIO_TO_INT(40)); sqn_is_gpio_irq_enabled = 1; } sqn_pr_info("%s: leave\n", __func__); sqn_pr_leave(); return rv; } int sqn_sdio_resume(struct sdio_func *func) { int rv = 0; struct sqn_sdio_card *sqn_card = sdio_get_drvdata(func); sqn_pr_enter(); sqn_pr_info("%s: enter\n", __func__); if (netif_queue_stopped(sqn_card->priv->dev)) { sqn_pr_dbg("wake netif_queue\n"); netif_wake_queue(sqn_card->priv->dev); } // Dima: we don't need this, card will be woken up when there will be // some TX data /* sqn_notify_host_wakeup(func); */ if (sqn_is_gpio_irq_enabled) { sqn_pr_info("disable GPIO40 interrupt\n"); disable_irq_wake(MSM_GPIO_TO_INT(40)); disable_irq(MSM_GPIO_TO_INT(40)); sqn_is_gpio_irq_enabled = 0; } sqn_pr_info("%s: leave\n", __func__); sqn_pr_leave(); return rv; } static struct sdio_driver sqn_sdio_driver = { .name = SQN_MODULE_NAME , .id_table = sqn_sdio_ids , .probe = sqn_sdio_probe , .remove = sqn_sdio_remove #ifdef ANDROID_KERNEL , .suspend = sqn_sdio_suspend , .resume = sqn_sdio_resume #endif }; /*******************************************************************/ /* Module initialization */ /*******************************************************************/ static int __init sqn_sdio_init_module(void) { int rc = 0; sqn_pr_enter(); sqn_pr_info("Sequans SDIO WiMAX driver, version %s\n" , SQN_MODULE_VERSION); sqn_pr_info("Copyright SEQUANS Communications\n"); // printk(KERN_WARNING "------------ %s ------------\n", __FUNCTION__); mmc_wimax_power(1); mmc_wimax_set_carddetect(1); // thp_wimax_uart_switch(1); mmc_wimax_set_status(1); rc = sdio_register_driver(&sqn_sdio_driver); #ifdef ANDROID_KERNEL register_android_earlysuspend(); #endif /* TI_KERNEL */ sqn_pr_info("Driver has been registered\n"); sqn_pr_leave(); return rc; } static void __exit sqn_sdio_exit_module(void) { sqn_pr_enter(); sdio_unregister_driver(&sqn_sdio_driver); #ifdef ANDROID_KERNEL unregister_android_earlysuspend(); #endif sqn_pr_info("Driver has been removed\n"); mmc_wimax_set_carddetect(0); mmc_wimax_power(0); // thp_wimax_uart_switch(0); mmc_wimax_set_status(0); sqn_pr_leave(); } module_init(sqn_sdio_init_module); module_exit(sqn_sdio_exit_module); MODULE_DESCRIPTION("Sequans WiMAX driver for SDIO devices"); MODULE_AUTHOR("Dmitriy Chumak, Andy Shevchenko"); MODULE_LICENSE("GPL"); MODULE_VERSION(SQN_MODULE_VERSION);