/*
  Copyright 2004, 2005 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

*/

#ifndef _HAVE_ISL_SM_H_
#define _HAVE_ISL_SM_H_

#include <linux/netdevice.h>
#include <linux/wireless.h>
#include <linux/list.h>
#include <linux/firmware.h>

#ifdef MADWIFI
#include <linux/version.h>
#include "net80211/if_media.h"
#include "net80211/ieee80211_var.h"
#else
#include <net/ieee80211softmac.h>
#define PCIUART 1
#endif				/* MADWIFI */

#include "islsm_protocol.h"
#include "islsm_alloc.h"
#include "islsm_uart.h"
#include "islsm_bra.h"

enum islsm_state {
	/* in this state we just received the plug event */
	ISLSM_PROBE = 0,
	/* communication bridge is ok (usb, pci) */
	ISLSM_BRIDGE_OK,
	/* rom is ok -- the uart protocol is ok */
	ISLSM_ROM_OK,
	/* firmware is being uploaded */
	ISLSM_FW_UPLOAD,
	/* freemac has been uploaded and is ok */
	ISLSM_FREEMAC,
	/* softmac has been uploaded and is ok */
	ISLSM_SOFTMAC,
};

/* callback typedefs */
typedef int (*islsm_tx_t) (struct sk_buff *);

struct islsm {

	/* intersil-firmware specific state machine
	 * variables
	 */

	/* islsm instances management */
	unsigned                minor;
	unsigned                islsm_state;

	/*
	 * Parameters set by the BRA parsing
	 */
	/* LMAC type */
	enum islsm_fw_type      fw_type;
	/* LMAC memory mapping */
	/* Generic LMac framespace */
	uint32_t                frame_mem_start;
	uint32_t                frame_mem_end;
	/* specialized LMac framespace */
	/* reserved space for control frames */
	uint32_t                txframe_mem_start;
	uint32_t                txframe_mem_end;
	/* reserved space for rx frames */
	uint32_t                rxframe_mem_start;
	/* memory allocator proper */
	mem_descr_t             memory;

	/* Rate index table */
	/* The frame format uses indexes in a table for specifying the
	 * sending rate of various frames. This rate table is present in
	 * the BRA. We mirror it here for use as a lookup table */
	char                    bra_rate_table[ISLSM_BRA_RATE_TABLE_SIZE];

	/*
	 * Other parameters (sort this mess)
	 */

	/* firmware name */
#define ISLSM_FW_NAME_LEN 33
	char                    firmware_name[ISLSM_FW_NAME_LEN + 1];

	/* LEDs state */
	unsigned                led_mode;
	unsigned                led_setting;

	/* bbp readback temporary */
	unsigned                bbp_reg_val;

	/* reserve this much space ahead of the skb for the needs of the
	   hardware tx queues */
	size_t                  device_tx_header_space;
	/* sm_tx_header_space to be added */
	/* sm_rx_header_space to be added (for monitor mode) */

	enum islsm_versions     device_version;
	/* signalled when particular states in the configuration have
	   been reached : init interrupt, then mgmt readback end */
	struct completion       dev_init_comp;
	int                     wait_flag;

	struct sm_p             smpar;
	short                   soft_rxfilter;
	u8                      filter_rateset[8];

	/*
	 * Parameters and data set by parsing of the EEPROM contents
	 */

	/* number of rates defined in the readback */
	uint8_t                 pa_points_per_curve;

	const char              *pda;
	uint8_t                 *eeprom;
	size_t                  eeprom_size;
	size_t                  pda_offset;
	size_t                  pda_length;
	/* FIXME : these arrays are *huge* (255 elements) -- we should
	 * get rid of them */
#ifdef MADWIFI
	struct wlan_pda_output_limits_channel_rev0_s output_pwr_limits[IEEE80211_CHAN_MAX];
	struct fw_cal_data_sample finfo4[IEEE80211_CHAN_MAX][ISLSM_POINTS_PER_CURVE];
	struct wlan_pda_iq_autocal_s    finfo6[IEEE80211_CHAN_MAX];
#else				/* MADWIFI */
	struct wlan_pda_output_limits_channel_rev0_s output_pwr_limits[ISLSM_NR_CHANNELS + 1];
	struct fw_cal_data_sample finfo4[ISLSM_NR_CHANNELS + 1][ISLSM_POINTS_PER_CURVE];
	struct wlan_pda_iq_autocal_s    finfo6[ISLSM_NR_CHANNELS + 1];
#endif
	unsigned int		last_chan;

#ifdef MADWIFI
	struct ieee80211com     sc_ic;
	struct ieee80211_beacon_offsets islsm_boff;
	int                     (*ieee80211_newstate) (struct ieee80211com *,
						       enum ieee80211_state,
						       int);
	struct timer_list       scan_ch;	/* AP scan timer */
#endif				/* MADWIFI */

/* 	struct timer_list       stats_update; */
	struct net_device_stats statistics;
#ifdef CONFIG_NET_WIRELESS
	struct iw_statistics    iwstatistics;
#endif				/* CONFIG_NET_WIRELESS */

	/* device-specific state machine and callbacks
	   We handle three types of devices :
	   - usb first generation
	   - usb second generation
	   - pci devices
	   These hooks are in no mean set in stone; they are here for
	   experimentation and will give way to a cleaner approach once
	   we have a general picture of how things work.
	 */

	/* boots the device */
	int                     (*isl_boot) (struct islsm *);
	int                     (*isl_stop) (struct net_device *);
	/* transmits the data in the skb to the lmac */
	islsm_tx_t              isl_tx;

	/* device operations */
	int                     (*isl_romboot) (struct islsm *);
	int                     (*isl_load_fw) (struct islsm *,
						const struct firmware *);

	/* uart structure and callbacks */
#ifdef PCIUART
	uart_instance_t         uart;
	void                    (*uart_prot_init) (struct islsm *);
	void                    (*uart_cts) (struct islsm *);
	void                    (*uart_prot_exit) (struct islsm *);
#endif				/* PCIUART */

	/* low-level stuff */
	/* read and write from the pci side */
	uint32_t                (*isl_read_pcireg) (struct islsm *,
						    uint32_t address);
	void                    (*isl_write_pcireg) (struct islsm *,
						     uint32_t value,
						     uint32_t address);
	uint32_t                (*isl_read_devmem) (struct islsm *,
						    uint32_t address);
	void                    (*isl_write_devmem) (struct islsm *,
						     uint32_t value,
						     uint32_t address);

	/*
	 * It should not be needed as we can fetch it with an offsetof
	 * macro. But this is safer in case allocations ever get out-of-line.
	 */
	struct net_device      *netdev;
#ifndef MADWIFI
	struct ieee80211_device *ieee;
	struct ieee80211softmac_device *softmac;
#endif

	/* This is the structure below us, filled with p54u or islpci
	 * device reference */
	/* This must be the last item so that it points to the data
	 * allocated beyond this structure by alloc_islsm */
	u8 priv[];
};

/*
 * These functions are modelled after the netdev and ieee80211 ones.
 * The train of structs for softmac:
 *  [netdev][ieee80211][ieee80211softmac][islsm][p54u]
 */

#ifdef MADWIFI
#define NETDEV_OF_ISLSM(device) ((device)->netdev)
#define ISLSM_OF_NETDEV(device) ((struct islsm *) netdev_priv(device))
#define SET_NETDEV(device,netdevice)  ((device)->netdev = (netdevice))
extern inline void *islsm_priv(struct net_device *dev) {
	return ((struct islsm *)netdev_priv(dev))->priv;
}

#else
#define NETDEV_OF_ISLSM(device) ((device)->netdev)
#define ISLSM_OF_NETDEV(device) ((struct islsm *) ieee80211softmac_priv(device))
static inline void *islsm_priv(struct net_device *dev) {
	return ((struct islsm *)ieee80211softmac_priv(dev))->priv;
}

#endif /* MADWIFI */

/*
 * Control block used by islsm
 */
struct islsm_cb {
	struct islsm_alloc_cb alloc;
};

struct net_device *alloc_islsm(int sizeof_priv);
void free_islsm(struct net_device *dev);
int register_islsm(struct net_device *netdev);
void unregister_islsm(struct net_device *netdev);

int islsm_mode_set_filter(struct net_device *netdev, int mode);

extern int              islsm_wait_timeout(struct islsm *islsm,
					   unsigned int delay);

/* now the helper functions, for sending packets */
int                     islsm_outofband_msg(struct islsm *islsm,
					    void *buf, size_t size);

int                     islsm_stats_readback(struct islsm *islsm);

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

int                     islsm_freq_change(struct islsm *islsm,
					  uint16_t channel, uint16_t freq,
					  uint16_t magic1, uint16_t magic2);

int                     islsm_data_tx(struct islsm *islsm, struct sm_tx_p *txp,
				      struct sk_buff *skb);

int                     islsm_get_pda(struct islsm *islsm);

void                    islsm_led_perm(struct islsm *islsm,
				       uint16_t mode, uint16_t perm_setting);
void                    islsm_led_temp(struct islsm *islsm,
				       uint16_t temp_setting, unsigned ms);

int                     islsm_ping_device(struct islsm *islsm, unsigned length);

int                     islsm_bbp_write(struct islsm *islsm, unsigned reg, unsigned val);
int                     islsm_bbp_read(struct islsm *islsm, unsigned reg, unsigned *val);


/* now the helper functions, for receiving packets */
void                    islsm_data_input(struct sk_buff *skb);
void                    islsm_bootup_input(struct sk_buff *skb);
int                     islsm_parse_eeprom(struct islsm *islsm);
int                     islsm_parse_pda(struct islsm *islsm);

int                     islsm_request_firmware(const struct firmware **fw,
					       struct islsm *device);

/* protocol settings */
void                    islsm_params_init(struct islsm *islsm);

/* helper function to get rid of -- replace with a standard one from one
 * of the stacks */
static inline unsigned int
islsm_ref_to_chan(unsigned int ref)
{
	/* FIXME : only works for the B/G band, we now need it for the A
	   band */
#ifdef MADWIFI
	return ieee80211_mhz2ieee(ref, IEEE80211_CHAN_2GHZ);
#else
	/* kludgy as hell */
	unsigned int out;
#define ISLSM_REF_CHAN0 0x0967
	if (ref <= ISLSM_REF_CHAN0) {
		printk(KERN_ERR
		       "%s: Problem requesting channel of ref %i : too small\n",
		       "islsm", ref);
		return 0;
	}
	out = (ref - ISLSM_REF_CHAN0) / 5;
	if (out > 14) {
		if (ref == 2484)
			return 14;
		printk(KERN_ERR
		       "%s: Problem requesting channel of ref %i : "
		       "channel %i too big\n", "islsm", ref, out);
		return 0;
	}
	return out;
#undef ISLSM_REF_CHAN0
#endif				/* MADWIFI */
}

int			islsm_chan_to_freq(unsigned int chan);
unsigned int		islsm_freq_to_chan(int freq);

/* Helper function for the hardware stacks, to be used to free a
   TX skb successfully transmitted to the umac */
static inline void
islsm_txskb_free(struct sk_buff *skb) {
	struct net_device *netdev = skb->dev;
	struct islsm *islsm = ISLSM_OF_NETDEV(netdev);
	uint32_t lmac_addr = LMAC_ADDR_OF_SKB(skb);
	unsigned flags = LMAC_ALLOC_FLAGS(skb);

	if (flags & ISLSM_ALLOC_FREE_WHEN_SUBMITTED)
		islsm_free(&islsm->memory, lmac_addr);

	dev_kfree_skb_irq(skb);
}

#endif				/* _HAVE_ISL_SM_H_ */
