2015-09-21 20:52:55 +03:00
/*
2016-06-07 02:58:20 +03:00
* Copyright ( c ) 2016 , Linaro Ltd .
2015-09-21 20:52:55 +03:00
* Copyright ( c ) 2015 , Sony Mobile Communications Inc .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 and
* only 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 .
*/
# include <linux/firmware.h>
# include <linux/module.h>
# include <linux/slab.h>
2016-06-07 02:58:20 +03:00
# include <linux/io.h>
# include <linux/of_platform.h>
# include <linux/platform_device.h>
2017-03-28 08:26:33 +03:00
# include <linux/rpmsg.h>
2016-06-07 02:58:20 +03:00
# include <linux/soc/qcom/wcnss_ctrl.h>
2015-09-21 20:52:55 +03:00
# define WCNSS_REQUEST_TIMEOUT (5 * HZ)
2016-06-07 02:58:20 +03:00
# define WCNSS_CBC_TIMEOUT (10 * HZ)
# define WCNSS_ACK_DONE_BOOTING 1
# define WCNSS_ACK_COLD_BOOTING 2
2015-09-21 20:52:55 +03:00
# define NV_FRAGMENT_SIZE 3072
# define NVBIN_FILE "wlan / prima / WCNSS_qcom_wlan_nv.bin"
/**
* struct wcnss_ctrl - driver context
* @ dev : device handle
* @ channel : SMD channel handle
* @ ack : completion for outstanding requests
2016-06-07 02:58:20 +03:00
* @ cbc : completion for cbc complete indication
2015-09-21 20:52:55 +03:00
* @ ack_status : status of the outstanding request
2016-06-07 02:58:20 +03:00
* @ probe_work : worker for uploading nv binary
2015-09-21 20:52:55 +03:00
*/
struct wcnss_ctrl {
struct device * dev ;
2017-03-28 08:26:33 +03:00
struct rpmsg_endpoint * channel ;
2015-09-21 20:52:55 +03:00
struct completion ack ;
2016-06-07 02:58:20 +03:00
struct completion cbc ;
2015-09-21 20:52:55 +03:00
int ack_status ;
2016-06-07 02:58:20 +03:00
struct work_struct probe_work ;
2015-09-21 20:52:55 +03:00
} ;
/* message types */
enum {
WCNSS_VERSION_REQ = 0x01000000 ,
WCNSS_VERSION_RESP ,
WCNSS_DOWNLOAD_NV_REQ ,
WCNSS_DOWNLOAD_NV_RESP ,
WCNSS_UPLOAD_CAL_REQ ,
WCNSS_UPLOAD_CAL_RESP ,
WCNSS_DOWNLOAD_CAL_REQ ,
WCNSS_DOWNLOAD_CAL_RESP ,
2016-06-07 02:58:20 +03:00
WCNSS_VBAT_LEVEL_IND ,
WCNSS_BUILD_VERSION_REQ ,
WCNSS_BUILD_VERSION_RESP ,
WCNSS_PM_CONFIG_REQ ,
WCNSS_CBC_COMPLETE_IND ,
2015-09-21 20:52:55 +03:00
} ;
/**
* struct wcnss_msg_hdr - common packet header for requests and responses
* @ type : packet message type
* @ len : total length of the packet , including this header
*/
struct wcnss_msg_hdr {
u32 type ;
u32 len ;
} __packed ;
/**
* struct wcnss_version_resp - version request response
* @ hdr : common packet wcnss_msg_hdr header
*/
struct wcnss_version_resp {
struct wcnss_msg_hdr hdr ;
u8 major ;
u8 minor ;
u8 version ;
u8 revision ;
} __packed ;
/**
* struct wcnss_download_nv_req - firmware fragment request
* @ hdr : common packet wcnss_msg_hdr header
* @ seq : sequence number of this fragment
* @ last : boolean indicator of this being the last fragment of the binary
* @ frag_size : length of this fragment
* @ fragment : fragment data
*/
struct wcnss_download_nv_req {
struct wcnss_msg_hdr hdr ;
u16 seq ;
u16 last ;
u32 frag_size ;
u8 fragment [ ] ;
} __packed ;
/**
* struct wcnss_download_nv_resp - firmware download response
* @ hdr : common packet wcnss_msg_hdr header
* @ status : boolean to indicate success of the download
*/
struct wcnss_download_nv_resp {
struct wcnss_msg_hdr hdr ;
u8 status ;
} __packed ;
/**
* wcnss_ctrl_smd_callback ( ) - handler from SMD responses
2016-03-29 07:35:22 +03:00
* @ channel : smd channel handle
2015-09-21 20:52:55 +03:00
* @ data : pointer to the incoming data packet
* @ count : size of the incoming data packet
*
* Handles any incoming packets from the remote WCNSS_CTRL service .
*/
2017-03-28 08:26:33 +03:00
static int wcnss_ctrl_smd_callback ( struct rpmsg_device * rpdev ,
void * data ,
int count ,
void * priv ,
u32 addr )
2015-09-21 20:52:55 +03:00
{
2017-03-28 08:26:33 +03:00
struct wcnss_ctrl * wcnss = dev_get_drvdata ( & rpdev - > dev ) ;
2015-09-21 20:52:55 +03:00
const struct wcnss_download_nv_resp * nvresp ;
const struct wcnss_version_resp * version ;
const struct wcnss_msg_hdr * hdr = data ;
switch ( hdr - > type ) {
case WCNSS_VERSION_RESP :
if ( count ! = sizeof ( * version ) ) {
dev_err ( wcnss - > dev ,
" invalid size of version response \n " ) ;
break ;
}
version = data ;
dev_info ( wcnss - > dev , " WCNSS Version %d.%d %d.%d \n " ,
version - > major , version - > minor ,
version - > version , version - > revision ) ;
2016-06-07 02:58:20 +03:00
complete ( & wcnss - > ack ) ;
2015-09-21 20:52:55 +03:00
break ;
case WCNSS_DOWNLOAD_NV_RESP :
if ( count ! = sizeof ( * nvresp ) ) {
dev_err ( wcnss - > dev ,
" invalid size of download response \n " ) ;
break ;
}
nvresp = data ;
wcnss - > ack_status = nvresp - > status ;
complete ( & wcnss - > ack ) ;
break ;
2016-06-07 02:58:20 +03:00
case WCNSS_CBC_COMPLETE_IND :
dev_dbg ( wcnss - > dev , " cold boot complete \n " ) ;
complete ( & wcnss - > cbc ) ;
break ;
2015-09-21 20:52:55 +03:00
default :
dev_info ( wcnss - > dev , " unknown message type %d \n " , hdr - > type ) ;
break ;
}
return 0 ;
}
/**
* wcnss_request_version ( ) - send a version request to WCNSS
* @ wcnss : wcnss ctrl driver context
*/
static int wcnss_request_version ( struct wcnss_ctrl * wcnss )
{
struct wcnss_msg_hdr msg ;
2016-06-07 02:58:20 +03:00
int ret ;
2015-09-21 20:52:55 +03:00
msg . type = WCNSS_VERSION_REQ ;
msg . len = sizeof ( msg ) ;
2017-03-28 08:26:33 +03:00
ret = rpmsg_send ( wcnss - > channel , & msg , sizeof ( msg ) ) ;
2016-06-07 02:58:20 +03:00
if ( ret < 0 )
return ret ;
ret = wait_for_completion_timeout ( & wcnss - > ack , WCNSS_CBC_TIMEOUT ) ;
if ( ! ret ) {
dev_err ( wcnss - > dev , " timeout waiting for version response \n " ) ;
return - ETIMEDOUT ;
}
2015-09-21 20:52:55 +03:00
2016-06-07 02:58:20 +03:00
return 0 ;
2015-09-21 20:52:55 +03:00
}
/**
* wcnss_download_nv ( ) - send nv binary to WCNSS
2016-06-07 02:58:20 +03:00
* @ wcnss : wcnss_ctrl state handle
* @ expect_cbc : indicator to caller that an cbc event is expected
*
* Returns 0 on success . Negative errno on failure .
2015-09-21 20:52:55 +03:00
*/
2016-06-07 02:58:20 +03:00
static int wcnss_download_nv ( struct wcnss_ctrl * wcnss , bool * expect_cbc )
2015-09-21 20:52:55 +03:00
{
struct wcnss_download_nv_req * req ;
const struct firmware * fw ;
const void * data ;
ssize_t left ;
int ret ;
req = kzalloc ( sizeof ( * req ) + NV_FRAGMENT_SIZE , GFP_KERNEL ) ;
if ( ! req )
2016-06-07 02:58:20 +03:00
return - ENOMEM ;
2015-09-21 20:52:55 +03:00
ret = request_firmware ( & fw , NVBIN_FILE , wcnss - > dev ) ;
2016-06-07 02:58:20 +03:00
if ( ret < 0 ) {
2015-09-21 20:52:55 +03:00
dev_err ( wcnss - > dev , " Failed to load nv file %s: %d \n " ,
NVBIN_FILE , ret ) ;
goto free_req ;
}
data = fw - > data ;
left = fw - > size ;
req - > hdr . type = WCNSS_DOWNLOAD_NV_REQ ;
req - > hdr . len = sizeof ( * req ) + NV_FRAGMENT_SIZE ;
req - > last = 0 ;
req - > frag_size = NV_FRAGMENT_SIZE ;
req - > seq = 0 ;
do {
if ( left < = NV_FRAGMENT_SIZE ) {
req - > last = 1 ;
req - > frag_size = left ;
req - > hdr . len = sizeof ( * req ) + left ;
}
memcpy ( req - > fragment , data , req - > frag_size ) ;
2017-03-28 08:26:33 +03:00
ret = rpmsg_send ( wcnss - > channel , req , req - > hdr . len ) ;
2016-06-07 02:58:20 +03:00
if ( ret < 0 ) {
2015-09-21 20:52:55 +03:00
dev_err ( wcnss - > dev , " failed to send smd packet \n " ) ;
goto release_fw ;
}
/* Increment for next fragment */
req - > seq + + ;
2018-02-28 03:45:25 +03:00
data + = NV_FRAGMENT_SIZE ;
2015-09-21 20:52:55 +03:00
left - = NV_FRAGMENT_SIZE ;
} while ( left > 0 ) ;
ret = wait_for_completion_timeout ( & wcnss - > ack , WCNSS_REQUEST_TIMEOUT ) ;
2016-06-07 02:58:20 +03:00
if ( ! ret ) {
2015-09-21 20:52:55 +03:00
dev_err ( wcnss - > dev , " timeout waiting for nv upload ack \n " ) ;
2016-06-07 02:58:20 +03:00
ret = - ETIMEDOUT ;
} else {
* expect_cbc = wcnss - > ack_status = = WCNSS_ACK_COLD_BOOTING ;
ret = 0 ;
}
2015-09-21 20:52:55 +03:00
release_fw :
release_firmware ( fw ) ;
free_req :
kfree ( req ) ;
2016-06-07 02:58:20 +03:00
return ret ;
}
/**
* qcom_wcnss_open_channel ( ) - open additional SMD channel to WCNSS
* @ wcnss : wcnss handle , retrieved from drvdata
* @ name : SMD channel name
* @ cb : callback to handle incoming data on the channel
*/
2017-03-28 08:26:33 +03:00
struct rpmsg_endpoint * qcom_wcnss_open_channel ( void * wcnss , const char * name , rpmsg_rx_cb_t cb , void * priv )
2016-06-07 02:58:20 +03:00
{
2017-03-28 08:26:33 +03:00
struct rpmsg_channel_info chinfo ;
2016-06-07 02:58:20 +03:00
struct wcnss_ctrl * _wcnss = wcnss ;
2017-03-28 08:26:33 +03:00
strncpy ( chinfo . name , name , sizeof ( chinfo . name ) ) ;
chinfo . src = RPMSG_ADDR_ANY ;
chinfo . dst = RPMSG_ADDR_ANY ;
return rpmsg_create_ept ( _wcnss - > channel - > rpdev , cb , priv , chinfo ) ;
2016-06-07 02:58:20 +03:00
}
EXPORT_SYMBOL ( qcom_wcnss_open_channel ) ;
static void wcnss_async_probe ( struct work_struct * work )
{
struct wcnss_ctrl * wcnss = container_of ( work , struct wcnss_ctrl , probe_work ) ;
bool expect_cbc ;
int ret ;
ret = wcnss_request_version ( wcnss ) ;
if ( ret < 0 )
return ;
ret = wcnss_download_nv ( wcnss , & expect_cbc ) ;
if ( ret < 0 )
return ;
/* Wait for pending cold boot completion if indicated by the nv downloader */
if ( expect_cbc ) {
ret = wait_for_completion_timeout ( & wcnss - > cbc , WCNSS_REQUEST_TIMEOUT ) ;
if ( ! ret )
dev_err ( wcnss - > dev , " expected cold boot completion \n " ) ;
}
of_platform_populate ( wcnss - > dev - > of_node , NULL , NULL , wcnss - > dev ) ;
2015-09-21 20:52:55 +03:00
}
2017-03-28 08:26:33 +03:00
static int wcnss_ctrl_probe ( struct rpmsg_device * rpdev )
2015-09-21 20:52:55 +03:00
{
struct wcnss_ctrl * wcnss ;
2017-03-28 08:26:33 +03:00
wcnss = devm_kzalloc ( & rpdev - > dev , sizeof ( * wcnss ) , GFP_KERNEL ) ;
2015-09-21 20:52:55 +03:00
if ( ! wcnss )
return - ENOMEM ;
2017-03-28 08:26:33 +03:00
wcnss - > dev = & rpdev - > dev ;
wcnss - > channel = rpdev - > ept ;
2015-09-21 20:52:55 +03:00
init_completion ( & wcnss - > ack ) ;
2016-06-07 02:58:20 +03:00
init_completion ( & wcnss - > cbc ) ;
INIT_WORK ( & wcnss - > probe_work , wcnss_async_probe ) ;
2015-09-21 20:52:55 +03:00
2017-03-28 08:26:33 +03:00
dev_set_drvdata ( & rpdev - > dev , wcnss ) ;
2016-06-07 02:58:20 +03:00
schedule_work ( & wcnss - > probe_work ) ;
return 0 ;
}
2017-03-28 08:26:33 +03:00
static void wcnss_ctrl_remove ( struct rpmsg_device * rpdev )
2016-06-07 02:58:20 +03:00
{
2017-03-28 08:26:33 +03:00
struct wcnss_ctrl * wcnss = dev_get_drvdata ( & rpdev - > dev ) ;
2015-09-21 20:52:55 +03:00
2016-06-07 02:58:20 +03:00
cancel_work_sync ( & wcnss - > probe_work ) ;
2017-03-28 08:26:33 +03:00
of_platform_depopulate ( & rpdev - > dev ) ;
2015-09-21 20:52:55 +03:00
}
2016-06-07 02:58:20 +03:00
static const struct of_device_id wcnss_ctrl_of_match [ ] = {
{ . compatible = " qcom,wcnss " , } ,
2015-09-21 20:52:55 +03:00
{ }
} ;
2017-07-02 16:23:36 +03:00
MODULE_DEVICE_TABLE ( of , wcnss_ctrl_of_match ) ;
2015-09-21 20:52:55 +03:00
2017-03-28 08:26:33 +03:00
static struct rpmsg_driver wcnss_ctrl_driver = {
2015-09-21 20:52:55 +03:00
. probe = wcnss_ctrl_probe ,
2016-06-07 02:58:20 +03:00
. remove = wcnss_ctrl_remove ,
2015-09-21 20:52:55 +03:00
. callback = wcnss_ctrl_smd_callback ,
2017-03-28 08:26:33 +03:00
. drv = {
2015-09-21 20:52:55 +03:00
. name = " qcom_wcnss_ctrl " ,
. owner = THIS_MODULE ,
2016-06-07 02:58:20 +03:00
. of_match_table = wcnss_ctrl_of_match ,
2015-09-21 20:52:55 +03:00
} ,
} ;
2017-03-28 08:26:33 +03:00
module_rpmsg_driver ( wcnss_ctrl_driver ) ;
2015-09-21 20:52:55 +03:00
MODULE_DESCRIPTION ( " Qualcomm WCNSS control driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;