From d591c3f6efef5f50fc970aeeedbf9e03b7bd5d21 Mon Sep 17 00:00:00 2001 From: Jon Lin Date: Fri, 17 Jun 2022 10:38:30 +0800 Subject: [PATCH] PCI: Add ROCKCHIP PCIe ASPM interface Change-Id: I1156bd10e352145d745899067bf43afda92d5a30 Signed-off-by: Jon Lin --- drivers/pci/pcie/Kconfig | 6 + drivers/pci/pcie/Makefile | 1 + drivers/pci/pcie/aspm_ext.c | 339 ++++++++++++++++++++++++++++++++++++ include/linux/aspm_ext.h | 16 ++ 4 files changed, 362 insertions(+) create mode 100644 drivers/pci/pcie/aspm_ext.c create mode 100644 include/linux/aspm_ext.h --- a/drivers/pci/pcie/Kconfig +++ b/drivers/pci/pcie/Kconfig @@ -123,6 +123,12 @@ config PCIEASPM_PERFORMANCE Disable PCI Express ASPM L0s and L1, even if the BIOS enabled them. endchoice +config PCIEASPM_EXT + tristate "Extend ASPM function" + depends on PCIEASPM + help + This enables the extensions APIs for ASPM control. + config PCIE_PME def_bool y depends on PCIEPORTBUS && PM --- a/drivers/pci/pcie/Makefile +++ b/drivers/pci/pcie/Makefile @@ -7,6 +7,7 @@ pcieportdrv-y := portdrv.o rcec.o obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv.o obj-y += aspm.o +obj-$(CONFIG_PCIEASPM_EXT) += aspm_ext.o obj-$(CONFIG_PCIEAER) += aer.o err.o obj-$(CONFIG_PCIEAER_INJECT) += aer_inject.o obj-$(CONFIG_PCIE_PME) += pme.o --- /dev/null +++ b/drivers/pci/pcie/aspm_ext.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Rockchip PCIe Apis For WIFI + * + * Copyright (c) 2022 Rockchip Electronics Co., Ltd. + */ + +#include +#include +#include +#include + + +static u32 rockchip_pcie_pcie_access_cap(struct pci_dev *pdev, int cap, uint offset, + bool is_ext, bool is_write, u32 writeval) +{ + int cap_ptr = 0; + u32 ret = -1; + u32 readval; + + if (!(pdev)) { + pci_err(pdev, "%s: pdev is NULL\n", __func__); + return ret; + } + + /* Find Capability offset */ + if (is_ext) { + /* removing max EXT_CAP_ID check as + * linux kernel definition's max value is not updated yet as per spec + */ + cap_ptr = pci_find_ext_capability(pdev, cap); + + } else { + /* removing max PCI_CAP_ID_MAX check as + * previous kernel versions dont have this definition + */ + cap_ptr = pci_find_capability(pdev, cap); + } + + /* Return if capability with given ID not found */ + if (cap_ptr == 0) { + pci_err(pdev, "%s: PCI Cap(0x%02x) not supported.\n", + __func__, cap); + return -EINVAL; + } + + if (is_write) { + pci_write_config_dword(pdev, (cap_ptr + offset), writeval); + ret = 0; + + } else { + pci_read_config_dword(pdev, (cap_ptr + offset), &readval); + ret = readval; + } + + return ret; +} + +static bool rockchip_pcie_bus_aspm_enable_dev(char *device, struct pci_dev *dev, bool enable) +{ + u32 linkctrl_before; + u32 linkctrl_after = 0; + u8 linkctrl_asm; + + linkctrl_before = rockchip_pcie_pcie_access_cap(dev, PCI_CAP_ID_EXP, PCI_EXP_LNKCTL, + false, false, 0); + linkctrl_asm = (linkctrl_before & PCI_EXP_LNKCTL_ASPMC); + + if (enable) { + if (linkctrl_asm == PCI_EXP_LNKCTL_ASPM_L1) { + pci_err(dev, "%s: %s already enabled linkctrl: 0x%x\n", + __func__, device, linkctrl_before); + return false; + } + /* Enable only L1 ASPM (bit 1) */ + rockchip_pcie_pcie_access_cap(dev, PCI_CAP_ID_EXP, PCI_EXP_LNKCTL, false, + true, (linkctrl_before | PCI_EXP_LNKCTL_ASPM_L1)); + } else { + if (linkctrl_asm == 0) { + pci_err(dev, "%s: %s already disabled linkctrl: 0x%x\n", + __func__, device, linkctrl_before); + return false; + } + /* Disable complete ASPM (bit 1 and bit 0) */ + rockchip_pcie_pcie_access_cap(dev, PCI_CAP_ID_EXP, PCI_EXP_LNKCTL, false, + true, (linkctrl_before & (~PCI_EXP_LNKCTL_ASPMC))); + } + + linkctrl_after = rockchip_pcie_pcie_access_cap(dev, PCI_CAP_ID_EXP, PCI_EXP_LNKCTL, + false, false, 0); + pci_err(dev, "%s: %s %s, linkctrl_before: 0x%x linkctrl_after: 0x%x\n", + __func__, device, (enable ? "ENABLE " : "DISABLE"), + linkctrl_before, linkctrl_after); + + return true; +} + +bool rockchip_pcie_bus_aspm_enable_rc_ep(struct pci_dev *child, struct pci_dev *parent, bool enable) +{ + bool ret; + + if (enable) { + /* Enable only L1 ASPM first RC then EP */ + ret = rockchip_pcie_bus_aspm_enable_dev("RC", parent, enable); + ret = rockchip_pcie_bus_aspm_enable_dev("EP", child, enable); + } else { + /* Disable complete ASPM first EP then RC */ + ret = rockchip_pcie_bus_aspm_enable_dev("EP", child, enable); + ret = rockchip_pcie_bus_aspm_enable_dev("RC", parent, enable); + } + + return ret; +} + +static void pci_clear_and_set_dword(struct pci_dev *pdev, int pos, + u32 clear, u32 set) +{ + u32 val; + + pci_read_config_dword(pdev, pos, &val); + val &= ~clear; + val |= set; + pci_write_config_dword(pdev, pos, val); +} + +/* Convert L1SS T_pwr encoding to usec */ +static u32 calc_l1ss_pwron(struct pci_dev *pdev, u32 scale, u32 val) +{ + switch (scale) { + case 0: + return val * 2; + case 1: + return val * 10; + case 2: + return val * 100; + } + + return 0; +} + +static void encode_l12_threshold(u32 threshold_us, u32 *scale, u32 *value) +{ + u32 threshold_ns = threshold_us * 1000; + + /* See PCIe r3.1, sec 7.33.3 and sec 6.18 */ + if (threshold_ns < 32) { + *scale = 0; + *value = threshold_ns; + } else if (threshold_ns < 1024) { + *scale = 1; + *value = threshold_ns >> 5; + } else if (threshold_ns < 32768) { + *scale = 2; + *value = threshold_ns >> 10; + } else if (threshold_ns < 1048576) { + *scale = 3; + *value = threshold_ns >> 15; + } else if (threshold_ns < 33554432) { + *scale = 4; + *value = threshold_ns >> 20; + } else { + *scale = 5; + *value = threshold_ns >> 25; + } +} + +/* Calculate L1.2 PM substate timing parameters */ +static void aspm_calc_l1ss_info(struct pci_dev *child, struct pci_dev *parent) +{ + u32 val1, val2, scale1, scale2; + u32 t_common_mode, t_power_on, l1_2_threshold, scale, value; + u32 ctl1 = 0, ctl2 = 0; + u32 pctl1, pctl2, cctl1, cctl2; + u32 pl1_2_enables, cl1_2_enables; + u32 parent_l1ss_cap, child_l1ss_cap; + + /* Setup L1 substate */ + pci_read_config_dword(parent, parent->l1ss + PCI_L1SS_CAP, + &parent_l1ss_cap); + pci_read_config_dword(child, child->l1ss + PCI_L1SS_CAP, + &child_l1ss_cap); + + /* Choose the greater of the two Port Common_Mode_Restore_Times */ + val1 = (parent_l1ss_cap & PCI_L1SS_CAP_CM_RESTORE_TIME) >> 8; + val2 = (child_l1ss_cap & PCI_L1SS_CAP_CM_RESTORE_TIME) >> 8; + t_common_mode = max(val1, val2); + + /* Choose the greater of the two Port T_POWER_ON times */ + val1 = (parent_l1ss_cap & PCI_L1SS_CAP_P_PWR_ON_VALUE) >> 19; + scale1 = (parent_l1ss_cap & PCI_L1SS_CAP_P_PWR_ON_SCALE) >> 16; + val2 = (child_l1ss_cap & PCI_L1SS_CAP_P_PWR_ON_VALUE) >> 19; + scale2 = (child_l1ss_cap & PCI_L1SS_CAP_P_PWR_ON_SCALE) >> 16; + + if (calc_l1ss_pwron(parent, scale1, val1) > + calc_l1ss_pwron(child, scale2, val2)) { + ctl2 |= scale1 | (val1 << 3); + t_power_on = calc_l1ss_pwron(parent, scale1, val1); + } else { + ctl2 |= scale2 | (val2 << 3); + t_power_on = calc_l1ss_pwron(child, scale2, val2); + } + + /* Set LTR_L1.2_THRESHOLD to the time required to transition the + * Link from L0 to L1.2 and back to L0 so we enter L1.2 only if + * downstream devices report (via LTR) that they can tolerate at + * least that much latency. + * + * Based on PCIe r3.1, sec 5.5.3.3.1, Figures 5-16 and 5-17, and + * Table 5-11. T(POWER_OFF) is at most 2us and T(L1.2) is at + * least 4us. + */ + l1_2_threshold = 2 + 4 + t_common_mode + t_power_on; + encode_l12_threshold(l1_2_threshold, &scale, &value); + ctl1 |= t_common_mode << 8 | scale << 29 | value << 16; + + pci_read_config_dword(parent, parent->l1ss + PCI_L1SS_CTL1, &pctl1); + pci_read_config_dword(parent, parent->l1ss + PCI_L1SS_CTL2, &pctl2); + pci_read_config_dword(child, child->l1ss + PCI_L1SS_CTL1, &cctl1); + pci_read_config_dword(child, child->l1ss + PCI_L1SS_CTL2, &cctl2); + + if (ctl1 == pctl1 && ctl1 == cctl1 && + ctl2 == pctl2 && ctl2 == cctl2) + return; + + /* Disable L1.2 while updating. See PCIe r5.0, sec 5.5.4, 7.8.3.3 */ + pl1_2_enables = pctl1 & PCI_L1SS_CTL1_L1_2_MASK; + cl1_2_enables = cctl1 & PCI_L1SS_CTL1_L1_2_MASK; + + if (pl1_2_enables || cl1_2_enables) { + pci_clear_and_set_dword(child, child->l1ss + PCI_L1SS_CTL1, + PCI_L1SS_CTL1_L1_2_MASK, 0); + pci_clear_and_set_dword(parent, parent->l1ss + PCI_L1SS_CTL1, + PCI_L1SS_CTL1_L1_2_MASK, 0); + } + + /* Program T_POWER_ON times in both ports */ + pci_write_config_dword(parent, parent->l1ss + PCI_L1SS_CTL2, ctl2); + pci_write_config_dword(child, child->l1ss + PCI_L1SS_CTL2, ctl2); + + /* Program Common_Mode_Restore_Time in upstream device */ + pci_clear_and_set_dword(parent, parent->l1ss + PCI_L1SS_CTL1, + PCI_L1SS_CTL1_CM_RESTORE_TIME, ctl1); + + /* Program LTR_L1.2_THRESHOLD time in both ports */ + pci_clear_and_set_dword(parent, parent->l1ss + PCI_L1SS_CTL1, + PCI_L1SS_CTL1_LTR_L12_TH_VALUE | + PCI_L1SS_CTL1_LTR_L12_TH_SCALE, ctl1); + pci_clear_and_set_dword(child, child->l1ss + PCI_L1SS_CTL1, + PCI_L1SS_CTL1_LTR_L12_TH_VALUE | + PCI_L1SS_CTL1_LTR_L12_TH_SCALE, ctl1); + + if (pl1_2_enables || cl1_2_enables) { + pci_clear_and_set_dword(parent, parent->l1ss + PCI_L1SS_CTL1, 0, + pl1_2_enables); + pci_clear_and_set_dword(child, child->l1ss + PCI_L1SS_CTL1, 0, + cl1_2_enables); + } +} + +static void rockchip_pcie_bus_l1ss_enable_dev(char *device, struct pci_dev *dev, bool enable) +{ + u32 l1ssctrl_before; + u32 l1ssctrl_after = 0; + u8 l1ss_ep; + + /* Extendend Capacility Reg */ + l1ssctrl_before = rockchip_pcie_pcie_access_cap(dev, PCI_EXT_CAP_ID_L1SS, + PCI_L1SS_CTL1, true, false, 0); + l1ss_ep = (l1ssctrl_before & PCI_L1SS_CTL1_L1SS_MASK); + + if (enable) { + if (l1ss_ep == PCI_L1SS_CTL1_L1SS_MASK) { + pci_err(dev, "%s: %s already enabled, l1ssctrl: 0x%x\n", + __func__, device, l1ssctrl_before); + return; + } + rockchip_pcie_pcie_access_cap(dev, PCI_EXT_CAP_ID_L1SS, PCI_L1SS_CTL1, + true, true, (l1ssctrl_before | PCI_L1SS_CTL1_L1SS_MASK)); + } else { + if (l1ss_ep == 0) { + pci_err(dev, "%s: %s already disabled, l1ssctrl: 0x%x\n", + __func__, device, l1ssctrl_before); + return; + } + rockchip_pcie_pcie_access_cap(dev, PCI_EXT_CAP_ID_L1SS, PCI_L1SS_CTL1, + true, true, (l1ssctrl_before & (~PCI_L1SS_CTL1_L1SS_MASK))); + } + l1ssctrl_after = rockchip_pcie_pcie_access_cap(dev, PCI_EXT_CAP_ID_L1SS, + PCI_L1SS_CTL1, true, false, 0); + pci_err(dev, "%s: %s %s, l1ssctrl_before: 0x%x l1ssctrl_after: 0x%x\n", + __func__, device, (enable ? "ENABLE " : "DISABLE"), + l1ssctrl_before, l1ssctrl_after); +} + +bool pcie_aspm_ext_is_rc_ep_l1ss_capable(struct pci_dev *child, struct pci_dev *parent) +{ + u32 parent_l1ss_cap, child_l1ss_cap; + + /* Setup L1 substate */ + pci_read_config_dword(parent, parent->l1ss + PCI_L1SS_CAP, + &parent_l1ss_cap); + pci_read_config_dword(child, child->l1ss + PCI_L1SS_CAP, + &child_l1ss_cap); + + if (!(parent_l1ss_cap & PCI_L1SS_CAP_L1_PM_SS)) + parent_l1ss_cap = 0; + if (!(child_l1ss_cap & PCI_L1SS_CAP_L1_PM_SS)) + child_l1ss_cap = 0; + + if (parent_l1ss_cap && child_l1ss_cap) + return true; + else + return false; +} +EXPORT_SYMBOL(pcie_aspm_ext_is_rc_ep_l1ss_capable); + +void pcie_aspm_ext_l1ss_enable(struct pci_dev *child, struct pci_dev *parent, bool enable) +{ + bool ret; + + /* Disable ASPM of RC and EP */ + ret = rockchip_pcie_bus_aspm_enable_rc_ep(child, parent, false); + + if (enable) { + /* Enable RC then EP */ + aspm_calc_l1ss_info(child, parent); + rockchip_pcie_bus_l1ss_enable_dev("RC", parent, enable); + rockchip_pcie_bus_l1ss_enable_dev("EP", child, enable); + } else { + /* Disable EP then RC */ + rockchip_pcie_bus_l1ss_enable_dev("EP", child, enable); + rockchip_pcie_bus_l1ss_enable_dev("RC", parent, enable); + } + + /* Enable ASPM of RC and EP only if this API disabled */ + if (ret) + rockchip_pcie_bus_aspm_enable_rc_ep(child, parent, true); +} +EXPORT_SYMBOL(pcie_aspm_ext_l1ss_enable); --- /dev/null +++ b/include/linux/aspm_ext.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* Copyright (c) 2022 Rockchip Electronics Co., Ltd. */ + +#ifndef _ASPM_EXT_H +#define _ASPM_EXT_H + +#if IS_REACHABLE(CONFIG_PCIEASPM_EXT) +bool pcie_aspm_ext_is_rc_ep_l1ss_capable(struct pci_dev *child, struct pci_dev *parent); +void pcie_aspm_ext_l1ss_enable(struct pci_dev *child, struct pci_dev *parent, bool enable); +#else +static inline bool pcie_aspm_ext_is_rc_ep_l1ss_capable(struct pci_dev *child, struct pci_dev *parent) { return false; } +static inline void pcie_aspm_ext_l1ss_enable(struct pci_dev *child, struct pci_dev *parent, bool enable) {} +#endif + +#endif