diff mbox series

[v2,6/7] wifi: mt76: mt7921: introduce remain_on_channel support

Message ID f1ddba9cca58a99a02cbc8f2ccdfd074cefa659b.1665533390.git.objelf@gmail.com
State Superseded
Headers show
Series [v2,1/7] wifi: mt76: connac: add mt76_connac_mcu_uni_set_chctx | expand

Commit Message

Sean Wang Oct. 12, 2022, 12:16 a.m. UTC
From: Sean Wang <sean.wang@mediatek.com>

Introduce remain_on_channel support. Additionally, we add
mt7921_check_offload_capability to disable .remain_on_channel and
.cancel_remain_on_channel and related configuration because those
operations would rely on the fundamental MCU commands that will be only
supported with newer firmware.

Co-developed-by: Deren Wu <deren.wu@mediatek.com>
Signed-off-by: Deren Wu <deren.wu@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
v2: move mt7921_check_offload_capability prior to ieee80211_alloc_hw
    and decie what ieee80211_ops instance we should use by the
    firmware capability bit.
---
 .../net/wireless/mediatek/mt76/mt7921/init.c  |  63 +++++++
 .../net/wireless/mediatek/mt76/mt7921/main.c  | 165 ++++++++++++++++++
 .../net/wireless/mediatek/mt76/mt7921/mcu.c   |  24 +++
 .../wireless/mediatek/mt76/mt7921/mt7921.h    |  49 ++++++
 .../net/wireless/mediatek/mt76/mt7921/pci.c   |  26 ++-
 .../net/wireless/mediatek/mt76/mt7921/sdio.c  |  17 +-
 .../net/wireless/mediatek/mt76/mt7921/usb.c   |  17 +-
 7 files changed, 347 insertions(+), 14 deletions(-)

Comments

kernel test robot Oct. 12, 2022, 5:56 a.m. UTC | #1
Hi,

I love your patch! Perhaps something to improve:

[auto build test WARNING on wireless-next/main]
[also build test WARNING on wireless/main linus/master next-20221012]
[cannot apply to v6.0]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/sean-wang-mediatek-com/wifi-mt76-connac-add-mt76_connac_mcu_uni_set_chctx/20221012-081836
base:   https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git main
config: loongarch-allyesconfig
compiler: loongarch64-linux-gcc (GCC) 12.1.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/intel-lab-lkp/linux/commit/6a229606eb180ff1a7ddde8fd9f08861b48dd6b9
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review sean-wang-mediatek-com/wifi-mt76-connac-add-mt76_connac_mcu_uni_set_chctx/20221012-081836
        git checkout 6a229606eb180ff1a7ddde8fd9f08861b48dd6b9
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=loongarch SHELL=/bin/bash drivers/net/wireless/mediatek/mt76/mt7921/

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

   drivers/net/wireless/mediatek/mt76/mt7921/main.c: In function 'mt7921_abort_roc':
>> drivers/net/wireless/mediatek/mt76/mt7921/main.c:425:13: warning: variable 'err' set but not used [-Wunused-but-set-variable]
     425 |         int err;
         |             ^~~


vim +/err +425 drivers/net/wireless/mediatek/mt76/mt7921/main.c

   422	
   423	static int mt7921_abort_roc(struct mt7921_phy *phy, struct mt7921_vif *vif)
   424	{
 > 425		int err;
   426	
   427		if (!test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
   428			return 0;
   429	
   430		del_timer_sync(&phy->roc_timer);
   431		cancel_work_sync(&phy->roc_work);
   432		err = mt7921_mcu_abort_roc(phy, vif, phy->roc_token_id);
   433		clear_bit(MT76_STATE_ROC, &phy->mt76->state);
   434	
   435		return 00;
   436	}
   437
kernel test robot Oct. 13, 2022, 1:48 p.m. UTC | #2
Hi,

I love your patch! Yet something to improve:

[auto build test ERROR on wireless-next/main]
[also build test ERROR on wireless/main linus/master next-20221012]
[cannot apply to v6.0]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/sean-wang-mediatek-com/wifi-mt76-connac-add-mt76_connac_mcu_uni_set_chctx/20221012-081836
base:   https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git main
config: powerpc-allmodconfig
compiler: clang version 16.0.0 (https://github.com/llvm/llvm-project 791a7ae1ba3efd6bca96338e10ffde557ba83920)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # install powerpc cross compiling tool for clang build
        # apt-get install binutils-powerpc-linux-gnu
        # https://github.com/intel-lab-lkp/linux/commit/6a229606eb180ff1a7ddde8fd9f08861b48dd6b9
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review sean-wang-mediatek-com/wifi-mt76-connac-add-mt76_connac_mcu_uni_set_chctx/20221012-081836
        git checkout 6a229606eb180ff1a7ddde8fd9f08861b48dd6b9
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=powerpc SHELL=/bin/bash drivers/net/wireless/mediatek/mt76/mt7921/

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>

All error/warnings (new ones prefixed by >>):

>> drivers/net/wireless/mediatek/mt76/mt7921/init.c:173:9: warning: variable 'features' is used uninitialized whenever 'while' loop exits because its condition is false [-Wsometimes-uninitialized]
           while (data < end) {
                  ^~~~~~~~~~
   drivers/net/wireless/mediatek/mt76/mt7921/init.c:187:9: note: uninitialized use occurs here
           return features->data;
                  ^~~~~~~~
   drivers/net/wireless/mediatek/mt76/mt7921/init.c:173:9: note: remove the condition if it is always true
           while (data < end) {
                  ^~~~~~~~~~
                  1
   drivers/net/wireless/mediatek/mt76/mt7921/init.c:143:37: note: initialize the variable 'features' to silence this warning
           struct mt7921_fw_features *features;
                                              ^
                                               = NULL
   1 warning generated.
--
>> drivers/net/wireless/mediatek/mt76/mt7921/usb.c:231:7: error: no member named 'ops' in 'struct mt7921_dev'
           dev->ops = ops;
           ~~~  ^
   1 error generated.


vim +231 drivers/net/wireless/mediatek/mt76/mt7921/usb.c

   168	
   169	static int mt7921u_probe(struct usb_interface *usb_intf,
   170				 const struct usb_device_id *id)
   171	{
   172		static const struct mt76_driver_ops drv_ops = {
   173			.txwi_size = MT_SDIO_TXD_SIZE,
   174			.drv_flags = MT_DRV_RX_DMA_HDR | MT_DRV_HW_MGMT_TXQ,
   175			.survey_flags = SURVEY_INFO_TIME_TX |
   176					SURVEY_INFO_TIME_RX |
   177					SURVEY_INFO_TIME_BSS_RX,
   178			.tx_prepare_skb = mt7921_usb_sdio_tx_prepare_skb,
   179			.tx_complete_skb = mt7921_usb_sdio_tx_complete_skb,
   180			.tx_status_data = mt7921_usb_sdio_tx_status_data,
   181			.rx_skb = mt7921_queue_rx_skb,
   182			.rx_check = mt7921_rx_check,
   183			.sta_ps = mt7921_sta_ps,
   184			.sta_add = mt7921_mac_sta_add,
   185			.sta_assoc = mt7921_mac_sta_assoc,
   186			.sta_remove = mt7921_mac_sta_remove,
   187			.update_survey = mt7921_update_channel,
   188		};
   189		static const struct mt7921_hif_ops hif_ops = {
   190			.mcu_init = mt7921u_mcu_init,
   191			.init_reset = mt7921u_init_reset,
   192			.reset = mt7921u_mac_reset,
   193		};
   194		static struct mt76_bus_ops bus_ops = {
   195			.rr = mt7921u_rr,
   196			.wr = mt7921u_wr,
   197			.rmw = mt7921u_rmw,
   198			.read_copy = mt76u_read_copy,
   199			.write_copy = mt7921u_copy,
   200			.type = MT76_BUS_USB,
   201		};
   202		struct usb_device *udev = interface_to_usbdev(usb_intf);
   203		struct ieee80211_ops *ops;
   204		struct ieee80211_hw *hw;
   205		struct mt7921_dev *dev;
   206		struct mt76_dev *mdev;
   207		const void *ops_src;
   208		u8 features;
   209		int ret;
   210	
   211		features = mt7921_check_offload_capability(&usb_intf->dev, (const char *)
   212							   id->driver_info);
   213		if (features & MT7921_FW_CAP_CNM)
   214			ops_src = &mt7921_ops_chanctx;
   215		else
   216			ops_src = &mt7921_ops;
   217	
   218		ops = devm_kmemdup(&usb_intf->dev, ops_src, sizeof(*ops), GFP_KERNEL);
   219		if (!ops)
   220			return -ENOMEM;
   221	
   222		ops->stop = mt7921u_stop;
   223	
   224		mdev = mt76_alloc_device(&usb_intf->dev, sizeof(*dev), ops, &drv_ops);
   225		if (!mdev)
   226			return -ENOMEM;
   227	
   228		dev = container_of(mdev, struct mt7921_dev, mt76);
   229		dev->fw_features = features;
   230		dev->hif_ops = &hif_ops;
 > 231		dev->ops = ops;
   232	
   233		udev = usb_get_dev(udev);
   234		usb_reset_device(udev);
   235	
   236		usb_set_intfdata(usb_intf, dev);
   237	
   238		ret = __mt76u_init(mdev, usb_intf, &bus_ops);
   239		if (ret < 0)
   240			goto error;
   241	
   242		mdev->rev = (mt76_rr(dev, MT_HW_CHIPID) << 16) |
   243			    (mt76_rr(dev, MT_HW_REV) & 0xff);
   244		dev_dbg(mdev->dev, "ASIC revision: %04x\n", mdev->rev);
   245	
   246		if (mt76_get_field(dev, MT_CONN_ON_MISC, MT_TOP_MISC2_FW_N9_RDY)) {
   247			ret = mt7921u_wfsys_reset(dev);
   248			if (ret)
   249				goto error;
   250		}
   251	
   252		ret = mt7921u_mcu_power_on(dev);
   253		if (ret)
   254			goto error;
   255	
   256		ret = mt76u_alloc_mcu_queue(&dev->mt76);
   257		if (ret)
   258			goto error;
   259	
   260		ret = mt76u_alloc_queues(&dev->mt76);
   261		if (ret)
   262			goto error;
   263	
   264		ret = mt7921u_dma_init(dev, false);
   265		if (ret)
   266			return ret;
   267	
   268		hw = mt76_hw(dev);
   269		/* check hw sg support in order to enable AMSDU */
   270		hw->max_tx_fragments = mdev->usb.sg_en ? MT_HW_TXP_MAX_BUF_NUM : 1;
   271	
   272		ret = mt7921_register_device(dev);
   273		if (ret)
   274			goto error;
   275	
   276		return 0;
   277	
   278	error:
   279		mt76u_queues_deinit(&dev->mt76);
   280	
   281		usb_set_intfdata(usb_intf, NULL);
   282		usb_put_dev(interface_to_usbdev(usb_intf));
   283	
   284		mt76_free_device(&dev->mt76);
   285	
   286		return ret;
   287	}
   288
kernel test robot Oct. 13, 2022, 1:48 p.m. UTC | #3
Hi,

I love your patch! Yet something to improve:

[auto build test ERROR on wireless-next/main]
[also build test ERROR on wireless/main linus/master next-20221013]
[cannot apply to v6.0]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/sean-wang-mediatek-com/wifi-mt76-connac-add-mt76_connac_mcu_uni_set_chctx/20221012-081836
base:   https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git main
config: parisc-allmodconfig
compiler: hppa-linux-gcc (GCC) 12.1.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/intel-lab-lkp/linux/commit/6a229606eb180ff1a7ddde8fd9f08861b48dd6b9
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review sean-wang-mediatek-com/wifi-mt76-connac-add-mt76_connac_mcu_uni_set_chctx/20221012-081836
        git checkout 6a229606eb180ff1a7ddde8fd9f08861b48dd6b9
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=parisc SHELL=/bin/bash drivers/net/wireless/mediatek/mt76/mt7921/

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>

All error/warnings (new ones prefixed by >>):

   drivers/net/wireless/mediatek/mt76/mt7921/main.c: In function 'mt7921_abort_roc':
>> drivers/net/wireless/mediatek/mt76/mt7921/main.c:425:13: warning: variable 'err' set but not used [-Wunused-but-set-variable]
     425 |         int err;
         |             ^~~
--
   drivers/net/wireless/mediatek/mt76/mt7921/usb.c: In function 'mt7921u_probe':
>> drivers/net/wireless/mediatek/mt76/mt7921/usb.c:231:12: error: 'struct mt7921_dev' has no member named 'ops'
     231 |         dev->ops = ops;
         |            ^~


vim +231 drivers/net/wireless/mediatek/mt76/mt7921/usb.c

   168	
   169	static int mt7921u_probe(struct usb_interface *usb_intf,
   170				 const struct usb_device_id *id)
   171	{
   172		static const struct mt76_driver_ops drv_ops = {
   173			.txwi_size = MT_SDIO_TXD_SIZE,
   174			.drv_flags = MT_DRV_RX_DMA_HDR | MT_DRV_HW_MGMT_TXQ,
   175			.survey_flags = SURVEY_INFO_TIME_TX |
   176					SURVEY_INFO_TIME_RX |
   177					SURVEY_INFO_TIME_BSS_RX,
   178			.tx_prepare_skb = mt7921_usb_sdio_tx_prepare_skb,
   179			.tx_complete_skb = mt7921_usb_sdio_tx_complete_skb,
   180			.tx_status_data = mt7921_usb_sdio_tx_status_data,
   181			.rx_skb = mt7921_queue_rx_skb,
   182			.rx_check = mt7921_rx_check,
   183			.sta_ps = mt7921_sta_ps,
   184			.sta_add = mt7921_mac_sta_add,
   185			.sta_assoc = mt7921_mac_sta_assoc,
   186			.sta_remove = mt7921_mac_sta_remove,
   187			.update_survey = mt7921_update_channel,
   188		};
   189		static const struct mt7921_hif_ops hif_ops = {
   190			.mcu_init = mt7921u_mcu_init,
   191			.init_reset = mt7921u_init_reset,
   192			.reset = mt7921u_mac_reset,
   193		};
   194		static struct mt76_bus_ops bus_ops = {
   195			.rr = mt7921u_rr,
   196			.wr = mt7921u_wr,
   197			.rmw = mt7921u_rmw,
   198			.read_copy = mt76u_read_copy,
   199			.write_copy = mt7921u_copy,
   200			.type = MT76_BUS_USB,
   201		};
   202		struct usb_device *udev = interface_to_usbdev(usb_intf);
   203		struct ieee80211_ops *ops;
   204		struct ieee80211_hw *hw;
   205		struct mt7921_dev *dev;
   206		struct mt76_dev *mdev;
   207		const void *ops_src;
   208		u8 features;
   209		int ret;
   210	
   211		features = mt7921_check_offload_capability(&usb_intf->dev, (const char *)
   212							   id->driver_info);
   213		if (features & MT7921_FW_CAP_CNM)
   214			ops_src = &mt7921_ops_chanctx;
   215		else
   216			ops_src = &mt7921_ops;
   217	
   218		ops = devm_kmemdup(&usb_intf->dev, ops_src, sizeof(*ops), GFP_KERNEL);
   219		if (!ops)
   220			return -ENOMEM;
   221	
   222		ops->stop = mt7921u_stop;
   223	
   224		mdev = mt76_alloc_device(&usb_intf->dev, sizeof(*dev), ops, &drv_ops);
   225		if (!mdev)
   226			return -ENOMEM;
   227	
   228		dev = container_of(mdev, struct mt7921_dev, mt76);
   229		dev->fw_features = features;
   230		dev->hif_ops = &hif_ops;
 > 231		dev->ops = ops;
   232	
   233		udev = usb_get_dev(udev);
   234		usb_reset_device(udev);
   235	
   236		usb_set_intfdata(usb_intf, dev);
   237	
   238		ret = __mt76u_init(mdev, usb_intf, &bus_ops);
   239		if (ret < 0)
   240			goto error;
   241	
   242		mdev->rev = (mt76_rr(dev, MT_HW_CHIPID) << 16) |
   243			    (mt76_rr(dev, MT_HW_REV) & 0xff);
   244		dev_dbg(mdev->dev, "ASIC revision: %04x\n", mdev->rev);
   245	
   246		if (mt76_get_field(dev, MT_CONN_ON_MISC, MT_TOP_MISC2_FW_N9_RDY)) {
   247			ret = mt7921u_wfsys_reset(dev);
   248			if (ret)
   249				goto error;
   250		}
   251	
   252		ret = mt7921u_mcu_power_on(dev);
   253		if (ret)
   254			goto error;
   255	
   256		ret = mt76u_alloc_mcu_queue(&dev->mt76);
   257		if (ret)
   258			goto error;
   259	
   260		ret = mt76u_alloc_queues(&dev->mt76);
   261		if (ret)
   262			goto error;
   263	
   264		ret = mt7921u_dma_init(dev, false);
   265		if (ret)
   266			return ret;
   267	
   268		hw = mt76_hw(dev);
   269		/* check hw sg support in order to enable AMSDU */
   270		hw->max_tx_fragments = mdev->usb.sg_en ? MT_HW_TXP_MAX_BUF_NUM : 1;
   271	
   272		ret = mt7921_register_device(dev);
   273		if (ret)
   274			goto error;
   275	
   276		return 0;
   277	
   278	error:
   279		mt76u_queues_deinit(&dev->mt76);
   280	
   281		usb_set_intfdata(usb_intf, NULL);
   282		usb_put_dev(interface_to_usbdev(usb_intf));
   283	
   284		mt76_free_device(&dev->mt76);
   285	
   286		return ret;
   287	}
   288
diff mbox series

Patch

diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/init.c b/drivers/net/wireless/mediatek/mt76/mt7921/init.c
index dcdb3cf04ac1..482b601f86d9 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/init.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/init.c
@@ -2,6 +2,7 @@ 
 /* Copyright (C) 2020 MediaTek Inc. */
 
 #include <linux/etherdevice.h>
+#include <linux/firmware.h>
 #include "mt7921.h"
 #include "mac.h"
 #include "mcu.h"
@@ -65,12 +66,18 @@  mt7921_init_wiphy(struct ieee80211_hw *hw)
 	hw->sta_data_size = sizeof(struct mt7921_sta);
 	hw->vif_data_size = sizeof(struct mt7921_vif);
 
+	if (dev->fw_features & MT7921_FW_CAP_CNM)
+		wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
+	else
+		wiphy->flags &= ~WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
+
 	wiphy->iface_combinations = if_comb;
 	wiphy->flags &= ~(WIPHY_FLAG_IBSS_RSN | WIPHY_FLAG_4ADDR_AP |
 			  WIPHY_FLAG_4ADDR_STATION);
 	wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
 				 BIT(NL80211_IFTYPE_AP);
 	wiphy->n_iface_combinations = ARRAY_SIZE(if_comb);
+	wiphy->max_remain_on_channel_duration = 5000;
 	wiphy->max_scan_ie_len = MT76_CONNAC_SCAN_IE_LEN;
 	wiphy->max_scan_ssids = 4;
 	wiphy->max_sched_scan_plan_interval =
@@ -129,6 +136,58 @@  mt7921_mac_init_band(struct mt7921_dev *dev, u8 band)
 	mt76_clear(dev, MT_DMA_DCR0(band), MT_DMA_DCR0_RXD_G5_EN);
 }
 
+u8 mt7921_check_offload_capability(struct device *dev, const char *fw_wm)
+{
+	const struct mt76_connac2_fw_trailer *hdr;
+	struct mt7921_realease_info *rel_info;
+	struct mt7921_fw_features *features;
+	const struct firmware *fw;
+	int ret, i, offset = 0;
+	const u8 *data, *end;
+
+	ret = request_firmware(&fw, fw_wm, dev);
+	if (ret)
+		return ret;
+
+	if (!fw || !fw->data || fw->size < sizeof(*hdr)) {
+		dev_err(dev, "Invalid firmware\n");
+		return -EINVAL;
+	}
+
+	data = fw->data;
+	hdr = (const void *)(fw->data + fw->size - sizeof(*hdr));
+
+	for (i = 0; i < hdr->n_region; i++) {
+		const struct mt76_connac2_fw_region *region;
+
+		region = (const void *)((const u8 *)hdr -
+					(hdr->n_region - i) * sizeof(*region));
+		offset += le32_to_cpu(region->len);
+	}
+
+	data += offset + 16;
+	rel_info = (struct mt7921_realease_info *)data;
+	data += sizeof(*rel_info);
+	end = data + le16_to_cpu(rel_info->len);
+
+	while (data < end) {
+		rel_info = (struct mt7921_realease_info *)data;
+		data += sizeof(*rel_info);
+
+		if (rel_info->tag == MT7921_FW_TAG_FEATURE) {
+			features = (struct mt7921_fw_features *)data;
+			break;
+		}
+
+		data += le16_to_cpu(rel_info->len) + rel_info->pad_len;
+	}
+
+	release_firmware(fw);
+
+	return features->data;
+}
+EXPORT_SYMBOL_GPL(mt7921_check_offload_capability);
+
 int mt7921_mac_init(struct mt7921_dev *dev)
 {
 	int i;
@@ -278,6 +337,10 @@  int mt7921_register_device(struct mt7921_dev *dev)
 	INIT_WORK(&dev->reset_work, mt7921_mac_reset_work);
 	INIT_WORK(&dev->init_work, mt7921_init_work);
 
+	INIT_WORK(&dev->phy.roc_work, mt7921_roc_work);
+	timer_setup(&dev->phy.roc_timer, mt7921_roc_timer, 0);
+	init_waitqueue_head(&dev->phy.roc_wait);
+
 	dev->pm.idle_timeout = MT7921_PM_TIMEOUT;
 	dev->pm.stats.last_wake_event = jiffies;
 	dev->pm.stats.last_doze_event = jiffies;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
index 82df5fa4fbc7..8e9257d3cc54 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
@@ -386,6 +386,116 @@  static void mt7921_remove_interface(struct ieee80211_hw *hw,
 	mt76_packet_id_flush(&dev->mt76, &msta->wcid);
 }
 
+static void mt7921_roc_iter(void *priv, u8 *mac,
+			    struct ieee80211_vif *vif)
+{
+	struct mt7921_vif *mvif = (struct mt7921_vif *)vif->drv_priv;
+	struct mt7921_phy *phy = priv;
+
+	mt7921_mcu_abort_roc(phy, mvif, phy->roc_token_id);
+}
+
+void mt7921_roc_work(struct work_struct *work)
+{
+	struct mt7921_phy *phy;
+
+	phy = (struct mt7921_phy *)container_of(work, struct mt7921_phy,
+						roc_work);
+
+	if (!test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
+		return;
+
+	mt7921_mutex_acquire(phy->dev);
+	ieee80211_iterate_active_interfaces(phy->mt76->hw,
+					    IEEE80211_IFACE_ITER_RESUME_ALL,
+					    mt7921_roc_iter, phy);
+	mt7921_mutex_release(phy->dev);
+	ieee80211_remain_on_channel_expired(phy->mt76->hw);
+}
+
+void mt7921_roc_timer(struct timer_list *timer)
+{
+	struct mt7921_phy *phy = from_timer(phy, timer, roc_timer);
+
+	ieee80211_queue_work(phy->mt76->hw, &phy->roc_work);
+}
+
+static int mt7921_abort_roc(struct mt7921_phy *phy, struct mt7921_vif *vif)
+{
+	int err;
+
+	if (!test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
+		return 0;
+
+	del_timer_sync(&phy->roc_timer);
+	cancel_work_sync(&phy->roc_work);
+	err = mt7921_mcu_abort_roc(phy, vif, phy->roc_token_id);
+	clear_bit(MT76_STATE_ROC, &phy->mt76->state);
+
+	return 00;
+}
+
+static int mt7921_set_roc(struct mt7921_phy *phy,
+			  struct mt7921_vif *vif,
+			  struct ieee80211_channel *chan,
+			  int duration,
+			  enum mt7921_roc_req type)
+{
+	int err;
+
+	if (test_and_set_bit(MT76_STATE_ROC, &phy->mt76->state))
+		return -EBUSY;
+
+	phy->roc_grant = false;
+
+	err = mt7921_mcu_set_roc(phy, vif, chan, duration, type,
+				 ++phy->roc_token_id);
+	if (err < 0) {
+		clear_bit(MT76_STATE_ROC, &phy->mt76->state);
+		goto out;
+	}
+
+	if (!wait_event_timeout(phy->roc_wait, phy->roc_grant, HZ)) {
+		mt7921_mcu_abort_roc(phy, vif, phy->roc_token_id);
+		clear_bit(MT76_STATE_ROC, &phy->mt76->state);
+		err = -ETIMEDOUT;
+	}
+
+out:
+	return err;
+}
+
+static int mt7921_remain_on_channel(struct ieee80211_hw *hw,
+				    struct ieee80211_vif *vif,
+				    struct ieee80211_channel *chan,
+				    int duration,
+				    enum ieee80211_roc_type type)
+{
+	struct mt7921_vif *mvif = (struct mt7921_vif *)vif->drv_priv;
+	struct mt7921_phy *phy = mt7921_hw_phy(hw);
+	int err;
+
+	mt7921_mutex_acquire(phy->dev);
+	err = mt7921_set_roc(phy, mvif, chan, duration, MT7921_ROC_REQ_ROC);
+	mt7921_mutex_release(phy->dev);
+
+	return err;
+}
+
+static int mt7921_cancel_remain_on_channel(struct ieee80211_hw *hw,
+					   struct ieee80211_vif *vif)
+{
+	struct mt7921_vif *mvif = (struct mt7921_vif *)vif->drv_priv;
+	struct mt7921_phy *phy = mt7921_hw_phy(hw);
+	int err;
+
+	mt7921_mutex_acquire(phy->dev);
+	err = mt7921_abort_roc(phy, mvif);
+	mt7921_mutex_release(phy->dev);
+
+	return err;
+}
+
 static int mt7921_set_channel(struct mt7921_phy *phy)
 {
 	struct mt7921_dev *dev = phy->dev;
@@ -1573,6 +1683,61 @@  mt7921_stop_ap(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
 	mt7921_mutex_release(dev);
 }
 
+const struct ieee80211_ops mt7921_ops_chanctx = {
+	.tx = mt7921_tx,
+	.start = mt7921_start,
+	.stop = mt7921_stop,
+	.add_interface = mt7921_add_interface,
+	.remove_interface = mt7921_remove_interface,
+	.config = mt7921_config,
+	.conf_tx = mt7921_conf_tx,
+	.configure_filter = mt7921_configure_filter,
+	.bss_info_changed = mt7921_bss_info_changed,
+	.start_ap = mt7921_start_ap,
+	.stop_ap = mt7921_stop_ap,
+	.sta_state = mt7921_sta_state,
+	.sta_pre_rcu_remove = mt76_sta_pre_rcu_remove,
+	.set_key = mt7921_set_key,
+	.sta_set_decap_offload = mt7921_sta_set_decap_offload,
+#if IS_ENABLED(CONFIG_IPV6)
+	.ipv6_addr_change = mt7921_ipv6_addr_change,
+#endif /* CONFIG_IPV6 */
+	.ampdu_action = mt7921_ampdu_action,
+	.set_rts_threshold = mt7921_set_rts_threshold,
+	.wake_tx_queue = mt76_wake_tx_queue,
+	.release_buffered_frames = mt76_release_buffered_frames,
+	.channel_switch_beacon = mt7921_channel_switch_beacon,
+	.get_txpower = mt76_get_txpower,
+	.get_stats = mt7921_get_stats,
+	.get_et_sset_count = mt7921_get_et_sset_count,
+	.get_et_strings = mt7921_get_et_strings,
+	.get_et_stats = mt7921_get_et_stats,
+	.get_tsf = mt7921_get_tsf,
+	.set_tsf = mt7921_set_tsf,
+	.get_survey = mt76_get_survey,
+	.get_antenna = mt76_get_antenna,
+	.set_antenna = mt7921_set_antenna,
+	.set_coverage_class = mt7921_set_coverage_class,
+	.hw_scan = mt7921_hw_scan,
+	.cancel_hw_scan = mt7921_cancel_hw_scan,
+	.sta_statistics = mt7921_sta_statistics,
+	.sched_scan_start = mt7921_start_sched_scan,
+	.sched_scan_stop = mt7921_stop_sched_scan,
+	CFG80211_TESTMODE_CMD(mt7921_testmode_cmd)
+	CFG80211_TESTMODE_DUMP(mt7921_testmode_dump)
+#ifdef CONFIG_PM
+	.suspend = mt7921_suspend,
+	.resume = mt7921_resume,
+	.set_wakeup = mt7921_set_wakeup,
+	.set_rekey_data = mt7921_set_rekey_data,
+#endif /* CONFIG_PM */
+	.flush = mt7921_flush,
+	.set_sar_specs = mt7921_set_sar_specs,
+	.remain_on_channel = mt7921_remain_on_channel,
+	.cancel_remain_on_channel = mt7921_cancel_remain_on_channel,
+};
+EXPORT_SYMBOL_GPL(mt7921_ops_chanctx);
+
 const struct ieee80211_ops mt7921_ops = {
 	.tx = mt7921_tx,
 	.start = mt7921_start,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
index 7a74abecb269..fb9c0f66cb27 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
@@ -154,6 +154,29 @@  void mt7921_mcu_set_suspend_iter(void *priv, u8 *mac, struct ieee80211_vif *vif)
 
 #endif /* CONFIG_PM */
 
+static void
+mt7921_mcu_uni_roc_event(struct mt7921_dev *dev, struct sk_buff *skb)
+{
+	struct mt7921_roc_grant_tlv *grant;
+	struct mt76_connac2_mcu_rxd *rxd;
+	int duration;
+
+	rxd = (struct mt76_connac2_mcu_rxd *)skb->data;
+	grant = (struct mt7921_roc_grant_tlv *)(rxd->tlv + 4);
+
+	/* should never happen */
+	WARN_ON_ONCE((le16_to_cpu(grant->tag) != UNI_EVENT_ROC_GRANT));
+
+	if (grant->reqtype == MT7921_ROC_REQ_ROC)
+		ieee80211_ready_on_channel(dev->mt76.phy.hw);
+
+	dev->phy.roc_grant = true;
+	wake_up(&dev->phy.roc_wait);
+	duration = le32_to_cpu(grant->max_interval);
+	mod_timer(&dev->phy.roc_timer,
+		  round_jiffies_up(jiffies + msecs_to_jiffies(duration)));
+}
+
 static void
 mt7921_mcu_scan_event(struct mt7921_dev *dev, struct sk_buff *skb)
 {
@@ -295,6 +318,7 @@  mt7921_mcu_uni_rx_unsolicited_event(struct mt7921_dev *dev,
 
 	switch (rxd->eid) {
 	case MCU_UNI_EVENT_ROC:
+		mt7921_mcu_uni_roc_event(dev, skb);
 		break;
 	default:
 		break;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h b/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h
index d9d78f6b088e..ab1bf34d5616 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h
@@ -32,6 +32,9 @@ 
 #define MT7921_MCU_INIT_RETRY_COUNT	10
 #define MT7921_WFSYS_INIT_RETRY_COUNT	2
 
+#define MT7921_FW_TAG_FEATURE		4
+#define MT7921_FW_CAP_CNM		BIT(7)
+
 #define MT7921_FIRMWARE_WM		"mediatek/WIFI_RAM_CODE_MT7961_1.bin"
 #define MT7921_ROM_PATCH		"mediatek/WIFI_MT7961_patch_mcu_1_2_hdr.bin"
 
@@ -67,6 +70,41 @@  enum mt7921_roc_req {
 	MT7921_ROC_REQ_NUM
 };
 
+enum {
+	UNI_EVENT_ROC_GRANT = 0,
+	UNI_EVENT_ROC_TAG_NUM
+};
+
+struct mt7921_realease_info {
+	__le16 len;
+	u8 pad_len;
+	u8 tag;
+} __packed;
+
+struct mt7921_fw_features {
+	u8 segment;
+	u8 data;
+	u8 rsv[14];
+} __packed;
+
+struct mt7921_roc_grant_tlv {
+	__le16 tag;
+	__le16 len;
+	u8 bss_idx;
+	u8 tokenid;
+	u8 status;
+	u8 primarychannel;
+	u8 rfsco;
+	u8 rfband;
+	u8 channelwidth;
+	u8 centerfreqseg1;
+	u8 centerfreqseg2;
+	u8 reqtype;
+	u8 dbdcband;
+	u8 rsv[1];
+	__le32 max_interval;
+} __packed;
+
 enum mt7921_sdio_pkt_type {
 	MT7921_SDIO_TXD,
 	MT7921_SDIO_DATA,
@@ -214,6 +252,12 @@  struct mt7921_phy {
 #endif
 
 	struct mt7921_clc *clc[MT7921_CLC_MAX_NUM];
+
+	struct work_struct roc_work;
+	struct timer_list roc_timer;
+	wait_queue_head_t roc_wait;
+	u8 roc_token_id;
+	bool roc_grant;
 };
 
 #define mt7921_init_reset(dev)		((dev)->hif_ops->init_reset(dev))
@@ -250,6 +294,7 @@  struct mt7921_dev {
 	struct work_struct init_work;
 
 	u8 fw_debug;
+	u8 fw_features;
 
 	struct mt76_connac_pm pm;
 	struct mt76_connac_coredump coredump;
@@ -312,6 +357,7 @@  mt7921_hw_dev(struct ieee80211_hw *hw)
 	mt76_connac_mutex_release(&(dev)->mt76, &(dev)->pm)
 
 extern const struct ieee80211_ops mt7921_ops;
+extern const struct ieee80211_ops mt7921_ops_chanctx;
 
 u32 mt7921_reg_map(struct mt7921_dev *dev, u32 addr);
 
@@ -439,6 +485,8 @@  int mt7921_mcu_uni_rx_ba(struct mt7921_dev *dev,
 			 struct ieee80211_ampdu_params *params,
 			 bool enable);
 void mt7921_scan_work(struct work_struct *work);
+void mt7921_roc_work(struct work_struct *work);
+void mt7921_roc_timer(struct timer_list *timer);
 int mt7921_mcu_uni_bss_ps(struct mt7921_dev *dev, struct ieee80211_vif *vif);
 int mt7921_mcu_drv_pmctrl(struct mt7921_dev *dev);
 int mt7921_mcu_fw_pmctrl(struct mt7921_dev *dev);
@@ -527,4 +575,5 @@  int mt7921_mcu_set_roc(struct mt7921_phy *phy, struct mt7921_vif *vif,
 		       enum mt7921_roc_req type, u8 token_id);
 int mt7921_mcu_abort_roc(struct mt7921_phy *phy, struct mt7921_vif *vif,
 			 u8 token_id);
+u8 mt7921_check_offload_capability(struct device *dev, const char *fw_wm);
 #endif
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
index b38d119b2ea9..6e7ad52b3659 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
@@ -13,10 +13,14 @@ 
 #include "../trace.h"
 
 static const struct pci_device_id mt7921_pci_device_table[] = {
-	{ PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x7961) },
-	{ PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x7922) },
-	{ PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x0608) },
-	{ PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x0616) },
+	{ PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x7961),
+		.driver_data = (kernel_ulong_t)MT7921_FIRMWARE_WM },
+	{ PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x7922),
+		.driver_data = (kernel_ulong_t)MT7922_FIRMWARE_WM },
+	{ PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x0608),
+		.driver_data = (kernel_ulong_t)MT7921_FIRMWARE_WM },
+	{ PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x0616),
+		.driver_data = (kernel_ulong_t)MT7921_FIRMWARE_WM },
 	{ },
 };
 
@@ -253,9 +257,11 @@  static int mt7921_pci_probe(struct pci_dev *pdev,
 		.fw_own = mt7921e_mcu_fw_pmctrl,
 	};
 
+	const struct ieee80211_ops *ops;
 	struct mt76_bus_ops *bus_ops;
 	struct mt7921_dev *dev;
 	struct mt76_dev *mdev;
+	u8 features;
 	int ret;
 
 	ret = pcim_enable_device(pdev);
@@ -279,8 +285,14 @@  static int mt7921_pci_probe(struct pci_dev *pdev,
 	if (mt7921_disable_aspm)
 		mt76_pci_disable_aspm(pdev);
 
-	mdev = mt76_alloc_device(&pdev->dev, sizeof(*dev), &mt7921_ops,
-				 &drv_ops);
+	features = mt7921_check_offload_capability(&pdev->dev, (const char *)
+						   id->driver_data);
+	if (features & MT7921_FW_CAP_CNM)
+		ops = &mt7921_ops_chanctx;
+	else
+		ops = &mt7921_ops;
+
+	mdev = mt76_alloc_device(&pdev->dev, sizeof(*dev), ops, &drv_ops);
 	if (!mdev) {
 		ret = -ENOMEM;
 		goto err_free_pci_vec;
@@ -289,8 +301,8 @@  static int mt7921_pci_probe(struct pci_dev *pdev,
 	pci_set_drvdata(pdev, mdev);
 
 	dev = container_of(mdev, struct mt7921_dev, mt76);
+	dev->fw_features = features;
 	dev->hif_ops = &mt7921_pcie_ops;
-
 	mt76_mmio_init(&dev->mt76, pcim_iomap_table(pdev)[0]);
 	tasklet_init(&dev->irq_tasklet, mt7921_irq_tasklet, (unsigned long)dev);
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c b/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
index 377ca5fa3f6e..87c90eec0245 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c
@@ -17,7 +17,8 @@ 
 #include "mcu.h"
 
 static const struct sdio_device_id mt7921s_table[] = {
-	{ SDIO_DEVICE(SDIO_VENDOR_ID_MEDIATEK, 0x7901) },
+	{ SDIO_DEVICE(SDIO_VENDOR_ID_MEDIATEK, 0x7901),
+		.driver_data = (kernel_ulong_t)MT7921_FIRMWARE_WM },
 	{ }	/* Terminating entry */
 };
 
@@ -122,18 +123,26 @@  static int mt7921s_probe(struct sdio_func *func,
 		.fw_own = mt7921s_mcu_fw_pmctrl,
 	};
 
+	const struct ieee80211_ops *ops;
 	struct mt7921_dev *dev;
 	struct mt76_dev *mdev;
+	u8 features;
 	int ret;
 
-	mdev = mt76_alloc_device(&func->dev, sizeof(*dev), &mt7921_ops,
-				 &drv_ops);
+	features = mt7921_check_offload_capability(&func->dev, (const char *)
+						   id->driver_data);
+	if (features & MT7921_FW_CAP_CNM)
+		ops = &mt7921_ops_chanctx;
+	else
+		ops = &mt7921_ops;
+
+	mdev = mt76_alloc_device(&func->dev, sizeof(*dev), ops, &drv_ops);
 	if (!mdev)
 		return -ENOMEM;
 
 	dev = container_of(mdev, struct mt7921_dev, mt76);
+	dev->fw_features = features;
 	dev->hif_ops = &mt7921_sdio_ops;
-
 	sdio_set_drvdata(func, dev);
 
 	ret = mt76s_init(mdev, func, &mt7921s_ops);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c
index 89249f0b6aba..c83f2b8315a0 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c
@@ -13,7 +13,8 @@ 
 #include "mac.h"
 
 static const struct usb_device_id mt7921u_device_table[] = {
-	{ USB_DEVICE_AND_INTERFACE_INFO(0x0e8d, 0x7961, 0xff, 0xff, 0xff) },
+	{ USB_DEVICE_AND_INTERFACE_INFO(0x0e8d, 0x7961, 0xff, 0xff, 0xff),
+		.driver_info = (kernel_ulong_t)MT7921_FIRMWARE_WM },
 	{ },
 };
 
@@ -204,10 +205,18 @@  static int mt7921u_probe(struct usb_interface *usb_intf,
 	struct ieee80211_hw *hw;
 	struct mt7921_dev *dev;
 	struct mt76_dev *mdev;
+	const void *ops_src;
+	u8 features;
 	int ret;
 
-	ops = devm_kmemdup(&usb_intf->dev, &mt7921_ops, sizeof(mt7921_ops),
-			   GFP_KERNEL);
+	features = mt7921_check_offload_capability(&usb_intf->dev, (const char *)
+						   id->driver_info);
+	if (features & MT7921_FW_CAP_CNM)
+		ops_src = &mt7921_ops_chanctx;
+	else
+		ops_src = &mt7921_ops;
+
+	ops = devm_kmemdup(&usb_intf->dev, ops_src, sizeof(*ops), GFP_KERNEL);
 	if (!ops)
 		return -ENOMEM;
 
@@ -218,7 +227,9 @@  static int mt7921u_probe(struct usb_interface *usb_intf,
 		return -ENOMEM;
 
 	dev = container_of(mdev, struct mt7921_dev, mt76);
+	dev->fw_features = features;
 	dev->hif_ops = &hif_ops;
+	dev->ops = ops;
 
 	udev = usb_get_dev(udev);
 	usb_reset_device(udev);