2010-08-27 11:19:57 +02:00

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