1696 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1696 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * This is part of the Sequans SQN1130 driver.
 | |
|  * Copyright 2008 SEQUANS Communications
 | |
|  * Written by Dmitriy Chumak <chumakd@gmail.com>,
 | |
|  *            Andy Shevchenko <andy@smile.org.ua>
 | |
|  *
 | |
|  * 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 <linux/module.h>
 | |
| #include <linux/init.h>
 | |
| #include <linux/delay.h>
 | |
| #include <linux/kthread.h>
 | |
| #include <linux/skbuff.h>
 | |
| #include <linux/netdevice.h>
 | |
| #include <linux/device.h>
 | |
| #include <linux/mmc/card.h>
 | |
| #include <linux/mmc/host.h>
 | |
| #include <linux/pm.h>
 | |
| #include <linux/jiffies.h>
 | |
| #include <linux/hardirq.h>
 | |
| #include <linux/mutex.h>
 | |
| #include <linux/wakelock.h>
 | |
| #include <linux/bug.h>
 | |
| #include <linux/irq.h>
 | |
| 
 | |
| // GPIO_WAKEUP
 | |
| #include <linux/gpio.h>
 | |
| #include <linux/gpio_event.h>
 | |
| #include <linux/interrupt.h>
 | |
| 
 | |
| #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);
 |