/*
 * Copyright (C) 2006 Jean-Baptiste Note <jean-baptiste.note@m4x.org>
 *
 * linux/net/driver/wireless/cx3110x/sm_drv_spi.c
 * (remodelled to fit the islsm way of doing things)
 *
 * Wireless extensions for Conexant CX3110x chipset.
 * Based on prim54 ioctls implementation.
 *
 * Copyright (C) 2003 Conexant Americas Inc. All Rights Reserved.
 * Copyright (C) 2004, 2005 Nokia Corporation
 * Author: Samuel Ortiz <samuel.ortiz@nokia.com>
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */

/*
 * TODO: don't push netdev as argument for all functions. Rather use the
 * priv structure whenever possible. Cleaner, you get many pointer work
 * out of the way.
 */

#define __KERNEL_SYSCALLS__

#include <linux/version.h>
#ifdef MODULE
#ifdef MODVERSIONS
#include <linux/modversions.h>
#endif
#include <linux/module.h>
#endif

#include <linux/init.h>
#include <linux/netdevice.h>
#include <linux/string.h>
#include <linux/firmware.h>
#include <linux/config.h>
#if !defined(CONFIG_FW_LOADER) && !defined(CONFIG_FW_LOADER_MODULE)
#error No Firmware Loading configured in the kernel !
#endif

#include <asm/irq.h>
#include <asm/hardware.h>
#include <asm/hardware/clock.h>

#include "isl_sm.h"
#include "islsm_log.h"
/* The pda is per-device normally ? */
#include "islspi_pda.h"
#include "islspi_init.h"

static void sm_drv_spi_cancel_tx_packets(struct net_device *dev);
static void sm_drv_spi_tasklet_action(unsigned long data);

static __attribute__((unused)) void sm_drv_spi_cli(struct net_device *dev);

static void sm_drv_spi_reset_device(unsigned boot);
static irqreturn_t sm_drv_spi_interrupt(int irq, void *config,
					struct pt_regs *regs);
static int sm_drv_spi_upload_firmware(struct islsm *islsm,
				      const struct firmware *fw);

static int sm_drv_spi_probe(struct device *dev);
static void sm_drv_spi_disconnect(struct net_device *dev);
static int sm_drv_spi_request_irq(struct net_device *dev);
static void sm_drv_spi_free_irq(struct net_device *dev);

static int bbp_fixup(struct islsm *);

#define DRIVER_NAME     "islspi_CX3110x"
#define VERSIONID       "0.8"
MODULE_LICENSE("GPL v2");

static u8 wlan_wait_bit(u16 reg, u32 mask, u32 expected )
{
	int i;
	char buffer[4];

	for (i = 0; i < 2000; i++) {
		sm_spi_read(reg, buffer, 4);
		if (((*(u32*)buffer) & mask) == expected){
			return 1;
		}
		msleep(1);
	}
	return 0;
}


/******************************************************************************
 *   Support Functions
 ******************************************************************************/

static void sm_drv_spi_cancel_tx_packets(struct net_device *dev)
{
	struct spi_hif_local_data *hif_lp = HIF_OF_NETDEV(dev);

	FN_ENTER;
	/* Free associated frame/skb */
	/* XXX Bug here ? */
	/* Don't understand what's done here, even in the Maemo driver
	   -- infinite loop, I don't see where the consume pointer gets
	   incremented */
	/* In our semantics, this function should dequeue and free all
	 * pending packets */
	while(tx_cspace(hif_lp) > 0) {
		struct sk_buff *txdata;
		txdata_get_c(hif_lp, &txdata);
		/* free according to allocator flag */
		/* XXX leak */
		/* Shouldn't we consume ? Or am I missing something here ? */
		tx_consume(hif_lp);
	}
	FN_EXIT0;
}

void link_changed(struct net_device *dev, u32 bitrate)
{
	/* do nothing. Exactly. */
}

/*
 * SPI timer.
 * If we stop receiving SPI packets,
 * we need to recover by writing the
 * RD_DONE bit.
 */
#define MAX_SPI_TIMER_LOOP 4
int spi_timer_nr_loop;
void spi_timer(unsigned long handle)
{
	struct net_device *dev = (struct net_device *)handle;
	struct spi_hif_local_data *hif_lp = HIF_OF_NETDEV(dev);
	uint32_t target_ints = SPI_TARGET_INT_RDDONE;
	uint32_t host_ints, wait_counter = 0;

	/* We haven't receive a single packet for 2 seconds...*/
	if (hif_lp->spi_packets == 0) {
		if (spi_timer_nr_loop > MAX_SPI_TIMER_LOOP) {
			/*
			 * If we've been looping on here for too long, we
			 * notify userspace and leave.
			 */
			printk("SPI timer timeout, getting out of here\n");
			link_changed(dev, 0);
			return;
		}

		printk("We haven't received an SPI packet for 1 second...\n");
		/* Here we wake the target up */
		target_ints = SPI_TARGET_INT_WAKEUP;
		sm_spi_write(SPI_ADRS_ARM_INTERRUPTS, (unsigned char *)&target_ints, sizeof target_ints );


		/* And wait for the READY interrupt */
		sm_spi_read(SPI_ADRS_HOST_INTERRUPTS, (unsigned char *)&host_ints, sizeof(host_ints));
		while (!(host_ints & SPI_HOST_INT_READY)) {
			if (wait_counter > 1000) {
				printk("Couldn't get the READY bit\n");
				break;
			}
			wait_counter++;
			udelay(5);
			sm_spi_read(SPI_ADRS_HOST_INTERRUPTS, (unsigned char *)&host_ints, sizeof(host_ints));
		}

		target_ints = SPI_TARGET_INT_RDDONE;
		sm_spi_write(SPI_ADRS_ARM_INTERRUPTS, (unsigned char *)&target_ints, sizeof(target_ints));

		/* XXX move tasklet to spi private data */
//		tasklet_hi_schedule(&lp->tasklet);

		spi_timer_nr_loop++;
	} else {
		spi_timer_nr_loop = 0;
		hif_lp->spi_packets = 0;
	}

	/* Let's restart the timer */
	mod_timer(&hif_lp->spi_timer, jiffies + HZ);
}

/******************************************************************************
 *   SPI Interconnect Handling (Bottom halve and helpers)
 ******************************************************************************/

static void sm_drv_spi_tasklet_action(unsigned long data)
{
	struct net_device *dev = (struct net_device *)data;
	struct islsm *islsm = ISLSM_OF_NETDEV(dev);
	struct spi_hif_local_data *hif_lp = HIF_OF_NETDEV(dev);

	struct sk_buff *skb;
	/* struct s_txdata *txdata; */

	struct s_dma_regs dma_regs;
	uint32_t host_ints, host_int_en, target_ints;
	unsigned short length;

	/* FIXME: this tasklet is OK on !SMP. But races aplenty on SMP */

	HIF_CLK_USE(hif_lp);

	/* The IRQ line won't be updated, but the register still will be */
	host_int_en = 0;
	sm_spi_write(SPI_ADRS_HOST_INT_EN, (unsigned char *)&host_int_en, sizeof(host_int_en));

	sm_spi_read(SPI_ADRS_HOST_INTERRUPTS, (unsigned char *)&host_ints, sizeof(host_ints));

	islog(L_IRQ, "%s interrupts 0x%x\n", DRIVER_NAME, host_ints);

	host_ints &= SPI_HOST_INTS_DEFAULT;
	sm_spi_write(SPI_ADRS_HOST_INT_ACK, (unsigned char *)&host_ints, sizeof(host_ints));

	/* Skip this ? */
/* 	if (lp->sm_descr.mtu == 0) { */
/* 		islog(DBG_BH, "%s SoftMAC not initialized\n", DRIVER_NAME); */
/* 		HIF_CLK_UNUSE(hif_lp); */
/* 		return; */
/* 	} */

	if(host_ints & SPI_HOST_INT_READY) {
		/* This path should be taken after the firmware upload */
		/* yeah, i don't really know what a proper state machine
		   looks like */
/* 		if (hif_lp->upload_state == UPLOAD_STATE_BOOTING) { */
/* 			sm_drv_spi_initialize(dev); */
 			hif_lp->upload_state = UPLOAD_STATE_RUNNING;
 			spi_timer_nr_loop = 0;
			complete(&islsm->dev_init_comp);
	}

	/* Check if LMAC has rx frame */
	if((host_ints & SPI_HOST_INT_UPDATE) || (host_ints & SPI_HOST_INT_SW_UPDATE)) {
		skb = dev_alloc_skb(2000);
		if (!skb)
			printk("Couldn't allocate RX frame\n");
		else {
			/* dummy read to flush SPI DMA controller bug */
			sm_spi_read(SPI_ADRS_GEN_PURP_1, (unsigned char *)&length, sizeof(length));

			sm_spi_read(SPI_ADRS_DMA_DATA, (unsigned char *)&length, sizeof(length));
			islog(L_DEBUG, "%s received frame len=%d\n", DRIVER_NAME, length);

			/* fragmentation support inexistant for now */
			if ( length > SPI_MAX_PACKET_SIZE )
				length = SPI_MAX_PACKET_SIZE;
			/* Get the data directly from the spi channel */
			sm_spi_dma_read(SPI_ADRS_DMA_DATA, (unsigned char *)skb->data, length);

			/* Transmit to the islsm */
			islsm_rxdata_debug(0, skb->data, length);
			islsm_data_input(skb);
			hif_lp->spi_packets++;
		}
	}

	/* See if there are queued messages for us to send
	 *
	 * WARNING: here we use the txdata as a buffer queue. Not what
	 * the original driver does
	 */
	while(tx_pspace(hif_lp) > 0)
	{
		uint32_t wait_counter = 0;
		struct sk_buff *skb;

		/* get tx skb from queue */
 		txdata_get_c(hif_lp, &skb);

		/* Firmware bug...First TX packets needs to be horribly delayed */
		if (hif_lp->initial_packets < SPI_DELAY_THRESHOLD)
			mdelay(5);

		/* Here we wake the target up */
		target_ints = SPI_TARGET_INT_WAKEUP;
		sm_spi_write(SPI_ADRS_ARM_INTERRUPTS, (unsigned char *)&target_ints, sizeof target_ints );

		/* And wait for the READY interrupt */
		sm_spi_read(SPI_ADRS_HOST_INTERRUPTS, (unsigned char *)&host_ints, sizeof(host_ints));
		while (!(host_ints & SPI_HOST_INT_READY)) {
			if (wait_counter > 1000) {
				printk("We haven't got a READY interrupt from WAKEUP\n");
				break;
			}
			wait_counter++;
			udelay(5);
			sm_spi_read(SPI_ADRS_HOST_INTERRUPTS, (unsigned char *)&host_ints, sizeof(host_ints));
		}
		wait_counter = 0;

		/* program DMA and transfer TX buffer */
		dma_regs.cmd  = SPI_DMA_WRITE_CTRL_ENABLE;
		dma_regs.len  = cpu_to_le16(skb->len);
		dma_regs.addr = cpu_to_le32(LMAC_ADDR_OF_SKB(skb));

		sm_spi_write(SPI_ADRS_DMA_WRITE_CTRL, (unsigned char *)&dma_regs, sizeof(struct s_dma_regs));
		sm_spi_dma_write(SPI_ADRS_DMA_DATA, skb->data, skb->len);

		/* We're busy waiting for the DMA end ??? */
		/* We wait for the DMA Ready interrupt */
		sm_spi_read(SPI_ADRS_HOST_INTERRUPTS, (unsigned char *)&host_ints, sizeof(host_ints));
		while(!(host_ints & SPI_HOST_INT_READY)) {
			if (wait_counter > 1000) {
				printk("We haven't got a READY interrupt from DMA READY\n");
				break;
			}
			wait_counter++;
			udelay(5);
			sm_spi_read(SPI_ADRS_HOST_INTERRUPTS, (unsigned char *)&host_ints, sizeof(host_ints));
		}
		wait_counter = 0;

 		tx_consume(hif_lp);
		islsm_txskb_free(skb);
	}

	/* The target can go back to sleep again */
	target_ints = SPI_TARGET_INT_SLEEP;
	sm_spi_write(SPI_ADRS_ARM_INTERRUPTS, (unsigned char *)&target_ints, sizeof target_ints );

	//handle_sm_callback(dev, callb_mask);

	/* Re-enable interrupts */
	host_int_en = SPI_HOST_INTS_DEFAULT;
	sm_spi_write(SPI_ADRS_HOST_INT_EN, (unsigned char *)&host_int_en, sizeof(host_int_en));

	HIF_CLK_UNUSE(hif_lp);
}

/* TX handler for islsm */
static int islspi_tx(struct sk_buff *skb) {
	struct spi_hif_local_data *hif_lp = HIF_OF_NETDEV(skb->dev);
	int err = 0;

	/* Racy. We're not under lock */
	/* just queue the frame if enough room */
	if (tx_pspace(hif_lp) > 0) {
		/* post-increment */
		txdata_set_p(hif_lp, skb);
		tx_produce(hif_lp);
	}
	else
		err = -ENOSPC;

	return err;
}

/******************************************************************************
 *   Driver model callbacks
 ******************************************************************************/

static int wlan_omap_suspend(struct device *dev, u32 state, u32 level)
{
	int ret = 0;
/* 	uint32_t hibernate = 1; */
/* 	struct net_device * net_dev; */
/* 	struct net_local *lp; */

	islog(L_DEBUG, "WLAN suspending...\n");

	/* TODO: I'd *love* to snoop this in action -- really. For now
	 * we don't support suspend -- to be reverse-engeneered. We
	 * could do a basic down/up of the device maybe.
	 */

/* 	net_dev = (struct net_device *)dev_get_drvdata(dev); */
/* 	lp = net_dev->priv; */

/* 	if (level == SUSPEND_POWER_DOWN && lp->sm_initialization) */
/* 		ret = sm_drv_oid_set((struct net_device *)dev_get_drvdata(dev), */
/* 				     GEN_OID_HIBERNATE, */
/* 				     &hibernate, */
/* 				     sizeof(uint32_t)); */
	return ret;
}

static int wlan_omap_resume(struct device *dev, u32 level)
{
	int ret = 0;
/* 	uint32_t hibernate = 0; */
/* 	struct net_device * net_dev; */
/* 	struct net_local *lp; */

	islog(L_DEBUG, "WLAN resuming...\n");

/* 	net_dev = (struct net_device *)dev_get_drvdata(dev); */
/* 	lp = net_dev->priv; */

/* 	if (level == RESUME_POWER_ON && lp->sm_initialization) */
/* 		ret = sm_drv_oid_set((struct net_device *)dev_get_drvdata(dev), */
/* 				     GEN_OID_HIBERNATE, */
/* 				     &hibernate, */
/* 				     sizeof(uint32_t)); */

	return ret;
}

static int wlan_drv_probe(struct device *dev)
{
	int err;
	struct net_device *ndev;

	FN_ENTER;

	err = sm_drv_spi_probe(dev);
	if (!err) {
		printk(KERN_WARNING "Could not register SoftMAC SPI Driver\n");
		goto exit;
	}

	ndev = dev_get_drvdata(dev);
	err = sm_drv_spi_request_irq(ndev);

 exit:
	FN_EXIT1(err);
	return err;
}

static int wlan_drv_remove(struct device *dev)
{
	struct net_device *ndev;
	/* get back to the netdev */
	ndev = dev_get_drvdata(dev);
	sm_drv_spi_disconnect(ndev);
	sm_drv_spi_free_irq(ndev);
	return 0;
}

/******************************************************************************
 *   Device init & shutdown sequence
 ******************************************************************************/

static int
islspi_boot(struct islsm *islsm)
{
	const struct firmware *fw_entry;
	int err = 0;

	FN_ENTER;

	err = islsm_request_firmware(&fw_entry, islsm);
	if (err)
		goto exit;

	/* upload firmware, trigger firmware boot */
	err = sm_drv_spi_upload_firmware(islsm, fw_entry);
	if (err) {
		islog(L_DEBUG, "firmware upload failed\n");
		goto exit_release_firmware;
	}

	/* wait 1sec for the init irq to be raised */
	err = islsm_wait_timeout(islsm, HZ);
	if (err)
		goto exit_release_firmware;

	/* fixup the BBP */
	err = bbp_fixup(islsm);

 exit_release_firmware:
	release_firmware(fw_entry);
 exit:
	FN_EXIT1(err);
	return err;
}

/* stub for islsm */
static int islspi_romboot(struct islsm *islsm) {
	(void) islsm;
	/* FIXME: disable IRQs */
	sm_drv_spi_reset_device(1);
	/* FIXME: reenable IRQs */
	return 0;
}

/******************************************************************************
 *   Initialization and Firmware Upload
 ******************************************************************************/

static
void sm_drv_spi_cli(struct net_device *dev)
{
	struct spi_hif_local_data *hif_lp = HIF_OF_NETDEV(dev);
	unsigned int host_reg;

	HIF_CLK_USE(hif_lp);

	host_reg = 0;
	sm_spi_write(SPI_ADRS_HOST_INT_EN, (unsigned char *)&host_reg, sizeof host_reg );

	omap_wlan_disable_irq(dev);

	HIF_CLK_UNUSE(hif_lp);
}

static void sm_drv_spi_reset_device(unsigned boot)
{
	unsigned short dev_reg;

	/* We do the ROM boot */
	dev_reg = SPI_CTRL_STAT_HOST_OVERRIDE | SPI_CTRL_STAT_HOST_RESET;
	if (boot)
		dev_reg |= SPI_CTRL_STAT_RAM_BOOT;

	sm_spi_write(SPI_ADRS_DEV_CTRL_STAT, (unsigned char *)&dev_reg, sizeof(dev_reg));
	msleep(200);

	dev_reg &= ~SPI_CTRL_STAT_HOST_RESET;
	sm_spi_write(SPI_ADRS_DEV_CTRL_STAT, (unsigned char *)&dev_reg, sizeof(dev_reg));
	msleep(200);
}

static int sm_drv_spi_upload_firmware(struct islsm *islsm,
				      const struct firmware *fw)
{
	struct spi_hif_local_data *hif_lp = HIF_OF_ISLSM(islsm);
	struct s_dma_regs dma_regs;
	unsigned long fw_len, address = FIRMWARE_ADDRESS;
	uint32_t host_reg;
	const u8 *fw_data;
	uint8_t * fw_buffer;
	int err = 0;

	FN_ENTER;

	HIF_CLK_USE(hif_lp);

	fw_len = fw->size;
	fw_data = fw->data;

	/* allocate the DMA-capable buffer */
	fw_buffer = kmalloc(SPI_MAX_PACKET_SIZE, GFP_KERNEL);
	if (!fw_buffer) {
		err = -ENOMEM;
		goto exit;
	}

	/* Stop device */
	sm_drv_spi_reset_device(0);

	/* fw_len is unsigned */
	while (fw_len != 0) {
		long _fw_len =
			(fw_len > SPI_MAX_PACKET_SIZE) ?
			SPI_MAX_PACKET_SIZE : fw_len;

		/* copy the firmware data to the DMA-capable buffer */
		memcpy(fw_buffer, fw_data, _fw_len);

		dma_regs.cmd   = SPI_DMA_WRITE_CTRL_ENABLE;
		dma_regs.len   = cpu_to_le16(_fw_len);
		dma_regs.addr  = cpu_to_le32(address);

		fw_len -= _fw_len;
		fw_data += _fw_len;
		address += _fw_len;

		/* as in the net2280 case, here we control
		   the DMA engine operation from the host. Normal
		   operation is not this */
		sm_spi_write(SPI_ADRS_DMA_WRITE_CTRL, (unsigned char *)(&dma_regs.cmd), sizeof(short));
		if (wlan_wait_bit(SPI_ADRS_DMA_WRITE_CTRL, HOST_ALLOWED, HOST_ALLOWED) == 0) {
			printk("fw_upload: Not allowed to DMA write\n");
			omap_wlan_dump_register();
			err = -EAGAIN;
			goto exit;
		}

		sm_spi_write(SPI_ADRS_DMA_WRITE_LEN, (unsigned char *)(&dma_regs.len), sizeof(short));
		sm_spi_write(SPI_ADRS_DMA_WRITE_BASE, (unsigned char *)(&dma_regs.addr), sizeof(long));

		sm_spi_dma_write(SPI_ADRS_DMA_DATA, fw_buffer, _fw_len);
	}


	BUG_ON(fw_len != 0);

	/* Enable host interrupts */
	host_reg = SPI_HOST_INTS_DEFAULT;
	sm_spi_write(SPI_ADRS_HOST_INT_EN, (unsigned char *)&host_reg, sizeof host_reg );

	/* Boot device */
	sm_drv_spi_reset_device(1);

	kfree(fw_buffer);
 exit:
	HIF_CLK_UNUSE(hif_lp);
	FN_EXIT1(err);
	return err;
}

static irqreturn_t sm_drv_spi_interrupt(int irq, void *config, struct pt_regs *regs)
{
	struct spi_hif_local_data *hif_lp = config;
//	int devstate = lp->device_state;

	/* no poking ? What about IRQ sharing ? */
//	if(devstate == DEVSTATE_ACTIVE || devstate == DEVSTATE_BOOTING)
	tasklet_hi_schedule(&hif_lp->tasklet);

	return IRQ_HANDLED;
}

static int sm_drv_spi_request_irq(struct net_device *dev)
{
	struct spi_hif_local_data *hif_lp = HIF_OF_NETDEV(dev);
	int err = 0;

	FN_ENTER;

	HIF_CLK_USE(hif_lp);

	/* Install interrupt handler */
	err = omap_wlan_request_irq(sm_drv_spi_interrupt, DRIVER_NAME, (void*)hif_lp);
	/* XXX original code does not CLK_UNUSE in case of failure.
	   What to do ? */

	HIF_CLK_UNUSE(hif_lp);

	FN_EXIT1(err);
	return err;
}

static void sm_drv_spi_free_irq(struct net_device *dev)
{
	FN_ENTER;
	omap_wlan_free_irq((void *)dev);
	FN_EXIT0;
}

/*
 * Merge this with the probe function of the driver
 */
static int sm_drv_spi_probe(struct device *device)
{
/*	struct platform_device *plat_dev = to_platform_device(device); */
	struct net_device *netdev;
	struct islsm * islsm;
	struct spi_hif_local_data *spi_lp;
	int err;

	FN_ENTER;

	netdev = alloc_islsm(sizeof(struct spi_hif_local_data));
	if (!netdev) {
		printk(KERN_ERR "Cannot allocate SPI local data");
		err = -ENOMEM;
		goto err_out_2;
	}

	islsm = ISLSM_OF_NETDEV(netdev);
	spi_lp = HIF_OF_NETDEV(netdev);
	SET_MODULE_OWNER(netdev);

	/* Our net_device will be our private data */
	dev_set_drvdata(device, netdev);
	SET_NETDEV_DEV(netdev, device);

	/* Initialize islsm structure */
	islsm->device_tx_header_space = 0;
	islsm->device_version = ISLSM_DEVICE_SPI_VER1;

	/* NB: wouldn't there be another way to actually
	   get the PDA ? */
	islsm->pda = umac_pda;
	islsm->pda_length = sizeof(umac_pda);

	islsm->isl_boot = islspi_boot;
	islsm->isl_tx = islspi_tx;

	/* (pci-specific) device callbacks */
	islsm->isl_read_pcireg = 0;
	islsm->isl_write_pcireg = 0;

	/* could be filled in with spi functions */
	islsm->isl_write_devmem = 0;
	islsm->isl_read_devmem = 0;
	islsm->isl_romboot = islspi_romboot;
	islsm->isl_load_fw = sm_drv_spi_upload_firmware;

	strcpy(islsm->firmware_name, UMAC_FIRMWARE_FILE);

	/* Initialize spi_lp structure */
	spi_lp->netdev = netdev;

 	spi_lp->nokia_wlan_config = omap_get_config(OMAP_TAG_WLAN,
						    struct omap_wlan_config);
 	if (spi_lp->nokia_wlan_config == NULL) {
		err = -EIO;
 		goto err_out_3;
	}

	/* McBSP2 clock is ARM_PER */
	HIF_CLK(spi_lp) = clk_get(NULL, "armper_ck");
	BUG_ON(HIF_CLK(spi_lp) == NULL);

	/* Fast mode disabled by default */
	spi_lp->fast_mode = 0;
	spin_lock_init(&spi_lp->lock);

	/* Initialise initial packet count */
	spi_lp->initial_packets = 0;
	spi_lp->spi_packets = 0;

	/* Initialise SPI timer */
	init_timer(&spi_lp->spi_timer);
	spi_lp->spi_timer.function = spi_timer;
	spi_lp->spi_timer.data = (unsigned long)netdev;

	/* Init our Bottom halve handler */
	tasklet_init(&spi_lp->tasklet, sm_drv_spi_tasklet_action, (unsigned long)netdev);

	/* Init the wireless stats queue */
	//INIT_WORK(&spi_lp->stats_work, sm_drv_update_stats, dev);
	//spi_lp->stats_timestamp = 0;

	err = register_islsm(netdev);
	if (err) {
		printk(KERN_ERR "%s: Failed to register islsm\n", DRIVER_NAME);
		goto err_out_3;
	}

	FN_EXIT1(err);
	return err;

 err_out_3:
	free_islsm(netdev);
 err_out_2:
	FN_EXIT1(err);
	return err;
}

static void sm_drv_spi_disconnect(struct net_device *dev)
{
	struct spi_hif_local_data *hif_lp = HIF_OF_NETDEV(dev);

	FN_ENTER;

	HIF_CLK_USE(hif_lp);

	/* Cancel and Free TX packets */
	sm_drv_spi_cancel_tx_packets(dev);
	sm_drv_spi_reset_device(0);
	del_timer(&hif_lp->spi_timer);

	unregister_islsm(dev);
	free_islsm(dev);

	HIF_CLK_UNUSE(hif_lp);

	FN_EXIT0;
}

/******************************************************************************
 *   Module Init (essentially hotplug)
 ******************************************************************************/

static void wlan_omap_device_release(struct device * dev)
{

}

static struct platform_device wlan_omap_device = {
	.name		= "wlan-omap",
	.id		= -1,
	.dev            = {
		.release = wlan_omap_device_release,
	},
};

static struct device_driver wlan_omap_driver = {
	.name   	= "wlan-omap",
	.bus		= &platform_bus_type,
	.probe          = wlan_drv_probe,
	.remove         = wlan_drv_remove,
	.suspend	= wlan_omap_suspend,
	.resume		= wlan_omap_resume
};

static int __init sm_drv_spi_init_module(void)
{
	int err;

	/* just register the driver to the platform bus */
	err = driver_register(&wlan_omap_driver);
	if (err)
		goto exit;

	err = platform_device_register(&wlan_omap_device);
	if (err) {
		printk("Couldn't register wlan_omap device\n");
		driver_unregister(&wlan_omap_driver);
		goto exit;
	}

	/* Then pray that hotplug does call probe and instanciates
	   the actual device -- else we may have to do this ourselves,
	   or find "linux hotplug for dummies" */

	printk("Loaded %s driver, version %s\n", DRIVER_NAME, VERSIONID);

 exit:
	return err;
}

static void __exit sm_drv_spi_cleanup_module( void )
{
	platform_device_unregister(&wlan_omap_device);
	driver_unregister(&wlan_omap_driver);
	printk("Unloaded ISL3825 SPI  driver, version %s\n", VERSIONID);
}

/******************************************************************************
 *   BBP fixup
 ******************************************************************************/

/* This is a temporary fix while we wait for a firmware one */
struct lmac_reg {
	uint32_t reg_nr;
	uint32_t reg_value;
};

static struct lmac_reg lmac_bb_reg_array [] = { {15, 0x0a0a},
						{16, 0x3f01},
						{17, 0x0109},
};

/* BBP fixup, after firmware upload and boot. How to put this in islsm ? */
static int bbp_fixup(struct islsm *islsm)
{
	unsigned i, nr_bb_reg = ARRAY_SIZE(lmac_bb_reg_array);
	int err;

	FN_ENTER;

	for (i = 0; i < nr_bb_reg; i++) {
		err = islsm_bbp_write(islsm,
				      lmac_bb_reg_array[i].reg_nr,
				      lmac_bb_reg_array[i].reg_value);
		if (err)
			break;
	}

	FN_EXIT1(err);
	return err;
}

module_init(sm_drv_spi_init_module);
module_exit(sm_drv_spi_cleanup_module);
