/*
 *  Copyright (C) 2002 Intersil Americas Inc.
 *  Copyright (C) 2003 Herbert Valerio Riedel <hvr@gnu.org>
 *  Copyright (C) 2003 Luis R. Rodriguez <mcgrof@ruslug.rutgers.edu>
 *  Copyright (C) 2005 Jean-Baptiste Note <jean-baptiste.note@m4x.org>
 *
 *  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
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <linux/version.h>
#include <linux/module.h>

#include <linux/netdevice.h>
#include <linux/pci.h>
#include <linux/etherdevice.h>
#include <linux/delay.h>
#include <linux/if_arp.h>
#include <linux/firmware.h>
#include <linux/interrupt.h>

#include <asm/io.h>

#include "isl_38xx.h"
#include "islpci_dev.h"
#include "isl_sm.h"
#include "islsm_protocol.h"

#ifdef PCIUART
#define ISL_PCI 1
#include "islsm_uartpci.h"
#endif

// get rid of this
//#include "islpci_eth.h"

#define ISL3877_IMAGE_FILE	"isl3877"
#define ISL3886_IMAGE_FILE	"isl3886"
#define ISL3890_IMAGE_FILE	"isl3890"

static int prism54_bring_down(islpci_private *);
static int islpci_alloc_memory(islpci_private *);
static int islpci_fill_rxqueue(islpci_private *priv, unsigned queue_ref);

/* Temporary dummy MAC address to use until firmware is loaded.
 * The idea there is that some tools (such as nameif) may query
 * the MAC address before the netdev is 'open'. By using a valid
 * OUI prefix, they can process the netdev properly.
 * Of course, this is not the final/real MAC address. It doesn't
 * matter, as you are suppose to be able to change it anytime via
 * ndev->set_mac_address. Jean II */
const unsigned char dummy_mac[6] = { 0x00, 0x30, 0xB4, 0x00, 0x00, 0x00 };

/********
 * Exported low-level functions
 ********/

/* Some of these functions should be shared with USB+PCI in a common
 * header for instance */

/* reboot the device into rom (ramboot bit clear),
   (address 0xe0000000) */
int
islpci_romboot(islpci_private *priv)
{
	u32 reg;
	void __iomem *device_base = priv->device_base;

	/* disable all device interrupts in case they weren't */
	isl38xx_disable_interrupts(priv->device_base);

	/* clear the RAMBoot and the Reset bit */
	reg = readl(device_base + ISL38XX_CTRL_STAT_REG);
	reg &= ~ISL38XX_CTRL_STAT_RESET;
	reg &= ~ISL38XX_CTRL_STAT_RAMBOOT;
	writel(reg, device_base + ISL38XX_CTRL_STAT_REG);
	wmb();
	udelay(ISL38XX_WRITEIO_DELAY);

	/* set the Reset bit without reading the register ! */
	reg |= ISL38XX_CTRL_STAT_RESET;
	writel(reg, device_base + ISL38XX_CTRL_STAT_REG);
	wmb();
	udelay(ISL38XX_WRITEIO_DELAY);

	/* clear the Reset bit */
	reg &= ~ISL38XX_CTRL_STAT_RESET;
	writel(reg, device_base + ISL38XX_CTRL_STAT_REG);
	wmb();

	/* wait a while for the device to reboot */
	mdelay(50);

	/* in case of a ramboot, we actually do more work before
	   //reenableing interrupts :
	   //isl38xx_interface_reset(priv->device_base, priv->device_host_address);
	 */

	/* reenable interrupts */
	isl38xx_enable_common_interrupts(priv->device_base);

	return 0;
}

/* reboot the device into ram (ramboot bit set),
   (address 0x00000000)*/
static int
islpci_ramboot(islpci_private *priv)
{
	u32 reg;
	void __iomem *device_base = priv->device_base;

	/* now reset the device
	 * clear the Reset & ClkRun bit, set the RAMBoot bit */
	reg = readl(device_base + ISL38XX_CTRL_STAT_REG);
	reg &= ~ISL38XX_CTRL_STAT_CLKRUN;
	reg &= ~ISL38XX_CTRL_STAT_RESET;
	reg |= ISL38XX_CTRL_STAT_RAMBOOT;
	isl38xx_w32_flush(device_base, reg, ISL38XX_CTRL_STAT_REG);
	wmb();
	udelay(ISL38XX_WRITEIO_DELAY);

	/* set the reset bit latches the host override and RAMBoot bits
	 * into the device for operation when the reset bit is reset */
	reg |= ISL38XX_CTRL_STAT_RESET;
	writel(reg, device_base + ISL38XX_CTRL_STAT_REG);
	/* don't do flush PCI posting here! */
	wmb();
	udelay(ISL38XX_WRITEIO_DELAY);

	/* clear the reset bit should start the whole circus */
	reg &= ~ISL38XX_CTRL_STAT_RESET;
	writel(reg, device_base + ISL38XX_CTRL_STAT_REG);
	/* don't do flush PCI posting here! */
	wmb();
	udelay(ISL38XX_WRITEIO_DELAY);

	return 0;
}

static int
isl_upload_firmware(islpci_private *priv)
{
	u32 reg;
	void __iomem *device_base = priv->device_base;
	struct islsm *islsm = ISLSM_OF_NETDEV(priv->ndev);
	int err;

	islpci_romboot(priv);

	{
		const struct firmware *fw_entry = NULL;
		long fw_len;
		const u32 *fw_ptr;

		err = islsm_request_firmware(&fw_entry, islsm);
		if (err)
			return err;

		/* send the firmware */
		/* prepare the Direct Memory Base register */
		reg = ISL38XX_DEV_FIRMWARE_ADDRES;

		fw_ptr = (u32 *) fw_entry->data;
		fw_len = fw_entry->size;

		if (fw_len % 4) {
			printk(KERN_ERR
			       "%s: firmware '%s' size is not multiple of 32bit, aborting!\n",
			       DRV_NAME, islsm->firmware_name);
			release_firmware(fw_entry);
			return -EILSEQ;	/* Illegal byte sequence  */
		}

		while (fw_len > 0) {
			long _fw_len =
			    (fw_len >
			     ISL38XX_MEMORY_WINDOW_SIZE) ?
			    ISL38XX_MEMORY_WINDOW_SIZE : fw_len;
			u32 __iomem *dev_fw_ptr =
			    device_base + ISL38XX_DIRECT_MEM_WIN;

			/* set the cards base address for writing the data */
			isl38xx_w32_flush(device_base, reg,
					  ISL38XX_DIR_MEM_BASE_REG);
			wmb();	/* be paranoid */

			/* increment the write address for next iteration */
			reg += _fw_len;
			fw_len -= _fw_len;

			/* write the data to the Direct Memory Window 32bit-wise */
			/* memcpy_toio() doesn't guarantee 32bit writes :-| */
			while (_fw_len > 0) {
				/* use non-swapping writel() */
				__raw_writel(*fw_ptr, dev_fw_ptr);
				fw_ptr++, dev_fw_ptr++;
				_fw_len -= 4;
			}

			/* flush PCI posting */
			(void) readl(device_base + ISL38XX_PCI_POSTING_FLUSH);
			wmb();	/* be paranoid again */

			BUG_ON(_fw_len != 0);
		}

		BUG_ON(fw_len != 0);
		release_firmware(fw_entry);
	}

	islpci_ramboot(priv);

	return 0;
}

static int
isl_load_fw(struct islsm *device, const struct firmware *fw)
{
	islpci_private *priv = ISLPCI_OF_ISLSM(device);

	/* full, complete reset. A wee bit too much maybe for freemac */
	return islpci_reset(priv, 1);
	/* this seems sufficient; ie we don't need to disable interrupts
	   nor initiate the reset request */
//      return isl_upload_firmware(priv);
}

int
isl_rombot(struct islsm *device)
{
	isl_reboot(device, 0);
	return 0;
}

/******************************************************************************
    Device Queue Management
******************************************************************************/
static
    void
islpci_frag_rx(islpci_private *priv,
	       struct islpci_membuf *membuf, isl38xx_fragment *frag)
{
	u32 addr_offset;
	unsigned size;
	struct sk_buff *skb = membuf->skb;

	pci_unmap_single(priv->pdev, membuf->pci_addr,
			 membuf->length, PCI_DMA_FROMDEVICE);

	/*
	 * Check if the LMAC has updated the host address pointer
	 * This leads to an offset which has to be reflected.
	 */
	addr_offset = le32_to_cpu(frag->address) - membuf->pci_addr;
	size = le16_to_cpu(frag->size);

	/*
	 * Update the skb data accordingly
	 */
	/* we didn't allocate enough room */
	if (size > ISLPCI_RX_SIZE) {
		printk(KERN_CRIT
		       "%s: not enough room for rx buffer (needed %i)\n",
		       DRV_NAME, size);
		BUG();
	}
	skb_reserve(skb, addr_offset);
	skb_put(skb, size);

	/*
	 * Send the data to the softmac layer
	 */
	islsm_data_input(skb);

	membuf->skb = (struct sk_buff *) 0;

	return;
}

static int
islpci_rx_queue(islpci_private *priv, unsigned queue_ref)
{
	isl38xx_control_block *cb =
	    (isl38xx_control_block *) priv->control_block;
	struct islpci_queue *queue = &priv->queues[queue_ref];
	unsigned int curr_frag;

	/* take this only once, avoiding endless loop */
	curr_frag = le32_to_cpu(cb->device_curr_frag[queue_ref]);
	barrier();

	BUG_ON(queue->index > curr_frag);
	while (queue->index < curr_frag) {
		/* race window is still here -- the interrupt and
		   another tasklet can hit before we increase the index
		   pointer, leading to the same skb being treated twice.
		   I now also have doubts about the frame exchange
		   protocol: what if the firmware rewrites the CB (does
		   it -- check this) */
		unsigned index = queue->index % queue->len;
		isl38xx_fragment *frag = &queue->cb_queue[index];
		struct islpci_membuf *membuf = &queue->ringbuf[index];
		queue->index++;
		wmb();
		islpci_frag_rx(priv, membuf, frag);
	}
	return 0;
}

/*
static inline void sm_drv_pci_trigger_device(struct pci_hif_local_data *hif_lp, int event)
{
    writel(event, (hif_lp->remapped_device_base + DEV38XX_DEV_INT_REG));
}
*/
static inline void
islpci_trigger(islpci_private *priv)
{
	u32 irq;
	priv->needs_trigger = 0;
	irq = ISL38XX_DEV_INT_UPDATE;
	FN_ENTER;

	/* In AP mode, the LMAC should never go to sleep */
//      irq |= DEV38XX_DEV_INT_SLEEP;
/* 	isl38xx_w32_flush(device_base, ISL38XX_DEV_INT_UPDATE, */
/* 			  ISL38XX_DEV_INT_REG); */
	// The XH driver only does this
//      writel(irq, priv->device_base  + ISL38XX_DEV_INT_REG);
	isl38xx_trigger_device(islpci_get_state(priv) == PRV_STATE_SLEEP,
			       priv->device_base);
	FN_EXIT0;
}

/* RX BH function */
void
rx_tasklet(unsigned long data)
{
/* 	unsigned long flags; */
	islpci_private *priv = (islpci_private *) data;

	FN_ENTER;

/* 	spin_lock_irqsave(&priv->rxqueue_lock, flags); */

	islpci_rx_queue(priv, ISL38XX_CB_RX_MGMTQ);
	islpci_fill_rxqueue(priv, ISL38XX_CB_RX_MGMTQ);

	islpci_rx_queue(priv, ISL38XX_CB_RX_DATA_LQ);
	islpci_fill_rxqueue(priv, ISL38XX_CB_RX_DATA_LQ);

	if (priv->needs_trigger)
		islpci_trigger(priv);

/* 	spin_unlock_irqrestore(&priv->rxqueue_lock, flags); */

	FN_EXIT0;
}

static
    void
islpci_frag_txed(islpci_private *priv,
		 struct islpci_membuf *membuf, isl38xx_fragment *frag)
{
	struct sk_buff *skb = membuf->skb;

	FN_ENTER;

	/* check for holes in the arrays caused by multi fragment frames
	 * searching for the last fragment of a frame */

	/* We don't support fragmentation for now. bug on this. */
	BUG_ON(membuf->pci_addr == (dma_addr_t) 0);

	if (membuf->pci_addr != (dma_addr_t) 0) {
		/* entry is the last fragment of a frame
		 * free the skb structure and unmap pci memory */
		islog(L_DEBUG,
		      "cleanup skb %p skb->data %p skb->len %u truesize %u\n",
		      skb, skb->data, skb->len, skb->truesize);

		pci_unmap_single(priv->pdev,
				 membuf->pci_addr,
				 membuf->length, PCI_DMA_TODEVICE);
		islsm_txskb_free(skb);
	}

	membuf->skb = NULL;
	membuf->pci_addr = 0;

	FN_EXIT0;
}

static void
islpci_tx_queue(islpci_private *priv, unsigned queue_ref)
{
	isl38xx_control_block *cb =
	    (isl38xx_control_block *) priv->control_block;
	struct islpci_queue *queue = &priv->queues[queue_ref];
	unsigned int curr_frag = le32_to_cpu(cb->device_curr_frag[queue_ref]);

	FN_ENTER;

	BUG_ON(queue->index > curr_frag);
	/* compare the control block read pointer with the free pointer */
	while (queue->index < curr_frag) {
		/* read the index of the first fragment to be freed */
		unsigned index = queue->index % queue->len;
		isl38xx_fragment *frag = &queue->cb_queue[index];
		struct islpci_membuf *membuf = &queue->ringbuf[index];

		islpci_frag_txed(priv, membuf, frag);
		queue->index++;
	}

	FN_EXIT0;
}

int
islpci_tx_submit(struct sk_buff *skb)
{
	struct net_device *netdev = skb->dev;
	islpci_private *priv = ISLPCI_OF_NETDEV(netdev);
	isl38xx_control_block *cb = priv->control_block;

	/* We choose here which queue they go to */
	unsigned int queue_ref = ISL38XX_CB_TX_DATA_LQ;
	struct islpci_queue *queue = &priv->queues[queue_ref];

	struct islpci_membuf *membuf;

	u32 index;
	dma_addr_t pci_map_address;
	isl38xx_fragment *fragment;
	unsigned long flags;
	u32 curr_frag;
	int err = 0;

	void *data = skb->data;
	unsigned frame_size = skb->len;

	FN_ENTER;

	islsm_txdata_debug(queue_ref, data, frame_size);

	/* lock the driver code */
	spin_lock_irqsave(&priv->slock, flags);

	/* determine the amount of fragments needed to store the frame */

	/* if skb->len is < ETH_ZLEN, don't just set the skb->len to
	 * ETH_ZLEN as you can potentially leak up to 60 bytes of
	 * kernel buffer space into packets during transmit. If
	 * we really need to send at least ETH_ZLEN size frames,
	 * I suggest we zero-pack the rest of skb, and then set
	 * the size to ETH_ZLEN. For now, it seems the device can handle
	 * smaller frames just fine. */

	/* check whether the destination queue has enough fragments for
	 * the frame */
	curr_frag = le32_to_cpu(cb->driver_curr_frag[queue_ref]);

	if (unlikely(curr_frag - queue->index >= queue->len)) {
		printk(KERN_ERR
		       "%s: transmit device queue full when awake\n",
		       netdev->name);
		/* TODO : Good idea ! do this for USB Too ! */
/* 		netif_stop_queue(ndev); */

		/* trigger the device */
		isl38xx_w32_flush(priv->device_base,
				  ISL38XX_DEV_INT_UPDATE, ISL38XX_DEV_INT_REG);
		udelay(ISL38XX_WRITEIO_DELAY);

		err = -EBUSY;
		goto drop;
	}

	/* Alignment should be done by the softmac and OK at this point */
	BUG_ON(((long) data & 0x03));

	/* map the skb buffer to pci memory for DMA operation */
	pci_map_address = pci_map_single(priv->pdev, data, frame_size,
					 PCI_DMA_TODEVICE);
	if (unlikely(pci_map_address == 0)) {
		printk(KERN_WARNING "%s: cannot map buffer to PCI\n",
		       netdev->name);

		err = -EIO;
		goto drop;
	}

	index = curr_frag % queue->len;
	fragment = &queue->cb_queue[index];
	membuf = &queue->ringbuf[index];

	/* store mbuf information */
	membuf->pci_addr = pci_map_address;
	membuf->mem = data;
	membuf->skb = skb;

	/* set the proper fragment start address and size information */
	fragment->size = cpu_to_le16(frame_size);
	fragment->flags = cpu_to_le16(0);	/* set to 1 if more fragments */
	fragment->address = cpu_to_le32(pci_map_address);
	fragment->lmac_address = cpu_to_le32(LMAC_ADDR_OF_SKB(skb));

	curr_frag++;

	/* The fragment address in the control block must have been
	 * written before announcing the frame buffer to device. */

	wmb();
	/* Place the fragment in the control block structure. */
	cb->driver_curr_frag[queue_ref] = cpu_to_le32(curr_frag);

	/* Will be problematic for mgmt queues */
/* 	if (curr_frag - priv->free_data_tx + ISL38XX_MIN_QTHRESHOLD */
/* 	    > ISL38XX_CB_TX_DATA_QSIZE) { */
/* 		/\* stop sends from upper layers *\/ */
/* 		netif_stop_queue(ndev); */

/* 		/\* set the full flag for the transmission queue *\/ */
/* 		priv->data_low_tx_full = 1; */
/* 	} */

	priv->needs_trigger = 1;
	tasklet_schedule(&priv->rx_task);

      drop:
	/* unlock the driver code */
	spin_unlock_irqrestore(&priv->slock, flags);
	FN_EXIT1(err);
	return err;
}

/******************************************************************************
    Device Interrupt Handler
******************************************************************************/
static
    u32
check_and_ack_irqs(void __iomem *device)
{
	u32 reg;
	/* received an interrupt request on a shared IRQ line
	 * first check whether the device is in sleep mode */

	/* check whether there is any source of interrupt on the device */
	reg = readl(device + ISL38XX_INT_IDENT_REG);

	/* also check the contents of the Interrupt Enable Register, because this
	 * will filter out interrupt sources from other devices on the same irq ! */
	reg &= readl(device + ISL38XX_INT_EN_REG);
	reg &= ISL38XX_INT_SOURCES;

	islog(L_IRQ,
	      "%s: IRQ Identification register 0x%p 0x%x\n",
	      DRV_NAME, device, reg);

	return reg;
}

irqreturn_t
islpci_interrupt(int irq, void *config, struct pt_regs * regs)
{
	islpci_private *priv = config;
	struct net_device *ndev = priv->ndev;
	struct islsm *islsm = ISLSM_OF_NETDEV(ndev);
	int powerstate;
	u32 irqs;

	/* lock the interrupt handler */
	spin_lock(&priv->slock);

	irqs = check_and_ack_irqs(priv->device_base);
	if (!irqs)
		goto out_none;

	/* reset the request bits in the Identification register */
	isl38xx_w32_flush(priv->device_base, irqs, ISL38XX_INT_ACK_REG);

	islog(L_IRQ, "%s: irqs 0x%08x, state %u\n",
	      DRV_NAME, irqs, priv->state);

	if (islpci_get_state(priv) != PRV_STATE_SLEEP)
		powerstate = ISL38XX_PSM_ACTIVE_STATE;
	else
		powerstate = ISL38XX_PSM_POWERSAVE_STATE;

	/* check for each bit in the register separately */
	if (irqs & ISL38XX_INT_IDENT_UPDATE) {
		/* Queue has been updated */
		islog(L_IRQ, "IRQ: Update flag\n");

		islog(L_IRQ, "CB drv Qs: [%i][%i][%i][%i]\n",
		      le32_to_cpu(priv->control_block->
				  driver_curr_frag[0]),
		      le32_to_cpu(priv->control_block->
				  driver_curr_frag[1]),
		      le32_to_cpu(priv->control_block->
				  driver_curr_frag[2]),
		      le32_to_cpu(priv->control_block->driver_curr_frag[3]));

		islog(L_IRQ, "CB dev Qs: [%i][%i][%i][%i]\n",
		      le32_to_cpu(priv->control_block->
				  device_curr_frag[0]),
		      le32_to_cpu(priv->control_block->
				  device_curr_frag[1]),
		      le32_to_cpu(priv->control_block->
				  device_curr_frag[2]),
		      le32_to_cpu(priv->control_block->device_curr_frag[3]));

		/* flush the Control Block in the d-cache */
		/* This may be needed in the hardmac driver for
		   correcting the mgmt timeouts */
/* 			flush = pci_map_single( priv->pdev, priv->driver_mem_address, */
/* 						sizeof(isl38xx_control_block), PCI_DMA_FROMDEVICE ); */

		/* cleanup the tx queues */
		islpci_tx_queue(priv, ISL38XX_CB_TX_MGMTQ);
		islpci_tx_queue(priv, ISL38XX_CB_TX_DATA_LQ);

		/* device is in active state, update the
		 * powerstate flag if necessary */
		powerstate = ISL38XX_PSM_ACTIVE_STATE;

		/* This should be in bh ? */
		tasklet_schedule(&priv->rx_task);

		/* unmap the Control Block memory */
/* 			pci_unmap_single( priv->pdev, flush, */
/* 					  sizeof(isl38xx_control_block), PCI_DMA_FROMDEVICE ); */

		/* TODO -- in usb too ? */
		/* check whether the data transmit queues were full */
/* 			if (priv->data_low_tx_full) { */
/* 				/\* check whether the transmit is not full anymore *\/ */
/* 				if (ISL38XX_CB_TX_DATA_QSIZE - */
/* 				    isl38xx_in_queue(priv->control_block, */
/* 						     ISL38XX_CB_TX_DATA_LQ) >= */
/* 				    ISL38XX_MIN_QTHRESHOLD) { */
/* 					/\* nope, the driver is ready for more network frames *\/ */
/* 					// What to do with madwifi here ? */
/* 					//netif_wake_queue(priv->ndev); */

/* 					/\* reset the full flag *\/ */
/* 					priv->data_low_tx_full = 0; */
/* 				} */
/* 			} */
		irqs &= ~ISL38XX_INT_IDENT_UPDATE;
		if (!irqs)
			goto out_handled;
	}

	if (irqs & ISL38XX_INT_IDENT_SLEEP) {
		/* Device intends to move to powersave state */
		islog(L_IRQ, "IRQ: Sleep flag, skipping\n");

		// This checks whether the queues are empty. Bound to
		// fail ?
/* 		isl38xx_handle_sleep_request(priv->control_block, */
/* 					     &powerstate, */
/* 					     priv->device_base); */

		irqs &= ~ISL38XX_INT_IDENT_SLEEP;
		if (!irqs)
			goto out_handled;
	}

	if (irqs & ISL38XX_INT_IDENT_WAKEUP) {
		/* Device has been woken up to active state */
		islog(L_IRQ, "IRQ: Wakeup flag\n");

		isl38xx_handle_wakeup(priv->control_block,
				      &powerstate, priv->device_base);

		irqs &= ~ISL38XX_INT_IDENT_WAKEUP;
		if (!irqs)
			goto out_handled;
	}

	/* least used */
	if (irqs & ISL38XX_INT_IDENT_INIT) {
		/* Device has been initialized */
		islog(L_IRQ, "IRQ: Init flag, device initialized\n");
		complete(&islsm->dev_init_comp);
		irqs &= ~ISL38XX_INT_IDENT_INIT;
		if (!irqs)
			goto out_handled;
	}
#ifdef PCIUART
	/* normally the spinlock protects us */
	if (irqs & ISL38XX_DEV_INT_PCIUART_CTS) {
		islog(L_IRQ, "IRQ: UARTPCI CTS\n");
		uart_cts(islsm);
	}

	if (irqs & ISL38XX_DEV_INT_PCIUART_DR) {
		islog(L_IRQ, "IRQ: UARTPCI DR\n");
		uart_dr(islsm);
	}
#endif

/*
#if VERBOSE > SHOW_ERROR_MESSAGES
	DEBUG(SHOW_TRACING, "Assuming someone else called the IRQ\n");
#endif
	spin_unlock(&priv->slock);
	return IRQ_NONE;
*/

	/* sleep -> ready */
	if (islpci_get_state(priv) == PRV_STATE_SLEEP
	    && powerstate == ISL38XX_PSM_ACTIVE_STATE)
		islpci_set_state(priv, PRV_STATE_READY);

	/* !sleep -> sleep */
	if (islpci_get_state(priv) != PRV_STATE_SLEEP
	    && powerstate == ISL38XX_PSM_POWERSAVE_STATE)
		islpci_set_state(priv, PRV_STATE_SLEEP);

      out_handled:
	/* unlock the interrupt handler */
	spin_unlock(&priv->slock);
	return IRQ_HANDLED;

      out_none:
	islog(L_IRQ, "%s: Assuming someone else called the IRQ\n", DRV_NAME);
	spin_unlock(&priv->slock);
	return IRQ_NONE;
}

/******************************************************************************
    Network Interface Control & Statistical functions
******************************************************************************/
int
islpci_boot(struct islsm *islsm)
{
	u32 rc;
	islpci_private *priv = ISLPCI_OF_ISLSM(islsm);

	if (islpci_get_state(priv) == PRV_STATE_READY ||	/* driver&device are in operational state */
	    islpci_get_state(priv) == PRV_STATE_SLEEP)	/* device in sleep mode */
		return 0;

	/* reset data structures, upload firmware and reset device */
	rc = islpci_reset(priv, 1);
	if (rc) {
		prism54_bring_down(priv);
		return rc;	/* Returns informative message */
	}

	return 0;
}

static int
prism54_bring_down(islpci_private *priv)
{
	/* we are going to shutdown the device */
	islpci_set_state(priv, PRV_STATE_PREBOOT);

	/* disable all device interrupts in case they weren't */
	isl38xx_disable_interrupts(priv->device_base);

	/* For safety reasons, we may want to ensure that no DMA transfer is
	 * currently in progress by emptying the TX and RX queues. */

	/* wait until interrupts have finished executing on other CPUs */
	synchronize_irq(priv->pdev->irq);

	islpci_romboot(priv);

	return 0;
}

static int
islpci_upload_fw(islpci_private *priv)
{
	struct islsm *islsm = ISLSM_OF_NETDEV(priv->ndev);
	islpci_state_t old_state;
	u32 rc;

	old_state = islpci_set_state(priv, PRV_STATE_BOOT);

	islog(L_FW, "%s: uploading firmware...\n", priv->ndev->name);

	rc = isl_upload_firmware(priv);
	if (rc) {
		/* error uploading the firmware */
		printk(KERN_ERR "%s: could not upload firmware ('%s')\n",
		       priv->ndev->name, islsm->firmware_name);

		islpci_set_state(priv, old_state);
		return rc;
	}

	islog(L_FW, "%s: firmware upload complete\n", priv->ndev->name);

	islpci_set_state(priv, PRV_STATE_POSTBOOT);

	return 0;
}

static int
islpci_reset_if(islpci_private *priv)
{
	struct net_device *ndev = priv->ndev;
	struct islsm *islsm = ISLSM_OF_NETDEV(ndev);
	int result = -ETIME;
	int count;

	/* now the last step is to reset the interface */
	isl38xx_interface_reset(priv->device_base, priv->device_host_address);
	islpci_set_state(priv, PRV_STATE_PREINIT);

	for (count = 0; count < 2 && result; count++) {
		/* The software reset acknowledge needs about 220 msec here.
		 * Be conservative and wait for up to one second. */

		if (!islsm_wait_timeout(islsm, HZ)) {
			result = 0;
			break;
		}

		/* If we're here it's because our IRQ hasn't yet gone through.
		 * Retry a bit more...
		 */
		printk(KERN_ERR
		       "%s: no 'reset complete' IRQ seen - retrying\n",
		       priv->ndev->name);
	}

	if (result) {
		printk(KERN_ERR "%s: interface reset failure\n",
		       priv->ndev->name);
		return result;
	}

	islpci_set_state(priv, PRV_STATE_INIT);

	// FIXME : they only allow the init interrupt ??? Bad, USB shows
	// other interrupts can come firts !

	/* Now that the device is 100% up, let's allow
	 * for the other interrupts --
	 * NOTE: this is not *yet* true since we've only allowed the
	 * INIT interrupt on the IRQ line. We can perhaps poll
	 * the IRQ line until we know for sure the reset went through */
	isl38xx_enable_common_interrupts(priv->device_base);

	islpci_set_state(priv, PRV_STATE_READY);

	islog(L_DEBUG, "%s: interface reset complete\n", priv->ndev->name);
	return 0;
}

int
islpci_reset(islpci_private *priv, int reload_firmware)
{
	isl38xx_control_block *cb =	/* volatile not needed */
	    (isl38xx_control_block *) priv->control_block;
	unsigned counter;
	int rc;

	if (reload_firmware)
		islpci_set_state(priv, PRV_STATE_PREBOOT);
	else
		islpci_set_state(priv, PRV_STATE_POSTBOOT);

//      printk(KERN_DEBUG "%s: resetting device...\n", priv->ndev->name);

	/* disable all device interrupts in case they weren't */
	isl38xx_disable_interrupts(priv->device_base);

	/* bring down the firmware with an appropriate interrupt */
	/* ABORT interrupt */

	/* can we reinit the lmac at this stage without
	 * reuploading the firmware ? (islpci_reset_if)
	 */

	/* flush all management queues */
	//priv->index_mgmt_tx = 0;
	//priv->index_mgmt_rx = 0;

	/* now we can operate on the shared data */
	/* clear the indexes in the frame pointer */
	for (counter = 0; counter < ISL38XX_CB_QCOUNT; counter++) {
		cb->driver_curr_frag[counter] = cpu_to_le32(0);
		cb->device_curr_frag[counter] = cpu_to_le32(0);
	}

	/* reset the mgmt receive queue */
	/* RESET THE QUEUES. How to do this ? -- for rx, tx ? */

	if (reload_firmware) {
		/* now that the data structures are cleaned up, upload
		 * firmware and reset interface */
		rc = islpci_upload_fw(priv);
		if (rc) {
			printk(KERN_ERR "%s: islpci_reset: failure\n",
			       priv->ndev->name);
			return rc;
		}
	}

	/* finally reset interface */
	rc = islpci_reset_if(priv);
	if (rc)
		printk(KERN_ERR
		       "prism54: Your card/socket may be faulty, or IRQ line too busy :(\n");

	return rc;
}

void
islpci_do_reset_and_wake(void *data)
{
	islpci_private *priv = (islpci_private *) data;
	islpci_reset(priv, 1);
	netif_wake_queue(priv->ndev);
	priv->reset_task_pending = 0;
}

/******************************************************************************
    Network device configuration functions
******************************************************************************/

static
    int
islpci_alloc_queue(islpci_private *priv, unsigned queue_ref,
		   isl38xx_fragment *cb_queue, unsigned size)
{
	struct islpci_queue *queue = &priv->queues[queue_ref];

	FN_ENTER;

	memset(queue, 0, sizeof (*queue));
	queue->len = size;
	queue->cb_queue = cb_queue;
	queue->ringbuf =
	    kcalloc(queue->len, sizeof (*queue->ringbuf), GFP_KERNEL);
	if (!queue->ringbuf) {
		FN_EXIT1(-ENOMEM);
		return -ENOMEM;
	}

	FN_EXIT0;
	return 0;
}

/* FIXME : leaks on error */
static
    int
islpci_fill_rxqueue(islpci_private *priv, unsigned queue_ref)
{
	struct net_device *netdev = NETDEV_OF_ISLPCI(priv);
	struct islpci_queue *queue = &priv->queues[queue_ref];
	unsigned int size = queue->len;
	isl38xx_control_block *cb = priv->control_block;
	u32 curr = le32_to_cpu(cb->driver_curr_frag[queue_ref]);

	FN_ENTER;

	while (curr - queue->index < size) {
		u32 index = curr % size;
		struct islpci_membuf *buf = &queue->ringbuf[index];
		isl38xx_fragment *frag = &queue->cb_queue[index];

		/* assert this ! */
		if (buf->skb == NULL) {
			struct sk_buff *new_skb;
//                      unsigned reserve_space;
			// size ? MAX_FRAGMENT_SIZE_RX + 2
			new_skb = dev_alloc_skb(ISLPCI_RX_SIZE + 2);
			if (!new_skb) {
				printk(KERN_WARNING
				       "%s: Error allocating management frame\n",
				       DRV_NAME);
				FN_EXIT1(-ENOMEM);
				return -ENOMEM;
			}

			buf->skb = new_skb;

			new_skb->input_dev = netdev;

			// buf->size = ISLPCI_RX_SIZE;
			/* realign buffer */
			skb_push(new_skb, skb_headroom(new_skb));
			skb_trim(new_skb, 0);
			/* realign buffer on 32-bits boundary */
			//reserve_space = (4 - (long) new_skb->data) & 0x03;
			//skb_reserve(new_skb, reserve_space);

			buf->mem = new_skb->data;
			// TOFIX hardmac should be this for hardmac
			// BUG_ON(buf->pci_addr != 0);
			buf->pci_addr = 0;
		}
		if (buf->pci_addr == 0) {
			buf->pci_addr =
			    pci_map_single(priv->pdev, (void *) buf->skb->data,
					   // on data path,
					   // maps the whole ISLPCI_RX_SIZE+2
					   ISLPCI_RX_SIZE, PCI_DMA_FROMDEVICE);
			if (!buf->pci_addr) {
				printk(KERN_WARNING
				       "Failed to make memory DMA'able\n");
				FN_EXIT1(-ENOMEM);
				return -ENOMEM;
			}
		}

		/* be safe: always reset control block information */
		frag->size = cpu_to_le16(ISLPCI_RX_SIZE);
		frag->address = cpu_to_le32(buf->pci_addr);
		frag->flags = 0;
		frag->lmac_address = 0;

		curr++;

		/* FIXME : can we move this out of the loop, so there's
		   only one write to the PCI region ? */

		/* The fragment address in the control block must have
		 * been written before announcing the frame buffer to
		 * device */
		wmb();
		cb->driver_curr_frag[queue_ref] = cpu_to_le32(curr);
		priv->needs_trigger = 1;
	}

	FN_EXIT0;
	return 0;
}

static int
islpci_alloc_memory(islpci_private *priv)
{
	int counter;
	u32 iobase;
	int err;

	iobase = pci_resource_start(priv->pdev, 0);
	if (iobase & 0x3)
		printk(KERN_WARNING "%s: iobase has been modified", DRV_NAME);
	iobase &= ~3;
	if (!iobase)
		printk(KERN_WARNING "%s: iobase is null", DRV_NAME);
	priv->device_base = ioremap(iobase, ISL38XX_PCI_MEM_SIZE);
	/* remap the PCI device base address to accessible */
	if (!(priv->device_base)) {
		/* error in remapping the PCI device memory address range */
		printk(KERN_ERR "%s: PCI memory remapping failed\n", DRV_NAME);
		return -EIO;
	}

	/* memory layout for consistent DMA region:
	 *
	 * Area 1: Control Block for the device interface
	 * Area 2: Power Save Mode Buffer for temporary frame storage. Be aware that
	 *         the number of supported stations in the AP determines the minimal
	 *         size of the buffer !
	 */

	/* perform the allocation */
	priv->driver_mem_address =
	    pci_alloc_consistent(priv->pdev, CONTROL_BLOCK_SIZE,
				 &priv->device_host_address);

	if (!priv->driver_mem_address) {
		/* error allocating the block of PCI memory */
		printk(KERN_ERR
		       "%s: could not allocate DMA memory, aborting!",
		       DRV_NAME);
		return -ENOMEM;
	}

	/* assign the Control Block to the first address of the
	 * allocated area */
	priv->control_block =
	    (isl38xx_control_block *) priv->driver_mem_address;

	/* set the Power Save Buffer pointer directly behind the CB */
//      priv->device_psm_buffer =
//              priv->device_host_address + CONTROL_BLOCK_SIZE;

	/* make sure all buffer pointers are initialized */
	for (counter = 0; counter < ISL38XX_CB_QCOUNT; counter++) {
		priv->control_block->driver_curr_frag[counter] = cpu_to_le32(0);
		priv->control_block->device_curr_frag[counter] = cpu_to_le32(0);
	}

	/* Allocate tx/rx queues. All now use streaming mappings. */

	/* mgmt_rx */
	err = islpci_alloc_queue(priv, ISL38XX_CB_RX_MGMTQ,
				 priv->control_block->rx_data_mgmt,
				 ISL38XX_CB_MGMT_QSIZE);
	if (err)
		goto out_free;
	islpci_fill_rxqueue(priv, ISL38XX_CB_RX_MGMTQ);

	/* mgmt_tx */
	err = islpci_alloc_queue(priv, ISL38XX_CB_TX_MGMTQ,
				 priv->control_block->tx_data_mgmt,
				 ISL38XX_CB_MGMT_QSIZE);
	if (err)
		goto out_free;

	/* now get the data rx skb's */
	err = islpci_alloc_queue(priv, ISL38XX_CB_RX_DATA_LQ,
				 priv->control_block->rx_data_low,
				 ISL38XX_CB_RX_DATA_QSIZE);
	if (err)
		goto out_free;
	islpci_fill_rxqueue(priv, ISL38XX_CB_RX_DATA_LQ);

	err = islpci_alloc_queue(priv, ISL38XX_CB_TX_DATA_LQ,
				 priv->control_block->tx_data_low,
				 ISL38XX_CB_TX_DATA_QSIZE);
	if (err)
		goto out_free;

	return 0;

      out_free:
	islpci_free_memory(priv);
	return err;
}

int
islpci_free_memory(islpci_private *priv)
{
	/*
	   you must have disabled the tasklets before calling this function.
	 */
	if (priv->device_base)
		iounmap(priv->device_base);
	priv->device_base = NULL;

	/* free consistent DMA area... */
	if (priv->driver_mem_address)
		pci_free_consistent(priv->pdev, CONTROL_BLOCK_SIZE,
				    priv->driver_mem_address,
				    priv->device_host_address);

	/* clear some dangling pointers */
	priv->driver_mem_address = NULL;
	priv->device_host_address = 0;
	priv->control_block = NULL;

	/* free the queues */
	/* TODO */

	return 0;
}

struct net_device *
islpci_alloc(struct pci_dev *pdev)
{
	islpci_private *priv;
	struct net_device *netdev;
	struct islsm *islsm;

	netdev = alloc_islsm(sizeof (islpci_private));

	if (!netdev)
		return netdev;

	/* netdev fillin */
	SET_MODULE_OWNER(netdev);
	pci_set_drvdata(pdev, netdev);
	SET_NETDEV_DEV(netdev, &pdev->dev);
	netdev->base_addr = pci_resource_start(pdev, 0);
	netdev->irq = pdev->irq;

	/* islsm fillin */
	islsm = ISLSM_OF_NETDEV(netdev);
	/* all chips but the 3888 can take the softmac firmware */
	/* It has a special load address too */
	strcpy(islsm->firmware_name, ISL3886_IMAGE_FILE);
	islsm->isl_tx = islpci_tx_submit;
	islsm->isl_boot = islpci_boot;
	islsm->device_version = ISLSM_DEVICE_PCI_VER1;
	islsm->device_tx_header_space = 0;

	/* (pci) device-specific callbacks */
	islsm->isl_read_pcireg = isl_read_pcireg;
	islsm->isl_write_pcireg = isl_write_pcireg;
	islsm->isl_write_devmem = isl_write_devmem;
	islsm->isl_read_devmem = isl_read_devmem;
	islsm->isl_romboot = isl_rombot;
	islsm->isl_load_fw = isl_load_fw;

	/* uart callbacks */
	islsm->uart_prot_init = uart_prot_init;
	islsm->uart_prot_exit = uart_prot_exit;
	islsm->uart_cts = uart_cts;

	/* islpci fillin */
	priv = ISLPCI_OF_NETDEV(netdev);
	priv->ndev = netdev;
	priv->pdev = pdev;

	tasklet_init(&priv->rx_task, rx_tasklet, (unsigned long) priv);

	/* init the queue read locks, process wait counter */
	spin_lock_init(&priv->slock);
	spin_lock_init(&priv->pcireg_lock);
	spin_lock_init(&priv->rxqueue_lock);

	/* init state machine with off#1 state */
	priv->state = PRV_STATE_OFF;
	priv->state_off = 1;

	INIT_WORK(&priv->reset_task, islpci_do_reset_and_wake, priv);
	priv->reset_task_pending = 0;

	/* allocate various memory areas */
	if (islpci_alloc_memory(priv))
		goto do_free_islsm;

	/* save the start and end address of the PCI memory area */
	/* TOFIX in hardmac : this is done when the fields are
	   meaningless */
	netdev->mem_start = (unsigned long) priv->device_base;
	netdev->mem_end = netdev->mem_start + ISL38XX_PCI_MEM_SIZE;

	/* uart instanciation */
#ifdef PCIUART
	if (uart_init_dev(islsm))
		goto do_cleanup_uart;
#endif				/* PCIUART */

	return netdev;

#ifdef PCIUART
	uart_release_dev(islsm);
      do_cleanup_uart:
#endif				/* PCIUART */
	islpci_free_memory(priv);
      do_free_islsm:
	pci_set_drvdata(pdev, NULL);
	free_islsm(netdev);
	return NULL;
}

void islpci_free(struct net_device *ndev) {
	islpci_private *priv = ISLPCI_OF_NETDEV(ndev);
	struct pci_dev *pdev = 	priv->pdev;
#ifdef PCIUART
	struct islsm *islsm = ISLSM_OF_NETDEV(ndev);
	uart_release_dev(islsm);
#endif				/* PCIUART */
	islpci_free_memory(priv);
	pci_set_drvdata(pdev, NULL);
	free_islsm(ndev);
}

islpci_state_t
islpci_get_state(islpci_private *priv)
{
	/* lock */
	return priv->state;
	/* unlock */
}

islpci_state_t
islpci_set_state(islpci_private *priv, islpci_state_t new_state)
{
	islpci_state_t old_state;

	/* lock */
	old_state = priv->state;

	/* this means either a race condition or some serious error in
	 * the driver code */
	switch (new_state) {
	case PRV_STATE_OFF:
		priv->state_off++;
	default:
		priv->state = new_state;
		break;

	case PRV_STATE_PREBOOT:
		/* there are actually many off-states, enumerated by
		 * state_off */
		if (old_state == PRV_STATE_OFF)
			priv->state_off--;

		/* only if hw_unavailable is zero now it means we either
		 * were in off#1 state, or came here from
		 * somewhere else */
		if (!priv->state_off)
			priv->state = new_state;
		break;
	};
#if 0
	printk(KERN_DEBUG "%s: state transition %d -> %d (off#%d)\n",
	       priv->ndev->name, old_state, new_state, priv->state_off);
#endif

	/* invariants */
	BUG_ON(priv->state_off < 0);
	BUG_ON(priv->state_off && (priv->state != PRV_STATE_OFF));
	BUG_ON(!priv->state_off && (priv->state == PRV_STATE_OFF));

	/* unlock */
	return old_state;
}
