xref: /linux/drivers/net/wwan/t7xx/t7xx_port_wwan.c (revision 79790b6818e96c58fe2bffee1b418c16e64e7b80)
161b7a291SChandrashekar Devegowda // SPDX-License-Identifier: GPL-2.0-only
261b7a291SChandrashekar Devegowda /*
361b7a291SChandrashekar Devegowda  * Copyright (c) 2021, MediaTek Inc.
461b7a291SChandrashekar Devegowda  * Copyright (c) 2021-2022, Intel Corporation.
5*2dac6381SJinjian Song  * Copyright (c) 2024, Fibocom Wireless Inc.
661b7a291SChandrashekar Devegowda  *
761b7a291SChandrashekar Devegowda  * Authors:
861b7a291SChandrashekar Devegowda  *  Amir Hanania <amir.hanania@intel.com>
961b7a291SChandrashekar Devegowda  *  Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
1061b7a291SChandrashekar Devegowda  *  Haijun Liu <haijun.liu@mediatek.com>
1161b7a291SChandrashekar Devegowda  *  Moises Veleta <moises.veleta@intel.com>
1261b7a291SChandrashekar Devegowda  *  Ricardo Martinez <ricardo.martinez@linux.intel.com>
1361b7a291SChandrashekar Devegowda  *
1461b7a291SChandrashekar Devegowda  * Contributors:
1561b7a291SChandrashekar Devegowda  *  Andy Shevchenko <andriy.shevchenko@linux.intel.com>
1661b7a291SChandrashekar Devegowda  *  Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com>
1761b7a291SChandrashekar Devegowda  *  Eliot Lee <eliot.lee@intel.com>
1861b7a291SChandrashekar Devegowda  *  Sreehari Kancharla <sreehari.kancharla@intel.com>
19*2dac6381SJinjian Song  *  Jinjian Song <jinjian.song@fibocom.com>
2061b7a291SChandrashekar Devegowda  */
2161b7a291SChandrashekar Devegowda 
2261b7a291SChandrashekar Devegowda #include <linux/atomic.h>
2361b7a291SChandrashekar Devegowda #include <linux/bitfield.h>
2461b7a291SChandrashekar Devegowda #include <linux/dev_printk.h>
2561b7a291SChandrashekar Devegowda #include <linux/err.h>
2661b7a291SChandrashekar Devegowda #include <linux/gfp.h>
2761b7a291SChandrashekar Devegowda #include <linux/minmax.h>
2861b7a291SChandrashekar Devegowda #include <linux/netdevice.h>
2961b7a291SChandrashekar Devegowda #include <linux/skbuff.h>
3061b7a291SChandrashekar Devegowda #include <linux/spinlock.h>
3161b7a291SChandrashekar Devegowda #include <linux/string.h>
3261b7a291SChandrashekar Devegowda #include <linux/wwan.h>
3361b7a291SChandrashekar Devegowda 
3461b7a291SChandrashekar Devegowda #include "t7xx_port.h"
3561b7a291SChandrashekar Devegowda #include "t7xx_port_proxy.h"
3661b7a291SChandrashekar Devegowda #include "t7xx_state_monitor.h"
3761b7a291SChandrashekar Devegowda 
t7xx_port_wwan_start(struct wwan_port * port)38*2dac6381SJinjian Song static int t7xx_port_wwan_start(struct wwan_port *port)
3961b7a291SChandrashekar Devegowda {
4061b7a291SChandrashekar Devegowda 	struct t7xx_port *port_mtk = wwan_port_get_drvdata(port);
4161b7a291SChandrashekar Devegowda 
4261b7a291SChandrashekar Devegowda 	if (atomic_read(&port_mtk->usage_cnt))
4361b7a291SChandrashekar Devegowda 		return -EBUSY;
4461b7a291SChandrashekar Devegowda 
4561b7a291SChandrashekar Devegowda 	atomic_inc(&port_mtk->usage_cnt);
4661b7a291SChandrashekar Devegowda 	return 0;
4761b7a291SChandrashekar Devegowda }
4861b7a291SChandrashekar Devegowda 
t7xx_port_wwan_stop(struct wwan_port * port)49*2dac6381SJinjian Song static void t7xx_port_wwan_stop(struct wwan_port *port)
5061b7a291SChandrashekar Devegowda {
5161b7a291SChandrashekar Devegowda 	struct t7xx_port *port_mtk = wwan_port_get_drvdata(port);
5261b7a291SChandrashekar Devegowda 
5361b7a291SChandrashekar Devegowda 	atomic_dec(&port_mtk->usage_cnt);
5461b7a291SChandrashekar Devegowda }
5561b7a291SChandrashekar Devegowda 
t7xx_port_fastboot_tx(struct t7xx_port * port,struct sk_buff * skb)56*2dac6381SJinjian Song static int t7xx_port_fastboot_tx(struct t7xx_port *port, struct sk_buff *skb)
5761b7a291SChandrashekar Devegowda {
58*2dac6381SJinjian Song 	struct sk_buff *cur = skb, *tx_skb;
59*2dac6381SJinjian Song 	size_t actual, len, offset = 0;
60*2dac6381SJinjian Song 	int txq_mtu;
61*2dac6381SJinjian Song 	int ret;
62*2dac6381SJinjian Song 
63*2dac6381SJinjian Song 	txq_mtu = t7xx_get_port_mtu(port);
64*2dac6381SJinjian Song 	if (txq_mtu < 0)
65*2dac6381SJinjian Song 		return -EINVAL;
66*2dac6381SJinjian Song 
67*2dac6381SJinjian Song 	actual = cur->len;
68*2dac6381SJinjian Song 	while (actual) {
69*2dac6381SJinjian Song 		len = min_t(size_t, actual, txq_mtu);
70*2dac6381SJinjian Song 		tx_skb = __dev_alloc_skb(len, GFP_KERNEL);
71*2dac6381SJinjian Song 		if (!tx_skb)
72*2dac6381SJinjian Song 			return -ENOMEM;
73*2dac6381SJinjian Song 
74*2dac6381SJinjian Song 		skb_put_data(tx_skb, cur->data + offset, len);
75*2dac6381SJinjian Song 
76*2dac6381SJinjian Song 		ret = t7xx_port_send_raw_skb(port, tx_skb);
77*2dac6381SJinjian Song 		if (ret) {
78*2dac6381SJinjian Song 			dev_kfree_skb(tx_skb);
79*2dac6381SJinjian Song 			dev_err(port->dev, "Write error on fastboot port, %d\n", ret);
80*2dac6381SJinjian Song 			break;
81*2dac6381SJinjian Song 		}
82*2dac6381SJinjian Song 		offset += len;
83*2dac6381SJinjian Song 		actual -= len;
84*2dac6381SJinjian Song 	}
85*2dac6381SJinjian Song 
86*2dac6381SJinjian Song 	dev_kfree_skb(skb);
87*2dac6381SJinjian Song 	return 0;
88*2dac6381SJinjian Song }
89*2dac6381SJinjian Song 
t7xx_port_ctrl_tx(struct t7xx_port * port,struct sk_buff * skb)90*2dac6381SJinjian Song static int t7xx_port_ctrl_tx(struct t7xx_port *port, struct sk_buff *skb)
91*2dac6381SJinjian Song {
9261b7a291SChandrashekar Devegowda 	const struct t7xx_port_conf *port_conf;
9336bd28c1Shaozhe chang 	struct sk_buff *cur = skb, *cloned;
9461b7a291SChandrashekar Devegowda 	struct t7xx_fsm_ctl *ctl;
9561b7a291SChandrashekar Devegowda 	enum md_state md_state;
9636bd28c1Shaozhe chang 	int cnt = 0, ret;
9761b7a291SChandrashekar Devegowda 
98*2dac6381SJinjian Song 	port_conf = port->port_conf;
99*2dac6381SJinjian Song 	ctl = port->t7xx_dev->md->fsm_ctl;
10061b7a291SChandrashekar Devegowda 	md_state = t7xx_fsm_get_md_state(ctl);
10161b7a291SChandrashekar Devegowda 	if (md_state == MD_STATE_WAITING_FOR_HS1 || md_state == MD_STATE_WAITING_FOR_HS2) {
102*2dac6381SJinjian Song 		dev_warn(port->dev, "Cannot write to %s port when md_state=%d\n",
10361b7a291SChandrashekar Devegowda 			 port_conf->name, md_state);
10461b7a291SChandrashekar Devegowda 		return -ENODEV;
10561b7a291SChandrashekar Devegowda 	}
10661b7a291SChandrashekar Devegowda 
10736bd28c1Shaozhe chang 	while (cur) {
10836bd28c1Shaozhe chang 		cloned = skb_clone(cur, GFP_KERNEL);
10936bd28c1Shaozhe chang 		cloned->len = skb_headlen(cur);
110*2dac6381SJinjian Song 		ret = t7xx_port_send_skb(port, cloned, 0, 0);
11161b7a291SChandrashekar Devegowda 		if (ret) {
11236bd28c1Shaozhe chang 			dev_kfree_skb(cloned);
113*2dac6381SJinjian Song 			dev_err(port->dev, "Write error on %s port, %d\n",
11461b7a291SChandrashekar Devegowda 				port_conf->name, ret);
11536bd28c1Shaozhe chang 			return cnt ? cnt + ret : ret;
11661b7a291SChandrashekar Devegowda 		}
11736bd28c1Shaozhe chang 		cnt += cur->len;
11836bd28c1Shaozhe chang 		if (cur == skb)
11936bd28c1Shaozhe chang 			cur = skb_shinfo(skb)->frag_list;
12036bd28c1Shaozhe chang 		else
12136bd28c1Shaozhe chang 			cur = cur->next;
12261b7a291SChandrashekar Devegowda 	}
12361b7a291SChandrashekar Devegowda 
12461b7a291SChandrashekar Devegowda 	dev_kfree_skb(skb);
12561b7a291SChandrashekar Devegowda 	return 0;
12661b7a291SChandrashekar Devegowda }
12761b7a291SChandrashekar Devegowda 
t7xx_port_wwan_tx(struct wwan_port * port,struct sk_buff * skb)128*2dac6381SJinjian Song static int t7xx_port_wwan_tx(struct wwan_port *port, struct sk_buff *skb)
129*2dac6381SJinjian Song {
130*2dac6381SJinjian Song 	struct t7xx_port *port_private = wwan_port_get_drvdata(port);
131*2dac6381SJinjian Song 	const struct t7xx_port_conf *port_conf = port_private->port_conf;
132*2dac6381SJinjian Song 	int ret;
133*2dac6381SJinjian Song 
134*2dac6381SJinjian Song 	if (!port_private->chan_enable)
135*2dac6381SJinjian Song 		return -EINVAL;
136*2dac6381SJinjian Song 
137*2dac6381SJinjian Song 	if (port_conf->port_type != WWAN_PORT_FASTBOOT)
138*2dac6381SJinjian Song 		ret = t7xx_port_ctrl_tx(port_private, skb);
139*2dac6381SJinjian Song 	else
140*2dac6381SJinjian Song 		ret = t7xx_port_fastboot_tx(port_private, skb);
141*2dac6381SJinjian Song 
142*2dac6381SJinjian Song 	return ret;
143*2dac6381SJinjian Song }
144*2dac6381SJinjian Song 
14561b7a291SChandrashekar Devegowda static const struct wwan_port_ops wwan_ops = {
146*2dac6381SJinjian Song 	.start = t7xx_port_wwan_start,
147*2dac6381SJinjian Song 	.stop = t7xx_port_wwan_stop,
148*2dac6381SJinjian Song 	.tx = t7xx_port_wwan_tx,
14961b7a291SChandrashekar Devegowda };
15061b7a291SChandrashekar Devegowda 
t7xx_port_wwan_create(struct t7xx_port * port)151*2dac6381SJinjian Song static void t7xx_port_wwan_create(struct t7xx_port *port)
152*2dac6381SJinjian Song {
153*2dac6381SJinjian Song 	const struct t7xx_port_conf *port_conf = port->port_conf;
154*2dac6381SJinjian Song 	unsigned int header_len = sizeof(struct ccci_header), mtu;
155*2dac6381SJinjian Song 	struct wwan_port_caps caps;
156*2dac6381SJinjian Song 
157*2dac6381SJinjian Song 	if (!port->wwan.wwan_port) {
158*2dac6381SJinjian Song 		mtu = t7xx_get_port_mtu(port);
159*2dac6381SJinjian Song 		caps.frag_len = mtu - header_len;
160*2dac6381SJinjian Song 		caps.headroom_len = header_len;
161*2dac6381SJinjian Song 		port->wwan.wwan_port = wwan_create_port(port->dev, port_conf->port_type,
162*2dac6381SJinjian Song 							&wwan_ops, &caps, port);
163*2dac6381SJinjian Song 		if (IS_ERR(port->wwan.wwan_port))
164*2dac6381SJinjian Song 			dev_err(port->dev, "Unable to create WWAN port %s", port_conf->name);
165*2dac6381SJinjian Song 	}
166*2dac6381SJinjian Song }
167*2dac6381SJinjian Song 
t7xx_port_wwan_init(struct t7xx_port * port)16861b7a291SChandrashekar Devegowda static int t7xx_port_wwan_init(struct t7xx_port *port)
16961b7a291SChandrashekar Devegowda {
170*2dac6381SJinjian Song 	const struct t7xx_port_conf *port_conf = port->port_conf;
171*2dac6381SJinjian Song 
172*2dac6381SJinjian Song 	if (port_conf->port_type == WWAN_PORT_FASTBOOT)
173*2dac6381SJinjian Song 		t7xx_port_wwan_create(port);
174*2dac6381SJinjian Song 
17561b7a291SChandrashekar Devegowda 	port->rx_length_th = RX_QUEUE_MAXLEN;
17661b7a291SChandrashekar Devegowda 	return 0;
17761b7a291SChandrashekar Devegowda }
17861b7a291SChandrashekar Devegowda 
t7xx_port_wwan_uninit(struct t7xx_port * port)17961b7a291SChandrashekar Devegowda static void t7xx_port_wwan_uninit(struct t7xx_port *port)
18061b7a291SChandrashekar Devegowda {
181fece7a8cSM Chetan Kumar 	if (!port->wwan.wwan_port)
18261b7a291SChandrashekar Devegowda 		return;
18361b7a291SChandrashekar Devegowda 
18461b7a291SChandrashekar Devegowda 	port->rx_length_th = 0;
185fece7a8cSM Chetan Kumar 	wwan_remove_port(port->wwan.wwan_port);
186fece7a8cSM Chetan Kumar 	port->wwan.wwan_port = NULL;
18761b7a291SChandrashekar Devegowda }
18861b7a291SChandrashekar Devegowda 
t7xx_port_wwan_recv_skb(struct t7xx_port * port,struct sk_buff * skb)18961b7a291SChandrashekar Devegowda static int t7xx_port_wwan_recv_skb(struct t7xx_port *port, struct sk_buff *skb)
19061b7a291SChandrashekar Devegowda {
19161b7a291SChandrashekar Devegowda 	if (!atomic_read(&port->usage_cnt) || !port->chan_enable) {
19261b7a291SChandrashekar Devegowda 		const struct t7xx_port_conf *port_conf = port->port_conf;
19361b7a291SChandrashekar Devegowda 
19461b7a291SChandrashekar Devegowda 		dev_kfree_skb_any(skb);
19561b7a291SChandrashekar Devegowda 		dev_err_ratelimited(port->dev, "Port %s is not opened, drop packets\n",
19661b7a291SChandrashekar Devegowda 				    port_conf->name);
19761b7a291SChandrashekar Devegowda 		/* Dropping skb, caller should not access skb.*/
19861b7a291SChandrashekar Devegowda 		return 0;
19961b7a291SChandrashekar Devegowda 	}
20061b7a291SChandrashekar Devegowda 
201fece7a8cSM Chetan Kumar 	wwan_port_rx(port->wwan.wwan_port, skb);
20261b7a291SChandrashekar Devegowda 	return 0;
20361b7a291SChandrashekar Devegowda }
20461b7a291SChandrashekar Devegowda 
t7xx_port_wwan_enable_chl(struct t7xx_port * port)20561b7a291SChandrashekar Devegowda static int t7xx_port_wwan_enable_chl(struct t7xx_port *port)
20661b7a291SChandrashekar Devegowda {
20761b7a291SChandrashekar Devegowda 	spin_lock(&port->port_update_lock);
20861b7a291SChandrashekar Devegowda 	port->chan_enable = true;
20961b7a291SChandrashekar Devegowda 	spin_unlock(&port->port_update_lock);
21061b7a291SChandrashekar Devegowda 
21161b7a291SChandrashekar Devegowda 	return 0;
21261b7a291SChandrashekar Devegowda }
21361b7a291SChandrashekar Devegowda 
t7xx_port_wwan_disable_chl(struct t7xx_port * port)21461b7a291SChandrashekar Devegowda static int t7xx_port_wwan_disable_chl(struct t7xx_port *port)
21561b7a291SChandrashekar Devegowda {
21661b7a291SChandrashekar Devegowda 	spin_lock(&port->port_update_lock);
21761b7a291SChandrashekar Devegowda 	port->chan_enable = false;
21861b7a291SChandrashekar Devegowda 	spin_unlock(&port->port_update_lock);
21961b7a291SChandrashekar Devegowda 
22061b7a291SChandrashekar Devegowda 	return 0;
22161b7a291SChandrashekar Devegowda }
22261b7a291SChandrashekar Devegowda 
t7xx_port_wwan_md_state_notify(struct t7xx_port * port,unsigned int state)22361b7a291SChandrashekar Devegowda static void t7xx_port_wwan_md_state_notify(struct t7xx_port *port, unsigned int state)
22461b7a291SChandrashekar Devegowda {
22561b7a291SChandrashekar Devegowda 	const struct t7xx_port_conf *port_conf = port->port_conf;
226*2dac6381SJinjian Song 
227*2dac6381SJinjian Song 	if (port_conf->port_type == WWAN_PORT_FASTBOOT)
228*2dac6381SJinjian Song 		return;
22961b7a291SChandrashekar Devegowda 
23061b7a291SChandrashekar Devegowda 	if (state != MD_STATE_READY)
23161b7a291SChandrashekar Devegowda 		return;
23261b7a291SChandrashekar Devegowda 
233*2dac6381SJinjian Song 	t7xx_port_wwan_create(port);
23461b7a291SChandrashekar Devegowda }
23561b7a291SChandrashekar Devegowda 
23661b7a291SChandrashekar Devegowda struct port_ops wwan_sub_port_ops = {
23761b7a291SChandrashekar Devegowda 	.init = t7xx_port_wwan_init,
23861b7a291SChandrashekar Devegowda 	.recv_skb = t7xx_port_wwan_recv_skb,
23961b7a291SChandrashekar Devegowda 	.uninit = t7xx_port_wwan_uninit,
24061b7a291SChandrashekar Devegowda 	.enable_chl = t7xx_port_wwan_enable_chl,
24161b7a291SChandrashekar Devegowda 	.disable_chl = t7xx_port_wwan_disable_chl,
24261b7a291SChandrashekar Devegowda 	.md_state_notify = t7xx_port_wwan_md_state_notify,
24361b7a291SChandrashekar Devegowda };
244