title>
aboutsummaryrefslogblamecommitdiffstats
path: root/drivers/net/wireless/libertas_tf/main.c
blob: 6ab30033c26c07fff18b1c54a0fc5d1bd19cca82 (plain) (tree)















































































































































































































                                                                                     
                            











                                                                           
                                                     

































































































                                                                               
                                                  




                                             

                            

                                       

                                                  
                                    





                                                   
                                                     



                                                             
                                                  


                                             

                                                         





                                               
                                                               

                                             

                                                






                                                                




















                                                                                



                                                                
                                      


                                                










                                                                     
                                                         

                                                                      
                               

                                                                       




















                                                                         






















                                                                       







                                                                 








                                                           
                                                            







                                                           
                         
                           
                                  









                                                                  





                                                      
                                                

                                                        




                                                                    
 




                                                            

                                                               





































                                                                        


                                             







































                                                                           

                                             





                                                                             















                                                                  
                                                 












































                                                                                
/*
 *  Copyright (C) 2008, cozybit Inc.
 *  Copyright (C) 2003-2006, Marvell International Ltd.
 *
 *  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 "libertas_tf.h"
#include "linux/etherdevice.h"

#define DRIVER_RELEASE_VERSION "004.p0"
/* thinfirm version: 5.132.X.pX */
#define LBTF_FW_VER_MIN		0x05840300
#define LBTF_FW_VER_MAX		0x0584ffff
#define QOS_CONTROL_LEN		2

static const char lbtf_driver_version[] = "THINFIRM-USB8388-" DRIVER_RELEASE_VERSION;
struct workqueue_struct *lbtf_wq;

static const struct ieee80211_channel lbtf_channels[] = {
	{ .center_freq = 2412, .hw_value = 1 },
	{ .center_freq = 2417, .hw_value = 2 },
	{ .center_freq = 2422, .hw_value = 3 },
	{ .center_freq = 2427, .hw_value = 4 },
	{ .center_freq = 2432, .hw_value = 5 },
	{ .center_freq = 2437, .hw_value = 6 },
	{ .center_freq = 2442, .hw_value = 7 },
	{ .center_freq = 2447, .hw_value = 8 },
	{ .center_freq = 2452, .hw_value = 9 },
	{ .center_freq = 2457, .hw_value = 10 },
	{ .center_freq = 2462, .hw_value = 11 },
	{ .center_freq = 2467, .hw_value = 12 },
	{ .center_freq = 2472, .hw_value = 13 },
	{ .center_freq = 2484, .hw_value = 14 },
};

/* This table contains the hardware specific values for the modulation rates. */
static const struct ieee80211_rate lbtf_rates[] = {
	{ .bitrate = 10,
	  .hw_value = 0, },
	{ .bitrate = 20,
	  .hw_value = 1,
	  .flags = IEEE80211_RATE_SHORT_PREAMBLE },
	{ .bitrate = 55,
	  .hw_value = 2,
	  .flags = IEEE80211_RATE_SHORT_PREAMBLE },
	{ .bitrate = 110,
	  .hw_value = 3,
	  .flags = IEEE80211_RATE_SHORT_PREAMBLE },
	{ .bitrate = 60,
	  .hw_value = 5,
	  .flags = 0 },
	{ .bitrate = 90,
	  .hw_value = 6,
	  .flags = 0 },
	{ .bitrate = 120,
	  .hw_value = 7,
	  .flags = 0 },
	{ .bitrate = 180,
	  .hw_value = 8,
	  .flags = 0 },
	{ .bitrate = 240,
	  .hw_value = 9,
	  .flags = 0 },
	{ .bitrate = 360,
	  .hw_value = 10,
	  .flags = 0 },
	{ .bitrate = 480,
	  .hw_value = 11,
	  .flags = 0 },
	{ .bitrate = 540,
	  .hw_value = 12,
	  .flags = 0 },
};

static void lbtf_cmd_work(struct work_struct *work)
{
	struct lbtf_private *priv = container_of(work, struct lbtf_private,
					 cmd_work);
	spin_lock_irq(&priv->driver_lock);
	/* command response? */
	if (priv->cmd_response_rxed) {
		priv->cmd_response_rxed = 0;
		spin_unlock_irq(&priv->driver_lock);
		lbtf_process_rx_command(priv);
		spin_lock_irq(&priv->driver_lock);
	}

	if (priv->cmd_timed_out && priv->cur_cmd) {
		struct cmd_ctrl_node *cmdnode = priv->cur_cmd;

		if (++priv->nr_retries > 10) {
			lbtf_complete_command(priv, cmdnode,
					      -ETIMEDOUT);
			priv->nr_retries = 0;
		} else {
			priv->cur_cmd = NULL;

			/* Stick it back at the _top_ of the pending
			 * queue for immediate resubmission */
			list_add(&cmdnode->list, &priv->cmdpendingq);
		}
	}
	priv->cmd_timed_out = 0;
	spin_unlock_irq(&priv->driver_lock);

	if (!priv->fw_ready)
		return;
	/* Execute the next command */
	if (!priv->cur_cmd)
		lbtf_execute_next_command(priv);
}

/**
 *  lbtf_setup_firmware: initialize firmware.
 *
 *  @priv    A pointer to struct lbtf_private structure
 *
 *  Returns: 0 on success.
 */
static int lbtf_setup_firmware(struct lbtf_private *priv)
{
	int ret = -1;

	/*
	 * Read priv address from HW
	 */
	memset(priv->current_addr, 0xff, ETH_ALEN);
	ret = lbtf_update_hw_spec(priv);
	if (ret) {
		ret = -1;
		goto done;
	}

	lbtf_set_mac_control(priv);
	lbtf_set_radio_control(priv);

	ret = 0;
done:
	return ret;
}

/**
 *  This function handles the timeout of command sending.
 *  It will re-send the same command again.
 */
static void command_timer_fn(unsigned long data)
{
	struct lbtf_private *priv = (struct lbtf_private *)data;
	unsigned long flags;

	spin_lock_irqsave(&priv->driver_lock, flags);

	if (!priv->cur_cmd) {
		printk(KERN_DEBUG "libertastf: command timer expired; "
				  "no pending command\n");
		goto out;
	}

	printk(KERN_DEBUG "libertas: command %x timed out\n",
		le16_to_cpu(priv->cur_cmd->cmdbuf->command));

	priv->cmd_timed_out = 1;
	queue_work(lbtf_wq, &priv->cmd_work);
out:
	spin_unlock_irqrestore(&priv->driver_lock, flags);
}

static int lbtf_init_adapter(struct lbtf_private *priv)
{
	memset(priv->current_addr, 0xff, ETH_ALEN);
	mutex_init(&priv->lock);

	priv->vif = NULL;
	setup_timer(&priv->command_timer, command_timer_fn,
		(unsigned long)priv);

	INIT_LIST_HEAD(&priv->cmdfreeq);
	INIT_LIST_HEAD(&priv->cmdpendingq);

	spin_lock_init(&priv->driver_lock);

	/* Allocate the command buffers */
	if (lbtf_allocate_cmd_buffer(priv))
		return -1;

	return 0;
}

static void lbtf_free_adapter(struct lbtf_private *priv)
{
	lbtf_free_cmd_buffer(priv);
	del_timer(&priv->command_timer);
}

static int lbtf_op_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
{
	struct lbtf_private *priv = hw->priv;

	priv->skb_to_tx = skb;
	queue_work(lbtf_wq, &priv->tx_work);
	/*
	 * queue will be restarted when we receive transmission feedback if
	 * there are no buffered multicast frames to send
	 */
	ieee80211_stop_queues(priv->hw);
	return NETDEV_TX_OK;
}

static void lbtf_tx_work(struct work_struct *work)
{
	struct lbtf_private *priv = container_of(work, struct lbtf_private,
					 tx_work);
	unsigned int len;
	struct ieee80211_tx_info *info;
	struct txpd *txpd;
	struct sk_buff *skb = NULL;
	int err;

	if ((priv->vif->type == NL80211_IFTYPE_AP) &&
	    (!skb_queue_empty(&priv->bc_ps_buf)))
		skb = skb_dequeue(&priv->bc_ps_buf);
	else if (priv->skb_to_tx) {
		skb = priv->skb_to_tx;
		priv->skb_to_tx = NULL;
	} else
		return;

	len = skb->len;
	info  = IEEE80211_SKB_CB(skb);
	txpd = (struct txpd *)  skb_push(skb, sizeof(struct txpd));

	if (priv->surpriseremoved) {
		dev_kfree_skb_any(skb);
		return;
	}

	memset(txpd, 0, sizeof(struct txpd));
	/* Activate per-packet rate selection */
	txpd->tx_control |= cpu_to_le32(MRVL_PER_PACKET_RATE |
			     ieee80211_get_tx_rate(priv->hw, info)->hw_value);

	/* copy destination address from 802.11 header */
	memcpy(txpd->tx_dest_addr_high, skb->data + sizeof(struct txpd) + 4,
		ETH_ALEN);
	txpd->tx_packet_length = cpu_to_le16(len);
	txpd->tx_packet_location = cpu_to_le32(sizeof(struct txpd));
	BUG_ON(priv->tx_skb);
	spin_lock_irq(&priv->driver_lock);
	priv->tx_skb = skb;
	err = priv->hw_host_to_card(priv, MVMS_DAT, skb->data, skb->len);
	spin_unlock_irq(&priv->driver_lock);
	if (err) {
		dev_kfree_skb_any(skb);
		priv->tx_skb = NULL;
	}
}

static int lbtf_op_start(struct ieee80211_hw *hw)
{
	struct lbtf_private *priv = hw->priv;
	void *card = priv->card;
	int ret = -1;

	if (!priv->fw_ready)
		/* Upload firmware */
		if (priv->hw_prog_firmware(card))
			goto err_prog_firmware;

	/* poke the firmware */
	priv->capability = WLAN_CAPABILITY_SHORT_PREAMBLE;
	priv->radioon = RADIO_ON;
	priv->mac_control = CMD_ACT_MAC_RX_ON | CMD_ACT_MAC_TX_ON;
	ret = lbtf_setup_firmware(priv);
	if (ret)
		goto err_prog_firmware;

	if ((priv->fwrelease < LBTF_FW_VER_MIN) ||
	    (priv->fwrelease > LBTF_FW_VER_MAX)) {
		ret = -1;
		goto err_prog_firmware;
	}

	printk(KERN_INFO "libertastf: Marvell WLAN 802.11 thinfirm adapter\n");
	return 0;

err_prog_firmware:
	priv->hw_reset_device(card);
	return ret;
}

static void lbtf_op_stop(struct ieee80211_hw *hw)
{
	struct lbtf_private *priv = hw->priv;
	unsigned long flags;
	struct sk_buff *skb;

	struct cmd_ctrl_node *cmdnode;
	/* Flush pending command nodes */
	spin_lock_irqsave(&priv->driver_lock, flags);