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