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 */
34struct 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 */
47struct 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 */
60struct 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 */
77struct 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 */
99int 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
145out:
146	kfree(pkt);
147	mutex_unlock(&rpm->lock);
148	return ret;
149}
150EXPORT_SYMBOL(qcom_rpm_smd_write);
151
152static 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
198static 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
216static void qcom_smd_rpm_remove(struct qcom_smd_device *sdev)
217{
218	of_platform_depopulate(&sdev->dev);
219}
220
221static const struct of_device_id qcom_smd_rpm_of_match[] = {
222	{ .compatible = "qcom,rpm-msm8974" },
223	{}
224};
225MODULE_DEVICE_TABLE(of, qcom_smd_rpm_of_match);
226
227static 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
238static int __init qcom_smd_rpm_init(void)
239{
240	return qcom_smd_driver_register(&qcom_smd_rpm_driver);
241}
242arch_initcall(qcom_smd_rpm_init);
243
244static void __exit qcom_smd_rpm_exit(void)
245{
246	qcom_smd_driver_unregister(&qcom_smd_rpm_driver);
247}
248module_exit(qcom_smd_rpm_exit);
249
250MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>");
251MODULE_DESCRIPTION("Qualcomm SMD backed RPM driver");
252MODULE_LICENSE("GPL v2");
253