Linux-libre 4.9.46-gnu
[librecmc/linux-libre.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/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         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 = qcom_smd_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 qcom_smd_channel *channel,
154                                  const void *data,
155                                  size_t count)
156 {
157         const struct qcom_rpm_header *hdr = data;
158         size_t hdr_length = le32_to_cpu(hdr->length);
159         const struct qcom_rpm_message *msg;
160         struct qcom_smd_rpm *rpm = qcom_smd_get_drvdata(channel);
161         const u8 *buf = data + sizeof(struct qcom_rpm_header);
162         const u8 *end = buf + hdr_length;
163         char msgbuf[32];
164         int status = 0;
165         u32 len, msg_length;
166
167         if (le32_to_cpu(hdr->service_type) != RPM_SERVICE_TYPE_REQUEST ||
168             hdr_length < sizeof(struct qcom_rpm_message)) {
169                 dev_err(rpm->dev, "invalid request\n");
170                 return 0;
171         }
172
173         while (buf < end) {
174                 msg = (struct qcom_rpm_message *)buf;
175                 msg_length = le32_to_cpu(msg->length);
176                 switch (le32_to_cpu(msg->msg_type)) {
177                 case RPM_MSG_TYPE_MSG_ID:
178                         break;
179                 case RPM_MSG_TYPE_ERR:
180                         len = min_t(u32, ALIGN(msg_length, 4), sizeof(msgbuf));
181                         memcpy_fromio(msgbuf, msg->message, len);
182                         msgbuf[len - 1] = 0;
183
184                         if (!strcmp(msgbuf, "resource does not exist"))
185                                 status = -ENXIO;
186                         else
187                                 status = -EINVAL;
188                         break;
189                 }
190
191                 buf = PTR_ALIGN(buf + 2 * sizeof(u32) + msg_length, 4);
192         }
193
194         rpm->ack_status = status;
195         complete(&rpm->ack);
196         return 0;
197 }
198
199 static int qcom_smd_rpm_probe(struct qcom_smd_device *sdev)
200 {
201         struct qcom_smd_rpm *rpm;
202
203         rpm = devm_kzalloc(&sdev->dev, sizeof(*rpm), GFP_KERNEL);
204         if (!rpm)
205                 return -ENOMEM;
206
207         mutex_init(&rpm->lock);
208         init_completion(&rpm->ack);
209
210         rpm->dev = &sdev->dev;
211         rpm->rpm_channel = sdev->channel;
212         qcom_smd_set_drvdata(sdev->channel, rpm);
213
214         dev_set_drvdata(&sdev->dev, rpm);
215
216         return of_platform_populate(sdev->dev.of_node, NULL, NULL, &sdev->dev);
217 }
218
219 static void qcom_smd_rpm_remove(struct qcom_smd_device *sdev)
220 {
221         of_platform_depopulate(&sdev->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         {}
229 };
230 MODULE_DEVICE_TABLE(of, qcom_smd_rpm_of_match);
231
232 static struct qcom_smd_driver qcom_smd_rpm_driver = {
233         .probe = qcom_smd_rpm_probe,
234         .remove = qcom_smd_rpm_remove,
235         .callback = qcom_smd_rpm_callback,
236         .driver  = {
237                 .name  = "qcom_smd_rpm",
238                 .owner = THIS_MODULE,
239                 .of_match_table = qcom_smd_rpm_of_match,
240         },
241 };
242
243 static int __init qcom_smd_rpm_init(void)
244 {
245         return qcom_smd_driver_register(&qcom_smd_rpm_driver);
246 }
247 arch_initcall(qcom_smd_rpm_init);
248
249 static void __exit qcom_smd_rpm_exit(void)
250 {
251         qcom_smd_driver_unregister(&qcom_smd_rpm_driver);
252 }
253 module_exit(qcom_smd_rpm_exit);
254
255 MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>");
256 MODULE_DESCRIPTION("Qualcomm SMD backed RPM driver");
257 MODULE_LICENSE("GPL v2");