2 * Copyright (c) 2015, Sony Mobile Communications AB.
3 * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2 and
7 * only version 2 as published by the Free Software Foundation.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
15 #include <linux/module.h>
16 #include <linux/platform_device.h>
17 #include <linux/of_platform.h>
19 #include <linux/interrupt.h>
20 #include <linux/slab.h>
22 #include <linux/rpmsg.h>
23 #include <linux/soc/qcom/smd-rpm.h>
25 #define RPM_REQUEST_TIMEOUT (5 * HZ)
28 * struct qcom_smd_rpm - state of the rpm device driver
29 * @rpm_channel: reference to the smd channel
30 * @ack: completion for acks
31 * @lock: mutual exclusion around the send/complete pair
32 * @ack_status: result of the rpm request
35 struct rpmsg_endpoint
*rpm_channel
;
38 struct completion ack
;
44 * struct qcom_rpm_header - header for all rpm requests and responses
45 * @service_type: identifier of the service
46 * @length: length of the payload
48 struct qcom_rpm_header
{
54 * struct qcom_rpm_request - request message to the rpm
55 * @msg_id: identifier of the outgoing message
56 * @flags: active/sleep state flags
57 * @type: resource type
59 * @data_len: length of the payload following this header
61 struct qcom_rpm_request
{
70 * struct qcom_rpm_message - response message from the rpm
71 * @msg_type: indicator of the type of message
72 * @length: the size of this message, including the message header
74 * @message: textual message from the rpm
76 * Multiple of these messages can be stacked in an rpm message.
78 struct qcom_rpm_message
{
87 #define RPM_SERVICE_TYPE_REQUEST 0x00716572 /* "req\0" */
89 #define RPM_MSG_TYPE_ERR 0x00727265 /* "err\0" */
90 #define RPM_MSG_TYPE_MSG_ID 0x2367736d /* "msg#" */
93 * qcom_rpm_smd_write - write @buf to @type:@id
95 * @type: resource type
96 * @id: resource identifier
97 * @buf: the data to be written
98 * @count: number of bytes in @buf
100 int qcom_rpm_smd_write(struct qcom_smd_rpm
*rpm
,
106 static unsigned msg_id
= 1;
110 struct qcom_rpm_header hdr
;
111 struct qcom_rpm_request req
;
114 size_t size
= sizeof(*pkt
) + count
;
116 /* SMD packets to the RPM may not exceed 256 bytes */
117 if (WARN_ON(size
>= 256))
120 pkt
= kmalloc(size
, GFP_KERNEL
);
124 mutex_lock(&rpm
->lock
);
126 pkt
->hdr
.service_type
= cpu_to_le32(RPM_SERVICE_TYPE_REQUEST
);
127 pkt
->hdr
.length
= cpu_to_le32(sizeof(struct qcom_rpm_request
) + count
);
129 pkt
->req
.msg_id
= cpu_to_le32(msg_id
++);
130 pkt
->req
.flags
= cpu_to_le32(state
);
131 pkt
->req
.type
= cpu_to_le32(type
);
132 pkt
->req
.id
= cpu_to_le32(id
);
133 pkt
->req
.data_len
= cpu_to_le32(count
);
134 memcpy(pkt
->payload
, buf
, count
);
136 ret
= rpmsg_send(rpm
->rpm_channel
, pkt
, size
);
140 left
= wait_for_completion_timeout(&rpm
->ack
, RPM_REQUEST_TIMEOUT
);
144 ret
= rpm
->ack_status
;
148 mutex_unlock(&rpm
->lock
);
151 EXPORT_SYMBOL(qcom_rpm_smd_write
);
153 static int qcom_smd_rpm_callback(struct rpmsg_device
*rpdev
,
159 const struct qcom_rpm_header
*hdr
= data
;
160 size_t hdr_length
= le32_to_cpu(hdr
->length
);
161 const struct qcom_rpm_message
*msg
;
162 struct qcom_smd_rpm
*rpm
= dev_get_drvdata(&rpdev
->dev
);
163 const u8
*buf
= data
+ sizeof(struct qcom_rpm_header
);
164 const u8
*end
= buf
+ hdr_length
;
169 if (le32_to_cpu(hdr
->service_type
) != RPM_SERVICE_TYPE_REQUEST
||
170 hdr_length
< sizeof(struct qcom_rpm_message
)) {
171 dev_err(rpm
->dev
, "invalid request\n");
176 msg
= (struct qcom_rpm_message
*)buf
;
177 msg_length
= le32_to_cpu(msg
->length
);
178 switch (le32_to_cpu(msg
->msg_type
)) {
179 case RPM_MSG_TYPE_MSG_ID
:
181 case RPM_MSG_TYPE_ERR
:
182 len
= min_t(u32
, ALIGN(msg_length
, 4), sizeof(msgbuf
));
183 memcpy_fromio(msgbuf
, msg
->message
, len
);
186 if (!strcmp(msgbuf
, "resource does not exist"))
193 buf
= PTR_ALIGN(buf
+ 2 * sizeof(u32
) + msg_length
, 4);
196 rpm
->ack_status
= status
;
201 static int qcom_smd_rpm_probe(struct rpmsg_device
*rpdev
)
203 struct qcom_smd_rpm
*rpm
;
205 rpm
= devm_kzalloc(&rpdev
->dev
, sizeof(*rpm
), GFP_KERNEL
);
209 mutex_init(&rpm
->lock
);
210 init_completion(&rpm
->ack
);
212 rpm
->dev
= &rpdev
->dev
;
213 rpm
->rpm_channel
= rpdev
->ept
;
214 dev_set_drvdata(&rpdev
->dev
, rpm
);
216 return of_platform_populate(rpdev
->dev
.of_node
, NULL
, NULL
, &rpdev
->dev
);
219 static void qcom_smd_rpm_remove(struct rpmsg_device
*rpdev
)
221 of_platform_depopulate(&rpdev
->dev
);
224 static const struct of_device_id qcom_smd_rpm_of_match
[] = {
225 { .compatible
= "qcom,rpm-apq8084" },
226 { .compatible
= "qcom,rpm-msm8916" },
227 { .compatible
= "qcom,rpm-msm8974" },
228 { .compatible
= "qcom,rpm-msm8996" },
229 { .compatible
= "qcom,rpm-msm8998" },
230 { .compatible
= "qcom,rpm-sdm660" },
231 { .compatible
= "qcom,rpm-qcs404" },
234 MODULE_DEVICE_TABLE(of
, qcom_smd_rpm_of_match
);
236 static struct rpmsg_driver qcom_smd_rpm_driver
= {
237 .probe
= qcom_smd_rpm_probe
,
238 .remove
= qcom_smd_rpm_remove
,
239 .callback
= qcom_smd_rpm_callback
,
241 .name
= "qcom_smd_rpm",
242 .of_match_table
= qcom_smd_rpm_of_match
,
246 static int __init
qcom_smd_rpm_init(void)
248 return register_rpmsg_driver(&qcom_smd_rpm_driver
);
250 arch_initcall(qcom_smd_rpm_init
);
252 static void __exit
qcom_smd_rpm_exit(void)
254 unregister_rpmsg_driver(&qcom_smd_rpm_driver
);
256 module_exit(qcom_smd_rpm_exit
);
258 MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>");
259 MODULE_DESCRIPTION("Qualcomm SMD backed RPM driver");
260 MODULE_LICENSE("GPL v2");