2013-04-15 13:19:20 +04:00
/*
* MEI Library for mei bus nfc device access
*
* Copyright ( C ) 2013 Intel Corporation . All rights reserved .
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms and conditions 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
2013-12-06 20:56:16 +04:00
* along with this program ; if not , see < http : //www.gnu.org/licenses/>.
2013-04-15 13:19:20 +04:00
*/
2013-04-05 23:27:39 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2013-04-15 13:19:20 +04:00
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/nfc.h>
# include "mei_phy.h"
2015-05-07 15:54:06 +03:00
struct mei_nfc_hdr {
u8 cmd ;
2015-05-07 15:54:04 +03:00
u8 status ;
u16 req_id ;
u32 reserved ;
u16 data_size ;
2015-05-07 15:54:06 +03:00
} __packed ;
struct mei_nfc_cmd {
struct mei_nfc_hdr hdr ;
2015-05-07 15:54:04 +03:00
u8 sub_command ;
u8 data [ ] ;
} __packed ;
struct mei_nfc_reply {
2015-05-07 15:54:06 +03:00
struct mei_nfc_hdr hdr ;
2015-05-07 15:54:04 +03:00
u8 sub_command ;
u8 reply_status ;
u8 data [ ] ;
} __packed ;
struct mei_nfc_if_version {
u8 radio_version_sw [ 3 ] ;
u8 reserved [ 3 ] ;
u8 radio_version_hw [ 3 ] ;
u8 i2c_addr ;
u8 fw_ivn ;
u8 vendor_id ;
u8 radio_type ;
} __packed ;
struct mei_nfc_connect {
u8 fw_ivn ;
u8 vendor_id ;
} __packed ;
struct mei_nfc_connect_resp {
u8 fw_ivn ;
u8 vendor_id ;
u16 me_major ;
u16 me_minor ;
u16 me_hotfix ;
u16 me_build ;
} __packed ;
2013-04-15 13:19:20 +04:00
2015-05-07 15:54:04 +03:00
# define MEI_NFC_CMD_MAINTENANCE 0x00
# define MEI_NFC_CMD_HCI_SEND 0x01
# define MEI_NFC_CMD_HCI_RECV 0x02
# define MEI_NFC_SUBCMD_CONNECT 0x00
# define MEI_NFC_SUBCMD_IF_VERSION 0x01
2013-04-15 13:19:20 +04:00
# define MEI_NFC_MAX_READ (MEI_NFC_HEADER_SIZE + MEI_NFC_MAX_HCI_PAYLOAD)
# define MEI_DUMP_SKB_IN(info, skb) \
do { \
pr_debug ( " %s: \n " , info ) ; \
print_hex_dump_debug ( " mei in : " , DUMP_PREFIX_OFFSET , \
16 , 1 , ( skb ) - > data , ( skb ) - > len , false ) ; \
} while ( 0 )
# define MEI_DUMP_SKB_OUT(info, skb) \
do { \
pr_debug ( " %s: \n " , info ) ; \
print_hex_dump_debug ( " mei out: " , DUMP_PREFIX_OFFSET , \
2015-05-07 15:54:04 +03:00
16 , 1 , ( skb ) - > data , ( skb ) - > len , false ) ; \
2013-04-15 13:19:20 +04:00
} while ( 0 )
2015-05-07 15:54:06 +03:00
# define MEI_DUMP_NFC_HDR(info, _hdr) \
do { \
pr_debug ( " %s: \n " , info ) ; \
pr_debug ( " cmd=%02d status=%d req_id=%d rsvd=%d size=%d \n " , \
( _hdr ) - > cmd , ( _hdr ) - > status , ( _hdr ) - > req_id , \
( _hdr ) - > reserved , ( _hdr ) - > data_size ) ; \
} while ( 0 )
2015-05-07 15:54:04 +03:00
static int mei_nfc_if_version ( struct nfc_mei_phy * phy )
2013-04-15 13:19:20 +04:00
{
2015-05-07 15:54:04 +03:00
struct mei_nfc_cmd cmd ;
struct mei_nfc_reply * reply = NULL ;
struct mei_nfc_if_version * version ;
size_t if_version_length ;
int bytes_recv , r ;
2013-04-15 13:19:20 +04:00
pr_info ( " %s \n " , __func__ ) ;
2015-05-07 15:54:04 +03:00
memset ( & cmd , 0 , sizeof ( struct mei_nfc_cmd ) ) ;
2015-05-07 15:54:06 +03:00
cmd . hdr . cmd = MEI_NFC_CMD_MAINTENANCE ;
cmd . hdr . data_size = 1 ;
2015-05-07 15:54:04 +03:00
cmd . sub_command = MEI_NFC_SUBCMD_IF_VERSION ;
2013-04-15 13:19:20 +04:00
2015-05-07 15:54:06 +03:00
MEI_DUMP_NFC_HDR ( " version " , & cmd . hdr ) ;
2015-05-07 15:54:04 +03:00
r = mei_cl_send ( phy - > device , ( u8 * ) & cmd , sizeof ( struct mei_nfc_cmd ) ) ;
2013-04-15 13:19:20 +04:00
if ( r < 0 ) {
2015-05-07 15:54:04 +03:00
pr_err ( " Could not send IF version cmd \n " ) ;
2013-06-07 23:01:55 +04:00
return r ;
2013-04-15 13:19:20 +04:00
}
2015-05-07 15:54:04 +03:00
/* to be sure on the stack we alloc memory */
if_version_length = sizeof ( struct mei_nfc_reply ) +
sizeof ( struct mei_nfc_if_version ) ;
2013-05-01 01:48:50 +04:00
2015-05-07 15:54:04 +03:00
reply = kzalloc ( if_version_length , GFP_KERNEL ) ;
if ( ! reply )
return - ENOMEM ;
bytes_recv = mei_cl_recv ( phy - > device , ( u8 * ) reply , if_version_length ) ;
if ( bytes_recv < 0 | | bytes_recv < sizeof ( struct mei_nfc_reply ) ) {
pr_err ( " Could not read IF version \n " ) ;
r = - EIO ;
goto err ;
2013-05-01 01:48:50 +04:00
}
2015-05-07 15:54:04 +03:00
version = ( struct mei_nfc_if_version * ) reply - > data ;
2013-04-15 13:19:20 +04:00
2015-05-07 15:54:04 +03:00
phy - > fw_ivn = version - > fw_ivn ;
phy - > vendor_id = version - > vendor_id ;
phy - > radio_type = version - > radio_type ;
err :
kfree ( reply ) ;
return r ;
2013-04-15 13:19:20 +04:00
}
2015-05-07 15:54:04 +03:00
static int mei_nfc_connect ( struct nfc_mei_phy * phy )
2013-04-15 13:19:20 +04:00
{
2015-05-07 15:54:04 +03:00
struct mei_nfc_cmd * cmd , * reply ;
struct mei_nfc_connect * connect ;
struct mei_nfc_connect_resp * connect_resp ;
size_t connect_length , connect_resp_length ;
int bytes_recv , r ;
2013-04-15 13:19:20 +04:00
pr_info ( " %s \n " , __func__ ) ;
2015-05-07 15:54:04 +03:00
connect_length = sizeof ( struct mei_nfc_cmd ) +
sizeof ( struct mei_nfc_connect ) ;
2013-04-15 13:19:20 +04:00
2015-05-07 15:54:04 +03:00
connect_resp_length = sizeof ( struct mei_nfc_cmd ) +
sizeof ( struct mei_nfc_connect_resp ) ;
cmd = kzalloc ( connect_length , GFP_KERNEL ) ;
if ( ! cmd )
return - ENOMEM ;
connect = ( struct mei_nfc_connect * ) cmd - > data ;
reply = kzalloc ( connect_resp_length , GFP_KERNEL ) ;
if ( ! reply ) {
kfree ( cmd ) ;
return - ENOMEM ;
}
connect_resp = ( struct mei_nfc_connect_resp * ) reply - > data ;
2015-05-07 15:54:06 +03:00
cmd - > hdr . cmd = MEI_NFC_CMD_MAINTENANCE ;
cmd - > hdr . data_size = 3 ;
2015-05-07 15:54:04 +03:00
cmd - > sub_command = MEI_NFC_SUBCMD_CONNECT ;
connect - > fw_ivn = phy - > fw_ivn ;
connect - > vendor_id = phy - > vendor_id ;
2015-05-07 15:54:06 +03:00
MEI_DUMP_NFC_HDR ( " connect request " , & cmd - > hdr ) ;
2015-05-07 15:54:04 +03:00
r = mei_cl_send ( phy - > device , ( u8 * ) cmd , connect_length ) ;
if ( r < 0 ) {
pr_err ( " Could not send connect cmd %d \n " , r ) ;
goto err ;
}
bytes_recv = mei_cl_recv ( phy - > device , ( u8 * ) reply , connect_resp_length ) ;
if ( bytes_recv < 0 ) {
r = bytes_recv ;
pr_err ( " Could not read connect response %d \n " , r ) ;
goto err ;
}
2015-05-07 15:54:06 +03:00
MEI_DUMP_NFC_HDR ( " connect reply " , & reply - > hdr ) ;
2015-05-07 15:54:04 +03:00
pr_info ( " IVN 0x%x Vendor ID 0x%x \n " ,
connect_resp - > fw_ivn , connect_resp - > vendor_id ) ;
pr_info ( " ME FW %d.%d.%d.%d \n " ,
connect_resp - > me_major , connect_resp - > me_minor ,
connect_resp - > me_hotfix , connect_resp - > me_build ) ;
r = 0 ;
err :
kfree ( reply ) ;
kfree ( cmd ) ;
return r ;
}
static int mei_nfc_send ( struct nfc_mei_phy * phy , u8 * buf , size_t length )
{
2015-05-07 15:54:06 +03:00
struct mei_nfc_hdr * hdr ;
2015-05-07 15:54:04 +03:00
u8 * mei_buf ;
int err ;
err = - ENOMEM ;
mei_buf = kzalloc ( length + MEI_NFC_HEADER_SIZE , GFP_KERNEL ) ;
if ( ! mei_buf )
goto out ;
2015-05-07 15:54:06 +03:00
hdr = ( struct mei_nfc_hdr * ) mei_buf ;
2015-05-07 15:54:04 +03:00
hdr - > cmd = MEI_NFC_CMD_HCI_SEND ;
hdr - > status = 0 ;
hdr - > req_id = phy - > req_id ;
hdr - > reserved = 0 ;
hdr - > data_size = length ;
2015-05-07 15:54:06 +03:00
MEI_DUMP_NFC_HDR ( " send " , hdr ) ;
2015-05-07 15:54:04 +03:00
memcpy ( mei_buf + MEI_NFC_HEADER_SIZE , buf , length ) ;
err = mei_cl_send ( phy - > device , mei_buf , length + MEI_NFC_HEADER_SIZE ) ;
if ( err < 0 )
goto out ;
if ( ! wait_event_interruptible_timeout ( phy - > send_wq ,
phy - > recv_req_id = = phy - > req_id , HZ ) ) {
pr_err ( " NFC MEI command timeout \n " ) ;
err = - ETIME ;
} else {
phy - > req_id + + ;
}
out :
kfree ( mei_buf ) ;
return err ;
2013-04-15 13:19:20 +04:00
}
/*
* Writing a frame must not return the number of written bytes .
* It must return either zero for success , or < 0 for error .
* In addition , it must not alter the skb
*/
static int nfc_mei_phy_write ( void * phy_id , struct sk_buff * skb )
{
struct nfc_mei_phy * phy = phy_id ;
int r ;
MEI_DUMP_SKB_OUT ( " mei frame sent " , skb ) ;
2015-05-07 15:54:04 +03:00
r = mei_nfc_send ( phy , skb - > data , skb - > len ) ;
2013-04-15 13:19:20 +04:00
if ( r > 0 )
r = 0 ;
return r ;
}
2015-05-07 15:54:04 +03:00
static int mei_nfc_recv ( struct nfc_mei_phy * phy , u8 * buf , size_t length )
{
2015-05-07 15:54:06 +03:00
struct mei_nfc_hdr * hdr ;
2015-05-07 15:54:04 +03:00
int received_length ;
received_length = mei_cl_recv ( phy - > device , buf , length ) ;
if ( received_length < 0 )
return received_length ;
2015-05-07 15:54:06 +03:00
hdr = ( struct mei_nfc_hdr * ) buf ;
2015-05-07 15:54:04 +03:00
2015-05-07 15:54:06 +03:00
MEI_DUMP_NFC_HDR ( " receive " , hdr ) ;
if ( hdr - > cmd = = MEI_NFC_CMD_HCI_SEND ) {
phy - > recv_req_id = hdr - > req_id ;
2015-05-07 15:54:04 +03:00
wake_up ( & phy - > send_wq ) ;
return 0 ;
}
return received_length ;
}
static void nfc_mei_event_cb ( struct mei_cl_device * device , u32 events ,
void * context )
2013-04-15 13:19:20 +04:00
{
struct nfc_mei_phy * phy = context ;
if ( phy - > hard_fault ! = 0 )
return ;
if ( events & BIT ( MEI_CL_EVENT_RX ) ) {
struct sk_buff * skb ;
int reply_size ;
skb = alloc_skb ( MEI_NFC_MAX_READ , GFP_KERNEL ) ;
if ( ! skb )
return ;
2015-05-07 15:54:04 +03:00
reply_size = mei_nfc_recv ( phy , skb - > data , MEI_NFC_MAX_READ ) ;
2013-04-15 13:19:20 +04:00
if ( reply_size < MEI_NFC_HEADER_SIZE ) {
2013-10-31 21:47:46 +04:00
kfree_skb ( skb ) ;
2013-04-15 13:19:20 +04:00
return ;
}
skb_put ( skb , reply_size ) ;
skb_pull ( skb , MEI_NFC_HEADER_SIZE ) ;
MEI_DUMP_SKB_IN ( " mei frame read " , skb ) ;
nfc_hci_recv_frame ( phy - > hdev , skb ) ;
}
}
2015-05-07 15:54:04 +03:00
static int nfc_mei_phy_enable ( void * phy_id )
{
int r ;
struct nfc_mei_phy * phy = phy_id ;
pr_info ( " %s \n " , __func__ ) ;
if ( phy - > powered = = 1 )
return 0 ;
r = mei_cl_enable_device ( phy - > device ) ;
if ( r < 0 ) {
pr_err ( " Could not enable device %d \n " , r ) ;
return r ;
}
r = mei_nfc_if_version ( phy ) ;
if ( r < 0 ) {
pr_err ( " Could not enable device %d \n " , r ) ;
goto err ;
}
r = mei_nfc_connect ( phy ) ;
if ( r < 0 ) {
pr_err ( " Could not connect to device %d \n " , r ) ;
goto err ;
}
2015-07-26 09:54:23 +03:00
r = mei_cl_register_event_cb ( phy - > device , BIT ( MEI_CL_EVENT_RX ) ,
nfc_mei_event_cb , phy ) ;
2015-05-07 15:54:04 +03:00
if ( r ) {
pr_err ( " Event cb registration failed %d \n " , r ) ;
goto err ;
}
phy - > powered = 1 ;
return 0 ;
err :
phy - > powered = 0 ;
mei_cl_disable_device ( phy - > device ) ;
return r ;
}
static void nfc_mei_phy_disable ( void * phy_id )
{
struct nfc_mei_phy * phy = phy_id ;
pr_info ( " %s \n " , __func__ ) ;
mei_cl_disable_device ( phy - > device ) ;
phy - > powered = 0 ;
}
2013-04-15 13:19:20 +04:00
struct nfc_phy_ops mei_phy_ops = {
. write = nfc_mei_phy_write ,
. enable = nfc_mei_phy_enable ,
. disable = nfc_mei_phy_disable ,
} ;
EXPORT_SYMBOL_GPL ( mei_phy_ops ) ;
struct nfc_mei_phy * nfc_mei_phy_alloc ( struct mei_cl_device * device )
{
struct nfc_mei_phy * phy ;
phy = kzalloc ( sizeof ( struct nfc_mei_phy ) , GFP_KERNEL ) ;
if ( ! phy )
return NULL ;
phy - > device = device ;
2015-05-07 15:54:04 +03:00
init_waitqueue_head ( & phy - > send_wq ) ;
2013-04-15 13:19:20 +04:00
mei_cl_set_drvdata ( device , phy ) ;
return phy ;
}
EXPORT_SYMBOL_GPL ( nfc_mei_phy_alloc ) ;
void nfc_mei_phy_free ( struct nfc_mei_phy * phy )
{
2015-05-07 15:54:04 +03:00
mei_cl_disable_device ( phy - > device ) ;
2013-04-15 13:19:20 +04:00
kfree ( phy ) ;
}
EXPORT_SYMBOL_GPL ( nfc_mei_phy_free ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " mei bus NFC device interface " ) ;