xref: /openbmc/linux/drivers/soc/qcom/smd-rpm.c (revision e3b9f1e8)
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 	{}
230 };
231 MODULE_DEVICE_TABLE(of, qcom_smd_rpm_of_match);
232 
233 static struct rpmsg_driver qcom_smd_rpm_driver = {
234 	.probe = qcom_smd_rpm_probe,
235 	.remove = qcom_smd_rpm_remove,
236 	.callback = qcom_smd_rpm_callback,
237 	.drv  = {
238 		.name  = "qcom_smd_rpm",
239 		.of_match_table = qcom_smd_rpm_of_match,
240 	},
241 };
242 
243 static int __init qcom_smd_rpm_init(void)
244 {
245 	return register_rpmsg_driver(&qcom_smd_rpm_driver);
246 }
247 arch_initcall(qcom_smd_rpm_init);
248 
249 static void __exit qcom_smd_rpm_exit(void)
250 {
251 	unregister_rpmsg_driver(&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");
258