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);
|