GNU Linux-libre 4.19.211-gnu1
[releases.git] / drivers / soc / qcom / smd-rpm.c
1 /*
2  * Copyright (c) 2015, Sony Mobile Communications AB.
3  * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
4  *
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.
8  *
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.
13  */
14
15 #include <linux/module.h>
16 #include <linux/platform_device.h>
17 #include <linux/of_platform.h>
18 #include <linux/io.h>
19 #include <linux/interrupt.h>
20 #include <linux/slab.h>
21
22 #include <linux/rpmsg.h>
23 #include <linux/soc/qcom/smd-rpm.h>
24
25 #define RPM_REQUEST_TIMEOUT     (5 * HZ)
26
27 /**
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
33  */
34 struct qcom_smd_rpm {
35         struct rpmsg_endpoint *rpm_channel;
36         struct device *dev;
37
38         struct completion ack;
39         struct mutex lock;
40         int ack_status;
41 };
42
43 /**
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
47  */
48 struct qcom_rpm_header {
49         __le32 service_type;
50         __le32 length;
51 };
52
53 /**
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
58  * @id:         resource id
59  * @data_len:   length of the payload following this header
60  */
61 struct qcom_rpm_request {
62         __le32 msg_id;
63         __le32 flags;
64         __le32 type;
65         __le32 id;
66         __le32 data_len;
67 };
68
69 /**
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
73  * @msg_id:     message id
74  * @message:    textual message from the rpm
75  *
76  * Multiple of these messages can be stacked in an rpm message.
77  */
78 struct qcom_rpm_message {
79         __le32 msg_type;
80         __le32 length;
81         union {
82                 __le32 msg_id;
83                 u8 message[0];
84         };
85 };
86
87 #define RPM_SERVICE_TYPE_REQUEST        0x00716572 /* "req\0" */
88
89 #define RPM_MSG_TYPE_ERR                0x00727265 /* "err\0" */
90 #define RPM_MSG_TYPE_MSG_ID             0x2367736d /* "msg#" */
91
92 /**
93  * qcom_rpm_smd_write - write @buf to @type:@id
94  * @rpm:        rpm handle
95  * @type:       resource type
96  * @id:         resource identifier
97  * @buf:        the data to be written
98  * @count:      number of bytes in @buf
99  */
100 int qcom_rpm_smd_write(struct qcom_smd_rpm *rpm,
101                        int state,
102                        u32 type, u32 id,
103                        void *buf,
104                        size_t count)
105 {
106         static unsigned msg_id = 1;
107         int left;
108         int ret;
109         struct {
110                 struct qcom_rpm_header hdr;
111                 struct qcom_rpm_request req;
112                 u8 payload[];
113         } *pkt;
114         size_t size = sizeof(*pkt) + count;
115
116         /* SMD packets to the RPM may not exceed 256 bytes */
117         if (WARN_ON(size >= 256))
118                 return -EINVAL;
119
120         pkt = kmalloc(size, GFP_KERNEL);
121         if (!pkt)
122                 return -ENOMEM;
123
124         mutex_lock(&rpm->lock);
125
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);
128
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);
135
136         ret = rpmsg_send(rpm->rpm_channel, pkt, size);
137         if (ret)
138                 goto out;
139
140         left = wait_for_completion_timeout(&rpm->ack, RPM_REQUEST_TIMEOUT);
141         if (!left)
142                 ret = -ETIMEDOUT;
143         else
144                 ret = rpm->ack_status;
145
146 out:
147         kfree(pkt);
148         mutex_unlock(&rpm->lock);
149         return ret;
150 }
151 EXPORT_SYMBOL(qcom_rpm_smd_write);
152
153 static int qcom_smd_rpm_callback(struct rpmsg_device *rpdev,
154                                  void *data,
155                                  int count,
156                                  void *priv,
157                                  u32 addr)
158 {
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;
165         char msgbuf[32];
166         int status = 0;
167         u32 len, msg_length;
168
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");
172                 return 0;
173         }
174
175         while (buf < end) {
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:
180                         break;
181                 case RPM_MSG_TYPE_ERR:
182                         len = min_t(u32, ALIGN(msg_length, 4), sizeof(msgbuf));
183                         memcpy_fromio(msgbuf, msg->message, len);
184                         msgbuf[len - 1] = 0;
185
186                         if (!strcmp(msgbuf, "resource does not exist"))
187                                 status = -ENXIO;
188                         else
189                                 status = -EINVAL;
190                         break;
191                 }
192
193                 buf = PTR_ALIGN(buf + 2 * sizeof(u32) + msg_length, 4);
194         }
195
196         rpm->ack_status = status;
197         complete(&rpm->ack);
198         return 0;
199 }
200
201 static int qcom_smd_rpm_probe(struct rpmsg_device *rpdev)
202 {
203         struct qcom_smd_rpm *rpm;
204
205         rpm = devm_kzalloc(&rpdev->dev, sizeof(*rpm), GFP_KERNEL);
206         if (!rpm)
207                 return -ENOMEM;
208
209         mutex_init(&rpm->lock);
210         init_completion(&rpm->ack);
211
212         rpm->dev = &rpdev->dev;
213         rpm->rpm_channel = rpdev->ept;
214         dev_set_drvdata(&rpdev->dev, rpm);
215
216         return of_platform_populate(rpdev->dev.of_node, NULL, NULL, &rpdev->dev);
217 }
218
219 static void qcom_smd_rpm_remove(struct rpmsg_device *rpdev)
220 {
221         of_platform_depopulate(&rpdev->dev);
222 }
223
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         {}
231 };
232 MODULE_DEVICE_TABLE(of, qcom_smd_rpm_of_match);
233
234 static struct rpmsg_driver qcom_smd_rpm_driver = {
235         .probe = qcom_smd_rpm_probe,
236         .remove = qcom_smd_rpm_remove,
237         .callback = qcom_smd_rpm_callback,
238         .drv  = {
239                 .name  = "qcom_smd_rpm",
240                 .of_match_table = qcom_smd_rpm_of_match,
241         },
242 };
243
244 static int __init qcom_smd_rpm_init(void)
245 {
246         return register_rpmsg_driver(&qcom_smd_rpm_driver);
247 }
248 arch_initcall(qcom_smd_rpm_init);
249
250 static void __exit qcom_smd_rpm_exit(void)
251 {
252         unregister_rpmsg_driver(&qcom_smd_rpm_driver);
253 }
254 module_exit(qcom_smd_rpm_exit);
255
256 MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>");
257 MODULE_DESCRIPTION("Qualcomm SMD backed RPM driver");
258 MODULE_LICENSE("GPL v2");