176 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			176 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: ISC
 | |
| /* Copyright (C) 2021 MediaTek Inc. */
 | |
| 
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/mmc/sdio_func.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/iopoll.h>
 | |
| 
 | |
| #include "mt7921.h"
 | |
| #include "../sdio.h"
 | |
| #include "../mt76_connac2_mac.h"
 | |
| #include "mcu.h"
 | |
| #include "regs.h"
 | |
| 
 | |
| static int
 | |
| mt7921s_mcu_send_message(struct mt76_dev *mdev, struct sk_buff *skb,
 | |
| 			 int cmd, int *seq)
 | |
| {
 | |
| 	struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76);
 | |
| 	enum mt7921_sdio_pkt_type type = MT7921_SDIO_CMD;
 | |
| 	enum mt76_mcuq_id txq = MT_MCUQ_WM;
 | |
| 	int ret, pad;
 | |
| 
 | |
| 	/* We just return in case firmware assertion to avoid blocking the
 | |
| 	 * common workqueue to run, for example, the coredump work might be
 | |
| 	 * blocked by mt792x_mac_work that is excuting register access via sdio
 | |
| 	 * bus.
 | |
| 	 */
 | |
| 	if (dev->fw_assert)
 | |
| 		return -EBUSY;
 | |
| 
 | |
| 	ret = mt76_connac2_mcu_fill_message(mdev, skb, cmd, seq);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	mdev->mcu.timeout = 3 * HZ;
 | |
| 
 | |
| 	if (cmd == MCU_CMD(FW_SCATTER))
 | |
| 		type = MT7921_SDIO_FWDL;
 | |
| 
 | |
| 	mt792x_skb_add_usb_sdio_hdr(dev, skb, type);
 | |
| 	pad = round_up(skb->len, 4) - skb->len;
 | |
| 	__skb_put_zero(skb, pad);
 | |
| 
 | |
| 	ret = mt76_tx_queue_skb_raw(dev, mdev->q_mcu[txq], skb, 0);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	mt76_queue_kick(dev, mdev->q_mcu[txq]);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static u32 mt7921s_read_rm3r(struct mt792x_dev *dev)
 | |
| {
 | |
| 	struct mt76_sdio *sdio = &dev->mt76.sdio;
 | |
| 
 | |
| 	return sdio_readl(sdio->func, MCR_D2HRM3R, NULL);
 | |
| }
 | |
| 
 | |
| static u32 mt7921s_clear_rm3r_drv_own(struct mt792x_dev *dev)
 | |
| {
 | |
| 	struct mt76_sdio *sdio = &dev->mt76.sdio;
 | |
| 	u32 val;
 | |
| 
 | |
| 	val = sdio_readl(sdio->func, MCR_D2HRM3R, NULL);
 | |
| 	if (val)
 | |
| 		sdio_writel(sdio->func, H2D_SW_INT_CLEAR_MAILBOX_ACK,
 | |
| 			    MCR_WSICR, NULL);
 | |
| 
 | |
| 	return val;
 | |
| }
 | |
| 
 | |
| int mt7921s_mcu_init(struct mt792x_dev *dev)
 | |
| {
 | |
| 	static const struct mt76_mcu_ops mt7921s_mcu_ops = {
 | |
| 		.headroom = MT_SDIO_HDR_SIZE +
 | |
| 			    sizeof(struct mt76_connac2_mcu_txd),
 | |
| 		.tailroom = MT_SDIO_TAIL_SIZE,
 | |
| 		.mcu_skb_send_msg = mt7921s_mcu_send_message,
 | |
| 		.mcu_parse_response = mt7921_mcu_parse_response,
 | |
| 		.mcu_rr = mt76_connac_mcu_reg_rr,
 | |
| 		.mcu_wr = mt76_connac_mcu_reg_wr,
 | |
| 	};
 | |
| 	int ret;
 | |
| 
 | |
| 	mt7921s_mcu_drv_pmctrl(dev);
 | |
| 
 | |
| 	dev->mt76.mcu_ops = &mt7921s_mcu_ops;
 | |
| 
 | |
| 	ret = mt7921_run_firmware(dev);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	set_bit(MT76_STATE_MCU_RUNNING, &dev->mphy.state);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int mt7921s_mcu_drv_pmctrl(struct mt792x_dev *dev)
 | |
| {
 | |
| 	struct sdio_func *func = dev->mt76.sdio.func;
 | |
| 	struct mt76_phy *mphy = &dev->mt76.phy;
 | |
| 	struct mt76_connac_pm *pm = &dev->pm;
 | |
| 	u32 status;
 | |
| 	int err;
 | |
| 
 | |
| 	sdio_claim_host(func);
 | |
| 
 | |
| 	sdio_writel(func, WHLPCR_FW_OWN_REQ_CLR, MCR_WHLPCR, NULL);
 | |
| 
 | |
| 	err = readx_poll_timeout(mt76s_read_pcr, &dev->mt76, status,
 | |
| 				 status & WHLPCR_IS_DRIVER_OWN, 2000, 1000000);
 | |
| 
 | |
| 	if (!err && test_bit(MT76_STATE_MCU_RUNNING, &dev->mphy.state))
 | |
| 		err = readx_poll_timeout(mt7921s_read_rm3r, dev, status,
 | |
| 					 status & D2HRM3R_IS_DRIVER_OWN,
 | |
| 					 2000, 1000000);
 | |
| 
 | |
| 	sdio_release_host(func);
 | |
| 
 | |
| 	if (err < 0) {
 | |
| 		dev_err(dev->mt76.dev, "driver own failed\n");
 | |
| 		return -EIO;
 | |
| 	}
 | |
| 
 | |
| 	clear_bit(MT76_STATE_PM, &mphy->state);
 | |
| 
 | |
| 	pm->stats.last_wake_event = jiffies;
 | |
| 	pm->stats.doze_time += pm->stats.last_wake_event -
 | |
| 			       pm->stats.last_doze_event;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int mt7921s_mcu_fw_pmctrl(struct mt792x_dev *dev)
 | |
| {
 | |
| 	struct sdio_func *func = dev->mt76.sdio.func;
 | |
| 	struct mt76_phy *mphy = &dev->mt76.phy;
 | |
| 	struct mt76_connac_pm *pm = &dev->pm;
 | |
| 	u32 status;
 | |
| 	int err;
 | |
| 
 | |
| 	sdio_claim_host(func);
 | |
| 
 | |
| 	if (test_bit(MT76_STATE_MCU_RUNNING, &dev->mphy.state)) {
 | |
| 		err = readx_poll_timeout(mt7921s_clear_rm3r_drv_own,
 | |
| 					 dev, status,
 | |
| 					 !(status & D2HRM3R_IS_DRIVER_OWN),
 | |
| 					 2000, 1000000);
 | |
| 		if (err < 0) {
 | |
| 			dev_err(dev->mt76.dev, "mailbox ACK not cleared\n");
 | |
| 			goto out;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	sdio_writel(func, WHLPCR_FW_OWN_REQ_SET, MCR_WHLPCR, NULL);
 | |
| 
 | |
| 	err = readx_poll_timeout(mt76s_read_pcr, &dev->mt76, status,
 | |
| 				 !(status & WHLPCR_IS_DRIVER_OWN), 2000, 1000000);
 | |
| out:
 | |
| 	sdio_release_host(func);
 | |
| 
 | |
| 	if (err < 0) {
 | |
| 		dev_err(dev->mt76.dev, "firmware own failed\n");
 | |
| 		clear_bit(MT76_STATE_PM, &mphy->state);
 | |
| 		return -EIO;
 | |
| 	}
 | |
| 
 | |
| 	pm->stats.last_doze_event = jiffies;
 | |
| 	pm->stats.awake_time += pm->stats.last_doze_event -
 | |
| 				pm->stats.last_wake_event;
 | |
| 
 | |
| 	return 0;
 | |
| }
 |