2019-05-29 17:17:58 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2015-07-28 06:20:32 +03:00
/*
* Copyright ( c ) 2015 , Sony Mobile Communications AB .
* Copyright ( c ) 2012 - 2013 , The Linux Foundation . All rights reserved .
*/
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/of_platform.h>
# include <linux/io.h>
# include <linux/interrupt.h>
2015-09-03 01:46:48 +03:00
# include <linux/slab.h>
2015-07-28 06:20:32 +03:00
2017-03-28 08:26:33 +03:00
# include <linux/rpmsg.h>
2015-07-28 06:20:32 +03:00
# include <linux/soc/qcom/smd-rpm.h>
# define RPM_REQUEST_TIMEOUT (5 * HZ)
/**
* struct qcom_smd_rpm - state of the rpm device driver
* @ rpm_channel : reference to the smd channel
2019-07-23 17:23:36 +03:00
* @ icc : interconnect proxy device
2020-07-29 10:44:15 +03:00
* @ dev : rpm device
2015-07-28 06:20:32 +03:00
* @ ack : completion for acks
* @ lock : mutual exclusion around the send / complete pair
* @ ack_status : result of the rpm request
*/
struct qcom_smd_rpm {
2017-03-28 08:26:33 +03:00
struct rpmsg_endpoint * rpm_channel ;
2019-07-23 17:23:36 +03:00
struct platform_device * icc ;
2016-03-29 07:35:22 +03:00
struct device * dev ;
2015-07-28 06:20:32 +03:00
struct completion ack ;
struct mutex lock ;
int ack_status ;
} ;
/**
* struct qcom_rpm_header - header for all rpm requests and responses
* @ service_type : identifier of the service
* @ length : length of the payload
*/
struct qcom_rpm_header {
2015-09-03 01:46:50 +03:00
__le32 service_type ;
__le32 length ;
2015-07-28 06:20:32 +03:00
} ;
/**
* struct qcom_rpm_request - request message to the rpm
* @ msg_id : identifier of the outgoing message
* @ flags : active / sleep state flags
* @ type : resource type
* @ id : resource id
* @ data_len : length of the payload following this header
*/
struct qcom_rpm_request {
2015-09-03 01:46:50 +03:00
__le32 msg_id ;
__le32 flags ;
__le32 type ;
__le32 id ;
__le32 data_len ;
2015-07-28 06:20:32 +03:00
} ;
/**
* struct qcom_rpm_message - response message from the rpm
* @ msg_type : indicator of the type of message
* @ length : the size of this message , including the message header
* @ msg_id : message id
* @ message : textual message from the rpm
*
* Multiple of these messages can be stacked in an rpm message .
*/
struct qcom_rpm_message {
2015-09-03 01:46:50 +03:00
__le32 msg_type ;
__le32 length ;
2015-07-28 06:20:32 +03:00
union {
2015-09-03 01:46:50 +03:00
__le32 msg_id ;
2015-07-28 06:20:32 +03:00
u8 message [ 0 ] ;
} ;
} ;
# define RPM_SERVICE_TYPE_REQUEST 0x00716572 /* "req\0" */
# define RPM_MSG_TYPE_ERR 0x00727265 /* "err\0" */
# define RPM_MSG_TYPE_MSG_ID 0x2367736d /* "msg#" */
/**
* qcom_rpm_smd_write - write @ buf to @ type : @ id
* @ rpm : rpm handle
2020-07-29 10:44:15 +03:00
* @ state : active / sleep state flags
2015-07-28 06:20:32 +03:00
* @ type : resource type
* @ id : resource identifier
* @ buf : the data to be written
* @ count : number of bytes in @ buf
*/
int qcom_rpm_smd_write ( struct qcom_smd_rpm * rpm ,
int state ,
u32 type , u32 id ,
void * buf ,
size_t count )
{
static unsigned msg_id = 1 ;
int left ;
int ret ;
struct {
struct qcom_rpm_header hdr ;
struct qcom_rpm_request req ;
2015-09-03 01:46:48 +03:00
u8 payload [ ] ;
} * pkt ;
size_t size = sizeof ( * pkt ) + count ;
2015-07-28 06:20:32 +03:00
/* SMD packets to the RPM may not exceed 256 bytes */
2015-09-03 01:46:48 +03:00
if ( WARN_ON ( size > = 256 ) )
2015-07-28 06:20:32 +03:00
return - EINVAL ;
2015-09-03 01:46:48 +03:00
pkt = kmalloc ( size , GFP_KERNEL ) ;
if ( ! pkt )
return - ENOMEM ;
2015-07-28 06:20:32 +03:00
mutex_lock ( & rpm - > lock ) ;
2015-09-03 01:46:50 +03:00
pkt - > hdr . service_type = cpu_to_le32 ( RPM_SERVICE_TYPE_REQUEST ) ;
pkt - > hdr . length = cpu_to_le32 ( sizeof ( struct qcom_rpm_request ) + count ) ;
2015-07-28 06:20:32 +03:00
2015-09-03 01:46:50 +03:00
pkt - > req . msg_id = cpu_to_le32 ( msg_id + + ) ;
2015-10-13 23:57:43 +03:00
pkt - > req . flags = cpu_to_le32 ( state ) ;
2015-09-03 01:46:50 +03:00
pkt - > req . type = cpu_to_le32 ( type ) ;
pkt - > req . id = cpu_to_le32 ( id ) ;
pkt - > req . data_len = cpu_to_le32 ( count ) ;
2015-09-03 01:46:48 +03:00
memcpy ( pkt - > payload , buf , count ) ;
2015-07-28 06:20:32 +03:00
2017-03-28 08:26:33 +03:00
ret = rpmsg_send ( rpm - > rpm_channel , pkt , size ) ;
2015-07-28 06:20:32 +03:00
if ( ret )
goto out ;
left = wait_for_completion_timeout ( & rpm - > ack , RPM_REQUEST_TIMEOUT ) ;
if ( ! left )
ret = - ETIMEDOUT ;
else
ret = rpm - > ack_status ;
out :
2015-09-03 01:46:48 +03:00
kfree ( pkt ) ;
2015-07-28 06:20:32 +03:00
mutex_unlock ( & rpm - > lock ) ;
return ret ;
}
EXPORT_SYMBOL ( qcom_rpm_smd_write ) ;
2017-03-28 08:26:33 +03:00
static int qcom_smd_rpm_callback ( struct rpmsg_device * rpdev ,
void * data ,
int count ,
void * priv ,
u32 addr )
2015-07-28 06:20:32 +03:00
{
const struct qcom_rpm_header * hdr = data ;
2015-09-03 01:46:50 +03:00
size_t hdr_length = le32_to_cpu ( hdr - > length ) ;
2015-07-28 06:20:32 +03:00
const struct qcom_rpm_message * msg ;
2017-03-28 08:26:33 +03:00
struct qcom_smd_rpm * rpm = dev_get_drvdata ( & rpdev - > dev ) ;
2015-07-28 06:20:32 +03:00
const u8 * buf = data + sizeof ( struct qcom_rpm_header ) ;
2015-09-03 01:46:50 +03:00
const u8 * end = buf + hdr_length ;
2015-07-28 06:20:32 +03:00
char msgbuf [ 32 ] ;
int status = 0 ;
2015-09-03 01:46:50 +03:00
u32 len , msg_length ;
2015-07-28 06:20:32 +03:00
2015-09-03 01:46:50 +03:00
if ( le32_to_cpu ( hdr - > service_type ) ! = RPM_SERVICE_TYPE_REQUEST | |
hdr_length < sizeof ( struct qcom_rpm_message ) ) {
2016-03-29 07:35:22 +03:00
dev_err ( rpm - > dev , " invalid request \n " ) ;
2015-07-28 06:20:32 +03:00
return 0 ;
}
while ( buf < end ) {
msg = ( struct qcom_rpm_message * ) buf ;
2015-09-03 01:46:50 +03:00
msg_length = le32_to_cpu ( msg - > length ) ;
switch ( le32_to_cpu ( msg - > msg_type ) ) {
2015-07-28 06:20:32 +03:00
case RPM_MSG_TYPE_MSG_ID :
break ;
case RPM_MSG_TYPE_ERR :
2015-09-03 01:46:50 +03:00
len = min_t ( u32 , ALIGN ( msg_length , 4 ) , sizeof ( msgbuf ) ) ;
2015-07-28 06:20:32 +03:00
memcpy_fromio ( msgbuf , msg - > message , len ) ;
msgbuf [ len - 1 ] = 0 ;
if ( ! strcmp ( msgbuf , " resource does not exist " ) )
status = - ENXIO ;
else
status = - EINVAL ;
break ;
}
2015-09-03 01:46:50 +03:00
buf = PTR_ALIGN ( buf + 2 * sizeof ( u32 ) + msg_length , 4 ) ;
2015-07-28 06:20:32 +03:00
}
rpm - > ack_status = status ;
complete ( & rpm - > ack ) ;
return 0 ;
}
2017-03-28 08:26:33 +03:00
static int qcom_smd_rpm_probe ( struct rpmsg_device * rpdev )
2015-07-28 06:20:32 +03:00
{
struct qcom_smd_rpm * rpm ;
2019-07-23 17:23:36 +03:00
int ret ;
2015-07-28 06:20:32 +03:00
2017-03-28 08:26:33 +03:00
rpm = devm_kzalloc ( & rpdev - > dev , sizeof ( * rpm ) , GFP_KERNEL ) ;
2015-07-28 06:20:32 +03:00
if ( ! rpm )
return - ENOMEM ;
mutex_init ( & rpm - > lock ) ;
init_completion ( & rpm - > ack ) ;
2017-03-28 08:26:33 +03:00
rpm - > dev = & rpdev - > dev ;
rpm - > rpm_channel = rpdev - > ept ;
dev_set_drvdata ( & rpdev - > dev , rpm ) ;
2015-07-28 06:20:32 +03:00
2019-07-23 17:23:36 +03:00
rpm - > icc = platform_device_register_data ( & rpdev - > dev , " icc_smd_rpm " , - 1 ,
NULL , 0 ) ;
if ( IS_ERR ( rpm - > icc ) )
return PTR_ERR ( rpm - > icc ) ;
ret = of_platform_populate ( rpdev - > dev . of_node , NULL , NULL , & rpdev - > dev ) ;
if ( ret )
platform_device_unregister ( rpm - > icc ) ;
return ret ;
2015-07-28 06:20:32 +03:00
}
2017-03-28 08:26:33 +03:00
static void qcom_smd_rpm_remove ( struct rpmsg_device * rpdev )
2015-07-28 06:20:32 +03:00
{
2019-07-23 17:23:36 +03:00
struct qcom_smd_rpm * rpm = dev_get_drvdata ( & rpdev - > dev ) ;
platform_device_unregister ( rpm - > icc ) ;
2017-03-28 08:26:33 +03:00
of_platform_depopulate ( & rpdev - > dev ) ;
2015-07-28 06:20:32 +03:00
}
static const struct of_device_id qcom_smd_rpm_of_match [ ] = {
2015-09-24 22:18:51 +03:00
{ . compatible = " qcom,rpm-apq8084 " } ,
2020-07-20 09:12:21 +03:00
{ . compatible = " qcom,rpm-ipq6018 " } ,
2015-09-24 22:18:51 +03:00
{ . compatible = " qcom,rpm-msm8916 " } ,
2020-06-13 10:27:44 +03:00
{ . compatible = " qcom,rpm-msm8936 " } ,
2015-07-28 06:20:32 +03:00
{ . compatible = " qcom,rpm-msm8974 " } ,
2019-09-21 13:12:05 +03:00
{ . compatible = " qcom,rpm-msm8976 " } ,
2020-06-02 23:04:07 +03:00
{ . compatible = " qcom,rpm-msm8994 " } ,
2017-03-28 08:26:35 +03:00
{ . compatible = " qcom,rpm-msm8996 " } ,
2018-03-27 18:55:15 +03:00
{ . compatible = " qcom,rpm-msm8998 " } ,
2018-08-11 19:24:17 +03:00
{ . compatible = " qcom,rpm-sdm660 " } ,
2018-09-20 04:45:40 +03:00
{ . compatible = " qcom,rpm-qcs404 " } ,
2015-07-28 06:20:32 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , qcom_smd_rpm_of_match ) ;
2017-03-28 08:26:33 +03:00
static struct rpmsg_driver qcom_smd_rpm_driver = {
2015-07-28 06:20:32 +03:00
. probe = qcom_smd_rpm_probe ,
. remove = qcom_smd_rpm_remove ,
. callback = qcom_smd_rpm_callback ,
2017-03-28 08:26:33 +03:00
. drv = {
2015-07-28 06:20:32 +03:00
. name = " qcom_smd_rpm " ,
. of_match_table = qcom_smd_rpm_of_match ,
} ,
} ;
static int __init qcom_smd_rpm_init ( void )
{
2017-03-28 08:26:33 +03:00
return register_rpmsg_driver ( & qcom_smd_rpm_driver ) ;
2015-07-28 06:20:32 +03:00
}
arch_initcall ( qcom_smd_rpm_init ) ;
static void __exit qcom_smd_rpm_exit ( void )
{
2017-03-28 08:26:33 +03:00
unregister_rpmsg_driver ( & qcom_smd_rpm_driver ) ;
2015-07-28 06:20:32 +03:00
}
module_exit ( qcom_smd_rpm_exit ) ;
MODULE_AUTHOR ( " Bjorn Andersson <bjorn.andersson@sonymobile.com> " ) ;
MODULE_DESCRIPTION ( " Qualcomm SMD backed RPM driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;