linux/drivers/thunderbolt/nhi_ops.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * NHI specific operations
   4 *
   5 * Copyright (C) 2019, Intel Corporation
   6 * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
   7 */
   8
   9#include <linux/delay.h>
  10#include <linux/suspend.h>
  11
  12#include "nhi.h"
  13#include "nhi_regs.h"
  14#include "tb.h"
  15
  16/* Ice Lake specific NHI operations */
  17
  18#define ICL_LC_MAILBOX_TIMEOUT  500 /* ms */
  19
  20static int check_for_device(struct device *dev, void *data)
  21{
  22        return tb_is_switch(dev);
  23}
  24
  25static bool icl_nhi_is_device_connected(struct tb_nhi *nhi)
  26{
  27        struct tb *tb = pci_get_drvdata(nhi->pdev);
  28        int ret;
  29
  30        ret = device_for_each_child(&tb->root_switch->dev, NULL,
  31                                    check_for_device);
  32        return ret > 0;
  33}
  34
  35static int icl_nhi_force_power(struct tb_nhi *nhi, bool power)
  36{
  37        u32 vs_cap;
  38
  39        /*
  40         * The Thunderbolt host controller is present always in Ice Lake
  41         * but the firmware may not be loaded and running (depending
  42         * whether there is device connected and so on). Each time the
  43         * controller is used we need to "Force Power" it first and wait
  44         * for the firmware to indicate it is up and running. This "Force
  45         * Power" is really not about actually powering on/off the
  46         * controller so it is accessible even if "Force Power" is off.
  47         *
  48         * The actual power management happens inside shared ACPI power
  49         * resources using standard ACPI methods.
  50         */
  51        pci_read_config_dword(nhi->pdev, VS_CAP_22, &vs_cap);
  52        if (power) {
  53                vs_cap &= ~VS_CAP_22_DMA_DELAY_MASK;
  54                vs_cap |= 0x22 << VS_CAP_22_DMA_DELAY_SHIFT;
  55                vs_cap |= VS_CAP_22_FORCE_POWER;
  56        } else {
  57                vs_cap &= ~VS_CAP_22_FORCE_POWER;
  58        }
  59        pci_write_config_dword(nhi->pdev, VS_CAP_22, vs_cap);
  60
  61        if (power) {
  62                unsigned int retries = 10;
  63                u32 val;
  64
  65                /* Wait until the firmware tells it is up and running */
  66                do {
  67                        pci_read_config_dword(nhi->pdev, VS_CAP_9, &val);
  68                        if (val & VS_CAP_9_FW_READY)
  69                                return 0;
  70                        msleep(250);
  71                } while (--retries);
  72
  73                return -ETIMEDOUT;
  74        }
  75
  76        return 0;
  77}
  78
  79static void icl_nhi_lc_mailbox_cmd(struct tb_nhi *nhi, enum icl_lc_mailbox_cmd cmd)
  80{
  81        u32 data;
  82
  83        data = (cmd << VS_CAP_19_CMD_SHIFT) & VS_CAP_19_CMD_MASK;
  84        pci_write_config_dword(nhi->pdev, VS_CAP_19, data | VS_CAP_19_VALID);
  85}
  86
  87static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout)
  88{
  89        unsigned long end;
  90        u32 data;
  91
  92        if (!timeout)
  93                goto clear;
  94
  95        end = jiffies + msecs_to_jiffies(timeout);
  96        do {
  97                pci_read_config_dword(nhi->pdev, VS_CAP_18, &data);
  98                if (data & VS_CAP_18_DONE)
  99                        goto clear;
 100                msleep(100);
 101        } while (time_before(jiffies, end));
 102
 103        return -ETIMEDOUT;
 104
 105clear:
 106        /* Clear the valid bit */
 107        pci_write_config_dword(nhi->pdev, VS_CAP_19, 0);
 108        return 0;
 109}
 110
 111static void icl_nhi_set_ltr(struct tb_nhi *nhi)
 112{
 113        u32 max_ltr, ltr;
 114
 115        pci_read_config_dword(nhi->pdev, VS_CAP_16, &max_ltr);
 116        max_ltr &= 0xffff;
 117        /* Program the same value for both snoop and no-snoop */
 118        ltr = max_ltr << 16 | max_ltr;
 119        pci_write_config_dword(nhi->pdev, VS_CAP_15, ltr);
 120}
 121
 122static int icl_nhi_suspend(struct tb_nhi *nhi)
 123{
 124        int ret;
 125
 126        if (icl_nhi_is_device_connected(nhi))
 127                return 0;
 128
 129        /*
 130         * If there is no device connected we need to perform both: a
 131         * handshake through LC mailbox and force power down before
 132         * entering D3.
 133         */
 134        icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET);
 135        ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
 136        if (ret)
 137                return ret;
 138
 139        return icl_nhi_force_power(nhi, false);
 140}
 141
 142static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup)
 143{
 144        enum icl_lc_mailbox_cmd cmd;
 145
 146        if (!pm_suspend_via_firmware())
 147                return icl_nhi_suspend(nhi);
 148
 149        cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE;
 150        icl_nhi_lc_mailbox_cmd(nhi, cmd);
 151        return icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
 152}
 153
 154static int icl_nhi_resume(struct tb_nhi *nhi)
 155{
 156        int ret;
 157
 158        ret = icl_nhi_force_power(nhi, true);
 159        if (ret)
 160                return ret;
 161
 162        icl_nhi_set_ltr(nhi);
 163        return 0;
 164}
 165
 166static void icl_nhi_shutdown(struct tb_nhi *nhi)
 167{
 168        icl_nhi_force_power(nhi, false);
 169}
 170
 171const struct tb_nhi_ops icl_nhi_ops = {
 172        .init = icl_nhi_resume,
 173        .suspend_noirq = icl_nhi_suspend_noirq,
 174        .resume_noirq = icl_nhi_resume,
 175        .runtime_suspend = icl_nhi_suspend,
 176        .runtime_resume = icl_nhi_resume,
 177        .shutdown = icl_nhi_shutdown,
 178};
 179