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