These changes are the raw update to linux-4.4.6-rt14. Kernel sources
[kvmfornfv.git] / kernel / 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/soc/qcom/smd.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 qcom_smd_channel *rpm_channel;
36
37         struct completion ack;
38         struct mutex lock;
39         int ack_status;
40 };
41
42 /**
43  * struct qcom_rpm_header - header for all rpm requests and responses
44  * @service_type:       identifier of the service
45  * @length:             length of the payload
46  */
47 struct qcom_rpm_header {
48         __le32 service_type;
49         __le32 length;
50 };
51
52 /**
53  * struct qcom_rpm_request - request message to the rpm
54  * @msg_id:     identifier of the outgoing message
55  * @flags:      active/sleep state flags
56  * @type:       resource type
57  * @id:         resource id
58  * @data_len:   length of the payload following this header
59  */
60 struct qcom_rpm_request {
61         __le32 msg_id;
62         __le32 flags;
63         __le32 type;
64         __le32 id;
65         __le32 data_len;
66 };
67
68 /**
69  * struct qcom_rpm_message - response message from the rpm
70  * @msg_type:   indicator of the type of message
71  * @length:     the size of this message, including the message header
72  * @msg_id:     message id
73  * @message:    textual message from the rpm
74  *
75  * Multiple of these messages can be stacked in an rpm message.
76  */
77 struct qcom_rpm_message {
78         __le32 msg_type;
79         __le32 length;
80         union {
81                 __le32 msg_id;
82                 u8 message[0];
83         };
84 };
85
86 #define RPM_SERVICE_TYPE_REQUEST        0x00716572 /* "req\0" */
87
88 #define RPM_MSG_TYPE_ERR                0x00727265 /* "err\0" */
89 #define RPM_MSG_TYPE_MSG_ID             0x2367736d /* "msg#" */
90
91 /**
92  * qcom_rpm_smd_write - write @buf to @type:@id
93  * @rpm:        rpm handle
94  * @type:       resource type
95  * @id:         resource identifier
96  * @buf:        the data to be written
97  * @count:      number of bytes in @buf
98  */
99 int qcom_rpm_smd_write(struct qcom_smd_rpm *rpm,
100                        int state,
101                        u32 type, u32 id,
102                        void *buf,
103                        size_t count)
104 {
105         static unsigned msg_id = 1;
106         int left;
107         int ret;
108         struct {
109                 struct qcom_rpm_header hdr;
110                 struct qcom_rpm_request req;
111                 u8 payload[];
112         } *pkt;
113         size_t size = sizeof(*pkt) + count;
114
115         /* SMD packets to the RPM may not exceed 256 bytes */
116         if (WARN_ON(size >= 256))
117                 return -EINVAL;
118
119         pkt = kmalloc(size, GFP_KERNEL);
120         if (!pkt)
121                 return -ENOMEM;
122
123         mutex_lock(&rpm->lock);
124
125         pkt->hdr.service_type = cpu_to_le32(RPM_SERVICE_TYPE_REQUEST);
126         pkt->hdr.length = cpu_to_le32(sizeof(struct qcom_rpm_request) + count);
127
128         pkt->req.msg_id = cpu_to_le32(msg_id++);
129         pkt->req.flags = cpu_to_le32(state);
130         pkt->req.type = cpu_to_le32(type);
131         pkt->req.id = cpu_to_le32(id);
132         pkt->req.data_len = cpu_to_le32(count);
133         memcpy(pkt->payload, buf, count);
134
135         ret = qcom_smd_send(rpm->rpm_channel, pkt, size);
136         if (ret)
137                 goto out;
138
139         left = wait_for_completion_timeout(&rpm->ack, RPM_REQUEST_TIMEOUT);
140         if (!left)
141                 ret = -ETIMEDOUT;
142         else
143                 ret = rpm->ack_status;
144
145 out:
146         kfree(pkt);
147         mutex_unlock(&rpm->lock);
148         return ret;
149 }
150 EXPORT_SYMBOL(qcom_rpm_smd_write);
151
152 static int qcom_smd_rpm_callback(struct qcom_smd_device *qsdev,
153                                  const void *data,
154                                  size_t count)
155 {
156         const struct qcom_rpm_header *hdr = data;
157         size_t hdr_length = le32_to_cpu(hdr->length);
158         const struct qcom_rpm_message *msg;
159         struct qcom_smd_rpm *rpm = dev_get_drvdata(&qsdev->dev);
160         const u8 *buf = data + sizeof(struct qcom_rpm_header);
161         const u8 *end = buf + hdr_length;
162         char msgbuf[32];
163         int status = 0;
164         u32 len, msg_length;
165
166         if (le32_to_cpu(hdr->service_type) != RPM_SERVICE_TYPE_REQUEST ||
167             hdr_length < sizeof(struct qcom_rpm_message)) {
168                 dev_err(&qsdev->dev, "invalid request\n");
169                 return 0;
170         }
171
172         while (buf < end) {
173                 msg = (struct qcom_rpm_message *)buf;
174                 msg_length = le32_to_cpu(msg->length);
175                 switch (le32_to_cpu(msg->msg_type)) {
176                 case RPM_MSG_TYPE_MSG_ID:
177                         break;
178                 case RPM_MSG_TYPE_ERR:
179                         len = min_t(u32, ALIGN(msg_length, 4), sizeof(msgbuf));
180                         memcpy_fromio(msgbuf, msg->message, len);
181                         msgbuf[len - 1] = 0;
182
183                         if (!strcmp(msgbuf, "resource does not exist"))
184                                 status = -ENXIO;
185                         else
186                                 status = -EINVAL;
187                         break;
188                 }
189
190                 buf = PTR_ALIGN(buf + 2 * sizeof(u32) + msg_length, 4);
191         }
192
193         rpm->ack_status = status;
194         complete(&rpm->ack);
195         return 0;
196 }
197
198 static int qcom_smd_rpm_probe(struct qcom_smd_device *sdev)
199 {
200         struct qcom_smd_rpm *rpm;
201
202         rpm = devm_kzalloc(&sdev->dev, sizeof(*rpm), GFP_KERNEL);
203         if (!rpm)
204                 return -ENOMEM;
205
206         mutex_init(&rpm->lock);
207         init_completion(&rpm->ack);
208
209         rpm->rpm_channel = sdev->channel;
210
211         dev_set_drvdata(&sdev->dev, rpm);
212
213         return of_platform_populate(sdev->dev.of_node, NULL, NULL, &sdev->dev);
214 }
215
216 static void qcom_smd_rpm_remove(struct qcom_smd_device *sdev)
217 {
218         of_platform_depopulate(&sdev->dev);
219 }
220
221 static const struct of_device_id qcom_smd_rpm_of_match[] = {
222         { .compatible = "qcom,rpm-msm8974" },
223         {}
224 };
225 MODULE_DEVICE_TABLE(of, qcom_smd_rpm_of_match);
226
227 static struct qcom_smd_driver qcom_smd_rpm_driver = {
228         .probe = qcom_smd_rpm_probe,
229         .remove = qcom_smd_rpm_remove,
230         .callback = qcom_smd_rpm_callback,
231         .driver  = {
232                 .name  = "qcom_smd_rpm",
233                 .owner = THIS_MODULE,
234                 .of_match_table = qcom_smd_rpm_of_match,
235         },
236 };
237
238 static int __init qcom_smd_rpm_init(void)
239 {
240         return qcom_smd_driver_register(&qcom_smd_rpm_driver);
241 }
242 arch_initcall(qcom_smd_rpm_init);
243
244 static void __exit qcom_smd_rpm_exit(void)
245 {
246         qcom_smd_driver_unregister(&qcom_smd_rpm_driver);
247 }
248 module_exit(qcom_smd_rpm_exit);
249
250 MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>");
251 MODULE_DESCRIPTION("Qualcomm SMD backed RPM driver");
252 MODULE_LICENSE("GPL v2");