xref: /linux/net/qrtr/mhi.c (revision 1fd1dc41724319406b0aff221a352a400b0ddfc5)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved.
4  */
5 
6 #include <linux/mhi.h>
7 #include <linux/mod_devicetable.h>
8 #include <linux/module.h>
9 #include <linux/skbuff.h>
10 #include <net/sock.h>
11 
12 #include "qrtr.h"
13 
14 struct qrtr_mhi_dev {
15 	struct qrtr_endpoint ep;
16 	struct mhi_device *mhi_dev;
17 	struct device *dev;
18 };
19 
20 /* From MHI to QRTR */
21 static void qcom_mhi_qrtr_dl_callback(struct mhi_device *mhi_dev,
22 				      struct mhi_result *mhi_res)
23 {
24 	struct qrtr_mhi_dev *qdev = dev_get_drvdata(&mhi_dev->dev);
25 	int rc;
26 
27 	if (!qdev || (mhi_res->transaction_status && mhi_res->transaction_status != -ENOTCONN))
28 		return;
29 
30 	/* Channel got reset. So just free the buffer */
31 	if (mhi_res->transaction_status == -ENOTCONN) {
32 		devm_kfree(&mhi_dev->dev, mhi_res->buf_addr);
33 		return;
34 	}
35 
36 	rc = qrtr_endpoint_post(&qdev->ep, mhi_res->buf_addr,
37 				mhi_res->bytes_xferd);
38 	if (rc == -EINVAL)
39 		dev_err(qdev->dev, "invalid ipcrouter packet\n");
40 
41 	/* Done with the buffer, now recycle it for future use */
42 	rc = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE, mhi_res->buf_addr,
43 			   mhi_dev->mhi_cntrl->buffer_len, MHI_EOT);
44 	if (rc)
45 		dev_err(&mhi_dev->dev, "Failed to recycle the buffer: %d\n", rc);
46 }
47 
48 /* From QRTR to MHI */
49 static void qcom_mhi_qrtr_ul_callback(struct mhi_device *mhi_dev,
50 				      struct mhi_result *mhi_res)
51 {
52 	struct sk_buff *skb = mhi_res->buf_addr;
53 
54 	if (skb->sk)
55 		sock_put(skb->sk);
56 	consume_skb(skb);
57 }
58 
59 /* Send data over MHI */
60 static int qcom_mhi_qrtr_send(struct qrtr_endpoint *ep, struct sk_buff *skb)
61 {
62 	struct qrtr_mhi_dev *qdev = container_of(ep, struct qrtr_mhi_dev, ep);
63 	int rc;
64 
65 	if (skb->sk)
66 		sock_hold(skb->sk);
67 
68 	rc = skb_linearize(skb);
69 	if (rc)
70 		goto free_skb;
71 
72 	rc = mhi_queue_skb(qdev->mhi_dev, DMA_TO_DEVICE, skb, skb->len,
73 			   MHI_EOT);
74 	if (rc)
75 		goto free_skb;
76 
77 	return rc;
78 
79 free_skb:
80 	if (skb->sk)
81 		sock_put(skb->sk);
82 	kfree_skb(skb);
83 
84 	return rc;
85 }
86 
87 static int qcom_mhi_qrtr_queue_dl_buffers(struct mhi_device *mhi_dev)
88 {
89 	u32 free_desc;
90 	void *buf;
91 	int ret;
92 
93 	free_desc = mhi_get_free_desc_count(mhi_dev, DMA_FROM_DEVICE);
94 	while (free_desc--) {
95 		buf = devm_kmalloc(&mhi_dev->dev, mhi_dev->mhi_cntrl->buffer_len, GFP_KERNEL);
96 		if (!buf)
97 			return -ENOMEM;
98 
99 		ret = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE, buf, mhi_dev->mhi_cntrl->buffer_len,
100 				    MHI_EOT);
101 		if (ret) {
102 			dev_err(&mhi_dev->dev, "Failed to queue buffer: %d\n", ret);
103 			return ret;
104 		}
105 	}
106 
107 	return 0;
108 }
109 
110 static int qcom_mhi_qrtr_probe(struct mhi_device *mhi_dev,
111 			       const struct mhi_device_id *id)
112 {
113 	struct qrtr_mhi_dev *qdev;
114 	int rc;
115 
116 	qdev = devm_kzalloc(&mhi_dev->dev, sizeof(*qdev), GFP_KERNEL);
117 	if (!qdev)
118 		return -ENOMEM;
119 
120 	qdev->mhi_dev = mhi_dev;
121 	qdev->dev = &mhi_dev->dev;
122 	qdev->ep.xmit = qcom_mhi_qrtr_send;
123 
124 	dev_set_drvdata(&mhi_dev->dev, qdev);
125 
126 	/* start channels */
127 	rc = mhi_prepare_for_transfer(mhi_dev);
128 	if (rc)
129 		return rc;
130 
131 	rc = qrtr_endpoint_register(&qdev->ep, QRTR_EP_NID_AUTO);
132 	if (rc)
133 		goto err_unprepare;
134 
135 	rc = qcom_mhi_qrtr_queue_dl_buffers(mhi_dev);
136 	if (rc)
137 		goto err_unregister;
138 
139 	dev_dbg(qdev->dev, "Qualcomm MHI QRTR driver probed\n");
140 
141 	return 0;
142 
143 err_unregister:
144 	qrtr_endpoint_unregister(&qdev->ep);
145 err_unprepare:
146 	mhi_unprepare_from_transfer(mhi_dev);
147 
148 	return rc;
149 }
150 
151 static void qcom_mhi_qrtr_remove(struct mhi_device *mhi_dev)
152 {
153 	struct qrtr_mhi_dev *qdev = dev_get_drvdata(&mhi_dev->dev);
154 
155 	qrtr_endpoint_unregister(&qdev->ep);
156 	mhi_unprepare_from_transfer(mhi_dev);
157 	dev_set_drvdata(&mhi_dev->dev, NULL);
158 }
159 
160 static const struct mhi_device_id qcom_mhi_qrtr_id_table[] = {
161 	{ .chan = "IPCR" },
162 	{}
163 };
164 MODULE_DEVICE_TABLE(mhi, qcom_mhi_qrtr_id_table);
165 
166 static int __maybe_unused qcom_mhi_qrtr_pm_suspend_late(struct device *dev)
167 {
168 	struct mhi_device *mhi_dev = container_of(dev, struct mhi_device, dev);
169 	enum mhi_state state;
170 
171 	state = mhi_get_mhi_state(mhi_dev->mhi_cntrl);
172 	/*
173 	 * If the device is in suspend state, then no need for the
174 	 * client driver to unprepare the channels.
175 	 */
176 	if (state == MHI_STATE_M3)
177 		return 0;
178 
179 	mhi_unprepare_from_transfer(mhi_dev);
180 
181 	return 0;
182 }
183 
184 static int __maybe_unused qcom_mhi_qrtr_pm_resume_early(struct device *dev)
185 {
186 	struct mhi_device *mhi_dev = container_of(dev, struct mhi_device, dev);
187 	enum mhi_state state;
188 	int rc;
189 
190 	state = mhi_get_mhi_state(mhi_dev->mhi_cntrl);
191 	/*
192 	 * If the device is in suspend state, we won't unprepare channels
193 	 * in suspend callback, therefore no need to prepare channels when
194 	 * resume.
195 	 */
196 	if (state == MHI_STATE_M3)
197 		return 0;
198 
199 	rc = mhi_prepare_for_transfer(mhi_dev);
200 	if (rc) {
201 		dev_err(dev, "failed to prepare for autoqueue transfer %d\n", rc);
202 		return rc;
203 	}
204 
205 	return qcom_mhi_qrtr_queue_dl_buffers(mhi_dev);
206 }
207 
208 static const struct dev_pm_ops qcom_mhi_qrtr_pm_ops = {
209 	SET_LATE_SYSTEM_SLEEP_PM_OPS(qcom_mhi_qrtr_pm_suspend_late,
210 				     qcom_mhi_qrtr_pm_resume_early)
211 };
212 
213 static struct mhi_driver qcom_mhi_qrtr_driver = {
214 	.probe = qcom_mhi_qrtr_probe,
215 	.remove = qcom_mhi_qrtr_remove,
216 	.dl_xfer_cb = qcom_mhi_qrtr_dl_callback,
217 	.ul_xfer_cb = qcom_mhi_qrtr_ul_callback,
218 	.id_table = qcom_mhi_qrtr_id_table,
219 	.driver = {
220 		.name = "qcom_mhi_qrtr",
221 		.pm = &qcom_mhi_qrtr_pm_ops,
222 	},
223 };
224 
225 module_mhi_driver(qcom_mhi_qrtr_driver);
226 
227 MODULE_AUTHOR("Chris Lew <clew@codeaurora.org>");
228 MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>");
229 MODULE_DESCRIPTION("Qualcomm IPC-Router MHI interface driver");
230 MODULE_LICENSE("GPL v2");
231