root/drivers/net/wireless/mediatek/mt76/mt7921/sdio_mac.c
// SPDX-License-Identifier: BSD-3-Clause-Clear
/* Copyright (C) 2021 MediaTek Inc. */

#include <linux/iopoll.h>
#include <linux/mmc/sdio_func.h>
#include "mt7921.h"
#include "../mt76_connac2_mac.h"
#include "../sdio.h"
#include <linux/mmc/host.h>
#include <linux/kallsyms.h>

static void mt7921s_enable_irq(struct mt76_dev *dev)
{
        struct mt76_sdio *sdio = &dev->sdio;

        sdio_claim_host(sdio->func);
        sdio_writel(sdio->func, WHLPCR_INT_EN_SET, MCR_WHLPCR, NULL);
        sdio_release_host(sdio->func);
}

static void mt7921s_disable_irq(struct mt76_dev *dev)
{
        struct mt76_sdio *sdio = &dev->sdio;

        sdio_claim_host(sdio->func);
        sdio_writel(sdio->func, WHLPCR_INT_EN_CLR, MCR_WHLPCR, NULL);
        sdio_release_host(sdio->func);
}

static u32 mt7921s_read_whcr(struct mt76_dev *dev)
{
        return sdio_readl(dev->sdio.func, MCR_WHCR, NULL);
}

int mt7921s_wfsys_reset(struct mt792x_dev *dev)
{
        struct mt76_sdio *sdio = &dev->mt76.sdio;
        u32 val, status;

        if (atomic_read(&dev->mt76.bus_hung))
                return 0;

        mt7921s_mcu_drv_pmctrl(dev);

        sdio_claim_host(sdio->func);

        val = sdio_readl(sdio->func, MCR_WHCR, NULL);
        val &= ~WF_WHOLE_PATH_RSTB;
        sdio_writel(sdio->func, val, MCR_WHCR, NULL);

        msleep(50);

        val = sdio_readl(sdio->func, MCR_WHCR, NULL);
        val &= ~WF_SDIO_WF_PATH_RSTB;
        sdio_writel(sdio->func, val, MCR_WHCR, NULL);

        usleep_range(1000, 2000);

        val = sdio_readl(sdio->func, MCR_WHCR, NULL);
        val |= WF_WHOLE_PATH_RSTB;
        sdio_writel(sdio->func, val, MCR_WHCR, NULL);

        readx_poll_timeout(mt7921s_read_whcr, &dev->mt76, status,
                           status & WF_RST_DONE, 50000, 2000000);

        sdio_release_host(sdio->func);

        clear_bit(MT76_STATE_MCU_RUNNING, &dev->mphy.state);

        /* activate mt7921s again */
        mt7921s_mcu_drv_pmctrl(dev);
        mt76_clear(dev, MT_CONN_STATUS, MT_WIFI_PATCH_DL_STATE);
        mt7921s_mcu_fw_pmctrl(dev);
        mt7921s_mcu_drv_pmctrl(dev);

        return 0;
}

int mt7921s_init_reset(struct mt792x_dev *dev)
{
        set_bit(MT76_MCU_RESET, &dev->mphy.state);

        wake_up(&dev->mt76.mcu.wait);
        skb_queue_purge(&dev->mt76.mcu.res_q);
        wait_event_timeout(dev->mt76.sdio.wait,
                           mt76s_txqs_empty(&dev->mt76), 5 * HZ);
        mt76_worker_disable(&dev->mt76.sdio.txrx_worker);

        mt7921s_disable_irq(&dev->mt76);
        mt7921s_wfsys_reset(dev);

        mt76_worker_enable(&dev->mt76.sdio.txrx_worker);
        clear_bit(MT76_MCU_RESET, &dev->mphy.state);
        mt7921s_enable_irq(&dev->mt76);

        return 0;
}

static struct mt76_sdio *msdio;
static void mt7921s_card_reset(struct work_struct *work)
{
        struct mmc_host *sdio_host = msdio->func->card->host;

        sdio_claim_host(msdio->func);
        sdio_release_irq(msdio->func);
        sdio_release_host(msdio->func);

        mmc_remove_host(sdio_host);
        msleep(50);
        mmc_add_host(sdio_host);
}

static DECLARE_WORK(sdio_reset_work, mt7921s_card_reset);
static int mt7921s_check_bus(struct mt76_dev *dev)
{
        struct mt76_sdio *sdio = &dev->sdio;
        int err;

        sdio_claim_host(sdio->func);
        sdio_readl(dev->sdio.func, MCR_WHCR, &err);
        sdio_release_host(sdio->func);

        return err;
}

static int mt7921s_host_reset(struct mt792x_dev *dev)
{
        struct mt76_dev *mdev = &dev->mt76;
        int err = -1;

        if (!atomic_read(&mdev->bus_hung))
                err = mt7921s_check_bus(&dev->mt76);

        if (err) {
                atomic_set(&mdev->bus_hung, true);
                msdio = &dev->mt76.sdio;
                dev_err(mdev->dev, "SDIO bus problem detected(%d), resetting card!!\n", err);
                schedule_work(&sdio_reset_work);
                return err;
        }

        atomic_set(&mdev->bus_hung, false);

        return 0;
}

int mt7921s_mac_reset(struct mt792x_dev *dev)
{
        int err;

        mt76_connac_free_pending_tx_skbs(&dev->pm, NULL);

        mt7921s_host_reset(dev);
        if (atomic_read(&dev->mt76.bus_hung))
                return 0;

        mt76_txq_schedule_all(&dev->mphy);
        mt76_worker_disable(&dev->mt76.tx_worker);
        set_bit(MT76_MCU_RESET, &dev->mphy.state);
        wake_up(&dev->mt76.mcu.wait);
        skb_queue_purge(&dev->mt76.mcu.res_q);
        wait_event_timeout(dev->mt76.sdio.wait,
                           mt76s_txqs_empty(&dev->mt76), 5 * HZ);
        mt76_worker_disable(&dev->mt76.sdio.txrx_worker);
        mt76_worker_disable(&dev->mt76.sdio.status_worker);
        mt76_worker_disable(&dev->mt76.sdio.net_worker);
        mt76_worker_disable(&dev->mt76.sdio.stat_worker);

        mt7921s_disable_irq(&dev->mt76);
        mt7921s_wfsys_reset(dev);

        mt76_worker_enable(&dev->mt76.sdio.txrx_worker);
        mt76_worker_enable(&dev->mt76.sdio.status_worker);
        mt76_worker_enable(&dev->mt76.sdio.net_worker);
        mt76_worker_enable(&dev->mt76.sdio.stat_worker);

        dev->fw_assert = false;
        clear_bit(MT76_MCU_RESET, &dev->mphy.state);
        mt7921s_enable_irq(&dev->mt76);

        err = mt7921_run_firmware(dev);
        if (err)
                goto out;

        err = mt7921_mcu_set_eeprom(dev);
        if (err)
                goto out;

        err = mt7921_mac_init(dev);
        if (err)
                goto out;

        err = __mt7921_start(&dev->phy);
out:

        mt76_worker_enable(&dev->mt76.tx_worker);

        return err;
}