d785ed945d
WWAN device is programmed to boot in normal mode or fastboot mode, when triggering a device reset through ACPI call or fastboot switch command. Maintain state machine synchronization and reprobe logic after a device reset. The PCIe device reset triggered by several ways. E.g.: - fastboot: echo "fastboot_switching" > /sys/bus/pci/devices/${bdf}/t7xx_mode. - reset: echo "reset" > /sys/bus/pci/devices/${bdf}/t7xx_mode. - IRQ: PCIe device request driver to reset itself by an interrupt request. Use pci_reset_function() as a generic way to reset device, save and restore the PCIe configuration before and after reset device to ensure the reprobe process. Suggestion from Bjorn: Link: https://lore.kernel.org/all/20230127133034.GA1364550@bhelgaas/ Signed-off-by: Jinjian Song <jinjian.song@fibocom.com> Signed-off-by: David S. Miller <davem@davemloft.net>
907 lines
23 KiB
C
907 lines
23 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2021, MediaTek Inc.
|
|
* Copyright (c) 2021-2022, Intel Corporation.
|
|
*
|
|
* Authors:
|
|
* Haijun Liu <haijun.liu@mediatek.com>
|
|
* Ricardo Martinez <ricardo.martinez@linux.intel.com>
|
|
* Sreehari Kancharla <sreehari.kancharla@intel.com>
|
|
*
|
|
* Contributors:
|
|
* Amir Hanania <amir.hanania@intel.com>
|
|
* Andy Shevchenko <andriy.shevchenko@linux.intel.com>
|
|
* Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com>
|
|
* Eliot Lee <eliot.lee@intel.com>
|
|
* Moises Veleta <moises.veleta@intel.com>
|
|
*/
|
|
|
|
#include <linux/atomic.h>
|
|
#include <linux/bits.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/device.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/gfp.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/list.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/pm_wakeup.h>
|
|
#include <linux/spinlock.h>
|
|
|
|
#include "t7xx_mhccif.h"
|
|
#include "t7xx_modem_ops.h"
|
|
#include "t7xx_pci.h"
|
|
#include "t7xx_pcie_mac.h"
|
|
#include "t7xx_reg.h"
|
|
#include "t7xx_state_monitor.h"
|
|
|
|
#define T7XX_PCI_IREG_BASE 0
|
|
#define T7XX_PCI_EREG_BASE 2
|
|
|
|
#define T7XX_INIT_TIMEOUT 20
|
|
#define PM_SLEEP_DIS_TIMEOUT_MS 20
|
|
#define PM_ACK_TIMEOUT_MS 1500
|
|
#define PM_AUTOSUSPEND_MS 20000
|
|
#define PM_RESOURCE_POLL_TIMEOUT_US 10000
|
|
#define PM_RESOURCE_POLL_STEP_US 100
|
|
|
|
static const char * const t7xx_mode_names[] = {
|
|
[T7XX_UNKNOWN] = "unknown",
|
|
[T7XX_READY] = "ready",
|
|
[T7XX_RESET] = "reset",
|
|
[T7XX_FASTBOOT_SWITCHING] = "fastboot_switching",
|
|
[T7XX_FASTBOOT_DOWNLOAD] = "fastboot_download",
|
|
[T7XX_FASTBOOT_DUMP] = "fastboot_dump",
|
|
};
|
|
|
|
static_assert(ARRAY_SIZE(t7xx_mode_names) == T7XX_MODE_LAST);
|
|
|
|
static ssize_t t7xx_mode_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct t7xx_pci_dev *t7xx_dev;
|
|
struct pci_dev *pdev;
|
|
enum t7xx_mode mode;
|
|
int index = 0;
|
|
|
|
pdev = to_pci_dev(dev);
|
|
t7xx_dev = pci_get_drvdata(pdev);
|
|
if (!t7xx_dev)
|
|
return -ENODEV;
|
|
|
|
mode = READ_ONCE(t7xx_dev->mode);
|
|
|
|
index = sysfs_match_string(t7xx_mode_names, buf);
|
|
if (index == mode)
|
|
return -EBUSY;
|
|
|
|
if (index == T7XX_FASTBOOT_SWITCHING) {
|
|
if (mode == T7XX_FASTBOOT_DOWNLOAD)
|
|
return count;
|
|
|
|
WRITE_ONCE(t7xx_dev->mode, T7XX_FASTBOOT_SWITCHING);
|
|
pm_runtime_resume(dev);
|
|
t7xx_reset_device(t7xx_dev, FASTBOOT);
|
|
} else if (index == T7XX_RESET) {
|
|
pm_runtime_resume(dev);
|
|
t7xx_reset_device(t7xx_dev, PLDR);
|
|
}
|
|
|
|
return count;
|
|
};
|
|
|
|
static ssize_t t7xx_mode_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
enum t7xx_mode mode = T7XX_UNKNOWN;
|
|
struct t7xx_pci_dev *t7xx_dev;
|
|
struct pci_dev *pdev;
|
|
|
|
pdev = to_pci_dev(dev);
|
|
t7xx_dev = pci_get_drvdata(pdev);
|
|
if (!t7xx_dev)
|
|
return -ENODEV;
|
|
|
|
mode = READ_ONCE(t7xx_dev->mode);
|
|
if (mode < T7XX_MODE_LAST)
|
|
return sysfs_emit(buf, "%s\n", t7xx_mode_names[mode]);
|
|
|
|
return sysfs_emit(buf, "%s\n", t7xx_mode_names[T7XX_UNKNOWN]);
|
|
}
|
|
|
|
static DEVICE_ATTR_RW(t7xx_mode);
|
|
|
|
static struct attribute *t7xx_mode_attr[] = {
|
|
&dev_attr_t7xx_mode.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group t7xx_mode_attribute_group = {
|
|
.attrs = t7xx_mode_attr,
|
|
};
|
|
|
|
void t7xx_mode_update(struct t7xx_pci_dev *t7xx_dev, enum t7xx_mode mode)
|
|
{
|
|
if (!t7xx_dev)
|
|
return;
|
|
|
|
WRITE_ONCE(t7xx_dev->mode, mode);
|
|
sysfs_notify(&t7xx_dev->pdev->dev.kobj, NULL, "t7xx_mode");
|
|
}
|
|
|
|
enum t7xx_pm_state {
|
|
MTK_PM_EXCEPTION,
|
|
MTK_PM_INIT, /* Device initialized, but handshake not completed */
|
|
MTK_PM_SUSPENDED,
|
|
MTK_PM_RESUMED,
|
|
};
|
|
|
|
static void t7xx_dev_set_sleep_capability(struct t7xx_pci_dev *t7xx_dev, bool enable)
|
|
{
|
|
void __iomem *ctrl_reg = IREG_BASE(t7xx_dev) + T7XX_PCIE_MISC_CTRL;
|
|
u32 value;
|
|
|
|
value = ioread32(ctrl_reg);
|
|
|
|
if (enable)
|
|
value &= ~T7XX_PCIE_MISC_MAC_SLEEP_DIS;
|
|
else
|
|
value |= T7XX_PCIE_MISC_MAC_SLEEP_DIS;
|
|
|
|
iowrite32(value, ctrl_reg);
|
|
}
|
|
|
|
static int t7xx_wait_pm_config(struct t7xx_pci_dev *t7xx_dev)
|
|
{
|
|
int ret, val;
|
|
|
|
ret = read_poll_timeout(ioread32, val,
|
|
(val & T7XX_PCIE_RESOURCE_STS_MSK) == T7XX_PCIE_RESOURCE_STS_MSK,
|
|
PM_RESOURCE_POLL_STEP_US, PM_RESOURCE_POLL_TIMEOUT_US, true,
|
|
IREG_BASE(t7xx_dev) + T7XX_PCIE_RESOURCE_STATUS);
|
|
if (ret == -ETIMEDOUT)
|
|
dev_err(&t7xx_dev->pdev->dev, "PM configuration timed out\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int t7xx_pci_pm_init(struct t7xx_pci_dev *t7xx_dev)
|
|
{
|
|
struct pci_dev *pdev = t7xx_dev->pdev;
|
|
|
|
INIT_LIST_HEAD(&t7xx_dev->md_pm_entities);
|
|
mutex_init(&t7xx_dev->md_pm_entity_mtx);
|
|
spin_lock_init(&t7xx_dev->md_pm_lock);
|
|
init_completion(&t7xx_dev->sleep_lock_acquire);
|
|
init_completion(&t7xx_dev->pm_sr_ack);
|
|
init_completion(&t7xx_dev->init_done);
|
|
atomic_set(&t7xx_dev->md_pm_state, MTK_PM_INIT);
|
|
|
|
device_init_wakeup(&pdev->dev, true);
|
|
dev_pm_set_driver_flags(&pdev->dev, pdev->dev.power.driver_flags |
|
|
DPM_FLAG_NO_DIRECT_COMPLETE);
|
|
|
|
iowrite32(T7XX_L1_BIT(0), IREG_BASE(t7xx_dev) + DISABLE_ASPM_LOWPWR);
|
|
pm_runtime_set_autosuspend_delay(&pdev->dev, PM_AUTOSUSPEND_MS);
|
|
pm_runtime_use_autosuspend(&pdev->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void t7xx_pci_pm_init_late(struct t7xx_pci_dev *t7xx_dev)
|
|
{
|
|
/* Enable the PCIe resource lock only after MD deep sleep is done */
|
|
t7xx_mhccif_mask_clr(t7xx_dev,
|
|
D2H_INT_DS_LOCK_ACK |
|
|
D2H_INT_SUSPEND_ACK |
|
|
D2H_INT_RESUME_ACK |
|
|
D2H_INT_SUSPEND_ACK_AP |
|
|
D2H_INT_RESUME_ACK_AP);
|
|
iowrite32(T7XX_L1_BIT(0), IREG_BASE(t7xx_dev) + ENABLE_ASPM_LOWPWR);
|
|
atomic_set(&t7xx_dev->md_pm_state, MTK_PM_RESUMED);
|
|
|
|
pm_runtime_mark_last_busy(&t7xx_dev->pdev->dev);
|
|
pm_runtime_allow(&t7xx_dev->pdev->dev);
|
|
pm_runtime_put_noidle(&t7xx_dev->pdev->dev);
|
|
complete_all(&t7xx_dev->init_done);
|
|
}
|
|
|
|
static int t7xx_pci_pm_reinit(struct t7xx_pci_dev *t7xx_dev)
|
|
{
|
|
/* The device is kept in FSM re-init flow
|
|
* so just roll back PM setting to the init setting.
|
|
*/
|
|
atomic_set(&t7xx_dev->md_pm_state, MTK_PM_INIT);
|
|
|
|
pm_runtime_get_noresume(&t7xx_dev->pdev->dev);
|
|
|
|
iowrite32(T7XX_L1_BIT(0), IREG_BASE(t7xx_dev) + DISABLE_ASPM_LOWPWR);
|
|
return t7xx_wait_pm_config(t7xx_dev);
|
|
}
|
|
|
|
void t7xx_pci_pm_exp_detected(struct t7xx_pci_dev *t7xx_dev)
|
|
{
|
|
iowrite32(T7XX_L1_BIT(0), IREG_BASE(t7xx_dev) + DISABLE_ASPM_LOWPWR);
|
|
t7xx_wait_pm_config(t7xx_dev);
|
|
atomic_set(&t7xx_dev->md_pm_state, MTK_PM_EXCEPTION);
|
|
}
|
|
|
|
int t7xx_pci_pm_entity_register(struct t7xx_pci_dev *t7xx_dev, struct md_pm_entity *pm_entity)
|
|
{
|
|
struct md_pm_entity *entity;
|
|
|
|
mutex_lock(&t7xx_dev->md_pm_entity_mtx);
|
|
list_for_each_entry(entity, &t7xx_dev->md_pm_entities, entity) {
|
|
if (entity->id == pm_entity->id) {
|
|
mutex_unlock(&t7xx_dev->md_pm_entity_mtx);
|
|
return -EEXIST;
|
|
}
|
|
}
|
|
|
|
list_add_tail(&pm_entity->entity, &t7xx_dev->md_pm_entities);
|
|
mutex_unlock(&t7xx_dev->md_pm_entity_mtx);
|
|
return 0;
|
|
}
|
|
|
|
int t7xx_pci_pm_entity_unregister(struct t7xx_pci_dev *t7xx_dev, struct md_pm_entity *pm_entity)
|
|
{
|
|
struct md_pm_entity *entity, *tmp_entity;
|
|
|
|
mutex_lock(&t7xx_dev->md_pm_entity_mtx);
|
|
list_for_each_entry_safe(entity, tmp_entity, &t7xx_dev->md_pm_entities, entity) {
|
|
if (entity->id == pm_entity->id) {
|
|
list_del(&pm_entity->entity);
|
|
mutex_unlock(&t7xx_dev->md_pm_entity_mtx);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&t7xx_dev->md_pm_entity_mtx);
|
|
|
|
return -ENXIO;
|
|
}
|
|
|
|
int t7xx_pci_sleep_disable_complete(struct t7xx_pci_dev *t7xx_dev)
|
|
{
|
|
struct device *dev = &t7xx_dev->pdev->dev;
|
|
int ret;
|
|
|
|
ret = wait_for_completion_timeout(&t7xx_dev->sleep_lock_acquire,
|
|
msecs_to_jiffies(PM_SLEEP_DIS_TIMEOUT_MS));
|
|
if (!ret)
|
|
dev_err_ratelimited(dev, "Resource wait complete timed out\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* t7xx_pci_disable_sleep() - Disable deep sleep capability.
|
|
* @t7xx_dev: MTK device.
|
|
*
|
|
* Lock the deep sleep capability, note that the device can still go into deep sleep
|
|
* state while device is in D0 state, from the host's point-of-view.
|
|
*
|
|
* If device is in deep sleep state, wake up the device and disable deep sleep capability.
|
|
*/
|
|
void t7xx_pci_disable_sleep(struct t7xx_pci_dev *t7xx_dev)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&t7xx_dev->md_pm_lock, flags);
|
|
t7xx_dev->sleep_disable_count++;
|
|
if (atomic_read(&t7xx_dev->md_pm_state) < MTK_PM_RESUMED)
|
|
goto unlock_and_complete;
|
|
|
|
if (t7xx_dev->sleep_disable_count == 1) {
|
|
u32 status;
|
|
|
|
reinit_completion(&t7xx_dev->sleep_lock_acquire);
|
|
t7xx_dev_set_sleep_capability(t7xx_dev, false);
|
|
|
|
status = ioread32(IREG_BASE(t7xx_dev) + T7XX_PCIE_RESOURCE_STATUS);
|
|
if (status & T7XX_PCIE_RESOURCE_STS_MSK)
|
|
goto unlock_and_complete;
|
|
|
|
t7xx_mhccif_h2d_swint_trigger(t7xx_dev, H2D_CH_DS_LOCK);
|
|
}
|
|
spin_unlock_irqrestore(&t7xx_dev->md_pm_lock, flags);
|
|
return;
|
|
|
|
unlock_and_complete:
|
|
spin_unlock_irqrestore(&t7xx_dev->md_pm_lock, flags);
|
|
complete_all(&t7xx_dev->sleep_lock_acquire);
|
|
}
|
|
|
|
/**
|
|
* t7xx_pci_enable_sleep() - Enable deep sleep capability.
|
|
* @t7xx_dev: MTK device.
|
|
*
|
|
* After enabling deep sleep, device can enter into deep sleep state.
|
|
*/
|
|
void t7xx_pci_enable_sleep(struct t7xx_pci_dev *t7xx_dev)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&t7xx_dev->md_pm_lock, flags);
|
|
t7xx_dev->sleep_disable_count--;
|
|
if (atomic_read(&t7xx_dev->md_pm_state) < MTK_PM_RESUMED)
|
|
goto unlock;
|
|
|
|
if (t7xx_dev->sleep_disable_count == 0)
|
|
t7xx_dev_set_sleep_capability(t7xx_dev, true);
|
|
|
|
unlock:
|
|
spin_unlock_irqrestore(&t7xx_dev->md_pm_lock, flags);
|
|
}
|
|
|
|
static int t7xx_send_pm_request(struct t7xx_pci_dev *t7xx_dev, u32 request)
|
|
{
|
|
unsigned long wait_ret;
|
|
|
|
reinit_completion(&t7xx_dev->pm_sr_ack);
|
|
t7xx_mhccif_h2d_swint_trigger(t7xx_dev, request);
|
|
wait_ret = wait_for_completion_timeout(&t7xx_dev->pm_sr_ack,
|
|
msecs_to_jiffies(PM_ACK_TIMEOUT_MS));
|
|
if (!wait_ret)
|
|
return -ETIMEDOUT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __t7xx_pci_pm_suspend(struct pci_dev *pdev)
|
|
{
|
|
enum t7xx_pm_id entity_id = PM_ENTITY_ID_INVALID;
|
|
struct t7xx_pci_dev *t7xx_dev;
|
|
struct md_pm_entity *entity;
|
|
int ret;
|
|
|
|
t7xx_dev = pci_get_drvdata(pdev);
|
|
if (atomic_read(&t7xx_dev->md_pm_state) <= MTK_PM_INIT ||
|
|
READ_ONCE(t7xx_dev->mode) != T7XX_READY) {
|
|
dev_err(&pdev->dev, "[PM] Exiting suspend, modem in invalid state\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
iowrite32(T7XX_L1_BIT(0), IREG_BASE(t7xx_dev) + DISABLE_ASPM_LOWPWR);
|
|
ret = t7xx_wait_pm_config(t7xx_dev);
|
|
if (ret) {
|
|
iowrite32(T7XX_L1_BIT(0), IREG_BASE(t7xx_dev) + ENABLE_ASPM_LOWPWR);
|
|
return ret;
|
|
}
|
|
|
|
atomic_set(&t7xx_dev->md_pm_state, MTK_PM_SUSPENDED);
|
|
t7xx_pcie_mac_clear_int(t7xx_dev, SAP_RGU_INT);
|
|
t7xx_dev->rgu_pci_irq_en = false;
|
|
|
|
list_for_each_entry(entity, &t7xx_dev->md_pm_entities, entity) {
|
|
if (!entity->suspend)
|
|
continue;
|
|
|
|
ret = entity->suspend(t7xx_dev, entity->entity_param);
|
|
if (ret) {
|
|
entity_id = entity->id;
|
|
dev_err(&pdev->dev, "[PM] Suspend error: %d, id: %d\n", ret, entity_id);
|
|
goto abort_suspend;
|
|
}
|
|
}
|
|
|
|
ret = t7xx_send_pm_request(t7xx_dev, H2D_CH_SUSPEND_REQ);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "[PM] MD suspend error: %d\n", ret);
|
|
goto abort_suspend;
|
|
}
|
|
|
|
ret = t7xx_send_pm_request(t7xx_dev, H2D_CH_SUSPEND_REQ_AP);
|
|
if (ret) {
|
|
t7xx_send_pm_request(t7xx_dev, H2D_CH_RESUME_REQ);
|
|
dev_err(&pdev->dev, "[PM] SAP suspend error: %d\n", ret);
|
|
goto abort_suspend;
|
|
}
|
|
|
|
list_for_each_entry(entity, &t7xx_dev->md_pm_entities, entity) {
|
|
if (entity->suspend_late)
|
|
entity->suspend_late(t7xx_dev, entity->entity_param);
|
|
}
|
|
|
|
iowrite32(T7XX_L1_BIT(0), IREG_BASE(t7xx_dev) + ENABLE_ASPM_LOWPWR);
|
|
return 0;
|
|
|
|
abort_suspend:
|
|
list_for_each_entry(entity, &t7xx_dev->md_pm_entities, entity) {
|
|
if (entity_id == entity->id)
|
|
break;
|
|
|
|
if (entity->resume)
|
|
entity->resume(t7xx_dev, entity->entity_param);
|
|
}
|
|
|
|
iowrite32(T7XX_L1_BIT(0), IREG_BASE(t7xx_dev) + ENABLE_ASPM_LOWPWR);
|
|
atomic_set(&t7xx_dev->md_pm_state, MTK_PM_RESUMED);
|
|
t7xx_pcie_mac_set_int(t7xx_dev, SAP_RGU_INT);
|
|
return ret;
|
|
}
|
|
|
|
static void t7xx_pcie_interrupt_reinit(struct t7xx_pci_dev *t7xx_dev)
|
|
{
|
|
t7xx_pcie_set_mac_msix_cfg(t7xx_dev, EXT_INT_NUM);
|
|
|
|
/* Disable interrupt first and let the IPs enable them */
|
|
iowrite32(MSIX_MSK_SET_ALL, IREG_BASE(t7xx_dev) + IMASK_HOST_MSIX_CLR_GRP0_0);
|
|
|
|
/* Device disables PCIe interrupts during resume and
|
|
* following function will re-enable PCIe interrupts.
|
|
*/
|
|
t7xx_pcie_mac_interrupts_en(t7xx_dev);
|
|
t7xx_pcie_mac_set_int(t7xx_dev, MHCCIF_INT);
|
|
}
|
|
|
|
static int t7xx_pcie_reinit(struct t7xx_pci_dev *t7xx_dev, bool is_d3)
|
|
{
|
|
int ret;
|
|
|
|
ret = pcim_enable_device(t7xx_dev->pdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
t7xx_pcie_mac_atr_init(t7xx_dev);
|
|
t7xx_pcie_interrupt_reinit(t7xx_dev);
|
|
|
|
if (is_d3) {
|
|
t7xx_mhccif_init(t7xx_dev);
|
|
t7xx_pci_pm_reinit(t7xx_dev);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int t7xx_send_fsm_command(struct t7xx_pci_dev *t7xx_dev, u32 event)
|
|
{
|
|
struct t7xx_fsm_ctl *fsm_ctl = t7xx_dev->md->fsm_ctl;
|
|
struct device *dev = &t7xx_dev->pdev->dev;
|
|
int ret = -EINVAL;
|
|
|
|
switch (event) {
|
|
case FSM_CMD_STOP:
|
|
ret = t7xx_fsm_append_cmd(fsm_ctl, FSM_CMD_STOP, FSM_CMD_FLAG_WAIT_FOR_COMPLETION);
|
|
break;
|
|
|
|
case FSM_CMD_START:
|
|
t7xx_pcie_mac_clear_int(t7xx_dev, SAP_RGU_INT);
|
|
t7xx_pcie_mac_clear_int_status(t7xx_dev, SAP_RGU_INT);
|
|
t7xx_dev->rgu_pci_irq_en = true;
|
|
t7xx_pcie_mac_set_int(t7xx_dev, SAP_RGU_INT);
|
|
ret = t7xx_fsm_append_cmd(fsm_ctl, FSM_CMD_START, 0);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (ret)
|
|
dev_err(dev, "Failure handling FSM command %u, %d\n", event, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int t7xx_pci_reprobe_early(struct t7xx_pci_dev *t7xx_dev)
|
|
{
|
|
enum t7xx_mode mode = READ_ONCE(t7xx_dev->mode);
|
|
int ret;
|
|
|
|
if (mode == T7XX_FASTBOOT_DOWNLOAD)
|
|
pm_runtime_put_noidle(&t7xx_dev->pdev->dev);
|
|
|
|
ret = t7xx_send_fsm_command(t7xx_dev, FSM_CMD_STOP);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int t7xx_pci_reprobe(struct t7xx_pci_dev *t7xx_dev, bool boot)
|
|
{
|
|
int ret;
|
|
|
|
ret = t7xx_pcie_reinit(t7xx_dev, boot);
|
|
if (ret)
|
|
return ret;
|
|
|
|
t7xx_clear_rgu_irq(t7xx_dev);
|
|
return t7xx_send_fsm_command(t7xx_dev, FSM_CMD_START);
|
|
}
|
|
|
|
static int __t7xx_pci_pm_resume(struct pci_dev *pdev, bool state_check)
|
|
{
|
|
struct t7xx_pci_dev *t7xx_dev;
|
|
struct md_pm_entity *entity;
|
|
u32 prev_state;
|
|
int ret = 0;
|
|
|
|
t7xx_dev = pci_get_drvdata(pdev);
|
|
if (atomic_read(&t7xx_dev->md_pm_state) <= MTK_PM_INIT) {
|
|
iowrite32(T7XX_L1_BIT(0), IREG_BASE(t7xx_dev) + ENABLE_ASPM_LOWPWR);
|
|
return 0;
|
|
}
|
|
|
|
t7xx_pcie_mac_interrupts_en(t7xx_dev);
|
|
prev_state = ioread32(IREG_BASE(t7xx_dev) + T7XX_PCIE_PM_RESUME_STATE);
|
|
|
|
if (state_check) {
|
|
/* For D3/L3 resume, the device could boot so quickly that the
|
|
* initial value of the dummy register might be overwritten.
|
|
* Identify new boots if the ATR source address register is not initialized.
|
|
*/
|
|
u32 atr_reg_val = ioread32(IREG_BASE(t7xx_dev) +
|
|
ATR_PCIE_WIN0_T0_ATR_PARAM_SRC_ADDR);
|
|
if (prev_state == PM_RESUME_REG_STATE_L3 ||
|
|
(prev_state == PM_RESUME_REG_STATE_INIT &&
|
|
atr_reg_val == ATR_SRC_ADDR_INVALID)) {
|
|
ret = t7xx_pci_reprobe_early(t7xx_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return t7xx_pci_reprobe(t7xx_dev, true);
|
|
}
|
|
|
|
if (prev_state == PM_RESUME_REG_STATE_EXP ||
|
|
prev_state == PM_RESUME_REG_STATE_L2_EXP) {
|
|
if (prev_state == PM_RESUME_REG_STATE_L2_EXP) {
|
|
ret = t7xx_pcie_reinit(t7xx_dev, false);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
atomic_set(&t7xx_dev->md_pm_state, MTK_PM_SUSPENDED);
|
|
t7xx_dev->rgu_pci_irq_en = true;
|
|
t7xx_pcie_mac_set_int(t7xx_dev, SAP_RGU_INT);
|
|
|
|
t7xx_mhccif_mask_clr(t7xx_dev,
|
|
D2H_INT_EXCEPTION_INIT |
|
|
D2H_INT_EXCEPTION_INIT_DONE |
|
|
D2H_INT_EXCEPTION_CLEARQ_DONE |
|
|
D2H_INT_EXCEPTION_ALLQ_RESET |
|
|
D2H_INT_PORT_ENUM);
|
|
|
|
return ret;
|
|
}
|
|
|
|
if (prev_state == PM_RESUME_REG_STATE_L2) {
|
|
ret = t7xx_pcie_reinit(t7xx_dev, false);
|
|
if (ret)
|
|
return ret;
|
|
|
|
} else if (prev_state != PM_RESUME_REG_STATE_L1 &&
|
|
prev_state != PM_RESUME_REG_STATE_INIT) {
|
|
ret = t7xx_send_fsm_command(t7xx_dev, FSM_CMD_STOP);
|
|
if (ret)
|
|
return ret;
|
|
|
|
t7xx_clear_rgu_irq(t7xx_dev);
|
|
atomic_set(&t7xx_dev->md_pm_state, MTK_PM_SUSPENDED);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
iowrite32(T7XX_L1_BIT(0), IREG_BASE(t7xx_dev) + DISABLE_ASPM_LOWPWR);
|
|
t7xx_wait_pm_config(t7xx_dev);
|
|
|
|
list_for_each_entry(entity, &t7xx_dev->md_pm_entities, entity) {
|
|
if (entity->resume_early)
|
|
entity->resume_early(t7xx_dev, entity->entity_param);
|
|
}
|
|
|
|
ret = t7xx_send_pm_request(t7xx_dev, H2D_CH_RESUME_REQ);
|
|
if (ret)
|
|
dev_err(&pdev->dev, "[PM] MD resume error: %d\n", ret);
|
|
|
|
ret = t7xx_send_pm_request(t7xx_dev, H2D_CH_RESUME_REQ_AP);
|
|
if (ret)
|
|
dev_err(&pdev->dev, "[PM] SAP resume error: %d\n", ret);
|
|
|
|
list_for_each_entry(entity, &t7xx_dev->md_pm_entities, entity) {
|
|
if (entity->resume) {
|
|
ret = entity->resume(t7xx_dev, entity->entity_param);
|
|
if (ret)
|
|
dev_err(&pdev->dev, "[PM] Resume entry ID: %d error: %d\n",
|
|
entity->id, ret);
|
|
}
|
|
}
|
|
|
|
t7xx_dev->rgu_pci_irq_en = true;
|
|
t7xx_pcie_mac_set_int(t7xx_dev, SAP_RGU_INT);
|
|
iowrite32(T7XX_L1_BIT(0), IREG_BASE(t7xx_dev) + ENABLE_ASPM_LOWPWR);
|
|
pm_runtime_mark_last_busy(&pdev->dev);
|
|
atomic_set(&t7xx_dev->md_pm_state, MTK_PM_RESUMED);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int t7xx_pci_pm_resume_noirq(struct device *dev)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(dev);
|
|
struct t7xx_pci_dev *t7xx_dev;
|
|
|
|
t7xx_dev = pci_get_drvdata(pdev);
|
|
t7xx_pcie_mac_interrupts_dis(t7xx_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void t7xx_pci_shutdown(struct pci_dev *pdev)
|
|
{
|
|
__t7xx_pci_pm_suspend(pdev);
|
|
}
|
|
|
|
static int t7xx_pci_pm_prepare(struct device *dev)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(dev);
|
|
struct t7xx_pci_dev *t7xx_dev;
|
|
|
|
t7xx_dev = pci_get_drvdata(pdev);
|
|
if (!wait_for_completion_timeout(&t7xx_dev->init_done, T7XX_INIT_TIMEOUT * HZ)) {
|
|
dev_warn(dev, "Not ready for system sleep.\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int t7xx_pci_pm_suspend(struct device *dev)
|
|
{
|
|
return __t7xx_pci_pm_suspend(to_pci_dev(dev));
|
|
}
|
|
|
|
static int t7xx_pci_pm_resume(struct device *dev)
|
|
{
|
|
return __t7xx_pci_pm_resume(to_pci_dev(dev), true);
|
|
}
|
|
|
|
static int t7xx_pci_pm_thaw(struct device *dev)
|
|
{
|
|
return __t7xx_pci_pm_resume(to_pci_dev(dev), false);
|
|
}
|
|
|
|
static int t7xx_pci_pm_runtime_suspend(struct device *dev)
|
|
{
|
|
return __t7xx_pci_pm_suspend(to_pci_dev(dev));
|
|
}
|
|
|
|
static int t7xx_pci_pm_runtime_resume(struct device *dev)
|
|
{
|
|
return __t7xx_pci_pm_resume(to_pci_dev(dev), true);
|
|
}
|
|
|
|
static const struct dev_pm_ops t7xx_pci_pm_ops = {
|
|
.prepare = t7xx_pci_pm_prepare,
|
|
.suspend = t7xx_pci_pm_suspend,
|
|
.resume = t7xx_pci_pm_resume,
|
|
.resume_noirq = t7xx_pci_pm_resume_noirq,
|
|
.freeze = t7xx_pci_pm_suspend,
|
|
.thaw = t7xx_pci_pm_thaw,
|
|
.poweroff = t7xx_pci_pm_suspend,
|
|
.restore = t7xx_pci_pm_resume,
|
|
.restore_noirq = t7xx_pci_pm_resume_noirq,
|
|
.runtime_suspend = t7xx_pci_pm_runtime_suspend,
|
|
.runtime_resume = t7xx_pci_pm_runtime_resume
|
|
};
|
|
|
|
static int t7xx_request_irq(struct pci_dev *pdev)
|
|
{
|
|
struct t7xx_pci_dev *t7xx_dev;
|
|
int ret = 0, i;
|
|
|
|
t7xx_dev = pci_get_drvdata(pdev);
|
|
|
|
for (i = 0; i < EXT_INT_NUM; i++) {
|
|
const char *irq_descr;
|
|
int irq_vec;
|
|
|
|
if (!t7xx_dev->intr_handler[i])
|
|
continue;
|
|
|
|
irq_descr = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s_%d",
|
|
dev_driver_string(&pdev->dev), i);
|
|
if (!irq_descr) {
|
|
ret = -ENOMEM;
|
|
break;
|
|
}
|
|
|
|
irq_vec = pci_irq_vector(pdev, i);
|
|
ret = request_threaded_irq(irq_vec, t7xx_dev->intr_handler[i],
|
|
t7xx_dev->intr_thread[i], 0, irq_descr,
|
|
t7xx_dev->callback_param[i]);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to request IRQ: %d\n", ret);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret) {
|
|
while (i--) {
|
|
if (!t7xx_dev->intr_handler[i])
|
|
continue;
|
|
|
|
free_irq(pci_irq_vector(pdev, i), t7xx_dev->callback_param[i]);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int t7xx_setup_msix(struct t7xx_pci_dev *t7xx_dev)
|
|
{
|
|
struct pci_dev *pdev = t7xx_dev->pdev;
|
|
int ret;
|
|
|
|
/* Only using 6 interrupts, but HW-design requires power-of-2 IRQs allocation */
|
|
ret = pci_alloc_irq_vectors(pdev, EXT_INT_NUM, EXT_INT_NUM, PCI_IRQ_MSIX);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "Failed to allocate MSI-X entry: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = t7xx_request_irq(pdev);
|
|
if (ret) {
|
|
pci_free_irq_vectors(pdev);
|
|
return ret;
|
|
}
|
|
|
|
t7xx_pcie_set_mac_msix_cfg(t7xx_dev, EXT_INT_NUM);
|
|
return 0;
|
|
}
|
|
|
|
static int t7xx_interrupt_init(struct t7xx_pci_dev *t7xx_dev)
|
|
{
|
|
int ret, i;
|
|
|
|
if (!t7xx_dev->pdev->msix_cap)
|
|
return -EINVAL;
|
|
|
|
ret = t7xx_setup_msix(t7xx_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* IPs enable interrupts when ready */
|
|
for (i = 0; i < EXT_INT_NUM; i++)
|
|
t7xx_pcie_mac_set_int(t7xx_dev, i);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void t7xx_pci_infracfg_ao_calc(struct t7xx_pci_dev *t7xx_dev)
|
|
{
|
|
t7xx_dev->base_addr.infracfg_ao_base = t7xx_dev->base_addr.pcie_ext_reg_base +
|
|
INFRACFG_AO_DEV_CHIP -
|
|
t7xx_dev->base_addr.pcie_dev_reg_trsl_addr;
|
|
}
|
|
|
|
static int t7xx_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
|
{
|
|
struct t7xx_pci_dev *t7xx_dev;
|
|
int ret;
|
|
|
|
t7xx_dev = devm_kzalloc(&pdev->dev, sizeof(*t7xx_dev), GFP_KERNEL);
|
|
if (!t7xx_dev)
|
|
return -ENOMEM;
|
|
|
|
pci_set_drvdata(pdev, t7xx_dev);
|
|
t7xx_dev->pdev = pdev;
|
|
|
|
ret = pcim_enable_device(pdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
pci_set_master(pdev);
|
|
|
|
ret = pcim_iomap_regions(pdev, BIT(T7XX_PCI_IREG_BASE) | BIT(T7XX_PCI_EREG_BASE),
|
|
pci_name(pdev));
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Could not request BARs: %d\n", ret);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64));
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Could not set PCI DMA mask: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64));
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Could not set consistent PCI DMA mask: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
IREG_BASE(t7xx_dev) = pcim_iomap_table(pdev)[T7XX_PCI_IREG_BASE];
|
|
t7xx_dev->base_addr.pcie_ext_reg_base = pcim_iomap_table(pdev)[T7XX_PCI_EREG_BASE];
|
|
|
|
ret = t7xx_pci_pm_init(t7xx_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
t7xx_pcie_mac_atr_init(t7xx_dev);
|
|
t7xx_pci_infracfg_ao_calc(t7xx_dev);
|
|
t7xx_mhccif_init(t7xx_dev);
|
|
|
|
ret = t7xx_md_init(t7xx_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
t7xx_pcie_mac_interrupts_dis(t7xx_dev);
|
|
|
|
ret = sysfs_create_group(&t7xx_dev->pdev->dev.kobj,
|
|
&t7xx_mode_attribute_group);
|
|
if (ret)
|
|
goto err_md_exit;
|
|
|
|
ret = t7xx_interrupt_init(t7xx_dev);
|
|
if (ret)
|
|
goto err_remove_group;
|
|
|
|
|
|
t7xx_pcie_mac_set_int(t7xx_dev, MHCCIF_INT);
|
|
t7xx_pcie_mac_interrupts_en(t7xx_dev);
|
|
|
|
return 0;
|
|
|
|
err_remove_group:
|
|
sysfs_remove_group(&t7xx_dev->pdev->dev.kobj,
|
|
&t7xx_mode_attribute_group);
|
|
|
|
err_md_exit:
|
|
t7xx_md_exit(t7xx_dev);
|
|
return ret;
|
|
}
|
|
|
|
static void t7xx_pci_remove(struct pci_dev *pdev)
|
|
{
|
|
struct t7xx_pci_dev *t7xx_dev;
|
|
int i;
|
|
|
|
t7xx_dev = pci_get_drvdata(pdev);
|
|
|
|
sysfs_remove_group(&t7xx_dev->pdev->dev.kobj,
|
|
&t7xx_mode_attribute_group);
|
|
t7xx_md_exit(t7xx_dev);
|
|
|
|
for (i = 0; i < EXT_INT_NUM; i++) {
|
|
if (!t7xx_dev->intr_handler[i])
|
|
continue;
|
|
|
|
free_irq(pci_irq_vector(pdev, i), t7xx_dev->callback_param[i]);
|
|
}
|
|
|
|
pci_free_irq_vectors(t7xx_dev->pdev);
|
|
}
|
|
|
|
static const struct pci_device_id t7xx_pci_table[] = {
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x4d75) },
|
|
{ PCI_DEVICE(0x14c0, 0x4d75) }, // Dell DW5933e
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(pci, t7xx_pci_table);
|
|
|
|
static struct pci_driver t7xx_pci_driver = {
|
|
.name = "mtk_t7xx",
|
|
.id_table = t7xx_pci_table,
|
|
.probe = t7xx_pci_probe,
|
|
.remove = t7xx_pci_remove,
|
|
.driver.pm = &t7xx_pci_pm_ops,
|
|
.shutdown = t7xx_pci_shutdown,
|
|
};
|
|
|
|
module_pci_driver(t7xx_pci_driver);
|
|
|
|
MODULE_AUTHOR("MediaTek Inc");
|
|
MODULE_DESCRIPTION("MediaTek PCIe 5G WWAN modem T7xx driver");
|
|
MODULE_LICENSE("GPL");
|