xref: /openbmc/linux/drivers/soc/qcom/smd-rpm.c (revision a8fe58ce)
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-apq8084" },
223 	{ .compatible = "qcom,rpm-msm8916" },
224 	{ .compatible = "qcom,rpm-msm8974" },
225 	{}
226 };
227 MODULE_DEVICE_TABLE(of, qcom_smd_rpm_of_match);
228 
229 static struct qcom_smd_driver qcom_smd_rpm_driver = {
230 	.probe = qcom_smd_rpm_probe,
231 	.remove = qcom_smd_rpm_remove,
232 	.callback = qcom_smd_rpm_callback,
233 	.driver  = {
234 		.name  = "qcom_smd_rpm",
235 		.owner = THIS_MODULE,
236 		.of_match_table = qcom_smd_rpm_of_match,
237 	},
238 };
239 
240 static int __init qcom_smd_rpm_init(void)
241 {
242 	return qcom_smd_driver_register(&qcom_smd_rpm_driver);
243 }
244 arch_initcall(qcom_smd_rpm_init);
245 
246 static void __exit qcom_smd_rpm_exit(void)
247 {
248 	qcom_smd_driver_unregister(&qcom_smd_rpm_driver);
249 }
250 module_exit(qcom_smd_rpm_exit);
251 
252 MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>");
253 MODULE_DESCRIPTION("Qualcomm SMD backed RPM driver");
254 MODULE_LICENSE("GPL v2");
255