692 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			692 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* * This is part of the Sequans SQN1130 driver.
 | |
|  * Copyright 2008 SEQUANS Communications
 | |
|  * Written by Dmitriy Chumak <chumakd@gmail.com>
 | |
|  *
 | |
|  * 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, or (at
 | |
|  * your option) any later version.
 | |
|  */
 | |
| 
 | |
| 
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/string.h>
 | |
| #include <linux/firmware.h>
 | |
| #include <linux/byteorder/generic.h>
 | |
| 
 | |
| #include "sdio.h"
 | |
| #include "msg.h"
 | |
| #include "sdio-sqn.h"
 | |
| #include "sdio-netdev.h"
 | |
| #include "version.h"
 | |
| #include "sdio-fw.h"
 | |
| 
 | |
| char	*fw1130_name = "sqn1130.bin";
 | |
| char	*fw1210_name = "sqn1210.bin";
 | |
| 
 | |
| 
 | |
| /* Tag, Lenght, Value struct */
 | |
| struct sqn_tlv {
 | |
| 	u32	tag;
 | |
| #define SWM_INFO_TAG_SQN_ROOT		0x80000000	/* SEQUANS root tag */
 | |
| #define SWM_INFO_TAG_SQN_MEMCPY		0x80000005	/* SEQUANS memcpy tag */
 | |
| #define SWM_INFO_TAG_SQN_MEMSET		0x80000006	/* SEQUANS memset tag */
 | |
| #define SWM_INFO_TAG_SQN_BOOTROM_GROUP	0x80040000
 | |
| #define SWM_INFO_TAG_SQN_ID_GROUP	0x80010000	/* SEQUANS identification group tag */
 | |
| #define SWM_INFO_TAG_SQN_MAC_ADDRESS	0x80010010	/* SEQUANS mac address tag */
 | |
| 	u32	length;
 | |
| 	u8	value[0];
 | |
| };
 | |
| 
 | |
| 
 | |
| /* body of SWM_INFO_TAG_SQN_MEMCPY tag */
 | |
| struct sqn_tag_memcpy {
 | |
| 	u32	address;
 | |
| 	u32	access_size;
 | |
| 	u32	data_size;
 | |
| 	u8	data[0];
 | |
| };
 | |
| 
 | |
| 
 | |
| /* body of SWM_INFO_TAG_SQN_MEMSET tag */
 | |
| struct sqn_tag_memset {
 | |
| 	u32	address;
 | |
| 	u32	access_size;
 | |
| 	u32	size;
 | |
| 	u8	pattern;
 | |
| };
 | |
| 
 | |
| 
 | |
| #define SQN_1130_SDRAM_BASE      0x00000000
 | |
| #define SQN_1130_SDRAM_END       0x03FFFFFF
 | |
| #define SQN_1130_SDRAMCTL_BASE   0x4B400000
 | |
| #define SQN_1130_SDRAMCTL_END    0x4B4003FF
 | |
| 
 | |
| #define SQN_1210_SDRAM_BASE      0x00000000
 | |
| #define SQN_1210_SDRAM_END       0x07FFFFFF
 | |
| #define SQN_1210_SDRAMCTL_BASE   0x20002000
 | |
| #define SQN_1210_SDRAMCTL_END    0x2000207F
 | |
| 
 | |
| static int is_good_ahb_address(u32 address, enum sqn_card_version card_version)
 | |
| {
 | |
| 	u32 sdram_base = 0;
 | |
| 	u32 sdram_end = 0;
 | |
| 	u32 sdram_ctl_base = 0;
 | |
| 	u32 sdram_ctl_end = 0;
 | |
| 	int status = 0;
 | |
| 
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	if (address % 4)
 | |
| 		return 0;
 | |
| 
 | |
| 	if (SQN_1130 == card_version) {
 | |
| 		sqn_pr_dbg("using 1130 AHB address boundaries\n");
 | |
| 		sdram_base	= SQN_1130_SDRAM_BASE;
 | |
| 		sdram_end	= SQN_1130_SDRAM_END;
 | |
| 		sdram_ctl_base	= SQN_1130_SDRAMCTL_BASE;
 | |
| 		sdram_ctl_end	= SQN_1130_SDRAMCTL_END;
 | |
| 	} else if (SQN_1210 == card_version) {
 | |
| 		sqn_pr_dbg("using 1210 AHB address boundaries\n");
 | |
| 		sdram_base	= SQN_1210_SDRAM_BASE;
 | |
| 		sdram_end	= SQN_1210_SDRAM_END;
 | |
| 		sdram_ctl_base	= SQN_1210_SDRAMCTL_BASE;
 | |
| 		sdram_ctl_end	= SQN_1210_SDRAMCTL_END;
 | |
| 	} else {
 | |
| 		sqn_pr_warn("Can't check AHB address because of unknown"
 | |
| 			" card version\n");
 | |
| 		status = 0;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	status = ((sdram_base <= address && address < sdram_end)
 | |
| 			|| (sdram_ctl_base <= address && address < sdram_ctl_end));
 | |
| out:
 | |
| 	sqn_pr_leave();
 | |
| 	return status;
 | |
| }
 | |
|  
 | |
| // Fix big buffer allocation problem during Firmware loading
 | |
| /**
 | |
|  *	sqn_alloc_big_buffer - tries to alloc a big buffer with kmalloc
 | |
|  *	@buf: pointer to buffer
 | |
|  *	@size: required buffer size
 | |
|  *	@gfp_flags: GFP_* flags
 | |
|  *
 | |
|  *	Tries to allocate a buffer of requested size with kmalloc. If it fails,
 | |
|  *	then decrease buffer size in two times (adjusting the new size to be a
 | |
|  *	multiple of 4) and try again. Use 6 retries in case of failures, after
 | |
|  *	this give up and try to alloc 4KB buffer if requested size bigger than
 | |
|  *	4KB, otherwise allocate nothing and return 0.
 | |
|  *
 | |
|  *	@return a real size of allocated buffer or 0 if allocation failed
 | |
|  */
 | |
| 
 | |
| static size_t sqn_alloc_big_buffer(u8 **buf, size_t size, gfp_t gfp_flags)
 | |
| {
 | |
| 	size_t	real_size = size;
 | |
| 	int	retries   = 6;
 | |
| 
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	/* Try to allocate buffer of requested size, if it failes try to
 | |
| 	 * allocate a twice smaller buffer. Repeat this <retries> number of
 | |
| 	 * times. */
 | |
| 	do
 | |
| 	{
 | |
| 		*buf = kmalloc(real_size, gfp_flags);
 | |
| 		if (!(*buf)) {
 | |
| 			real_size /= 2;
 | |
| 			/* adjust the size to be a multiple of 4 */
 | |
| 			real_size += real_size % 4 ? 4 - real_size % 4 : 0;
 | |
| 		}
 | |
| 	} while (retries-- > 0 && !(*buf));
 | |
| 
 | |
| 	/* If all retries failed, then allocate 4KB buffer */
 | |
| 	if (!(*buf)) {
 | |
| 		real_size = 4 * 1024;
 | |
| 		if (size >= real_size) {
 | |
| 			*buf = kmalloc(real_size, gfp_flags);
 | |
| 			/* If it also failed, then just return 0, indicating
 | |
| 			 * that we failed to alloc buffer */
 | |
| 			if (!(*buf))
 | |
| 				real_size = 0;
 | |
| 		} else {
 | |
| 			/* We should _not_ return buffer bigger than requested */
 | |
| 			real_size = 0;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	sqn_pr_leave();
 | |
| 
 | |
| 	return real_size;
 | |
| }
 | |
| 
 | |
| #define SQN_SDIO_ADA_ADDR	0x00002060
 | |
| #define SQN_SDIO_ADA_RDWR	0x00002064
 | |
| 
 | |
| 
 | |
| static int write_data(struct sdio_func *func, u32 addr, void *data
 | |
| 	, u32 size, u32 access_size)
 | |
| {
 | |
| 	int rv = 0, retry = 0;
 | |
| 	struct sqn_sdio_card *sqn_card = sdio_get_drvdata(func);
 | |
| 
 | |
| 	sqn_pr_enter();
 | |
| 	sdio_claim_host(func);
 | |
| 
 | |
| 	if (is_good_ahb_address(addr, sqn_card->version)
 | |
| 		&& 0 == (size % 4) && 4 == access_size)
 | |
| 	{
 | |
| 		/* write data using AHB */
 | |
| 		u8 *buf = 0;
 | |
| 		size_t buf_size = 0;
 | |
| 		u32 written_size = 0;
 | |
| 
 | |
| #ifdef DEBUG
 | |
| 		u8 *read_data  = 0;
 | |
| #endif
 | |
| 
 | |
| 		sqn_pr_dbg("write data using AHB\n");
 | |
| 		sdio_writel(func, addr, SQN_SDIO_ADA_ADDR, &rv);
 | |
| 		if (rv) {
 | |
| 			sqn_pr_dbg("can't set SQN_SDIO_ADA_ADDR register\n");
 | |
| 			goto out;
 | |
| 		}
 | |
| 		sqn_pr_dbg("after SQN_SDIO_ADA_ADDR\n");
 | |
| 
 | |
| 		written_size = 0;
 | |
| 		buf_size = sqn_alloc_big_buffer(&buf, size, GFP_KERNEL | GFP_DMA);
 | |
| 		if (!buf) {
 | |
| 			sqn_pr_err("failed to allocate buffer of %u bytes\n", size);
 | |
| 			goto out;
 | |
| 		}
 | |
| 
 | |
| 		do {
 | |
| 			memcpy(buf, data + written_size, buf_size);
 | |
| 			rv = sdio_writesb(func, SQN_SDIO_ADA_RDWR, buf, buf_size);
 | |
| 			if (rv) {
 | |
| 				sqn_pr_dbg("can't write to SQN_SDIO_ADA_RDWR register\n");
 | |
| 				goto out;
 | |
| 			}
 | |
| 			written_size += buf_size;
 | |
| 			if (written_size + buf_size > size)
 | |
| 				buf_size = size - written_size;
 | |
| 		} while (written_size < size);
 | |
| 		kfree(buf);
 | |
| 
 | |
| 		/*
 | |
| 		 * Workaround when sdio_writesb doesn't work because DMA
 | |
| 		 * alignment
 | |
| 		 */
 | |
| 		/*
 | |
| 		int i = 0;
 | |
| 		for (; i < size/4; ++i) {
 | |
| 			sdio_writel(func, *((u32*)data + i), SQN_SDIO_ADA_RDWR, &rv);
 | |
| 			if (rv) {
 | |
| 				sqn_pr_dbg("can't write to SQN_SDIO_ADA_RDWR register\n");
 | |
| 				goto out;
 | |
| 			}
 | |
| 		}
 | |
| 		*/
 | |
| 
 | |
| 		sqn_pr_dbg("after SQN_SDIO_ADA_RDWR\n");
 | |
| 
 | |
| 		/* ******** only for debugging ******** */
 | |
| 		/* validate written data */
 | |
| /* #ifdef DEBUG */
 | |
| #if 0
 | |
| 		sqn_pr_dbg("reading data using AHB\n");
 | |
| 		sdio_writel(func, addr, SQN_SDIO_ADA_ADDR, &rv);
 | |
| 		if (rv) {
 | |
| 			sqn_pr_dbg("can't set SQN_SDIO_ADA_ADDR register\n");
 | |
| 			goto out;
 | |
| 		}
 | |
| 		sqn_pr_dbg("after SQN_SDIO_ADA_ADDR\n");
 | |
| 
 | |
| 		read_data = kmalloc(size, GFP_KERNEL);
 | |
| 		rv = sdio_readsb(func, read_data, SQN_SDIO_ADA_RDWR, size);
 | |
| 		if (rv) {
 | |
| 			sqn_pr_dbg("can't read from SQN_SDIO_ADA_RDWR register\n");
 | |
| 			kfree(read_data);
 | |
| 			goto out;
 | |
| 		}
 | |
| 
 | |
| 		if (memcmp(data, read_data, size))
 | |
| 			sqn_pr_dbg("WARNING: written data are __not__ equal\n");
 | |
| 		else
 | |
| 			sqn_pr_dbg("OK: written data are equal\n");
 | |
| 
 | |
| 		kfree(read_data);
 | |
| #endif /* DEBUG */
 | |
| 		/* ******** only for debugging ******** */
 | |
| 
 | |
| 	} else if (4 == access_size && size >= 4) {
 | |
| 		/* write data using CMD53 */
 | |
| 		sqn_pr_dbg("write data using CMD53\n");
 | |
| 		rv = sdio_memcpy_toio(func, addr, data , size);
 | |
| 	} else {
 | |
| 		/* write data using CMD52 */
 | |
| 		/* not implemented yet, so we use CMD53 */
 | |
| 		/* rv = sdio_memcpy_toio(func, addr, data , size); */
 | |
| 		int i = 0;
 | |
| 		sqn_pr_dbg("write data using CMD52\n");
 | |
| 		for (i = 0; i < size; ++i) {
 | |
| 			sdio_writeb(func, *((u8*)data + i), addr + i, &rv);
 | |
| 			if (rv) {
 | |
| 				sqn_pr_dbg("can't write 1 byte to %xh addr using CMD52\n"
 | |
| 					, addr + i);
 | |
| 				goto out;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| out:
 | |
| 	sdio_release_host(func);
 | |
| 	sqn_pr_leave();
 | |
| 	return rv;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int sqn_handle_memcpy_tag(struct sdio_func *func
 | |
| 	, struct sqn_tag_memcpy * mcpy_tag)
 | |
| {
 | |
| 	int rv = 0;
 | |
| 
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	/*
 | |
| 	 * Convert values accordingly to platform "endianes"
 | |
| 	 * (big or little endian) because bootstrapper file
 | |
| 	 * data is big endian
 | |
| 	 */
 | |
| 	mcpy_tag->address = be32_to_cpu(mcpy_tag->address);
 | |
| 	mcpy_tag->access_size = be32_to_cpu(mcpy_tag->access_size);
 | |
| 	mcpy_tag->data_size = be32_to_cpu(mcpy_tag->data_size);
 | |
| 
 | |
| 	/* sqn_pr_dbg("----------------------------------------\n"); */
 | |
| 	sqn_pr_dbg("address: 0x%02X access_size: %u data_size: %u\n"
 | |
| 			, mcpy_tag->address, mcpy_tag->access_size
 | |
| 			, mcpy_tag->data_size);
 | |
| 	/* sqn_pr_dbg_dump("|", mcpy_tag->data, mcpy_tag->data_size); */
 | |
| 
 | |
| 	rv = write_data(func, mcpy_tag->address, mcpy_tag->data
 | |
| 		, mcpy_tag->data_size, mcpy_tag->access_size);
 | |
| 
 | |
| 	sqn_pr_leave();
 | |
| 	return rv;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int sqn_handle_memset_tag(struct sdio_func *func
 | |
| 	, struct sqn_tag_memset * mset_tag)
 | |
| {
 | |
| 	int rv = 0;
 | |
| 	u8 *buf = 0;
 | |
| 	const u32 buf_size = 1024;
 | |
| 	u32 left_bytes = 0;
 | |
| 
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	/*
 | |
| 	 * Convert values accordingly to platform "endianes"
 | |
| 	 * (big or little endian) because bootstrapper file
 | |
| 	 * data is big endian
 | |
| 	 */
 | |
| 	mset_tag->address = be32_to_cpu(mset_tag->address);
 | |
| 	mset_tag->access_size = be32_to_cpu(mset_tag->access_size);
 | |
| 	mset_tag->size = be32_to_cpu(mset_tag->size);
 | |
| 
 | |
| 	/* sqn_pr_dbg("----------------------------------------\n"); */
 | |
| 	sqn_pr_dbg("address: 0x%02X access_size: %u size: %u pattern 0x%02X\n"
 | |
| 			, mset_tag->address, mset_tag->access_size
 | |
| 			, mset_tag->size, mset_tag->pattern);
 | |
| 
 | |
| 	buf = kmalloc(buf_size, GFP_KERNEL);
 | |
| 	if (0 == buf)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	memset(buf, mset_tag->pattern, buf_size);
 | |
| 
 | |
| 	left_bytes = mset_tag->size;
 | |
| 
 | |
| 	while (left_bytes) {
 | |
| 		u32 bytes_to_write = min(buf_size, left_bytes);
 | |
| 		rv = write_data(func, mset_tag->address, buf, bytes_to_write,
 | |
| 			mset_tag->access_size);
 | |
| 		if (rv)
 | |
| 			goto out;
 | |
| 		left_bytes -= bytes_to_write;
 | |
| 	}
 | |
| 
 | |
| out:
 | |
| 	kfree(buf);
 | |
| 	sqn_pr_leave();
 | |
| 	return rv;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int char_to_int(u8 c)
 | |
| {
 | |
| 	int rv = 0;
 | |
| 
 | |
| 	if ('0' <= c && c <= '9') {
 | |
| 		rv = c - '0';
 | |
| 	} else if ('a' <= c && c <= 'f') {
 | |
| 		rv = c - 'a' + 0xA;
 | |
| 	} else if ('A' <= c && c <= 'F') {
 | |
| 		rv = c - 'A' + 0xA;
 | |
| 	} else {
 | |
| 		rv = -1;
 | |
| 	}
 | |
| 
 | |
| 	return rv;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int get_mac_addr_from_str(u8 *data, u32 length, u8 *result)
 | |
| {
 | |
| 	int rv = 0;
 | |
| 	int i = 0;
 | |
| 
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	if (0 == length) {
 | |
| 		rv = -1;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Check if we have delimiters on appropriate places:
 | |
| 	 *
 | |
| 	 * X X : X X : X X : X X  :  X  X  :  X  X
 | |
| 	 * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
 | |
| 	 */
 | |
| 
 | |
| 	if ( !( ( ':' == data[2] || '-' == data[2])
 | |
| 		&& ( ':' == data[5] || '-' == data[5])
 | |
| 		&& ( ':' == data[8] || '-' == data[8])
 | |
| 		&& ( ':' == data[11] || '-' == data[11])
 | |
| 		&& ( ':' == data[14] || '-' == data[14]) ))
 | |
| 	{
 | |
| 		sqn_pr_err("can't get mac address from firmware"
 | |
| 			" - incorrect mac address\n");
 | |
| 		rv = -1;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	i = 0;
 | |
| 	while (i < length) {
 | |
| 		int high = 0;
 | |
| 		int low = 0;
 | |
| 
 | |
| 		if ((high = char_to_int(data[i])) >= 0
 | |
| 			&& (low = char_to_int(data[i + 1])) >= 0)
 | |
| 		{
 | |
| 			result[i/3] = low;
 | |
| 			result[i/3] |= high << 4;
 | |
| 		} else {
 | |
| 			sqn_pr_err("can't get mac address from firmware"
 | |
| 					" - incorrect mac address\n");
 | |
| 			rv = -1;
 | |
| 			goto out;
 | |
| 		}
 | |
| 
 | |
| 		i += 3;
 | |
| 	}
 | |
| 
 | |
| out:
 | |
| 	if (length > 0) {
 | |
| 		data[length - 1] = 0;
 | |
| 		sqn_pr_dbg("mac addr string: %s\n", data);
 | |
| 	}
 | |
| 	sqn_pr_leave();
 | |
| 	return rv;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int sqn_handle_mac_addr_tag(struct sdio_func *func, u8 *data, u32 length)
 | |
| {
 | |
| 	int rv = 0;
 | |
| 	struct sqn_private *priv =
 | |
| 		((struct sqn_sdio_card *)sdio_get_drvdata(func))->priv;
 | |
| 
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	/*
 | |
| 	 * This tag could contain one or two mac addresses in string
 | |
| 	 * form, delimited by some symbol (space or something else).
 | |
| 	 * Each mac address written as a string has constant length.
 | |
| 	 * Thus we can determine the number of mac addresses by the
 | |
| 	 * length of the tag:
 | |
| 	 *
 | |
| 	 * mac addr length in string form: XX:XX:XX:XX:XX:XX = 17 bytes
 | |
| 	 * tag length: 17 bytes [ + 1 byte + 17 bytes ]
 | |
| 	 */
 | |
| 
 | |
| #define MAC_ADDR_STRING_LEN	17
 | |
| 
 | |
| 	/*
 | |
| 	 * If we have only one mac addr we should increment it by one
 | |
| 	 * and use it.
 | |
| 	 * If we have two mac addresses we should use a second one.
 | |
| 	 */
 | |
| 
 | |
| 	if (MAC_ADDR_STRING_LEN <= length
 | |
| 		&& length < 2 * MAC_ADDR_STRING_LEN + 1)
 | |
| 	{
 | |
| 		sqn_pr_dbg("single mac address\n");
 | |
| 		/* we have only one mac addr */
 | |
| 		get_mac_addr_from_str(data, length, priv->mac_addr);
 | |
| 				
 | |
| 		// Andrew 0720
 | |
| 		// ++(priv->mac_addr[ETH_ALEN - 1])
 | |
|         // real MAC: 38:E6:D8:86:00:00 
 | |
| 		// hboot will store: 38:E6:D8:85:FF:FF (minus 1)
 | |
| 		// sdio need to recovery it by plusing 1: 38:E6:D8:86:00:00 (plus 1)
 | |
| 	
 | |
| 		if ((++(priv->mac_addr[ETH_ALEN - 1])) == 0x00)
 | |
|            if ((++(priv->mac_addr[ETH_ALEN - 2])) == 0x00)
 | |
| 			   if ((++(priv->mac_addr[ETH_ALEN - 3])) == 0x00)
 | |
|                   if ((++(priv->mac_addr[ETH_ALEN - 4])) == 0x00) 
 | |
| 					  if ((++(priv->mac_addr[ETH_ALEN - 5])) == 0x00) 
 | |
| 						  ++(priv->mac_addr[ETH_ALEN - 6]);
 | |
| 
 | |
| 	}
 | |
| 	else if (2 * MAC_ADDR_STRING_LEN + 1 == length) { /* we have two macs */
 | |
| 		sqn_pr_dbg("two mac addresses, using second\n");
 | |
| 		get_mac_addr_from_str(data + MAC_ADDR_STRING_LEN + 1
 | |
| 			, length - (MAC_ADDR_STRING_LEN + 1), priv->mac_addr);
 | |
| 	}
 | |
| 	else { /* incorrect data length */
 | |
| 		sqn_pr_err("can't get mac address from bootloader"
 | |
| 			" - incorrect mac address length\n");
 | |
| 		rv = -1;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	sqn_pr_info("setting MAC address from bootloader: "
 | |
| 		"%02x:%02x:%02x:%02x:%02x:%02x\n", priv->mac_addr[0]
 | |
| 		, priv->mac_addr[1], priv->mac_addr[2], priv->mac_addr[3]
 | |
| 		, priv->mac_addr[4], priv->mac_addr[5]);
 | |
| 
 | |
| out:
 | |
| 	sqn_pr_leave();
 | |
| 	return rv;
 | |
| }
 | |
| 
 | |
| 
 | |
| /** sqn_load_bootstrapper - reads a binary boostrapper file, analize it
 | |
|  *  and loads data to the card.
 | |
|  *
 | |
|  *  Bootstrapper is consists of Tag, Length, Value (TLV) sections.
 | |
|  *  Each section starts with 4 bytes tag. Then goes length of data (4 bytes)
 | |
|  *  and then the data itself.
 | |
|  *
 | |
|  *  All fields of bootstrapper file is in BIG ENDIAN format.
 | |
|  */
 | |
| static int sqn_load_bootstrapper(struct sdio_func *func, u8 *data, int size)
 | |
| {
 | |
| 	struct sqn_tlv *tlv = (struct sqn_tlv*) data;
 | |
| 	int rv = 0;
 | |
| 
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	while (size > 0) {
 | |
| 		/*
 | |
| 		 * Convert values accordingly to platform "endianes"
 | |
| 		 * (big or little endian) because bootstrapper file
 | |
| 		 * data is big endian
 | |
| 		 */
 | |
| 		tlv->tag = be32_to_cpu(tlv->tag);
 | |
| 		tlv->length = be32_to_cpu(tlv->length);
 | |
| 
 | |
| 		switch (tlv->tag) {
 | |
| 		case SWM_INFO_TAG_SQN_ROOT:
 | |
| 		case SWM_INFO_TAG_SQN_BOOTROM_GROUP:
 | |
| 		case SWM_INFO_TAG_SQN_ID_GROUP:
 | |
| 			/*
 | |
| 			 * This tag is a "container" tag - it's value field
 | |
| 			 * contains other tags
 | |
| 			 */
 | |
| 
 | |
| 			/* sqn_pr_dbg("========================================\n"); */
 | |
| 			sqn_pr_dbg("tag: CONTAINER %x length: %u\n", tlv->tag
 | |
| 				, tlv->length);
 | |
| 			/* sqn_pr_dbg_dump("|", tlv->value, tlv->length); */
 | |
| 
 | |
| 			/*
 | |
| 			 * If this is a buggy tag, adjust length to
 | |
| 			 * the rest of data
 | |
| 			 */
 | |
| 			if (0 == tlv->length)
 | |
| 				tlv->length = size - sizeof(*tlv);
 | |
| 
 | |
| 			rv = sqn_load_bootstrapper(func, (u8*) tlv->value
 | |
| 				, tlv->length);
 | |
| 			if (rv)
 | |
| 				goto out;
 | |
| 			break;
 | |
| 
 | |
| 		case SWM_INFO_TAG_SQN_MEMCPY:
 | |
| 			/* sqn_pr_dbg("========================================\n"); */
 | |
| 			sqn_pr_dbg("tag: SWM_INFO_TAG_SQN_MEMCPY length: %u\n"
 | |
| 					, tlv->length);
 | |
| 			/* sqn_pr_dbg_dump("|", tlv->value, tlv->length); */
 | |
| 			rv = sqn_handle_memcpy_tag(func
 | |
| 				, (struct sqn_tag_memcpy*) tlv->value);
 | |
| 			if (rv)
 | |
| 				goto out;
 | |
| 			break;
 | |
| 
 | |
| 		case SWM_INFO_TAG_SQN_MEMSET:
 | |
| 			/* sqn_pr_dbg("========================================\n"); */
 | |
| 			sqn_pr_dbg("tag: SWM_INFO_TAG_SQN_MEMSET length: %u\n"
 | |
| 					, tlv->length);
 | |
| 			/* sqn_pr_dbg_dump("|", tlv->value, tlv->length); */
 | |
| 			rv = sqn_handle_memset_tag(func
 | |
| 				, (struct sqn_tag_memset*) tlv->value);
 | |
| 			if (rv)
 | |
| 				goto out;
 | |
| 			break;
 | |
| 
 | |
| 		case SWM_INFO_TAG_SQN_MAC_ADDRESS:
 | |
| 			/* sqn_pr_dbg("========================================\n"); */
 | |
| 			sqn_pr_dbg("tag: SWM_INFO_TAG_SQN_MAC_ADDRESS length: %u\n"
 | |
| 					, tlv->length);
 | |
| 			/* sqn_pr_dbg_dump("|", tlv->value, tlv->length); */
 | |
| 
 | |
| 			rv = sqn_handle_mac_addr_tag(func, tlv->value
 | |
| 				, tlv->length);
 | |
| 			if (rv)
 | |
| 				goto out;
 | |
| 			break;
 | |
| 
 | |
| 		default:
 | |
| 			/* skip all other tags */
 | |
| 			/* sqn_pr_dbg("========================================\n"); */
 | |
| 			sqn_pr_dbg("tag: UNKNOWN %x length: %u\n"
 | |
| 					, tlv->tag, tlv->length);
 | |
| 			/* sqn_pr_dbg_dump("|", tlv->value, tlv->length); */
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		/* increment tlv to point it to the beginning of the next
 | |
| 		 * sqn_tlv struct and decrement size accordingly
 | |
| 		 */
 | |
| 		size = (int)(size - (sizeof(*tlv) + tlv->length));
 | |
| 		tlv = (struct sqn_tlv*) ((u8*)tlv + sizeof(*tlv) + tlv->length);
 | |
| 	}
 | |
| 
 | |
| 	if (0 != size) {
 | |
| 		/* something wrong with parsing of tlv values */
 | |
| 		rv = -1;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| out:
 | |
| 	sqn_pr_leave();
 | |
| 	return rv;
 | |
| }
 | |
| 
 | |
| 
 | |
| extern char *firmware_name;
 | |
| 
 | |
| /** sqn_load_firmware - loads firmware to card
 | |
|  *  @func: SDIO function, used to transfer data via SDIO interface,
 | |
|  *         also used to obtain pointer to device structure.
 | |
|  *
 | |
|  *  But now the only work it does - is loading of bootstrapper to card,
 | |
|  *  because firmware is supposed to be loaded by a userspace program.
 | |
|  */
 | |
| int sqn_load_firmware(struct sdio_func *func)
 | |
| {
 | |
| 	int rv = 0;
 | |
| 	const struct firmware *fw = 0;
 | |
| //Create a local firmware_name with path to replace original global firmware_name -- Tony Wu.
 | |
| 	const char *firmware_name = "../../../data/wimax/Boot.bin";
 | |
| 
 | |
| 	struct sqn_sdio_card *sqn_card = sdio_get_drvdata(func);
 | |
| 
 | |
| 	sqn_pr_enter();
 | |
| 
 | |
| 	sqn_pr_info("trying to find bootloader image: \"%s\"\n", firmware_name);
 | |
| 	if ((rv = request_firmware(&fw, firmware_name, &func->dev)))
 | |
| 		goto out;
 | |
| 
 | |
| 	if (SQN_1130 == sqn_card->version) {
 | |
| 		sdio_claim_host(func);
 | |
| 
 | |
| 		/* properly setup registers for firmware loading */
 | |
| 		sqn_pr_dbg("setting up SQN_H_SDRAM_NO_EMR register\n");
 | |
| 		sdio_writeb(func, 0, SQN_H_SDRAM_NO_EMR, &rv);
 | |
| 		if (rv) {
 | |
| 			sdio_release_host(func);
 | |
| 			goto out;
 | |
| 		}
 | |
| 
 | |
| 		sqn_pr_dbg("setting up SQN_H_SDRAMCTL_RSTN register\n");
 | |
| 		sdio_writeb(func, 1, SQN_H_SDRAMCTL_RSTN, &rv);
 | |
| 		sdio_release_host(func);
 | |
| 		if (rv)
 | |
| 			goto out;
 | |
| 	}
 | |
| 
 | |
| 	sqn_pr_info("loading bootloader to the card...\n");
 | |
| 	if ((rv = sqn_load_bootstrapper(func, fw->data, fw->size)))
 | |
| 		goto out;
 | |
| 
 | |
| 	/* boot the card */
 | |
| 	sqn_pr_info("bootting the card...\n");
 | |
| 	sdio_claim_host(func); // by daniel
 | |
| 	sdio_writeb(func, 1, SQN_H_CRSTN, &rv);
 | |
| 	sdio_release_host(func); // by daniel
 | |
| 	if (rv)
 | |
| 		goto out;
 | |
| 	sqn_pr_info("  done\n");
 | |
| 
 | |
| out:
 | |
| 	sqn_pr_leave();
 | |
| 	return rv;
 | |
| }
 |