839 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			839 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
|  /*
 | |
|  * This is part of the Sequans SQN1130 driver.
 | |
|  * Copyright 2008 SEQUANS Communications
 | |
|  * Written by Dmitriy Chumak <chumakd@gmail.com>
 | |
|  *
 | |
|  * 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.
 | |
|  */
 | |
| 
 | |
| /*
 | |
|  * This file includes code that is responsible for
 | |
|  * power management and LSP notifications handling.
 | |
|  */
 | |
| 
 | |
| 
 | |
| #include <linux/if_ether.h>
 | |
| #include <linux/skbuff.h>
 | |
| #include <linux/netdevice.h>
 | |
| #include <linux/string.h>
 | |
| #include <linux/byteorder/generic.h>
 | |
| #include <linux/wait.h>
 | |
| #include <linux/spinlock.h>
 | |
| #include <linux/delay.h>
 | |
| #include <linux/wakelock.h>
 | |
| 
 | |
| #ifdef ANDROID_KERNEL
 | |
| #include <linux/earlysuspend.h>
 | |
| #endif /* ANDROID_KERNEL */
 | |
| 
 | |
| #include "sdio-netdev.h"
 | |
| #include "sdio.h"
 | |
| #include "sdio-pm.h"
 | |
| #include "msg.h"
 | |
| #include "thp.h"
 | |
| #include "sdio-sqn.h"
 | |
| 
 | |
| #define IGNORE_CARRIER_STATE 1
 | |
| extern int mmc_wimax_get_hostwakeup_gpio(void);
 | |
| 
 | |
| enum sqn_thsp_service {
 | |
| #define	THSP_LSP_SERVICE_BASE		0x10010000
 | |
| 	THSP_GET_MEDIA_CONNECTION_STATE = THSP_LSP_SERVICE_BASE
 | |
| 	, THSP_MEDIA_CONNECTION_STATE_CHANGE
 | |
| 	, THSP_SET_POWER_MODE		/* deprecated */
 | |
| 
 | |
| 	, THSP_SET_HOST_POWER_MODE
 | |
| 	, THSP_SET_HOST_POWER_MODE_ACK
 | |
| 
 | |
| 	, THSP_SET_FW_POWER_MODE
 | |
| 	, THSP_SET_FW_POWER_MODE_ACK
 | |
| 
 | |
| 	, THSP_SQN_STATE_CHANGE
 | |
| 	, THSP_SQN_STATE_CHANGE_REPLY
 | |
| 
 | |
| 	, THSP_THP_AVAILABLE
 | |
| 	, THSP_THP_AVAILABLE_REPLY
 | |
| };
 | |
| 
 | |
| 
 | |
| /* Deprecated */
 | |
| /* enum sqn_power_mode { */
 | |
| 	/* SQN_PM_OPERATIONAL */
 | |
| 	/* , SQN_PM_SHUTDOWN */
 | |
| 	/* , SQN_PM_STANDBY */
 | |
| /* }; */
 | |
| 
 | |
| 
 | |
| enum sqn_host_power_mode {
 | |
|     LSP_HPM_OPERATIONAL
 | |
|     , LSP_HPM_ASLEEP
 | |
| };
 | |
| 
 | |
| 
 | |
| enum sqn_fw_power_mode {
 | |
|     LSP_FPM_OPERATIONAL
 | |
|     , LSP_FPM_SHUTDOWN
 | |
|     , LSP_FPM_STANDBY
 | |
| };
 | |
| 
 | |
| 
 | |
| enum sqn_pm_status {
 | |
| 	SQN_PM_STATUS_SUCCES
 | |
| 	, SQN_PM_STATUS_CHANGE_IN_PROGRESS
 | |
| 	, SQN_PM_STATUS_UNKNOWN
 | |
| };
 | |
| 
 | |
| 
 | |
| enum sqn_thsp_media_connection_state {
 | |
| 	THSP_MEDIA_CONNECTION_DISCONNECTED
 | |
| 	, THSP_MEDIA_CONNECTION_CONNECTING
 | |
| 	, THSP_MEDIA_CONNECTION_CONNECTED
 | |
| 	, THSP_MEDIA_CONNECTION_ATTACHED
 | |
| };
 | |
| 
 | |
| 
 | |
| enum sqn_fw_state {
 | |
|     LSP_SQN_ACTIVE
 | |
|     , LSP_SQN_IDLE
 | |
|     , LSP_SQN_DROPPED
 | |
|     , LSP_SQN_REENTRY
 | |
| };
 | |
| 
 | |
| 
 | |
| enum sqn_thp_available_reply {
 | |
|     LSP_THPA_ACK
 | |
|     , LSP_THPA_FINISHED
 | |
|     , LSP_THPA_EXIT
 | |
| };
 | |
| 
 | |
| 
 | |
| struct sqn_eth_header {
 | |
| 	u8	dst_addr[ETH_ALEN];
 | |
| 	u8	src_addr[ETH_ALEN];
 | |
| 	u16	len;
 | |
| };
 | |
| 
 | |
| 
 | |
| struct sqn_lsp_header {
 | |
| 	u32	id;
 | |
| 	union {
 | |
| 		u32	tid;
 | |
| 		enum sqn_thsp_media_connection_state	media_con_state;
 | |
| 
 | |
| 		struct {
 | |
| 			u32				tid;
 | |
| 			enum sqn_host_power_mode	mode;
 | |
| 		} host_power;
 | |
| 
 | |
| 		struct {
 | |
| 			u32				tid;
 | |
| 			enum sqn_fw_power_mode		mode;
 | |
| 		} fw_power;
 | |
| 
 | |
| 		struct {
 | |
| 			u32				tid;
 | |
| 			enum sqn_fw_state		state;
 | |
| 		} fw_state;
 | |
| 
 | |
| 		struct {
 | |
| 			u32				tid;
 | |
| 			enum sqn_thp_available_reply	reply;
 | |
| 		} thp_avl;
 | |
| 	} u;
 | |
| };
 | |
| 
 | |
| 
 | |
| struct sqn_lsp_packet {
 | |
|     struct sqn_thp_header	thp_header;
 | |
|     struct sqn_lsp_header	lsp_header;
 | |
| };
 | |
| 
 | |
| 
 | |
| static u8 g_lsp_host_mac[] = {0x00, 0x16, 0x08, 0xff, 0x00, 0x06};
 | |
| static u8 g_lsp_device_mac[] = {0x00, 0x16, 0x08, 0xff, 0x00, 0x05};
 | |
| 
 | |
| 
 | |
| /* TODO: add all global variables to private per-card structure */
 | |
| static struct sk_buff	*g_last_request_skb = 0;
 | |
| static spinlock_t	g_last_request_lock = SPIN_LOCK_UNLOCKED;
 | |
| static u32		g_last_request_pm = 0;
 | |
| 
 | |
| 
 | |
| /* TODO: move this to per-card private structure */
 | |
| static DECLARE_WAIT_QUEUE_HEAD(g_card_sleep_waitq);
 | |
| 
 | |
| 
 | |
| /* Transaction ID for lsp requests */
 | |
| static u32		g_tid = 0;
 | |
| static spinlock_t	g_tid_lock = SPIN_LOCK_UNLOCKED;
 | |
| 
 | |
| 
 | |
| static u32 get_current_tid(void)
 | |
| {
 | |
| 	u32 tid = 0;
 | |
| 
 | |
| 	spin_lock(&g_tid_lock);
 | |
| 	tid = g_tid;
 | |
| 	spin_unlock(&g_tid_lock);
 | |
| 
 | |
| 	return tid;
 | |
| }
 | |
| 
 | |
| 
 | |
| static u32 get_next_tid(void)
 | |
| {
 | |
| 	u32 tid = 0;
 | |
| 
 | |
| 	spin_lock(&g_tid_lock);
 | |
| 	g_tid += 1;
 | |
| 	tid = g_tid;
 | |
| 	spin_unlock(&g_tid_lock);
 | |
| 
 | |
| 	return tid;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void free_last_request(void)
 | |
| {
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	spin_lock(&g_last_request_lock);
 | |
| 	if (0 != g_last_request_skb) {
 | |
| 		dev_kfree_skb_any(g_last_request_skb);
 | |
| 		g_last_request_skb = 0;
 | |
| 	}
 | |
| 	spin_unlock(&g_last_request_lock);
 | |
| 
 | |
| 	sqn_pr_leave();
 | |
| }
 | |
| 
 | |
| 
 | |
| static struct sk_buff* lsp_to_skb(struct sqn_lsp_packet *lsp_packet)
 | |
| {
 | |
| 	struct sqn_eth_header eth_header = {
 | |
| 		.len = htons(sizeof(struct sqn_lsp_packet))
 | |
| 	};
 | |
| 
 | |
| 	struct sk_buff *skb =
 | |
| 		__dev_alloc_skb(sizeof(eth_header) + sizeof(struct sqn_lsp_packet)
 | |
| 				, GFP_ATOMIC | GFP_DMA);
 | |
| 
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	if (0 == skb)
 | |
| 		goto out;
 | |
| 
 | |
| 	skb_reserve(skb, 2);
 | |
| 
 | |
| 	memcpy(eth_header.dst_addr, g_lsp_device_mac, sizeof(g_lsp_device_mac));
 | |
| 	memcpy(eth_header.src_addr, g_lsp_host_mac, sizeof(g_lsp_host_mac));
 | |
| 
 | |
| 	memcpy(skb->data, ð_header, sizeof(eth_header));
 | |
| 	skb_put(skb, sizeof(eth_header));
 | |
| 
 | |
| 	memcpy(skb->data + skb->len, lsp_packet, sizeof(struct sqn_lsp_packet));
 | |
| 	skb_put(skb, sizeof(struct sqn_lsp_packet));
 | |
| 
 | |
| 	sqn_pr_leave();
 | |
| 
 | |
| out:
 | |
| 	return skb;
 | |
| 
 | |
| }
 | |
| 
 | |
| 
 | |
| static struct sk_buff* construct_lsp_packet(u32 id, u32 param1, u32 param2)
 | |
| {
 | |
| 	struct sqn_lsp_packet lsp_packet = {
 | |
| 		.thp_header = {
 | |
| 			.transport_version = 1
 | |
| 			, .flags = 1
 | |
| 			, .seq_number = 0
 | |
| 			, .ack_number = 0
 | |
| 		}
 | |
| 		, .lsp_header = {
 | |
| 			.id = htonl(id)
 | |
| 		}
 | |
| 	};
 | |
| 
 | |
| 	struct sk_buff *skb = 0;
 | |
| 
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	switch (id) {
 | |
| 	case THSP_GET_MEDIA_CONNECTION_STATE:
 | |
| 		/* no parameters are needed */
 | |
| 		sqn_pr_dbg("id: THSP_GET_MEDIA_CONNECTION_STATE\n");
 | |
| 		lsp_packet.thp_header.length =
 | |
| 			htons(sizeof(struct sqn_lsp_header) - 4);
 | |
| 		lsp_packet.thp_header.total_length =
 | |
| 			htonl(sizeof(struct sqn_lsp_header) - 4);
 | |
| 		break;
 | |
| 	case THSP_SET_POWER_MODE:
 | |
| 		/* deprecated */
 | |
| 		sqn_pr_dbg("id: THSP_SET_POWER_MODE (deprecated)\n");
 | |
| 		break;
 | |
| 	case THSP_SET_HOST_POWER_MODE:
 | |
| 		lsp_packet.thp_header.length =
 | |
| 			htons(sizeof(struct sqn_lsp_header));
 | |
| 		lsp_packet.thp_header.total_length =
 | |
| 			htonl(sizeof(struct sqn_lsp_header));
 | |
| 		lsp_packet.lsp_header.u.host_power.tid = htonl(get_next_tid());
 | |
| 		lsp_packet.lsp_header.u.host_power.mode = htonl(param1);
 | |
| 		sqn_pr_dbg("id: THSP_SET_HOST_POWER_MODE, tid: 0x%x, mode: %d\n"
 | |
| 			, ntohl(lsp_packet.lsp_header.u.host_power.tid)
 | |
| 			, param1);
 | |
| 		break;
 | |
| 	case THSP_SET_FW_POWER_MODE:
 | |
| 		lsp_packet.thp_header.length =
 | |
| 			htons(sizeof(struct sqn_lsp_header));
 | |
| 		lsp_packet.thp_header.total_length =
 | |
| 			htonl(sizeof(struct sqn_lsp_header));
 | |
| 		lsp_packet.lsp_header.u.fw_power.tid = htonl(get_next_tid());
 | |
| 		lsp_packet.lsp_header.u.fw_power.mode = htonl(param1);
 | |
| 		sqn_pr_dbg("id: THSP_SET_FW_POWER_MODE, tid: 0x%x, mode: %d\n"
 | |
| 			, htonl(lsp_packet.lsp_header.u.fw_power.tid)
 | |
| 			, param1);
 | |
| 		break;
 | |
| 	case THSP_SQN_STATE_CHANGE_REPLY:
 | |
| 		lsp_packet.thp_header.length =
 | |
| 			htons(sizeof(struct sqn_lsp_header) - 4);
 | |
| 		lsp_packet.thp_header.total_length =
 | |
| 			htonl(sizeof(struct sqn_lsp_header) - 4);
 | |
| 		lsp_packet.lsp_header.u.fw_state.tid = htonl(param1);
 | |
| 		sqn_pr_dbg("id: THSP_SQN_STATE_CHANGE_REPLY, tid: %xh\n"
 | |
| 			, param1);
 | |
| 		break;
 | |
| 	case THSP_THP_AVAILABLE_REPLY:
 | |
| 		lsp_packet.thp_header.length =
 | |
| 			htons(sizeof(struct sqn_lsp_header));
 | |
| 		lsp_packet.thp_header.total_length =
 | |
| 			htonl(sizeof(struct sqn_lsp_header));
 | |
| 		lsp_packet.lsp_header.u.thp_avl.tid = htonl(param1);
 | |
| 		lsp_packet.lsp_header.u.thp_avl.reply = htonl(param2);
 | |
| 		sqn_pr_dbg("id: THSP_THP_AVAILABLE_REPLY, tid: 0x%x, reply: %d\n"
 | |
| 			, param1, param2);
 | |
| 		break;
 | |
| 	default:
 | |
| 		sqn_pr_dbg("id: UNKNOWN\n");
 | |
| 	}
 | |
| 
 | |
| 	skb = lsp_to_skb(&lsp_packet);
 | |
| 
 | |
| 	sqn_pr_leave();
 | |
| 
 | |
| 	return skb;
 | |
| }
 | |
| 
 | |
| 
 | |
| int is_lsp_packet(const struct sk_buff *skb)
 | |
| {
 | |
| 	struct sqn_eth_header *eth_hdr = (struct sqn_eth_header*)skb->data;
 | |
| 
 | |
| 	/* sqn_pr_dbg_dump("skb________", skb->data, skb->len); */
 | |
| 	/* sqn_pr_dbg_dump("lsp_dev_mac", g_lsp_device_mac, sizeof(g_lsp_device_mac)); */
 | |
| 	/* sqn_pr_dbg_dump("skb_addr___", eth_hdr->src_addr, sizeof(g_lsp_device_mac)); */
 | |
| 
 | |
| 	return !memcmp(eth_hdr->dst_addr, g_lsp_host_mac
 | |
| 		, sizeof(g_lsp_host_mac));
 | |
| }
 | |
| 
 | |
| 
 | |
| static int sqn_set_power_mode_helper(struct sdio_func *func
 | |
| 	, enum sqn_thsp_service command_id, u32 pm)
 | |
| {
 | |
| 	unsigned long irq_flags = 0;
 | |
| 	struct sqn_sdio_card *card = sdio_get_drvdata(func);
 | |
| 
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	free_last_request();
 | |
| 
 | |
| 	spin_lock(&g_last_request_lock);
 | |
| 
 | |
| 	g_last_request_pm = pm;
 | |
| 	g_last_request_skb = construct_lsp_packet(command_id, pm, 0);
 | |
| 
 | |
| 	if (0 == g_last_request_skb)
 | |
| 		return 1;
 | |
| 
 | |
| 	netif_stop_queue(card->priv->dev);
 | |
| 
 | |
| 	/*
 | |
| 	 * We can't call sqn_sdio_tx_skb() from here, because we are not in
 | |
| 	 * process context
 | |
| 	 */
 | |
| 	skb_queue_tail(&card->tx_queue, g_last_request_skb);
 | |
| 	g_last_request_skb = 0;
 | |
| 
 | |
| 	spin_unlock(&g_last_request_lock);
 | |
| 
 | |
| 	spin_lock_irqsave(&card->priv->drv_lock, irq_flags);
 | |
| 	card->pm_complete = 0;
 | |
| 	spin_unlock_irqrestore(&card->priv->drv_lock, irq_flags);
 | |
| 
 | |
| 	wake_up_interruptible(&card->priv->tx_waitq);
 | |
| 
 | |
| 	sqn_pr_leave();
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int sqn_set_host_power_mode(struct sdio_func *func, enum sqn_host_power_mode pm)
 | |
| {
 | |
| 	int rv = 0;
 | |
| 
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	rv = sqn_set_power_mode_helper(func, THSP_SET_HOST_POWER_MODE, pm);
 | |
| 
 | |
| 	sqn_pr_leave();
 | |
| 
 | |
| 	return rv;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int sqn_set_fw_power_mode(struct sdio_func *func, enum sqn_fw_power_mode pm)
 | |
| {
 | |
| 	int rv = 0;
 | |
| 
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	rv = sqn_set_power_mode_helper(func, THSP_SET_FW_POWER_MODE, pm);
 | |
| 
 | |
| 	sqn_pr_leave();
 | |
| 
 | |
| 	return rv;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void signal_pm_request_completion(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->pm_complete = 1;
 | |
| 	spin_unlock_irqrestore(&priv->drv_lock, irq_flags);
 | |
| 
 | |
| 	wake_up_interruptible(&card->pm_waitq);
 | |
| 
 | |
| 	sqn_pr_leave();
 | |
| }
 | |
| 
 | |
| 
 | |
| void signal_card_sleep_completion(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->is_card_sleeps = 0;
 | |
| 	spin_unlock_irqrestore(&priv->drv_lock, irq_flags);
 | |
| 	wake_up_interruptible(&g_card_sleep_waitq);
 | |
| 	sqn_pr_dbg("card sleep completion is signaled\n");
 | |
| 
 | |
| 	sqn_pr_leave();
 | |
| }
 | |
| 
 | |
| 
 | |
| int sqn_notify_host_sleep(struct sdio_func *func)
 | |
| {
 | |
| 	int rv = 0;
 | |
| 	unsigned long irq_flags = 0;
 | |
| 	u32 timeout = 0;
 | |
| 	struct sqn_sdio_card *sqn_card = sdio_get_drvdata(func);
 | |
| 
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	sqn_card->waiting_pm_notification = 1;
 | |
| 
 | |
| 	sqn_pr_info("notify card about host goes to sleep...\n");
 | |
| 	sqn_set_host_power_mode(func, LSP_HPM_ASLEEP);
 | |
| 
 | |
| 	timeout = 50;
 | |
| 	sqn_pr_info("wait for completion (timeout %u msec)...\n", timeout);
 | |
| 	rv = wait_event_interruptible_timeout(sqn_card->pm_waitq
 | |
| 		, sqn_card->pm_complete, msecs_to_jiffies(timeout));
 | |
| 	if (-ERESTARTSYS == rv) {
 | |
| 		sqn_pr_warn("got a signal from kernel %d\n", rv);
 | |
| 	} else if (0 == rv) {
 | |
| 		/* a timeout elapsed */
 | |
| 		sqn_pr_warn("timeout elapsed - still no ack from card"
 | |
| 			", assume that card in sleep mode now\n");
 | |
| 		sqn_card->is_card_sleeps = 1;
 | |
| 	} else {
 | |
| 		/* we got an ack from card */
 | |
| 		sqn_pr_info("card in sleep mode now\n");
 | |
| 		sqn_card->is_card_sleeps = 1;
 | |
| 		rv = 0;
 | |
| 	}
 | |
| 
 | |
| 	sqn_card->pm_complete = 0;
 | |
| 	sqn_card->waiting_pm_notification = 0;
 | |
| 
 | |
| 	sqn_pr_leave();
 | |
| 
 | |
| 	return rv;
 | |
| }
 | |
| 
 | |
| 
 | |
| int sqn_notify_host_wakeup(struct sdio_func *func)
 | |
| {
 | |
| 	int rv = 0;
 | |
| 
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	rv = sqn_wakeup_fw(func);
 | |
| 
 | |
| 	sqn_pr_leave();
 | |
| 
 | |
| 	return rv;
 | |
| };
 | |
| 
 | |
| 
 | |
| static void handle_sqn_state_change_msg(struct sqn_private *priv
 | |
| 	, struct sqn_lsp_packet *lsp)
 | |
| {
 | |
| 	struct sqn_sdio_card *card = priv->card;
 | |
| 	struct sk_buff *skb_reply = 0;
 | |
| 	unsigned long irq_flags = 0;
 | |
| 	const int card_state = ntohl(lsp->lsp_header.u.fw_state.state);
 | |
| 
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	switch (card_state) {
 | |
| 	case LSP_SQN_ACTIVE:
 | |
| 		sqn_pr_info("card switched to ACTIVE state\n");
 | |
| 		spin_lock_irqsave(&priv->drv_lock, irq_flags);
 | |
| 		card->is_card_sleeps = 0;
 | |
| 		spin_unlock_irqrestore(&priv->drv_lock, irq_flags);
 | |
| 		break;
 | |
| 	case LSP_SQN_IDLE:
 | |
| 		sqn_pr_info("card switched to IDLE state\n");
 | |
| 		spin_lock_irqsave(&priv->drv_lock, irq_flags);
 | |
| 		card->is_card_sleeps = 1;
 | |
| 		spin_unlock_irqrestore(&priv->drv_lock, irq_flags);
 | |
| 		break;
 | |
| 	case LSP_SQN_DROPPED:
 | |
| 		sqn_pr_info("card switched to DROPPED state\n");
 | |
| 		spin_lock_irqsave(&priv->drv_lock, irq_flags);
 | |
| 		card->is_card_sleeps = 1;
 | |
| 		spin_unlock_irqrestore(&priv->drv_lock, irq_flags);
 | |
| 		break;
 | |
| 	case LSP_SQN_REENTRY:
 | |
| 		sqn_pr_info("card switched to REENTRY state\n");
 | |
| 		spin_lock_irqsave(&priv->drv_lock, irq_flags);
 | |
| 		card->is_card_sleeps = 1;
 | |
| 		spin_unlock_irqrestore(&priv->drv_lock, irq_flags);
 | |
| 		break;
 | |
| 	default:
 | |
| 		sqn_pr_info("card switched to UNSUPPORTED mode %d/0x%x\n"
 | |
| 			, card_state, card_state);
 | |
| 		spin_lock_irqsave(&priv->drv_lock, irq_flags);
 | |
| 		card->is_card_sleeps = 0;
 | |
| 		spin_unlock_irqrestore(&priv->drv_lock, irq_flags);
 | |
| 		break;
 | |
| 	}
 | |
| 	skb_reply = construct_lsp_packet(THSP_SQN_STATE_CHANGE_REPLY
 | |
| 			, ntohl(lsp->lsp_header.u.thp_avl.tid), 0);
 | |
| 	if (0 != (skb_reply = sqn_sdio_prepare_skb_for_tx(skb_reply)))
 | |
| 		sqn_sdio_tx_skb(card, skb_reply, 0);
 | |
| 	wake_up_interruptible(&g_card_sleep_waitq);
 | |
| 
 | |
| 	sqn_pr_leave();
 | |
| }
 | |
| 
 | |
| 
 | |
| static void handle_thp_avl_msg(struct sqn_private *priv
 | |
| 	, struct sqn_lsp_packet *lsp)
 | |
| {
 | |
| 	struct sqn_sdio_card *card = priv->card;
 | |
| 	struct sk_buff *skb_reply = 0;
 | |
| 	enum sqn_thp_available_reply thp_rpl; 
 | |
| 	unsigned long irq_flags = 0;
 | |
| 
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	spin_lock_irqsave(&priv->drv_lock, irq_flags);
 | |
| 	/* if (card->is_card_sleeps) { */
 | |
| 	if (priv->is_tx_queue_empty(priv)) {
 | |
| 		sqn_pr_dbg("TX queue empty, thp_rpl=FINISH\n");
 | |
| 		/* sqn_pr_dbg("card was asleep, thp_rpl=FINISH\n"); */
 | |
| 		thp_rpl = LSP_THPA_FINISHED;
 | |
| 		card->is_card_sleeps = 1;
 | |
| 	/* } else if (priv->is_tx_queue_empty(priv)) { */
 | |
| 		/* sqn_pr_dbg("card was not asleep and tx_queue is empty, thp_rpl=FINISHED\n"); */
 | |
| 		/* thp_rpl = LSP_THPA_FINISHED; */
 | |
| 		/* card->is_card_sleeps = 1; */
 | |
| 	} else {
 | |
| 		/* sqn_pr_info("card was not asleep but tx_queue is no empty, thp_rpl=EXIT\n"); */
 | |
| 		sqn_pr_dbg("TX queue not empty, thp_rpl=ACK\n");
 | |
| 		/* sqn_pr_dbg("card was not asleep, thp_rpl=ACK\n"); */
 | |
| 		thp_rpl = LSP_THPA_ACK;
 | |
| 		card->is_card_sleeps = 0;
 | |
| 	}
 | |
| 	spin_unlock_irqrestore(&priv->drv_lock, irq_flags);
 | |
| 	skb_reply = construct_lsp_packet(THSP_THP_AVAILABLE_REPLY
 | |
| 			, ntohl(lsp->lsp_header.u.thp_avl.tid)
 | |
| 			, thp_rpl);
 | |
| 	if (0 != (skb_reply = sqn_sdio_prepare_skb_for_tx(skb_reply)))
 | |
| 		sqn_sdio_tx_skb(card, skb_reply, 0);
 | |
| 	wake_up_interruptible(&g_card_sleep_waitq);
 | |
| 	if (netif_queue_stopped(priv->dev))
 | |
| 		netif_wake_queue(priv->dev);
 | |
| 
 | |
| 	sqn_pr_leave();
 | |
| }
 | |
| 
 | |
| 
 | |
| int sqn_handle_lsp_packet(struct sqn_private *priv
 | |
| 	, struct sk_buff *skb)
 | |
| {
 | |
| 	struct sqn_sdio_card *card = priv->card;
 | |
| 	unsigned long irq_flags = 0;
 | |
| 	struct sqn_lsp_packet *lsp_response = (struct sqn_lsp_packet*)
 | |
| 		((u8*)skb->data + sizeof(struct sqn_eth_header));
 | |
| 
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	if (!is_lsp_packet(skb)) {
 | |
| 		sqn_pr_dbg("not LSP packet\n");
 | |
| 		sqn_pr_leave();
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	sqn_pr_dbg("LSP packet\n");
 | |
| 
 | |
| 	switch (ntohl(lsp_response->lsp_header.id)) {
 | |
| 	case THSP_GET_MEDIA_CONNECTION_STATE:
 | |
| 		sqn_pr_dbg("id: THSP_GET_MEDIA_CONNECTION_STATE state=%xh\n"
 | |
| 			, ntohl(lsp_response->lsp_header.u.media_con_state));
 | |
| 		sqn_pr_warn("THSP_GET_MEDIA_CONNECTION_STATE not implemented\n");
 | |
| 		break;
 | |
| 	case THSP_MEDIA_CONNECTION_STATE_CHANGE:
 | |
| 		sqn_pr_dbg("id: THSP_MEDIA_CONNECTION_STATE_CHANGE state=%xh\n"
 | |
| 			, ntohl(lsp_response->lsp_header.u.media_con_state));
 | |
| 		if (THSP_MEDIA_CONNECTION_ATTACHED
 | |
| 			== ntohl(lsp_response->lsp_header.u.media_con_state))
 | |
| 		{
 | |
| 			
 | |
| #if IGNORE_CARRIER_STATE
 | |
|             /* netif_carrier_on(priv->dev); */
 | |
|             sqn_pr_info("WiMAX carrier PRESENT [ignored]\n");
 | |
| #else
 | |
|             netif_carrier_on(priv->dev);
 | |
| 			sqn_pr_info("WiMAX carrier PRESENT\n");
 | |
| #endif            
 | |
| 		} else {
 | |
| #if IGNORE_CARRIER_STATE
 | |
| 			/* netif_carrier_off(priv->dev); */
 | |
| 			sqn_pr_info("WiMAX carrier LOST [ignored]\n");
 | |
| #else
 | |
| 			netif_carrier_off(priv->dev);
 | |
| 			sqn_pr_info("WiMAX carrier LOST\n");
 | |
| #endif
 | |
| 		}
 | |
| 		break;
 | |
| 	case THSP_SET_HOST_POWER_MODE_ACK:
 | |
| 		sqn_pr_dbg("id: THSP_SET_HOST_POWER_MODE_ACK tid=0x%x\n"
 | |
| 			, ntohl(lsp_response->lsp_header.u.host_power.tid));
 | |
| 		free_last_request();
 | |
| 		spin_lock_irqsave(&priv->drv_lock, irq_flags);
 | |
| 		card->is_card_sleeps = 1;
 | |
| 		spin_unlock_irqrestore(&priv->drv_lock, irq_flags);
 | |
| 		signal_pm_request_completion(priv);
 | |
| 		break;
 | |
| 	case THSP_SET_FW_POWER_MODE_ACK:
 | |
| 		sqn_pr_dbg("id: THSP_SET_FW_POWER_MODE_ACK tid=0x%x\n"
 | |
| 			, ntohl(lsp_response->lsp_header.u.fw_power.tid));
 | |
| 		sqn_pr_dbg("THSP_SET_FW_POWER_MODE_ACK not implemented\n");
 | |
| 		break;
 | |
| 	case THSP_SQN_STATE_CHANGE:
 | |
| 		sqn_pr_dbg("id: THSP_SQN_STATE_CHANGE tid=0x%x, state=%xh\n"
 | |
| 			, ntohl(lsp_response->lsp_header.u.fw_state.tid)
 | |
| 			, ntohl(lsp_response->lsp_header.u.fw_state.state));
 | |
| 		handle_sqn_state_change_msg(priv, lsp_response);
 | |
| 		break;
 | |
| 	case THSP_THP_AVAILABLE:
 | |
| 		sqn_pr_dbg("id: THSP_THP_AVAILABLE tid=0x%x, reply=%xh\n"
 | |
| 			, ntohl(lsp_response->lsp_header.u.thp_avl.tid)
 | |
| 			, ntohl(lsp_response->lsp_header.u.thp_avl.reply));
 | |
| 		handle_thp_avl_msg(priv, lsp_response);
 | |
| 		break;
 | |
| 	default:
 | |
| 		sqn_pr_dbg("lsp_id: UNKNOWN=0x%x\n"
 | |
| 			, ntohl(lsp_response->lsp_header.id));
 | |
| 	}
 | |
| 
 | |
| 	dev_kfree_skb_any(skb);
 | |
| 	sqn_pr_leave();
 | |
| 
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| 
 | |
| int sqn_wakeup_fw(struct sdio_func *func)
 | |
| {
 | |
| 	int rv = 0;
 | |
| 	int ver = 0;
 | |
| 	int counter = 0;
 | |
|   
 | |
| 	int retry_cnt = 3;
 | |
| 	u32 wakeup_delay = 0;
 | |
| 	unsigned long timeout = msecs_to_jiffies(800);
 | |
| 
 | |
| 	unsigned long irq_flags = 0;
 | |
| 	struct sqn_private *priv = ((struct sqn_sdio_card *)sdio_get_drvdata(func))->priv;
 | |
| 	struct sqn_sdio_card *card = priv->card;
 | |
| 	u8 need_to_unlock_wakelock = 0;
 | |
| 
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	sqn_pr_info("waking up the card...\n");
 | |
| 
 | |
| 	if (!wake_lock_active(&card->wakelock)) {
 | |
| 		sqn_pr_dbg("lock wake_lock\n");
 | |
| 		wake_lock(&card->wakelock);
 | |
| 		need_to_unlock_wakelock = 1;
 | |
| 	}
 | |
| 
 | |
| retry:
 | |
| 	sdio_claim_host(func);
 | |
| 
 | |
| #define  SDIO_CCCR_CCCR_SDIO_VERSION_VALUE	0x11
 | |
| 
 | |
|     wakeup_delay = 2;
 | |
| 	counter = 5;
 | |
| 	do {
 | |
| 		ver = sdio_readb(func, SDIO_CCCR_CCCR_SDIO_VERSION, &rv);
 | |
| 		// To avoid FW sutck in PLLOFF, SDIO isn't able to wake up it.
 | |
| 		mdelay(wakeup_delay);
 | |
| 		--counter;
 | |
| 	} while((rv || ver != SDIO_CCCR_CCCR_SDIO_VERSION_VALUE) && counter > 0);
 | |
| 
 | |
| 	if (rv) {
 | |
| 		sqn_pr_err("error when reading SDIO_VERSION\n");
 | |
| 		sdio_release_host(func);
 | |
| 		goto out;
 | |
| 	} else {
 | |
| 		sqn_pr_dbg("SDIO_VERSION has been read successfully\n");
 | |
| 	}
 | |
| 
 | |
| 	sqn_pr_dbg("send wake-up signal to card\n");
 | |
| 	sdio_writeb(func, 1, SQN_SOC_SIGS_LSBS, &rv);
 | |
| 	if (rv)
 | |
| 		sqn_pr_err("error when writing to SQN_SOC_SIGS_LSBS: %d\n", rv);
 | |
| 
 | |
| 	sdio_release_host(func);
 | |
| 
 | |
| 	sqn_pr_info("wait for completion (timeout %d msec)...\n"
 | |
| 		, jiffies_to_msecs(timeout));
 | |
| 
 | |
| 	rv = wait_event_interruptible_timeout(g_card_sleep_waitq
 | |
| 		, 0 == card->is_card_sleeps, timeout);
 | |
| 
 | |
| 	if (-ERESTARTSYS == rv) {
 | |
| 		sqn_pr_warn("got a signal from kernel %d\n", rv);
 | |
| 	} else if (0 == rv) {
 | |
| 		rv = -1;
 | |
| 		sqn_pr_err("can't wake up the card - timeout elapsed\n");
 | |
| 
 | |
| 		if (retry_cnt-- > 0) {
 | |
| 			sqn_pr_info("retry wake up\n");
 | |
| 			goto retry;
 | |
|     	}
 | |
| 		sqn_pr_info("giving up to wake up the card\n");
 | |
| 
 | |
| 		spin_lock_irqsave(&priv->drv_lock, irq_flags);
 | |
| 		card->is_card_sleeps = 0;
 | |
| 		spin_unlock_irqrestore(&priv->drv_lock, irq_flags);
 | |
| 	} else {
 | |
| 		rv = 0;
 | |
| 		sqn_pr_info("card is waked up successfully\n");
 | |
| 	}
 | |
| 
 | |
| out:
 | |
| 	if (need_to_unlock_wakelock && wake_lock_active(&card->wakelock)) {
 | |
| 		sqn_pr_dbg("wake_lock is active, release it\n");
 | |
| 		wake_unlock(&card->wakelock);
 | |
| 	}
 | |
| 
 | |
| 	sqn_pr_leave();
 | |
| 	return rv;
 | |
| }
 | |
| 
 | |
| #ifdef ANDROID_KERNEL
 | |
| 
 | |
| extern u8 sqn_is_gpio_irq_enabled;
 | |
| 
 | |
| static void sqn_handle_android_early_suspend(struct early_suspend *h)
 | |
| {
 | |
| 	sqn_pr_enter();
 | |
| 	sqn_pr_info("%s: enter\n", __func__);
 | |
| 
 | |
| 	if (!sqn_is_gpio_irq_enabled) {
 | |
| 		sqn_pr_info("enable GPIO%d interrupt\n", mmc_wimax_get_hostwakeup_gpio());
 | |
| 		enable_irq(MSM_GPIO_TO_INT(mmc_wimax_get_hostwakeup_gpio()));
 | |
| 		enable_irq_wake(MSM_GPIO_TO_INT(mmc_wimax_get_hostwakeup_gpio()));
 | |
| 
 | |
| 		sqn_is_gpio_irq_enabled = 1;
 | |
| 	}
 | |
| 
 | |
| 	sqn_pr_info("%s: leave\n", __func__);
 | |
| 	sqn_pr_leave();
 | |
| }
 | |
| 
 | |
| 
 | |
| static void sqn_handle_android_late_resume(struct early_suspend *h)
 | |
| {
 | |
| 	sqn_pr_enter();
 | |
| 	sqn_pr_info("%s: enter\n", __func__);
 | |
| 
 | |
| 	if (sqn_is_gpio_irq_enabled) {
 | |
| 		sqn_pr_info("disable GPIO%d interrupt\n", (mmc_wimax_get_hostwakeup_gpio()));
 | |
| 		disable_irq_wake(MSM_GPIO_TO_INT(mmc_wimax_get_hostwakeup_gpio()));
 | |
| 		disable_irq(MSM_GPIO_TO_INT(mmc_wimax_get_hostwakeup_gpio()));
 | |
| 		sqn_is_gpio_irq_enabled = 0;
 | |
|     }
 | |
| 
 | |
| 	sqn_pr_info("%s: leave\n", __func__);
 | |
| 	sqn_pr_leave();
 | |
| }
 | |
| 
 | |
| 
 | |
| static struct early_suspend sqn_early_suspend_desc = {
 | |
|         .level = EARLY_SUSPEND_LEVEL_DISABLE_FB
 | |
|         , .suspend = sqn_handle_android_early_suspend
 | |
|         , .resume = sqn_handle_android_late_resume
 | |
| };
 | |
| 
 | |
| 
 | |
| void register_android_earlysuspend(void)
 | |
| {
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	register_early_suspend(&sqn_early_suspend_desc);
 | |
| 
 | |
| 	sqn_pr_leave();
 | |
| }
 | |
| 
 | |
| 
 | |
| void unregister_android_earlysuspend(void)
 | |
| {
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	unregister_early_suspend(&sqn_early_suspend_desc);
 | |
| 
 | |
| 	sqn_pr_leave();
 | |
| }
 | |
| #endif /* ANDROID_KERNEL */
 |