/*
  Copyright 2004, 2005, 2006 Jean-Baptiste Note

  This file is part of prism54usb.

  prism54usb 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.

  prism54usb 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 prism54usb; if not, write to the Free Software Foundation,
  Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

*/

#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/vmalloc.h>

#include "islsm_log.h"
#include "isl_sm.h"
#include "islsm_protocol.h"
#include "islsm_alloc.h"

static int
islsm_tx_skb(struct sk_buff *skb)
{
	struct islsm *islsm = ISLSM_OF_NETDEV(skb->dev);
	int err;

	/* We now have all the data to compute the CRC */
	/* FIXME : move this down the pipeline -- at hardware level */
	switch (islsm->fw_type) {
	case ISLSM_FW_LM87:
	{
		u32 crc;
		/* compute & write the CRC */
		crc = islsm_crc((u32 *)skb->data, skb->len);
		(void) skb_push(skb,sizeof(crc));
	}
	break;
	default:
		break;
	};

	BUG_ON( (long)skb->data & 3 );

	err = islsm->isl_tx(skb);
	if (err)
		printk(KERN_ERR "%s: Hardware refused frame %p\n",
		       "islsm", skb);

	return err;
}

/* all, and only control packets, go through this function */
static int
islsm_make_prism_header_skb(struct sk_buff *skb, uint16_t type,
			    uint16_t reqtype, u8 retry1, u8 retry2)
{
	struct islsm *islsm = ISLSM_OF_NETDEV(skb->dev);
	struct islsm_tx_control *islsm_txc;
	uint32_t lmac_addr;
	int len = skb->len;
	int err;

	islsm_txc = (void *) skb_push(skb, SIZE_TX_CONTROL);
	islsm_txc->id.magic1 = cpu_to_le16(type);
	islsm_txc->id.length = cpu_to_le16(len);
	islsm_txc->type = cpu_to_le16(reqtype);
	islsm_txc->retry1 = retry1;
	islsm_txc->retry2 = retry2;

	/* XXX : maybe 2.13 needs the checksum copied too */
	err = islsm_alloc(&islsm->memory, skb);
	if (err)
		return err;

	lmac_addr = LMAC_ADDR_OF_SKB(skb);
	islsm_txc->req_id = lmac_addr;

	err = islsm_tx_skb(skb);
	if (err)
		islsm_free(&islsm->memory, lmac_addr);

	return err;
}

/* TX/RX/MODE packet */
static
    int
islsm_make_rx_filter_skb(struct sk_buff *skb,
			 uint16_t filter_type, const uint8_t *bssid,
			 u8 antenna, uint32_t magic3,
			 uint16_t magic8, uint16_t magic9)
{
	struct net_device *netdev = skb->dev;
	struct islsm *islsm = ISLSM_OF_NETDEV(netdev);
	struct islsm_tx_control_filter *txcf;
	unsigned int i;

	txcf = (void *) skb_push(skb, SIZE_TX_CONTROL_FILTER);
	memset(txcf, 0, sizeof (*txcf));

	txcf->filter_type = cpu_to_le16(filter_type);
	txcf->rxhw = cpu_to_le16(islsm->smpar.rxhw);

	txcf->antenna = antenna;
	txcf->debug = 0;
	txcf->magic3 = cpu_to_le32(magic3);
	memcpy(txcf->rates, islsm->filter_rateset, sizeof (islsm->filter_rateset));
	txcf->rx_addr = cpu_to_le32(islsm->rxframe_mem_start);

	/* fixme: compute this cleverly */
	if (islsm->device_version == ISLSM_DEVICE_USB_VER1) {
		txcf->mru = cpu_to_le16(ISLSM_TX_CONTROL_FILTER_MAGIC7_VER1);
	} else if (islsm->device_version == ISLSM_DEVICE_USB_VER2) {
		txcf->mru = cpu_to_le16(ISLSM_TX_CONTROL_FILTER_MAGIC7_VER2);
	} else {
		/* PCI */
		txcf->mru = cpu_to_le16(ISLSM_TX_CONTROL_FILTER_MAGIC7_PCI);
	}

	txcf->magic8 = cpu_to_le16(magic8);
	txcf->magic9 = cpu_to_le16(magic9);

	/* my mac address  */
	memcpy(&txcf->destination[0], netdev->dev_addr, ETH_ALEN);

	/* the other mac address */
	for (i = 0; i < ETH_ALEN; i++)
		txcf->source[i] = bssid[i];

	/* what's next */
	return islsm_make_prism_header_skb(skb,
					   ISLSM_TX_CONTROL_NORESPONSE_ID,
					   ISLSM_TX_CONTROL_TYPE_FILTER_SET, 0, 0);
}

/* Frequency change packet building */
/* common filling */
static void
fill_basic_channel(struct islsm *islsm,
		   struct islsm_tx_control_channel *tx_chan,
		   unsigned int chan, unsigned int freq)
{
	/* we normalized this */
	unsigned int array_index = chan;

	if (islsm->finfo6[array_index].frequency != 0) {
		/* the information was present */
		tx_chan->freq = islsm->finfo6[array_index];
	} else {
#define IQ_PARAM_DEFAULT 0x8000U
		/* fill with observed default value */
		islog(L_PDA, "channel with only partial information "
		      "in third array");
/*
  we could use the [0] as a repository for default values. for now
  tx_chan->freq = islsm->finfo6[0];
*/
		tx_chan->freq.iq_param_1 = cpu_to_le16(IQ_PARAM_DEFAULT);
		tx_chan->freq.iq_param_2 = cpu_to_le16(IQ_PARAM_DEFAULT);
		tx_chan->freq.iq_param_3 = cpu_to_le16(IQ_PARAM_DEFAULT);
		tx_chan->freq.iq_param_4 = cpu_to_le16(IQ_PARAM_DEFAULT);
		tx_chan->freq.frequency = cpu_to_le16(freq);
#undef IQ_PARAM_DEFAULT
	}

	/* This part remains to be understood ; i'm sure there are
	   better approximations  */
	tx_chan->pa_points_per_curve = islsm->pa_points_per_curve;

	/* output levels that we want */
	tx_chan->output_pwr_levels.val_barker = 0x38;
	tx_chan->output_pwr_levels.val_bpsk   = islsm->output_pwr_limits[array_index].val_bpsk;
	tx_chan->output_pwr_levels.val_qpsk   = islsm->output_pwr_limits[array_index].val_qpsk;
	tx_chan->output_pwr_levels.val_16qam  = islsm->output_pwr_limits[array_index].val_16qam;
	tx_chan->output_pwr_levels.val_64qam  = islsm->output_pwr_limits[array_index].val_64qam;

	/* copy the curve data */
	memcpy(tx_chan->curve_data, &islsm->finfo4[array_index],
	       sizeof (islsm->finfo4[array_index]));
}

/* chan is the ieee channel number */
static
    int
islsm_make_tx_control_channel_skb(struct sk_buff *skb, unsigned int chan,
				  unsigned int freq, uint16_t magic1,
				  uint16_t magic2)
{
	struct net_device *netdev = skb->dev;
	struct islsm *islsm = ISLSM_OF_NETDEV(netdev);
	struct islsm_tx_control_channel *tx_chan = 0;
	size_t mysize;

	mysize =
	    islsm->device_version ==
	    ISLSM_DEVICE_USB_VER1 ? SIZE_TX_CONTROL_CHANNEL :
	    SIZE_TX_CONTROL_CHANNEL_VER2;

	tx_chan = (void *) skb_push(skb, mysize);

	memset(tx_chan, 0, sizeof (*tx_chan));

	tx_chan->magic1 = cpu_to_le16(magic1);
	tx_chan->magic2 = cpu_to_le16(magic2);

	/* version 2 devices do not seem to use this ; comment out as i
	 * don't understand what it means for now. I am under the
	 * impression that during active scan this is wrong. */
/* 	if (islsm_chan == 14 && magic1 == ISLSM_TX_CONTROL_CHANNEL_MAGIC1_SCAN) */
/* 		tx_chan->magic1 |= cpu_to_le16(ISLSM_TX_CONTROL_CHANNEL_MAGIC1_SCAN_WRAP); */

	fill_basic_channel(islsm, tx_chan, chan, freq);

	/* version 2 devices. This is here to emphasize the difference.
	   Of course it sits better in fill_basic_channel */
	if (islsm->device_version != ISLSM_DEVICE_USB_VER1) {
		/* FIXME : this is not a device_version problem but a
		 * firmware version thing -- fix this */
		struct islsm_tx_control_channel_ver2 *tx_chan2 = (void *) tx_chan;	/* only 4 bytes longer */
		/* Copy at the end of the packet four limits... Don't
		 * know why */
		memcpy(&(tx_chan2->basic_packet.padding),
		       &(tx_chan2->basic_packet.output_pwr_levels.val_bpsk),
		       sizeof (tx_chan2->basic_packet.padding));
		tx_chan2->padding = 0;
	}

	return islsm_make_prism_header_skb(skb,
					   ISLSM_TX_CONTROL_NORESPONSE_ID,
					   ISLSM_TX_CONTROL_TYPE_CHANNEL_CHANGE,
					   0, 0);
}

static int
islsm_make_control_led_skb(struct sk_buff *skb,
			   uint16_t mode, uint16_t perm_setting,
			   uint16_t temp_setting, uint16_t duration)
{
	struct islsm_tx_control_led *payload;

	payload = (void *) skb_push(skb, SIZE_TX_CONTROL_LED);

	payload->mode = cpu_to_le16(mode);
	payload->led_setting_temporary = cpu_to_le16(temp_setting);
	payload->led_setting_permanent = cpu_to_le16(perm_setting);
	payload->temporary_setting_duration = cpu_to_le16(duration);

	return islsm_make_prism_header_skb(skb,
					   ISLSM_TX_CONTROL_NORESPONSE_ID,
					   ISLSM_TX_CONTROL_TYPE_LED, 0, 0);
}

static
    int
islsm_make_freequeue_skb(struct sk_buff *skb, uint32_t lmac_addr)
{
	struct islsm_tx_control_freequeue *payload;

	payload = (void *) skb_push(skb, SIZE_TX_CONTROL_FREEQUEUE);
	payload->queue = cpu_to_le32(lmac_addr);

	return islsm_make_prism_header_skb(skb,
					   ISLSM_TX_CONTROL_NORESPONSE_ID,
					   ISLSM_TX_CONTROL_TYPE_FREEQUEUE, 0, 0);
}

typedef int (* readback_t)(struct sk_buff *skb,
			   unsigned offset, unsigned length);

static int
islsm_make_readback_skb(struct sk_buff *skb, unsigned offset, unsigned length)
{
	struct islsm_control_eeprom_lm86 *payload;
	int err;

	FN_ENTER;

	payload = (void *) skb_push(skb, SIZE_CONTROL_EEPROM + length);

	payload->offset = cpu_to_le16(offset);
	payload->len = cpu_to_le16(length);
	memset(payload->data, 0, length);

	err = islsm_make_prism_header_skb(skb,
					  ISLSM_ID_EEPROM_READBACK,
					  ISLSM_TX_CONTROL_TYPE_EEPROM_READBACK,
					  0, 0);

	FN_EXIT1(err);
	return err;
}

static int
islsm_make_readback_skb_lm87(struct sk_buff *skb, unsigned offset, unsigned length) {
	struct islsm_control_eeprom_lm87 *payload;
	int err;

	FN_ENTER;

	payload = (void *) skb_push(skb, sizeof(*payload) + length);

	payload->address = cpu_to_le32(offset);
	payload->len = cpu_to_le16(length);
	payload->unknown = cpu_to_le16(0xf);
	payload->magic = cpu_to_le32(ISLSM_LOCK);
	memset(payload->data, 0, length);

	err = islsm_make_prism_header_skb(skb,
					  ISLSM_ID_EEPROM_READBACK,
					  ISLSM_TX_CONTROL_TYPE_EEPROM_READBACK,
					  0, 0);

	FN_EXIT1(err);
	return err;
}

/* send packet, higly suboptimal version, and API will change.  I'd
   really like to be able to ask the upper layers to send a skb with a
   minimal headroom in it... */

static int
islsm_make_tx_datapkt_skb(struct sk_buff *skb, uint8_t padding, struct sm_tx_p *txp)
{
	struct islsm *islsm = ISLSM_OF_NETDEV(skb->dev);
	struct islsm_tx_control_allocdata *txd;
	struct islsm_tx_control *islsm_txc;
	/* Warning, the size field must be the size of the data payload,
	   not the size of the payload + header */
	unsigned size = skb->len;
	uint16_t id_magic1;
	int err;

	/* As soon as we change the skb state, as is the case here,
	   we cannot return an error, lest the skb is requeued */

	txd = (void *) skb_push(skb, SIZE_TX_CONTROL_ALLOCDATA + padding);
	memset(txd, 0, SIZE_TX_CONTROL_ALLOCDATA);

	/* The fact that the length embeds the full header or not is
	 * certainly specified in magic1 (so that we parse with no
	 * backtrack) : find out how. See also examples on input */
	txd->frame_type = cpu_to_le32(txp->frame_type);

	txd->magic4 = cpu_to_le32(txp->magic4);
	txd->magic5 = cpu_to_le32(txp->magic5);

	memcpy(txd->rateset, txp->rateset, 8);

	islsm_txc = (void *) skb_push(skb, SIZE_TX_CONTROL);

	id_magic1 = txp->id_magic1;
	if (padding) {
		txd->frame_align[0] = padding;
		id_magic1 |= ISLSM_TX_PAD_FLAG;
	}

	islsm_txc->id.magic1 = cpu_to_le16(id_magic1);
	islsm_txc->id.length = cpu_to_le16(size);
	islsm_txc->type = cpu_to_le16(txp->req_type);
	islsm_txc->retry1 = txp->retry1;
	islsm_txc->retry2 = txp->retry2;

	islsm_alloc_set_flags(skb, ISLSM_ALLOC_FREE_ON_ACK);
	err = islsm_alloc(&islsm->memory, skb);
	if (err)
		goto exit_free_skb;
	islsm_txc->req_id = LMAC_ADDR_OF_SKB(skb);

	err = islsm_tx_skb(skb);
	if (!err)
		return 0;

	/* error path fall-through */
	islsm_free(&islsm->memory, LMAC_ADDR_OF_SKB(skb));
 exit_free_skb:
	dev_kfree_skb(skb);
	return 0;
}

/* functions for actual sending */
static struct sk_buff *
islsm_skb_alloc(struct islsm *islsm, size_t headroom, unsigned flags)
{
	struct net_device *ndev = NETDEV_OF_ISLSM(islsm);
	struct sk_buff *skb;

	skb = dev_alloc_skb(ISLSM_MAX_FRAME_SIZE + ISLSM_ALIGN_SPACE);
	if (!skb)
		return 0;

	headroom += islsm->device_tx_header_space;

	/* realign buffer */
	/* This naturally makes sure that the beginning of the */
	/* frame will be 32-bits aligned */
	skb_push(skb, skb_headroom(skb));
	skb_trim(skb, 0);
	skb_reserve(skb, headroom);

	skb->dev = ndev;
	islsm_alloc_set_flags(skb, flags);

	return skb;
}

static inline void
islsm_skb_free(struct islsm *islsm, struct sk_buff *skb) {
	dev_kfree_skb(skb);
}

static int
islsm_config_readback(struct islsm *islsm,
		      unsigned offset,
		      unsigned *actual_length, unsigned length)
{
	struct sk_buff *eeprom_packet;
	unsigned header_size, read_size;
	int err;
	readback_t readback_fill;

	FN_ENTER;

	switch(islsm->fw_type) {
	case ISLSM_FW_LM86:
	{
		header_size = sizeof(struct islsm_control_eeprom_lm86);
		readback_fill = islsm_make_readback_skb;
	}
	break;
	case ISLSM_FW_LM87:
	{
		header_size = sizeof(struct islsm_control_eeprom_lm87);
		readback_fill = islsm_make_readback_skb_lm87;
		/* doesn't work with max value -- hangs on the last
		 * packet of size 0x60 -- find the bug, it works for
		 * the windows driver */
		length = min((unsigned) 0x200, length);
	}
	break;
	default:
		err = -EINVAL;
		goto exit;
	}

	read_size = min(length, ISLSM_MAX_CONTROL-header_size);
	*actual_length = read_size;

	eeprom_packet = islsm_skb_alloc(islsm,
					header_size + read_size + SIZE_TX_CONTROL,
					ISLSM_ALLOC_FREE_ON_RESPONSE);
	if (!eeprom_packet)
		return -ENOMEM;

	err = readback_fill(eeprom_packet, offset, read_size);

	if (err)
		islsm_skb_free(islsm,eeprom_packet);

 exit:
	FN_EXIT1(err);
	return err;
}

int
islsm_set_filter(struct islsm *islsm,
		 uint16_t filter_type, const uint8_t *bssid,
		 u8 antenna, uint32_t magic3,
		 uint16_t magic8, uint16_t magic9)
{
	struct sk_buff *filter_packet;
	int err;

	filter_packet = islsm_skb_alloc(islsm, FULL_TX_CONTROL_FILTER,
					ISLSM_ALLOC_FREE_WHEN_SUBMITTED);
	if (!filter_packet)
		return -ENOMEM;

	err = islsm_make_rx_filter_skb(filter_packet, filter_type,
				       bssid, antenna, magic3, magic8, magic9);
	if (err)
		islsm_skb_free(islsm,filter_packet);
	return err;
}

int
islsm_stats_readback(struct islsm *islsm)
{
	struct sk_buff *stats_packet;
	int err;

	stats_packet = islsm_skb_alloc(islsm,
				       SIZE_TX_CONTROL + ISLSM_SIZE_STATS,
				       ISLSM_ALLOC_FREE_ON_RESPONSE);
	if (!stats_packet)
		return -ENOMEM;

	/* leave room for the device to write */
	skb_push(stats_packet, ISLSM_SIZE_STATS);
	err = islsm_make_prism_header_skb(stats_packet,
					  ISLSM_ID_EEPROM_READBACK,
					  ISLSM_TX_CONTROL_TYPE_STAT_READBACK,
					  0, 0);
	if (err)
		islsm_skb_free(islsm,stats_packet);

	return err;
}

int
islsm_freq_change(struct islsm *islsm,
		  uint16_t channel, uint16_t freq,
		  uint16_t magic1, uint16_t magic2)
{
	struct sk_buff *channel_packet;
	size_t mysize;
	int err;

	mysize =
	    islsm->device_version ==
	    ISLSM_DEVICE_USB_VER1 ? FULL_TX_CONTROL_CHANNEL :
	    FULL_TX_CONTROL_CHANNEL_VER2;

	/* to be checked, actually */
	channel_packet = islsm_skb_alloc(islsm, mysize,
					 ISLSM_ALLOC_FREE_WHEN_SUBMITTED);

	if (!channel_packet)
		return -ENOMEM;

	err = islsm_make_tx_control_channel_skb(channel_packet,
						channel, freq, magic1, magic2);
	if (err)
		islsm_skb_free(islsm,channel_packet);

	return err;
}

int
islsm_data_tx(struct islsm *islsm, struct sm_tx_p *txp,
	      struct sk_buff *skb)
{
	struct net_device *netdev = NETDEV_OF_ISLSM(islsm);
	int err = 0;
	uint8_t padding = 0;
	size_t headroom_needed = FULL_TX_CONTROL_ALLOCDATA + TX_MAX_PADDING +
		islsm->device_tx_header_space;

	/* FIXME: maybe this should be done beforehand */
	skb->dev = netdev;

	/* at this step we make use of the padding to realign
	   beginning of the packet to a multiple of 32 bits */
	padding = (uint8_t)((long) skb->data - FULL_TX_CONTROL_ALLOCDATA) & 3;
	islog(L_SM_OUTPUT, "data length %i, padding %i\n", skb->len, padding);

	/* TODO: once everything is sorted out, get rid of this check --
	   skb_cow already does it */
	if (skb_headroom(skb) < headroom_needed) {
		/* This should not be triggered once we correctly set hard_header_len */
		islog(L_DEBUG, "tx skb: headroom too small\n");
	}

	/* skb_cow changes the skb state. Can we still return an error
	   after this ? -- actually we never do this, but just in
	   case, i'd like to know */
	err = skb_cow(skb, headroom_needed);
	if (err) {
		islog(L_DEBUG, "dropped tx skb do to error on cow\n");
		goto bad;
	}

	/* If the txqueue if full, then we should return an error, so
	 * that the skb is freed, and free the lmac tx queue */
	return islsm_make_tx_datapkt_skb(skb, padding, txp);

      bad:
	/* we propagate the error, therefore the upper layers
	   are responsible for freeing the skb */
	return err;
}

int
islsm_empty_queue(struct islsm *islsm, uint8_t queue_id)
{
	struct sk_buff *free_packet;
	int err;
	/* to be checked */
	free_packet = islsm_skb_alloc(islsm, FULL_TX_CONTROL_FREEQUEUE,
				      ISLSM_ALLOC_FREE_WHEN_SUBMITTED);
	err = islsm_make_freequeue_skb(free_packet, queue_id);
	if (err)
		islsm_skb_free(islsm,free_packet);
	return err;
}

/* transmit a ping frame to the device -- freemac-testing purpose */
int
islsm_ping_device(struct islsm *islsm, unsigned length)
{
	struct sk_buff *ping_packet;
	char *payload;
	int err;

	ping_packet = islsm_skb_alloc(islsm, SIZE_TX_CONTROL + length,
				      ISLSM_ALLOC_FREE_ON_RESPONSE);
	/* fill in the payload */
	payload = (char *) skb_push(ping_packet, length);
	memset(payload,0x69,length);
	err = islsm_make_prism_header_skb(ping_packet, ISLSM_ID_EEPROM_READBACK,
					  ISLSM_TX_CONTROL_TYPE_PING, 0, 0);
	if (err)
		islsm_skb_free(islsm,ping_packet);

	return err;
}
EXPORT_SYMBOL(islsm_ping_device);

/* transmits a raw frame to the device */
int
islsm_outofband_msg(struct islsm *islsm, void *buf, size_t size)
{
	struct sk_buff *skb = islsm_skb_alloc(islsm, 0,
					      ISLSM_ALLOC_FREE_WHEN_SUBMITTED);
	int err;

	if (!skb)
		return -ENOMEM;

	memcpy(skb->data, buf, size);
	skb_put(skb, size);

	err = islsm_alloc(&islsm->memory, skb);
	if (err)
		return err;

	err = islsm->isl_tx(skb);
	if (err)
		islsm_free(&islsm->memory, LMAC_ADDR_OF_SKB(skb));

	return err;
}

static void
timeout_complete(unsigned long data)
{
	struct islsm *islsm = (struct islsm *) data;
	islsm->wait_flag = -ETIMEDOUT;
	islog(L_DEBUG, "islsm: timeout waiting\n");
	complete(&islsm->dev_init_comp);
	return;
}

int
islsm_wait_timeout(struct islsm *islsm, unsigned int delay)
{
	struct timer_list timer;

	init_timer(&timer);
	timer.expires = jiffies + delay;
	timer.data = (unsigned long) (islsm);
	timer.function = timeout_complete;

	islsm->wait_flag = 0;

	add_timer(&timer);
	wait_for_completion(&islsm->dev_init_comp);
	del_timer_sync(&timer);

	return islsm->wait_flag;
}

EXPORT_SYMBOL(islsm_wait_timeout);

/* this function reads back the serial eeprom contents via the feature
 * built in intersil firmwares
 */

static int
islsm_eeprom_readback(struct islsm *islsm)
{
	const size_t eeprom_size = ISLSM_EEPROM_SIZE;
	unsigned len = 0;
	int remains;
	int err = 0;

	FN_ENTER;

	/* We don't redo download if the eeprom has
	   already been loaded */
	if (islsm->eeprom)
		goto out_noerr;

	/* This allocate 8K, the size of the eeprom */
	islsm->eeprom = vmalloc(eeprom_size);
	if (!islsm->eeprom) {
		err = -ENOMEM;
		goto out_err;
	}
	islsm->eeprom_size = eeprom_size;

	/* EEPROM seems to be always 8Kb */
	remains = eeprom_size;

	/* Each iteration will trigger a pipe response */
	/* We got a problem here, Olivier's computer is too fast ! --
	 * either we wait for the block to be returned, or we allocate
	 * other pipes to make the requests */
	while (remains > 0) {
		/* try to read as much as possible, not exceeding
		 * remains */
		err = islsm_config_readback(islsm, eeprom_size - remains, &len, remains);
		if (err)
			goto out_err;

		/* Wait for the device's response before returning :
		 * we reuse the dev_init_comp semaphore */
		err = islsm_wait_timeout(islsm, 2 * HZ);
		if (err)
			goto out_err;

		remains -= len;
	}

	err = islsm_parse_eeprom(islsm);
	if (!err) {
		goto out_noerr;
	}

	printk(KERN_ERR "unable to parse the eeprom contents\n");
 out_err:
	vfree(islsm->eeprom);
	islsm->eeprom = 0;
 out_noerr:
	FN_EXIT1(err);
	return err;
}

/* get and parse the PDA */
int
islsm_get_pda(struct islsm *islsm) {
	int err;
	if (!islsm->pda) {
		/* USB and PCI read back the PDA which is written in
		   an eeprom beside the wifi chip */
		err = islsm_eeprom_readback(islsm);
		if (err)
			goto exit;
	}
	/* pda is available, so parse it now */
	err = islsm_parse_pda(islsm);
 exit:
	return err;
}

static void
islsm_conf_leds(struct islsm *islsm,
		uint16_t mode, uint16_t perm_setting,
		uint16_t temp_setting, uint16_t duration) {
	struct sk_buff *ledskb;
	int err;

	FN_ENTER;

	ledskb = islsm_skb_alloc(islsm, FULL_TX_CONTROL_LED,
				 ISLSM_ALLOC_FREE_WHEN_SUBMITTED);
	if (!ledskb)
		return;

	err = islsm_make_control_led_skb(ledskb, mode, perm_setting,
					 temp_setting, duration);
	if (err)
		islsm_skb_free(islsm,ledskb);

	FN_EXIT0;
}

/* make a temporary setting */
void islsm_led_temp(struct islsm *islsm,
		   uint16_t temp_setting, unsigned ms) {
	unsigned setting = islsm->led_setting;
	islsm_conf_leds(islsm,
			       islsm->led_mode, setting,
			       setting ^ temp_setting, ms);
}

void islsm_led_perm(struct islsm *islsm,
		   uint16_t mode, uint16_t perm_setting) {
	islsm->led_mode = mode;
	islsm->led_setting = perm_setting;
	/* don't care if it does not succeed, this is non-critical
	   and will be set right on the next call */
	islsm_conf_leds(islsm, mode, perm_setting, 0, 0);
}

/*
 * BBP registers IO through firmware.
 * Needed for N770 support.
 */

static int
islsm_send_control_skb(struct sk_buff *skb,
		       const u16 type, const int write__read) {
	int flags = ISLSM_ID_CONTROL;
	if (!write__read)
		flags |= ISLSM_ID_CTRL_FLAG_READ;
	return islsm_make_prism_header_skb(skb, flags, type, 0, 0);
}

static int
islsm_bbp_io(struct islsm *islsm, const int write__read,
	     unsigned reg, unsigned val) {
	struct sk_buff *bbp_skb;
	struct islsm_control_bbp *bbpio;
	const int flag = write__read ? ISLSM_ALLOC_FREE_WHEN_SUBMITTED :
		ISLSM_ALLOC_FREE_ON_RESPONSE;
	int err;

	FN_ENTER;

	bbp_skb = islsm_skb_alloc(islsm, FULL_CONTROL_BBP, flag);
	if (!bbp_skb) {
		err = -ENOMEM;
		goto exit;
	}
	bbpio = (void *) skb_push(bbp_skb, sizeof(*bbpio));
	bbpio->reg = reg;
	bbpio->val = val;

	err = islsm_send_control_skb(bbp_skb, ISLSM_TX_CONTROL_TYPE_BBP,
				     write__read);
	if (err)
		islsm_skb_free(islsm,bbp_skb);

 exit:
	FN_EXIT1(err);
	return err;
}

int
islsm_bbp_write(struct islsm *islsm, unsigned reg, unsigned val)
{
	return islsm_bbp_io(islsm, 1, reg, val);
}
EXPORT_SYMBOL(islsm_bbp_write);

int
islsm_bbp_read(struct islsm *islsm, unsigned reg, unsigned *val)
{
	int err;
	err = islsm_bbp_io(islsm, 0, reg, 0);
	if (err)
		goto exit;

	/* Wait for the read to complete,
	   read back the value */
	err = islsm_wait_timeout(islsm, 2 * HZ);
	if (err)
		goto exit;

	*val = islsm->bbp_reg_val;
 exit:
	return err;
}
EXPORT_SYMBOL(islsm_bbp_read);

static void __attribute__ ((unused))
islsm_params_print(struct sm_tx_p *params)
{
#define PRINT_FIELD(name,type) printk(# name " is %" # type "\n", params->name )
	PRINT_FIELD(ack_timer, i);
	PRINT_FIELD(id_magic1, i);
	PRINT_FIELD(req_type, i);
	PRINT_FIELD(retry1, i);
	PRINT_FIELD(retry2, i);
/* 	PRINT_FIELD(rateset[8],i); */
	PRINT_FIELD(magic4, i);
	PRINT_FIELD(magic5, i);
	PRINT_FIELD(frame_type, i);
}

/* fill default protocol values */
void
islsm_params_init(struct islsm *islsm)
{
#define FILL_TXP(txp,ack,id,r_type,w_r1,w_r2,m4,m5,f_type) \
	{ \
	(txp)->ack_timer = ack; \
	(txp)->id_magic1 = id; \
	(txp)->req_type = r_type; \
	(txp)->retry1 = w_r1; \
	(txp)->retry2 = w_r2; \
	(txp)->magic4 = m4; \
	(txp)->magic5 = m5; \
	(txp)->frame_type = f_type; }

	/* sending of data packets */
	FILL_TXP(&(islsm->smpar.data),
		 ISLSM_ACK_TIMER, ISLSM_TX_DATA_ID,
		 ISLSM_TX_CONTROL_TYPE_DATAACK,
		 7, 7,
		 ISLSM_TX_FRAME_MAGIC4_MGMT, ISLSM_TX_FRAME_MAGIC5_MGMT,
		 ISLSM_FRAME_DATA);

	/* sending of probe packets */
	FILL_TXP(&(islsm->smpar.probe),
		 0, ISLSM_TX_MGMT_ID,
		 ISLSM_TX_CONTROL_TYPE_DATANOACK,
		 1, 1, 0, 0, ISLSM_FRAME_PROBE);

	/* sending of beacon packets */
	FILL_TXP(&(islsm->smpar.beacon),
		 0, ISLSM_TX_BEACON_ID,
		 ISLSM_TX_CONTROL_TYPE_DATANOACK,
		 1, 1, 0, 0, ISLSM_FRAME_BEACON);

	/* generic mgmt frame */
	FILL_TXP(&(islsm->smpar.mgmt),
		 ISLSM_ACK_TIMER, ISLSM_TX_MGMT_ID,
		 ISLSM_TX_CONTROL_TYPE_DATAACK,
		 7, 7,
		 ISLSM_TX_FRAME_MAGIC4_MGMT, ISLSM_TX_FRAME_MAGIC5_MGMT,
		 ISLSM_FRAME_MGMT);

	/* other values ? */

#undef FILL_TXP
}
