2008-08-21 23:27:51 +01:00
/*
2009-02-04 23:05:57 +00:00
* Hermes download helper .
2008-08-21 23:27:51 +01:00
*
2009-02-04 23:05:57 +00:00
* This helper :
2008-08-21 23:27:51 +01:00
* - is capable of writing to the volatile area of the hermes device
* - is currently not capable of writing to non - volatile areas
* - provide helpers to identify and update plugin data
* - is not capable of interpreting a fw image directly . That is up to
* the main card driver .
* - deals with Hermes I devices . It can probably be modified to deal
* with Hermes II devices
*
* Copyright ( C ) 2007 , David Kilroy
*
* Plug data code slightly modified from spectrum_cs driver
* Copyright ( C ) 2002 - 2005 Pavel Roskin < proski @ gnu . org >
* Portions based on information in wl_lkm_718 Agere driver
* COPYRIGHT ( C ) 2001 - 2004 by Agere Systems Inc . All Rights Reserved
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 ( the " License " ) ; you may not use this file except in
* compliance with the License . You may obtain a copy of the License
* at http : //www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an " AS IS "
* basis , WITHOUT WARRANTY OF ANY KIND , either express or implied . See
* the License for the specific language governing rights and
* limitations under the License .
*
* Alternatively , the contents of this file may be used under the
* terms of the GNU General Public License version 2 ( the " GPL " ) , in
* which case the provisions of the GPL are applicable instead of the
* above . If you wish to allow the use of your version of this file
* only under the terms of the GPL and not to allow others to use your
* version of this file under the MPL , indicate your decision by
* deleting the provisions above and replace them with the notice and
* other provisions required by the GPL . If you do not delete the
* provisions above , a recipient may use your version of this file
* under either the MPL or the GPL .
*/
# include <linux/module.h>
# include <linux/delay.h>
# include "hermes.h"
# include "hermes_dld.h"
# define PFX "hermes_dld: "
2008-08-21 23:27:52 +01:00
/* End markers used in dblocks */
2008-08-21 23:27:51 +01:00
# define PDI_END 0x00000000 /* End of PDA */
# define BLOCK_END 0xFFFFFFFF /* Last image block */
2008-08-21 23:27:52 +01:00
# define TEXT_END 0x1A /* End of text header */
2008-08-21 23:27:51 +01:00
/*
* The following structures have little - endian fields denoted by
* the leading underscore . Don ' t access them directly - use inline
* functions defined below .
*/
/*
* The binary image to be downloaded consists of series of data blocks .
* Each block has the following structure .
*/
struct dblock {
__le32 addr ; /* adapter address where to write the block */
__le16 len ; /* length of the data only, in bytes */
char data [ 0 ] ; /* data to be written */
} __attribute__ ( ( packed ) ) ;
/*
* Plug Data References are located in in the image after the last data
* block . They refer to areas in the adapter memory where the plug data
* items with matching ID should be written .
*/
struct pdr {
__le32 id ; /* record ID */
__le32 addr ; /* adapter address where to write the data */
__le32 len ; /* expected length of the data, in bytes */
char next [ 0 ] ; /* next PDR starts here */
} __attribute__ ( ( packed ) ) ;
/*
* Plug Data Items are located in the EEPROM read from the adapter by
* primary firmware . They refer to the device - specific data that should
* be plugged into the secondary firmware .
*/
struct pdi {
__le16 len ; /* length of ID and data, in words */
__le16 id ; /* record ID */
char data [ 0 ] ; /* plug data */
} __attribute__ ( ( packed ) ) ;
2008-08-21 23:27:52 +01:00
/*** FW data block access functions ***/
2008-08-21 23:27:51 +01:00
static inline u32
dblock_addr ( const struct dblock * blk )
{
return le32_to_cpu ( blk - > addr ) ;
}
static inline u32
dblock_len ( const struct dblock * blk )
{
return le16_to_cpu ( blk - > len ) ;
}
2008-08-21 23:27:52 +01:00
/*** PDR Access functions ***/
2008-08-21 23:27:51 +01:00
static inline u32
pdr_id ( const struct pdr * pdr )
{
return le32_to_cpu ( pdr - > id ) ;
}
static inline u32
pdr_addr ( const struct pdr * pdr )
{
return le32_to_cpu ( pdr - > addr ) ;
}
static inline u32
pdr_len ( const struct pdr * pdr )
{
return le32_to_cpu ( pdr - > len ) ;
}
2008-08-21 23:27:52 +01:00
/*** PDI Access functions ***/
2008-08-21 23:27:51 +01:00
static inline u32
pdi_id ( const struct pdi * pdi )
{
return le16_to_cpu ( pdi - > id ) ;
}
/* Return length of the data only, in bytes */
static inline u32
pdi_len ( const struct pdi * pdi )
{
return 2 * ( le16_to_cpu ( pdi - > len ) - 1 ) ;
}
2008-08-21 23:27:52 +01:00
/*** Plug Data Functions ***/
2008-08-21 23:27:51 +01:00
/*
* Scan PDR for the record with the specified RECORD_ID .
* If it ' s not found , return NULL .
*/
2009-02-21 16:52:54 +00:00
static const struct pdr *
hermes_find_pdr ( const struct pdr * first_pdr , u32 record_id , const void * end )
2008-08-21 23:27:51 +01:00
{
2009-02-21 16:52:54 +00:00
const struct pdr * pdr = first_pdr ;
2008-08-21 23:27:51 +01:00
2009-02-21 16:52:54 +00:00
end - = sizeof ( struct pdr ) ;
while ( ( ( void * ) pdr < = end ) & &
2008-08-21 23:27:52 +01:00
( pdr_id ( pdr ) ! = PDI_END ) ) {
2008-08-21 23:27:51 +01:00
/*
* PDR area is currently not terminated by PDI_END .
* It ' s followed by CRC records , which have the type
* field where PDR has length . The type can be 0 or 1.
*/
if ( pdr_len ( pdr ) < 2 )
return NULL ;
/* If the record ID matches, we are done */
if ( pdr_id ( pdr ) = = record_id )
return pdr ;
pdr = ( struct pdr * ) pdr - > next ;
}
return NULL ;
}
2008-08-21 23:27:53 +01:00
/* Scan production data items for a particular entry */
2009-02-21 16:52:54 +00:00
static const struct pdi *
hermes_find_pdi ( const struct pdi * first_pdi , u32 record_id , const void * end )
2008-08-21 23:27:53 +01:00
{
2009-02-21 16:52:54 +00:00
const struct pdi * pdi = first_pdi ;
end - = sizeof ( struct pdi ) ;
2008-08-21 23:27:53 +01:00
2009-02-21 16:52:54 +00:00
while ( ( ( void * ) pdi < = end ) & &
( pdi_id ( pdi ) ! = PDI_END ) ) {
2008-08-21 23:27:53 +01:00
/* If the record ID matches, we are done */
if ( pdi_id ( pdi ) = = record_id )
return pdi ;
pdi = ( struct pdi * ) & pdi - > data [ pdi_len ( pdi ) ] ;
}
return NULL ;
}
2008-08-21 23:27:51 +01:00
/* Process one Plug Data Item - find corresponding PDR and plug it */
static int
2009-02-21 16:52:54 +00:00
hermes_plug_pdi ( hermes_t * hw , const struct pdr * first_pdr ,
const struct pdi * pdi , const void * pdr_end )
2008-08-21 23:27:51 +01:00
{
2009-02-21 16:52:54 +00:00
const struct pdr * pdr ;
2008-08-21 23:27:51 +01:00
2008-08-21 23:27:52 +01:00
/* Find the PDR corresponding to this PDI */
2009-02-21 16:52:54 +00:00
pdr = hermes_find_pdr ( first_pdr , pdi_id ( pdi ) , pdr_end ) ;
2008-08-21 23:27:51 +01:00
/* No match is found, safe to ignore */
if ( ! pdr )
return 0 ;
/* Lengths of the data in PDI and PDR must match */
if ( pdi_len ( pdi ) ! = pdr_len ( pdr ) )
return - EINVAL ;
/* do the actual plugging */
2010-05-01 14:05:43 +01:00
hw - > ops - > program ( hw , pdi - > data , pdr_addr ( pdr ) , pdi_len ( pdi ) ) ;
2008-08-21 23:27:51 +01:00
return 0 ;
}
2008-08-21 23:27:52 +01:00
/* Parse PDA and write the records into the adapter
*
* Attempt to write every records that is in the specified pda
* which also has a valid production data record for the firmware .
*/
int hermes_apply_pda ( hermes_t * hw ,
const char * first_pdr ,
2009-02-21 16:52:54 +00:00
const void * pdr_end ,
const __le16 * pda ,
const void * pda_end )
2008-08-21 23:27:51 +01:00
{
int ret ;
2008-08-21 23:27:52 +01:00
const struct pdi * pdi ;
2009-02-21 16:52:54 +00:00
const struct pdr * pdr ;
2008-08-21 23:27:51 +01:00
2009-02-21 16:52:54 +00:00
pdr = ( const struct pdr * ) first_pdr ;
pda_end - = sizeof ( struct pdi ) ;
2008-08-21 23:27:51 +01:00
/* Go through every PDI and plug them into the adapter */
2008-08-21 23:27:52 +01:00
pdi = ( const struct pdi * ) ( pda + 2 ) ;
2009-02-21 16:52:54 +00:00
while ( ( ( void * ) pdi < = pda_end ) & &
( pdi_id ( pdi ) ! = PDI_END ) ) {
ret = hermes_plug_pdi ( hw , pdr , pdi , pdr_end ) ;
2008-08-21 23:27:51 +01:00
if ( ret )
return ret ;
/* Increment to the next PDI */
2008-08-21 23:27:52 +01:00
pdi = ( const struct pdi * ) & pdi - > data [ pdi_len ( pdi ) ] ;
2008-08-21 23:27:51 +01:00
}
return 0 ;
}
2008-08-21 23:27:52 +01:00
/* Identify the total number of bytes in all blocks
* including the header data .
*/
size_t
2009-02-21 16:52:54 +00:00
hermes_blocks_length ( const char * first_block , const void * end )
2008-08-21 23:27:52 +01:00
{
const struct dblock * blk = ( const struct dblock * ) first_block ;
int total_len = 0 ;
int len ;
2009-02-21 16:52:54 +00:00
end - = sizeof ( * blk ) ;
2008-08-21 23:27:52 +01:00
/* Skip all blocks to locate Plug Data References
* ( Spectrum CS ) */
2009-02-21 16:52:54 +00:00
while ( ( ( void * ) blk < = end ) & &
( dblock_addr ( blk ) ! = BLOCK_END ) ) {
2008-08-21 23:27:52 +01:00
len = dblock_len ( blk ) ;
total_len + = sizeof ( * blk ) + len ;
blk = ( struct dblock * ) & blk - > data [ len ] ;
}
return total_len ;
}
/*** Hermes programming ***/
2008-08-21 23:27:51 +01:00
2008-08-21 23:27:52 +01:00
/* Program the data blocks */
2009-02-21 16:52:54 +00:00
int hermes_program ( hermes_t * hw , const char * first_block , const void * end )
2008-08-21 23:27:51 +01:00
{
const struct dblock * blk ;
u32 blkaddr ;
u32 blklen ;
2010-05-01 14:05:43 +01:00
int err = 0 ;
2008-08-21 23:27:52 +01:00
blk = ( const struct dblock * ) first_block ;
2009-02-21 16:52:54 +00:00
if ( ( void * ) blk > ( end - sizeof ( * blk ) ) )
2008-08-21 23:27:52 +01:00
return - EIO ;
2008-08-21 23:27:51 +01:00
blkaddr = dblock_addr ( blk ) ;
blklen = dblock_len ( blk ) ;
2008-08-21 23:27:52 +01:00
while ( ( blkaddr ! = BLOCK_END ) & &
2009-02-21 16:52:54 +00:00
( ( ( void * ) blk + blklen ) < = end ) ) {
2009-06-18 23:21:27 +01:00
pr_debug ( PFX " Programming block of length %d "
" to address 0x%08x \n " , blklen , blkaddr ) ;
2008-08-21 23:27:52 +01:00
2010-05-01 14:05:43 +01:00
err = hw - > ops - > program ( hw , blk - > data , blkaddr , blklen ) ;
if ( err )
break ;
2008-08-21 23:27:52 +01:00
blk = ( const struct dblock * ) & blk - > data [ blklen ] ;
2009-02-21 16:52:54 +00:00
if ( ( void * ) blk > ( end - sizeof ( * blk ) ) )
2008-08-21 23:27:52 +01:00
return - EIO ;
2008-08-21 23:27:51 +01:00
blkaddr = dblock_addr ( blk ) ;
blklen = dblock_len ( blk ) ;
}
2010-05-01 14:05:43 +01:00
return err ;
2008-08-21 23:27:51 +01:00
}
2008-08-21 23:27:53 +01:00
/*** Default plugging data for Hermes I ***/
/* Values from wl_lkm_718/hcf/dhf.c */
# define DEFINE_DEFAULT_PDR(pid, length, data) \
static const struct { \
__le16 len ; \
__le16 id ; \
u8 val [ length ] ; \
} __attribute__ ( ( packed ) ) default_pdr_data_ # # pid = { \
2009-02-21 16:52:54 +00:00
cpu_to_le16 ( ( sizeof ( default_pdr_data_ # # pid ) / \
2008-08-21 23:27:53 +01:00
sizeof ( __le16 ) ) - 1 ) , \
2009-02-21 16:52:54 +00:00
cpu_to_le16 ( pid ) , \
2008-08-21 23:27:53 +01:00
data \
}
# define DEFAULT_PDR(pid) default_pdr_data_##pid
2009-09-28 21:43:57 -04:00
/* HWIF Compatibility */
2008-08-21 23:27:53 +01:00
DEFINE_DEFAULT_PDR ( 0x0005 , 10 , " \x00 \x00 \x06 \x00 \x01 \x00 \x01 \x00 \x01 \x00 " ) ;
/* PPPPSign */
DEFINE_DEFAULT_PDR ( 0x0108 , 4 , " \x00 \x00 \x00 \x00 " ) ;
/* PPPPProf */
DEFINE_DEFAULT_PDR ( 0x0109 , 10 , " \x00 \x00 \x00 \x00 \x03 \x00 \x00 \x00 \x00 \x00 " ) ;
/* Antenna diversity */
DEFINE_DEFAULT_PDR ( 0x0150 , 2 , " \x00 \x3F " ) ;
/* Modem VCO band Set-up */
DEFINE_DEFAULT_PDR ( 0x0160 , 28 ,
" \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 "
" \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 "
" \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x00 "
" \x00 \x00 \x00 \x00 " ) ;
/* Modem Rx Gain Table Values */
DEFINE_DEFAULT_PDR ( 0x0161 , 256 ,
" \x3F \x01 \x3F \01 \x3F \x01 \x3F \x01 "
" \x3F \x01 \x3F \01 \x3F \x01 \x3F \x01 "
" \x3F \x01 \x3F \01 \x3F \x01 \x3F \x01 "
" \x3F \x01 \x3F \01 \x3F \x01 \x3F \x01 "
" \x3F \x01 \x3E \01 \x3E \x01 \x3D \x01 "
" \x3D \x01 \x3C \01 \x3C \x01 \x3B \x01 "
" \x3B \x01 \x3A \01 \x3A \x01 \x39 \x01 "
" \x39 \x01 \x38 \01 \x38 \x01 \x37 \x01 "
" \x37 \x01 \x36 \01 \x36 \x01 \x35 \x01 "
" \x35 \x01 \x34 \01 \x34 \x01 \x33 \x01 "
" \x33 \x01 \x32 \x01 \x32 \x01 \x31 \x01 "
" \x31 \x01 \x30 \x01 \x30 \x01 \x7B \x01 "
" \x7B \x01 \x7A \x01 \x7A \x01 \x79 \x01 "
" \x79 \x01 \x78 \x01 \x78 \x01 \x77 \x01 "
" \x77 \x01 \x76 \x01 \x76 \x01 \x75 \x01 "
" \x75 \x01 \x74 \x01 \x74 \x01 \x73 \x01 "
" \x73 \x01 \x72 \x01 \x72 \x01 \x71 \x01 "
" \x71 \x01 \x70 \x01 \x70 \x01 \x68 \x01 "
" \x68 \x01 \x67 \x01 \x67 \x01 \x66 \x01 "
" \x66 \x01 \x65 \x01 \x65 \x01 \x57 \x01 "
" \x57 \x01 \x56 \x01 \x56 \x01 \x55 \x01 "
" \x55 \x01 \x54 \x01 \x54 \x01 \x53 \x01 "
" \x53 \x01 \x52 \x01 \x52 \x01 \x51 \x01 "
" \x51 \x01 \x50 \x01 \x50 \x01 \x48 \x01 "
" \x48 \x01 \x47 \x01 \x47 \x01 \x46 \x01 "
" \x46 \x01 \x45 \x01 \x45 \x01 \x44 \x01 "
" \x44 \x01 \x43 \x01 \x43 \x01 \x42 \x01 "
" \x42 \x01 \x41 \x01 \x41 \x01 \x40 \x01 "
" \x40 \x01 \x40 \x01 \x40 \x01 \x40 \x01 "
" \x40 \x01 \x40 \x01 \x40 \x01 \x40 \x01 "
" \x40 \x01 \x40 \x01 \x40 \x01 \x40 \x01 "
" \x40 \x01 \x40 \x01 \x40 \x01 \x40 \x01 " ) ;
/* Write PDA according to certain rules.
*
* For every production data record , look for a previous setting in
* the pda , and use that .
*
* For certain records , use defaults if they are not found in pda .
*/
int hermes_apply_pda_with_defaults ( hermes_t * hw ,
const char * first_pdr ,
2009-02-21 16:52:54 +00:00
const void * pdr_end ,
const __le16 * pda ,
const void * pda_end )
2008-08-21 23:27:53 +01:00
{
const struct pdr * pdr = ( const struct pdr * ) first_pdr ;
2009-02-21 16:52:54 +00:00
const struct pdi * first_pdi = ( const struct pdi * ) & pda [ 2 ] ;
const struct pdi * pdi ;
const struct pdi * default_pdi = NULL ;
const struct pdi * outdoor_pdi ;
2008-08-21 23:27:53 +01:00
int record_id ;
2009-02-21 16:52:54 +00:00
pdr_end - = sizeof ( struct pdr ) ;
while ( ( ( void * ) pdr < = pdr_end ) & &
2008-08-21 23:27:53 +01:00
( pdr_id ( pdr ) ! = PDI_END ) ) {
/*
* For spectrum_cs firmwares ,
* PDR area is currently not terminated by PDI_END .
* It ' s followed by CRC records , which have the type
* field where PDR has length . The type can be 0 or 1.
*/
if ( pdr_len ( pdr ) < 2 )
break ;
record_id = pdr_id ( pdr ) ;
2009-02-21 16:52:54 +00:00
pdi = hermes_find_pdi ( first_pdi , record_id , pda_end ) ;
2008-08-21 23:27:53 +01:00
if ( pdi )
2009-06-18 23:21:27 +01:00
pr_debug ( PFX " Found record 0x%04x at %p \n " ,
record_id , pdi ) ;
2008-08-21 23:27:53 +01:00
switch ( record_id ) {
case 0x110 : /* Modem REFDAC values */
case 0x120 : /* Modem VGDAC values */
2009-02-21 16:52:54 +00:00
outdoor_pdi = hermes_find_pdi ( first_pdi , record_id + 1 ,
pda_end ) ;
2008-08-21 23:27:53 +01:00
default_pdi = NULL ;
if ( outdoor_pdi ) {
pdi = outdoor_pdi ;
2009-06-18 23:21:27 +01:00
pr_debug ( PFX
" Using outdoor record 0x%04x at %p \n " ,
record_id + 1 , pdi ) ;
2008-08-21 23:27:53 +01:00
}
break ;
2009-09-28 21:43:57 -04:00
case 0x5 : /* HWIF Compatibility */
2008-08-21 23:27:53 +01:00
default_pdi = ( struct pdi * ) & DEFAULT_PDR ( 0x0005 ) ;
break ;
case 0x108 : /* PPPPSign */
default_pdi = ( struct pdi * ) & DEFAULT_PDR ( 0x0108 ) ;
break ;
case 0x109 : /* PPPPProf */
default_pdi = ( struct pdi * ) & DEFAULT_PDR ( 0x0109 ) ;
break ;
case 0x150 : /* Antenna diversity */
default_pdi = ( struct pdi * ) & DEFAULT_PDR ( 0x0150 ) ;
break ;
case 0x160 : /* Modem VCO band Set-up */
default_pdi = ( struct pdi * ) & DEFAULT_PDR ( 0x0160 ) ;
break ;
case 0x161 : /* Modem Rx Gain Table Values */
default_pdi = ( struct pdi * ) & DEFAULT_PDR ( 0x0161 ) ;
break ;
default :
default_pdi = NULL ;
break ;
}
if ( ! pdi & & default_pdi ) {
/* Use default */
pdi = default_pdi ;
2009-06-18 23:21:27 +01:00
pr_debug ( PFX " Using default record 0x%04x at %p \n " ,
record_id , pdi ) ;
2008-08-21 23:27:53 +01:00
}
if ( pdi ) {
/* Lengths of the data in PDI and PDR must match */
2009-02-21 16:52:54 +00:00
if ( ( pdi_len ( pdi ) = = pdr_len ( pdr ) ) & &
( ( void * ) pdi - > data + pdi_len ( pdi ) < pda_end ) ) {
2008-08-21 23:27:53 +01:00
/* do the actual plugging */
2010-05-01 14:05:43 +01:00
hw - > ops - > program ( hw , pdi - > data , pdr_addr ( pdr ) ,
pdi_len ( pdi ) ) ;
2008-08-21 23:27:53 +01:00
}
}
pdr + + ;
}
return 0 ;
}