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(dev: &mhi_dev->dev); |
25 | int rc; |
26 | |
27 | if (!qdev || mhi_res->transaction_status) |
28 | return; |
29 | |
30 | rc = qrtr_endpoint_post(ep: &qdev->ep, data: mhi_res->buf_addr, |
31 | len: mhi_res->bytes_xferd); |
32 | if (rc == -EINVAL) |
33 | dev_err(qdev->dev, "invalid ipcrouter packet\n" ); |
34 | } |
35 | |
36 | /* From QRTR to MHI */ |
37 | static void qcom_mhi_qrtr_ul_callback(struct mhi_device *mhi_dev, |
38 | struct mhi_result *mhi_res) |
39 | { |
40 | struct sk_buff *skb = mhi_res->buf_addr; |
41 | |
42 | if (skb->sk) |
43 | sock_put(sk: skb->sk); |
44 | consume_skb(skb); |
45 | } |
46 | |
47 | /* Send data over MHI */ |
48 | static int qcom_mhi_qrtr_send(struct qrtr_endpoint *ep, struct sk_buff *skb) |
49 | { |
50 | struct qrtr_mhi_dev *qdev = container_of(ep, struct qrtr_mhi_dev, ep); |
51 | int rc; |
52 | |
53 | if (skb->sk) |
54 | sock_hold(sk: skb->sk); |
55 | |
56 | rc = skb_linearize(skb); |
57 | if (rc) |
58 | goto free_skb; |
59 | |
60 | rc = mhi_queue_skb(mhi_dev: qdev->mhi_dev, dir: DMA_TO_DEVICE, skb, len: skb->len, |
61 | mflags: MHI_EOT); |
62 | if (rc) |
63 | goto free_skb; |
64 | |
65 | return rc; |
66 | |
67 | free_skb: |
68 | if (skb->sk) |
69 | sock_put(sk: skb->sk); |
70 | kfree_skb(skb); |
71 | |
72 | return rc; |
73 | } |
74 | |
75 | static int qcom_mhi_qrtr_probe(struct mhi_device *mhi_dev, |
76 | const struct mhi_device_id *id) |
77 | { |
78 | struct qrtr_mhi_dev *qdev; |
79 | int rc; |
80 | |
81 | qdev = devm_kzalloc(dev: &mhi_dev->dev, size: sizeof(*qdev), GFP_KERNEL); |
82 | if (!qdev) |
83 | return -ENOMEM; |
84 | |
85 | qdev->mhi_dev = mhi_dev; |
86 | qdev->dev = &mhi_dev->dev; |
87 | qdev->ep.xmit = qcom_mhi_qrtr_send; |
88 | |
89 | dev_set_drvdata(dev: &mhi_dev->dev, data: qdev); |
90 | rc = qrtr_endpoint_register(ep: &qdev->ep, QRTR_EP_NID_AUTO); |
91 | if (rc) |
92 | return rc; |
93 | |
94 | /* start channels */ |
95 | rc = mhi_prepare_for_transfer_autoqueue(mhi_dev); |
96 | if (rc) { |
97 | qrtr_endpoint_unregister(ep: &qdev->ep); |
98 | return rc; |
99 | } |
100 | |
101 | dev_dbg(qdev->dev, "Qualcomm MHI QRTR driver probed\n" ); |
102 | |
103 | return 0; |
104 | } |
105 | |
106 | static void qcom_mhi_qrtr_remove(struct mhi_device *mhi_dev) |
107 | { |
108 | struct qrtr_mhi_dev *qdev = dev_get_drvdata(dev: &mhi_dev->dev); |
109 | |
110 | qrtr_endpoint_unregister(ep: &qdev->ep); |
111 | mhi_unprepare_from_transfer(mhi_dev); |
112 | dev_set_drvdata(dev: &mhi_dev->dev, NULL); |
113 | } |
114 | |
115 | static const struct mhi_device_id qcom_mhi_qrtr_id_table[] = { |
116 | { .chan = "IPCR" }, |
117 | {} |
118 | }; |
119 | MODULE_DEVICE_TABLE(mhi, qcom_mhi_qrtr_id_table); |
120 | |
121 | static struct mhi_driver qcom_mhi_qrtr_driver = { |
122 | .probe = qcom_mhi_qrtr_probe, |
123 | .remove = qcom_mhi_qrtr_remove, |
124 | .dl_xfer_cb = qcom_mhi_qrtr_dl_callback, |
125 | .ul_xfer_cb = qcom_mhi_qrtr_ul_callback, |
126 | .id_table = qcom_mhi_qrtr_id_table, |
127 | .driver = { |
128 | .name = "qcom_mhi_qrtr" , |
129 | }, |
130 | }; |
131 | |
132 | module_mhi_driver(qcom_mhi_qrtr_driver); |
133 | |
134 | MODULE_AUTHOR("Chris Lew <clew@codeaurora.org>" ); |
135 | MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>" ); |
136 | MODULE_DESCRIPTION("Qualcomm IPC-Router MHI interface driver" ); |
137 | MODULE_LICENSE("GPL v2" ); |
138 | |