/*
  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 "islsm_bra.h"
#include "islsm_log.h"
#include "isl_sm.h"

#ifdef MADWIFI
#define DRV_NAME "islsm"
#else
#include "islusb_dev.h"
#endif

#define U16_OF_STRUCT(x,struc) uint16_t x = le16_to_cpu(struc->x)
void islsm_print_exp_if(struct s_bootrec_exp_if *exp_if, unsigned int loglvl) {
	U16_OF_STRUCT(role,exp_if);
	U16_OF_STRUCT(if_id,exp_if);
	U16_OF_STRUCT(variant,exp_if);
	U16_OF_STRUCT(btm_compat,exp_if);
	U16_OF_STRUCT(top_compat,exp_if);

	islog(loglvl, "exposed interface : "
	      "role %i, if_id %i variant %i, "
	      "bottom_compat %i, top_compat %i\n",
	      role, if_id, variant,
	      btm_compat, top_compat);
}

static void bootrec_dep_if(struct s_bootrec_dep_if *dep_if) {
	U16_OF_STRUCT(role,dep_if);
	U16_OF_STRUCT(if_id,dep_if);
	U16_OF_STRUCT(variant,dep_if);

	islog(L_FW, "exported interface : "
	      "role %i, if_id %i variant %i",
	      role, if_id, variant);
}
#undef U16_OF_STRUCT

/* Intersil's BootRecordArea parsing function.
 *
 * This function should be able to check that the firmware is correct for
 * the device. It also should set the islsm parameters accordingly, maybe
 * looking into this header.
 */
static int
islsm_parse_firmware(struct islsm *islsm,
		     const void *const data, const unsigned len)
{
	struct s_bootrecord *bootrec = 0;
	int err = 0;
	int end = 0;
	uint32_t *fw_data = (uint32_t *) data;
	const uint32_t *fw_end_data = fw_data + len / sizeof(*fw_data);

	FN_ENTER;

	/* the start of the firmware file is filled with ARM code.
	   skip it. */
	while (fw_data < fw_end_data && *fw_data != 0)
		fw_data++;

	/* find the beginning of umac header after a section of zero
	 * padding */
	while (fw_data < fw_end_data && *fw_data == 0)
		fw_data++;

	/* we've normally reached the first bootrec */
	bootrec = (struct s_bootrecord *)fw_data;
	islog(L_FW, "found start of umac description at offset %td\n",
	      fw_data - (uint32_t *)data);

	while (!end) {
		unsigned length;
		uint32_t code;
		char *bdata;

		/* Security check */
		err = safe_bootrecord(bootrec, (char*)fw_end_data);
		if (err) {
			islog(L_FW, "unsafe bootrecord, aborting\n");
			break;
		}

		err = valid_bootrecord(bootrec);
		if (err) {
			islog(L_FW, "invalid bootrecord, aborting\n");
			break;
		}

		length = le32_to_cpu(bootrec->hdr.length) * sizeof(uint32_t);
		code = le32_to_cpu(bootrec->hdr.code);
		bdata = bootrec->data;

		switch (code) {
		case BR_CODE_COMPONENT_ID:
		{
			uint32_t id = ((struct s_bootrec_comp_id*)bootrec)->id;
			switch (be32_to_cpu(id)) {
			case FW_FMAC:
				islog(L_FW, "Uploading FreeMAC firmware\n");
				islsm->fw_type = ISLSM_FW_FMAC;
				break;
			case FW_LM20:
				islog(L_FW, "Uploading LM20 firmware\n");
				islsm->fw_type = ISLSM_FW_LM20;
				break;
			case FW_LM86:
				islog(L_FW, "Uploading LM86 firmware\n");
				islsm->fw_type = ISLSM_FW_LM86;
				break;
			case FW_LM87:
				islog(L_FW, "Uploading LM87 firmware\n");
				islsm->fw_type = ISLSM_FW_LM87;
				/* reserve space for the CRC */
				islsm->device_tx_header_space += sizeof(u32);
				/* in case we're dealing with 3887
				 * devices, we must also include
				 * headroom for the address -- possibly
				 * this should be done for all frames */
				islsm->device_tx_header_space += sizeof(u32);
				break;
			default:
				printk(KERN_WARNING "Uploading unsupported firmware signature %08x\n",
				       id);
			}
			break;
		}
		case BR_CODE_COMPONENT_VERSION:
			islog(L_FW, "Component version: '%.*s'\n", length, bdata);
			break;
		case BR_CODE_DESCR:
		{
			struct s_bootrec_descr *descr= (struct s_bootrec_descr *)bdata;
			int16_t b0 = le16_to_cpu(descr->bound0);
			int16_t b1 = le16_to_cpu(descr->bound1);
			int16_t v0 = le16_to_cpu(descr->val0);
			int16_t v1 = le16_to_cpu(descr->val1);
			islsm->frame_mem_start = le32_to_cpu(descr->frame_mem_start);
			islsm->frame_mem_end   = le32_to_cpu(descr->frame_mem_end);
			memcpy(islsm->bra_rate_table,descr->rate_table,ISLSM_BRA_RATE_TABLE_SIZE);

			/* Unknown fields */
			islog(L_FW, "descriptor option, frame memory space [%x,%x],\n"
			      " unk1 is %08x, bounds are [%i,%i]"
			      " and associated values [%i,%i]\n",
			      islsm->frame_mem_start,
			      islsm->frame_mem_end,
			      le32_to_cpu(descr->unk1),
			      b0, b1, v0, v1);
			break;
		}
		case BR_CODE_EXPOSED_IF:
		{
			int i;
			struct s_bootrec_exp_if *exp_if= (void *) bdata;
			int no = nof_exp_interfaces(bootrec);
			if (no < 0)
				break;
			for(i = 0; i < no; i++)
				islsm_print_exp_if(exp_if++,L_FW);
			break;
		}
		case BR_CODE_DEPENDENT_IF:
			/* this should make the difference between usb
			   and pci devices, for instance */
		{
			int i;
			struct s_bootrec_dep_if *dep_if= (void *) bdata;
			int no = nof_dep_interfaces(bootrec);
			if (no < 0)
				break;
			for(i = 0; i < no; i++)
				bootrec_dep_if(dep_if++);
			break;
		}
		case BR_CODE_END_OF_BRA:
		case LEGACY_BR_CODE_END_OF_BRA:
		{
			struct s_bootrec_end *bootrec_end = (struct s_bootrec_end *)bootrec;
			islog(L_FW, "final option CRC %08x\n",
			      le16_to_cpu(bootrec_end->CRC));
			end = 1;
			break;
		}
		default:
			islog(L_FW, "unknown option %08x length %08x, dumped:\n",
			      code, length);
			islog_bytes(L_FW,bdata,length);
		}

		bootrec = next_bootrecord(bootrec);
	}

	/* we've reached the version string, print it */
	if (end) {
		islog(L_FW, "firmware version string: '%.*s'\n",
		      FW_VERSION_STRING_LEN, (char*) bootrec);
	}

	FN_EXIT1(err);
	return err;
}

int islsm_request_firmware(const struct firmware **fw, struct islsm *islsm) {
	struct net_device *netdev = NETDEV_OF_ISLSM(islsm);
	const char *fw_name = islsm->firmware_name;
	const struct firmware *fw_entry;
	int err;

	FN_ENTER;

	err = request_firmware(fw, fw_name, netdev->class_dev.dev);
	if (err) {
		printk(KERN_ERR "%s: Request firmware for '%s' failed: %i\n",
		       DRV_NAME, fw_name, err);
		goto exit;
	}

	fw_entry = *fw;

	/* parse the firmware */
	err = islsm_parse_firmware(islsm,fw_entry->data,fw_entry->size);
	if (err) {
		printk(KERN_ERR "%s: Firmware parsing failed: %i\n",
		       DRV_NAME, err);
		goto exit_release_firmware;
	}

	islsm->rxframe_mem_start = islsm->frame_mem_end - ISLSM_RXSPACE;

	/* initialize the memory allocator accordingly */
	err = islsm_alloc_create(&islsm->memory,
				 islsm->frame_mem_start,
				 islsm->rxframe_mem_start);
	if (err) {
		printk(KERN_ERR "%s: unable to setup memory allocator\n",
		       DRV_NAME);
		goto exit_release_firmware;
	}

	FN_EXIT1(0);
	return 0;

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

EXPORT_SYMBOL(islsm_request_firmware);
