/* * This is part of the Sequans SQN1130 driver. * Copyright 2008 SEQUANS Communications * Written by Dmitriy Chumak * * 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 #include #include #include #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 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; }