Merge tag 'pci-v4.12-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/helgaas/pci
Pull PCI updates from Bjorn Helgaas: - add framework for supporting PCIe devices in Endpoint mode (Kishon Vijay Abraham I) - use non-postable PCI config space mappings when possible (Lorenzo Pieralisi) - clean up and unify mmap of PCI BARs (David Woodhouse) - export and unify Function Level Reset support (Christoph Hellwig) - avoid FLR for Intel 82579 NICs (Sasha Neftin) - add pci_request_irq() and pci_free_irq() helpers (Christoph Hellwig) - short-circuit config access failures for disconnected devices (Keith Busch) - remove D3 sleep delay when possible (Adrian Hunter) - freeze PME scan before suspending devices (Lukas Wunner) - stop disabling MSI/MSI-X in pci_device_shutdown() (Prarit Bhargava) - disable boot interrupt quirk for ASUS M2N-LR (Stefan Assmann) - add arch-specific alignment control to improve device passthrough by avoiding multiple BARs in a page (Yongji Xie) - add sysfs sriov_drivers_autoprobe to control VF driver binding (Bodong Wang) - allow slots below PCI-to-PCIe "reverse bridges" (Bjorn Helgaas) - fix crashes when unbinding host controllers that don't support removal (Brian Norris) - add driver for MicroSemi Switchtec management interface (Logan Gunthorpe) - add driver for Faraday Technology FTPCI100 host bridge (Linus Walleij) - add i.MX7D support (Andrey Smirnov) - use generic MSI support for Aardvark (Thomas Petazzoni) - make Rockchip driver modular (Brian Norris) - advertise 128-byte Read Completion Boundary support for Rockchip (Shawn Lin) - advertise PCI_EXP_LNKSTA_SLC for Rockchip root port (Shawn Lin) - convert atomic_t to refcount_t in HV driver (Elena Reshetova) - add CPU IRQ affinity in HV driver (K. Y. Srinivasan) - fix PCI bus removal in HV driver (Long Li) - add support for ThunderX2 DMA alias topology (Jayachandran C) - add ThunderX pass2.x 2nd node MCFG quirk (Tomasz Nowicki) - add ITE 8893 bridge DMA alias quirk (Jarod Wilson) - restrict Cavium ACS quirk only to CN81xx/CN83xx/CN88xx devices (Manish Jaggi) * tag 'pci-v4.12-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/helgaas/pci: (146 commits) PCI: Don't allow unbinding host controllers that aren't prepared ARM: DRA7: clockdomain: Change the CLKTRCTRL of CM_PCIE_CLKSTCTRL to SW_WKUP MAINTAINERS: Add PCI Endpoint maintainer Documentation: PCI: Add userguide for PCI endpoint test function tools: PCI: Add sample test script to invoke pcitest tools: PCI: Add a userspace tool to test PCI endpoint Documentation: misc-devices: Add Documentation for pci-endpoint-test driver misc: Add host side PCI driver for PCI test function device PCI: Add device IDs for DRA74x and DRA72x dt-bindings: PCI: dra7xx: Add DT bindings to enable unaligned access PCI: dwc: dra7xx: Workaround for errata id i870 dt-bindings: PCI: dra7xx: Add DT bindings for PCI dra7xx EP mode PCI: dwc: dra7xx: Add EP mode support PCI: dwc: dra7xx: Facilitate wrapper and MSI interrupts to be enabled independently dt-bindings: PCI: Add DT bindings for PCI designware EP mode PCI: dwc: designware: Add EP mode support Documentation: PCI: Add binding documentation for pci-test endpoint function ixgbe: Use pcie_flr() instead of duplicating it IB/hfi1: Use pcie_flr() instead of duplicating it PCI: imx6: Fix spelling mistake: "contol" -> "control" ...
This commit is contained in:
@@ -134,3 +134,5 @@ config PCI_HYPERV
|
||||
source "drivers/pci/hotplug/Kconfig"
|
||||
source "drivers/pci/dwc/Kconfig"
|
||||
source "drivers/pci/host/Kconfig"
|
||||
source "drivers/pci/endpoint/Kconfig"
|
||||
source "drivers/pci/switch/Kconfig"
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
obj-y += access.o bus.o probe.o host-bridge.o remove.o pci.o \
|
||||
pci-driver.o search.o pci-sysfs.o rom.o setup-res.o \
|
||||
irq.o vpd.o setup-bus.o vc.o
|
||||
irq.o vpd.o setup-bus.o vc.o mmap.o
|
||||
obj-$(CONFIG_PROC_FS) += proc.o
|
||||
obj-$(CONFIG_SYSFS) += slot.o
|
||||
|
||||
@@ -68,3 +68,4 @@ ccflags-$(CONFIG_PCI_DEBUG) := -DDEBUG
|
||||
|
||||
# PCI host controller drivers
|
||||
obj-y += host/
|
||||
obj-y += switch/
|
||||
|
@@ -629,7 +629,7 @@ void pci_vpd_release(struct pci_dev *dev)
|
||||
*
|
||||
* When access is locked, any userspace reads or writes to config
|
||||
* space and concurrent lock requests will sleep until access is
|
||||
* allowed via pci_cfg_access_unlocked again.
|
||||
* allowed via pci_cfg_access_unlock() again.
|
||||
*/
|
||||
void pci_cfg_access_lock(struct pci_dev *dev)
|
||||
{
|
||||
@@ -700,7 +700,8 @@ static bool pcie_downstream_port(const struct pci_dev *dev)
|
||||
int type = pci_pcie_type(dev);
|
||||
|
||||
return type == PCI_EXP_TYPE_ROOT_PORT ||
|
||||
type == PCI_EXP_TYPE_DOWNSTREAM;
|
||||
type == PCI_EXP_TYPE_DOWNSTREAM ||
|
||||
type == PCI_EXP_TYPE_PCIE_BRIDGE;
|
||||
}
|
||||
|
||||
bool pcie_cap_has_lnkctl(const struct pci_dev *dev)
|
||||
@@ -890,3 +891,59 @@ int pcie_capability_clear_and_set_dword(struct pci_dev *dev, int pos,
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(pcie_capability_clear_and_set_dword);
|
||||
|
||||
int pci_read_config_byte(const struct pci_dev *dev, int where, u8 *val)
|
||||
{
|
||||
if (pci_dev_is_disconnected(dev)) {
|
||||
*val = ~0;
|
||||
return -ENODEV;
|
||||
}
|
||||
return pci_bus_read_config_byte(dev->bus, dev->devfn, where, val);
|
||||
}
|
||||
EXPORT_SYMBOL(pci_read_config_byte);
|
||||
|
||||
int pci_read_config_word(const struct pci_dev *dev, int where, u16 *val)
|
||||
{
|
||||
if (pci_dev_is_disconnected(dev)) {
|
||||
*val = ~0;
|
||||
return -ENODEV;
|
||||
}
|
||||
return pci_bus_read_config_word(dev->bus, dev->devfn, where, val);
|
||||
}
|
||||
EXPORT_SYMBOL(pci_read_config_word);
|
||||
|
||||
int pci_read_config_dword(const struct pci_dev *dev, int where,
|
||||
u32 *val)
|
||||
{
|
||||
if (pci_dev_is_disconnected(dev)) {
|
||||
*val = ~0;
|
||||
return -ENODEV;
|
||||
}
|
||||
return pci_bus_read_config_dword(dev->bus, dev->devfn, where, val);
|
||||
}
|
||||
EXPORT_SYMBOL(pci_read_config_dword);
|
||||
|
||||
int pci_write_config_byte(const struct pci_dev *dev, int where, u8 val)
|
||||
{
|
||||
if (pci_dev_is_disconnected(dev))
|
||||
return -ENODEV;
|
||||
return pci_bus_write_config_byte(dev->bus, dev->devfn, where, val);
|
||||
}
|
||||
EXPORT_SYMBOL(pci_write_config_byte);
|
||||
|
||||
int pci_write_config_word(const struct pci_dev *dev, int where, u16 val)
|
||||
{
|
||||
if (pci_dev_is_disconnected(dev))
|
||||
return -ENODEV;
|
||||
return pci_bus_write_config_word(dev->bus, dev->devfn, where, val);
|
||||
}
|
||||
EXPORT_SYMBOL(pci_write_config_word);
|
||||
|
||||
int pci_write_config_dword(const struct pci_dev *dev, int where,
|
||||
u32 val)
|
||||
{
|
||||
if (pci_dev_is_disconnected(dev))
|
||||
return -ENODEV;
|
||||
return pci_bus_write_config_dword(dev->bus, dev->devfn, where, val);
|
||||
}
|
||||
EXPORT_SYMBOL(pci_write_config_dword);
|
||||
|
@@ -9,16 +9,44 @@ config PCIE_DW_HOST
|
||||
depends on PCI_MSI_IRQ_DOMAIN
|
||||
select PCIE_DW
|
||||
|
||||
config PCIE_DW_EP
|
||||
bool
|
||||
depends on PCI_ENDPOINT
|
||||
select PCIE_DW
|
||||
|
||||
config PCI_DRA7XX
|
||||
bool "TI DRA7xx PCIe controller"
|
||||
depends on PCI
|
||||
depends on (PCI && PCI_MSI_IRQ_DOMAIN) || PCI_ENDPOINT
|
||||
depends on OF && HAS_IOMEM && TI_PIPE3
|
||||
help
|
||||
Enables support for the PCIe controller in the DRA7xx SoC. There
|
||||
are two instances of PCIe controller in DRA7xx. This controller can
|
||||
work either as EP or RC. In order to enable host-specific features
|
||||
PCI_DRA7XX_HOST must be selected and in order to enable device-
|
||||
specific features PCI_DRA7XX_EP must be selected. This uses
|
||||
the Designware core.
|
||||
|
||||
if PCI_DRA7XX
|
||||
|
||||
config PCI_DRA7XX_HOST
|
||||
bool "PCI DRA7xx Host Mode"
|
||||
depends on PCI
|
||||
depends on PCI_MSI_IRQ_DOMAIN
|
||||
select PCIE_DW_HOST
|
||||
default y
|
||||
help
|
||||
Enables support for the PCIe controller in the DRA7xx SoC. There
|
||||
are two instances of PCIe controller in DRA7xx. This controller can
|
||||
act both as EP and RC. This reuses the Designware core.
|
||||
Enables support for the PCIe controller in the DRA7xx SoC to work in
|
||||
host mode.
|
||||
|
||||
config PCI_DRA7XX_EP
|
||||
bool "PCI DRA7xx Endpoint Mode"
|
||||
depends on PCI_ENDPOINT
|
||||
select PCIE_DW_EP
|
||||
help
|
||||
Enables support for the PCIe controller in the DRA7xx SoC to work in
|
||||
endpoint mode.
|
||||
|
||||
endif
|
||||
|
||||
config PCIE_DW_PLAT
|
||||
bool "Platform bus based DesignWare PCIe Controller"
|
||||
|
@@ -1,7 +1,10 @@
|
||||
obj-$(CONFIG_PCIE_DW) += pcie-designware.o
|
||||
obj-$(CONFIG_PCIE_DW_HOST) += pcie-designware-host.o
|
||||
obj-$(CONFIG_PCIE_DW_EP) += pcie-designware-ep.o
|
||||
obj-$(CONFIG_PCIE_DW_PLAT) += pcie-designware-plat.o
|
||||
obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o
|
||||
ifneq ($(filter y,$(CONFIG_PCI_DRA7XX_HOST) $(CONFIG_PCI_DRA7XX_EP)),)
|
||||
obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o
|
||||
endif
|
||||
obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o
|
||||
obj-$(CONFIG_PCI_IMX6) += pci-imx6.o
|
||||
obj-$(CONFIG_PCIE_SPEAR13XX) += pcie-spear13xx.o
|
||||
|
@@ -10,12 +10,14 @@
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/of_pci.h>
|
||||
#include <linux/pci.h>
|
||||
@@ -24,6 +26,8 @@
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/resource.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include "pcie-designware.h"
|
||||
|
||||
@@ -57,6 +61,11 @@
|
||||
#define MSI BIT(4)
|
||||
#define LEG_EP_INTERRUPTS (INTA | INTB | INTC | INTD)
|
||||
|
||||
#define PCIECTRL_TI_CONF_DEVICE_TYPE 0x0100
|
||||
#define DEVICE_TYPE_EP 0x0
|
||||
#define DEVICE_TYPE_LEG_EP 0x1
|
||||
#define DEVICE_TYPE_RC 0x4
|
||||
|
||||
#define PCIECTRL_DRA7XX_CONF_DEVICE_CMD 0x0104
|
||||
#define LTSSM_EN 0x1
|
||||
|
||||
@@ -66,6 +75,13 @@
|
||||
|
||||
#define EXP_CAP_ID_OFFSET 0x70
|
||||
|
||||
#define PCIECTRL_TI_CONF_INTX_ASSERT 0x0124
|
||||
#define PCIECTRL_TI_CONF_INTX_DEASSERT 0x0128
|
||||
|
||||
#define PCIECTRL_TI_CONF_MSI_XMT 0x012c
|
||||
#define MSI_REQ_GRANT BIT(0)
|
||||
#define MSI_VECTOR_SHIFT 7
|
||||
|
||||
struct dra7xx_pcie {
|
||||
struct dw_pcie *pci;
|
||||
void __iomem *base; /* DT ti_conf */
|
||||
@@ -73,6 +89,11 @@ struct dra7xx_pcie {
|
||||
struct phy **phy;
|
||||
int link_gen;
|
||||
struct irq_domain *irq_domain;
|
||||
enum dw_pcie_device_mode mode;
|
||||
};
|
||||
|
||||
struct dra7xx_pcie_of_data {
|
||||
enum dw_pcie_device_mode mode;
|
||||
};
|
||||
|
||||
#define to_dra7xx_pcie(x) dev_get_drvdata((x)->dev)
|
||||
@@ -88,6 +109,11 @@ static inline void dra7xx_pcie_writel(struct dra7xx_pcie *pcie, u32 offset,
|
||||
writel(value, pcie->base + offset);
|
||||
}
|
||||
|
||||
static u64 dra7xx_pcie_cpu_addr_fixup(u64 pci_addr)
|
||||
{
|
||||
return pci_addr & DRA7XX_CPU_TO_BUS_ADDR;
|
||||
}
|
||||
|
||||
static int dra7xx_pcie_link_up(struct dw_pcie *pci)
|
||||
{
|
||||
struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pci);
|
||||
@@ -96,9 +122,19 @@ static int dra7xx_pcie_link_up(struct dw_pcie *pci)
|
||||
return !!(reg & LINK_UP);
|
||||
}
|
||||
|
||||
static int dra7xx_pcie_establish_link(struct dra7xx_pcie *dra7xx)
|
||||
static void dra7xx_pcie_stop_link(struct dw_pcie *pci)
|
||||
{
|
||||
struct dw_pcie *pci = dra7xx->pci;
|
||||
struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pci);
|
||||
u32 reg;
|
||||
|
||||
reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD);
|
||||
reg &= ~LTSSM_EN;
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD, reg);
|
||||
}
|
||||
|
||||
static int dra7xx_pcie_establish_link(struct dw_pcie *pci)
|
||||
{
|
||||
struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pci);
|
||||
struct device *dev = pci->dev;
|
||||
u32 reg;
|
||||
u32 exp_cap_off = EXP_CAP_ID_OFFSET;
|
||||
@@ -132,19 +168,31 @@ static int dra7xx_pcie_establish_link(struct dra7xx_pcie *dra7xx)
|
||||
reg |= LTSSM_EN;
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD, reg);
|
||||
|
||||
return dw_pcie_wait_for_link(pci);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dra7xx_pcie_enable_msi_interrupts(struct dra7xx_pcie *dra7xx)
|
||||
{
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MSI,
|
||||
~LEG_EP_INTERRUPTS & ~MSI);
|
||||
|
||||
dra7xx_pcie_writel(dra7xx,
|
||||
PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MSI,
|
||||
MSI | LEG_EP_INTERRUPTS);
|
||||
}
|
||||
|
||||
static void dra7xx_pcie_enable_wrapper_interrupts(struct dra7xx_pcie *dra7xx)
|
||||
{
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN,
|
||||
~INTERRUPTS);
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MAIN,
|
||||
INTERRUPTS);
|
||||
}
|
||||
|
||||
static void dra7xx_pcie_enable_interrupts(struct dra7xx_pcie *dra7xx)
|
||||
{
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN,
|
||||
~INTERRUPTS);
|
||||
dra7xx_pcie_writel(dra7xx,
|
||||
PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MAIN, INTERRUPTS);
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MSI,
|
||||
~LEG_EP_INTERRUPTS & ~MSI);
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_IRQENABLE_SET_MSI,
|
||||
MSI | LEG_EP_INTERRUPTS);
|
||||
dra7xx_pcie_enable_wrapper_interrupts(dra7xx);
|
||||
dra7xx_pcie_enable_msi_interrupts(dra7xx);
|
||||
}
|
||||
|
||||
static void dra7xx_pcie_host_init(struct pcie_port *pp)
|
||||
@@ -152,14 +200,10 @@ static void dra7xx_pcie_host_init(struct pcie_port *pp)
|
||||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||||
struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pci);
|
||||
|
||||
pp->io_base &= DRA7XX_CPU_TO_BUS_ADDR;
|
||||
pp->mem_base &= DRA7XX_CPU_TO_BUS_ADDR;
|
||||
pp->cfg0_base &= DRA7XX_CPU_TO_BUS_ADDR;
|
||||
pp->cfg1_base &= DRA7XX_CPU_TO_BUS_ADDR;
|
||||
|
||||
dw_pcie_setup_rc(pp);
|
||||
|
||||
dra7xx_pcie_establish_link(dra7xx);
|
||||
dra7xx_pcie_establish_link(pci);
|
||||
dw_pcie_wait_for_link(pci);
|
||||
dw_pcie_msi_init(pp);
|
||||
dra7xx_pcie_enable_interrupts(dra7xx);
|
||||
}
|
||||
@@ -237,6 +281,7 @@ static irqreturn_t dra7xx_pcie_irq_handler(int irq, void *arg)
|
||||
struct dra7xx_pcie *dra7xx = arg;
|
||||
struct dw_pcie *pci = dra7xx->pci;
|
||||
struct device *dev = pci->dev;
|
||||
struct dw_pcie_ep *ep = &pci->ep;
|
||||
u32 reg;
|
||||
|
||||
reg = dra7xx_pcie_readl(dra7xx, PCIECTRL_DRA7XX_CONF_IRQSTATUS_MAIN);
|
||||
@@ -273,8 +318,11 @@ static irqreturn_t dra7xx_pcie_irq_handler(int irq, void *arg)
|
||||
if (reg & LINK_REQ_RST)
|
||||
dev_dbg(dev, "Link Request Reset\n");
|
||||
|
||||
if (reg & LINK_UP_EVT)
|
||||
if (reg & LINK_UP_EVT) {
|
||||
if (dra7xx->mode == DW_PCIE_EP_TYPE)
|
||||
dw_pcie_ep_linkup(ep);
|
||||
dev_dbg(dev, "Link-up state change\n");
|
||||
}
|
||||
|
||||
if (reg & CFG_BME_EVT)
|
||||
dev_dbg(dev, "CFG 'Bus Master Enable' change\n");
|
||||
@@ -287,6 +335,94 @@ static irqreturn_t dra7xx_pcie_irq_handler(int irq, void *arg)
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void dra7xx_pcie_ep_init(struct dw_pcie_ep *ep)
|
||||
{
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pci);
|
||||
|
||||
dra7xx_pcie_enable_wrapper_interrupts(dra7xx);
|
||||
}
|
||||
|
||||
static void dra7xx_pcie_raise_legacy_irq(struct dra7xx_pcie *dra7xx)
|
||||
{
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_TI_CONF_INTX_ASSERT, 0x1);
|
||||
mdelay(1);
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_TI_CONF_INTX_DEASSERT, 0x1);
|
||||
}
|
||||
|
||||
static void dra7xx_pcie_raise_msi_irq(struct dra7xx_pcie *dra7xx,
|
||||
u8 interrupt_num)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
reg = (interrupt_num - 1) << MSI_VECTOR_SHIFT;
|
||||
reg |= MSI_REQ_GRANT;
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_TI_CONF_MSI_XMT, reg);
|
||||
}
|
||||
|
||||
static int dra7xx_pcie_raise_irq(struct dw_pcie_ep *ep,
|
||||
enum pci_epc_irq_type type, u8 interrupt_num)
|
||||
{
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pci);
|
||||
|
||||
switch (type) {
|
||||
case PCI_EPC_IRQ_LEGACY:
|
||||
dra7xx_pcie_raise_legacy_irq(dra7xx);
|
||||
break;
|
||||
case PCI_EPC_IRQ_MSI:
|
||||
dra7xx_pcie_raise_msi_irq(dra7xx, interrupt_num);
|
||||
break;
|
||||
default:
|
||||
dev_err(pci->dev, "UNKNOWN IRQ type\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct dw_pcie_ep_ops pcie_ep_ops = {
|
||||
.ep_init = dra7xx_pcie_ep_init,
|
||||
.raise_irq = dra7xx_pcie_raise_irq,
|
||||
};
|
||||
|
||||
static int __init dra7xx_add_pcie_ep(struct dra7xx_pcie *dra7xx,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct dw_pcie_ep *ep;
|
||||
struct resource *res;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct dw_pcie *pci = dra7xx->pci;
|
||||
|
||||
ep = &pci->ep;
|
||||
ep->ops = &pcie_ep_ops;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ep_dbics");
|
||||
pci->dbi_base = devm_ioremap(dev, res->start, resource_size(res));
|
||||
if (!pci->dbi_base)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ep_dbics2");
|
||||
pci->dbi_base2 = devm_ioremap(dev, res->start, resource_size(res));
|
||||
if (!pci->dbi_base2)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "addr_space");
|
||||
if (!res)
|
||||
return -EINVAL;
|
||||
|
||||
ep->phys_base = res->start;
|
||||
ep->addr_size = resource_size(res);
|
||||
|
||||
ret = dw_pcie_ep_init(ep);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to initialize endpoint\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init dra7xx_add_pcie_port(struct dra7xx_pcie *dra7xx,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
@@ -329,6 +465,9 @@ static int __init dra7xx_add_pcie_port(struct dra7xx_pcie *dra7xx,
|
||||
}
|
||||
|
||||
static const struct dw_pcie_ops dw_pcie_ops = {
|
||||
.cpu_addr_fixup = dra7xx_pcie_cpu_addr_fixup,
|
||||
.start_link = dra7xx_pcie_establish_link,
|
||||
.stop_link = dra7xx_pcie_stop_link,
|
||||
.link_up = dra7xx_pcie_link_up,
|
||||
};
|
||||
|
||||
@@ -371,6 +510,68 @@ err_phy:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct dra7xx_pcie_of_data dra7xx_pcie_rc_of_data = {
|
||||
.mode = DW_PCIE_RC_TYPE,
|
||||
};
|
||||
|
||||
static const struct dra7xx_pcie_of_data dra7xx_pcie_ep_of_data = {
|
||||
.mode = DW_PCIE_EP_TYPE,
|
||||
};
|
||||
|
||||
static const struct of_device_id of_dra7xx_pcie_match[] = {
|
||||
{
|
||||
.compatible = "ti,dra7-pcie",
|
||||
.data = &dra7xx_pcie_rc_of_data,
|
||||
},
|
||||
{
|
||||
.compatible = "ti,dra7-pcie-ep",
|
||||
.data = &dra7xx_pcie_ep_of_data,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
/*
|
||||
* dra7xx_pcie_ep_unaligned_memaccess: workaround for AM572x/AM571x Errata i870
|
||||
* @dra7xx: the dra7xx device where the workaround should be applied
|
||||
*
|
||||
* Access to the PCIe slave port that are not 32-bit aligned will result
|
||||
* in incorrect mapping to TLP Address and Byte enable fields. Therefore,
|
||||
* byte and half-word accesses are not possible to byte offset 0x1, 0x2, or
|
||||
* 0x3.
|
||||
*
|
||||
* To avoid this issue set PCIE_SS1_AXI2OCP_LEGACY_MODE_ENABLE to 1.
|
||||
*/
|
||||
static int dra7xx_pcie_ep_unaligned_memaccess(struct device *dev)
|
||||
{
|
||||
int ret;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct of_phandle_args args;
|
||||
struct regmap *regmap;
|
||||
|
||||
regmap = syscon_regmap_lookup_by_phandle(np,
|
||||
"ti,syscon-unaligned-access");
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_dbg(dev, "can't get ti,syscon-unaligned-access\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = of_parse_phandle_with_fixed_args(np, "ti,syscon-unaligned-access",
|
||||
2, 0, &args);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to parse ti,syscon-unaligned-access\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_update_bits(regmap, args.args[0], args.args[1],
|
||||
args.args[1]);
|
||||
if (ret)
|
||||
dev_err(dev, "failed to enable unaligned access\n");
|
||||
|
||||
of_node_put(args.np);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __init dra7xx_pcie_probe(struct platform_device *pdev)
|
||||
{
|
||||
u32 reg;
|
||||
@@ -388,6 +589,16 @@ static int __init dra7xx_pcie_probe(struct platform_device *pdev)
|
||||
struct device_node *np = dev->of_node;
|
||||
char name[10];
|
||||
struct gpio_desc *reset;
|
||||
const struct of_device_id *match;
|
||||
const struct dra7xx_pcie_of_data *data;
|
||||
enum dw_pcie_device_mode mode;
|
||||
|
||||
match = of_match_device(of_match_ptr(of_dra7xx_pcie_match), dev);
|
||||
if (!match)
|
||||
return -EINVAL;
|
||||
|
||||
data = (struct dra7xx_pcie_of_data *)match->data;
|
||||
mode = (enum dw_pcie_device_mode)data->mode;
|
||||
|
||||
dra7xx = devm_kzalloc(dev, sizeof(*dra7xx), GFP_KERNEL);
|
||||
if (!dra7xx)
|
||||
@@ -409,13 +620,6 @@ static int __init dra7xx_pcie_probe(struct platform_device *pdev)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(dev, irq, dra7xx_pcie_irq_handler,
|
||||
IRQF_SHARED, "dra7xx-pcie-main", dra7xx);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to request irq\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ti_conf");
|
||||
base = devm_ioremap_nocache(dev, res->start, resource_size(res));
|
||||
if (!base)
|
||||
@@ -473,9 +677,37 @@ static int __init dra7xx_pcie_probe(struct platform_device *pdev)
|
||||
if (dra7xx->link_gen < 0 || dra7xx->link_gen > 2)
|
||||
dra7xx->link_gen = 2;
|
||||
|
||||
ret = dra7xx_add_pcie_port(dra7xx, pdev);
|
||||
if (ret < 0)
|
||||
switch (mode) {
|
||||
case DW_PCIE_RC_TYPE:
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_TI_CONF_DEVICE_TYPE,
|
||||
DEVICE_TYPE_RC);
|
||||
ret = dra7xx_add_pcie_port(dra7xx, pdev);
|
||||
if (ret < 0)
|
||||
goto err_gpio;
|
||||
break;
|
||||
case DW_PCIE_EP_TYPE:
|
||||
dra7xx_pcie_writel(dra7xx, PCIECTRL_TI_CONF_DEVICE_TYPE,
|
||||
DEVICE_TYPE_EP);
|
||||
|
||||
ret = dra7xx_pcie_ep_unaligned_memaccess(dev);
|
||||
if (ret)
|
||||
goto err_gpio;
|
||||
|
||||
ret = dra7xx_add_pcie_ep(dra7xx, pdev);
|
||||
if (ret < 0)
|
||||
goto err_gpio;
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "INVALID device type %d\n", mode);
|
||||
}
|
||||
dra7xx->mode = mode;
|
||||
|
||||
ret = devm_request_irq(dev, irq, dra7xx_pcie_irq_handler,
|
||||
IRQF_SHARED, "dra7xx-pcie-main", dra7xx);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to request irq\n");
|
||||
goto err_gpio;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -496,6 +728,9 @@ static int dra7xx_pcie_suspend(struct device *dev)
|
||||
struct dw_pcie *pci = dra7xx->pci;
|
||||
u32 val;
|
||||
|
||||
if (dra7xx->mode != DW_PCIE_RC_TYPE)
|
||||
return 0;
|
||||
|
||||
/* clear MSE */
|
||||
val = dw_pcie_readl_dbi(pci, PCI_COMMAND);
|
||||
val &= ~PCI_COMMAND_MEMORY;
|
||||
@@ -510,6 +745,9 @@ static int dra7xx_pcie_resume(struct device *dev)
|
||||
struct dw_pcie *pci = dra7xx->pci;
|
||||
u32 val;
|
||||
|
||||
if (dra7xx->mode != DW_PCIE_RC_TYPE)
|
||||
return 0;
|
||||
|
||||
/* set MSE */
|
||||
val = dw_pcie_readl_dbi(pci, PCI_COMMAND);
|
||||
val |= PCI_COMMAND_MEMORY;
|
||||
@@ -548,11 +786,6 @@ static const struct dev_pm_ops dra7xx_pcie_pm_ops = {
|
||||
dra7xx_pcie_resume_noirq)
|
||||
};
|
||||
|
||||
static const struct of_device_id of_dra7xx_pcie_match[] = {
|
||||
{ .compatible = "ti,dra7-pcie", },
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver dra7xx_pcie_driver = {
|
||||
.driver = {
|
||||
.name = "dra7-pcie",
|
||||
|
@@ -521,23 +521,25 @@ static void exynos_pcie_enable_interrupts(struct exynos_pcie *ep)
|
||||
exynos_pcie_msi_init(ep);
|
||||
}
|
||||
|
||||
static u32 exynos_pcie_readl_dbi(struct dw_pcie *pci, u32 reg)
|
||||
static u32 exynos_pcie_read_dbi(struct dw_pcie *pci, void __iomem *base,
|
||||
u32 reg, size_t size)
|
||||
{
|
||||
struct exynos_pcie *ep = to_exynos_pcie(pci);
|
||||
u32 val;
|
||||
|
||||
exynos_pcie_sideband_dbi_r_mode(ep, true);
|
||||
val = readl(pci->dbi_base + reg);
|
||||
dw_pcie_read(base + reg, size, &val);
|
||||
exynos_pcie_sideband_dbi_r_mode(ep, false);
|
||||
return val;
|
||||
}
|
||||
|
||||
static void exynos_pcie_writel_dbi(struct dw_pcie *pci, u32 reg, u32 val)
|
||||
static void exynos_pcie_write_dbi(struct dw_pcie *pci, void __iomem *base,
|
||||
u32 reg, size_t size, u32 val)
|
||||
{
|
||||
struct exynos_pcie *ep = to_exynos_pcie(pci);
|
||||
|
||||
exynos_pcie_sideband_dbi_w_mode(ep, true);
|
||||
writel(val, pci->dbi_base + reg);
|
||||
dw_pcie_write(base + reg, size, val);
|
||||
exynos_pcie_sideband_dbi_w_mode(ep, false);
|
||||
}
|
||||
|
||||
@@ -644,8 +646,8 @@ static int __init exynos_add_pcie_port(struct exynos_pcie *ep,
|
||||
}
|
||||
|
||||
static const struct dw_pcie_ops dw_pcie_ops = {
|
||||
.readl_dbi = exynos_pcie_readl_dbi,
|
||||
.writel_dbi = exynos_pcie_writel_dbi,
|
||||
.read_dbi = exynos_pcie_read_dbi,
|
||||
.write_dbi = exynos_pcie_write_dbi,
|
||||
.link_up = exynos_pcie_link_up,
|
||||
};
|
||||
|
||||
|
@@ -17,6 +17,7 @@
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
|
||||
#include <linux/mfd/syscon/imx7-iomuxc-gpr.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/of_device.h>
|
||||
@@ -27,6 +28,7 @@
|
||||
#include <linux/signal.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#include "pcie-designware.h"
|
||||
|
||||
@@ -36,6 +38,7 @@ enum imx6_pcie_variants {
|
||||
IMX6Q,
|
||||
IMX6SX,
|
||||
IMX6QP,
|
||||
IMX7D,
|
||||
};
|
||||
|
||||
struct imx6_pcie {
|
||||
@@ -47,6 +50,8 @@ struct imx6_pcie {
|
||||
struct clk *pcie_inbound_axi;
|
||||
struct clk *pcie;
|
||||
struct regmap *iomuxc_gpr;
|
||||
struct reset_control *pciephy_reset;
|
||||
struct reset_control *apps_reset;
|
||||
enum imx6_pcie_variants variant;
|
||||
u32 tx_deemph_gen1;
|
||||
u32 tx_deemph_gen2_3p5db;
|
||||
@@ -56,6 +61,11 @@ struct imx6_pcie {
|
||||
int link_gen;
|
||||
};
|
||||
|
||||
/* Parameters for the waiting for PCIe PHY PLL to lock on i.MX7 */
|
||||
#define PHY_PLL_LOCK_WAIT_MAX_RETRIES 2000
|
||||
#define PHY_PLL_LOCK_WAIT_USLEEP_MIN 50
|
||||
#define PHY_PLL_LOCK_WAIT_USLEEP_MAX 200
|
||||
|
||||
/* PCIe Root Complex registers (memory-mapped) */
|
||||
#define PCIE_RC_LCR 0x7c
|
||||
#define PCIE_RC_LCR_MAX_LINK_SPEEDS_GEN1 0x1
|
||||
@@ -248,6 +258,10 @@ static int imx6q_pcie_abort_handler(unsigned long addr,
|
||||
static void imx6_pcie_assert_core_reset(struct imx6_pcie *imx6_pcie)
|
||||
{
|
||||
switch (imx6_pcie->variant) {
|
||||
case IMX7D:
|
||||
reset_control_assert(imx6_pcie->pciephy_reset);
|
||||
reset_control_assert(imx6_pcie->apps_reset);
|
||||
break;
|
||||
case IMX6SX:
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
|
||||
IMX6SX_GPR12_PCIE_TEST_POWERDOWN,
|
||||
@@ -303,11 +317,32 @@ static int imx6_pcie_enable_ref_clk(struct imx6_pcie *imx6_pcie)
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1,
|
||||
IMX6Q_GPR1_PCIE_REF_CLK_EN, 1 << 16);
|
||||
break;
|
||||
case IMX7D:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void imx7d_pcie_wait_for_phy_pll_lock(struct imx6_pcie *imx6_pcie)
|
||||
{
|
||||
u32 val;
|
||||
unsigned int retries;
|
||||
struct device *dev = imx6_pcie->pci->dev;
|
||||
|
||||
for (retries = 0; retries < PHY_PLL_LOCK_WAIT_MAX_RETRIES; retries++) {
|
||||
regmap_read(imx6_pcie->iomuxc_gpr, IOMUXC_GPR22, &val);
|
||||
|
||||
if (val & IMX7D_GPR22_PCIE_PHY_PLL_LOCKED)
|
||||
return;
|
||||
|
||||
usleep_range(PHY_PLL_LOCK_WAIT_USLEEP_MIN,
|
||||
PHY_PLL_LOCK_WAIT_USLEEP_MAX);
|
||||
}
|
||||
|
||||
dev_err(dev, "PCIe PLL lock timeout\n");
|
||||
}
|
||||
|
||||
static void imx6_pcie_deassert_core_reset(struct imx6_pcie *imx6_pcie)
|
||||
{
|
||||
struct dw_pcie *pci = imx6_pcie->pci;
|
||||
@@ -351,6 +386,10 @@ static void imx6_pcie_deassert_core_reset(struct imx6_pcie *imx6_pcie)
|
||||
}
|
||||
|
||||
switch (imx6_pcie->variant) {
|
||||
case IMX7D:
|
||||
reset_control_deassert(imx6_pcie->pciephy_reset);
|
||||
imx7d_pcie_wait_for_phy_pll_lock(imx6_pcie);
|
||||
break;
|
||||
case IMX6SX:
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR5,
|
||||
IMX6SX_GPR5_PCIE_BTNRST_RESET, 0);
|
||||
@@ -377,35 +416,44 @@ err_pcie_bus:
|
||||
|
||||
static void imx6_pcie_init_phy(struct imx6_pcie *imx6_pcie)
|
||||
{
|
||||
if (imx6_pcie->variant == IMX6SX)
|
||||
switch (imx6_pcie->variant) {
|
||||
case IMX7D:
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
|
||||
IMX7D_GPR12_PCIE_PHY_REFCLK_SEL, 0);
|
||||
break;
|
||||
case IMX6SX:
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
|
||||
IMX6SX_GPR12_PCIE_RX_EQ_MASK,
|
||||
IMX6SX_GPR12_PCIE_RX_EQ_2);
|
||||
/* FALLTHROUGH */
|
||||
default:
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
|
||||
IMX6Q_GPR12_PCIE_CTL_2, 0 << 10);
|
||||
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
|
||||
IMX6Q_GPR12_PCIE_CTL_2, 0 << 10);
|
||||
/* configure constant input signal to the pcie ctrl and phy */
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
|
||||
IMX6Q_GPR12_LOS_LEVEL, 9 << 4);
|
||||
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
|
||||
IMX6Q_GPR8_TX_DEEMPH_GEN1,
|
||||
imx6_pcie->tx_deemph_gen1 << 0);
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
|
||||
IMX6Q_GPR8_TX_DEEMPH_GEN2_3P5DB,
|
||||
imx6_pcie->tx_deemph_gen2_3p5db << 6);
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
|
||||
IMX6Q_GPR8_TX_DEEMPH_GEN2_6DB,
|
||||
imx6_pcie->tx_deemph_gen2_6db << 12);
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
|
||||
IMX6Q_GPR8_TX_SWING_FULL,
|
||||
imx6_pcie->tx_swing_full << 18);
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
|
||||
IMX6Q_GPR8_TX_SWING_LOW,
|
||||
imx6_pcie->tx_swing_low << 25);
|
||||
break;
|
||||
}
|
||||
|
||||
/* configure constant input signal to the pcie ctrl and phy */
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
|
||||
IMX6Q_GPR12_DEVICE_TYPE, PCI_EXP_TYPE_ROOT_PORT << 12);
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
|
||||
IMX6Q_GPR12_LOS_LEVEL, 9 << 4);
|
||||
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
|
||||
IMX6Q_GPR8_TX_DEEMPH_GEN1,
|
||||
imx6_pcie->tx_deemph_gen1 << 0);
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
|
||||
IMX6Q_GPR8_TX_DEEMPH_GEN2_3P5DB,
|
||||
imx6_pcie->tx_deemph_gen2_3p5db << 6);
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
|
||||
IMX6Q_GPR8_TX_DEEMPH_GEN2_6DB,
|
||||
imx6_pcie->tx_deemph_gen2_6db << 12);
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
|
||||
IMX6Q_GPR8_TX_SWING_FULL,
|
||||
imx6_pcie->tx_swing_full << 18);
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
|
||||
IMX6Q_GPR8_TX_SWING_LOW,
|
||||
imx6_pcie->tx_swing_low << 25);
|
||||
}
|
||||
|
||||
static int imx6_pcie_wait_for_link(struct imx6_pcie *imx6_pcie)
|
||||
@@ -469,8 +517,11 @@ static int imx6_pcie_establish_link(struct imx6_pcie *imx6_pcie)
|
||||
dw_pcie_writel_dbi(pci, PCIE_RC_LCR, tmp);
|
||||
|
||||
/* Start LTSSM. */
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
|
||||
IMX6Q_GPR12_PCIE_CTL_2, 1 << 10);
|
||||
if (imx6_pcie->variant == IMX7D)
|
||||
reset_control_deassert(imx6_pcie->apps_reset);
|
||||
else
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
|
||||
IMX6Q_GPR12_PCIE_CTL_2, 1 << 10);
|
||||
|
||||
ret = imx6_pcie_wait_for_link(imx6_pcie);
|
||||
if (ret)
|
||||
@@ -482,31 +533,42 @@ static int imx6_pcie_establish_link(struct imx6_pcie *imx6_pcie)
|
||||
tmp &= ~PCIE_RC_LCR_MAX_LINK_SPEEDS_MASK;
|
||||
tmp |= PCIE_RC_LCR_MAX_LINK_SPEEDS_GEN2;
|
||||
dw_pcie_writel_dbi(pci, PCIE_RC_LCR, tmp);
|
||||
|
||||
/*
|
||||
* Start Directed Speed Change so the best possible
|
||||
* speed both link partners support can be negotiated.
|
||||
*/
|
||||
tmp = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL);
|
||||
tmp |= PORT_LOGIC_SPEED_CHANGE;
|
||||
dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, tmp);
|
||||
|
||||
if (imx6_pcie->variant != IMX7D) {
|
||||
/*
|
||||
* On i.MX7, DIRECT_SPEED_CHANGE behaves differently
|
||||
* from i.MX6 family when no link speed transition
|
||||
* occurs and we go Gen1 -> yep, Gen1. The difference
|
||||
* is that, in such case, it will not be cleared by HW
|
||||
* which will cause the following code to report false
|
||||
* failure.
|
||||
*/
|
||||
|
||||
ret = imx6_pcie_wait_for_speed_change(imx6_pcie);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to bring link up!\n");
|
||||
goto err_reset_phy;
|
||||
}
|
||||
}
|
||||
|
||||
/* Make sure link training is finished as well! */
|
||||
ret = imx6_pcie_wait_for_link(imx6_pcie);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to bring link up!\n");
|
||||
goto err_reset_phy;
|
||||
}
|
||||
} else {
|
||||
dev_info(dev, "Link: Gen2 disabled\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Start Directed Speed Change so the best possible speed both link
|
||||
* partners support can be negotiated.
|
||||
*/
|
||||
tmp = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL);
|
||||
tmp |= PORT_LOGIC_SPEED_CHANGE;
|
||||
dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, tmp);
|
||||
|
||||
ret = imx6_pcie_wait_for_speed_change(imx6_pcie);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to bring link up!\n");
|
||||
goto err_reset_phy;
|
||||
}
|
||||
|
||||
/* Make sure link training is finished as well! */
|
||||
ret = imx6_pcie_wait_for_link(imx6_pcie);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to bring link up!\n");
|
||||
goto err_reset_phy;
|
||||
}
|
||||
|
||||
tmp = dw_pcie_readl_dbi(pci, PCIE_RC_LCSR);
|
||||
dev_info(dev, "Link up, Gen%i\n", (tmp >> 16) & 0xf);
|
||||
return 0;
|
||||
@@ -544,8 +606,8 @@ static struct dw_pcie_host_ops imx6_pcie_host_ops = {
|
||||
.host_init = imx6_pcie_host_init,
|
||||
};
|
||||
|
||||
static int __init imx6_add_pcie_port(struct imx6_pcie *imx6_pcie,
|
||||
struct platform_device *pdev)
|
||||
static int imx6_add_pcie_port(struct imx6_pcie *imx6_pcie,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
struct dw_pcie *pci = imx6_pcie->pci;
|
||||
struct pcie_port *pp = &pci->pp;
|
||||
@@ -585,7 +647,7 @@ static const struct dw_pcie_ops dw_pcie_ops = {
|
||||
.link_up = imx6_pcie_link_up,
|
||||
};
|
||||
|
||||
static int __init imx6_pcie_probe(struct platform_device *pdev)
|
||||
static int imx6_pcie_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct dw_pcie *pci;
|
||||
@@ -609,10 +671,6 @@ static int __init imx6_pcie_probe(struct platform_device *pdev)
|
||||
imx6_pcie->variant =
|
||||
(enum imx6_pcie_variants)of_device_get_match_data(dev);
|
||||
|
||||
/* Added for PCI abort handling */
|
||||
hook_fault_code(16 + 6, imx6q_pcie_abort_handler, SIGBUS, 0,
|
||||
"imprecise external abort");
|
||||
|
||||
dbi_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
pci->dbi_base = devm_ioremap_resource(dev, dbi_base);
|
||||
if (IS_ERR(pci->dbi_base))
|
||||
@@ -632,6 +690,8 @@ static int __init imx6_pcie_probe(struct platform_device *pdev)
|
||||
dev_err(dev, "unable to get reset gpio\n");
|
||||
return ret;
|
||||
}
|
||||
} else if (imx6_pcie->reset_gpio == -EPROBE_DEFER) {
|
||||
return imx6_pcie->reset_gpio;
|
||||
}
|
||||
|
||||
/* Fetch clocks */
|
||||
@@ -653,13 +713,31 @@ static int __init imx6_pcie_probe(struct platform_device *pdev)
|
||||
return PTR_ERR(imx6_pcie->pcie);
|
||||
}
|
||||
|
||||
if (imx6_pcie->variant == IMX6SX) {
|
||||
switch (imx6_pcie->variant) {
|
||||
case IMX6SX:
|
||||
imx6_pcie->pcie_inbound_axi = devm_clk_get(dev,
|
||||
"pcie_inbound_axi");
|
||||
if (IS_ERR(imx6_pcie->pcie_inbound_axi)) {
|
||||
dev_err(dev, "pcie_inbound_axi clock missing or invalid\n");
|
||||
return PTR_ERR(imx6_pcie->pcie_inbound_axi);
|
||||
}
|
||||
break;
|
||||
case IMX7D:
|
||||
imx6_pcie->pciephy_reset = devm_reset_control_get(dev,
|
||||
"pciephy");
|
||||
if (IS_ERR(imx6_pcie->pciephy_reset)) {
|
||||
dev_err(dev, "Failed to get PCIEPHY reset control\n");
|
||||
return PTR_ERR(imx6_pcie->pciephy_reset);
|
||||
}
|
||||
|
||||
imx6_pcie->apps_reset = devm_reset_control_get(dev, "apps");
|
||||
if (IS_ERR(imx6_pcie->apps_reset)) {
|
||||
dev_err(dev, "Failed to get PCIE APPS reset control\n");
|
||||
return PTR_ERR(imx6_pcie->apps_reset);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Grab GPR config register range */
|
||||
@@ -718,6 +796,7 @@ static const struct of_device_id imx6_pcie_of_match[] = {
|
||||
{ .compatible = "fsl,imx6q-pcie", .data = (void *)IMX6Q, },
|
||||
{ .compatible = "fsl,imx6sx-pcie", .data = (void *)IMX6SX, },
|
||||
{ .compatible = "fsl,imx6qp-pcie", .data = (void *)IMX6QP, },
|
||||
{ .compatible = "fsl,imx7d-pcie", .data = (void *)IMX7D, },
|
||||
{},
|
||||
};
|
||||
|
||||
@@ -725,12 +804,24 @@ static struct platform_driver imx6_pcie_driver = {
|
||||
.driver = {
|
||||
.name = "imx6q-pcie",
|
||||
.of_match_table = imx6_pcie_of_match,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = imx6_pcie_probe,
|
||||
.shutdown = imx6_pcie_shutdown,
|
||||
};
|
||||
|
||||
static int __init imx6_pcie_init(void)
|
||||
{
|
||||
return platform_driver_probe(&imx6_pcie_driver, imx6_pcie_probe);
|
||||
/*
|
||||
* Since probe() can be deferred we need to make sure that
|
||||
* hook_fault_code is not called after __init memory is freed
|
||||
* by kernel and since imx6q_pcie_abort_handler() is a no-op,
|
||||
* we can install the handler here without risking it
|
||||
* accessing some uninitialized driver state.
|
||||
*/
|
||||
hook_fault_code(16 + 6, imx6q_pcie_abort_handler, SIGBUS, 0,
|
||||
"imprecise external abort");
|
||||
|
||||
return platform_driver_register(&imx6_pcie_driver);
|
||||
}
|
||||
device_initcall(imx6_pcie_init);
|
||||
|
@@ -543,7 +543,7 @@ int __init ks_dw_pcie_host_init(struct keystone_pcie *ks_pcie,
|
||||
|
||||
/* Index 0 is the config reg. space address */
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
pci->dbi_base = devm_ioremap_resource(dev, res);
|
||||
pci->dbi_base = devm_pci_remap_cfg_resource(dev, res);
|
||||
if (IS_ERR(pci->dbi_base))
|
||||
return PTR_ERR(pci->dbi_base);
|
||||
|
||||
|
@@ -283,7 +283,7 @@ static int __init ls_pcie_probe(struct platform_device *pdev)
|
||||
pcie->pci = pci;
|
||||
|
||||
dbi_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs");
|
||||
pci->dbi_base = devm_ioremap_resource(dev, dbi_base);
|
||||
pci->dbi_base = devm_pci_remap_cfg_resource(dev, dbi_base);
|
||||
if (IS_ERR(pci->dbi_base))
|
||||
return PTR_ERR(pci->dbi_base);
|
||||
|
||||
@@ -305,6 +305,7 @@ static struct platform_driver ls_pcie_driver = {
|
||||
.driver = {
|
||||
.name = "layerscape-pcie",
|
||||
.of_match_table = ls_pcie_of_match,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
};
|
||||
builtin_platform_driver_probe(ls_pcie_driver, ls_pcie_probe);
|
||||
|
@@ -230,7 +230,7 @@ static int armada8k_pcie_probe(struct platform_device *pdev)
|
||||
|
||||
/* Get the dw-pcie unit configuration/control registers base. */
|
||||
base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ctrl");
|
||||
pci->dbi_base = devm_ioremap_resource(dev, base);
|
||||
pci->dbi_base = devm_pci_remap_cfg_resource(dev, base);
|
||||
if (IS_ERR(pci->dbi_base)) {
|
||||
dev_err(dev, "couldn't remap regs base %p\n", base);
|
||||
ret = PTR_ERR(pci->dbi_base);
|
||||
@@ -262,6 +262,7 @@ static struct platform_driver armada8k_pcie_driver = {
|
||||
.driver = {
|
||||
.name = "armada8k-pcie",
|
||||
.of_match_table = of_match_ptr(armada8k_pcie_of_match),
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
};
|
||||
builtin_platform_driver(armada8k_pcie_driver);
|
||||
|
@@ -78,6 +78,11 @@ static void artpec6_pcie_writel(struct artpec6_pcie *artpec6_pcie, u32 offset, u
|
||||
regmap_write(artpec6_pcie->regmap, offset, val);
|
||||
}
|
||||
|
||||
static u64 artpec6_pcie_cpu_addr_fixup(u64 pci_addr)
|
||||
{
|
||||
return pci_addr & ARTPEC6_CPU_TO_BUS_ADDR;
|
||||
}
|
||||
|
||||
static int artpec6_pcie_establish_link(struct artpec6_pcie *artpec6_pcie)
|
||||
{
|
||||
struct dw_pcie *pci = artpec6_pcie->pci;
|
||||
@@ -142,11 +147,6 @@ static int artpec6_pcie_establish_link(struct artpec6_pcie *artpec6_pcie)
|
||||
*/
|
||||
dw_pcie_writel_dbi(pci, MISC_CONTROL_1_OFF, DBI_RO_WR_EN);
|
||||
|
||||
pp->io_base &= ARTPEC6_CPU_TO_BUS_ADDR;
|
||||
pp->mem_base &= ARTPEC6_CPU_TO_BUS_ADDR;
|
||||
pp->cfg0_base &= ARTPEC6_CPU_TO_BUS_ADDR;
|
||||
pp->cfg1_base &= ARTPEC6_CPU_TO_BUS_ADDR;
|
||||
|
||||
/* setup root complex */
|
||||
dw_pcie_setup_rc(pp);
|
||||
|
||||
@@ -235,6 +235,7 @@ static int artpec6_add_pcie_port(struct artpec6_pcie *artpec6_pcie,
|
||||
}
|
||||
|
||||
static const struct dw_pcie_ops dw_pcie_ops = {
|
||||
.cpu_addr_fixup = artpec6_pcie_cpu_addr_fixup,
|
||||
};
|
||||
|
||||
static int artpec6_pcie_probe(struct platform_device *pdev)
|
||||
@@ -294,6 +295,7 @@ static struct platform_driver artpec6_pcie_driver = {
|
||||
.driver = {
|
||||
.name = "artpec6-pcie",
|
||||
.of_match_table = artpec6_pcie_of_match,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
};
|
||||
builtin_platform_driver(artpec6_pcie_driver);
|
||||
|
342
drivers/pci/dwc/pcie-designware-ep.c
Normal file
342
drivers/pci/dwc/pcie-designware-ep.c
Normal file
@@ -0,0 +1,342 @@
|
||||
/**
|
||||
* Synopsys Designware PCIe Endpoint controller driver
|
||||
*
|
||||
* Copyright (C) 2017 Texas Instruments
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 of
|
||||
* the License as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/of.h>
|
||||
|
||||
#include "pcie-designware.h"
|
||||
#include <linux/pci-epc.h>
|
||||
#include <linux/pci-epf.h>
|
||||
|
||||
void dw_pcie_ep_linkup(struct dw_pcie_ep *ep)
|
||||
{
|
||||
struct pci_epc *epc = ep->epc;
|
||||
|
||||
pci_epc_linkup(epc);
|
||||
}
|
||||
|
||||
static void dw_pcie_ep_reset_bar(struct dw_pcie *pci, enum pci_barno bar)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
reg = PCI_BASE_ADDRESS_0 + (4 * bar);
|
||||
dw_pcie_writel_dbi2(pci, reg, 0x0);
|
||||
dw_pcie_writel_dbi(pci, reg, 0x0);
|
||||
}
|
||||
|
||||
static int dw_pcie_ep_write_header(struct pci_epc *epc,
|
||||
struct pci_epf_header *hdr)
|
||||
{
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
|
||||
dw_pcie_writew_dbi(pci, PCI_VENDOR_ID, hdr->vendorid);
|
||||
dw_pcie_writew_dbi(pci, PCI_DEVICE_ID, hdr->deviceid);
|
||||
dw_pcie_writeb_dbi(pci, PCI_REVISION_ID, hdr->revid);
|
||||
dw_pcie_writeb_dbi(pci, PCI_CLASS_PROG, hdr->progif_code);
|
||||
dw_pcie_writew_dbi(pci, PCI_CLASS_DEVICE,
|
||||
hdr->subclass_code | hdr->baseclass_code << 8);
|
||||
dw_pcie_writeb_dbi(pci, PCI_CACHE_LINE_SIZE,
|
||||
hdr->cache_line_size);
|
||||
dw_pcie_writew_dbi(pci, PCI_SUBSYSTEM_VENDOR_ID,
|
||||
hdr->subsys_vendor_id);
|
||||
dw_pcie_writew_dbi(pci, PCI_SUBSYSTEM_ID, hdr->subsys_id);
|
||||
dw_pcie_writeb_dbi(pci, PCI_INTERRUPT_PIN,
|
||||
hdr->interrupt_pin);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_pcie_ep_inbound_atu(struct dw_pcie_ep *ep, enum pci_barno bar,
|
||||
dma_addr_t cpu_addr,
|
||||
enum dw_pcie_as_type as_type)
|
||||
{
|
||||
int ret;
|
||||
u32 free_win;
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
|
||||
free_win = find_first_zero_bit(&ep->ib_window_map,
|
||||
sizeof(ep->ib_window_map));
|
||||
if (free_win >= ep->num_ib_windows) {
|
||||
dev_err(pci->dev, "no free inbound window\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = dw_pcie_prog_inbound_atu(pci, free_win, bar, cpu_addr,
|
||||
as_type);
|
||||
if (ret < 0) {
|
||||
dev_err(pci->dev, "Failed to program IB window\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ep->bar_to_atu[bar] = free_win;
|
||||
set_bit(free_win, &ep->ib_window_map);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_pcie_ep_outbound_atu(struct dw_pcie_ep *ep, phys_addr_t phys_addr,
|
||||
u64 pci_addr, size_t size)
|
||||
{
|
||||
u32 free_win;
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
|
||||
free_win = find_first_zero_bit(&ep->ob_window_map,
|
||||
sizeof(ep->ob_window_map));
|
||||
if (free_win >= ep->num_ob_windows) {
|
||||
dev_err(pci->dev, "no free outbound window\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dw_pcie_prog_outbound_atu(pci, free_win, PCIE_ATU_TYPE_MEM,
|
||||
phys_addr, pci_addr, size);
|
||||
|
||||
set_bit(free_win, &ep->ob_window_map);
|
||||
ep->outbound_addr[free_win] = phys_addr;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dw_pcie_ep_clear_bar(struct pci_epc *epc, enum pci_barno bar)
|
||||
{
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
u32 atu_index = ep->bar_to_atu[bar];
|
||||
|
||||
dw_pcie_ep_reset_bar(pci, bar);
|
||||
|
||||
dw_pcie_disable_atu(pci, atu_index, DW_PCIE_REGION_INBOUND);
|
||||
clear_bit(atu_index, &ep->ib_window_map);
|
||||
}
|
||||
|
||||
static int dw_pcie_ep_set_bar(struct pci_epc *epc, enum pci_barno bar,
|
||||
dma_addr_t bar_phys, size_t size, int flags)
|
||||
{
|
||||
int ret;
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
enum dw_pcie_as_type as_type;
|
||||
u32 reg = PCI_BASE_ADDRESS_0 + (4 * bar);
|
||||
|
||||
if (!(flags & PCI_BASE_ADDRESS_SPACE))
|
||||
as_type = DW_PCIE_AS_MEM;
|
||||
else
|
||||
as_type = DW_PCIE_AS_IO;
|
||||
|
||||
ret = dw_pcie_ep_inbound_atu(ep, bar, bar_phys, as_type);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dw_pcie_writel_dbi2(pci, reg, size - 1);
|
||||
dw_pcie_writel_dbi(pci, reg, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_pcie_find_index(struct dw_pcie_ep *ep, phys_addr_t addr,
|
||||
u32 *atu_index)
|
||||
{
|
||||
u32 index;
|
||||
|
||||
for (index = 0; index < ep->num_ob_windows; index++) {
|
||||
if (ep->outbound_addr[index] != addr)
|
||||
continue;
|
||||
*atu_index = index;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void dw_pcie_ep_unmap_addr(struct pci_epc *epc, phys_addr_t addr)
|
||||
{
|
||||
int ret;
|
||||
u32 atu_index;
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
|
||||
ret = dw_pcie_find_index(ep, addr, &atu_index);
|
||||
if (ret < 0)
|
||||
return;
|
||||
|
||||
dw_pcie_disable_atu(pci, atu_index, DW_PCIE_REGION_OUTBOUND);
|
||||
clear_bit(atu_index, &ep->ob_window_map);
|
||||
}
|
||||
|
||||
static int dw_pcie_ep_map_addr(struct pci_epc *epc, phys_addr_t addr,
|
||||
u64 pci_addr, size_t size)
|
||||
{
|
||||
int ret;
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
|
||||
ret = dw_pcie_ep_outbound_atu(ep, addr, pci_addr, size);
|
||||
if (ret) {
|
||||
dev_err(pci->dev, "failed to enable address\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_pcie_ep_get_msi(struct pci_epc *epc)
|
||||
{
|
||||
int val;
|
||||
u32 lower_addr;
|
||||
u32 upper_addr;
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
|
||||
val = dw_pcie_readb_dbi(pci, MSI_MESSAGE_CONTROL);
|
||||
val = (val & MSI_CAP_MME_MASK) >> MSI_CAP_MME_SHIFT;
|
||||
|
||||
lower_addr = dw_pcie_readl_dbi(pci, MSI_MESSAGE_ADDR_L32);
|
||||
upper_addr = dw_pcie_readl_dbi(pci, MSI_MESSAGE_ADDR_U32);
|
||||
|
||||
if (!(lower_addr || upper_addr))
|
||||
return -EINVAL;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static int dw_pcie_ep_set_msi(struct pci_epc *epc, u8 encode_int)
|
||||
{
|
||||
int val;
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
|
||||
val = (encode_int << MSI_CAP_MMC_SHIFT);
|
||||
dw_pcie_writew_dbi(pci, MSI_MESSAGE_CONTROL, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_pcie_ep_raise_irq(struct pci_epc *epc,
|
||||
enum pci_epc_irq_type type, u8 interrupt_num)
|
||||
{
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
|
||||
if (!ep->ops->raise_irq)
|
||||
return -EINVAL;
|
||||
|
||||
return ep->ops->raise_irq(ep, type, interrupt_num);
|
||||
}
|
||||
|
||||
static void dw_pcie_ep_stop(struct pci_epc *epc)
|
||||
{
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
|
||||
if (!pci->ops->stop_link)
|
||||
return;
|
||||
|
||||
pci->ops->stop_link(pci);
|
||||
}
|
||||
|
||||
static int dw_pcie_ep_start(struct pci_epc *epc)
|
||||
{
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
|
||||
if (!pci->ops->start_link)
|
||||
return -EINVAL;
|
||||
|
||||
return pci->ops->start_link(pci);
|
||||
}
|
||||
|
||||
static const struct pci_epc_ops epc_ops = {
|
||||
.write_header = dw_pcie_ep_write_header,
|
||||
.set_bar = dw_pcie_ep_set_bar,
|
||||
.clear_bar = dw_pcie_ep_clear_bar,
|
||||
.map_addr = dw_pcie_ep_map_addr,
|
||||
.unmap_addr = dw_pcie_ep_unmap_addr,
|
||||
.set_msi = dw_pcie_ep_set_msi,
|
||||
.get_msi = dw_pcie_ep_get_msi,
|
||||
.raise_irq = dw_pcie_ep_raise_irq,
|
||||
.start = dw_pcie_ep_start,
|
||||
.stop = dw_pcie_ep_stop,
|
||||
};
|
||||
|
||||
void dw_pcie_ep_exit(struct dw_pcie_ep *ep)
|
||||
{
|
||||
struct pci_epc *epc = ep->epc;
|
||||
|
||||
pci_epc_mem_exit(epc);
|
||||
}
|
||||
|
||||
int dw_pcie_ep_init(struct dw_pcie_ep *ep)
|
||||
{
|
||||
int ret;
|
||||
void *addr;
|
||||
enum pci_barno bar;
|
||||
struct pci_epc *epc;
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
struct device *dev = pci->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
|
||||
if (!pci->dbi_base || !pci->dbi_base2) {
|
||||
dev_err(dev, "dbi_base/deb_base2 is not populated\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(np, "num-ib-windows", &ep->num_ib_windows);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "unable to read *num-ib-windows* property\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(np, "num-ob-windows", &ep->num_ob_windows);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "unable to read *num-ob-windows* property\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
addr = devm_kzalloc(dev, sizeof(phys_addr_t) * ep->num_ob_windows,
|
||||
GFP_KERNEL);
|
||||
if (!addr)
|
||||
return -ENOMEM;
|
||||
ep->outbound_addr = addr;
|
||||
|
||||
for (bar = BAR_0; bar <= BAR_5; bar++)
|
||||
dw_pcie_ep_reset_bar(pci, bar);
|
||||
|
||||
if (ep->ops->ep_init)
|
||||
ep->ops->ep_init(ep);
|
||||
|
||||
epc = devm_pci_epc_create(dev, &epc_ops);
|
||||
if (IS_ERR(epc)) {
|
||||
dev_err(dev, "failed to create epc device\n");
|
||||
return PTR_ERR(epc);
|
||||
}
|
||||
|
||||
ret = of_property_read_u8(np, "max-functions", &epc->max_functions);
|
||||
if (ret < 0)
|
||||
epc->max_functions = 1;
|
||||
|
||||
ret = pci_epc_mem_init(epc, ep->phys_base, ep->addr_size);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to initialize address space\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ep->epc = epc;
|
||||
epc_set_drvdata(epc, ep);
|
||||
dw_pcie_setup(pci);
|
||||
|
||||
return 0;
|
||||
}
|
@@ -56,24 +56,25 @@ static struct irq_chip dw_msi_irq_chip = {
|
||||
/* MSI int handler */
|
||||
irqreturn_t dw_handle_msi_irq(struct pcie_port *pp)
|
||||
{
|
||||
unsigned long val;
|
||||
u32 val;
|
||||
int i, pos, irq;
|
||||
irqreturn_t ret = IRQ_NONE;
|
||||
|
||||
for (i = 0; i < MAX_MSI_CTRLS; i++) {
|
||||
dw_pcie_rd_own_conf(pp, PCIE_MSI_INTR0_STATUS + i * 12, 4,
|
||||
(u32 *)&val);
|
||||
if (val) {
|
||||
ret = IRQ_HANDLED;
|
||||
pos = 0;
|
||||
while ((pos = find_next_bit(&val, 32, pos)) != 32) {
|
||||
irq = irq_find_mapping(pp->irq_domain,
|
||||
i * 32 + pos);
|
||||
dw_pcie_wr_own_conf(pp, PCIE_MSI_INTR0_STATUS +
|
||||
i * 12, 4, 1 << pos);
|
||||
generic_handle_irq(irq);
|
||||
pos++;
|
||||
}
|
||||
&val);
|
||||
if (!val)
|
||||
continue;
|
||||
|
||||
ret = IRQ_HANDLED;
|
||||
pos = 0;
|
||||
while ((pos = find_next_bit((unsigned long *) &val, 32,
|
||||
pos)) != 32) {
|
||||
irq = irq_find_mapping(pp->irq_domain, i * 32 + pos);
|
||||
dw_pcie_wr_own_conf(pp, PCIE_MSI_INTR0_STATUS + i * 12,
|
||||
4, 1 << pos);
|
||||
generic_handle_irq(irq);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,8 +339,9 @@ int dw_pcie_host_init(struct pcie_port *pp)
|
||||
}
|
||||
|
||||
if (!pci->dbi_base) {
|
||||
pci->dbi_base = devm_ioremap(dev, pp->cfg->start,
|
||||
resource_size(pp->cfg));
|
||||
pci->dbi_base = devm_pci_remap_cfgspace(dev,
|
||||
pp->cfg->start,
|
||||
resource_size(pp->cfg));
|
||||
if (!pci->dbi_base) {
|
||||
dev_err(dev, "error with ioremap\n");
|
||||
ret = -ENOMEM;
|
||||
@@ -350,8 +352,8 @@ int dw_pcie_host_init(struct pcie_port *pp)
|
||||
pp->mem_base = pp->mem->start;
|
||||
|
||||
if (!pp->va_cfg0_base) {
|
||||
pp->va_cfg0_base = devm_ioremap(dev, pp->cfg0_base,
|
||||
pp->cfg0_size);
|
||||
pp->va_cfg0_base = devm_pci_remap_cfgspace(dev,
|
||||
pp->cfg0_base, pp->cfg0_size);
|
||||
if (!pp->va_cfg0_base) {
|
||||
dev_err(dev, "error with ioremap in function\n");
|
||||
ret = -ENOMEM;
|
||||
@@ -360,7 +362,8 @@ int dw_pcie_host_init(struct pcie_port *pp)
|
||||
}
|
||||
|
||||
if (!pp->va_cfg1_base) {
|
||||
pp->va_cfg1_base = devm_ioremap(dev, pp->cfg1_base,
|
||||
pp->va_cfg1_base = devm_pci_remap_cfgspace(dev,
|
||||
pp->cfg1_base,
|
||||
pp->cfg1_size);
|
||||
if (!pp->va_cfg1_base) {
|
||||
dev_err(dev, "error with ioremap\n");
|
||||
|
@@ -133,6 +133,7 @@ static struct platform_driver dw_plat_pcie_driver = {
|
||||
.driver = {
|
||||
.name = "dw-pcie",
|
||||
.of_match_table = dw_plat_pcie_of_match,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = dw_plat_pcie_probe,
|
||||
};
|
||||
|
@@ -61,91 +61,253 @@ int dw_pcie_write(void __iomem *addr, int size, u32 val)
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
|
||||
u32 dw_pcie_readl_dbi(struct dw_pcie *pci, u32 reg)
|
||||
u32 __dw_pcie_read_dbi(struct dw_pcie *pci, void __iomem *base, u32 reg,
|
||||
size_t size)
|
||||
{
|
||||
if (pci->ops->readl_dbi)
|
||||
return pci->ops->readl_dbi(pci, reg);
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
return readl(pci->dbi_base + reg);
|
||||
if (pci->ops->read_dbi)
|
||||
return pci->ops->read_dbi(pci, base, reg, size);
|
||||
|
||||
ret = dw_pcie_read(base + reg, size, &val);
|
||||
if (ret)
|
||||
dev_err(pci->dev, "read DBI address failed\n");
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
void dw_pcie_writel_dbi(struct dw_pcie *pci, u32 reg, u32 val)
|
||||
void __dw_pcie_write_dbi(struct dw_pcie *pci, void __iomem *base, u32 reg,
|
||||
size_t size, u32 val)
|
||||
{
|
||||
if (pci->ops->writel_dbi)
|
||||
pci->ops->writel_dbi(pci, reg, val);
|
||||
else
|
||||
writel(val, pci->dbi_base + reg);
|
||||
int ret;
|
||||
|
||||
if (pci->ops->write_dbi) {
|
||||
pci->ops->write_dbi(pci, base, reg, size, val);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = dw_pcie_write(base + reg, size, val);
|
||||
if (ret)
|
||||
dev_err(pci->dev, "write DBI address failed\n");
|
||||
}
|
||||
|
||||
static u32 dw_pcie_readl_unroll(struct dw_pcie *pci, u32 index, u32 reg)
|
||||
static u32 dw_pcie_readl_ob_unroll(struct dw_pcie *pci, u32 index, u32 reg)
|
||||
{
|
||||
u32 offset = PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index);
|
||||
|
||||
return dw_pcie_readl_dbi(pci, offset + reg);
|
||||
}
|
||||
|
||||
static void dw_pcie_writel_unroll(struct dw_pcie *pci, u32 index, u32 reg,
|
||||
u32 val)
|
||||
static void dw_pcie_writel_ob_unroll(struct dw_pcie *pci, u32 index, u32 reg,
|
||||
u32 val)
|
||||
{
|
||||
u32 offset = PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index);
|
||||
|
||||
dw_pcie_writel_dbi(pci, offset + reg, val);
|
||||
}
|
||||
|
||||
void dw_pcie_prog_outbound_atu_unroll(struct dw_pcie *pci, int index, int type,
|
||||
u64 cpu_addr, u64 pci_addr, u32 size)
|
||||
{
|
||||
u32 retries, val;
|
||||
|
||||
dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_BASE,
|
||||
lower_32_bits(cpu_addr));
|
||||
dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_BASE,
|
||||
upper_32_bits(cpu_addr));
|
||||
dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LIMIT,
|
||||
lower_32_bits(cpu_addr + size - 1));
|
||||
dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET,
|
||||
lower_32_bits(pci_addr));
|
||||
dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET,
|
||||
upper_32_bits(pci_addr));
|
||||
dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1,
|
||||
type);
|
||||
dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
|
||||
PCIE_ATU_ENABLE);
|
||||
|
||||
/*
|
||||
* Make sure ATU enable takes effect before any subsequent config
|
||||
* and I/O accesses.
|
||||
*/
|
||||
for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
|
||||
val = dw_pcie_readl_ob_unroll(pci, index,
|
||||
PCIE_ATU_UNR_REGION_CTRL2);
|
||||
if (val & PCIE_ATU_ENABLE)
|
||||
return;
|
||||
|
||||
usleep_range(LINK_WAIT_IATU_MIN, LINK_WAIT_IATU_MAX);
|
||||
}
|
||||
dev_err(pci->dev, "outbound iATU is not being enabled\n");
|
||||
}
|
||||
|
||||
void dw_pcie_prog_outbound_atu(struct dw_pcie *pci, int index, int type,
|
||||
u64 cpu_addr, u64 pci_addr, u32 size)
|
||||
{
|
||||
u32 retries, val;
|
||||
|
||||
if (pci->ops->cpu_addr_fixup)
|
||||
cpu_addr = pci->ops->cpu_addr_fixup(cpu_addr);
|
||||
|
||||
if (pci->iatu_unroll_enabled) {
|
||||
dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_LOWER_BASE,
|
||||
lower_32_bits(cpu_addr));
|
||||
dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_UPPER_BASE,
|
||||
upper_32_bits(cpu_addr));
|
||||
dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_LIMIT,
|
||||
lower_32_bits(cpu_addr + size - 1));
|
||||
dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET,
|
||||
lower_32_bits(pci_addr));
|
||||
dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET,
|
||||
upper_32_bits(pci_addr));
|
||||
dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1,
|
||||
type);
|
||||
dw_pcie_writel_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
|
||||
PCIE_ATU_ENABLE);
|
||||
} else {
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT,
|
||||
PCIE_ATU_REGION_OUTBOUND | index);
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_BASE,
|
||||
lower_32_bits(cpu_addr));
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_BASE,
|
||||
upper_32_bits(cpu_addr));
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_LIMIT,
|
||||
lower_32_bits(cpu_addr + size - 1));
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET,
|
||||
lower_32_bits(pci_addr));
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET,
|
||||
upper_32_bits(pci_addr));
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, type);
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE);
|
||||
dw_pcie_prog_outbound_atu_unroll(pci, index, type, cpu_addr,
|
||||
pci_addr, size);
|
||||
return;
|
||||
}
|
||||
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT,
|
||||
PCIE_ATU_REGION_OUTBOUND | index);
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_BASE,
|
||||
lower_32_bits(cpu_addr));
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_BASE,
|
||||
upper_32_bits(cpu_addr));
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_LIMIT,
|
||||
lower_32_bits(cpu_addr + size - 1));
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET,
|
||||
lower_32_bits(pci_addr));
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET,
|
||||
upper_32_bits(pci_addr));
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, type);
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE);
|
||||
|
||||
/*
|
||||
* Make sure ATU enable takes effect before any subsequent config
|
||||
* and I/O accesses.
|
||||
*/
|
||||
for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
|
||||
if (pci->iatu_unroll_enabled)
|
||||
val = dw_pcie_readl_unroll(pci, index,
|
||||
PCIE_ATU_UNR_REGION_CTRL2);
|
||||
else
|
||||
val = dw_pcie_readl_dbi(pci, PCIE_ATU_CR2);
|
||||
|
||||
val = dw_pcie_readl_dbi(pci, PCIE_ATU_CR2);
|
||||
if (val == PCIE_ATU_ENABLE)
|
||||
return;
|
||||
|
||||
usleep_range(LINK_WAIT_IATU_MIN, LINK_WAIT_IATU_MAX);
|
||||
}
|
||||
dev_err(pci->dev, "iATU is not being enabled\n");
|
||||
dev_err(pci->dev, "outbound iATU is not being enabled\n");
|
||||
}
|
||||
|
||||
static u32 dw_pcie_readl_ib_unroll(struct dw_pcie *pci, u32 index, u32 reg)
|
||||
{
|
||||
u32 offset = PCIE_GET_ATU_INB_UNR_REG_OFFSET(index);
|
||||
|
||||
return dw_pcie_readl_dbi(pci, offset + reg);
|
||||
}
|
||||
|
||||
static void dw_pcie_writel_ib_unroll(struct dw_pcie *pci, u32 index, u32 reg,
|
||||
u32 val)
|
||||
{
|
||||
u32 offset = PCIE_GET_ATU_INB_UNR_REG_OFFSET(index);
|
||||
|
||||
dw_pcie_writel_dbi(pci, offset + reg, val);
|
||||
}
|
||||
|
||||
int dw_pcie_prog_inbound_atu_unroll(struct dw_pcie *pci, int index, int bar,
|
||||
u64 cpu_addr, enum dw_pcie_as_type as_type)
|
||||
{
|
||||
int type;
|
||||
u32 retries, val;
|
||||
|
||||
dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET,
|
||||
lower_32_bits(cpu_addr));
|
||||
dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET,
|
||||
upper_32_bits(cpu_addr));
|
||||
|
||||
switch (as_type) {
|
||||
case DW_PCIE_AS_MEM:
|
||||
type = PCIE_ATU_TYPE_MEM;
|
||||
break;
|
||||
case DW_PCIE_AS_IO:
|
||||
type = PCIE_ATU_TYPE_IO;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, type);
|
||||
dw_pcie_writel_ib_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2,
|
||||
PCIE_ATU_ENABLE |
|
||||
PCIE_ATU_BAR_MODE_ENABLE | (bar << 8));
|
||||
|
||||
/*
|
||||
* Make sure ATU enable takes effect before any subsequent config
|
||||
* and I/O accesses.
|
||||
*/
|
||||
for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
|
||||
val = dw_pcie_readl_ib_unroll(pci, index,
|
||||
PCIE_ATU_UNR_REGION_CTRL2);
|
||||
if (val & PCIE_ATU_ENABLE)
|
||||
return 0;
|
||||
|
||||
usleep_range(LINK_WAIT_IATU_MIN, LINK_WAIT_IATU_MAX);
|
||||
}
|
||||
dev_err(pci->dev, "inbound iATU is not being enabled\n");
|
||||
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, int index, int bar,
|
||||
u64 cpu_addr, enum dw_pcie_as_type as_type)
|
||||
{
|
||||
int type;
|
||||
u32 retries, val;
|
||||
|
||||
if (pci->iatu_unroll_enabled)
|
||||
return dw_pcie_prog_inbound_atu_unroll(pci, index, bar,
|
||||
cpu_addr, as_type);
|
||||
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, PCIE_ATU_REGION_INBOUND |
|
||||
index);
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET, lower_32_bits(cpu_addr));
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET, upper_32_bits(cpu_addr));
|
||||
|
||||
switch (as_type) {
|
||||
case DW_PCIE_AS_MEM:
|
||||
type = PCIE_ATU_TYPE_MEM;
|
||||
break;
|
||||
case DW_PCIE_AS_IO:
|
||||
type = PCIE_ATU_TYPE_IO;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, type);
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE
|
||||
| PCIE_ATU_BAR_MODE_ENABLE | (bar << 8));
|
||||
|
||||
/*
|
||||
* Make sure ATU enable takes effect before any subsequent config
|
||||
* and I/O accesses.
|
||||
*/
|
||||
for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
|
||||
val = dw_pcie_readl_dbi(pci, PCIE_ATU_CR2);
|
||||
if (val & PCIE_ATU_ENABLE)
|
||||
return 0;
|
||||
|
||||
usleep_range(LINK_WAIT_IATU_MIN, LINK_WAIT_IATU_MAX);
|
||||
}
|
||||
dev_err(pci->dev, "inbound iATU is not being enabled\n");
|
||||
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
void dw_pcie_disable_atu(struct dw_pcie *pci, int index,
|
||||
enum dw_pcie_region_type type)
|
||||
{
|
||||
int region;
|
||||
|
||||
switch (type) {
|
||||
case DW_PCIE_REGION_INBOUND:
|
||||
region = PCIE_ATU_REGION_INBOUND;
|
||||
break;
|
||||
case DW_PCIE_REGION_OUTBOUND:
|
||||
region = PCIE_ATU_REGION_OUTBOUND;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, region | index);
|
||||
dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, ~PCIE_ATU_ENABLE);
|
||||
}
|
||||
|
||||
int dw_pcie_wait_for_link(struct dw_pcie *pci)
|
||||
|
@@ -18,6 +18,9 @@
|
||||
#include <linux/msi.h>
|
||||
#include <linux/pci.h>
|
||||
|
||||
#include <linux/pci-epc.h>
|
||||
#include <linux/pci-epf.h>
|
||||
|
||||
/* Parameters for the waiting for link up routine */
|
||||
#define LINK_WAIT_MAX_RETRIES 10
|
||||
#define LINK_WAIT_USLEEP_MIN 90000
|
||||
@@ -89,6 +92,16 @@
|
||||
#define PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(region) \
|
||||
((0x3 << 20) | ((region) << 9))
|
||||
|
||||
#define PCIE_GET_ATU_INB_UNR_REG_OFFSET(region) \
|
||||
((0x3 << 20) | ((region) << 9) | (0x1 << 8))
|
||||
|
||||
#define MSI_MESSAGE_CONTROL 0x52
|
||||
#define MSI_CAP_MMC_SHIFT 1
|
||||
#define MSI_CAP_MME_SHIFT 4
|
||||
#define MSI_CAP_MME_MASK (7 << MSI_CAP_MME_SHIFT)
|
||||
#define MSI_MESSAGE_ADDR_L32 0x54
|
||||
#define MSI_MESSAGE_ADDR_U32 0x58
|
||||
|
||||
/*
|
||||
* Maximum number of MSI IRQs can be 256 per controller. But keep
|
||||
* it 32 as of now. Probably we will never need more than 32. If needed,
|
||||
@@ -99,6 +112,20 @@
|
||||
|
||||
struct pcie_port;
|
||||
struct dw_pcie;
|
||||
struct dw_pcie_ep;
|
||||
|
||||
enum dw_pcie_region_type {
|
||||
DW_PCIE_REGION_UNKNOWN,
|
||||
DW_PCIE_REGION_INBOUND,
|
||||
DW_PCIE_REGION_OUTBOUND,
|
||||
};
|
||||
|
||||
enum dw_pcie_device_mode {
|
||||
DW_PCIE_UNKNOWN_TYPE,
|
||||
DW_PCIE_EP_TYPE,
|
||||
DW_PCIE_LEG_EP_TYPE,
|
||||
DW_PCIE_RC_TYPE,
|
||||
};
|
||||
|
||||
struct dw_pcie_host_ops {
|
||||
int (*rd_own_conf)(struct pcie_port *pp, int where, int size, u32 *val);
|
||||
@@ -142,35 +169,116 @@ struct pcie_port {
|
||||
DECLARE_BITMAP(msi_irq_in_use, MAX_MSI_IRQS);
|
||||
};
|
||||
|
||||
enum dw_pcie_as_type {
|
||||
DW_PCIE_AS_UNKNOWN,
|
||||
DW_PCIE_AS_MEM,
|
||||
DW_PCIE_AS_IO,
|
||||
};
|
||||
|
||||
struct dw_pcie_ep_ops {
|
||||
void (*ep_init)(struct dw_pcie_ep *ep);
|
||||
int (*raise_irq)(struct dw_pcie_ep *ep, enum pci_epc_irq_type type,
|
||||
u8 interrupt_num);
|
||||
};
|
||||
|
||||
struct dw_pcie_ep {
|
||||
struct pci_epc *epc;
|
||||
struct dw_pcie_ep_ops *ops;
|
||||
phys_addr_t phys_base;
|
||||
size_t addr_size;
|
||||
u8 bar_to_atu[6];
|
||||
phys_addr_t *outbound_addr;
|
||||
unsigned long ib_window_map;
|
||||
unsigned long ob_window_map;
|
||||
u32 num_ib_windows;
|
||||
u32 num_ob_windows;
|
||||
};
|
||||
|
||||
struct dw_pcie_ops {
|
||||
u32 (*readl_dbi)(struct dw_pcie *pcie, u32 reg);
|
||||
void (*writel_dbi)(struct dw_pcie *pcie, u32 reg, u32 val);
|
||||
u64 (*cpu_addr_fixup)(u64 cpu_addr);
|
||||
u32 (*read_dbi)(struct dw_pcie *pcie, void __iomem *base, u32 reg,
|
||||
size_t size);
|
||||
void (*write_dbi)(struct dw_pcie *pcie, void __iomem *base, u32 reg,
|
||||
size_t size, u32 val);
|
||||
int (*link_up)(struct dw_pcie *pcie);
|
||||
int (*start_link)(struct dw_pcie *pcie);
|
||||
void (*stop_link)(struct dw_pcie *pcie);
|
||||
};
|
||||
|
||||
struct dw_pcie {
|
||||
struct device *dev;
|
||||
void __iomem *dbi_base;
|
||||
void __iomem *dbi_base2;
|
||||
u32 num_viewport;
|
||||
u8 iatu_unroll_enabled;
|
||||
struct pcie_port pp;
|
||||
struct dw_pcie_ep ep;
|
||||
const struct dw_pcie_ops *ops;
|
||||
};
|
||||
|
||||
#define to_dw_pcie_from_pp(port) container_of((port), struct dw_pcie, pp)
|
||||
|
||||
#define to_dw_pcie_from_ep(endpoint) \
|
||||
container_of((endpoint), struct dw_pcie, ep)
|
||||
|
||||
int dw_pcie_read(void __iomem *addr, int size, u32 *val);
|
||||
int dw_pcie_write(void __iomem *addr, int size, u32 val);
|
||||
|
||||
u32 dw_pcie_readl_dbi(struct dw_pcie *pci, u32 reg);
|
||||
void dw_pcie_writel_dbi(struct dw_pcie *pci, u32 reg, u32 val);
|
||||
u32 __dw_pcie_read_dbi(struct dw_pcie *pci, void __iomem *base, u32 reg,
|
||||
size_t size);
|
||||
void __dw_pcie_write_dbi(struct dw_pcie *pci, void __iomem *base, u32 reg,
|
||||
size_t size, u32 val);
|
||||
int dw_pcie_link_up(struct dw_pcie *pci);
|
||||
int dw_pcie_wait_for_link(struct dw_pcie *pci);
|
||||
void dw_pcie_prog_outbound_atu(struct dw_pcie *pci, int index,
|
||||
int type, u64 cpu_addr, u64 pci_addr,
|
||||
u32 size);
|
||||
int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, int index, int bar,
|
||||
u64 cpu_addr, enum dw_pcie_as_type as_type);
|
||||
void dw_pcie_disable_atu(struct dw_pcie *pci, int index,
|
||||
enum dw_pcie_region_type type);
|
||||
void dw_pcie_setup(struct dw_pcie *pci);
|
||||
|
||||
static inline void dw_pcie_writel_dbi(struct dw_pcie *pci, u32 reg, u32 val)
|
||||
{
|
||||
__dw_pcie_write_dbi(pci, pci->dbi_base, reg, 0x4, val);
|
||||
}
|
||||
|
||||
static inline u32 dw_pcie_readl_dbi(struct dw_pcie *pci, u32 reg)
|
||||
{
|
||||
return __dw_pcie_read_dbi(pci, pci->dbi_base, reg, 0x4);
|
||||
}
|
||||
|
||||
static inline void dw_pcie_writew_dbi(struct dw_pcie *pci, u32 reg, u16 val)
|
||||
{
|
||||
__dw_pcie_write_dbi(pci, pci->dbi_base, reg, 0x2, val);
|
||||
}
|
||||
|
||||
static inline u16 dw_pcie_readw_dbi(struct dw_pcie *pci, u32 reg)
|
||||
{
|
||||
return __dw_pcie_read_dbi(pci, pci->dbi_base, reg, 0x2);
|
||||
}
|
||||
|
||||
static inline void dw_pcie_writeb_dbi(struct dw_pcie *pci, u32 reg, u8 val)
|
||||
{
|
||||
__dw_pcie_write_dbi(pci, pci->dbi_base, reg, 0x1, val);
|
||||
}
|
||||
|
||||
static inline u8 dw_pcie_readb_dbi(struct dw_pcie *pci, u32 reg)
|
||||
{
|
||||
return __dw_pcie_read_dbi(pci, pci->dbi_base, reg, 0x1);
|
||||
}
|
||||
|
||||
static inline void dw_pcie_writel_dbi2(struct dw_pcie *pci, u32 reg, u32 val)
|
||||
{
|
||||
__dw_pcie_write_dbi(pci, pci->dbi_base2, reg, 0x4, val);
|
||||
}
|
||||
|
||||
static inline u32 dw_pcie_readl_dbi2(struct dw_pcie *pci, u32 reg)
|
||||
{
|
||||
return __dw_pcie_read_dbi(pci, pci->dbi_base2, reg, 0x4);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PCIE_DW_HOST
|
||||
irqreturn_t dw_handle_msi_irq(struct pcie_port *pp);
|
||||
void dw_pcie_msi_init(struct pcie_port *pp);
|
||||
@@ -195,4 +303,23 @@ static inline int dw_pcie_host_init(struct pcie_port *pp)
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PCIE_DW_EP
|
||||
void dw_pcie_ep_linkup(struct dw_pcie_ep *ep);
|
||||
int dw_pcie_ep_init(struct dw_pcie_ep *ep);
|
||||
void dw_pcie_ep_exit(struct dw_pcie_ep *ep);
|
||||
#else
|
||||
static inline void dw_pcie_ep_linkup(struct dw_pcie_ep *ep)
|
||||
{
|
||||
}
|
||||
|
||||
static inline int dw_pcie_ep_init(struct dw_pcie_ep *ep)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void dw_pcie_ep_exit(struct dw_pcie_ep *ep)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
#endif /* _PCIE_DESIGNWARE_H */
|
||||
|
@@ -99,7 +99,7 @@ static int hisi_pcie_init(struct pci_config_window *cfg)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
reg_base = devm_ioremap(dev, res->start, resource_size(res));
|
||||
reg_base = devm_pci_remap_cfgspace(dev, res->start, resource_size(res));
|
||||
if (!reg_base)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -296,10 +296,9 @@ static int hisi_pcie_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
reg = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rc_dbi");
|
||||
pci->dbi_base = devm_ioremap_resource(dev, reg);
|
||||
pci->dbi_base = devm_pci_remap_cfg_resource(dev, reg);
|
||||
if (IS_ERR(pci->dbi_base))
|
||||
return PTR_ERR(pci->dbi_base);
|
||||
|
||||
platform_set_drvdata(pdev, hisi_pcie);
|
||||
|
||||
ret = hisi_add_pcie_port(hisi_pcie, pdev);
|
||||
@@ -334,6 +333,7 @@ static struct platform_driver hisi_pcie_driver = {
|
||||
.driver = {
|
||||
.name = "hisi-pcie",
|
||||
.of_match_table = hisi_pcie_of_match,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
};
|
||||
builtin_platform_driver(hisi_pcie_driver);
|
||||
@@ -360,7 +360,7 @@ static int hisi_pcie_platform_init(struct pci_config_window *cfg)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
reg_base = devm_ioremap(dev, res->start, resource_size(res));
|
||||
reg_base = devm_pci_remap_cfgspace(dev, res->start, resource_size(res));
|
||||
if (!reg_base)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -395,6 +395,7 @@ static struct platform_driver hisi_pcie_almost_ecam_driver = {
|
||||
.driver = {
|
||||
.name = "hisi-pcie-almost-ecam",
|
||||
.of_match_table = hisi_pcie_almost_ecam_of_match,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
};
|
||||
builtin_platform_driver(hisi_pcie_almost_ecam_driver);
|
||||
|
@@ -700,7 +700,7 @@ static int qcom_pcie_probe(struct platform_device *pdev)
|
||||
return PTR_ERR(pcie->parf);
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");
|
||||
pci->dbi_base = devm_ioremap_resource(dev, res);
|
||||
pci->dbi_base = devm_pci_remap_cfg_resource(dev, res);
|
||||
if (IS_ERR(pci->dbi_base))
|
||||
return PTR_ERR(pci->dbi_base);
|
||||
|
||||
|
@@ -273,7 +273,7 @@ static int spear13xx_pcie_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
dbi_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");
|
||||
pci->dbi_base = devm_ioremap_resource(dev, dbi_base);
|
||||
pci->dbi_base = devm_pci_remap_cfg_resource(dev, dbi_base);
|
||||
if (IS_ERR(pci->dbi_base)) {
|
||||
dev_err(dev, "couldn't remap dbi base %p\n", dbi_base);
|
||||
ret = PTR_ERR(pci->dbi_base);
|
||||
@@ -308,6 +308,7 @@ static struct platform_driver spear13xx_pcie_driver = {
|
||||
.driver = {
|
||||
.name = "spear-pcie",
|
||||
.of_match_table = of_match_ptr(spear13xx_pcie_of_match),
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
};
|
||||
|
||||
|
@@ -84,12 +84,14 @@ struct pci_config_window *pci_ecam_create(struct device *dev,
|
||||
if (!cfg->winp)
|
||||
goto err_exit_malloc;
|
||||
for (i = 0; i < bus_range; i++) {
|
||||
cfg->winp[i] = ioremap(cfgres->start + i * bsz, bsz);
|
||||
cfg->winp[i] =
|
||||
pci_remap_cfgspace(cfgres->start + i * bsz,
|
||||
bsz);
|
||||
if (!cfg->winp[i])
|
||||
goto err_exit_iomap;
|
||||
}
|
||||
} else {
|
||||
cfg->win = ioremap(cfgres->start, bus_range * bsz);
|
||||
cfg->win = pci_remap_cfgspace(cfgres->start, bus_range * bsz);
|
||||
if (!cfg->win)
|
||||
goto err_exit_iomap;
|
||||
}
|
||||
|
31
drivers/pci/endpoint/Kconfig
Normal file
31
drivers/pci/endpoint/Kconfig
Normal file
@@ -0,0 +1,31 @@
|
||||
#
|
||||
# PCI Endpoint Support
|
||||
#
|
||||
|
||||
menu "PCI Endpoint"
|
||||
|
||||
config PCI_ENDPOINT
|
||||
bool "PCI Endpoint Support"
|
||||
help
|
||||
Enable this configuration option to support configurable PCI
|
||||
endpoint. This should be enabled if the platform has a PCI
|
||||
controller that can operate in endpoint mode.
|
||||
|
||||
Enabling this option will build the endpoint library, which
|
||||
includes endpoint controller library and endpoint function
|
||||
library.
|
||||
|
||||
If in doubt, say "N" to disable Endpoint support.
|
||||
|
||||
config PCI_ENDPOINT_CONFIGFS
|
||||
bool "PCI Endpoint Configfs Support"
|
||||
depends on PCI_ENDPOINT
|
||||
select CONFIGFS_FS
|
||||
help
|
||||
This will enable the configfs entry that can be used to
|
||||
configure the endpoint function and used to bind the
|
||||
function with a endpoint controller.
|
||||
|
||||
source "drivers/pci/endpoint/functions/Kconfig"
|
||||
|
||||
endmenu
|
7
drivers/pci/endpoint/Makefile
Normal file
7
drivers/pci/endpoint/Makefile
Normal file
@@ -0,0 +1,7 @@
|
||||
#
|
||||
# Makefile for PCI Endpoint Support
|
||||
#
|
||||
|
||||
obj-$(CONFIG_PCI_ENDPOINT_CONFIGFS) += pci-ep-cfs.o
|
||||
obj-$(CONFIG_PCI_ENDPOINT) += pci-epc-core.o pci-epf-core.o\
|
||||
pci-epc-mem.o functions/
|
12
drivers/pci/endpoint/functions/Kconfig
Normal file
12
drivers/pci/endpoint/functions/Kconfig
Normal file
@@ -0,0 +1,12 @@
|
||||
#
|
||||
# PCI Endpoint Functions
|
||||
#
|
||||
|
||||
config PCI_EPF_TEST
|
||||
tristate "PCI Endpoint Test driver"
|
||||
depends on PCI_ENDPOINT
|
||||
help
|
||||
Enable this configuration option to enable the test driver
|
||||
for PCI Endpoint.
|
||||
|
||||
If in doubt, say "N" to disable Endpoint test driver.
|
5
drivers/pci/endpoint/functions/Makefile
Normal file
5
drivers/pci/endpoint/functions/Makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# Makefile for PCI Endpoint Functions
|
||||
#
|
||||
|
||||
obj-$(CONFIG_PCI_EPF_TEST) += pci-epf-test.o
|
510
drivers/pci/endpoint/functions/pci-epf-test.c
Normal file
510
drivers/pci/endpoint/functions/pci-epf-test.c
Normal file
@@ -0,0 +1,510 @@
|
||||
/**
|
||||
* Test driver to test endpoint functionality
|
||||
*
|
||||
* Copyright (C) 2017 Texas Instruments
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 of
|
||||
* the License as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/crc32.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/pci_ids.h>
|
||||
#include <linux/random.h>
|
||||
|
||||
#include <linux/pci-epc.h>
|
||||
#include <linux/pci-epf.h>
|
||||
#include <linux/pci_regs.h>
|
||||
|
||||
#define COMMAND_RAISE_LEGACY_IRQ BIT(0)
|
||||
#define COMMAND_RAISE_MSI_IRQ BIT(1)
|
||||
#define MSI_NUMBER_SHIFT 2
|
||||
#define MSI_NUMBER_MASK (0x3f << MSI_NUMBER_SHIFT)
|
||||
#define COMMAND_READ BIT(8)
|
||||
#define COMMAND_WRITE BIT(9)
|
||||
#define COMMAND_COPY BIT(10)
|
||||
|
||||
#define STATUS_READ_SUCCESS BIT(0)
|
||||
#define STATUS_READ_FAIL BIT(1)
|
||||
#define STATUS_WRITE_SUCCESS BIT(2)
|
||||
#define STATUS_WRITE_FAIL BIT(3)
|
||||
#define STATUS_COPY_SUCCESS BIT(4)
|
||||
#define STATUS_COPY_FAIL BIT(5)
|
||||
#define STATUS_IRQ_RAISED BIT(6)
|
||||
#define STATUS_SRC_ADDR_INVALID BIT(7)
|
||||
#define STATUS_DST_ADDR_INVALID BIT(8)
|
||||
|
||||
#define TIMER_RESOLUTION 1
|
||||
|
||||
static struct workqueue_struct *kpcitest_workqueue;
|
||||
|
||||
struct pci_epf_test {
|
||||
void *reg[6];
|
||||
struct pci_epf *epf;
|
||||
struct delayed_work cmd_handler;
|
||||
};
|
||||
|
||||
struct pci_epf_test_reg {
|
||||
u32 magic;
|
||||
u32 command;
|
||||
u32 status;
|
||||
u64 src_addr;
|
||||
u64 dst_addr;
|
||||
u32 size;
|
||||
u32 checksum;
|
||||
} __packed;
|
||||
|
||||
static struct pci_epf_header test_header = {
|
||||
.vendorid = PCI_ANY_ID,
|
||||
.deviceid = PCI_ANY_ID,
|
||||
.baseclass_code = PCI_CLASS_OTHERS,
|
||||
.interrupt_pin = PCI_INTERRUPT_INTA,
|
||||
};
|
||||
|
||||
static int bar_size[] = { 512, 1024, 16384, 131072, 1048576 };
|
||||
|
||||
static int pci_epf_test_copy(struct pci_epf_test *epf_test)
|
||||
{
|
||||
int ret;
|
||||
void __iomem *src_addr;
|
||||
void __iomem *dst_addr;
|
||||
phys_addr_t src_phys_addr;
|
||||
phys_addr_t dst_phys_addr;
|
||||
struct pci_epf *epf = epf_test->epf;
|
||||
struct device *dev = &epf->dev;
|
||||
struct pci_epc *epc = epf->epc;
|
||||
struct pci_epf_test_reg *reg = epf_test->reg[0];
|
||||
|
||||
src_addr = pci_epc_mem_alloc_addr(epc, &src_phys_addr, reg->size);
|
||||
if (!src_addr) {
|
||||
dev_err(dev, "failed to allocate source address\n");
|
||||
reg->status = STATUS_SRC_ADDR_INVALID;
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = pci_epc_map_addr(epc, src_phys_addr, reg->src_addr, reg->size);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to map source address\n");
|
||||
reg->status = STATUS_SRC_ADDR_INVALID;
|
||||
goto err_src_addr;
|
||||
}
|
||||
|
||||
dst_addr = pci_epc_mem_alloc_addr(epc, &dst_phys_addr, reg->size);
|
||||
if (!dst_addr) {
|
||||
dev_err(dev, "failed to allocate destination address\n");
|
||||
reg->status = STATUS_DST_ADDR_INVALID;
|
||||
ret = -ENOMEM;
|
||||
goto err_src_map_addr;
|
||||
}
|
||||
|
||||
ret = pci_epc_map_addr(epc, dst_phys_addr, reg->dst_addr, reg->size);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to map destination address\n");
|
||||
reg->status = STATUS_DST_ADDR_INVALID;
|
||||
goto err_dst_addr;
|
||||
}
|
||||
|
||||
memcpy(dst_addr, src_addr, reg->size);
|
||||
|
||||
pci_epc_unmap_addr(epc, dst_phys_addr);
|
||||
|
||||
err_dst_addr:
|
||||
pci_epc_mem_free_addr(epc, dst_phys_addr, dst_addr, reg->size);
|
||||
|
||||
err_src_map_addr:
|
||||
pci_epc_unmap_addr(epc, src_phys_addr);
|
||||
|
||||
err_src_addr:
|
||||
pci_epc_mem_free_addr(epc, src_phys_addr, src_addr, reg->size);
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pci_epf_test_read(struct pci_epf_test *epf_test)
|
||||
{
|
||||
int ret;
|
||||
void __iomem *src_addr;
|
||||
void *buf;
|
||||
u32 crc32;
|
||||
phys_addr_t phys_addr;
|
||||
struct pci_epf *epf = epf_test->epf;
|
||||
struct device *dev = &epf->dev;
|
||||
struct pci_epc *epc = epf->epc;
|
||||
struct pci_epf_test_reg *reg = epf_test->reg[0];
|
||||
|
||||
src_addr = pci_epc_mem_alloc_addr(epc, &phys_addr, reg->size);
|
||||
if (!src_addr) {
|
||||
dev_err(dev, "failed to allocate address\n");
|
||||
reg->status = STATUS_SRC_ADDR_INVALID;
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = pci_epc_map_addr(epc, phys_addr, reg->src_addr, reg->size);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to map address\n");
|
||||
reg->status = STATUS_SRC_ADDR_INVALID;
|
||||
goto err_addr;
|
||||
}
|
||||
|
||||
buf = kzalloc(reg->size, GFP_KERNEL);
|
||||
if (!buf) {
|
||||
ret = -ENOMEM;
|
||||
goto err_map_addr;
|
||||
}
|
||||
|
||||
memcpy(buf, src_addr, reg->size);
|
||||
|
||||
crc32 = crc32_le(~0, buf, reg->size);
|
||||
if (crc32 != reg->checksum)
|
||||
ret = -EIO;
|
||||
|
||||
kfree(buf);
|
||||
|
||||
err_map_addr:
|
||||
pci_epc_unmap_addr(epc, phys_addr);
|
||||
|
||||
err_addr:
|
||||
pci_epc_mem_free_addr(epc, phys_addr, src_addr, reg->size);
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pci_epf_test_write(struct pci_epf_test *epf_test)
|
||||
{
|
||||
int ret;
|
||||
void __iomem *dst_addr;
|
||||
void *buf;
|
||||
phys_addr_t phys_addr;
|
||||
struct pci_epf *epf = epf_test->epf;
|
||||
struct device *dev = &epf->dev;
|
||||
struct pci_epc *epc = epf->epc;
|
||||
struct pci_epf_test_reg *reg = epf_test->reg[0];
|
||||
|
||||
dst_addr = pci_epc_mem_alloc_addr(epc, &phys_addr, reg->size);
|
||||
if (!dst_addr) {
|
||||
dev_err(dev, "failed to allocate address\n");
|
||||
reg->status = STATUS_DST_ADDR_INVALID;
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = pci_epc_map_addr(epc, phys_addr, reg->dst_addr, reg->size);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to map address\n");
|
||||
reg->status = STATUS_DST_ADDR_INVALID;
|
||||
goto err_addr;
|
||||
}
|
||||
|
||||
buf = kzalloc(reg->size, GFP_KERNEL);
|
||||
if (!buf) {
|
||||
ret = -ENOMEM;
|
||||
goto err_map_addr;
|
||||
}
|
||||
|
||||
get_random_bytes(buf, reg->size);
|
||||
reg->checksum = crc32_le(~0, buf, reg->size);
|
||||
|
||||
memcpy(dst_addr, buf, reg->size);
|
||||
|
||||
/*
|
||||
* wait 1ms inorder for the write to complete. Without this delay L3
|
||||
* error in observed in the host system.
|
||||
*/
|
||||
mdelay(1);
|
||||
|
||||
kfree(buf);
|
||||
|
||||
err_map_addr:
|
||||
pci_epc_unmap_addr(epc, phys_addr);
|
||||
|
||||
err_addr:
|
||||
pci_epc_mem_free_addr(epc, phys_addr, dst_addr, reg->size);
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void pci_epf_test_raise_irq(struct pci_epf_test *epf_test)
|
||||
{
|
||||
u8 irq;
|
||||
u8 msi_count;
|
||||
struct pci_epf *epf = epf_test->epf;
|
||||
struct pci_epc *epc = epf->epc;
|
||||
struct pci_epf_test_reg *reg = epf_test->reg[0];
|
||||
|
||||
reg->status |= STATUS_IRQ_RAISED;
|
||||
msi_count = pci_epc_get_msi(epc);
|
||||
irq = (reg->command & MSI_NUMBER_MASK) >> MSI_NUMBER_SHIFT;
|
||||
if (irq > msi_count || msi_count <= 0)
|
||||
pci_epc_raise_irq(epc, PCI_EPC_IRQ_LEGACY, 0);
|
||||
else
|
||||
pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSI, irq);
|
||||
}
|
||||
|
||||
static void pci_epf_test_cmd_handler(struct work_struct *work)
|
||||
{
|
||||
int ret;
|
||||
u8 irq;
|
||||
u8 msi_count;
|
||||
struct pci_epf_test *epf_test = container_of(work, struct pci_epf_test,
|
||||
cmd_handler.work);
|
||||
struct pci_epf *epf = epf_test->epf;
|
||||
struct pci_epc *epc = epf->epc;
|
||||
struct pci_epf_test_reg *reg = epf_test->reg[0];
|
||||
|
||||
if (!reg->command)
|
||||
goto reset_handler;
|
||||
|
||||
if (reg->command & COMMAND_RAISE_LEGACY_IRQ) {
|
||||
reg->status = STATUS_IRQ_RAISED;
|
||||
pci_epc_raise_irq(epc, PCI_EPC_IRQ_LEGACY, 0);
|
||||
goto reset_handler;
|
||||
}
|
||||
|
||||
if (reg->command & COMMAND_WRITE) {
|
||||
ret = pci_epf_test_write(epf_test);
|
||||
if (ret)
|
||||
reg->status |= STATUS_WRITE_FAIL;
|
||||
else
|
||||
reg->status |= STATUS_WRITE_SUCCESS;
|
||||
pci_epf_test_raise_irq(epf_test);
|
||||
goto reset_handler;
|
||||
}
|
||||
|
||||
if (reg->command & COMMAND_READ) {
|
||||
ret = pci_epf_test_read(epf_test);
|
||||
if (!ret)
|
||||
reg->status |= STATUS_READ_SUCCESS;
|
||||
else
|
||||
reg->status |= STATUS_READ_FAIL;
|
||||
pci_epf_test_raise_irq(epf_test);
|
||||
goto reset_handler;
|
||||
}
|
||||
|
||||
if (reg->command & COMMAND_COPY) {
|
||||
ret = pci_epf_test_copy(epf_test);
|
||||
if (!ret)
|
||||
reg->status |= STATUS_COPY_SUCCESS;
|
||||
else
|
||||
reg->status |= STATUS_COPY_FAIL;
|
||||
pci_epf_test_raise_irq(epf_test);
|
||||
goto reset_handler;
|
||||
}
|
||||
|
||||
if (reg->command & COMMAND_RAISE_MSI_IRQ) {
|
||||
msi_count = pci_epc_get_msi(epc);
|
||||
irq = (reg->command & MSI_NUMBER_MASK) >> MSI_NUMBER_SHIFT;
|
||||
if (irq > msi_count || msi_count <= 0)
|
||||
goto reset_handler;
|
||||
reg->status = STATUS_IRQ_RAISED;
|
||||
pci_epc_raise_irq(epc, PCI_EPC_IRQ_MSI, irq);
|
||||
goto reset_handler;
|
||||
}
|
||||
|
||||
reset_handler:
|
||||
reg->command = 0;
|
||||
|
||||
queue_delayed_work(kpcitest_workqueue, &epf_test->cmd_handler,
|
||||
msecs_to_jiffies(1));
|
||||
}
|
||||
|
||||
static void pci_epf_test_linkup(struct pci_epf *epf)
|
||||
{
|
||||
struct pci_epf_test *epf_test = epf_get_drvdata(epf);
|
||||
|
||||
queue_delayed_work(kpcitest_workqueue, &epf_test->cmd_handler,
|
||||
msecs_to_jiffies(1));
|
||||
}
|
||||
|
||||
static void pci_epf_test_unbind(struct pci_epf *epf)
|
||||
{
|
||||
struct pci_epf_test *epf_test = epf_get_drvdata(epf);
|
||||
struct pci_epc *epc = epf->epc;
|
||||
int bar;
|
||||
|
||||
cancel_delayed_work(&epf_test->cmd_handler);
|
||||
pci_epc_stop(epc);
|
||||
for (bar = BAR_0; bar <= BAR_5; bar++) {
|
||||
if (epf_test->reg[bar]) {
|
||||
pci_epf_free_space(epf, epf_test->reg[bar], bar);
|
||||
pci_epc_clear_bar(epc, bar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int pci_epf_test_set_bar(struct pci_epf *epf)
|
||||
{
|
||||
int flags;
|
||||
int bar;
|
||||
int ret;
|
||||
struct pci_epf_bar *epf_bar;
|
||||
struct pci_epc *epc = epf->epc;
|
||||
struct device *dev = &epf->dev;
|
||||
struct pci_epf_test *epf_test = epf_get_drvdata(epf);
|
||||
|
||||
flags = PCI_BASE_ADDRESS_SPACE_MEMORY | PCI_BASE_ADDRESS_MEM_TYPE_32;
|
||||
if (sizeof(dma_addr_t) == 0x8)
|
||||
flags |= PCI_BASE_ADDRESS_MEM_TYPE_64;
|
||||
|
||||
for (bar = BAR_0; bar <= BAR_5; bar++) {
|
||||
epf_bar = &epf->bar[bar];
|
||||
ret = pci_epc_set_bar(epc, bar, epf_bar->phys_addr,
|
||||
epf_bar->size, flags);
|
||||
if (ret) {
|
||||
pci_epf_free_space(epf, epf_test->reg[bar], bar);
|
||||
dev_err(dev, "failed to set BAR%d\n", bar);
|
||||
if (bar == BAR_0)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pci_epf_test_alloc_space(struct pci_epf *epf)
|
||||
{
|
||||
struct pci_epf_test *epf_test = epf_get_drvdata(epf);
|
||||
struct device *dev = &epf->dev;
|
||||
void *base;
|
||||
int bar;
|
||||
|
||||
base = pci_epf_alloc_space(epf, sizeof(struct pci_epf_test_reg),
|
||||
BAR_0);
|
||||
if (!base) {
|
||||
dev_err(dev, "failed to allocated register space\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
epf_test->reg[0] = base;
|
||||
|
||||
for (bar = BAR_1; bar <= BAR_5; bar++) {
|
||||
base = pci_epf_alloc_space(epf, bar_size[bar - 1], bar);
|
||||
if (!base)
|
||||
dev_err(dev, "failed to allocate space for BAR%d\n",
|
||||
bar);
|
||||
epf_test->reg[bar] = base;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pci_epf_test_bind(struct pci_epf *epf)
|
||||
{
|
||||
int ret;
|
||||
struct pci_epf_header *header = epf->header;
|
||||
struct pci_epc *epc = epf->epc;
|
||||
struct device *dev = &epf->dev;
|
||||
|
||||
if (WARN_ON_ONCE(!epc))
|
||||
return -EINVAL;
|
||||
|
||||
ret = pci_epc_write_header(epc, header);
|
||||
if (ret) {
|
||||
dev_err(dev, "configuration header write failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = pci_epf_test_alloc_space(epf);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = pci_epf_test_set_bar(epf);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = pci_epc_set_msi(epc, epf->msi_interrupts);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pci_epf_test_probe(struct pci_epf *epf)
|
||||
{
|
||||
struct pci_epf_test *epf_test;
|
||||
struct device *dev = &epf->dev;
|
||||
|
||||
epf_test = devm_kzalloc(dev, sizeof(*epf_test), GFP_KERNEL);
|
||||
if (!epf_test)
|
||||
return -ENOMEM;
|
||||
|
||||
epf->header = &test_header;
|
||||
epf_test->epf = epf;
|
||||
|
||||
INIT_DELAYED_WORK(&epf_test->cmd_handler, pci_epf_test_cmd_handler);
|
||||
|
||||
epf_set_drvdata(epf, epf_test);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pci_epf_test_remove(struct pci_epf *epf)
|
||||
{
|
||||
struct pci_epf_test *epf_test = epf_get_drvdata(epf);
|
||||
|
||||
kfree(epf_test);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct pci_epf_ops ops = {
|
||||
.unbind = pci_epf_test_unbind,
|
||||
.bind = pci_epf_test_bind,
|
||||
.linkup = pci_epf_test_linkup,
|
||||
};
|
||||
|
||||
static const struct pci_epf_device_id pci_epf_test_ids[] = {
|
||||
{
|
||||
.name = "pci_epf_test",
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
static struct pci_epf_driver test_driver = {
|
||||
.driver.name = "pci_epf_test",
|
||||
.probe = pci_epf_test_probe,
|
||||
.remove = pci_epf_test_remove,
|
||||
.id_table = pci_epf_test_ids,
|
||||
.ops = &ops,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int __init pci_epf_test_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
kpcitest_workqueue = alloc_workqueue("kpcitest",
|
||||
WQ_MEM_RECLAIM | WQ_HIGHPRI, 0);
|
||||
ret = pci_epf_register_driver(&test_driver);
|
||||
if (ret) {
|
||||
pr_err("failed to register pci epf test driver --> %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
module_init(pci_epf_test_init);
|
||||
|
||||
static void __exit pci_epf_test_exit(void)
|
||||
{
|
||||
pci_epf_unregister_driver(&test_driver);
|
||||
}
|
||||
module_exit(pci_epf_test_exit);
|
||||
|
||||
MODULE_DESCRIPTION("PCI EPF TEST DRIVER");
|
||||
MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
509
drivers/pci/endpoint/pci-ep-cfs.c
Normal file
509
drivers/pci/endpoint/pci-ep-cfs.c
Normal file
@@ -0,0 +1,509 @@
|
||||
/**
|
||||
* configfs to configure the PCI endpoint
|
||||
*
|
||||
* Copyright (C) 2017 Texas Instruments
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 of
|
||||
* the License as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <linux/pci-epc.h>
|
||||
#include <linux/pci-epf.h>
|
||||
#include <linux/pci-ep-cfs.h>
|
||||
|
||||
static struct config_group *functions_group;
|
||||
static struct config_group *controllers_group;
|
||||
|
||||
struct pci_epf_group {
|
||||
struct config_group group;
|
||||
struct pci_epf *epf;
|
||||
};
|
||||
|
||||
struct pci_epc_group {
|
||||
struct config_group group;
|
||||
struct pci_epc *epc;
|
||||
bool start;
|
||||
unsigned long function_num_map;
|
||||
};
|
||||
|
||||
static inline struct pci_epf_group *to_pci_epf_group(struct config_item *item)
|
||||
{
|
||||
return container_of(to_config_group(item), struct pci_epf_group, group);
|
||||
}
|
||||
|
||||
static inline struct pci_epc_group *to_pci_epc_group(struct config_item *item)
|
||||
{
|
||||
return container_of(to_config_group(item), struct pci_epc_group, group);
|
||||
}
|
||||
|
||||
static ssize_t pci_epc_start_store(struct config_item *item, const char *page,
|
||||
size_t len)
|
||||
{
|
||||
int ret;
|
||||
bool start;
|
||||
struct pci_epc *epc;
|
||||
struct pci_epc_group *epc_group = to_pci_epc_group(item);
|
||||
|
||||
epc = epc_group->epc;
|
||||
|
||||
ret = kstrtobool(page, &start);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!start) {
|
||||
pci_epc_stop(epc);
|
||||
return len;
|
||||
}
|
||||
|
||||
ret = pci_epc_start(epc);
|
||||
if (ret) {
|
||||
dev_err(&epc->dev, "failed to start endpoint controller\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
epc_group->start = start;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t pci_epc_start_show(struct config_item *item, char *page)
|
||||
{
|
||||
return sprintf(page, "%d\n",
|
||||
to_pci_epc_group(item)->start);
|
||||
}
|
||||
|
||||
CONFIGFS_ATTR(pci_epc_, start);
|
||||
|
||||
static struct configfs_attribute *pci_epc_attrs[] = {
|
||||
&pci_epc_attr_start,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static int pci_epc_epf_link(struct config_item *epc_item,
|
||||
struct config_item *epf_item)
|
||||
{
|
||||
int ret;
|
||||
u32 func_no = 0;
|
||||
struct pci_epc *epc;
|
||||
struct pci_epf *epf;
|
||||
struct pci_epf_group *epf_group = to_pci_epf_group(epf_item);
|
||||
struct pci_epc_group *epc_group = to_pci_epc_group(epc_item);
|
||||
|
||||
epc = epc_group->epc;
|
||||
epf = epf_group->epf;
|
||||
ret = pci_epc_add_epf(epc, epf);
|
||||
if (ret)
|
||||
goto err_add_epf;
|
||||
|
||||
func_no = find_first_zero_bit(&epc_group->function_num_map,
|
||||
sizeof(epc_group->function_num_map));
|
||||
set_bit(func_no, &epc_group->function_num_map);
|
||||
epf->func_no = func_no;
|
||||
|
||||
ret = pci_epf_bind(epf);
|
||||
if (ret)
|
||||
goto err_epf_bind;
|
||||
|
||||
return 0;
|
||||
|
||||
err_epf_bind:
|
||||
pci_epc_remove_epf(epc, epf);
|
||||
|
||||
err_add_epf:
|
||||
clear_bit(func_no, &epc_group->function_num_map);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void pci_epc_epf_unlink(struct config_item *epc_item,
|
||||
struct config_item *epf_item)
|
||||
{
|
||||
struct pci_epc *epc;
|
||||
struct pci_epf *epf;
|
||||
struct pci_epf_group *epf_group = to_pci_epf_group(epf_item);
|
||||
struct pci_epc_group *epc_group = to_pci_epc_group(epc_item);
|
||||
|
||||
WARN_ON_ONCE(epc_group->start);
|
||||
|
||||
epc = epc_group->epc;
|
||||
epf = epf_group->epf;
|
||||
clear_bit(epf->func_no, &epc_group->function_num_map);
|
||||
pci_epf_unbind(epf);
|
||||
pci_epc_remove_epf(epc, epf);
|
||||
}
|
||||
|
||||
static struct configfs_item_operations pci_epc_item_ops = {
|
||||
.allow_link = pci_epc_epf_link,
|
||||
.drop_link = pci_epc_epf_unlink,
|
||||
};
|
||||
|
||||
static struct config_item_type pci_epc_type = {
|
||||
.ct_item_ops = &pci_epc_item_ops,
|
||||
.ct_attrs = pci_epc_attrs,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
struct config_group *pci_ep_cfs_add_epc_group(const char *name)
|
||||
{
|
||||
int ret;
|
||||
struct pci_epc *epc;
|
||||
struct config_group *group;
|
||||
struct pci_epc_group *epc_group;
|
||||
|
||||
epc_group = kzalloc(sizeof(*epc_group), GFP_KERNEL);
|
||||
if (!epc_group) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
group = &epc_group->group;
|
||||
|
||||
config_group_init_type_name(group, name, &pci_epc_type);
|
||||
ret = configfs_register_group(controllers_group, group);
|
||||
if (ret) {
|
||||
pr_err("failed to register configfs group for %s\n", name);
|
||||
goto err_register_group;
|
||||
}
|
||||
|
||||
epc = pci_epc_get(name);
|
||||
if (IS_ERR(epc)) {
|
||||
ret = PTR_ERR(epc);
|
||||
goto err_epc_get;
|
||||
}
|
||||
|
||||
epc_group->epc = epc;
|
||||
|
||||
return group;
|
||||
|
||||
err_epc_get:
|
||||
configfs_unregister_group(group);
|
||||
|
||||
err_register_group:
|
||||
kfree(epc_group);
|
||||
|
||||
err:
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
EXPORT_SYMBOL(pci_ep_cfs_add_epc_group);
|
||||
|
||||
void pci_ep_cfs_remove_epc_group(struct config_group *group)
|
||||
{
|
||||
struct pci_epc_group *epc_group;
|
||||
|
||||
if (!group)
|
||||
return;
|
||||
|
||||
epc_group = container_of(group, struct pci_epc_group, group);
|
||||
pci_epc_put(epc_group->epc);
|
||||
configfs_unregister_group(&epc_group->group);
|
||||
kfree(epc_group);
|
||||
}
|
||||
EXPORT_SYMBOL(pci_ep_cfs_remove_epc_group);
|
||||
|
||||
#define PCI_EPF_HEADER_R(_name) \
|
||||
static ssize_t pci_epf_##_name##_show(struct config_item *item, char *page) \
|
||||
{ \
|
||||
struct pci_epf *epf = to_pci_epf_group(item)->epf; \
|
||||
if (WARN_ON_ONCE(!epf->header)) \
|
||||
return -EINVAL; \
|
||||
return sprintf(page, "0x%04x\n", epf->header->_name); \
|
||||
}
|
||||
|
||||
#define PCI_EPF_HEADER_W_u32(_name) \
|
||||
static ssize_t pci_epf_##_name##_store(struct config_item *item, \
|
||||
const char *page, size_t len) \
|
||||
{ \
|
||||
u32 val; \
|
||||
int ret; \
|
||||
struct pci_epf *epf = to_pci_epf_group(item)->epf; \
|
||||
if (WARN_ON_ONCE(!epf->header)) \
|
||||
return -EINVAL; \
|
||||
ret = kstrtou32(page, 0, &val); \
|
||||
if (ret) \
|
||||
return ret; \
|
||||
epf->header->_name = val; \
|
||||
return len; \
|
||||
}
|
||||
|
||||
#define PCI_EPF_HEADER_W_u16(_name) \
|
||||
static ssize_t pci_epf_##_name##_store(struct config_item *item, \
|
||||
const char *page, size_t len) \
|
||||
{ \
|
||||
u16 val; \
|
||||
int ret; \
|
||||
struct pci_epf *epf = to_pci_epf_group(item)->epf; \
|
||||
if (WARN_ON_ONCE(!epf->header)) \
|
||||
return -EINVAL; \
|
||||
ret = kstrtou16(page, 0, &val); \
|
||||
if (ret) \
|
||||
return ret; \
|
||||
epf->header->_name = val; \
|
||||
return len; \
|
||||
}
|
||||
|
||||
#define PCI_EPF_HEADER_W_u8(_name) \
|
||||
static ssize_t pci_epf_##_name##_store(struct config_item *item, \
|
||||
const char *page, size_t len) \
|
||||
{ \
|
||||
u8 val; \
|
||||
int ret; \
|
||||
struct pci_epf *epf = to_pci_epf_group(item)->epf; \
|
||||
if (WARN_ON_ONCE(!epf->header)) \
|
||||
return -EINVAL; \
|
||||
ret = kstrtou8(page, 0, &val); \
|
||||
if (ret) \
|
||||
return ret; \
|
||||
epf->header->_name = val; \
|
||||
return len; \
|
||||
}
|
||||
|
||||
static ssize_t pci_epf_msi_interrupts_store(struct config_item *item,
|
||||
const char *page, size_t len)
|
||||
{
|
||||
u8 val;
|
||||
int ret;
|
||||
|
||||
ret = kstrtou8(page, 0, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
to_pci_epf_group(item)->epf->msi_interrupts = val;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t pci_epf_msi_interrupts_show(struct config_item *item,
|
||||
char *page)
|
||||
{
|
||||
return sprintf(page, "%d\n",
|
||||
to_pci_epf_group(item)->epf->msi_interrupts);
|
||||
}
|
||||
|
||||
PCI_EPF_HEADER_R(vendorid)
|
||||
PCI_EPF_HEADER_W_u16(vendorid)
|
||||
|
||||
PCI_EPF_HEADER_R(deviceid)
|
||||
PCI_EPF_HEADER_W_u16(deviceid)
|
||||
|
||||
PCI_EPF_HEADER_R(revid)
|
||||
PCI_EPF_HEADER_W_u8(revid)
|
||||
|
||||
PCI_EPF_HEADER_R(progif_code)
|
||||
PCI_EPF_HEADER_W_u8(progif_code)
|
||||
|
||||
PCI_EPF_HEADER_R(subclass_code)
|
||||
PCI_EPF_HEADER_W_u8(subclass_code)
|
||||
|
||||
PCI_EPF_HEADER_R(baseclass_code)
|
||||
PCI_EPF_HEADER_W_u8(baseclass_code)
|
||||
|
||||
PCI_EPF_HEADER_R(cache_line_size)
|
||||
PCI_EPF_HEADER_W_u8(cache_line_size)
|
||||
|
||||
PCI_EPF_HEADER_R(subsys_vendor_id)
|
||||
PCI_EPF_HEADER_W_u16(subsys_vendor_id)
|
||||
|
||||
PCI_EPF_HEADER_R(subsys_id)
|
||||
PCI_EPF_HEADER_W_u16(subsys_id)
|
||||
|
||||
PCI_EPF_HEADER_R(interrupt_pin)
|
||||
PCI_EPF_HEADER_W_u8(interrupt_pin)
|
||||
|
||||
CONFIGFS_ATTR(pci_epf_, vendorid);
|
||||
CONFIGFS_ATTR(pci_epf_, deviceid);
|
||||
CONFIGFS_ATTR(pci_epf_, revid);
|
||||
CONFIGFS_ATTR(pci_epf_, progif_code);
|
||||
CONFIGFS_ATTR(pci_epf_, subclass_code);
|
||||
CONFIGFS_ATTR(pci_epf_, baseclass_code);
|
||||
CONFIGFS_ATTR(pci_epf_, cache_line_size);
|
||||
CONFIGFS_ATTR(pci_epf_, subsys_vendor_id);
|
||||
CONFIGFS_ATTR(pci_epf_, subsys_id);
|
||||
CONFIGFS_ATTR(pci_epf_, interrupt_pin);
|
||||
CONFIGFS_ATTR(pci_epf_, msi_interrupts);
|
||||
|
||||
static struct configfs_attribute *pci_epf_attrs[] = {
|
||||
&pci_epf_attr_vendorid,
|
||||
&pci_epf_attr_deviceid,
|
||||
&pci_epf_attr_revid,
|
||||
&pci_epf_attr_progif_code,
|
||||
&pci_epf_attr_subclass_code,
|
||||
&pci_epf_attr_baseclass_code,
|
||||
&pci_epf_attr_cache_line_size,
|
||||
&pci_epf_attr_subsys_vendor_id,
|
||||
&pci_epf_attr_subsys_id,
|
||||
&pci_epf_attr_interrupt_pin,
|
||||
&pci_epf_attr_msi_interrupts,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static void pci_epf_release(struct config_item *item)
|
||||
{
|
||||
struct pci_epf_group *epf_group = to_pci_epf_group(item);
|
||||
|
||||
pci_epf_destroy(epf_group->epf);
|
||||
kfree(epf_group);
|
||||
}
|
||||
|
||||
static struct configfs_item_operations pci_epf_ops = {
|
||||
.release = pci_epf_release,
|
||||
};
|
||||
|
||||
static struct config_item_type pci_epf_type = {
|
||||
.ct_item_ops = &pci_epf_ops,
|
||||
.ct_attrs = pci_epf_attrs,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static struct config_group *pci_epf_make(struct config_group *group,
|
||||
const char *name)
|
||||
{
|
||||
struct pci_epf_group *epf_group;
|
||||
struct pci_epf *epf;
|
||||
|
||||
epf_group = kzalloc(sizeof(*epf_group), GFP_KERNEL);
|
||||
if (!epf_group)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
config_group_init_type_name(&epf_group->group, name, &pci_epf_type);
|
||||
|
||||
epf = pci_epf_create(group->cg_item.ci_name);
|
||||
if (IS_ERR(epf)) {
|
||||
pr_err("failed to create endpoint function device\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
epf_group->epf = epf;
|
||||
|
||||
return &epf_group->group;
|
||||
}
|
||||
|
||||
static void pci_epf_drop(struct config_group *group, struct config_item *item)
|
||||
{
|
||||
config_item_put(item);
|
||||
}
|
||||
|
||||
static struct configfs_group_operations pci_epf_group_ops = {
|
||||
.make_group = &pci_epf_make,
|
||||
.drop_item = &pci_epf_drop,
|
||||
};
|
||||
|
||||
static struct config_item_type pci_epf_group_type = {
|
||||
.ct_group_ops = &pci_epf_group_ops,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
struct config_group *pci_ep_cfs_add_epf_group(const char *name)
|
||||
{
|
||||
struct config_group *group;
|
||||
|
||||
group = configfs_register_default_group(functions_group, name,
|
||||
&pci_epf_group_type);
|
||||
if (IS_ERR(group))
|
||||
pr_err("failed to register configfs group for %s function\n",
|
||||
name);
|
||||
|
||||
return group;
|
||||
}
|
||||
EXPORT_SYMBOL(pci_ep_cfs_add_epf_group);
|
||||
|
||||
void pci_ep_cfs_remove_epf_group(struct config_group *group)
|
||||
{
|
||||
if (IS_ERR_OR_NULL(group))
|
||||
return;
|
||||
|
||||
configfs_unregister_default_group(group);
|
||||
}
|
||||
EXPORT_SYMBOL(pci_ep_cfs_remove_epf_group);
|
||||
|
||||
static struct config_item_type pci_functions_type = {
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static struct config_item_type pci_controllers_type = {
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static struct config_item_type pci_ep_type = {
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static struct configfs_subsystem pci_ep_cfs_subsys = {
|
||||
.su_group = {
|
||||
.cg_item = {
|
||||
.ci_namebuf = "pci_ep",
|
||||
.ci_type = &pci_ep_type,
|
||||
},
|
||||
},
|
||||
.su_mutex = __MUTEX_INITIALIZER(pci_ep_cfs_subsys.su_mutex),
|
||||
};
|
||||
|
||||
static int __init pci_ep_cfs_init(void)
|
||||
{
|
||||
int ret;
|
||||
struct config_group *root = &pci_ep_cfs_subsys.su_group;
|
||||
|
||||
config_group_init(root);
|
||||
|
||||
ret = configfs_register_subsystem(&pci_ep_cfs_subsys);
|
||||
if (ret) {
|
||||
pr_err("Error %d while registering subsystem %s\n",
|
||||
ret, root->cg_item.ci_namebuf);
|
||||
goto err;
|
||||
}
|
||||
|
||||
functions_group = configfs_register_default_group(root, "functions",
|
||||
&pci_functions_type);
|
||||
if (IS_ERR(functions_group)) {
|
||||
ret = PTR_ERR(functions_group);
|
||||
pr_err("Error %d while registering functions group\n",
|
||||
ret);
|
||||
goto err_functions_group;
|
||||
}
|
||||
|
||||
controllers_group =
|
||||
configfs_register_default_group(root, "controllers",
|
||||
&pci_controllers_type);
|
||||
if (IS_ERR(controllers_group)) {
|
||||
ret = PTR_ERR(controllers_group);
|
||||
pr_err("Error %d while registering controllers group\n",
|
||||
ret);
|
||||
goto err_controllers_group;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_controllers_group:
|
||||
configfs_unregister_default_group(functions_group);
|
||||
|
||||
err_functions_group:
|
||||
configfs_unregister_subsystem(&pci_ep_cfs_subsys);
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
module_init(pci_ep_cfs_init);
|
||||
|
||||
static void __exit pci_ep_cfs_exit(void)
|
||||
{
|
||||
configfs_unregister_default_group(controllers_group);
|
||||
configfs_unregister_default_group(functions_group);
|
||||
configfs_unregister_subsystem(&pci_ep_cfs_subsys);
|
||||
}
|
||||
module_exit(pci_ep_cfs_exit);
|
||||
|
||||
MODULE_DESCRIPTION("PCI EP CONFIGFS");
|
||||
MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
580
drivers/pci/endpoint/pci-epc-core.c
Normal file
580
drivers/pci/endpoint/pci-epc-core.c
Normal file
@@ -0,0 +1,580 @@
|
||||
/**
|
||||
* PCI Endpoint *Controller* (EPC) library
|
||||
*
|
||||
* Copyright (C) 2017 Texas Instruments
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 of
|
||||
* the License as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <linux/pci-epc.h>
|
||||
#include <linux/pci-epf.h>
|
||||
#include <linux/pci-ep-cfs.h>
|
||||
|
||||
static struct class *pci_epc_class;
|
||||
|
||||
static void devm_pci_epc_release(struct device *dev, void *res)
|
||||
{
|
||||
struct pci_epc *epc = *(struct pci_epc **)res;
|
||||
|
||||
pci_epc_destroy(epc);
|
||||
}
|
||||
|
||||
static int devm_pci_epc_match(struct device *dev, void *res, void *match_data)
|
||||
{
|
||||
struct pci_epc **epc = res;
|
||||
|
||||
return *epc == match_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_epc_put() - release the PCI endpoint controller
|
||||
* @epc: epc returned by pci_epc_get()
|
||||
*
|
||||
* release the refcount the caller obtained by invoking pci_epc_get()
|
||||
*/
|
||||
void pci_epc_put(struct pci_epc *epc)
|
||||
{
|
||||
if (!epc || IS_ERR(epc))
|
||||
return;
|
||||
|
||||
module_put(epc->ops->owner);
|
||||
put_device(&epc->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_put);
|
||||
|
||||
/**
|
||||
* pci_epc_get() - get the PCI endpoint controller
|
||||
* @epc_name: device name of the endpoint controller
|
||||
*
|
||||
* Invoke to get struct pci_epc * corresponding to the device name of the
|
||||
* endpoint controller
|
||||
*/
|
||||
struct pci_epc *pci_epc_get(const char *epc_name)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
struct pci_epc *epc;
|
||||
struct device *dev;
|
||||
struct class_dev_iter iter;
|
||||
|
||||
class_dev_iter_init(&iter, pci_epc_class, NULL, NULL);
|
||||
while ((dev = class_dev_iter_next(&iter))) {
|
||||
if (strcmp(epc_name, dev_name(dev)))
|
||||
continue;
|
||||
|
||||
epc = to_pci_epc(dev);
|
||||
if (!try_module_get(epc->ops->owner)) {
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
class_dev_iter_exit(&iter);
|
||||
get_device(&epc->dev);
|
||||
return epc;
|
||||
}
|
||||
|
||||
err:
|
||||
class_dev_iter_exit(&iter);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_get);
|
||||
|
||||
/**
|
||||
* pci_epc_stop() - stop the PCI link
|
||||
* @epc: the link of the EPC device that has to be stopped
|
||||
*
|
||||
* Invoke to stop the PCI link
|
||||
*/
|
||||
void pci_epc_stop(struct pci_epc *epc)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (IS_ERR(epc) || !epc->ops->stop)
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
epc->ops->stop(epc);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_stop);
|
||||
|
||||
/**
|
||||
* pci_epc_start() - start the PCI link
|
||||
* @epc: the link of *this* EPC device has to be started
|
||||
*
|
||||
* Invoke to start the PCI link
|
||||
*/
|
||||
int pci_epc_start(struct pci_epc *epc)
|
||||
{
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
|
||||
if (IS_ERR(epc))
|
||||
return -EINVAL;
|
||||
|
||||
if (!epc->ops->start)
|
||||
return 0;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
ret = epc->ops->start(epc);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_start);
|
||||
|
||||
/**
|
||||
* pci_epc_raise_irq() - interrupt the host system
|
||||
* @epc: the EPC device which has to interrupt the host
|
||||
* @type: specify the type of interrupt; legacy or MSI
|
||||
* @interrupt_num: the MSI interrupt number
|
||||
*
|
||||
* Invoke to raise an MSI or legacy interrupt
|
||||
*/
|
||||
int pci_epc_raise_irq(struct pci_epc *epc, enum pci_epc_irq_type type,
|
||||
u8 interrupt_num)
|
||||
{
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
|
||||
if (IS_ERR(epc))
|
||||
return -EINVAL;
|
||||
|
||||
if (!epc->ops->raise_irq)
|
||||
return 0;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
ret = epc->ops->raise_irq(epc, type, interrupt_num);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_raise_irq);
|
||||
|
||||
/**
|
||||
* pci_epc_get_msi() - get the number of MSI interrupt numbers allocated
|
||||
* @epc: the EPC device to which MSI interrupts was requested
|
||||
*
|
||||
* Invoke to get the number of MSI interrupts allocated by the RC
|
||||
*/
|
||||
int pci_epc_get_msi(struct pci_epc *epc)
|
||||
{
|
||||
int interrupt;
|
||||
unsigned long flags;
|
||||
|
||||
if (IS_ERR(epc))
|
||||
return 0;
|
||||
|
||||
if (!epc->ops->get_msi)
|
||||
return 0;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
interrupt = epc->ops->get_msi(epc);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
|
||||
if (interrupt < 0)
|
||||
return 0;
|
||||
|
||||
interrupt = 1 << interrupt;
|
||||
|
||||
return interrupt;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_get_msi);
|
||||
|
||||
/**
|
||||
* pci_epc_set_msi() - set the number of MSI interrupt numbers required
|
||||
* @epc: the EPC device on which MSI has to be configured
|
||||
* @interrupts: number of MSI interrupts required by the EPF
|
||||
*
|
||||
* Invoke to set the required number of MSI interrupts.
|
||||
*/
|
||||
int pci_epc_set_msi(struct pci_epc *epc, u8 interrupts)
|
||||
{
|
||||
int ret;
|
||||
u8 encode_int;
|
||||
unsigned long flags;
|
||||
|
||||
if (IS_ERR(epc))
|
||||
return -EINVAL;
|
||||
|
||||
if (!epc->ops->set_msi)
|
||||
return 0;
|
||||
|
||||
encode_int = order_base_2(interrupts);
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
ret = epc->ops->set_msi(epc, encode_int);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_set_msi);
|
||||
|
||||
/**
|
||||
* pci_epc_unmap_addr() - unmap CPU address from PCI address
|
||||
* @epc: the EPC device on which address is allocated
|
||||
* @phys_addr: physical address of the local system
|
||||
*
|
||||
* Invoke to unmap the CPU address from PCI address.
|
||||
*/
|
||||
void pci_epc_unmap_addr(struct pci_epc *epc, phys_addr_t phys_addr)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (IS_ERR(epc))
|
||||
return;
|
||||
|
||||
if (!epc->ops->unmap_addr)
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
epc->ops->unmap_addr(epc, phys_addr);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_unmap_addr);
|
||||
|
||||
/**
|
||||
* pci_epc_map_addr() - map CPU address to PCI address
|
||||
* @epc: the EPC device on which address is allocated
|
||||
* @phys_addr: physical address of the local system
|
||||
* @pci_addr: PCI address to which the physical address should be mapped
|
||||
* @size: the size of the allocation
|
||||
*
|
||||
* Invoke to map CPU address with PCI address.
|
||||
*/
|
||||
int pci_epc_map_addr(struct pci_epc *epc, phys_addr_t phys_addr,
|
||||
u64 pci_addr, size_t size)
|
||||
{
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
|
||||
if (IS_ERR(epc))
|
||||
return -EINVAL;
|
||||
|
||||
if (!epc->ops->map_addr)
|
||||
return 0;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
ret = epc->ops->map_addr(epc, phys_addr, pci_addr, size);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_map_addr);
|
||||
|
||||
/**
|
||||
* pci_epc_clear_bar() - reset the BAR
|
||||
* @epc: the EPC device for which the BAR has to be cleared
|
||||
* @bar: the BAR number that has to be reset
|
||||
*
|
||||
* Invoke to reset the BAR of the endpoint device.
|
||||
*/
|
||||
void pci_epc_clear_bar(struct pci_epc *epc, int bar)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (IS_ERR(epc))
|
||||
return;
|
||||
|
||||
if (!epc->ops->clear_bar)
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
epc->ops->clear_bar(epc, bar);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_clear_bar);
|
||||
|
||||
/**
|
||||
* pci_epc_set_bar() - configure BAR in order for host to assign PCI addr space
|
||||
* @epc: the EPC device on which BAR has to be configured
|
||||
* @bar: the BAR number that has to be configured
|
||||
* @size: the size of the addr space
|
||||
* @flags: specify memory allocation/io allocation/32bit address/64 bit address
|
||||
*
|
||||
* Invoke to configure the BAR of the endpoint device.
|
||||
*/
|
||||
int pci_epc_set_bar(struct pci_epc *epc, enum pci_barno bar,
|
||||
dma_addr_t bar_phys, size_t size, int flags)
|
||||
{
|
||||
int ret;
|
||||
unsigned long irq_flags;
|
||||
|
||||
if (IS_ERR(epc))
|
||||
return -EINVAL;
|
||||
|
||||
if (!epc->ops->set_bar)
|
||||
return 0;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, irq_flags);
|
||||
ret = epc->ops->set_bar(epc, bar, bar_phys, size, flags);
|
||||
spin_unlock_irqrestore(&epc->lock, irq_flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_set_bar);
|
||||
|
||||
/**
|
||||
* pci_epc_write_header() - write standard configuration header
|
||||
* @epc: the EPC device to which the configuration header should be written
|
||||
* @header: standard configuration header fields
|
||||
*
|
||||
* Invoke to write the configuration header to the endpoint controller. Every
|
||||
* endpoint controller will have a dedicated location to which the standard
|
||||
* configuration header would be written. The callback function should write
|
||||
* the header fields to this dedicated location.
|
||||
*/
|
||||
int pci_epc_write_header(struct pci_epc *epc, struct pci_epf_header *header)
|
||||
{
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
|
||||
if (IS_ERR(epc))
|
||||
return -EINVAL;
|
||||
|
||||
if (!epc->ops->write_header)
|
||||
return 0;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
ret = epc->ops->write_header(epc, header);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_write_header);
|
||||
|
||||
/**
|
||||
* pci_epc_add_epf() - bind PCI endpoint function to an endpoint controller
|
||||
* @epc: the EPC device to which the endpoint function should be added
|
||||
* @epf: the endpoint function to be added
|
||||
*
|
||||
* A PCI endpoint device can have one or more functions. In the case of PCIe,
|
||||
* the specification allows up to 8 PCIe endpoint functions. Invoke
|
||||
* pci_epc_add_epf() to add a PCI endpoint function to an endpoint controller.
|
||||
*/
|
||||
int pci_epc_add_epf(struct pci_epc *epc, struct pci_epf *epf)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (epf->epc)
|
||||
return -EBUSY;
|
||||
|
||||
if (IS_ERR(epc))
|
||||
return -EINVAL;
|
||||
|
||||
if (epf->func_no > epc->max_functions - 1)
|
||||
return -EINVAL;
|
||||
|
||||
epf->epc = epc;
|
||||
dma_set_coherent_mask(&epf->dev, epc->dev.coherent_dma_mask);
|
||||
epf->dev.dma_mask = epc->dev.dma_mask;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
list_add_tail(&epf->list, &epc->pci_epf);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_add_epf);
|
||||
|
||||
/**
|
||||
* pci_epc_remove_epf() - remove PCI endpoint function from endpoint controller
|
||||
* @epc: the EPC device from which the endpoint function should be removed
|
||||
* @epf: the endpoint function to be removed
|
||||
*
|
||||
* Invoke to remove PCI endpoint function from the endpoint controller.
|
||||
*/
|
||||
void pci_epc_remove_epf(struct pci_epc *epc, struct pci_epf *epf)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (!epc || IS_ERR(epc))
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
list_del(&epf->list);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_remove_epf);
|
||||
|
||||
/**
|
||||
* pci_epc_linkup() - Notify the EPF device that EPC device has established a
|
||||
* connection with the Root Complex.
|
||||
* @epc: the EPC device which has established link with the host
|
||||
*
|
||||
* Invoke to Notify the EPF device that the EPC device has established a
|
||||
* connection with the Root Complex.
|
||||
*/
|
||||
void pci_epc_linkup(struct pci_epc *epc)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct pci_epf *epf;
|
||||
|
||||
if (!epc || IS_ERR(epc))
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&epc->lock, flags);
|
||||
list_for_each_entry(epf, &epc->pci_epf, list)
|
||||
pci_epf_linkup(epf);
|
||||
spin_unlock_irqrestore(&epc->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_linkup);
|
||||
|
||||
/**
|
||||
* pci_epc_destroy() - destroy the EPC device
|
||||
* @epc: the EPC device that has to be destroyed
|
||||
*
|
||||
* Invoke to destroy the PCI EPC device
|
||||
*/
|
||||
void pci_epc_destroy(struct pci_epc *epc)
|
||||
{
|
||||
pci_ep_cfs_remove_epc_group(epc->group);
|
||||
device_unregister(&epc->dev);
|
||||
kfree(epc);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_destroy);
|
||||
|
||||
/**
|
||||
* devm_pci_epc_destroy() - destroy the EPC device
|
||||
* @dev: device that wants to destroy the EPC
|
||||
* @epc: the EPC device that has to be destroyed
|
||||
*
|
||||
* Invoke to destroy the devres associated with this
|
||||
* pci_epc and destroy the EPC device.
|
||||
*/
|
||||
void devm_pci_epc_destroy(struct device *dev, struct pci_epc *epc)
|
||||
{
|
||||
int r;
|
||||
|
||||
r = devres_destroy(dev, devm_pci_epc_release, devm_pci_epc_match,
|
||||
epc);
|
||||
dev_WARN_ONCE(dev, r, "couldn't find PCI EPC resource\n");
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_pci_epc_destroy);
|
||||
|
||||
/**
|
||||
* __pci_epc_create() - create a new endpoint controller (EPC) device
|
||||
* @dev: device that is creating the new EPC
|
||||
* @ops: function pointers for performing EPC operations
|
||||
* @owner: the owner of the module that creates the EPC device
|
||||
*
|
||||
* Invoke to create a new EPC device and add it to pci_epc class.
|
||||
*/
|
||||
struct pci_epc *
|
||||
__pci_epc_create(struct device *dev, const struct pci_epc_ops *ops,
|
||||
struct module *owner)
|
||||
{
|
||||
int ret;
|
||||
struct pci_epc *epc;
|
||||
|
||||
if (WARN_ON(!dev)) {
|
||||
ret = -EINVAL;
|
||||
goto err_ret;
|
||||
}
|
||||
|
||||
epc = kzalloc(sizeof(*epc), GFP_KERNEL);
|
||||
if (!epc) {
|
||||
ret = -ENOMEM;
|
||||
goto err_ret;
|
||||
}
|
||||
|
||||
spin_lock_init(&epc->lock);
|
||||
INIT_LIST_HEAD(&epc->pci_epf);
|
||||
|
||||
device_initialize(&epc->dev);
|
||||
dma_set_coherent_mask(&epc->dev, dev->coherent_dma_mask);
|
||||
epc->dev.class = pci_epc_class;
|
||||
epc->dev.dma_mask = dev->dma_mask;
|
||||
epc->ops = ops;
|
||||
|
||||
ret = dev_set_name(&epc->dev, "%s", dev_name(dev));
|
||||
if (ret)
|
||||
goto put_dev;
|
||||
|
||||
ret = device_add(&epc->dev);
|
||||
if (ret)
|
||||
goto put_dev;
|
||||
|
||||
epc->group = pci_ep_cfs_add_epc_group(dev_name(dev));
|
||||
|
||||
return epc;
|
||||
|
||||
put_dev:
|
||||
put_device(&epc->dev);
|
||||
kfree(epc);
|
||||
|
||||
err_ret:
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__pci_epc_create);
|
||||
|
||||
/**
|
||||
* __devm_pci_epc_create() - create a new endpoint controller (EPC) device
|
||||
* @dev: device that is creating the new EPC
|
||||
* @ops: function pointers for performing EPC operations
|
||||
* @owner: the owner of the module that creates the EPC device
|
||||
*
|
||||
* Invoke to create a new EPC device and add it to pci_epc class.
|
||||
* While at that, it also associates the device with the pci_epc using devres.
|
||||
* On driver detach, release function is invoked on the devres data,
|
||||
* then, devres data is freed.
|
||||
*/
|
||||
struct pci_epc *
|
||||
__devm_pci_epc_create(struct device *dev, const struct pci_epc_ops *ops,
|
||||
struct module *owner)
|
||||
{
|
||||
struct pci_epc **ptr, *epc;
|
||||
|
||||
ptr = devres_alloc(devm_pci_epc_release, sizeof(*ptr), GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
epc = __pci_epc_create(dev, ops, owner);
|
||||
if (!IS_ERR(epc)) {
|
||||
*ptr = epc;
|
||||
devres_add(dev, ptr);
|
||||
} else {
|
||||
devres_free(ptr);
|
||||
}
|
||||
|
||||
return epc;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__devm_pci_epc_create);
|
||||
|
||||
static int __init pci_epc_init(void)
|
||||
{
|
||||
pci_epc_class = class_create(THIS_MODULE, "pci_epc");
|
||||
if (IS_ERR(pci_epc_class)) {
|
||||
pr_err("failed to create pci epc class --> %ld\n",
|
||||
PTR_ERR(pci_epc_class));
|
||||
return PTR_ERR(pci_epc_class);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
module_init(pci_epc_init);
|
||||
|
||||
static void __exit pci_epc_exit(void)
|
||||
{
|
||||
class_destroy(pci_epc_class);
|
||||
}
|
||||
module_exit(pci_epc_exit);
|
||||
|
||||
MODULE_DESCRIPTION("PCI EPC Library");
|
||||
MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
143
drivers/pci/endpoint/pci-epc-mem.c
Normal file
143
drivers/pci/endpoint/pci-epc-mem.c
Normal file
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* PCI Endpoint *Controller* Address Space Management
|
||||
*
|
||||
* Copyright (C) 2017 Texas Instruments
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 of
|
||||
* the License as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <linux/pci-epc.h>
|
||||
|
||||
/**
|
||||
* pci_epc_mem_init() - initialize the pci_epc_mem structure
|
||||
* @epc: the EPC device that invoked pci_epc_mem_init
|
||||
* @phys_base: the physical address of the base
|
||||
* @size: the size of the address space
|
||||
*
|
||||
* Invoke to initialize the pci_epc_mem structure used by the
|
||||
* endpoint functions to allocate mapped PCI address.
|
||||
*/
|
||||
int pci_epc_mem_init(struct pci_epc *epc, phys_addr_t phys_base, size_t size)
|
||||
{
|
||||
int ret;
|
||||
struct pci_epc_mem *mem;
|
||||
unsigned long *bitmap;
|
||||
int pages = size >> PAGE_SHIFT;
|
||||
int bitmap_size = BITS_TO_LONGS(pages) * sizeof(long);
|
||||
|
||||
mem = kzalloc(sizeof(*mem), GFP_KERNEL);
|
||||
if (!mem) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
bitmap = kzalloc(bitmap_size, GFP_KERNEL);
|
||||
if (!bitmap) {
|
||||
ret = -ENOMEM;
|
||||
goto err_mem;
|
||||
}
|
||||
|
||||
mem->bitmap = bitmap;
|
||||
mem->phys_base = phys_base;
|
||||
mem->pages = pages;
|
||||
mem->size = size;
|
||||
|
||||
epc->mem = mem;
|
||||
|
||||
return 0;
|
||||
|
||||
err_mem:
|
||||
kfree(mem);
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_mem_init);
|
||||
|
||||
/**
|
||||
* pci_epc_mem_exit() - cleanup the pci_epc_mem structure
|
||||
* @epc: the EPC device that invoked pci_epc_mem_exit
|
||||
*
|
||||
* Invoke to cleanup the pci_epc_mem structure allocated in
|
||||
* pci_epc_mem_init().
|
||||
*/
|
||||
void pci_epc_mem_exit(struct pci_epc *epc)
|
||||
{
|
||||
struct pci_epc_mem *mem = epc->mem;
|
||||
|
||||
epc->mem = NULL;
|
||||
kfree(mem->bitmap);
|
||||
kfree(mem);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_mem_exit);
|
||||
|
||||
/**
|
||||
* pci_epc_mem_alloc_addr() - allocate memory address from EPC addr space
|
||||
* @epc: the EPC device on which memory has to be allocated
|
||||
* @phys_addr: populate the allocated physical address here
|
||||
* @size: the size of the address space that has to be allocated
|
||||
*
|
||||
* Invoke to allocate memory address from the EPC address space. This
|
||||
* is usually done to map the remote RC address into the local system.
|
||||
*/
|
||||
void __iomem *pci_epc_mem_alloc_addr(struct pci_epc *epc,
|
||||
phys_addr_t *phys_addr, size_t size)
|
||||
{
|
||||
int pageno;
|
||||
void __iomem *virt_addr;
|
||||
struct pci_epc_mem *mem = epc->mem;
|
||||
int order = get_order(size);
|
||||
|
||||
pageno = bitmap_find_free_region(mem->bitmap, mem->pages, order);
|
||||
if (pageno < 0)
|
||||
return NULL;
|
||||
|
||||
*phys_addr = mem->phys_base + (pageno << PAGE_SHIFT);
|
||||
virt_addr = ioremap(*phys_addr, size);
|
||||
if (!virt_addr)
|
||||
bitmap_release_region(mem->bitmap, pageno, order);
|
||||
|
||||
return virt_addr;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_mem_alloc_addr);
|
||||
|
||||
/**
|
||||
* pci_epc_mem_free_addr() - free the allocated memory address
|
||||
* @epc: the EPC device on which memory was allocated
|
||||
* @phys_addr: the allocated physical address
|
||||
* @virt_addr: virtual address of the allocated mem space
|
||||
* @size: the size of the allocated address space
|
||||
*
|
||||
* Invoke to free the memory allocated using pci_epc_mem_alloc_addr.
|
||||
*/
|
||||
void pci_epc_mem_free_addr(struct pci_epc *epc, phys_addr_t phys_addr,
|
||||
void __iomem *virt_addr, size_t size)
|
||||
{
|
||||
int pageno;
|
||||
int order = get_order(size);
|
||||
struct pci_epc_mem *mem = epc->mem;
|
||||
|
||||
iounmap(virt_addr);
|
||||
pageno = (phys_addr - mem->phys_base) >> PAGE_SHIFT;
|
||||
bitmap_release_region(mem->bitmap, pageno, order);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_mem_free_addr);
|
||||
|
||||
MODULE_DESCRIPTION("PCI EPC Address Space Management");
|
||||
MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
359
drivers/pci/endpoint/pci-epf-core.c
Normal file
359
drivers/pci/endpoint/pci-epf-core.c
Normal file
@@ -0,0 +1,359 @@
|
||||
/**
|
||||
* PCI Endpoint *Function* (EPF) library
|
||||
*
|
||||
* Copyright (C) 2017 Texas Instruments
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 of
|
||||
* the License as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <linux/pci-epc.h>
|
||||
#include <linux/pci-epf.h>
|
||||
#include <linux/pci-ep-cfs.h>
|
||||
|
||||
static struct bus_type pci_epf_bus_type;
|
||||
static struct device_type pci_epf_type;
|
||||
|
||||
/**
|
||||
* pci_epf_linkup() - Notify the function driver that EPC device has
|
||||
* established a connection with the Root Complex.
|
||||
* @epf: the EPF device bound to the EPC device which has established
|
||||
* the connection with the host
|
||||
*
|
||||
* Invoke to notify the function driver that EPC device has established
|
||||
* a connection with the Root Complex.
|
||||
*/
|
||||
void pci_epf_linkup(struct pci_epf *epf)
|
||||
{
|
||||
if (!epf->driver) {
|
||||
dev_WARN(&epf->dev, "epf device not bound to driver\n");
|
||||
return;
|
||||
}
|
||||
|
||||
epf->driver->ops->linkup(epf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epf_linkup);
|
||||
|
||||
/**
|
||||
* pci_epf_unbind() - Notify the function driver that the binding between the
|
||||
* EPF device and EPC device has been lost
|
||||
* @epf: the EPF device which has lost the binding with the EPC device
|
||||
*
|
||||
* Invoke to notify the function driver that the binding between the EPF device
|
||||
* and EPC device has been lost.
|
||||
*/
|
||||
void pci_epf_unbind(struct pci_epf *epf)
|
||||
{
|
||||
if (!epf->driver) {
|
||||
dev_WARN(&epf->dev, "epf device not bound to driver\n");
|
||||
return;
|
||||
}
|
||||
|
||||
epf->driver->ops->unbind(epf);
|
||||
module_put(epf->driver->owner);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epf_unbind);
|
||||
|
||||
/**
|
||||
* pci_epf_bind() - Notify the function driver that the EPF device has been
|
||||
* bound to a EPC device
|
||||
* @epf: the EPF device which has been bound to the EPC device
|
||||
*
|
||||
* Invoke to notify the function driver that it has been bound to a EPC device
|
||||
*/
|
||||
int pci_epf_bind(struct pci_epf *epf)
|
||||
{
|
||||
if (!epf->driver) {
|
||||
dev_WARN(&epf->dev, "epf device not bound to driver\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!try_module_get(epf->driver->owner))
|
||||
return -EAGAIN;
|
||||
|
||||
return epf->driver->ops->bind(epf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epf_bind);
|
||||
|
||||
/**
|
||||
* pci_epf_free_space() - free the allocated PCI EPF register space
|
||||
* @addr: the virtual address of the PCI EPF register space
|
||||
* @bar: the BAR number corresponding to the register space
|
||||
*
|
||||
* Invoke to free the allocated PCI EPF register space.
|
||||
*/
|
||||
void pci_epf_free_space(struct pci_epf *epf, void *addr, enum pci_barno bar)
|
||||
{
|
||||
struct device *dev = &epf->dev;
|
||||
|
||||
if (!addr)
|
||||
return;
|
||||
|
||||
dma_free_coherent(dev, epf->bar[bar].size, addr,
|
||||
epf->bar[bar].phys_addr);
|
||||
|
||||
epf->bar[bar].phys_addr = 0;
|
||||
epf->bar[bar].size = 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epf_free_space);
|
||||
|
||||
/**
|
||||
* pci_epf_alloc_space() - allocate memory for the PCI EPF register space
|
||||
* @size: the size of the memory that has to be allocated
|
||||
* @bar: the BAR number corresponding to the allocated register space
|
||||
*
|
||||
* Invoke to allocate memory for the PCI EPF register space.
|
||||
*/
|
||||
void *pci_epf_alloc_space(struct pci_epf *epf, size_t size, enum pci_barno bar)
|
||||
{
|
||||
void *space;
|
||||
struct device *dev = &epf->dev;
|
||||
dma_addr_t phys_addr;
|
||||
|
||||
if (size < 128)
|
||||
size = 128;
|
||||
size = roundup_pow_of_two(size);
|
||||
|
||||
space = dma_alloc_coherent(dev, size, &phys_addr, GFP_KERNEL);
|
||||
if (!space) {
|
||||
dev_err(dev, "failed to allocate mem space\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
epf->bar[bar].phys_addr = phys_addr;
|
||||
epf->bar[bar].size = size;
|
||||
|
||||
return space;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epf_alloc_space);
|
||||
|
||||
/**
|
||||
* pci_epf_unregister_driver() - unregister the PCI EPF driver
|
||||
* @driver: the PCI EPF driver that has to be unregistered
|
||||
*
|
||||
* Invoke to unregister the PCI EPF driver.
|
||||
*/
|
||||
void pci_epf_unregister_driver(struct pci_epf_driver *driver)
|
||||
{
|
||||
pci_ep_cfs_remove_epf_group(driver->group);
|
||||
driver_unregister(&driver->driver);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epf_unregister_driver);
|
||||
|
||||
/**
|
||||
* __pci_epf_register_driver() - register a new PCI EPF driver
|
||||
* @driver: structure representing PCI EPF driver
|
||||
* @owner: the owner of the module that registers the PCI EPF driver
|
||||
*
|
||||
* Invoke to register a new PCI EPF driver.
|
||||
*/
|
||||
int __pci_epf_register_driver(struct pci_epf_driver *driver,
|
||||
struct module *owner)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!driver->ops)
|
||||
return -EINVAL;
|
||||
|
||||
if (!driver->ops->bind || !driver->ops->unbind || !driver->ops->linkup)
|
||||
return -EINVAL;
|
||||
|
||||
driver->driver.bus = &pci_epf_bus_type;
|
||||
driver->driver.owner = owner;
|
||||
|
||||
ret = driver_register(&driver->driver);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
driver->group = pci_ep_cfs_add_epf_group(driver->driver.name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__pci_epf_register_driver);
|
||||
|
||||
/**
|
||||
* pci_epf_destroy() - destroy the created PCI EPF device
|
||||
* @epf: the PCI EPF device that has to be destroyed.
|
||||
*
|
||||
* Invoke to destroy the PCI EPF device created by invoking pci_epf_create().
|
||||
*/
|
||||
void pci_epf_destroy(struct pci_epf *epf)
|
||||
{
|
||||
device_unregister(&epf->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epf_destroy);
|
||||
|
||||
/**
|
||||
* pci_epf_create() - create a new PCI EPF device
|
||||
* @name: the name of the PCI EPF device. This name will be used to bind the
|
||||
* the EPF device to a EPF driver
|
||||
*
|
||||
* Invoke to create a new PCI EPF device by providing the name of the function
|
||||
* device.
|
||||
*/
|
||||
struct pci_epf *pci_epf_create(const char *name)
|
||||
{
|
||||
int ret;
|
||||
struct pci_epf *epf;
|
||||
struct device *dev;
|
||||
char *func_name;
|
||||
char *buf;
|
||||
|
||||
epf = kzalloc(sizeof(*epf), GFP_KERNEL);
|
||||
if (!epf) {
|
||||
ret = -ENOMEM;
|
||||
goto err_ret;
|
||||
}
|
||||
|
||||
buf = kstrdup(name, GFP_KERNEL);
|
||||
if (!buf) {
|
||||
ret = -ENOMEM;
|
||||
goto free_epf;
|
||||
}
|
||||
|
||||
func_name = buf;
|
||||
buf = strchrnul(buf, '.');
|
||||
*buf = '\0';
|
||||
|
||||
epf->name = kstrdup(func_name, GFP_KERNEL);
|
||||
if (!epf->name) {
|
||||
ret = -ENOMEM;
|
||||
goto free_func_name;
|
||||
}
|
||||
|
||||
dev = &epf->dev;
|
||||
device_initialize(dev);
|
||||
dev->bus = &pci_epf_bus_type;
|
||||
dev->type = &pci_epf_type;
|
||||
|
||||
ret = dev_set_name(dev, "%s", name);
|
||||
if (ret)
|
||||
goto put_dev;
|
||||
|
||||
ret = device_add(dev);
|
||||
if (ret)
|
||||
goto put_dev;
|
||||
|
||||
kfree(func_name);
|
||||
return epf;
|
||||
|
||||
put_dev:
|
||||
put_device(dev);
|
||||
kfree(epf->name);
|
||||
|
||||
free_func_name:
|
||||
kfree(func_name);
|
||||
|
||||
free_epf:
|
||||
kfree(epf);
|
||||
|
||||
err_ret:
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epf_create);
|
||||
|
||||
static void pci_epf_dev_release(struct device *dev)
|
||||
{
|
||||
struct pci_epf *epf = to_pci_epf(dev);
|
||||
|
||||
kfree(epf->name);
|
||||
kfree(epf);
|
||||
}
|
||||
|
||||
static struct device_type pci_epf_type = {
|
||||
.release = pci_epf_dev_release,
|
||||
};
|
||||
|
||||
static int
|
||||
pci_epf_match_id(const struct pci_epf_device_id *id, const struct pci_epf *epf)
|
||||
{
|
||||
while (id->name[0]) {
|
||||
if (strcmp(epf->name, id->name) == 0)
|
||||
return true;
|
||||
id++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int pci_epf_device_match(struct device *dev, struct device_driver *drv)
|
||||
{
|
||||
struct pci_epf *epf = to_pci_epf(dev);
|
||||
struct pci_epf_driver *driver = to_pci_epf_driver(drv);
|
||||
|
||||
if (driver->id_table)
|
||||
return pci_epf_match_id(driver->id_table, epf);
|
||||
|
||||
return !strcmp(epf->name, drv->name);
|
||||
}
|
||||
|
||||
static int pci_epf_device_probe(struct device *dev)
|
||||
{
|
||||
struct pci_epf *epf = to_pci_epf(dev);
|
||||
struct pci_epf_driver *driver = to_pci_epf_driver(dev->driver);
|
||||
|
||||
if (!driver->probe)
|
||||
return -ENODEV;
|
||||
|
||||
epf->driver = driver;
|
||||
|
||||
return driver->probe(epf);
|
||||
}
|
||||
|
||||
static int pci_epf_device_remove(struct device *dev)
|
||||
{
|
||||
int ret;
|
||||
struct pci_epf *epf = to_pci_epf(dev);
|
||||
struct pci_epf_driver *driver = to_pci_epf_driver(dev->driver);
|
||||
|
||||
ret = driver->remove(epf);
|
||||
epf->driver = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct bus_type pci_epf_bus_type = {
|
||||
.name = "pci-epf",
|
||||
.match = pci_epf_device_match,
|
||||
.probe = pci_epf_device_probe,
|
||||
.remove = pci_epf_device_remove,
|
||||
};
|
||||
|
||||
static int __init pci_epf_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = bus_register(&pci_epf_bus_type);
|
||||
if (ret) {
|
||||
pr_err("failed to register pci epf bus --> %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
module_init(pci_epf_init);
|
||||
|
||||
static void __exit pci_epf_exit(void)
|
||||
{
|
||||
bus_unregister(&pci_epf_bus_type);
|
||||
}
|
||||
module_exit(pci_epf_exit);
|
||||
|
||||
MODULE_DESCRIPTION("PCI EPF Library");
|
||||
MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@ti.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
@@ -27,6 +27,12 @@ config PCIE_XILINX_NWL
|
||||
or End Point. The current option selection will only
|
||||
support root port enabling.
|
||||
|
||||
config PCI_FTPCI100
|
||||
bool "Faraday Technology FTPCI100 PCI controller"
|
||||
depends on OF
|
||||
depends on ARM
|
||||
default ARCH_GEMINI
|
||||
|
||||
config PCI_TEGRA
|
||||
bool "NVIDIA Tegra PCIe controller"
|
||||
depends on ARCH_TEGRA
|
||||
@@ -95,6 +101,7 @@ config PCI_VERSATILE
|
||||
|
||||
config PCIE_IPROC
|
||||
tristate
|
||||
select PCI_DOMAINS
|
||||
help
|
||||
This enables the iProc PCIe core controller support for Broadcom's
|
||||
iProc family of SoCs. An appropriate bus interface driver needs
|
||||
@@ -115,7 +122,6 @@ config PCIE_IPROC_BCMA
|
||||
depends on ARM && (ARCH_BCM_IPROC || COMPILE_TEST)
|
||||
select PCIE_IPROC
|
||||
select BCMA
|
||||
select PCI_DOMAINS
|
||||
default ARCH_BCM_5301X
|
||||
help
|
||||
Say Y here if you want to use the Broadcom iProc PCIe controller
|
||||
@@ -164,7 +170,7 @@ config PCI_HOST_THUNDER_ECAM
|
||||
Say Y here if you want ECAM support for CN88XX-Pass-1.x Cavium Thunder SoCs.
|
||||
|
||||
config PCIE_ROCKCHIP
|
||||
bool "Rockchip PCIe controller"
|
||||
tristate "Rockchip PCIe controller"
|
||||
depends on ARCH_ROCKCHIP || COMPILE_TEST
|
||||
depends on OF
|
||||
depends on PCI_MSI_IRQ_DOMAIN
|
||||
|
@@ -1,3 +1,4 @@
|
||||
obj-$(CONFIG_PCI_FTPCI100) += pci-ftpci100.o
|
||||
obj-$(CONFIG_PCI_HYPERV) += pci-hyperv.o
|
||||
obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o
|
||||
obj-$(CONFIG_PCI_AARDVARK) += pci-aardvark.o
|
||||
|
@@ -200,10 +200,12 @@ struct advk_pcie {
|
||||
struct list_head resources;
|
||||
struct irq_domain *irq_domain;
|
||||
struct irq_chip irq_chip;
|
||||
struct msi_controller msi;
|
||||
struct irq_domain *msi_domain;
|
||||
struct irq_domain *msi_inner_domain;
|
||||
struct irq_chip msi_bottom_irq_chip;
|
||||
struct irq_chip msi_irq_chip;
|
||||
DECLARE_BITMAP(msi_irq_in_use, MSI_IRQ_NUM);
|
||||
struct msi_domain_info msi_domain_info;
|
||||
DECLARE_BITMAP(msi_used, MSI_IRQ_NUM);
|
||||
struct mutex msi_used_lock;
|
||||
u16 msi_msg;
|
||||
int root_bus_nr;
|
||||
@@ -545,94 +547,64 @@ static struct pci_ops advk_pcie_ops = {
|
||||
.write = advk_pcie_wr_conf,
|
||||
};
|
||||
|
||||
static int advk_pcie_alloc_msi(struct advk_pcie *pcie)
|
||||
static void advk_msi_irq_compose_msi_msg(struct irq_data *data,
|
||||
struct msi_msg *msg)
|
||||
{
|
||||
int hwirq;
|
||||
struct advk_pcie *pcie = irq_data_get_irq_chip_data(data);
|
||||
phys_addr_t msi_msg = virt_to_phys(&pcie->msi_msg);
|
||||
|
||||
msg->address_lo = lower_32_bits(msi_msg);
|
||||
msg->address_hi = upper_32_bits(msi_msg);
|
||||
msg->data = data->irq;
|
||||
}
|
||||
|
||||
static int advk_msi_set_affinity(struct irq_data *irq_data,
|
||||
const struct cpumask *mask, bool force)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int advk_msi_irq_domain_alloc(struct irq_domain *domain,
|
||||
unsigned int virq,
|
||||
unsigned int nr_irqs, void *args)
|
||||
{
|
||||
struct advk_pcie *pcie = domain->host_data;
|
||||
int hwirq, i;
|
||||
|
||||
mutex_lock(&pcie->msi_used_lock);
|
||||
hwirq = find_first_zero_bit(pcie->msi_irq_in_use, MSI_IRQ_NUM);
|
||||
if (hwirq >= MSI_IRQ_NUM)
|
||||
hwirq = -ENOSPC;
|
||||
else
|
||||
set_bit(hwirq, pcie->msi_irq_in_use);
|
||||
hwirq = bitmap_find_next_zero_area(pcie->msi_used, MSI_IRQ_NUM,
|
||||
0, nr_irqs, 0);
|
||||
if (hwirq >= MSI_IRQ_NUM) {
|
||||
mutex_unlock(&pcie->msi_used_lock);
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
bitmap_set(pcie->msi_used, hwirq, nr_irqs);
|
||||
mutex_unlock(&pcie->msi_used_lock);
|
||||
|
||||
for (i = 0; i < nr_irqs; i++)
|
||||
irq_domain_set_info(domain, virq + i, hwirq + i,
|
||||
&pcie->msi_bottom_irq_chip,
|
||||
domain->host_data, handle_simple_irq,
|
||||
NULL, NULL);
|
||||
|
||||
return hwirq;
|
||||
}
|
||||
|
||||
static void advk_pcie_free_msi(struct advk_pcie *pcie, int hwirq)
|
||||
static void advk_msi_irq_domain_free(struct irq_domain *domain,
|
||||
unsigned int virq, unsigned int nr_irqs)
|
||||
{
|
||||
struct device *dev = &pcie->pdev->dev;
|
||||
struct irq_data *d = irq_domain_get_irq_data(domain, virq);
|
||||
struct advk_pcie *pcie = domain->host_data;
|
||||
|
||||
mutex_lock(&pcie->msi_used_lock);
|
||||
if (!test_bit(hwirq, pcie->msi_irq_in_use))
|
||||
dev_err(dev, "trying to free unused MSI#%d\n", hwirq);
|
||||
else
|
||||
clear_bit(hwirq, pcie->msi_irq_in_use);
|
||||
bitmap_clear(pcie->msi_used, d->hwirq, nr_irqs);
|
||||
mutex_unlock(&pcie->msi_used_lock);
|
||||
}
|
||||
|
||||
static int advk_pcie_setup_msi_irq(struct msi_controller *chip,
|
||||
struct pci_dev *pdev,
|
||||
struct msi_desc *desc)
|
||||
{
|
||||
struct advk_pcie *pcie = pdev->bus->sysdata;
|
||||
struct msi_msg msg;
|
||||
int virq, hwirq;
|
||||
phys_addr_t msi_msg_phys;
|
||||
|
||||
/* We support MSI, but not MSI-X */
|
||||
if (desc->msi_attrib.is_msix)
|
||||
return -EINVAL;
|
||||
|
||||
hwirq = advk_pcie_alloc_msi(pcie);
|
||||
if (hwirq < 0)
|
||||
return hwirq;
|
||||
|
||||
virq = irq_create_mapping(pcie->msi_domain, hwirq);
|
||||
if (!virq) {
|
||||
advk_pcie_free_msi(pcie, hwirq);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
irq_set_msi_desc(virq, desc);
|
||||
|
||||
msi_msg_phys = virt_to_phys(&pcie->msi_msg);
|
||||
|
||||
msg.address_lo = lower_32_bits(msi_msg_phys);
|
||||
msg.address_hi = upper_32_bits(msi_msg_phys);
|
||||
msg.data = virq;
|
||||
|
||||
pci_write_msi_msg(virq, &msg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void advk_pcie_teardown_msi_irq(struct msi_controller *chip,
|
||||
unsigned int irq)
|
||||
{
|
||||
struct irq_data *d = irq_get_irq_data(irq);
|
||||
struct msi_desc *msi = irq_data_get_msi_desc(d);
|
||||
struct advk_pcie *pcie = msi_desc_to_pci_sysdata(msi);
|
||||
unsigned long hwirq = d->hwirq;
|
||||
|
||||
irq_dispose_mapping(irq);
|
||||
advk_pcie_free_msi(pcie, hwirq);
|
||||
}
|
||||
|
||||
static int advk_pcie_msi_map(struct irq_domain *domain,
|
||||
unsigned int virq, irq_hw_number_t hw)
|
||||
{
|
||||
struct advk_pcie *pcie = domain->host_data;
|
||||
|
||||
irq_set_chip_and_handler(virq, &pcie->msi_irq_chip,
|
||||
handle_simple_irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops advk_pcie_msi_irq_ops = {
|
||||
.map = advk_pcie_msi_map,
|
||||
static const struct irq_domain_ops advk_msi_domain_ops = {
|
||||
.alloc = advk_msi_irq_domain_alloc,
|
||||
.free = advk_msi_irq_domain_free,
|
||||
};
|
||||
|
||||
static void advk_pcie_irq_mask(struct irq_data *d)
|
||||
@@ -680,31 +652,26 @@ static int advk_pcie_init_msi_irq_domain(struct advk_pcie *pcie)
|
||||
{
|
||||
struct device *dev = &pcie->pdev->dev;
|
||||
struct device_node *node = dev->of_node;
|
||||
struct irq_chip *msi_irq_chip;
|
||||
struct msi_controller *msi;
|
||||
struct irq_chip *bottom_ic, *msi_ic;
|
||||
struct msi_domain_info *msi_di;
|
||||
phys_addr_t msi_msg_phys;
|
||||
int ret;
|
||||
|
||||
msi_irq_chip = &pcie->msi_irq_chip;
|
||||
|
||||
msi_irq_chip->name = devm_kasprintf(dev, GFP_KERNEL, "%s-msi",
|
||||
dev_name(dev));
|
||||
if (!msi_irq_chip->name)
|
||||
return -ENOMEM;
|
||||
|
||||
msi_irq_chip->irq_enable = pci_msi_unmask_irq;
|
||||
msi_irq_chip->irq_disable = pci_msi_mask_irq;
|
||||
msi_irq_chip->irq_mask = pci_msi_mask_irq;
|
||||
msi_irq_chip->irq_unmask = pci_msi_unmask_irq;
|
||||
|
||||
msi = &pcie->msi;
|
||||
|
||||
msi->setup_irq = advk_pcie_setup_msi_irq;
|
||||
msi->teardown_irq = advk_pcie_teardown_msi_irq;
|
||||
msi->of_node = node;
|
||||
|
||||
mutex_init(&pcie->msi_used_lock);
|
||||
|
||||
bottom_ic = &pcie->msi_bottom_irq_chip;
|
||||
|
||||
bottom_ic->name = "MSI";
|
||||
bottom_ic->irq_compose_msi_msg = advk_msi_irq_compose_msi_msg;
|
||||
bottom_ic->irq_set_affinity = advk_msi_set_affinity;
|
||||
|
||||
msi_ic = &pcie->msi_irq_chip;
|
||||
msi_ic->name = "advk-MSI";
|
||||
|
||||
msi_di = &pcie->msi_domain_info;
|
||||
msi_di->flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
|
||||
MSI_FLAG_MULTI_PCI_MSI;
|
||||
msi_di->chip = msi_ic;
|
||||
|
||||
msi_msg_phys = virt_to_phys(&pcie->msi_msg);
|
||||
|
||||
advk_writel(pcie, lower_32_bits(msi_msg_phys),
|
||||
@@ -712,16 +679,18 @@ static int advk_pcie_init_msi_irq_domain(struct advk_pcie *pcie)
|
||||
advk_writel(pcie, upper_32_bits(msi_msg_phys),
|
||||
PCIE_MSI_ADDR_HIGH_REG);
|
||||
|
||||
pcie->msi_domain =
|
||||
pcie->msi_inner_domain =
|
||||
irq_domain_add_linear(NULL, MSI_IRQ_NUM,
|
||||
&advk_pcie_msi_irq_ops, pcie);
|
||||
if (!pcie->msi_domain)
|
||||
&advk_msi_domain_ops, pcie);
|
||||
if (!pcie->msi_inner_domain)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = of_pci_msi_chip_add(msi);
|
||||
if (ret < 0) {
|
||||
irq_domain_remove(pcie->msi_domain);
|
||||
return ret;
|
||||
pcie->msi_domain =
|
||||
pci_msi_create_irq_domain(of_node_to_fwnode(node),
|
||||
msi_di, pcie->msi_inner_domain);
|
||||
if (!pcie->msi_domain) {
|
||||
irq_domain_remove(pcie->msi_inner_domain);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -729,8 +698,8 @@ static int advk_pcie_init_msi_irq_domain(struct advk_pcie *pcie)
|
||||
|
||||
static void advk_pcie_remove_msi_irq_domain(struct advk_pcie *pcie)
|
||||
{
|
||||
of_pci_msi_chip_remove(&pcie->msi);
|
||||
irq_domain_remove(pcie->msi_domain);
|
||||
irq_domain_remove(pcie->msi_inner_domain);
|
||||
}
|
||||
|
||||
static int advk_pcie_init_irq_domain(struct advk_pcie *pcie)
|
||||
@@ -917,8 +886,6 @@ static int advk_pcie_probe(struct platform_device *pdev)
|
||||
struct advk_pcie *pcie;
|
||||
struct resource *res;
|
||||
struct pci_bus *bus, *child;
|
||||
struct msi_controller *msi;
|
||||
struct device_node *msi_node;
|
||||
int ret, irq;
|
||||
|
||||
pcie = devm_kzalloc(dev, sizeof(struct advk_pcie), GFP_KERNEL);
|
||||
@@ -962,14 +929,8 @@ static int advk_pcie_probe(struct platform_device *pdev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
msi_node = of_parse_phandle(dev->of_node, "msi-parent", 0);
|
||||
if (msi_node)
|
||||
msi = of_pci_find_msi_chip_by_node(msi_node);
|
||||
else
|
||||
msi = NULL;
|
||||
|
||||
bus = pci_scan_root_bus_msi(dev, 0, &advk_pcie_ops,
|
||||
pcie, &pcie->resources, &pcie->msi);
|
||||
bus = pci_scan_root_bus(dev, 0, &advk_pcie_ops,
|
||||
pcie, &pcie->resources);
|
||||
if (!bus) {
|
||||
advk_pcie_remove_msi_irq_domain(pcie);
|
||||
advk_pcie_remove_irq_domain(pcie);
|
||||
|
563
drivers/pci/host/pci-ftpci100.c
Normal file
563
drivers/pci/host/pci-ftpci100.c
Normal file
@@ -0,0 +1,563 @@
|
||||
/*
|
||||
* Support for Faraday Technology FTPC100 PCI Controller
|
||||
*
|
||||
* Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
|
||||
*
|
||||
* Based on the out-of-tree OpenWRT patch for Cortina Gemini:
|
||||
* Copyright (C) 2009 Janos Laube <janos.dev@gmail.com>
|
||||
* Copyright (C) 2009 Paulius Zaleckas <paulius.zaleckas@teltonika.lt>
|
||||
* Based on SL2312 PCI controller code
|
||||
* Storlink (C) 2003
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_pci.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/irqchip/chained_irq.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/irq.h>
|
||||
|
||||
/*
|
||||
* Special configuration registers directly in the first few words
|
||||
* in I/O space.
|
||||
*/
|
||||
#define PCI_IOSIZE 0x00
|
||||
#define PCI_PROT 0x04 /* AHB protection */
|
||||
#define PCI_CTRL 0x08 /* PCI control signal */
|
||||
#define PCI_SOFTRST 0x10 /* Soft reset counter and response error enable */
|
||||
#define PCI_CONFIG 0x28 /* PCI configuration command register */
|
||||
#define PCI_DATA 0x2C
|
||||
|
||||
#define FARADAY_PCI_PMC 0x40 /* Power management control */
|
||||
#define FARADAY_PCI_PMCSR 0x44 /* Power management status */
|
||||
#define FARADAY_PCI_CTRL1 0x48 /* Control register 1 */
|
||||
#define FARADAY_PCI_CTRL2 0x4C /* Control register 2 */
|
||||
#define FARADAY_PCI_MEM1_BASE_SIZE 0x50 /* Memory base and size #1 */
|
||||
#define FARADAY_PCI_MEM2_BASE_SIZE 0x54 /* Memory base and size #2 */
|
||||
#define FARADAY_PCI_MEM3_BASE_SIZE 0x58 /* Memory base and size #3 */
|
||||
|
||||
/* Bits 31..28 gives INTD..INTA status */
|
||||
#define PCI_CTRL2_INTSTS_SHIFT 28
|
||||
#define PCI_CTRL2_INTMASK_CMDERR BIT(27)
|
||||
#define PCI_CTRL2_INTMASK_PARERR BIT(26)
|
||||
/* Bits 25..22 masks INTD..INTA */
|
||||
#define PCI_CTRL2_INTMASK_SHIFT 22
|
||||
#define PCI_CTRL2_INTMASK_MABRT_RX BIT(21)
|
||||
#define PCI_CTRL2_INTMASK_TABRT_RX BIT(20)
|
||||
#define PCI_CTRL2_INTMASK_TABRT_TX BIT(19)
|
||||
#define PCI_CTRL2_INTMASK_RETRY4 BIT(18)
|
||||
#define PCI_CTRL2_INTMASK_SERR_RX BIT(17)
|
||||
#define PCI_CTRL2_INTMASK_PERR_RX BIT(16)
|
||||
/* Bit 15 reserved */
|
||||
#define PCI_CTRL2_MSTPRI_REQ6 BIT(14)
|
||||
#define PCI_CTRL2_MSTPRI_REQ5 BIT(13)
|
||||
#define PCI_CTRL2_MSTPRI_REQ4 BIT(12)
|
||||
#define PCI_CTRL2_MSTPRI_REQ3 BIT(11)
|
||||
#define PCI_CTRL2_MSTPRI_REQ2 BIT(10)
|
||||
#define PCI_CTRL2_MSTPRI_REQ1 BIT(9)
|
||||
#define PCI_CTRL2_MSTPRI_REQ0 BIT(8)
|
||||
/* Bits 7..4 reserved */
|
||||
/* Bits 3..0 TRDYW */
|
||||
|
||||
/*
|
||||
* Memory configs:
|
||||
* Bit 31..20 defines the PCI side memory base
|
||||
* Bit 19..16 (4 bits) defines the size per below
|
||||
*/
|
||||
#define FARADAY_PCI_MEMBASE_MASK 0xfff00000
|
||||
#define FARADAY_PCI_MEMSIZE_1MB 0x0
|
||||
#define FARADAY_PCI_MEMSIZE_2MB 0x1
|
||||
#define FARADAY_PCI_MEMSIZE_4MB 0x2
|
||||
#define FARADAY_PCI_MEMSIZE_8MB 0x3
|
||||
#define FARADAY_PCI_MEMSIZE_16MB 0x4
|
||||
#define FARADAY_PCI_MEMSIZE_32MB 0x5
|
||||
#define FARADAY_PCI_MEMSIZE_64MB 0x6
|
||||
#define FARADAY_PCI_MEMSIZE_128MB 0x7
|
||||
#define FARADAY_PCI_MEMSIZE_256MB 0x8
|
||||
#define FARADAY_PCI_MEMSIZE_512MB 0x9
|
||||
#define FARADAY_PCI_MEMSIZE_1GB 0xa
|
||||
#define FARADAY_PCI_MEMSIZE_2GB 0xb
|
||||
#define FARADAY_PCI_MEMSIZE_SHIFT 16
|
||||
|
||||
/*
|
||||
* The DMA base is set to 0x0 for all memory segments, it reflects the
|
||||
* fact that the memory of the host system starts at 0x0.
|
||||
*/
|
||||
#define FARADAY_PCI_DMA_MEM1_BASE 0x00000000
|
||||
#define FARADAY_PCI_DMA_MEM2_BASE 0x00000000
|
||||
#define FARADAY_PCI_DMA_MEM3_BASE 0x00000000
|
||||
|
||||
/* Defines for PCI configuration command register */
|
||||
#define PCI_CONF_ENABLE BIT(31)
|
||||
#define PCI_CONF_WHERE(r) ((r) & 0xFC)
|
||||
#define PCI_CONF_BUS(b) (((b) & 0xFF) << 16)
|
||||
#define PCI_CONF_DEVICE(d) (((d) & 0x1F) << 11)
|
||||
#define PCI_CONF_FUNCTION(f) (((f) & 0x07) << 8)
|
||||
|
||||
/**
|
||||
* struct faraday_pci_variant - encodes IP block differences
|
||||
* @cascaded_irq: this host has cascaded IRQs from an interrupt controller
|
||||
* embedded in the host bridge.
|
||||
*/
|
||||
struct faraday_pci_variant {
|
||||
bool cascaded_irq;
|
||||
};
|
||||
|
||||
struct faraday_pci {
|
||||
struct device *dev;
|
||||
void __iomem *base;
|
||||
struct irq_domain *irqdomain;
|
||||
struct pci_bus *bus;
|
||||
};
|
||||
|
||||
static int faraday_res_to_memcfg(resource_size_t mem_base,
|
||||
resource_size_t mem_size, u32 *val)
|
||||
{
|
||||
u32 outval;
|
||||
|
||||
switch (mem_size) {
|
||||
case SZ_1M:
|
||||
outval = FARADAY_PCI_MEMSIZE_1MB;
|
||||
break;
|
||||
case SZ_2M:
|
||||
outval = FARADAY_PCI_MEMSIZE_2MB;
|
||||
break;
|
||||
case SZ_4M:
|
||||
outval = FARADAY_PCI_MEMSIZE_4MB;
|
||||
break;
|
||||
case SZ_8M:
|
||||
outval = FARADAY_PCI_MEMSIZE_8MB;
|
||||
break;
|
||||
case SZ_16M:
|
||||
outval = FARADAY_PCI_MEMSIZE_16MB;
|
||||
break;
|
||||
case SZ_32M:
|
||||
outval = FARADAY_PCI_MEMSIZE_32MB;
|
||||
break;
|
||||
case SZ_64M:
|
||||
outval = FARADAY_PCI_MEMSIZE_64MB;
|
||||
break;
|
||||
case SZ_128M:
|
||||
outval = FARADAY_PCI_MEMSIZE_128MB;
|
||||
break;
|
||||
case SZ_256M:
|
||||
outval = FARADAY_PCI_MEMSIZE_256MB;
|
||||
break;
|
||||
case SZ_512M:
|
||||
outval = FARADAY_PCI_MEMSIZE_512MB;
|
||||
break;
|
||||
case SZ_1G:
|
||||
outval = FARADAY_PCI_MEMSIZE_1GB;
|
||||
break;
|
||||
case SZ_2G:
|
||||
outval = FARADAY_PCI_MEMSIZE_2GB;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
outval <<= FARADAY_PCI_MEMSIZE_SHIFT;
|
||||
|
||||
/* This is probably not good */
|
||||
if (mem_base & ~(FARADAY_PCI_MEMBASE_MASK))
|
||||
pr_warn("truncated PCI memory base\n");
|
||||
/* Translate to bridge side address space */
|
||||
outval |= (mem_base & FARADAY_PCI_MEMBASE_MASK);
|
||||
pr_debug("Translated pci base @%pap, size %pap to config %08x\n",
|
||||
&mem_base, &mem_size, outval);
|
||||
|
||||
*val = outval;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int faraday_pci_read_config(struct pci_bus *bus, unsigned int fn,
|
||||
int config, int size, u32 *value)
|
||||
{
|
||||
struct faraday_pci *p = bus->sysdata;
|
||||
|
||||
writel(PCI_CONF_BUS(bus->number) |
|
||||
PCI_CONF_DEVICE(PCI_SLOT(fn)) |
|
||||
PCI_CONF_FUNCTION(PCI_FUNC(fn)) |
|
||||
PCI_CONF_WHERE(config) |
|
||||
PCI_CONF_ENABLE,
|
||||
p->base + PCI_CONFIG);
|
||||
|
||||
*value = readl(p->base + PCI_DATA);
|
||||
|
||||
if (size == 1)
|
||||
*value = (*value >> (8 * (config & 3))) & 0xFF;
|
||||
else if (size == 2)
|
||||
*value = (*value >> (8 * (config & 3))) & 0xFFFF;
|
||||
|
||||
dev_dbg(&bus->dev,
|
||||
"[read] slt: %.2d, fnc: %d, cnf: 0x%.2X, val (%d bytes): 0x%.8X\n",
|
||||
PCI_SLOT(fn), PCI_FUNC(fn), config, size, *value);
|
||||
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
|
||||
static int faraday_pci_write_config(struct pci_bus *bus, unsigned int fn,
|
||||
int config, int size, u32 value)
|
||||
{
|
||||
struct faraday_pci *p = bus->sysdata;
|
||||
int ret = PCIBIOS_SUCCESSFUL;
|
||||
|
||||
dev_dbg(&bus->dev,
|
||||
"[write] slt: %.2d, fnc: %d, cnf: 0x%.2X, val (%d bytes): 0x%.8X\n",
|
||||
PCI_SLOT(fn), PCI_FUNC(fn), config, size, value);
|
||||
|
||||
writel(PCI_CONF_BUS(bus->number) |
|
||||
PCI_CONF_DEVICE(PCI_SLOT(fn)) |
|
||||
PCI_CONF_FUNCTION(PCI_FUNC(fn)) |
|
||||
PCI_CONF_WHERE(config) |
|
||||
PCI_CONF_ENABLE,
|
||||
p->base + PCI_CONFIG);
|
||||
|
||||
switch (size) {
|
||||
case 4:
|
||||
writel(value, p->base + PCI_DATA);
|
||||
break;
|
||||
case 2:
|
||||
writew(value, p->base + PCI_DATA + (config & 3));
|
||||
break;
|
||||
case 1:
|
||||
writeb(value, p->base + PCI_DATA + (config & 3));
|
||||
break;
|
||||
default:
|
||||
ret = PCIBIOS_BAD_REGISTER_NUMBER;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct pci_ops faraday_pci_ops = {
|
||||
.read = faraday_pci_read_config,
|
||||
.write = faraday_pci_write_config,
|
||||
};
|
||||
|
||||
static void faraday_pci_ack_irq(struct irq_data *d)
|
||||
{
|
||||
struct faraday_pci *p = irq_data_get_irq_chip_data(d);
|
||||
unsigned int reg;
|
||||
|
||||
faraday_pci_read_config(p->bus, 0, FARADAY_PCI_CTRL2, 4, ®);
|
||||
reg &= ~(0xF << PCI_CTRL2_INTSTS_SHIFT);
|
||||
reg |= BIT(irqd_to_hwirq(d) + PCI_CTRL2_INTSTS_SHIFT);
|
||||
faraday_pci_write_config(p->bus, 0, FARADAY_PCI_CTRL2, 4, reg);
|
||||
}
|
||||
|
||||
static void faraday_pci_mask_irq(struct irq_data *d)
|
||||
{
|
||||
struct faraday_pci *p = irq_data_get_irq_chip_data(d);
|
||||
unsigned int reg;
|
||||
|
||||
faraday_pci_read_config(p->bus, 0, FARADAY_PCI_CTRL2, 4, ®);
|
||||
reg &= ~((0xF << PCI_CTRL2_INTSTS_SHIFT)
|
||||
| BIT(irqd_to_hwirq(d) + PCI_CTRL2_INTMASK_SHIFT));
|
||||
faraday_pci_write_config(p->bus, 0, FARADAY_PCI_CTRL2, 4, reg);
|
||||
}
|
||||
|
||||
static void faraday_pci_unmask_irq(struct irq_data *d)
|
||||
{
|
||||
struct faraday_pci *p = irq_data_get_irq_chip_data(d);
|
||||
unsigned int reg;
|
||||
|
||||
faraday_pci_read_config(p->bus, 0, FARADAY_PCI_CTRL2, 4, ®);
|
||||
reg &= ~(0xF << PCI_CTRL2_INTSTS_SHIFT);
|
||||
reg |= BIT(irqd_to_hwirq(d) + PCI_CTRL2_INTMASK_SHIFT);
|
||||
faraday_pci_write_config(p->bus, 0, FARADAY_PCI_CTRL2, 4, reg);
|
||||
}
|
||||
|
||||
static void faraday_pci_irq_handler(struct irq_desc *desc)
|
||||
{
|
||||
struct faraday_pci *p = irq_desc_get_handler_data(desc);
|
||||
struct irq_chip *irqchip = irq_desc_get_chip(desc);
|
||||
unsigned int irq_stat, reg, i;
|
||||
|
||||
faraday_pci_read_config(p->bus, 0, FARADAY_PCI_CTRL2, 4, ®);
|
||||
irq_stat = reg >> PCI_CTRL2_INTSTS_SHIFT;
|
||||
|
||||
chained_irq_enter(irqchip, desc);
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
if ((irq_stat & BIT(i)) == 0)
|
||||
continue;
|
||||
generic_handle_irq(irq_find_mapping(p->irqdomain, i));
|
||||
}
|
||||
|
||||
chained_irq_exit(irqchip, desc);
|
||||
}
|
||||
|
||||
static struct irq_chip faraday_pci_irq_chip = {
|
||||
.name = "PCI",
|
||||
.irq_ack = faraday_pci_ack_irq,
|
||||
.irq_mask = faraday_pci_mask_irq,
|
||||
.irq_unmask = faraday_pci_unmask_irq,
|
||||
};
|
||||
|
||||
static int faraday_pci_irq_map(struct irq_domain *domain, unsigned int irq,
|
||||
irq_hw_number_t hwirq)
|
||||
{
|
||||
irq_set_chip_and_handler(irq, &faraday_pci_irq_chip, handle_level_irq);
|
||||
irq_set_chip_data(irq, domain->host_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops faraday_pci_irqdomain_ops = {
|
||||
.map = faraday_pci_irq_map,
|
||||
};
|
||||
|
||||
static int faraday_pci_setup_cascaded_irq(struct faraday_pci *p)
|
||||
{
|
||||
struct device_node *intc = of_get_next_child(p->dev->of_node, NULL);
|
||||
int irq;
|
||||
int i;
|
||||
|
||||
if (!intc) {
|
||||
dev_err(p->dev, "missing child interrupt-controller node\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* All PCI IRQs cascade off this one */
|
||||
irq = of_irq_get(intc, 0);
|
||||
if (!irq) {
|
||||
dev_err(p->dev, "failed to get parent IRQ\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
p->irqdomain = irq_domain_add_linear(intc, 4,
|
||||
&faraday_pci_irqdomain_ops, p);
|
||||
if (!p->irqdomain) {
|
||||
dev_err(p->dev, "failed to create Gemini PCI IRQ domain\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
irq_set_chained_handler_and_data(irq, faraday_pci_irq_handler, p);
|
||||
|
||||
for (i = 0; i < 4; i++)
|
||||
irq_create_mapping(p->irqdomain, i);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pci_dma_range_parser_init(struct of_pci_range_parser *parser,
|
||||
struct device_node *node)
|
||||
{
|
||||
const int na = 3, ns = 2;
|
||||
int rlen;
|
||||
|
||||
parser->node = node;
|
||||
parser->pna = of_n_addr_cells(node);
|
||||
parser->np = parser->pna + na + ns;
|
||||
|
||||
parser->range = of_get_property(node, "dma-ranges", &rlen);
|
||||
if (!parser->range)
|
||||
return -ENOENT;
|
||||
parser->end = parser->range + rlen / sizeof(__be32);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int faraday_pci_parse_map_dma_ranges(struct faraday_pci *p,
|
||||
struct device_node *np)
|
||||
{
|
||||
struct of_pci_range range;
|
||||
struct of_pci_range_parser parser;
|
||||
struct device *dev = p->dev;
|
||||
u32 confreg[3] = {
|
||||
FARADAY_PCI_MEM1_BASE_SIZE,
|
||||
FARADAY_PCI_MEM2_BASE_SIZE,
|
||||
FARADAY_PCI_MEM3_BASE_SIZE,
|
||||
};
|
||||
int i = 0;
|
||||
u32 val;
|
||||
|
||||
if (pci_dma_range_parser_init(&parser, np)) {
|
||||
dev_err(dev, "missing dma-ranges property\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the dma-ranges from the device tree
|
||||
*/
|
||||
for_each_of_pci_range(&parser, &range) {
|
||||
u64 end = range.pci_addr + range.size - 1;
|
||||
int ret;
|
||||
|
||||
ret = faraday_res_to_memcfg(range.pci_addr, range.size, &val);
|
||||
if (ret) {
|
||||
dev_err(dev,
|
||||
"DMA range %d: illegal MEM resource size\n", i);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev_info(dev, "DMA MEM%d BASE: 0x%016llx -> 0x%016llx config %08x\n",
|
||||
i + 1, range.pci_addr, end, val);
|
||||
if (i <= 2) {
|
||||
faraday_pci_write_config(p->bus, 0, confreg[i],
|
||||
4, val);
|
||||
} else {
|
||||
dev_err(dev, "ignore extraneous dma-range %d\n", i);
|
||||
break;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int faraday_pci_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
const struct faraday_pci_variant *variant =
|
||||
of_device_get_match_data(dev);
|
||||
struct resource *regs;
|
||||
resource_size_t io_base;
|
||||
struct resource_entry *win;
|
||||
struct faraday_pci *p;
|
||||
struct resource *mem;
|
||||
struct resource *io;
|
||||
struct pci_host_bridge *host;
|
||||
int ret;
|
||||
u32 val;
|
||||
LIST_HEAD(res);
|
||||
|
||||
host = pci_alloc_host_bridge(sizeof(*p));
|
||||
if (!host)
|
||||
return -ENOMEM;
|
||||
|
||||
host->dev.parent = dev;
|
||||
host->ops = &faraday_pci_ops;
|
||||
host->busnr = 0;
|
||||
host->msi = NULL;
|
||||
p = pci_host_bridge_priv(host);
|
||||
host->sysdata = p;
|
||||
p->dev = dev;
|
||||
|
||||
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
p->base = devm_ioremap_resource(dev, regs);
|
||||
if (IS_ERR(p->base))
|
||||
return PTR_ERR(p->base);
|
||||
|
||||
ret = of_pci_get_host_bridge_resources(dev->of_node, 0, 0xff,
|
||||
&res, &io_base);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_request_pci_bus_resources(dev, &res);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Get the I/O and memory ranges from DT */
|
||||
resource_list_for_each_entry(win, &res) {
|
||||
switch (resource_type(win->res)) {
|
||||
case IORESOURCE_IO:
|
||||
io = win->res;
|
||||
io->name = "Gemini PCI I/O";
|
||||
if (!faraday_res_to_memcfg(io->start - win->offset,
|
||||
resource_size(io), &val)) {
|
||||
/* setup I/O space size */
|
||||
writel(val, p->base + PCI_IOSIZE);
|
||||
} else {
|
||||
dev_err(dev, "illegal IO mem size\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = pci_remap_iospace(io, io_base);
|
||||
if (ret) {
|
||||
dev_warn(dev, "error %d: failed to map resource %pR\n",
|
||||
ret, io);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case IORESOURCE_MEM:
|
||||
mem = win->res;
|
||||
mem->name = "Gemini PCI MEM";
|
||||
break;
|
||||
case IORESOURCE_BUS:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Setup hostbridge */
|
||||
val = readl(p->base + PCI_CTRL);
|
||||
val |= PCI_COMMAND_IO;
|
||||
val |= PCI_COMMAND_MEMORY;
|
||||
val |= PCI_COMMAND_MASTER;
|
||||
writel(val, p->base + PCI_CTRL);
|
||||
|
||||
list_splice_init(&res, &host->windows);
|
||||
ret = pci_register_host_bridge(host);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to register host: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
p->bus = host->bus;
|
||||
|
||||
/* Mask and clear all interrupts */
|
||||
faraday_pci_write_config(p->bus, 0, FARADAY_PCI_CTRL2 + 2, 2, 0xF000);
|
||||
if (variant->cascaded_irq) {
|
||||
ret = faraday_pci_setup_cascaded_irq(p);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to setup cascaded IRQ\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = faraday_pci_parse_map_dma_ranges(p, dev->of_node);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pci_scan_child_bus(p->bus);
|
||||
pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci);
|
||||
pci_bus_assign_resources(p->bus);
|
||||
pci_bus_add_devices(p->bus);
|
||||
pci_free_resource_list(&res);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* We encode bridge variants here, we have at least two so it doesn't
|
||||
* hurt to have infrastructure to encompass future variants as well.
|
||||
*/
|
||||
const struct faraday_pci_variant faraday_regular = {
|
||||
.cascaded_irq = true,
|
||||
};
|
||||
|
||||
const struct faraday_pci_variant faraday_dual = {
|
||||
.cascaded_irq = false,
|
||||
};
|
||||
|
||||
static const struct of_device_id faraday_pci_of_match[] = {
|
||||
{
|
||||
.compatible = "faraday,ftpci100",
|
||||
.data = &faraday_regular,
|
||||
},
|
||||
{
|
||||
.compatible = "faraday,ftpci100-dual",
|
||||
.data = &faraday_dual,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver faraday_pci_driver = {
|
||||
.driver = {
|
||||
.name = "ftpci100",
|
||||
.of_match_table = of_match_ptr(faraday_pci_of_match),
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = faraday_pci_probe,
|
||||
};
|
||||
builtin_platform_driver(faraday_pci_driver);
|
@@ -60,6 +60,7 @@ static struct platform_driver gen_pci_driver = {
|
||||
.driver = {
|
||||
.name = "pci-host-generic",
|
||||
.of_match_table = gen_pci_of_match,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = gen_pci_probe,
|
||||
};
|
||||
|
@@ -56,6 +56,7 @@
|
||||
#include <asm/apic.h>
|
||||
#include <linux/msi.h>
|
||||
#include <linux/hyperv.h>
|
||||
#include <linux/refcount.h>
|
||||
#include <asm/mshyperv.h>
|
||||
|
||||
/*
|
||||
@@ -72,6 +73,7 @@ enum {
|
||||
PCI_PROTOCOL_VERSION_CURRENT = PCI_PROTOCOL_VERSION_1_1
|
||||
};
|
||||
|
||||
#define CPU_AFFINITY_ALL -1ULL
|
||||
#define PCI_CONFIG_MMIO_LENGTH 0x2000
|
||||
#define CFG_PAGE_OFFSET 0x1000
|
||||
#define CFG_PAGE_SIZE (PCI_CONFIG_MMIO_LENGTH - CFG_PAGE_OFFSET)
|
||||
@@ -350,6 +352,7 @@ enum hv_pcibus_state {
|
||||
hv_pcibus_init = 0,
|
||||
hv_pcibus_probed,
|
||||
hv_pcibus_installed,
|
||||
hv_pcibus_removed,
|
||||
hv_pcibus_maximum
|
||||
};
|
||||
|
||||
@@ -421,7 +424,7 @@ enum hv_pcidev_ref_reason {
|
||||
struct hv_pci_dev {
|
||||
/* List protected by pci_rescan_remove_lock */
|
||||
struct list_head list_entry;
|
||||
atomic_t refs;
|
||||
refcount_t refs;
|
||||
enum hv_pcichild_state state;
|
||||
struct pci_function_description desc;
|
||||
bool reported_missing;
|
||||
@@ -876,7 +879,7 @@ static void hv_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
|
||||
hv_int_desc_free(hpdev, int_desc);
|
||||
}
|
||||
|
||||
int_desc = kzalloc(sizeof(*int_desc), GFP_KERNEL);
|
||||
int_desc = kzalloc(sizeof(*int_desc), GFP_ATOMIC);
|
||||
if (!int_desc)
|
||||
goto drop_reference;
|
||||
|
||||
@@ -897,9 +900,13 @@ static void hv_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
|
||||
* processors because Hyper-V only supports 64 in a guest.
|
||||
*/
|
||||
affinity = irq_data_get_affinity_mask(data);
|
||||
for_each_cpu_and(cpu, affinity, cpu_online_mask) {
|
||||
int_pkt->int_desc.cpu_mask |=
|
||||
(1ULL << vmbus_cpu_number_to_vp_number(cpu));
|
||||
if (cpumask_weight(affinity) >= 32) {
|
||||
int_pkt->int_desc.cpu_mask = CPU_AFFINITY_ALL;
|
||||
} else {
|
||||
for_each_cpu_and(cpu, affinity, cpu_online_mask) {
|
||||
int_pkt->int_desc.cpu_mask |=
|
||||
(1ULL << vmbus_cpu_number_to_vp_number(cpu));
|
||||
}
|
||||
}
|
||||
|
||||
ret = vmbus_sendpacket(hpdev->hbus->hdev->channel, int_pkt,
|
||||
@@ -1208,9 +1215,11 @@ static int create_root_hv_pci_bus(struct hv_pcibus_device *hbus)
|
||||
hbus->pci_bus->msi = &hbus->msi_chip;
|
||||
hbus->pci_bus->msi->dev = &hbus->hdev->device;
|
||||
|
||||
pci_lock_rescan_remove();
|
||||
pci_scan_child_bus(hbus->pci_bus);
|
||||
pci_bus_assign_resources(hbus->pci_bus);
|
||||
pci_bus_add_devices(hbus->pci_bus);
|
||||
pci_unlock_rescan_remove();
|
||||
hbus->state = hv_pcibus_installed;
|
||||
return 0;
|
||||
}
|
||||
@@ -1254,13 +1263,13 @@ static void q_resource_requirements(void *context, struct pci_response *resp,
|
||||
static void get_pcichild(struct hv_pci_dev *hpdev,
|
||||
enum hv_pcidev_ref_reason reason)
|
||||
{
|
||||
atomic_inc(&hpdev->refs);
|
||||
refcount_inc(&hpdev->refs);
|
||||
}
|
||||
|
||||
static void put_pcichild(struct hv_pci_dev *hpdev,
|
||||
enum hv_pcidev_ref_reason reason)
|
||||
{
|
||||
if (atomic_dec_and_test(&hpdev->refs))
|
||||
if (refcount_dec_and_test(&hpdev->refs))
|
||||
kfree(hpdev);
|
||||
}
|
||||
|
||||
@@ -1314,7 +1323,7 @@ static struct hv_pci_dev *new_pcichild_device(struct hv_pcibus_device *hbus,
|
||||
wait_for_completion(&comp_pkt.host_event);
|
||||
|
||||
hpdev->desc = *desc;
|
||||
get_pcichild(hpdev, hv_pcidev_ref_initial);
|
||||
refcount_set(&hpdev->refs, 1);
|
||||
get_pcichild(hpdev, hv_pcidev_ref_childlist);
|
||||
spin_lock_irqsave(&hbus->device_list_lock, flags);
|
||||
|
||||
@@ -1504,13 +1513,24 @@ static void pci_devices_present_work(struct work_struct *work)
|
||||
put_pcichild(hpdev, hv_pcidev_ref_initial);
|
||||
}
|
||||
|
||||
/* Tell the core to rescan bus because there may have been changes. */
|
||||
if (hbus->state == hv_pcibus_installed) {
|
||||
switch(hbus->state) {
|
||||
case hv_pcibus_installed:
|
||||
/*
|
||||
* Tell the core to rescan bus
|
||||
* because there may have been changes.
|
||||
*/
|
||||
pci_lock_rescan_remove();
|
||||
pci_scan_child_bus(hbus->pci_bus);
|
||||
pci_unlock_rescan_remove();
|
||||
} else {
|
||||
break;
|
||||
|
||||
case hv_pcibus_init:
|
||||
case hv_pcibus_probed:
|
||||
survey_child_resources(hbus);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
up(&hbus->enum_sem);
|
||||
@@ -1600,8 +1620,10 @@ static void hv_eject_device_work(struct work_struct *work)
|
||||
pdev = pci_get_domain_bus_and_slot(hpdev->hbus->sysdata.domain, 0,
|
||||
wslot);
|
||||
if (pdev) {
|
||||
pci_lock_rescan_remove();
|
||||
pci_stop_and_remove_bus_device(pdev);
|
||||
pci_dev_put(pdev);
|
||||
pci_unlock_rescan_remove();
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&hpdev->hbus->device_list_lock, flags);
|
||||
@@ -2185,6 +2207,7 @@ static int hv_pci_probe(struct hv_device *hdev,
|
||||
hbus = kzalloc(sizeof(*hbus), GFP_KERNEL);
|
||||
if (!hbus)
|
||||
return -ENOMEM;
|
||||
hbus->state = hv_pcibus_init;
|
||||
|
||||
/*
|
||||
* The PCI bus "domain" is what is called "segment" in ACPI and
|
||||
@@ -2348,6 +2371,7 @@ static int hv_pci_remove(struct hv_device *hdev)
|
||||
pci_stop_root_bus(hbus->pci_bus);
|
||||
pci_remove_root_bus(hbus->pci_bus);
|
||||
pci_unlock_rescan_remove();
|
||||
hbus->state = hv_pcibus_removed;
|
||||
}
|
||||
|
||||
hv_pci_bus_exit(hdev);
|
||||
|
@@ -752,10 +752,11 @@ static int mvebu_sw_pci_bridge_write(struct mvebu_pcie_port *port,
|
||||
* If the mask is 0xffff0000, then we only want to write
|
||||
* the link control register, rather than clearing the
|
||||
* RW1C bits in the link status register. Mask out the
|
||||
* status register bits.
|
||||
* RW1C status register bits.
|
||||
*/
|
||||
if (mask == 0xffff0000)
|
||||
value &= 0xffff;
|
||||
value &= ~((PCI_EXP_LNKSTA_LABS |
|
||||
PCI_EXP_LNKSTA_LBMS) << 16);
|
||||
|
||||
mvebu_writel(port, value, PCIE_CAP_PCIEXP + PCI_EXP_LNKCTL);
|
||||
break;
|
||||
@@ -1005,22 +1006,6 @@ static int mvebu_get_tgt_attr(struct device_node *np, int devfn,
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static void mvebu_pcie_msi_enable(struct mvebu_pcie *pcie)
|
||||
{
|
||||
struct device_node *msi_node;
|
||||
|
||||
msi_node = of_parse_phandle(pcie->pdev->dev.of_node,
|
||||
"msi-parent", 0);
|
||||
if (!msi_node)
|
||||
return;
|
||||
|
||||
pcie->msi = of_pci_find_msi_chip_by_node(msi_node);
|
||||
of_node_put(msi_node);
|
||||
|
||||
if (pcie->msi)
|
||||
pcie->msi->dev = &pcie->pdev->dev;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int mvebu_pcie_suspend(struct device *dev)
|
||||
{
|
||||
@@ -1298,7 +1283,6 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
|
||||
for (i = 0; i < (IO_SPACE_LIMIT - SZ_64K); i += SZ_64K)
|
||||
pci_ioremap_io(i, pcie->io.start + i);
|
||||
|
||||
mvebu_pcie_msi_enable(pcie);
|
||||
mvebu_pcie_enable(pcie);
|
||||
|
||||
platform_set_drvdata(pdev, pcie);
|
||||
|
@@ -380,7 +380,7 @@ static struct tegra_pcie_bus *tegra_pcie_bus_alloc(struct tegra_pcie *pcie,
|
||||
unsigned int busnr)
|
||||
{
|
||||
struct device *dev = pcie->dev;
|
||||
pgprot_t prot = pgprot_device(PAGE_KERNEL);
|
||||
pgprot_t prot = pgprot_noncached(PAGE_KERNEL);
|
||||
phys_addr_t cs = pcie->cs->start;
|
||||
struct tegra_pcie_bus *bus;
|
||||
unsigned int i;
|
||||
@@ -1962,7 +1962,7 @@ static int tegra_pcie_parse_dt(struct tegra_pcie *pcie)
|
||||
rp->pcie = pcie;
|
||||
rp->np = port;
|
||||
|
||||
rp->base = devm_ioremap_resource(dev, &rp->regs);
|
||||
rp->base = devm_pci_remap_cfg_resource(dev, &rp->regs);
|
||||
if (IS_ERR(rp->base))
|
||||
return PTR_ERR(rp->base);
|
||||
|
||||
|
@@ -373,6 +373,7 @@ static struct platform_driver thunder_ecam_driver = {
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.of_match_table = thunder_ecam_of_match,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = thunder_ecam_probe,
|
||||
};
|
||||
|
@@ -474,6 +474,7 @@ static struct platform_driver thunder_pem_driver = {
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.of_match_table = thunder_pem_of_match,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = thunder_pem_probe,
|
||||
};
|
||||
|
@@ -138,7 +138,8 @@ static int versatile_pci_probe(struct platform_device *pdev)
|
||||
return PTR_ERR(versatile_cfg_base[0]);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
|
||||
versatile_cfg_base[1] = devm_ioremap_resource(&pdev->dev, res);
|
||||
versatile_cfg_base[1] = devm_pci_remap_cfg_resource(&pdev->dev,
|
||||
res);
|
||||
if (IS_ERR(versatile_cfg_base[1]))
|
||||
return PTR_ERR(versatile_cfg_base[1]);
|
||||
|
||||
@@ -221,6 +222,7 @@ static struct platform_driver versatile_pci_driver = {
|
||||
.driver = {
|
||||
.name = "versatile-pci",
|
||||
.of_match_table = versatile_pci_of_match,
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = versatile_pci_probe,
|
||||
};
|
||||
|
@@ -248,7 +248,7 @@ static int xgene_pcie_ecam_init(struct pci_config_window *cfg, u32 ipversion)
|
||||
dev_err(dev, "can't get CSR resource\n");
|
||||
return ret;
|
||||
}
|
||||
port->csr_base = devm_ioremap_resource(dev, &csr);
|
||||
port->csr_base = devm_pci_remap_cfg_resource(dev, &csr);
|
||||
if (IS_ERR(port->csr_base))
|
||||
return PTR_ERR(port->csr_base);
|
||||
|
||||
@@ -359,7 +359,7 @@ static int xgene_pcie_map_reg(struct xgene_pcie_port *port,
|
||||
struct resource *res;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "csr");
|
||||
port->csr_base = devm_ioremap_resource(dev, res);
|
||||
port->csr_base = devm_pci_remap_cfg_resource(dev, res);
|
||||
if (IS_ERR(port->csr_base))
|
||||
return PTR_ERR(port->csr_base);
|
||||
|
||||
@@ -697,6 +697,7 @@ static struct platform_driver xgene_pcie_driver = {
|
||||
.driver = {
|
||||
.name = "xgene-pcie",
|
||||
.of_match_table = of_match_ptr(xgene_pcie_match_table),
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = xgene_pcie_probe_bridge,
|
||||
};
|
||||
|
@@ -67,7 +67,8 @@ static int iproc_pcie_pltfm_probe(struct platform_device *pdev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
pcie->base = devm_ioremap(dev, reg.start, resource_size(®));
|
||||
pcie->base = devm_pci_remap_cfgspace(dev, reg.start,
|
||||
resource_size(®));
|
||||
if (!pcie->base) {
|
||||
dev_err(dev, "unable to map controller registers\n");
|
||||
return -ENOMEM;
|
||||
|
@@ -26,6 +26,7 @@
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_pci.h>
|
||||
@@ -223,9 +224,11 @@ struct rockchip_pcie {
|
||||
int link_gen;
|
||||
struct device *dev;
|
||||
struct irq_domain *irq_domain;
|
||||
u32 io_size;
|
||||
int offset;
|
||||
struct pci_bus *root_bus;
|
||||
struct resource *io;
|
||||
phys_addr_t io_bus_addr;
|
||||
u32 io_size;
|
||||
void __iomem *msg_region;
|
||||
u32 mem_size;
|
||||
phys_addr_t msg_bus_addr;
|
||||
@@ -425,7 +428,8 @@ static struct pci_ops rockchip_pcie_ops = {
|
||||
|
||||
static void rockchip_pcie_set_power_limit(struct rockchip_pcie *rockchip)
|
||||
{
|
||||
u32 status, curr, scale, power;
|
||||
int curr;
|
||||
u32 status, scale, power;
|
||||
|
||||
if (IS_ERR(rockchip->vpcie3v3))
|
||||
return;
|
||||
@@ -437,24 +441,25 @@ static void rockchip_pcie_set_power_limit(struct rockchip_pcie *rockchip)
|
||||
* to the actual power supply.
|
||||
*/
|
||||
curr = regulator_get_current_limit(rockchip->vpcie3v3);
|
||||
if (curr > 0) {
|
||||
scale = 3; /* 0.001x */
|
||||
curr = curr / 1000; /* convert to mA */
|
||||
power = (curr * 3300) / 1000; /* milliwatt */
|
||||
while (power > PCIE_RC_CONFIG_DCR_CSPL_LIMIT) {
|
||||
if (!scale) {
|
||||
dev_warn(rockchip->dev, "invalid power supply\n");
|
||||
return;
|
||||
}
|
||||
scale--;
|
||||
power = power / 10;
|
||||
}
|
||||
if (curr <= 0)
|
||||
return;
|
||||
|
||||
status = rockchip_pcie_read(rockchip, PCIE_RC_CONFIG_DCR);
|
||||
status |= (power << PCIE_RC_CONFIG_DCR_CSPL_SHIFT) |
|
||||
(scale << PCIE_RC_CONFIG_DCR_CPLS_SHIFT);
|
||||
rockchip_pcie_write(rockchip, status, PCIE_RC_CONFIG_DCR);
|
||||
scale = 3; /* 0.001x */
|
||||
curr = curr / 1000; /* convert to mA */
|
||||
power = (curr * 3300) / 1000; /* milliwatt */
|
||||
while (power > PCIE_RC_CONFIG_DCR_CSPL_LIMIT) {
|
||||
if (!scale) {
|
||||
dev_warn(rockchip->dev, "invalid power supply\n");
|
||||
return;
|
||||
}
|
||||
scale--;
|
||||
power = power / 10;
|
||||
}
|
||||
|
||||
status = rockchip_pcie_read(rockchip, PCIE_RC_CONFIG_DCR);
|
||||
status |= (power << PCIE_RC_CONFIG_DCR_CSPL_SHIFT) |
|
||||
(scale << PCIE_RC_CONFIG_DCR_CPLS_SHIFT);
|
||||
rockchip_pcie_write(rockchip, status, PCIE_RC_CONFIG_DCR);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -596,7 +601,12 @@ static int rockchip_pcie_init_port(struct rockchip_pcie *rockchip)
|
||||
|
||||
/* Set RC's clock architecture as common clock */
|
||||
status = rockchip_pcie_read(rockchip, PCIE_RC_CONFIG_LCS);
|
||||
status |= PCI_EXP_LNKCTL_CCC;
|
||||
status |= PCI_EXP_LNKSTA_SLC << 16;
|
||||
rockchip_pcie_write(rockchip, status, PCIE_RC_CONFIG_LCS);
|
||||
|
||||
/* Set RC's RCB to 128 */
|
||||
status = rockchip_pcie_read(rockchip, PCIE_RC_CONFIG_LCS);
|
||||
status |= PCI_EXP_LNKCTL_RCB;
|
||||
rockchip_pcie_write(rockchip, status, PCIE_RC_CONFIG_LCS);
|
||||
|
||||
/* Enable Gen1 training */
|
||||
@@ -822,7 +832,7 @@ static int rockchip_pcie_parse_dt(struct rockchip_pcie *rockchip)
|
||||
regs = platform_get_resource_byname(pdev,
|
||||
IORESOURCE_MEM,
|
||||
"axi-base");
|
||||
rockchip->reg_base = devm_ioremap_resource(dev, regs);
|
||||
rockchip->reg_base = devm_pci_remap_cfg_resource(dev, regs);
|
||||
if (IS_ERR(rockchip->reg_base))
|
||||
return PTR_ERR(rockchip->reg_base);
|
||||
|
||||
@@ -1359,6 +1369,7 @@ static int rockchip_pcie_probe(struct platform_device *pdev)
|
||||
err, io);
|
||||
continue;
|
||||
}
|
||||
rockchip->io = io;
|
||||
break;
|
||||
case IORESOURCE_MEM:
|
||||
mem = win->res;
|
||||
@@ -1390,6 +1401,7 @@ static int rockchip_pcie_probe(struct platform_device *pdev)
|
||||
err = -ENOMEM;
|
||||
goto err_free_res;
|
||||
}
|
||||
rockchip->root_bus = bus;
|
||||
|
||||
pci_bus_size_bridges(bus);
|
||||
pci_bus_assign_resources(bus);
|
||||
@@ -1397,7 +1409,7 @@ static int rockchip_pcie_probe(struct platform_device *pdev)
|
||||
pcie_bus_configure_settings(child);
|
||||
|
||||
pci_bus_add_devices(bus);
|
||||
return err;
|
||||
return 0;
|
||||
|
||||
err_free_res:
|
||||
pci_free_resource_list(&res);
|
||||
@@ -1420,6 +1432,34 @@ err_aclk_pcie:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int rockchip_pcie_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct rockchip_pcie *rockchip = dev_get_drvdata(dev);
|
||||
|
||||
pci_stop_root_bus(rockchip->root_bus);
|
||||
pci_remove_root_bus(rockchip->root_bus);
|
||||
pci_unmap_iospace(rockchip->io);
|
||||
irq_domain_remove(rockchip->irq_domain);
|
||||
|
||||
phy_power_off(rockchip->phy);
|
||||
phy_exit(rockchip->phy);
|
||||
|
||||
clk_disable_unprepare(rockchip->clk_pcie_pm);
|
||||
clk_disable_unprepare(rockchip->hclk_pcie);
|
||||
clk_disable_unprepare(rockchip->aclk_perf_pcie);
|
||||
clk_disable_unprepare(rockchip->aclk_pcie);
|
||||
|
||||
if (!IS_ERR(rockchip->vpcie3v3))
|
||||
regulator_disable(rockchip->vpcie3v3);
|
||||
if (!IS_ERR(rockchip->vpcie1v8))
|
||||
regulator_disable(rockchip->vpcie1v8);
|
||||
if (!IS_ERR(rockchip->vpcie0v9))
|
||||
regulator_disable(rockchip->vpcie0v9);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops rockchip_pcie_pm_ops = {
|
||||
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(rockchip_pcie_suspend_noirq,
|
||||
rockchip_pcie_resume_noirq)
|
||||
@@ -1429,6 +1469,7 @@ static const struct of_device_id rockchip_pcie_of_match[] = {
|
||||
{ .compatible = "rockchip,rk3399-pcie", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rockchip_pcie_of_match);
|
||||
|
||||
static struct platform_driver rockchip_pcie_driver = {
|
||||
.driver = {
|
||||
@@ -1437,6 +1478,10 @@ static struct platform_driver rockchip_pcie_driver = {
|
||||
.pm = &rockchip_pcie_pm_ops,
|
||||
},
|
||||
.probe = rockchip_pcie_probe,
|
||||
|
||||
.remove = rockchip_pcie_remove,
|
||||
};
|
||||
builtin_platform_driver(rockchip_pcie_driver);
|
||||
module_platform_driver(rockchip_pcie_driver);
|
||||
|
||||
MODULE_AUTHOR("Rockchip Inc");
|
||||
MODULE_DESCRIPTION("Rockchip AXI PCIe driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
@@ -761,7 +761,7 @@ static int nwl_pcie_parse_dt(struct nwl_pcie *pcie,
|
||||
pcie->phys_pcie_reg_base = res->start;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cfg");
|
||||
pcie->ecam_base = devm_ioremap_resource(dev, res);
|
||||
pcie->ecam_base = devm_pci_remap_cfg_resource(dev, res);
|
||||
if (IS_ERR(pcie->ecam_base))
|
||||
return PTR_ERR(pcie->ecam_base);
|
||||
pcie->phys_ecam_base = res->start;
|
||||
|
@@ -606,7 +606,7 @@ static int xilinx_pcie_parse_dt(struct xilinx_pcie_port *port)
|
||||
return err;
|
||||
}
|
||||
|
||||
port->reg_base = devm_ioremap_resource(dev, ®s);
|
||||
port->reg_base = devm_pci_remap_cfg_resource(dev, ®s);
|
||||
if (IS_ERR(port->reg_base))
|
||||
return PTR_ERR(port->reg_base);
|
||||
|
||||
|
@@ -109,6 +109,12 @@ int pciehp_unconfigure_device(struct slot *p_slot)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!presence) {
|
||||
pci_dev_set_disconnected(dev, NULL);
|
||||
if (pci_has_subordinate(dev))
|
||||
pci_walk_bus(dev->subordinate,
|
||||
pci_dev_set_disconnected, NULL);
|
||||
}
|
||||
pci_stop_and_remove_bus_device(dev);
|
||||
/*
|
||||
* Ensure that no new Requests will be generated from
|
||||
|
@@ -450,6 +450,7 @@ found:
|
||||
iov->total_VFs = total;
|
||||
iov->pgsz = pgsz;
|
||||
iov->self = dev;
|
||||
iov->drivers_autoprobe = true;
|
||||
pci_read_config_dword(dev, pos + PCI_SRIOV_CAP, &iov->cap);
|
||||
pci_read_config_byte(dev, pos + PCI_SRIOV_FUNC_LINK, &iov->link);
|
||||
if (pci_pcie_type(dev) == PCI_EXP_TYPE_RC_END)
|
||||
|
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
* PCI IRQ failure handing code
|
||||
* PCI IRQ handling code
|
||||
*
|
||||
* Copyright (c) 2008 James Bottomley <James.Bottomley@HansenPartnership.com>
|
||||
* Copyright (C) 2017 Christoph Hellwig.
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
@@ -59,3 +60,61 @@ enum pci_lost_interrupt_reason pci_lost_interrupt(struct pci_dev *pdev)
|
||||
return PCI_LOST_IRQ_NO_INFORMATION;
|
||||
}
|
||||
EXPORT_SYMBOL(pci_lost_interrupt);
|
||||
|
||||
/**
|
||||
* pci_request_irq - allocate an interrupt line for a PCI device
|
||||
* @dev: PCI device to operate on
|
||||
* @nr: device-relative interrupt vector index (0-based).
|
||||
* @handler: Function to be called when the IRQ occurs.
|
||||
* Primary handler for threaded interrupts.
|
||||
* If NULL and thread_fn != NULL the default primary handler is
|
||||
* installed.
|
||||
* @thread_fn: Function called from the IRQ handler thread
|
||||
* If NULL, no IRQ thread is created
|
||||
* @dev_id: Cookie passed back to the handler function
|
||||
* @fmt: Printf-like format string naming the handler
|
||||
*
|
||||
* This call allocates interrupt resources and enables the interrupt line and
|
||||
* IRQ handling. From the point this call is made @handler and @thread_fn may
|
||||
* be invoked. All interrupts requested using this function might be shared.
|
||||
*
|
||||
* @dev_id must not be NULL and must be globally unique.
|
||||
*/
|
||||
int pci_request_irq(struct pci_dev *dev, unsigned int nr, irq_handler_t handler,
|
||||
irq_handler_t thread_fn, void *dev_id, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int ret;
|
||||
char *devname;
|
||||
|
||||
va_start(ap, fmt);
|
||||
devname = kvasprintf(GFP_KERNEL, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
ret = request_threaded_irq(pci_irq_vector(dev, nr), handler, thread_fn,
|
||||
IRQF_SHARED, devname, dev_id);
|
||||
if (ret)
|
||||
kfree(devname);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(pci_request_irq);
|
||||
|
||||
/**
|
||||
* pci_free_irq - free an interrupt allocated with pci_request_irq
|
||||
* @dev: PCI device to operate on
|
||||
* @nr: device-relative interrupt vector index (0-based).
|
||||
* @dev_id: Device identity to free
|
||||
*
|
||||
* Remove an interrupt handler. The handler is removed and if the interrupt
|
||||
* line is no longer in use by any driver it is disabled. The caller must
|
||||
* ensure the interrupt is disabled on the device before calling this function.
|
||||
* The function does not return until any executing interrupts for this IRQ
|
||||
* have completed.
|
||||
*
|
||||
* This function must not be called from interrupt context.
|
||||
*/
|
||||
void pci_free_irq(struct pci_dev *dev, unsigned int nr, void *dev_id)
|
||||
{
|
||||
kfree(free_irq(pci_irq_vector(dev, nr), dev_id));
|
||||
}
|
||||
EXPORT_SYMBOL(pci_free_irq);
|
||||
|
99
drivers/pci/mmap.c
Normal file
99
drivers/pci/mmap.c
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* mmap.c — generic PCI resource mmap helper
|
||||
*
|
||||
* Copyright © 2017 Amazon.com, Inc. or its affiliates.
|
||||
*
|
||||
* Author: David Woodhouse <dwmw2@infradead.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/pci.h>
|
||||
|
||||
#ifdef ARCH_GENERIC_PCI_MMAP_RESOURCE
|
||||
|
||||
/*
|
||||
* Modern setup: generic pci_mmap_resource_range(), and implement the legacy
|
||||
* pci_mmap_page_range() (if needed) as a wrapper round it.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_PCI_MMAP
|
||||
int pci_mmap_page_range(struct pci_dev *pdev, int bar,
|
||||
struct vm_area_struct *vma,
|
||||
enum pci_mmap_state mmap_state, int write_combine)
|
||||
{
|
||||
resource_size_t start, end;
|
||||
|
||||
pci_resource_to_user(pdev, bar, &pdev->resource[bar], &start, &end);
|
||||
|
||||
/* Adjust vm_pgoff to be the offset within the resource */
|
||||
vma->vm_pgoff -= start >> PAGE_SHIFT;
|
||||
return pci_mmap_resource_range(pdev, bar, vma, mmap_state,
|
||||
write_combine);
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct vm_operations_struct pci_phys_vm_ops = {
|
||||
#ifdef CONFIG_HAVE_IOREMAP_PROT
|
||||
.access = generic_access_phys,
|
||||
#endif
|
||||
};
|
||||
|
||||
int pci_mmap_resource_range(struct pci_dev *pdev, int bar,
|
||||
struct vm_area_struct *vma,
|
||||
enum pci_mmap_state mmap_state, int write_combine)
|
||||
{
|
||||
unsigned long size;
|
||||
int ret;
|
||||
|
||||
size = ((pci_resource_len(pdev, bar) - 1) >> PAGE_SHIFT) + 1;
|
||||
if (vma->vm_pgoff + vma_pages(vma) > size)
|
||||
return -EINVAL;
|
||||
|
||||
if (write_combine)
|
||||
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
|
||||
else
|
||||
vma->vm_page_prot = pgprot_device(vma->vm_page_prot);
|
||||
|
||||
if (mmap_state == pci_mmap_io) {
|
||||
ret = pci_iobar_pfn(pdev, bar, vma);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else
|
||||
vma->vm_pgoff += (pci_resource_start(pdev, bar) >> PAGE_SHIFT);
|
||||
|
||||
vma->vm_ops = &pci_phys_vm_ops;
|
||||
|
||||
return io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
|
||||
vma->vm_end - vma->vm_start,
|
||||
vma->vm_page_prot);
|
||||
}
|
||||
|
||||
#elif defined(HAVE_PCI_MMAP) /* && !ARCH_GENERIC_PCI_MMAP_RESOURCE */
|
||||
|
||||
/*
|
||||
* Legacy setup: Impement pci_mmap_resource_range() as a wrapper around
|
||||
* the architecture's pci_mmap_page_range(), converting to "user visible"
|
||||
* addresses as necessary.
|
||||
*/
|
||||
|
||||
int pci_mmap_resource_range(struct pci_dev *pdev, int bar,
|
||||
struct vm_area_struct *vma,
|
||||
enum pci_mmap_state mmap_state, int write_combine)
|
||||
{
|
||||
resource_size_t start, end;
|
||||
|
||||
/*
|
||||
* pci_mmap_page_range() expects the same kind of entry as coming
|
||||
* from /proc/bus/pci/ which is a "user visible" value. If this is
|
||||
* different from the resource itself, arch will do necessary fixup.
|
||||
*/
|
||||
pci_resource_to_user(pdev, bar, &pdev->resource[bar], &start, &end);
|
||||
vma->vm_pgoff += start >> PAGE_SHIFT;
|
||||
return pci_mmap_page_range(pdev, bar, vma, mmap_state, write_combine);
|
||||
}
|
||||
#endif
|
@@ -298,7 +298,7 @@ void __pci_write_msi_msg(struct msi_desc *entry, struct msi_msg *msg)
|
||||
{
|
||||
struct pci_dev *dev = msi_desc_to_pci_dev(entry);
|
||||
|
||||
if (dev->current_state != PCI_D0) {
|
||||
if (dev->current_state != PCI_D0 || pci_dev_is_disconnected(dev)) {
|
||||
/* Don't touch the hardware now */
|
||||
} else if (entry->msi_attrib.is_msix) {
|
||||
void __iomem *base = pci_msix_desc_addr(entry);
|
||||
@@ -541,7 +541,8 @@ msi_setup_entry(struct pci_dev *dev, int nvec, const struct irq_affinity *affd)
|
||||
if (affd) {
|
||||
masks = irq_create_affinity_masks(nvec, affd);
|
||||
if (!masks)
|
||||
pr_err("Unable to allocate affinity masks, ignoring\n");
|
||||
dev_err(&dev->dev, "can't allocate MSI affinity masks for %d vectors\n",
|
||||
nvec);
|
||||
}
|
||||
|
||||
/* MSI Entry Initialization */
|
||||
@@ -681,7 +682,8 @@ static int msix_setup_entries(struct pci_dev *dev, void __iomem *base,
|
||||
if (affd) {
|
||||
masks = irq_create_affinity_masks(nvec, affd);
|
||||
if (!masks)
|
||||
pr_err("Unable to allocate affinity masks, ignoring\n");
|
||||
dev_err(&dev->dev, "can't allocate MSI-X affinity masks for %d vectors\n",
|
||||
nvec);
|
||||
}
|
||||
|
||||
for (i = 0, curmsk = masks; i < nvec; i++) {
|
||||
@@ -882,7 +884,7 @@ int pci_msi_vec_count(struct pci_dev *dev)
|
||||
}
|
||||
EXPORT_SYMBOL(pci_msi_vec_count);
|
||||
|
||||
void pci_msi_shutdown(struct pci_dev *dev)
|
||||
static void pci_msi_shutdown(struct pci_dev *dev)
|
||||
{
|
||||
struct msi_desc *desc;
|
||||
u32 mask;
|
||||
@@ -973,13 +975,18 @@ static int __pci_enable_msix(struct pci_dev *dev, struct msix_entry *entries,
|
||||
return msix_capability_init(dev, entries, nvec, affd);
|
||||
}
|
||||
|
||||
void pci_msix_shutdown(struct pci_dev *dev)
|
||||
static void pci_msix_shutdown(struct pci_dev *dev)
|
||||
{
|
||||
struct msi_desc *entry;
|
||||
|
||||
if (!pci_msi_enable || !dev || !dev->msix_enabled)
|
||||
return;
|
||||
|
||||
if (pci_dev_is_disconnected(dev)) {
|
||||
dev->msix_enabled = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Return the device with MSI-X masked as initial states */
|
||||
for_each_pci_msi_entry(entry, dev) {
|
||||
/* Keep cached states to be restored */
|
||||
|
@@ -394,6 +394,18 @@ void __weak pcibios_free_irq(struct pci_dev *dev)
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PCI_IOV
|
||||
static inline bool pci_device_can_probe(struct pci_dev *pdev)
|
||||
{
|
||||
return (!pdev->is_virtfn || pdev->physfn->sriov->drivers_autoprobe);
|
||||
}
|
||||
#else
|
||||
static inline bool pci_device_can_probe(struct pci_dev *pdev)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int pci_device_probe(struct device *dev)
|
||||
{
|
||||
int error;
|
||||
@@ -405,10 +417,12 @@ static int pci_device_probe(struct device *dev)
|
||||
return error;
|
||||
|
||||
pci_dev_get(pci_dev);
|
||||
error = __pci_device_probe(drv, pci_dev);
|
||||
if (error) {
|
||||
pcibios_free_irq(pci_dev);
|
||||
pci_dev_put(pci_dev);
|
||||
if (pci_device_can_probe(pci_dev)) {
|
||||
error = __pci_device_probe(drv, pci_dev);
|
||||
if (error) {
|
||||
pcibios_free_irq(pci_dev);
|
||||
pci_dev_put(pci_dev);
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
@@ -461,8 +475,6 @@ static void pci_device_shutdown(struct device *dev)
|
||||
|
||||
if (drv && drv->shutdown)
|
||||
drv->shutdown(pci_dev);
|
||||
pci_msi_shutdown(pci_dev);
|
||||
pci_msix_shutdown(pci_dev);
|
||||
|
||||
/*
|
||||
* If this is a kexec reboot, turn off Bus Master bit on the
|
||||
|
@@ -526,10 +526,37 @@ exit:
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t sriov_drivers_autoprobe_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
|
||||
return sprintf(buf, "%u\n", pdev->sriov->drivers_autoprobe);
|
||||
}
|
||||
|
||||
static ssize_t sriov_drivers_autoprobe_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
bool drivers_autoprobe;
|
||||
|
||||
if (kstrtobool(buf, &drivers_autoprobe) < 0)
|
||||
return -EINVAL;
|
||||
|
||||
pdev->sriov->drivers_autoprobe = drivers_autoprobe;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct device_attribute sriov_totalvfs_attr = __ATTR_RO(sriov_totalvfs);
|
||||
static struct device_attribute sriov_numvfs_attr =
|
||||
__ATTR(sriov_numvfs, (S_IRUGO|S_IWUSR|S_IWGRP),
|
||||
sriov_numvfs_show, sriov_numvfs_store);
|
||||
static struct device_attribute sriov_drivers_autoprobe_attr =
|
||||
__ATTR(sriov_drivers_autoprobe, (S_IRUGO|S_IWUSR|S_IWGRP),
|
||||
sriov_drivers_autoprobe_show, sriov_drivers_autoprobe_store);
|
||||
#endif /* CONFIG_PCI_IOV */
|
||||
|
||||
static ssize_t driver_override_store(struct device *dev,
|
||||
@@ -980,20 +1007,24 @@ void pci_remove_legacy_files(struct pci_bus *b)
|
||||
}
|
||||
#endif /* HAVE_PCI_LEGACY */
|
||||
|
||||
#ifdef HAVE_PCI_MMAP
|
||||
#if defined(HAVE_PCI_MMAP) || defined(ARCH_GENERIC_PCI_MMAP_RESOURCE)
|
||||
|
||||
int pci_mmap_fits(struct pci_dev *pdev, int resno, struct vm_area_struct *vma,
|
||||
enum pci_mmap_api mmap_api)
|
||||
{
|
||||
unsigned long nr, start, size, pci_start;
|
||||
unsigned long nr, start, size;
|
||||
resource_size_t pci_start = 0, pci_end;
|
||||
|
||||
if (pci_resource_len(pdev, resno) == 0)
|
||||
return 0;
|
||||
nr = vma_pages(vma);
|
||||
start = vma->vm_pgoff;
|
||||
size = ((pci_resource_len(pdev, resno) - 1) >> PAGE_SHIFT) + 1;
|
||||
pci_start = (mmap_api == PCI_MMAP_PROCFS) ?
|
||||
pci_resource_start(pdev, resno) >> PAGE_SHIFT : 0;
|
||||
if (mmap_api == PCI_MMAP_PROCFS) {
|
||||
pci_resource_to_user(pdev, resno, &pdev->resource[resno],
|
||||
&pci_start, &pci_end);
|
||||
pci_start >>= PAGE_SHIFT;
|
||||
}
|
||||
if (start >= pci_start && start < pci_start + size &&
|
||||
start + nr <= pci_start + size)
|
||||
return 1;
|
||||
@@ -1013,37 +1044,24 @@ static int pci_mmap_resource(struct kobject *kobj, struct bin_attribute *attr,
|
||||
struct vm_area_struct *vma, int write_combine)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
|
||||
struct resource *res = attr->private;
|
||||
int bar = (unsigned long)attr->private;
|
||||
enum pci_mmap_state mmap_type;
|
||||
resource_size_t start, end;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < PCI_ROM_RESOURCE; i++)
|
||||
if (res == &pdev->resource[i])
|
||||
break;
|
||||
if (i >= PCI_ROM_RESOURCE)
|
||||
return -ENODEV;
|
||||
struct resource *res = &pdev->resource[bar];
|
||||
|
||||
if (res->flags & IORESOURCE_MEM && iomem_is_exclusive(res->start))
|
||||
return -EINVAL;
|
||||
|
||||
if (!pci_mmap_fits(pdev, i, vma, PCI_MMAP_SYSFS)) {
|
||||
if (!pci_mmap_fits(pdev, bar, vma, PCI_MMAP_SYSFS)) {
|
||||
WARN(1, "process \"%s\" tried to map 0x%08lx bytes at page 0x%08lx on %s BAR %d (start 0x%16Lx, size 0x%16Lx)\n",
|
||||
current->comm, vma->vm_end-vma->vm_start, vma->vm_pgoff,
|
||||
pci_name(pdev), i,
|
||||
(u64)pci_resource_start(pdev, i),
|
||||
(u64)pci_resource_len(pdev, i));
|
||||
pci_name(pdev), bar,
|
||||
(u64)pci_resource_start(pdev, bar),
|
||||
(u64)pci_resource_len(pdev, bar));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* pci_mmap_page_range() expects the same kind of entry as coming
|
||||
* from /proc/bus/pci/ which is a "user visible" value. If this is
|
||||
* different from the resource itself, arch will do necessary fixup.
|
||||
*/
|
||||
pci_resource_to_user(pdev, i, res, &start, &end);
|
||||
vma->vm_pgoff += start >> PAGE_SHIFT;
|
||||
mmap_type = res->flags & IORESOURCE_MEM ? pci_mmap_mem : pci_mmap_io;
|
||||
return pci_mmap_page_range(pdev, vma, mmap_type, write_combine);
|
||||
|
||||
return pci_mmap_resource_range(pdev, bar, vma, mmap_type, write_combine);
|
||||
}
|
||||
|
||||
static int pci_mmap_resource_uc(struct file *filp, struct kobject *kobj,
|
||||
@@ -1065,22 +1083,18 @@ static ssize_t pci_resource_io(struct file *filp, struct kobject *kobj,
|
||||
loff_t off, size_t count, bool write)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
|
||||
struct resource *res = attr->private;
|
||||
int bar = (unsigned long)attr->private;
|
||||
struct resource *res;
|
||||
unsigned long port = off;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < PCI_ROM_RESOURCE; i++)
|
||||
if (res == &pdev->resource[i])
|
||||
break;
|
||||
if (i >= PCI_ROM_RESOURCE)
|
||||
return -ENODEV;
|
||||
res = &pdev->resource[bar];
|
||||
|
||||
port += pci_resource_start(pdev, i);
|
||||
port += pci_resource_start(pdev, bar);
|
||||
|
||||
if (port > pci_resource_end(pdev, i))
|
||||
if (port > pci_resource_end(pdev, bar))
|
||||
return 0;
|
||||
|
||||
if (port + count - 1 > pci_resource_end(pdev, i))
|
||||
if (port + count - 1 > pci_resource_end(pdev, bar))
|
||||
return -EINVAL;
|
||||
|
||||
switch (count) {
|
||||
@@ -1170,16 +1184,19 @@ static int pci_create_attr(struct pci_dev *pdev, int num, int write_combine)
|
||||
} else {
|
||||
pdev->res_attr[num] = res_attr;
|
||||
sprintf(res_attr_name, "resource%d", num);
|
||||
res_attr->mmap = pci_mmap_resource_uc;
|
||||
}
|
||||
if (pci_resource_flags(pdev, num) & IORESOURCE_IO) {
|
||||
res_attr->read = pci_read_resource_io;
|
||||
res_attr->write = pci_write_resource_io;
|
||||
if (pci_resource_flags(pdev, num) & IORESOURCE_IO) {
|
||||
res_attr->read = pci_read_resource_io;
|
||||
res_attr->write = pci_write_resource_io;
|
||||
if (arch_can_pci_mmap_io())
|
||||
res_attr->mmap = pci_mmap_resource_uc;
|
||||
} else {
|
||||
res_attr->mmap = pci_mmap_resource_uc;
|
||||
}
|
||||
}
|
||||
res_attr->attr.name = res_attr_name;
|
||||
res_attr->attr.mode = S_IRUSR | S_IWUSR;
|
||||
res_attr->size = pci_resource_len(pdev, num);
|
||||
res_attr->private = &pdev->resource[num];
|
||||
res_attr->private = (void *)(unsigned long)num;
|
||||
retval = sysfs_create_bin_file(&pdev->dev.kobj, res_attr);
|
||||
if (retval)
|
||||
kfree(res_attr);
|
||||
@@ -1207,9 +1224,9 @@ static int pci_create_resource_files(struct pci_dev *pdev)
|
||||
|
||||
retval = pci_create_attr(pdev, i, 0);
|
||||
/* for prefetchable resources, create a WC mappable file */
|
||||
if (!retval && pdev->resource[i].flags & IORESOURCE_PREFETCH)
|
||||
if (!retval && arch_can_pci_mmap_wc() &&
|
||||
pdev->resource[i].flags & IORESOURCE_PREFETCH)
|
||||
retval = pci_create_attr(pdev, i, 1);
|
||||
|
||||
if (retval) {
|
||||
pci_remove_resource_files(pdev);
|
||||
return retval;
|
||||
@@ -1549,6 +1566,7 @@ static struct attribute_group pci_dev_hp_attr_group = {
|
||||
static struct attribute *sriov_dev_attrs[] = {
|
||||
&sriov_totalvfs_attr.attr,
|
||||
&sriov_numvfs_attr.attr,
|
||||
&sriov_drivers_autoprobe_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
@@ -66,7 +66,8 @@ static void pci_dev_d3_sleep(struct pci_dev *dev)
|
||||
if (delay < pci_pm_d3_delay)
|
||||
delay = pci_pm_d3_delay;
|
||||
|
||||
msleep(delay);
|
||||
if (delay)
|
||||
msleep(delay);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PCI_DOMAINS
|
||||
@@ -827,7 +828,8 @@ static void __pci_start_power_transition(struct pci_dev *dev, pci_power_t state)
|
||||
* because have already delayed for the bridge.
|
||||
*/
|
||||
if (dev->runtime_d3cold) {
|
||||
msleep(dev->d3cold_delay);
|
||||
if (dev->d3cold_delay)
|
||||
msleep(dev->d3cold_delay);
|
||||
/*
|
||||
* When powering on a bridge from D3cold, the
|
||||
* whole hierarchy may be powered on into
|
||||
@@ -1782,8 +1784,8 @@ static void pci_pme_list_scan(struct work_struct *work)
|
||||
}
|
||||
}
|
||||
if (!list_empty(&pci_pme_list))
|
||||
schedule_delayed_work(&pci_pme_work,
|
||||
msecs_to_jiffies(PME_TIMEOUT));
|
||||
queue_delayed_work(system_freezable_wq, &pci_pme_work,
|
||||
msecs_to_jiffies(PME_TIMEOUT));
|
||||
mutex_unlock(&pci_pme_list_mutex);
|
||||
}
|
||||
|
||||
@@ -1848,8 +1850,9 @@ void pci_pme_active(struct pci_dev *dev, bool enable)
|
||||
mutex_lock(&pci_pme_list_mutex);
|
||||
list_add(&pme_dev->list, &pci_pme_list);
|
||||
if (list_is_singular(&pci_pme_list))
|
||||
schedule_delayed_work(&pci_pme_work,
|
||||
msecs_to_jiffies(PME_TIMEOUT));
|
||||
queue_delayed_work(system_freezable_wq,
|
||||
&pci_pme_work,
|
||||
msecs_to_jiffies(PME_TIMEOUT));
|
||||
mutex_unlock(&pci_pme_list_mutex);
|
||||
} else {
|
||||
mutex_lock(&pci_pme_list_mutex);
|
||||
@@ -3363,7 +3366,7 @@ unsigned long __weak pci_address_to_pio(phys_addr_t address)
|
||||
* Only architectures that have memory mapped IO functions defined
|
||||
* (and the PCI_IOBASE value defined) should call this function.
|
||||
*/
|
||||
int __weak pci_remap_iospace(const struct resource *res, phys_addr_t phys_addr)
|
||||
int pci_remap_iospace(const struct resource *res, phys_addr_t phys_addr)
|
||||
{
|
||||
#if defined(PCI_IOBASE) && defined(CONFIG_MMU)
|
||||
unsigned long vaddr = (unsigned long)PCI_IOBASE + res->start;
|
||||
@@ -3383,6 +3386,7 @@ int __weak pci_remap_iospace(const struct resource *res, phys_addr_t phys_addr)
|
||||
return -ENODEV;
|
||||
#endif
|
||||
}
|
||||
EXPORT_SYMBOL(pci_remap_iospace);
|
||||
|
||||
/**
|
||||
* pci_unmap_iospace - Unmap the memory mapped I/O space
|
||||
@@ -3400,6 +3404,89 @@ void pci_unmap_iospace(struct resource *res)
|
||||
unmap_kernel_range(vaddr, resource_size(res));
|
||||
#endif
|
||||
}
|
||||
EXPORT_SYMBOL(pci_unmap_iospace);
|
||||
|
||||
/**
|
||||
* devm_pci_remap_cfgspace - Managed pci_remap_cfgspace()
|
||||
* @dev: Generic device to remap IO address for
|
||||
* @offset: Resource address to map
|
||||
* @size: Size of map
|
||||
*
|
||||
* Managed pci_remap_cfgspace(). Map is automatically unmapped on driver
|
||||
* detach.
|
||||
*/
|
||||
void __iomem *devm_pci_remap_cfgspace(struct device *dev,
|
||||
resource_size_t offset,
|
||||
resource_size_t size)
|
||||
{
|
||||
void __iomem **ptr, *addr;
|
||||
|
||||
ptr = devres_alloc(devm_ioremap_release, sizeof(*ptr), GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return NULL;
|
||||
|
||||
addr = pci_remap_cfgspace(offset, size);
|
||||
if (addr) {
|
||||
*ptr = addr;
|
||||
devres_add(dev, ptr);
|
||||
} else
|
||||
devres_free(ptr);
|
||||
|
||||
return addr;
|
||||
}
|
||||
EXPORT_SYMBOL(devm_pci_remap_cfgspace);
|
||||
|
||||
/**
|
||||
* devm_pci_remap_cfg_resource - check, request region and ioremap cfg resource
|
||||
* @dev: generic device to handle the resource for
|
||||
* @res: configuration space resource to be handled
|
||||
*
|
||||
* Checks that a resource is a valid memory region, requests the memory
|
||||
* region and ioremaps with pci_remap_cfgspace() API that ensures the
|
||||
* proper PCI configuration space memory attributes are guaranteed.
|
||||
*
|
||||
* All operations are managed and will be undone on driver detach.
|
||||
*
|
||||
* Returns a pointer to the remapped memory or an ERR_PTR() encoded error code
|
||||
* on failure. Usage example:
|
||||
*
|
||||
* res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
* base = devm_pci_remap_cfg_resource(&pdev->dev, res);
|
||||
* if (IS_ERR(base))
|
||||
* return PTR_ERR(base);
|
||||
*/
|
||||
void __iomem *devm_pci_remap_cfg_resource(struct device *dev,
|
||||
struct resource *res)
|
||||
{
|
||||
resource_size_t size;
|
||||
const char *name;
|
||||
void __iomem *dest_ptr;
|
||||
|
||||
BUG_ON(!dev);
|
||||
|
||||
if (!res || resource_type(res) != IORESOURCE_MEM) {
|
||||
dev_err(dev, "invalid resource\n");
|
||||
return IOMEM_ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
size = resource_size(res);
|
||||
name = res->name ?: dev_name(dev);
|
||||
|
||||
if (!devm_request_mem_region(dev, res->start, size, name)) {
|
||||
dev_err(dev, "can't request region for resource %pR\n", res);
|
||||
return IOMEM_ERR_PTR(-EBUSY);
|
||||
}
|
||||
|
||||
dest_ptr = devm_pci_remap_cfgspace(dev, res->start, size);
|
||||
if (!dest_ptr) {
|
||||
dev_err(dev, "ioremap failed for resource %pR\n", res);
|
||||
devm_release_mem_region(dev, res->start, size);
|
||||
dest_ptr = IOMEM_ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
return dest_ptr;
|
||||
}
|
||||
EXPORT_SYMBOL(devm_pci_remap_cfg_resource);
|
||||
|
||||
static void __pci_set_master(struct pci_dev *dev, bool enable)
|
||||
{
|
||||
@@ -3773,24 +3860,41 @@ static void pci_flr_wait(struct pci_dev *dev)
|
||||
(i - 1) * 100);
|
||||
}
|
||||
|
||||
static int pcie_flr(struct pci_dev *dev, int probe)
|
||||
/**
|
||||
* pcie_has_flr - check if a device supports function level resets
|
||||
* @dev: device to check
|
||||
*
|
||||
* Returns true if the device advertises support for PCIe function level
|
||||
* resets.
|
||||
*/
|
||||
static bool pcie_has_flr(struct pci_dev *dev)
|
||||
{
|
||||
u32 cap;
|
||||
|
||||
if (dev->dev_flags & PCI_DEV_FLAGS_NO_FLR_RESET)
|
||||
return false;
|
||||
|
||||
pcie_capability_read_dword(dev, PCI_EXP_DEVCAP, &cap);
|
||||
if (!(cap & PCI_EXP_DEVCAP_FLR))
|
||||
return -ENOTTY;
|
||||
|
||||
if (probe)
|
||||
return 0;
|
||||
return cap & PCI_EXP_DEVCAP_FLR;
|
||||
}
|
||||
|
||||
/**
|
||||
* pcie_flr - initiate a PCIe function level reset
|
||||
* @dev: device to reset
|
||||
*
|
||||
* Initiate a function level reset on @dev. The caller should ensure the
|
||||
* device supports FLR before calling this function, e.g. by using the
|
||||
* pcie_has_flr() helper.
|
||||
*/
|
||||
void pcie_flr(struct pci_dev *dev)
|
||||
{
|
||||
if (!pci_wait_for_pending_transaction(dev))
|
||||
dev_err(&dev->dev, "timed out waiting for pending transaction; performing function level reset anyway\n");
|
||||
|
||||
pcie_capability_set_word(dev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_BCR_FLR);
|
||||
pci_flr_wait(dev);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pcie_flr);
|
||||
|
||||
static int pci_af_flr(struct pci_dev *dev, int probe)
|
||||
{
|
||||
@@ -3801,6 +3905,9 @@ static int pci_af_flr(struct pci_dev *dev, int probe)
|
||||
if (!pos)
|
||||
return -ENOTTY;
|
||||
|
||||
if (dev->dev_flags & PCI_DEV_FLAGS_NO_FLR_RESET)
|
||||
return -ENOTTY;
|
||||
|
||||
pci_read_config_byte(dev, pos + PCI_AF_CAP, &cap);
|
||||
if (!(cap & PCI_AF_CAP_TP) || !(cap & PCI_AF_CAP_FLR))
|
||||
return -ENOTTY;
|
||||
@@ -3971,9 +4078,12 @@ static int __pci_dev_reset(struct pci_dev *dev, int probe)
|
||||
if (rc != -ENOTTY)
|
||||
goto done;
|
||||
|
||||
rc = pcie_flr(dev, probe);
|
||||
if (rc != -ENOTTY)
|
||||
if (pcie_has_flr(dev)) {
|
||||
if (!probe)
|
||||
pcie_flr(dev);
|
||||
rc = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
rc = pci_af_flr(dev, probe);
|
||||
if (rc != -ENOTTY)
|
||||
@@ -4932,6 +5042,8 @@ bool pci_device_is_present(struct pci_dev *pdev)
|
||||
{
|
||||
u32 v;
|
||||
|
||||
if (pci_dev_is_disconnected(pdev))
|
||||
return false;
|
||||
return pci_bus_read_dev_vendor_id(pdev->bus, pdev->devfn, &v, 0);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_device_is_present);
|
||||
@@ -4947,6 +5059,11 @@ void pci_ignore_hotplug(struct pci_dev *dev)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_ignore_hotplug);
|
||||
|
||||
resource_size_t __weak pcibios_default_alignment(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define RESOURCE_ALIGNMENT_PARAM_SIZE COMMAND_LINE_SIZE
|
||||
static char resource_alignment_param[RESOURCE_ALIGNMENT_PARAM_SIZE] = {0};
|
||||
static DEFINE_SPINLOCK(resource_alignment_lock);
|
||||
@@ -4954,22 +5071,25 @@ static DEFINE_SPINLOCK(resource_alignment_lock);
|
||||
/**
|
||||
* pci_specified_resource_alignment - get resource alignment specified by user.
|
||||
* @dev: the PCI device to get
|
||||
* @resize: whether or not to change resources' size when reassigning alignment
|
||||
*
|
||||
* RETURNS: Resource alignment if it is specified.
|
||||
* Zero if it is not specified.
|
||||
*/
|
||||
static resource_size_t pci_specified_resource_alignment(struct pci_dev *dev)
|
||||
static resource_size_t pci_specified_resource_alignment(struct pci_dev *dev,
|
||||
bool *resize)
|
||||
{
|
||||
int seg, bus, slot, func, align_order, count;
|
||||
unsigned short vendor, device, subsystem_vendor, subsystem_device;
|
||||
resource_size_t align = 0;
|
||||
resource_size_t align = pcibios_default_alignment();
|
||||
char *p;
|
||||
|
||||
spin_lock(&resource_alignment_lock);
|
||||
p = resource_alignment_param;
|
||||
if (!*p)
|
||||
if (!*p && !align)
|
||||
goto out;
|
||||
if (pci_has_flag(PCI_PROBE_ONLY)) {
|
||||
align = 0;
|
||||
pr_info_once("PCI: Ignoring requested alignments (PCI_PROBE_ONLY)\n");
|
||||
goto out;
|
||||
}
|
||||
@@ -4999,6 +5119,7 @@ static resource_size_t pci_specified_resource_alignment(struct pci_dev *dev)
|
||||
(!device || (device == dev->device)) &&
|
||||
(!subsystem_vendor || (subsystem_vendor == dev->subsystem_vendor)) &&
|
||||
(!subsystem_device || (subsystem_device == dev->subsystem_device))) {
|
||||
*resize = true;
|
||||
if (align_order == -1)
|
||||
align = PAGE_SIZE;
|
||||
else
|
||||
@@ -5024,6 +5145,7 @@ static resource_size_t pci_specified_resource_alignment(struct pci_dev *dev)
|
||||
bus == dev->bus->number &&
|
||||
slot == PCI_SLOT(dev->devfn) &&
|
||||
func == PCI_FUNC(dev->devfn)) {
|
||||
*resize = true;
|
||||
if (align_order == -1)
|
||||
align = PAGE_SIZE;
|
||||
else
|
||||
@@ -5043,6 +5165,68 @@ out:
|
||||
return align;
|
||||
}
|
||||
|
||||
static void pci_request_resource_alignment(struct pci_dev *dev, int bar,
|
||||
resource_size_t align, bool resize)
|
||||
{
|
||||
struct resource *r = &dev->resource[bar];
|
||||
resource_size_t size;
|
||||
|
||||
if (!(r->flags & IORESOURCE_MEM))
|
||||
return;
|
||||
|
||||
if (r->flags & IORESOURCE_PCI_FIXED) {
|
||||
dev_info(&dev->dev, "BAR%d %pR: ignoring requested alignment %#llx\n",
|
||||
bar, r, (unsigned long long)align);
|
||||
return;
|
||||
}
|
||||
|
||||
size = resource_size(r);
|
||||
if (size >= align)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Increase the alignment of the resource. There are two ways we
|
||||
* can do this:
|
||||
*
|
||||
* 1) Increase the size of the resource. BARs are aligned on their
|
||||
* size, so when we reallocate space for this resource, we'll
|
||||
* allocate it with the larger alignment. This also prevents
|
||||
* assignment of any other BARs inside the alignment region, so
|
||||
* if we're requesting page alignment, this means no other BARs
|
||||
* will share the page.
|
||||
*
|
||||
* The disadvantage is that this makes the resource larger than
|
||||
* the hardware BAR, which may break drivers that compute things
|
||||
* based on the resource size, e.g., to find registers at a
|
||||
* fixed offset before the end of the BAR.
|
||||
*
|
||||
* 2) Retain the resource size, but use IORESOURCE_STARTALIGN and
|
||||
* set r->start to the desired alignment. By itself this
|
||||
* doesn't prevent other BARs being put inside the alignment
|
||||
* region, but if we realign *every* resource of every device in
|
||||
* the system, none of them will share an alignment region.
|
||||
*
|
||||
* When the user has requested alignment for only some devices via
|
||||
* the "pci=resource_alignment" argument, "resize" is true and we
|
||||
* use the first method. Otherwise we assume we're aligning all
|
||||
* devices and we use the second.
|
||||
*/
|
||||
|
||||
dev_info(&dev->dev, "BAR%d %pR: requesting alignment to %#llx\n",
|
||||
bar, r, (unsigned long long)align);
|
||||
|
||||
if (resize) {
|
||||
r->start = 0;
|
||||
r->end = align - 1;
|
||||
} else {
|
||||
r->flags &= ~IORESOURCE_SIZEALIGN;
|
||||
r->flags |= IORESOURCE_STARTALIGN;
|
||||
r->start = align;
|
||||
r->end = r->start + size - 1;
|
||||
}
|
||||
r->flags |= IORESOURCE_UNSET;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function disables memory decoding and releases memory resources
|
||||
* of the device specified by kernel's boot parameter 'pci=resource_alignment='.
|
||||
@@ -5054,8 +5238,9 @@ void pci_reassigndev_resource_alignment(struct pci_dev *dev)
|
||||
{
|
||||
int i;
|
||||
struct resource *r;
|
||||
resource_size_t align, size;
|
||||
resource_size_t align;
|
||||
u16 command;
|
||||
bool resize = false;
|
||||
|
||||
/*
|
||||
* VF BARs are read-only zero according to SR-IOV spec r1.1, sec
|
||||
@@ -5067,7 +5252,7 @@ void pci_reassigndev_resource_alignment(struct pci_dev *dev)
|
||||
return;
|
||||
|
||||
/* check if specified PCI is target device to reassign */
|
||||
align = pci_specified_resource_alignment(dev);
|
||||
align = pci_specified_resource_alignment(dev, &resize);
|
||||
if (!align)
|
||||
return;
|
||||
|
||||
@@ -5084,28 +5269,11 @@ void pci_reassigndev_resource_alignment(struct pci_dev *dev)
|
||||
command &= ~PCI_COMMAND_MEMORY;
|
||||
pci_write_config_word(dev, PCI_COMMAND, command);
|
||||
|
||||
for (i = 0; i < PCI_BRIDGE_RESOURCES; i++) {
|
||||
r = &dev->resource[i];
|
||||
if (!(r->flags & IORESOURCE_MEM))
|
||||
continue;
|
||||
if (r->flags & IORESOURCE_PCI_FIXED) {
|
||||
dev_info(&dev->dev, "Ignoring requested alignment for BAR%d: %pR\n",
|
||||
i, r);
|
||||
continue;
|
||||
}
|
||||
for (i = 0; i <= PCI_ROM_RESOURCE; i++)
|
||||
pci_request_resource_alignment(dev, i, align, resize);
|
||||
|
||||
size = resource_size(r);
|
||||
if (size < align) {
|
||||
size = align;
|
||||
dev_info(&dev->dev,
|
||||
"Rounding up size of resource #%d to %#llx.\n",
|
||||
i, (unsigned long long)size);
|
||||
}
|
||||
r->flags |= IORESOURCE_UNSET;
|
||||
r->end = size - 1;
|
||||
r->start = 0;
|
||||
}
|
||||
/* Need to disable bridge's resource window,
|
||||
/*
|
||||
* Need to disable bridge's resource window,
|
||||
* to enable the kernel to reassign new resource
|
||||
* window later on.
|
||||
*/
|
||||
|
@@ -23,14 +23,14 @@ void pci_create_firmware_label_files(struct pci_dev *pdev);
|
||||
void pci_remove_firmware_label_files(struct pci_dev *pdev);
|
||||
#endif
|
||||
void pci_cleanup_rom(struct pci_dev *dev);
|
||||
#ifdef HAVE_PCI_MMAP
|
||||
|
||||
enum pci_mmap_api {
|
||||
PCI_MMAP_SYSFS, /* mmap on /sys/bus/pci/devices/<BDF>/resource<N> */
|
||||
PCI_MMAP_PROCFS /* mmap on /proc/bus/pci/<BDF> */
|
||||
};
|
||||
int pci_mmap_fits(struct pci_dev *pdev, int resno, struct vm_area_struct *vmai,
|
||||
enum pci_mmap_api mmap_api);
|
||||
#endif
|
||||
|
||||
int pci_probe_reset_function(struct pci_dev *dev);
|
||||
|
||||
/**
|
||||
@@ -274,8 +274,23 @@ struct pci_sriov {
|
||||
struct pci_dev *self; /* this PF */
|
||||
struct mutex lock; /* lock for setting sriov_numvfs in sysfs */
|
||||
resource_size_t barsz[PCI_SRIOV_NUM_BARS]; /* VF BAR size */
|
||||
bool drivers_autoprobe; /* auto probing of VFs by driver */
|
||||
};
|
||||
|
||||
/* pci_dev priv_flags */
|
||||
#define PCI_DEV_DISCONNECTED 0
|
||||
|
||||
static inline int pci_dev_set_disconnected(struct pci_dev *dev, void *unused)
|
||||
{
|
||||
set_bit(PCI_DEV_DISCONNECTED, &dev->priv_flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline bool pci_dev_is_disconnected(const struct pci_dev *dev)
|
||||
{
|
||||
return test_bit(PCI_DEV_DISCONNECTED, &dev->priv_flags);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PCI_ATS
|
||||
void pci_restore_ats_state(struct pci_dev *dev);
|
||||
#else
|
||||
|
@@ -14,6 +14,7 @@
|
||||
#include <linux/init.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/pcieport_if.h>
|
||||
#include "../pci.h"
|
||||
|
||||
struct dpc_dev {
|
||||
struct pcie_device *dev;
|
||||
@@ -66,6 +67,10 @@ static void interrupt_event_handler(struct work_struct *work)
|
||||
list_for_each_entry_safe_reverse(dev, temp, &parent->devices,
|
||||
bus_list) {
|
||||
pci_dev_get(dev);
|
||||
pci_dev_set_disconnected(dev, NULL);
|
||||
if (pci_has_subordinate(dev))
|
||||
pci_walk_bus(dev->subordinate,
|
||||
pci_dev_set_disconnected, NULL);
|
||||
pci_stop_and_remove_bus_device(dev);
|
||||
pci_dev_put(dev);
|
||||
}
|
||||
|
@@ -175,7 +175,7 @@ static inline unsigned long decode_bar(struct pci_dev *dev, u32 bar)
|
||||
int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type,
|
||||
struct resource *res, unsigned int pos)
|
||||
{
|
||||
u32 l, sz, mask;
|
||||
u32 l = 0, sz = 0, mask;
|
||||
u64 l64, sz64, mask64;
|
||||
u16 orig_cmd;
|
||||
struct pci_bus_region region, inverted_region;
|
||||
@@ -231,7 +231,7 @@ int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type,
|
||||
res->flags |= IORESOURCE_ROM_ENABLE;
|
||||
l64 = l & PCI_ROM_ADDRESS_MASK;
|
||||
sz64 = sz & PCI_ROM_ADDRESS_MASK;
|
||||
mask64 = (u32)PCI_ROM_ADDRESS_MASK;
|
||||
mask64 = PCI_ROM_ADDRESS_MASK;
|
||||
}
|
||||
|
||||
if (res->flags & IORESOURCE_MEM_64) {
|
||||
|
@@ -202,6 +202,8 @@ static long proc_bus_pci_ioctl(struct file *file, unsigned int cmd,
|
||||
|
||||
#ifdef HAVE_PCI_MMAP
|
||||
case PCIIOC_MMAP_IS_IO:
|
||||
if (!arch_can_pci_mmap_io())
|
||||
return -EINVAL;
|
||||
fpriv->mmap_state = pci_mmap_io;
|
||||
break;
|
||||
|
||||
@@ -210,14 +212,15 @@ static long proc_bus_pci_ioctl(struct file *file, unsigned int cmd,
|
||||
break;
|
||||
|
||||
case PCIIOC_WRITE_COMBINE:
|
||||
if (arg)
|
||||
fpriv->write_combine = 1;
|
||||
else
|
||||
fpriv->write_combine = 0;
|
||||
break;
|
||||
|
||||
if (arch_can_pci_mmap_wc()) {
|
||||
if (arg)
|
||||
fpriv->write_combine = 1;
|
||||
else
|
||||
fpriv->write_combine = 0;
|
||||
break;
|
||||
}
|
||||
/* If arch decided it can't, fall through... */
|
||||
#endif /* HAVE_PCI_MMAP */
|
||||
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
@@ -231,25 +234,35 @@ static int proc_bus_pci_mmap(struct file *file, struct vm_area_struct *vma)
|
||||
{
|
||||
struct pci_dev *dev = PDE_DATA(file_inode(file));
|
||||
struct pci_filp_private *fpriv = file->private_data;
|
||||
int i, ret, write_combine;
|
||||
int i, ret, write_combine = 0, res_bit = IORESOURCE_MEM;
|
||||
|
||||
if (!capable(CAP_SYS_RAWIO))
|
||||
return -EPERM;
|
||||
|
||||
if (fpriv->mmap_state == pci_mmap_io) {
|
||||
if (!arch_can_pci_mmap_io())
|
||||
return -EINVAL;
|
||||
res_bit = IORESOURCE_IO;
|
||||
}
|
||||
|
||||
/* Make sure the caller is mapping a real resource for this device */
|
||||
for (i = 0; i < PCI_ROM_RESOURCE; i++) {
|
||||
if (pci_mmap_fits(dev, i, vma, PCI_MMAP_PROCFS))
|
||||
if (dev->resource[i].flags & res_bit &&
|
||||
pci_mmap_fits(dev, i, vma, PCI_MMAP_PROCFS))
|
||||
break;
|
||||
}
|
||||
|
||||
if (i >= PCI_ROM_RESOURCE)
|
||||
return -ENODEV;
|
||||
|
||||
if (fpriv->mmap_state == pci_mmap_mem)
|
||||
write_combine = fpriv->write_combine;
|
||||
else
|
||||
write_combine = 0;
|
||||
ret = pci_mmap_page_range(dev, vma,
|
||||
if (fpriv->mmap_state == pci_mmap_mem &&
|
||||
fpriv->write_combine) {
|
||||
if (dev->resource[i].flags & IORESOURCE_PREFETCH)
|
||||
write_combine = 1;
|
||||
else
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = pci_mmap_page_range(dev, i, vma,
|
||||
fpriv->mmap_state, write_combine);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
@@ -1685,6 +1685,29 @@ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x260a, quirk_intel_pcie_pm);
|
||||
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x260b, quirk_intel_pcie_pm);
|
||||
|
||||
#ifdef CONFIG_X86_IO_APIC
|
||||
static int dmi_disable_ioapicreroute(const struct dmi_system_id *d)
|
||||
{
|
||||
noioapicreroute = 1;
|
||||
pr_info("%s detected: disable boot interrupt reroute\n", d->ident);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct dmi_system_id boot_interrupt_dmi_table[] = {
|
||||
/*
|
||||
* Systems to exclude from boot interrupt reroute quirks
|
||||
*/
|
||||
{
|
||||
.callback = dmi_disable_ioapicreroute,
|
||||
.ident = "ASUSTek Computer INC. M2N-LR",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTek Computer INC."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "M2N-LR"),
|
||||
},
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
/*
|
||||
* Boot interrupts on some chipsets cannot be turned off. For these chipsets,
|
||||
* remap the original interrupt in the linux kernel to the boot interrupt, so
|
||||
@@ -1693,6 +1716,7 @@ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x260b, quirk_intel_pcie_pm);
|
||||
*/
|
||||
static void quirk_reroute_to_boot_interrupts_intel(struct pci_dev *dev)
|
||||
{
|
||||
dmi_check_system(boot_interrupt_dmi_table);
|
||||
if (noioapicquirk || noioapicreroute)
|
||||
return;
|
||||
|
||||
@@ -3642,19 +3666,11 @@ static int reset_intel_82599_sfp_virtfn(struct pci_dev *dev, int probe)
|
||||
*
|
||||
* The 82599 supports FLR on VFs, but FLR support is reported only
|
||||
* in the PF DEVCAP (sec 9.3.10.4), not in the VF DEVCAP (sec 9.5).
|
||||
* Therefore, we can't use pcie_flr(), which checks the VF DEVCAP.
|
||||
* Thus we must call pcie_flr() directly without first checking if it is
|
||||
* supported.
|
||||
*/
|
||||
|
||||
if (probe)
|
||||
return 0;
|
||||
|
||||
if (!pci_wait_for_pending_transaction(dev))
|
||||
dev_err(&dev->dev, "transaction is not cleared; proceeding with reset anyway\n");
|
||||
|
||||
pcie_capability_set_word(dev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_BCR_FLR);
|
||||
|
||||
msleep(100);
|
||||
|
||||
if (!probe)
|
||||
pcie_flr(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -3759,20 +3775,7 @@ static int reset_chelsio_generic_dev(struct pci_dev *dev, int probe)
|
||||
PCI_MSIX_FLAGS_ENABLE |
|
||||
PCI_MSIX_FLAGS_MASKALL);
|
||||
|
||||
/*
|
||||
* Start of pcie_flr() code sequence. This reset code is a copy of
|
||||
* the guts of pcie_flr() because that's not an exported function.
|
||||
*/
|
||||
|
||||
if (!pci_wait_for_pending_transaction(dev))
|
||||
dev_err(&dev->dev, "transaction is not cleared; proceeding with reset anyway\n");
|
||||
|
||||
pcie_capability_set_word(dev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_BCR_FLR);
|
||||
msleep(100);
|
||||
|
||||
/*
|
||||
* End of pcie_flr() code sequence.
|
||||
*/
|
||||
pcie_flr(dev);
|
||||
|
||||
/*
|
||||
* Restore the configuration information (BAR values, etc.) including
|
||||
@@ -3939,6 +3942,8 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ASMEDIA, 0x1080,
|
||||
DECLARE_PCI_FIXUP_HEADER(0x10e3, 0x8113, quirk_use_pcie_bridge_dma_alias);
|
||||
/* ITE 8892, https://bugzilla.kernel.org/show_bug.cgi?id=73551 */
|
||||
DECLARE_PCI_FIXUP_HEADER(0x1283, 0x8892, quirk_use_pcie_bridge_dma_alias);
|
||||
/* ITE 8893 has the same problem as the 8892 */
|
||||
DECLARE_PCI_FIXUP_HEADER(0x1283, 0x8893, quirk_use_pcie_bridge_dma_alias);
|
||||
/* Intel 82801, https://bugzilla.kernel.org/show_bug.cgi?id=44881#c49 */
|
||||
DECLARE_PCI_FIXUP_HEADER(0x8086, 0x244e, quirk_use_pcie_bridge_dma_alias);
|
||||
|
||||
@@ -3957,6 +3962,20 @@ static void quirk_mic_x200_dma_alias(struct pci_dev *pdev)
|
||||
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2260, quirk_mic_x200_dma_alias);
|
||||
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2264, quirk_mic_x200_dma_alias);
|
||||
|
||||
/*
|
||||
* The IOMMU and interrupt controller on Broadcom Vulcan/Cavium ThunderX2 are
|
||||
* associated not at the root bus, but at a bridge below. This quirk avoids
|
||||
* generating invalid DMA aliases.
|
||||
*/
|
||||
static void quirk_bridge_cavm_thrx2_pcie_root(struct pci_dev *pdev)
|
||||
{
|
||||
pdev->dev_flags |= PCI_DEV_FLAGS_BRIDGE_XLATE_ROOT;
|
||||
}
|
||||
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_BROADCOM, 0x9000,
|
||||
quirk_bridge_cavm_thrx2_pcie_root);
|
||||
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_BROADCOM, 0x9084,
|
||||
quirk_bridge_cavm_thrx2_pcie_root);
|
||||
|
||||
/*
|
||||
* Intersil/Techwell TW686[4589]-based video capture cards have an empty (zero)
|
||||
* class code. Fix it.
|
||||
@@ -4095,6 +4114,9 @@ static int pci_quirk_cavium_acs(struct pci_dev *dev, u16 acs_flags)
|
||||
acs_flags &= ~(PCI_ACS_SV | PCI_ACS_TB | PCI_ACS_RR |
|
||||
PCI_ACS_CR | PCI_ACS_UF | PCI_ACS_DT);
|
||||
|
||||
if (!((dev->device >= 0xa000) && (dev->device <= 0xa0ff)))
|
||||
return -ENOTTY;
|
||||
|
||||
return acs_flags ? 0 : 1;
|
||||
}
|
||||
|
||||
@@ -4634,3 +4656,11 @@ DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x2030, quirk_no_aersid);
|
||||
DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x2031, quirk_no_aersid);
|
||||
DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x2032, quirk_no_aersid);
|
||||
DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x2033, quirk_no_aersid);
|
||||
|
||||
/* FLR may cause some 82579 devices to hang. */
|
||||
static void quirk_intel_no_flr(struct pci_dev *dev)
|
||||
{
|
||||
dev->dev_flags |= PCI_DEV_FLAGS_NO_FLR_RESET;
|
||||
}
|
||||
DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x1502, quirk_intel_no_flr);
|
||||
DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x1503, quirk_intel_no_flr);
|
||||
|
@@ -60,6 +60,10 @@ int pci_for_each_dma_alias(struct pci_dev *pdev,
|
||||
|
||||
tmp = bus->self;
|
||||
|
||||
/* stop at bridge where translation unit is associated */
|
||||
if (tmp->dev_flags & PCI_DEV_FLAGS_BRIDGE_XLATE_ROOT)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* PCIe-to-PCI/X bridges alias transactions from downstream
|
||||
* devices using the subordinate bus number (PCI Express to
|
||||
|
@@ -1066,10 +1066,10 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask,
|
||||
r->flags = 0;
|
||||
continue;
|
||||
}
|
||||
size += r_size;
|
||||
size += max(r_size, align);
|
||||
/* Exclude ranges with size > align from
|
||||
calculation of the alignment. */
|
||||
if (r_size == align)
|
||||
if (r_size <= align)
|
||||
aligns[order] += align;
|
||||
if (order > max_order)
|
||||
max_order = order;
|
||||
|
@@ -63,7 +63,7 @@ static void pci_std_update_resource(struct pci_dev *dev, int resno)
|
||||
mask = (u32)PCI_BASE_ADDRESS_IO_MASK;
|
||||
new |= res->flags & ~PCI_BASE_ADDRESS_IO_MASK;
|
||||
} else if (resno == PCI_ROM_RESOURCE) {
|
||||
mask = (u32)PCI_ROM_ADDRESS_MASK;
|
||||
mask = PCI_ROM_ADDRESS_MASK;
|
||||
} else {
|
||||
mask = (u32)PCI_BASE_ADDRESS_MEM_MASK;
|
||||
new |= res->flags & ~PCI_BASE_ADDRESS_MEM_MASK;
|
||||
|
13
drivers/pci/switch/Kconfig
Normal file
13
drivers/pci/switch/Kconfig
Normal file
@@ -0,0 +1,13 @@
|
||||
menu "PCI switch controller drivers"
|
||||
depends on PCI
|
||||
|
||||
config PCI_SW_SWITCHTEC
|
||||
tristate "MicroSemi Switchtec PCIe Switch Management Driver"
|
||||
help
|
||||
Enables support for the management interface for the MicroSemi
|
||||
Switchtec series of PCIe switches. Supports userspace access
|
||||
to submit MRPC commands to the switch via /dev/switchtecX
|
||||
devices. See <file:Documentation/switchtec.txt> for more
|
||||
information.
|
||||
|
||||
endmenu
|
1
drivers/pci/switch/Makefile
Normal file
1
drivers/pci/switch/Makefile
Normal file
@@ -0,0 +1 @@
|
||||
obj-$(CONFIG_PCI_SW_SWITCHTEC) += switchtec.o
|
1600
drivers/pci/switch/switchtec.c
Normal file
1600
drivers/pci/switch/switchtec.c
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user