/*
* File: drivers/pci/pcie/aspm.c
* Enabling PCIE link L0s/L1 state and Clock Power Management
*
* Copyright (C) 2007 Intel
* Copyright (C) Zhang Yanmin (yanmin.zhang@intel.com)
* Copyright (C) Shaohua Li (shaohua.li@intel.com)
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/pci.h>
#include <linux/pci_regs.h>
#include <linux/errno.h>
#include <linux/pm.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/pci-aspm.h>
#include "../pci.h"
#ifdef MODULE_PARAM_PREFIX
#undef MODULE_PARAM_PREFIX
#endif
#define MODULE_PARAM_PREFIX "pcie_aspm."
struct endpoint_state {
unsigned int l0s_acceptable_latency;
unsigned int l1_acceptable_latency;
};
struct pcie_link_state {
struct list_head sibiling;
struct pci_dev *pdev;
bool downstream_has_switch;
struct pcie_link_state *parent;
struct list_head children;
struct list_head link;
/* ASPM state */
unsigned int support_state;
unsigned int enabled_state;
unsigned int bios_aspm_state;
/* upstream component */
unsigned int l0s_upper_latency;
unsigned int l1_upper_latency;
/* downstream component */
unsigned int l0s_down_latency;
unsigned int l1_down_latency;
/* Clock PM state*/
unsigned int clk_pm_capable;
unsigned int clk_pm_enabled;
unsigned int bios_clk_state;
/*
* A pcie downstream port only has one slot under it, so at most there
* are 8 functions
*/
struct endpoint_state endpoints[8];
};
static int aspm_disabled, aspm_force;
static DEFINE_MUTEX(aspm_lock);
static LIST_HEAD(link_list);
#define POLICY_DEFAULT 0 /* BIOS default setting */
#define POLICY_PERFORMANCE 1 /* high performance */
#define POLICY_POWERSAVE 2 /* high power saving */
static int aspm_policy;
static const char *policy_str[] = {
[POLICY_DEFAULT] = "default",
[POLICY_PERFORMANCE] = "performance",
[POLICY_POWERSAVE] = "powersave"
};
static int policy_to_aspm_state(struct pci_dev *pdev)
{
struct pcie_link_state *link_state = pdev->link_state;
switch (aspm_policy) {
case POLICY_PERFORMANCE:
/* Disable ASPM and Clock PM */
return 0;
case POLICY_POWERSAVE:
/* Enable ASPM L0s/L1 */
return PCIE_LINK_STATE_L0S|PCIE_LINK_STATE_L1;
case POLICY_DEFAULT:
return link_state->bios_aspm_state;
}
return 0;
}
static int policy_to_clkpm_state(struct pci_dev *pdev)
{
struct pcie_link_state *link_state = pdev->link_state;
switch (aspm_policy) {
case POLICY_PERFORMANCE:
/* Disable ASPM and Clock PM */
return 0;
case POLICY_POWERSAVE:
/* Disable Clock PM */
return 1;
case POLICY_DEFAULT:
return link_state->bios_clk_state;
}
return 0;
}
static void pcie_set_clock_pm(struct pci_dev *pdev, int enable)
{
struct pci_dev *child_dev;
int pos;
u16 reg16;
struct pcie_link_state *link_state = pdev->link_state;
list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) {
pos = pci_find_capability(child_dev, PCI_CAP_ID_EXP);
if (!pos)
return;
pci_read_config_word(child_dev, pos + PCI_EXP_LNKCTL, ®16);
if (enable)
reg16 |= PCI_EXP_LNKCTL_CLKREQ_EN;
else
reg16 &= ~PCI_EXP_LNKCTL_CLKREQ_EN;
pci_write_config_word(child_dev, pos + PCI_EXP_LNKCTL, reg16);
}
link_state->clk_pm_enabled = !!enable;
}
static void pcie_check_clock_pm(struct pci_dev *pdev, int blacklist)
{
int pos;
u32 reg32;
u16 reg16;
int capable = 1, enabled = 1;
struct pci_dev *child_dev;
struct pcie_link_state *link_state = pdev->link_state;
/* All functions should have the same cap and state, take the worst */
list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) {
pos = pci_find_capability(child_dev, PCI_CAP_ID_EXP);
if (!pos)
return;
pci_read_config_dword(child_dev, pos + PCI_EXP_LNKCAP, ®32);
if (!(reg32 & PCI_EXP_LNKCAP_CLKPM)) {
capable = 0;
enabled = 0;
break;
}
pci_read_config_word(child_dev, pos + PCI_EXP_LNKCTL, ®16);
if (!(reg16 & PCI_EXP_LNKCTL_CLKREQ_EN))
enabled = 0;
}
link_state->clk_pm_enabled = enabled;
link_state->bios_clk_state = enabled;
if (!blacklist) {
link_state->clk_pm_capable = capable;
pcie_set_clock_pm(pdev, policy_to_clkpm_state(pdev));
} else {
link_state->clk_pm_capable = 0;
pcie_set_clock_pm(pdev, 0