2015-10-26 10:27:39 +01:00
/*
* Marvell NFC driver : Firmware downloader
*
* Copyright ( C ) 2015 , Marvell International Ltd .
*
* This software file ( the " File " ) is distributed by Marvell International
* Ltd . under the terms of the GNU General Public License Version 2 , June 1991
* ( the " License " ) . You may use , redistribute and / or modify this File in
* accordance with the terms and conditions of the License , a copy of which
* is available on the worldwide web at
* http : //www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* THE FILE IS DISTRIBUTED AS - IS , WITHOUT WARRANTY OF ANY KIND , AND THE
* IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
* ARE EXPRESSLY DISCLAIMED . The License provides additional details about
* this warranty disclaimer .
*/
# include <linux/module.h>
2016-10-26 11:00:12 +02:00
# include <asm/unaligned.h>
2015-10-26 10:27:39 +01:00
# include <linux/firmware.h>
# include <linux/nfc.h>
# include <net/nfc/nci.h>
# include <net/nfc/nci_core.h>
# include "nfcmrvl.h"
# define FW_DNLD_TIMEOUT 15000
# define NCI_OP_PROPRIETARY_BOOT_CMD nci_opcode_pack(NCI_GID_PROPRIETARY, \
NCI_OP_PROP_BOOT_CMD )
/* FW download states */
enum {
STATE_RESET = 0 ,
STATE_INIT ,
STATE_SET_REF_CLOCK ,
STATE_SET_HI_CONFIG ,
STATE_OPEN_LC ,
STATE_FW_DNLD ,
STATE_CLOSE_LC ,
STATE_BOOT
} ;
enum {
SUBSTATE_WAIT_COMMAND = 0 ,
SUBSTATE_WAIT_ACK_CREDIT ,
SUBSTATE_WAIT_NACK_CREDIT ,
SUBSTATE_WAIT_DATA_CREDIT ,
} ;
/*
* * Patterns for responses
*/
static const uint8_t nci_pattern_core_reset_ntf [ ] = {
0x60 , 0x00 , 0x02 , 0xA0 , 0x01
} ;
static const uint8_t nci_pattern_core_init_rsp [ ] = {
0x40 , 0x01 , 0x11
} ;
static const uint8_t nci_pattern_core_set_config_rsp [ ] = {
0x40 , 0x02 , 0x02 , 0x00 , 0x00
} ;
static const uint8_t nci_pattern_core_conn_create_rsp [ ] = {
0x40 , 0x04 , 0x04 , 0x00
} ;
static const uint8_t nci_pattern_core_conn_close_rsp [ ] = {
0x40 , 0x05 , 0x01 , 0x00
} ;
static const uint8_t nci_pattern_core_conn_credits_ntf [ ] = {
0x60 , 0x06 , 0x03 , 0x01 , NCI_CORE_LC_CONNID_PROP_FW_DL , 0x01
} ;
static const uint8_t nci_pattern_proprietary_boot_rsp [ ] = {
0x4F , 0x3A , 0x01 , 0x00
} ;
static struct sk_buff * alloc_lc_skb ( struct nfcmrvl_private * priv , uint8_t plen )
{
struct sk_buff * skb ;
struct nci_data_hdr * hdr ;
skb = nci_skb_alloc ( priv - > ndev , ( NCI_DATA_HDR_SIZE + plen ) , GFP_KERNEL ) ;
if ( ! skb ) {
pr_err ( " no memory for data \n " ) ;
return NULL ;
}
networking: make skb_put & friends return void pointers
It seems like a historic accident that these return unsigned char *,
and in many places that means casts are required, more often than not.
Make these functions (skb_put, __skb_put and pskb_put) return void *
and remove all the casts across the tree, adding a (u8 *) cast only
where the unsigned char pointer was used directly, all done with the
following spatch:
@@
expression SKB, LEN;
typedef u8;
identifier fn = { skb_put, __skb_put };
@@
- *(fn(SKB, LEN))
+ *(u8 *)fn(SKB, LEN)
@@
expression E, SKB, LEN;
identifier fn = { skb_put, __skb_put };
type T;
@@
- E = ((T *)(fn(SKB, LEN)))
+ E = fn(SKB, LEN)
which actually doesn't cover pskb_put since there are only three
users overall.
A handful of stragglers were converted manually, notably a macro in
drivers/isdn/i4l/isdn_bsdcomp.c and, oddly enough, one of the many
instances in net/bluetooth/hci_sock.c. In the former file, I also
had to fix one whitespace problem spatch introduced.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2017-06-16 14:29:21 +02:00
hdr = skb_put ( skb , NCI_DATA_HDR_SIZE ) ;
2015-10-26 10:27:39 +01:00
hdr - > conn_id = NCI_CORE_LC_CONNID_PROP_FW_DL ;
hdr - > rfu = 0 ;
hdr - > plen = plen ;
nci_mt_set ( ( __u8 * ) hdr , NCI_MT_DATA_PKT ) ;
nci_pbf_set ( ( __u8 * ) hdr , NCI_PBF_LAST ) ;
return skb ;
}
static void fw_dnld_over ( struct nfcmrvl_private * priv , u32 error )
{
if ( priv - > fw_dnld . fw ) {
release_firmware ( priv - > fw_dnld . fw ) ;
priv - > fw_dnld . fw = NULL ;
priv - > fw_dnld . header = NULL ;
priv - > fw_dnld . binary_config = NULL ;
}
atomic_set ( & priv - > ndev - > cmd_cnt , 0 ) ;
2015-11-03 19:19:36 +01:00
if ( timer_pending ( & priv - > ndev - > cmd_timer ) )
del_timer_sync ( & priv - > ndev - > cmd_timer ) ;
if ( timer_pending ( & priv - > fw_dnld . timer ) )
del_timer_sync ( & priv - > fw_dnld . timer ) ;
2015-10-26 10:27:39 +01:00
nfc_info ( priv - > dev , " FW loading over (%d)] \n " , error ) ;
if ( error ! = 0 ) {
/* failed, halt the chip to avoid power consumption */
nfcmrvl_chip_halt ( priv ) ;
}
nfc_fw_download_done ( priv - > ndev - > nfc_dev , priv - > fw_dnld . name , error ) ;
}
static void fw_dnld_timeout ( unsigned long arg )
{
struct nfcmrvl_private * priv = ( struct nfcmrvl_private * ) arg ;
nfc_err ( priv - > dev , " FW loading timeout " ) ;
priv - > fw_dnld . state = STATE_RESET ;
fw_dnld_over ( priv , - ETIMEDOUT ) ;
}
static int process_state_reset ( struct nfcmrvl_private * priv ,
struct sk_buff * skb )
{
if ( sizeof ( nci_pattern_core_reset_ntf ) ! = skb - > len | |
memcmp ( skb - > data , nci_pattern_core_reset_ntf ,
sizeof ( nci_pattern_core_reset_ntf ) ) )
return - EINVAL ;
nfc_info ( priv - > dev , " BootROM reset, start fw download \n " ) ;
/* Start FW download state machine */
priv - > fw_dnld . state = STATE_INIT ;
nci_send_cmd ( priv - > ndev , NCI_OP_CORE_INIT_CMD , 0 , NULL ) ;
return 0 ;
}
static int process_state_init ( struct nfcmrvl_private * priv , struct sk_buff * skb )
{
struct nci_core_set_config_cmd cmd ;
if ( sizeof ( nci_pattern_core_init_rsp ) > = skb - > len | |
memcmp ( skb - > data , nci_pattern_core_init_rsp ,
sizeof ( nci_pattern_core_init_rsp ) ) )
return - EINVAL ;
cmd . num_params = 1 ;
cmd . param . id = NFCMRVL_PROP_REF_CLOCK ;
cmd . param . len = 4 ;
memcpy ( cmd . param . val , & priv - > fw_dnld . header - > ref_clock , 4 ) ;
nci_send_cmd ( priv - > ndev , NCI_OP_CORE_SET_CONFIG_CMD , 3 + cmd . param . len ,
& cmd ) ;
priv - > fw_dnld . state = STATE_SET_REF_CLOCK ;
return 0 ;
}
static void create_lc ( struct nfcmrvl_private * priv )
{
uint8_t param [ 2 ] = { NCI_CORE_LC_PROP_FW_DL , 0x0 } ;
priv - > fw_dnld . state = STATE_OPEN_LC ;
nci_send_cmd ( priv - > ndev , NCI_OP_CORE_CONN_CREATE_CMD , 2 , param ) ;
}
static int process_state_set_ref_clock ( struct nfcmrvl_private * priv ,
struct sk_buff * skb )
{
struct nci_core_set_config_cmd cmd ;
if ( sizeof ( nci_pattern_core_set_config_rsp ) ! = skb - > len | |
memcmp ( skb - > data , nci_pattern_core_set_config_rsp , skb - > len ) )
return - EINVAL ;
cmd . num_params = 1 ;
cmd . param . id = NFCMRVL_PROP_SET_HI_CONFIG ;
switch ( priv - > phy ) {
case NFCMRVL_PHY_UART :
cmd . param . len = 5 ;
memcpy ( cmd . param . val ,
& priv - > fw_dnld . binary_config - > uart . baudrate ,
4 ) ;
cmd . param . val [ 4 ] =
priv - > fw_dnld . binary_config - > uart . flow_control ;
break ;
case NFCMRVL_PHY_I2C :
cmd . param . len = 5 ;
memcpy ( cmd . param . val ,
& priv - > fw_dnld . binary_config - > i2c . clk ,
4 ) ;
cmd . param . val [ 4 ] = 0 ;
break ;
case NFCMRVL_PHY_SPI :
cmd . param . len = 5 ;
memcpy ( cmd . param . val ,
& priv - > fw_dnld . binary_config - > spi . clk ,
4 ) ;
cmd . param . val [ 4 ] = 0 ;
break ;
default :
create_lc ( priv ) ;
return 0 ;
}
priv - > fw_dnld . state = STATE_SET_HI_CONFIG ;
nci_send_cmd ( priv - > ndev , NCI_OP_CORE_SET_CONFIG_CMD , 3 + cmd . param . len ,
& cmd ) ;
return 0 ;
}
static int process_state_set_hi_config ( struct nfcmrvl_private * priv ,
struct sk_buff * skb )
{
if ( sizeof ( nci_pattern_core_set_config_rsp ) ! = skb - > len | |
memcmp ( skb - > data , nci_pattern_core_set_config_rsp , skb - > len ) )
return - EINVAL ;
create_lc ( priv ) ;
return 0 ;
}
static int process_state_open_lc ( struct nfcmrvl_private * priv ,
struct sk_buff * skb )
{
if ( sizeof ( nci_pattern_core_conn_create_rsp ) > = skb - > len | |
memcmp ( skb - > data , nci_pattern_core_conn_create_rsp ,
sizeof ( nci_pattern_core_conn_create_rsp ) ) )
return - EINVAL ;
priv - > fw_dnld . state = STATE_FW_DNLD ;
priv - > fw_dnld . substate = SUBSTATE_WAIT_COMMAND ;
priv - > fw_dnld . offset = priv - > fw_dnld . binary_config - > offset ;
return 0 ;
}
static int process_state_fw_dnld ( struct nfcmrvl_private * priv ,
struct sk_buff * skb )
{
uint16_t len ;
uint16_t comp_len ;
struct sk_buff * out_skb ;
switch ( priv - > fw_dnld . substate ) {
case SUBSTATE_WAIT_COMMAND :
/*
* Command format :
* B0 . .2 : NCI header
* B3 : Helper command ( 0xA5 )
* B4 . .5 : le16 data size
* B6 . .7 : le16 data size complement ( ~ )
* B8 . . N : payload
*/
/* Remove NCI HDR */
skb_pull ( skb , 3 ) ;
if ( skb - > data [ 0 ] ! = HELPER_CMD_PACKET_FORMAT | | skb - > len ! = 5 ) {
nfc_err ( priv - > dev , " bad command " ) ;
return - EINVAL ;
}
skb_pull ( skb , 1 ) ;
2017-04-17 00:42:22 +02:00
len = get_unaligned_le16 ( skb - > data ) ;
2015-10-26 10:27:39 +01:00
skb_pull ( skb , 2 ) ;
2017-04-17 00:42:22 +02:00
comp_len = get_unaligned_le16 ( skb - > data ) ;
2015-10-26 10:27:39 +01:00
memcpy ( & comp_len , skb - > data , 2 ) ;
skb_pull ( skb , 2 ) ;
if ( ( ( ~ len ) & 0xFFFF ) ! = comp_len ) {
nfc_err ( priv - > dev , " bad len complement: %x %x %x " ,
len , comp_len , ( ~ len & 0xFFFF ) ) ;
out_skb = alloc_lc_skb ( priv , 1 ) ;
if ( ! out_skb )
return - ENOMEM ;
networking: make skb_put & friends return void pointers
It seems like a historic accident that these return unsigned char *,
and in many places that means casts are required, more often than not.
Make these functions (skb_put, __skb_put and pskb_put) return void *
and remove all the casts across the tree, adding a (u8 *) cast only
where the unsigned char pointer was used directly, all done with the
following spatch:
@@
expression SKB, LEN;
typedef u8;
identifier fn = { skb_put, __skb_put };
@@
- *(fn(SKB, LEN))
+ *(u8 *)fn(SKB, LEN)
@@
expression E, SKB, LEN;
identifier fn = { skb_put, __skb_put };
type T;
@@
- E = ((T *)(fn(SKB, LEN)))
+ E = fn(SKB, LEN)
which actually doesn't cover pskb_put since there are only three
users overall.
A handful of stragglers were converted manually, notably a macro in
drivers/isdn/i4l/isdn_bsdcomp.c and, oddly enough, one of the many
instances in net/bluetooth/hci_sock.c. In the former file, I also
had to fix one whitespace problem spatch introduced.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2017-06-16 14:29:21 +02:00
* ( u8 * ) skb_put ( out_skb , 1 ) = 0xBF ;
2015-10-26 10:27:39 +01:00
nci_send_frame ( priv - > ndev , out_skb ) ;
priv - > fw_dnld . substate = SUBSTATE_WAIT_NACK_CREDIT ;
return 0 ;
}
priv - > fw_dnld . chunk_len = len ;
out_skb = alloc_lc_skb ( priv , 1 ) ;
if ( ! out_skb )
return - ENOMEM ;
networking: make skb_put & friends return void pointers
It seems like a historic accident that these return unsigned char *,
and in many places that means casts are required, more often than not.
Make these functions (skb_put, __skb_put and pskb_put) return void *
and remove all the casts across the tree, adding a (u8 *) cast only
where the unsigned char pointer was used directly, all done with the
following spatch:
@@
expression SKB, LEN;
typedef u8;
identifier fn = { skb_put, __skb_put };
@@
- *(fn(SKB, LEN))
+ *(u8 *)fn(SKB, LEN)
@@
expression E, SKB, LEN;
identifier fn = { skb_put, __skb_put };
type T;
@@
- E = ((T *)(fn(SKB, LEN)))
+ E = fn(SKB, LEN)
which actually doesn't cover pskb_put since there are only three
users overall.
A handful of stragglers were converted manually, notably a macro in
drivers/isdn/i4l/isdn_bsdcomp.c and, oddly enough, one of the many
instances in net/bluetooth/hci_sock.c. In the former file, I also
had to fix one whitespace problem spatch introduced.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2017-06-16 14:29:21 +02:00
* ( u8 * ) skb_put ( out_skb , 1 ) = HELPER_ACK_PACKET_FORMAT ;
2015-10-26 10:27:39 +01:00
nci_send_frame ( priv - > ndev , out_skb ) ;
priv - > fw_dnld . substate = SUBSTATE_WAIT_ACK_CREDIT ;
break ;
case SUBSTATE_WAIT_ACK_CREDIT :
if ( sizeof ( nci_pattern_core_conn_credits_ntf ) ! = skb - > len | |
memcmp ( nci_pattern_core_conn_credits_ntf , skb - > data ,
skb - > len ) ) {
nfc_err ( priv - > dev , " bad packet: waiting for credit " ) ;
return - EINVAL ;
}
if ( priv - > fw_dnld . chunk_len = = 0 ) {
/* FW Loading is done */
uint8_t conn_id = NCI_CORE_LC_CONNID_PROP_FW_DL ;
priv - > fw_dnld . state = STATE_CLOSE_LC ;
nci_send_cmd ( priv - > ndev , NCI_OP_CORE_CONN_CLOSE_CMD ,
1 , & conn_id ) ;
} else {
out_skb = alloc_lc_skb ( priv , priv - > fw_dnld . chunk_len ) ;
if ( ! out_skb )
return - ENOMEM ;
networking: introduce and use skb_put_data()
A common pattern with skb_put() is to just want to memcpy()
some data into the new space, introduce skb_put_data() for
this.
An spatch similar to the one for skb_put_zero() converts many
of the places using it:
@@
identifier p, p2;
expression len, skb, data;
type t, t2;
@@
(
-p = skb_put(skb, len);
+p = skb_put_data(skb, data, len);
|
-p = (t)skb_put(skb, len);
+p = skb_put_data(skb, data, len);
)
(
p2 = (t2)p;
-memcpy(p2, data, len);
|
-memcpy(p, data, len);
)
@@
type t, t2;
identifier p, p2;
expression skb, data;
@@
t *p;
...
(
-p = skb_put(skb, sizeof(t));
+p = skb_put_data(skb, data, sizeof(t));
|
-p = (t *)skb_put(skb, sizeof(t));
+p = skb_put_data(skb, data, sizeof(t));
)
(
p2 = (t2)p;
-memcpy(p2, data, sizeof(*p));
|
-memcpy(p, data, sizeof(*p));
)
@@
expression skb, len, data;
@@
-memcpy(skb_put(skb, len), data, len);
+skb_put_data(skb, data, len);
(again, manually post-processed to retain some comments)
Reviewed-by: Stephen Hemminger <stephen@networkplumber.org>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2017-06-16 14:29:20 +02:00
skb_put_data ( out_skb ,
( ( uint8_t * ) priv - > fw_dnld . fw - > data ) + priv - > fw_dnld . offset ,
priv - > fw_dnld . chunk_len ) ;
2015-10-26 10:27:39 +01:00
nci_send_frame ( priv - > ndev , out_skb ) ;
priv - > fw_dnld . substate = SUBSTATE_WAIT_DATA_CREDIT ;
}
break ;
case SUBSTATE_WAIT_DATA_CREDIT :
if ( sizeof ( nci_pattern_core_conn_credits_ntf ) ! = skb - > len | |
memcmp ( nci_pattern_core_conn_credits_ntf , skb - > data ,
skb - > len ) ) {
nfc_err ( priv - > dev , " bad packet: waiting for credit " ) ;
return - EINVAL ;
}
priv - > fw_dnld . offset + = priv - > fw_dnld . chunk_len ;
priv - > fw_dnld . chunk_len = 0 ;
priv - > fw_dnld . substate = SUBSTATE_WAIT_COMMAND ;
break ;
case SUBSTATE_WAIT_NACK_CREDIT :
if ( sizeof ( nci_pattern_core_conn_credits_ntf ) ! = skb - > len | |
memcmp ( nci_pattern_core_conn_credits_ntf , skb - > data ,
skb - > len ) ) {
nfc_err ( priv - > dev , " bad packet: waiting for credit " ) ;
return - EINVAL ;
}
priv - > fw_dnld . substate = SUBSTATE_WAIT_COMMAND ;
break ;
}
return 0 ;
}
static int process_state_close_lc ( struct nfcmrvl_private * priv ,
struct sk_buff * skb )
{
if ( sizeof ( nci_pattern_core_conn_close_rsp ) ! = skb - > len | |
memcmp ( skb - > data , nci_pattern_core_conn_close_rsp , skb - > len ) )
return - EINVAL ;
priv - > fw_dnld . state = STATE_BOOT ;
nci_send_cmd ( priv - > ndev , NCI_OP_PROPRIETARY_BOOT_CMD , 0 , NULL ) ;
return 0 ;
}
static int process_state_boot ( struct nfcmrvl_private * priv , struct sk_buff * skb )
{
if ( sizeof ( nci_pattern_proprietary_boot_rsp ) ! = skb - > len | |
memcmp ( skb - > data , nci_pattern_proprietary_boot_rsp , skb - > len ) )
return - EINVAL ;
/*
* Update HI config to use the right configuration for the next
* data exchanges .
*/
priv - > if_ops - > nci_update_config ( priv ,
& priv - > fw_dnld . binary_config - > config ) ;
if ( priv - > fw_dnld . binary_config = = & priv - > fw_dnld . header - > helper ) {
/*
* This is the case where an helper was needed and we have
* uploaded it . Now we have to wait the next RESET NTF to start
* FW download .
*/
priv - > fw_dnld . state = STATE_RESET ;
priv - > fw_dnld . binary_config = & priv - > fw_dnld . header - > firmware ;
nfc_info ( priv - > dev , " FW loading: helper loaded " ) ;
} else {
nfc_info ( priv - > dev , " FW loading: firmware loaded " ) ;
fw_dnld_over ( priv , 0 ) ;
}
return 0 ;
}
static void fw_dnld_rx_work ( struct work_struct * work )
{
int ret ;
struct sk_buff * skb ;
struct nfcmrvl_fw_dnld * fw_dnld = container_of ( work ,
struct nfcmrvl_fw_dnld ,
rx_work ) ;
struct nfcmrvl_private * priv = container_of ( fw_dnld ,
struct nfcmrvl_private ,
fw_dnld ) ;
while ( ( skb = skb_dequeue ( & fw_dnld - > rx_q ) ) ) {
nfc_send_to_raw_sock ( priv - > ndev - > nfc_dev , skb ,
RAW_PAYLOAD_NCI , NFC_DIRECTION_RX ) ;
switch ( fw_dnld - > state ) {
case STATE_RESET :
ret = process_state_reset ( priv , skb ) ;
break ;
case STATE_INIT :
ret = process_state_init ( priv , skb ) ;
break ;
case STATE_SET_REF_CLOCK :
ret = process_state_set_ref_clock ( priv , skb ) ;
break ;
case STATE_SET_HI_CONFIG :
ret = process_state_set_hi_config ( priv , skb ) ;
break ;
case STATE_OPEN_LC :
ret = process_state_open_lc ( priv , skb ) ;
break ;
case STATE_FW_DNLD :
ret = process_state_fw_dnld ( priv , skb ) ;
break ;
case STATE_CLOSE_LC :
ret = process_state_close_lc ( priv , skb ) ;
break ;
case STATE_BOOT :
ret = process_state_boot ( priv , skb ) ;
break ;
default :
ret = - EFAULT ;
}
kfree_skb ( skb ) ;
if ( ret ! = 0 ) {
nfc_err ( priv - > dev , " FW loading error " ) ;
fw_dnld_over ( priv , ret ) ;
break ;
}
}
}
int nfcmrvl_fw_dnld_init ( struct nfcmrvl_private * priv )
{
char name [ 32 ] ;
INIT_WORK ( & priv - > fw_dnld . rx_work , fw_dnld_rx_work ) ;
snprintf ( name , sizeof ( name ) , " %s_nfcmrvl_fw_dnld_rx_wq " ,
dev_name ( priv - > dev ) ) ;
priv - > fw_dnld . rx_wq = create_singlethread_workqueue ( name ) ;
if ( ! priv - > fw_dnld . rx_wq )
return - ENOMEM ;
skb_queue_head_init ( & priv - > fw_dnld . rx_q ) ;
return 0 ;
}
void nfcmrvl_fw_dnld_deinit ( struct nfcmrvl_private * priv )
{
destroy_workqueue ( priv - > fw_dnld . rx_wq ) ;
}
void nfcmrvl_fw_dnld_recv_frame ( struct nfcmrvl_private * priv ,
struct sk_buff * skb )
{
2015-11-03 19:19:36 +01:00
/* Discard command timer */
if ( timer_pending ( & priv - > ndev - > cmd_timer ) )
del_timer_sync ( & priv - > ndev - > cmd_timer ) ;
2015-10-26 10:27:39 +01:00
/* Allow next command */
atomic_set ( & priv - > ndev - > cmd_cnt , 1 ) ;
/* Queue and trigger rx work */
skb_queue_tail ( & priv - > fw_dnld . rx_q , skb ) ;
queue_work ( priv - > fw_dnld . rx_wq , & priv - > fw_dnld . rx_work ) ;
}
void nfcmrvl_fw_dnld_abort ( struct nfcmrvl_private * priv )
{
fw_dnld_over ( priv , - EHOSTDOWN ) ;
}
int nfcmrvl_fw_dnld_start ( struct nci_dev * ndev , const char * firmware_name )
{
struct nfcmrvl_private * priv = nci_get_drvdata ( ndev ) ;
struct nfcmrvl_fw_dnld * fw_dnld = & priv - > fw_dnld ;
if ( ! priv - > support_fw_dnld )
return - ENOTSUPP ;
if ( ! firmware_name | | ! firmware_name [ 0 ] )
return - EINVAL ;
strcpy ( fw_dnld - > name , firmware_name ) ;
/*
* Retrieve FW binary file and parse it to initialize FW download
* state machine .
*/
/* Retrieve FW binary */
if ( request_firmware ( & fw_dnld - > fw , firmware_name , priv - > dev ) < 0 ) {
nfc_err ( priv - > dev , " failed to retrieve FW %s " , firmware_name ) ;
return - ENOENT ;
}
fw_dnld - > header = ( const struct nfcmrvl_fw * ) priv - > fw_dnld . fw - > data ;
if ( fw_dnld - > header - > magic ! = NFCMRVL_FW_MAGIC | |
fw_dnld - > header - > phy ! = priv - > phy ) {
nfc_err ( priv - > dev , " bad firmware binary %s magic=0x%x phy=%d " ,
firmware_name , fw_dnld - > header - > magic ,
fw_dnld - > header - > phy ) ;
release_firmware ( fw_dnld - > fw ) ;
fw_dnld - > header = NULL ;
return - EINVAL ;
}
if ( fw_dnld - > header - > helper . offset ! = 0 ) {
nfc_info ( priv - > dev , " loading helper " ) ;
fw_dnld - > binary_config = & fw_dnld - > header - > helper ;
} else {
nfc_info ( priv - > dev , " loading firmware " ) ;
fw_dnld - > binary_config = & fw_dnld - > header - > firmware ;
}
/* Configure a timer for timeout */
setup_timer ( & priv - > fw_dnld . timer , fw_dnld_timeout ,
( unsigned long ) priv ) ;
mod_timer ( & priv - > fw_dnld . timer ,
jiffies + msecs_to_jiffies ( FW_DNLD_TIMEOUT ) ) ;
/* Ronfigure HI to be sure that it is the bootrom values */
priv - > if_ops - > nci_update_config ( priv ,
& fw_dnld - > header - > bootrom . config ) ;
/* Allow first command */
atomic_set ( & priv - > ndev - > cmd_cnt , 1 ) ;
/* First, reset the chip */
priv - > fw_dnld . state = STATE_RESET ;
nfcmrvl_chip_reset ( priv ) ;
/* Now wait for CORE_RESET_NTF or timeout */
return 0 ;
}