1 // SPDX-License-Identifier: GPL-2.0-only
2 /* Copyright (c) 2021, Stephan Gerhold <stephan@gerhold.net> */
3 #include <linux/kernel.h>
4 #include <linux/mod_devicetable.h>
5 #include <linux/module.h>
6 #include <linux/platform_device.h>
7 #include <linux/rpmsg.h>
8 #include <linux/wwan.h>
10 struct rpmsg_wwan_dev
{
11 /* Lower level is a rpmsg dev, upper level is a wwan port */
12 struct rpmsg_device
*rpdev
;
13 struct wwan_port
*wwan_port
;
14 struct rpmsg_endpoint
*ept
;
17 static int rpmsg_wwan_ctrl_callback(struct rpmsg_device
*rpdev
,
18 void *buf
, int len
, void *priv
, u32 src
)
20 struct rpmsg_wwan_dev
*rpwwan
= priv
;
23 skb
= alloc_skb(len
, GFP_ATOMIC
);
27 skb_put_data(skb
, buf
, len
);
28 wwan_port_rx(rpwwan
->wwan_port
, skb
);
32 static int rpmsg_wwan_ctrl_start(struct wwan_port
*port
)
34 struct rpmsg_wwan_dev
*rpwwan
= wwan_port_get_drvdata(port
);
35 struct rpmsg_channel_info chinfo
= {
36 .src
= rpwwan
->rpdev
->src
,
37 .dst
= RPMSG_ADDR_ANY
,
40 strscpy(chinfo
.name
, rpwwan
->rpdev
->id
.name
, sizeof(chinfo
.name
));
41 rpwwan
->ept
= rpmsg_create_ept(rpwwan
->rpdev
, rpmsg_wwan_ctrl_callback
,
49 static void rpmsg_wwan_ctrl_stop(struct wwan_port
*port
)
51 struct rpmsg_wwan_dev
*rpwwan
= wwan_port_get_drvdata(port
);
53 rpmsg_destroy_ept(rpwwan
->ept
);
57 static int rpmsg_wwan_ctrl_tx(struct wwan_port
*port
, struct sk_buff
*skb
)
59 struct rpmsg_wwan_dev
*rpwwan
= wwan_port_get_drvdata(port
);
62 ret
= rpmsg_trysend(rpwwan
->ept
, skb
->data
, skb
->len
);
70 static int rpmsg_wwan_ctrl_tx_blocking(struct wwan_port
*port
, struct sk_buff
*skb
)
72 struct rpmsg_wwan_dev
*rpwwan
= wwan_port_get_drvdata(port
);
75 ret
= rpmsg_send(rpwwan
->ept
, skb
->data
, skb
->len
);
83 static __poll_t
rpmsg_wwan_ctrl_tx_poll(struct wwan_port
*port
,
84 struct file
*filp
, poll_table
*wait
)
86 struct rpmsg_wwan_dev
*rpwwan
= wwan_port_get_drvdata(port
);
88 return rpmsg_poll(rpwwan
->ept
, filp
, wait
);
91 static const struct wwan_port_ops rpmsg_wwan_pops
= {
92 .start
= rpmsg_wwan_ctrl_start
,
93 .stop
= rpmsg_wwan_ctrl_stop
,
94 .tx
= rpmsg_wwan_ctrl_tx
,
95 .tx_blocking
= rpmsg_wwan_ctrl_tx_blocking
,
96 .tx_poll
= rpmsg_wwan_ctrl_tx_poll
,
99 static struct device
*rpmsg_wwan_find_parent(struct device
*dev
)
101 /* Select first platform device as parent for the WWAN ports.
102 * On Qualcomm platforms this is usually the platform device that
103 * represents the modem remote processor. This might need to be
104 * adjusted when adding device IDs for other platforms.
106 for (dev
= dev
->parent
; dev
; dev
= dev
->parent
) {
107 if (dev_is_platform(dev
))
113 static int rpmsg_wwan_ctrl_probe(struct rpmsg_device
*rpdev
)
115 struct rpmsg_wwan_dev
*rpwwan
;
116 struct wwan_port
*port
;
117 struct device
*parent
;
119 parent
= rpmsg_wwan_find_parent(&rpdev
->dev
);
123 rpwwan
= devm_kzalloc(&rpdev
->dev
, sizeof(*rpwwan
), GFP_KERNEL
);
127 rpwwan
->rpdev
= rpdev
;
128 dev_set_drvdata(&rpdev
->dev
, rpwwan
);
130 /* Register as a wwan port, id.driver_data contains wwan port type */
131 port
= wwan_create_port(parent
, rpdev
->id
.driver_data
,
132 &rpmsg_wwan_pops
, NULL
, rpwwan
);
134 return PTR_ERR(port
);
136 rpwwan
->wwan_port
= port
;
141 static void rpmsg_wwan_ctrl_remove(struct rpmsg_device
*rpdev
)
143 struct rpmsg_wwan_dev
*rpwwan
= dev_get_drvdata(&rpdev
->dev
);
145 wwan_remove_port(rpwwan
->wwan_port
);
148 static const struct rpmsg_device_id rpmsg_wwan_ctrl_id_table
[] = {
149 /* RPMSG channels for Qualcomm SoCs with integrated modem */
150 { .name
= "DATA5_CNTL", .driver_data
= WWAN_PORT_QMI
},
151 { .name
= "DATA4", .driver_data
= WWAN_PORT_AT
},
152 { .name
= "DATA1", .driver_data
= WWAN_PORT_AT
},
155 MODULE_DEVICE_TABLE(rpmsg
, rpmsg_wwan_ctrl_id_table
);
157 static struct rpmsg_driver rpmsg_wwan_ctrl_driver
= {
158 .drv
.name
= "rpmsg_wwan_ctrl",
159 .id_table
= rpmsg_wwan_ctrl_id_table
,
160 .probe
= rpmsg_wwan_ctrl_probe
,
161 .remove
= rpmsg_wwan_ctrl_remove
,
163 module_rpmsg_driver(rpmsg_wwan_ctrl_driver
);
165 MODULE_LICENSE("GPL v2");
166 MODULE_DESCRIPTION("RPMSG WWAN CTRL Driver");
167 MODULE_AUTHOR("Stephan Gerhold <stephan@gerhold.net>");