2009-10-17 13:09:34 +04:00
/*
* iwmc3200top - Intel Wireless MultiCom 3200 Top Driver
* drivers / misc / iwmc3200top / fw - download . c
*
* Copyright ( C ) 2009 Intel Corporation . All rights reserved .
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation .
*
* This program 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 this program ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA
* 02110 - 1301 , USA .
*
*
* Author Name : Maxim Grabarnik < maxim . grabarnink @ intel . com >
* -
*
*/
# include <linux/firmware.h>
# include <linux/mmc/sdio_func.h>
# include <asm/unaligned.h>
# include "iwmc3200top.h"
# include "log.h"
# include "fw-msg.h"
# define CHECKSUM_BYTES_NUM sizeof(u32)
/**
init parser struct with file
*/
static int iwmct_fw_parser_init ( struct iwmct_priv * priv , const u8 * file ,
size_t file_size , size_t block_size )
{
struct iwmct_parser * parser = & priv - > parser ;
struct iwmct_fw_hdr * fw_hdr = & parser - > versions ;
LOG_INFOEX ( priv , INIT , " --> \n " ) ;
LOG_INFO ( priv , FW_DOWNLOAD , " file_size=%zd \n " , file_size ) ;
parser - > file = file ;
parser - > file_size = file_size ;
parser - > cur_pos = 0 ;
parser - > buf = NULL ;
parser - > buf = kzalloc ( block_size , GFP_KERNEL ) ;
if ( ! parser - > buf ) {
LOG_ERROR ( priv , FW_DOWNLOAD , " kzalloc error \n " ) ;
return - ENOMEM ;
}
parser - > buf_size = block_size ;
/* extract fw versions */
memcpy ( fw_hdr , parser - > file , sizeof ( struct iwmct_fw_hdr ) ) ;
LOG_INFO ( priv , FW_DOWNLOAD , " fw versions are: \n "
" top %u.%u.%u gps %u.%u.%u bt %u.%u.%u tic %s \n " ,
fw_hdr - > top_major , fw_hdr - > top_minor , fw_hdr - > top_revision ,
fw_hdr - > gps_major , fw_hdr - > gps_minor , fw_hdr - > gps_revision ,
fw_hdr - > bt_major , fw_hdr - > bt_minor , fw_hdr - > bt_revision ,
fw_hdr - > tic_name ) ;
parser - > cur_pos + = sizeof ( struct iwmct_fw_hdr ) ;
LOG_INFOEX ( priv , INIT , " <-- \n " ) ;
return 0 ;
}
static bool iwmct_checksum ( struct iwmct_priv * priv )
{
struct iwmct_parser * parser = & priv - > parser ;
__le32 * file = ( __le32 * ) parser - > file ;
int i , pad , steps ;
u32 accum = 0 ;
u32 checksum ;
u32 mask = 0xffffffff ;
pad = ( parser - > file_size - CHECKSUM_BYTES_NUM ) % 4 ;
steps = ( parser - > file_size - CHECKSUM_BYTES_NUM ) / 4 ;
LOG_INFO ( priv , FW_DOWNLOAD , " pad=%d steps=%d \n " , pad , steps ) ;
for ( i = 0 ; i < steps ; i + + )
accum + = le32_to_cpu ( file [ i ] ) ;
if ( pad ) {
mask < < = 8 * ( 4 - pad ) ;
accum + = le32_to_cpu ( file [ steps ] ) & mask ;
}
checksum = get_unaligned_le32 ( ( __le32 * ) ( parser - > file +
parser - > file_size - CHECKSUM_BYTES_NUM ) ) ;
LOG_INFO ( priv , FW_DOWNLOAD ,
" compare checksum accum=0x%x to checksum=0x%x \n " ,
accum , checksum ) ;
return checksum = = accum ;
}
static int iwmct_parse_next_section ( struct iwmct_priv * priv , const u8 * * p_sec ,
size_t * sec_size , __le32 * sec_addr )
{
struct iwmct_parser * parser = & priv - > parser ;
struct iwmct_dbg * dbg = & priv - > dbg ;
struct iwmct_fw_sec_hdr * sec_hdr ;
LOG_INFOEX ( priv , INIT , " --> \n " ) ;
while ( parser - > cur_pos + sizeof ( struct iwmct_fw_sec_hdr )
< = parser - > file_size ) {
sec_hdr = ( struct iwmct_fw_sec_hdr * )
( parser - > file + parser - > cur_pos ) ;
parser - > cur_pos + = sizeof ( struct iwmct_fw_sec_hdr ) ;
LOG_INFO ( priv , FW_DOWNLOAD ,
" sec hdr: type=%s addr=0x%x size=%d \n " ,
sec_hdr - > type , sec_hdr - > target_addr ,
sec_hdr - > data_size ) ;
if ( strcmp ( sec_hdr - > type , " ENT " ) = = 0 )
parser - > entry_point = le32_to_cpu ( sec_hdr - > target_addr ) ;
else if ( strcmp ( sec_hdr - > type , " LBL " ) = = 0 )
strcpy ( dbg - > label_fw , parser - > file + parser - > cur_pos ) ;
else if ( ( ( strcmp ( sec_hdr - > type , " TOP " ) = = 0 ) & &
( priv - > barker & BARKER_DNLOAD_TOP_MSK ) ) | |
( ( strcmp ( sec_hdr - > type , " GPS " ) = = 0 ) & &
( priv - > barker & BARKER_DNLOAD_GPS_MSK ) ) | |
( ( strcmp ( sec_hdr - > type , " BTH " ) = = 0 ) & &
( priv - > barker & BARKER_DNLOAD_BT_MSK ) ) ) {
* sec_addr = sec_hdr - > target_addr ;
* sec_size = le32_to_cpu ( sec_hdr - > data_size ) ;
* p_sec = parser - > file + parser - > cur_pos ;
parser - > cur_pos + = le32_to_cpu ( sec_hdr - > data_size ) ;
return 1 ;
} else if ( strcmp ( sec_hdr - > type , " LOG " ) ! = 0 )
LOG_WARNING ( priv , FW_DOWNLOAD ,
" skipping section type %s \n " ,
sec_hdr - > type ) ;
parser - > cur_pos + = le32_to_cpu ( sec_hdr - > data_size ) ;
LOG_INFO ( priv , FW_DOWNLOAD ,
" finished with section cur_pos=%zd \n " , parser - > cur_pos ) ;
}
LOG_INFOEX ( priv , INIT , " <-- \n " ) ;
return 0 ;
}
static int iwmct_download_section ( struct iwmct_priv * priv , const u8 * p_sec ,
size_t sec_size , __le32 addr )
{
struct iwmct_parser * parser = & priv - > parser ;
struct iwmct_fw_load_hdr * hdr = ( struct iwmct_fw_load_hdr * ) parser - > buf ;
const u8 * cur_block = p_sec ;
size_t sent = 0 ;
int cnt = 0 ;
int ret = 0 ;
u32 cmd = 0 ;
LOG_INFOEX ( priv , INIT , " --> \n " ) ;
LOG_INFO ( priv , FW_DOWNLOAD , " Download address 0x%x size 0x%zx \n " ,
addr , sec_size ) ;
while ( sent < sec_size ) {
int i ;
u32 chksm = 0 ;
u32 reset = atomic_read ( & priv - > reset ) ;
/* actual FW data */
u32 data_size = min ( parser - > buf_size - sizeof ( * hdr ) ,
sec_size - sent ) ;
/* Pad to block size */
u32 trans_size = ( data_size + sizeof ( * hdr ) +
IWMC_SDIO_BLK_SIZE - 1 ) &
~ ( IWMC_SDIO_BLK_SIZE - 1 ) ;
+ + cnt ;
/* in case of reset, interrupt FW DOWNLAOD */
if ( reset ) {
LOG_INFO ( priv , FW_DOWNLOAD ,
" Reset detected. Abort FW download!!! " ) ;
ret = - ECANCELED ;
goto exit ;
}
memset ( parser - > buf , 0 , parser - > buf_size ) ;
cmd | = IWMC_OPCODE_WRITE < < CMD_HDR_OPCODE_POS ;
cmd | = IWMC_CMD_SIGNATURE < < CMD_HDR_SIGNATURE_POS ;
cmd | = ( priv - > dbg . direct ? 1 : 0 ) < < CMD_HDR_DIRECT_ACCESS_POS ;
cmd | = ( priv - > dbg . checksum ? 1 : 0 ) < < CMD_HDR_USE_CHECKSUM_POS ;
hdr - > data_size = cpu_to_le32 ( data_size ) ;
hdr - > target_addr = addr ;
/* checksum is allowed for sizes divisible by 4 */
if ( data_size & 0x3 )
cmd & = ~ CMD_HDR_USE_CHECKSUM_MSK ;
memcpy ( hdr - > data , cur_block , data_size ) ;
if ( cmd & CMD_HDR_USE_CHECKSUM_MSK ) {
chksm = data_size + le32_to_cpu ( addr ) + cmd ;
for ( i = 0 ; i < data_size > > 2 ; i + + )
chksm + = ( ( u32 * ) cur_block ) [ i ] ;
hdr - > block_chksm = cpu_to_le32 ( chksm ) ;
LOG_INFO ( priv , FW_DOWNLOAD , " Checksum = 0x%X \n " ,
hdr - > block_chksm ) ;
}
LOG_INFO ( priv , FW_DOWNLOAD , " trans#%d, len=%d, sent=%zd, "
" sec_size=%zd, startAddress 0x%X \n " ,
cnt , trans_size , sent , sec_size , addr ) ;
if ( priv - > dbg . dump )
LOG_HEXDUMP ( FW_DOWNLOAD , parser - > buf , trans_size ) ;
hdr - > cmd = cpu_to_le32 ( cmd ) ;
/* send it down */
/* TODO: add more proper sending and error checking */
ret = iwmct_tx ( priv , 0 , parser - > buf , trans_size ) ;
if ( ret ! = 0 ) {
LOG_INFO ( priv , FW_DOWNLOAD ,
" iwmct_tx returned %d \n " , ret ) ;
goto exit ;
}
addr = cpu_to_le32 ( le32_to_cpu ( addr ) + data_size ) ;
sent + = data_size ;
cur_block = p_sec + sent ;
if ( priv - > dbg . blocks & & ( cnt + 1 ) > = priv - > dbg . blocks ) {
LOG_INFO ( priv , FW_DOWNLOAD ,
" Block number limit is reached [%d] \n " ,
priv - > dbg . blocks ) ;
break ;
}
}
if ( sent < sec_size )
ret = - EINVAL ;
exit :
LOG_INFOEX ( priv , INIT , " <-- \n " ) ;
return ret ;
}
static int iwmct_kick_fw ( struct iwmct_priv * priv , bool jump )
{
struct iwmct_parser * parser = & priv - > parser ;
struct iwmct_fw_load_hdr * hdr = ( struct iwmct_fw_load_hdr * ) parser - > buf ;
int ret ;
u32 cmd ;
LOG_INFOEX ( priv , INIT , " --> \n " ) ;
memset ( parser - > buf , 0 , parser - > buf_size ) ;
cmd = IWMC_CMD_SIGNATURE < < CMD_HDR_SIGNATURE_POS ;
if ( jump ) {
cmd | = IWMC_OPCODE_JUMP < < CMD_HDR_OPCODE_POS ;
hdr - > target_addr = cpu_to_le32 ( parser - > entry_point ) ;
LOG_INFO ( priv , FW_DOWNLOAD , " jump address 0x%x \n " ,
parser - > entry_point ) ;
} else {
cmd | = IWMC_OPCODE_LAST_COMMAND < < CMD_HDR_OPCODE_POS ;
LOG_INFO ( priv , FW_DOWNLOAD , " last command \n " ) ;
}
hdr - > cmd = cpu_to_le32 ( cmd ) ;
LOG_HEXDUMP ( FW_DOWNLOAD , parser - > buf , sizeof ( * hdr ) ) ;
/* send it down */
/* TODO: add more proper sending and error checking */
ret = iwmct_tx ( priv , 0 , parser - > buf , IWMC_SDIO_BLK_SIZE ) ;
if ( ret )
LOG_INFO ( priv , FW_DOWNLOAD , " iwmct_tx returned %d " , ret ) ;
LOG_INFOEX ( priv , INIT , " <-- \n " ) ;
return 0 ;
}
int iwmct_fw_load ( struct iwmct_priv * priv )
{
2009-11-19 10:29:57 +03:00
const u8 * fw_name = FW_NAME ( FW_API_VER ) ;
const struct firmware * raw ;
2009-10-17 13:09:34 +04:00
const u8 * pdata ;
2009-11-19 10:29:57 +03:00
size_t len ;
__le32 addr ;
int ret ;
2009-10-17 13:09:34 +04:00
/* clear parser struct */
memset ( & priv - > parser , 0 , sizeof ( struct iwmct_parser ) ) ;
/* get the firmware */
2009-11-19 10:29:57 +03:00
ret = request_firmware ( & raw , fw_name , & priv - > func - > dev ) ;
2009-10-17 13:09:34 +04:00
if ( ret < 0 ) {
LOG_ERROR ( priv , FW_DOWNLOAD , " %s request_firmware failed %d \n " ,
2009-11-19 10:29:57 +03:00
fw_name , ret ) ;
2009-10-17 13:09:34 +04:00
goto exit ;
}
if ( raw - > size < sizeof ( struct iwmct_fw_sec_hdr ) ) {
LOG_ERROR ( priv , FW_DOWNLOAD , " %s smaller then (%zd) (%zd) \n " ,
2009-11-19 10:29:57 +03:00
fw_name , sizeof ( struct iwmct_fw_sec_hdr ) , raw - > size ) ;
2009-10-17 13:09:34 +04:00
goto exit ;
}
2009-11-19 10:29:57 +03:00
LOG_INFO ( priv , FW_DOWNLOAD , " Read firmware '%s' \n " , fw_name ) ;
2009-10-17 13:09:34 +04:00
ret = iwmct_fw_parser_init ( priv , raw - > data , raw - > size , priv - > trans_len ) ;
if ( ret < 0 ) {
LOG_ERROR ( priv , FW_DOWNLOAD ,
" iwmct_parser_init failed: Reason %d \n " , ret ) ;
goto exit ;
}
/* checksum */
if ( ! iwmct_checksum ( priv ) ) {
LOG_ERROR ( priv , FW_DOWNLOAD , " checksum error \n " ) ;
ret = - EINVAL ;
goto exit ;
}
/* download firmware to device */
while ( iwmct_parse_next_section ( priv , & pdata , & len , & addr ) ) {
if ( iwmct_download_section ( priv , pdata , len , addr ) ) {
LOG_ERROR ( priv , FW_DOWNLOAD ,
2009-11-19 10:29:57 +03:00
" %s download section failed \n " , fw_name ) ;
2009-10-17 13:09:34 +04:00
ret = - EIO ;
goto exit ;
}
}
iwmct_kick_fw ( priv , ! ! ( priv - > barker & BARKER_DNLOAD_JUMP_MSK ) ) ;
exit :
kfree ( priv - > parser . buf ) ;
if ( raw )
release_firmware ( raw ) ;
raw = NULL ;
return ret ;
}